Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adds CUSTOM_TEXT block + guide to create custom blocks #1505

Merged
merged 11 commits into from
Jan 20, 2023
99 changes: 99 additions & 0 deletions guides/CUSTOM_BLOCK.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# MyMeta - Custom Blocks

This shall serve as a minimal guide towards developing custom blocks and linking your MyMeta player profiles, guild profiles and/or personal dashboards with external applications or APIs.

## Pre-requesites

- The MyMeta frontend codebase ([`packages/web`](https://github.com/MetaFam/TheGame/blob/develop/packages/web)) uses NextJS, so a basic understanding of NextJS architecture is a must.
- Some important files that you must understand before proceeding to develop your own custom blocks:
1. Dashboard Page - [`pages/dashboard.tsx`](https://github.com/MetaFam/TheGame/blob/develop/packages/web/pages/dashboard.tsx)
2. Player Profile Page- [`pages/player/[username].tsx`](https://github.com/MetaFam/TheGame/blob/develop/packages/web/pages/player/[username.tsx])
3. Guild Profile Page - [`pages/guild/[guildname]/index.tsx`](https://github.com/MetaFam/TheGame/blob/develop/packages/web/pages/guild/%5Bguildname%5D/index.tsx)
4. Common EditableGridLayout Component - [`components/EditableGridLayout.tsx`](https://github.com/MetaFam/TheGame/blob/develop/packages/web/components/EditableGridLayout.tsx)
- This component is the basis for building the layout of blocks.
- One of the props for `EditableGridLayout`, is the `displayComponent` which renders the actual block. The different display components for different pages are given below.
5. Dashboard Display Component - [`components/Dashboard/DashboardSection.tsx`](https://github.com/MetaFam/TheGame/blob/develop/packages/web/components/Dashboard/DashboardSection.tsx)
- The `displayComponent` for `pages/dashboard.tsx`
6. Player Profile Display Component - [`components/Player/PlayerSection.tsx`](https://github.com/MetaFam/TheGame/blob/develop/packages/web/components/Player/PlayerSection.tsx)
- The `displayComponent` for `pages/player/[username].tsx`
7. Guild Profile Display Component - [`components/Guild/GuildSection.tsx`](https://github.com/MetaFam/TheGame/blob/develop/packages/web/components/Guild/GuildSection.tsx)
- The `displayComponent` for `pages/guild/[guildname]/index.tsx`
8. AddBoxSection Component - [`components/Section/AddBoxSection.tsx`](https://github.com/MetaFam/TheGame/blob/develop/packages/web/components/Section/AddBoxSection.tsx)
- Another key component in `EditableGridLayout`.
- This renders the UI for the user to customize and create a new block on their `EditableGridLayout`.
9. Box Types Utils - [`utils/boxTypes.ts`](https://github.com/MetaFam/TheGame/blob/develop/packages/web/utils/boxTypes.ts)
- This file contains all the basic utilities methods and constants required for the functionality of custom blocks

## Steps

> We shall be creating a simple block with a custom title and content. Similar steps can be followed to create more complex custom blocks.

### Step 1: Create a new BoxType

- Add a new `BoxType` in `utils/boxTypes.ts`.

```
CUSTOM_TEXT: 'custom-text'
```

- Refer to the commit [830bb74](https://github.com/MetaFam/TheGame/commit/830bb7423be0d1f9af7d871d50e41ec9d3695d37) for the code changes

### Step 2: Define the structure of metadata

- Each block can use a custom metadata with the type of `BoxMetadata`, and we must define the type for our custom block. For this usecase we shall assume that it requires two text parameters `title` and `content`.

### Step 3: Create the component for this boxType

- Now create a new component `CustomTextSection` with `title` and `content` as props.
- Refer to the commit [e00d05b](https://github.com/MetaFam/TheGame/commit/e00d05be92bbebbd24743143386d41a581384901) for the code changes

### Step 4: Create the metadata editor component

- Next, we can create a component `CustomTextSectionMetadata` which has two props `metadata` & `setMetadata` which shall be used in `AddBoxSection` for the user to create the metadata required for `CustomTextSection`.
- Refer to the commit [e00d05b](https://github.com/MetaFam/TheGame/commit/e00d05be92bbebbd24743143386d41a581384901) for the code changes

### Step 5: Add the render option in the display components

- Add an extra case in the switch statements in `DashboardSection`, `PlayerSection` and `GuildSection` components to handle the metadata and render the `CustomTextSection`

```
case BoxTypes.CUSTOM_TEXT: {
const { title, content } = metadata ?? {};
return title && content ? (
<CustomTextSection {...{ title, content }} />
) : null;
}
```

- In this case, our custom text can be added to all three pages, but if your custom box is relavant only to one of the pages then add it only to that particular display component.
- Refer to the commit [f1ba43d](https://github.com/MetaFam/TheGame/commit/f1ba43dd29195d4032a2c34cd668e2ba7c38d275) for the code changes

### Step 6: Add our boxType to the supported list of boxes

- Add `BoxTypes.CUSTOM_TEXT` to the list of `ALL_BOXES` to the `config.ts` of each page
- `components/Player/Section/config.ts` for player profiles
- `components/Guild/Section/config.ts` for guild profiles
- `components/Dashboard/config.ts` for dashboard
- Refer to the commit [6307ef1](https://github.com/MetaFam/TheGame/commit/6307ef135805a73f0380c172ec3d276ef561f23f) for the code changes

### Step 7: Add our metadata editor component

- Add the `CustomTextSectionMetadata` editor component to the `AddBoxSection` component
- Refer to the commit [bb9e74a](https://github.com/MetaFam/TheGame/commit/bb9e74afc78e0f29078a10635d53632a6cccc47f) for the code changes

### Step 7: Add specific config/helpers for this boxType

- Add a default box height in `utils/layoutHelpers.ts` under `DEFAULT_BOX_HEIGHTS`.
- In this case, the custom text box can be added multiple times with different metadata, therefore we shall add it the list of `MULTIPLE_ALLOWED_BOXES` as well.
- If needed we may need to edit `isResizableBox` if this block is resizable, although we leave it untouched for our usecase. See `components/Dashboard/Calendar.tsx` for an example of a resizable block.
- Refer to the commit [594d22b](https://github.com/MetaFam/TheGame/commit/594d22baf7a9fc3a6d2415e5d74040db7bc5b89e) for the code changes

## Conclusion

- Hurray! We have successfully added a basic custom block to MyMeta.
- Next step is to create a PR with our `develop` branch.
- Please follow our [`CONTRIBUTING_GUIDE`](https://github.com/MetaFam/TheGame/blob/develop/guides/CONTRIBUTING.md) for complete instructions on contributing to MyMeta.

## Help & Support

- For any queries, please join our discord and go to the `#builders` channel for help!
2 changes: 1 addition & 1 deletion packages/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"@ethersproject/address": "5.6.0",
"bignumber.js": "^9.1.0",
"cids": "1.1.9",
"ethers": "5.6.9",
"ethers": "5.7.0",
dan13ram marked this conversation as resolved.
Show resolved Hide resolved
"imgix-core-js": "2.3.2",
"js-base64": "3.7.2",
"node-fetch": "3.2.1",
Expand Down
26 changes: 16 additions & 10 deletions packages/web/components/Dashboard/Calendar.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Flex, Text } from '@metafam/ds';
import { CONFIG } from 'config';
import React, { useEffect, useState } from 'react';

Expand All @@ -13,15 +14,20 @@ export const Calendar: React.FC = () => {
const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;

return (
<iframe
title="calendar"
src={`https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23ffffff&ctz=${timezone}&showPrint=0&mode=AGENDA&showTitle=0&showNav=1&showTabs=0&showPrint=0&showCalendars=0&src=${CONFIG.calendarId}&color=%23F09300`}
style={{
marginTop: '1rem',
flex: 1,
}}
width="100%"
scrolling="no"
/>
<Flex direction="column" p={6} w="100%" minH="100%">
<Text fontSize="lg" fontWeight="bold" textTransform="uppercase">
Calendar
</Text>
<iframe
title="calendar"
src={`https://calendar.google.com/calendar/embed?height=600&wkst=1&bgcolor=%23ffffff&ctz=${timezone}&showPrint=0&mode=AGENDA&showTitle=0&showNav=1&showTabs=0&showPrint=0&showCalendars=0&src=${CONFIG.calendarId}&color=%23F09300`}
style={{
marginTop: '1rem',
flex: 1,
}}
width="100%"
scrolling="no"
/>
</Flex>
);
};
52 changes: 11 additions & 41 deletions packages/web/components/Dashboard/DashboardSection.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { Flex, IconButton, Text } from '@metafam/ds';
import { Flex, IconButton } from '@metafam/ds';
import { Calendar } from 'components/Dashboard/Calendar';
import { LatestContent } from 'components/Dashboard/LatestContent';
import { Leaderboard } from 'components/Dashboard/Leaderboard';
import { Seed } from 'components/Dashboard/Seed';
import { XP } from 'components/Dashboard/XP';
import { CustomTextSection } from 'components/Section/CustomTextSection';
import { EmbeddedUrl } from 'components/Section/EmbeddedUrlSection';
import { Player } from 'graphql/autogen/types';
import React, { forwardRef } from 'react';
Expand Down Expand Up @@ -46,37 +47,20 @@ const DashboardSectionInner: React.FC<Props> = ({
const { url } = metadata ?? {};
return url ? <EmbeddedUrl {...{ url, editing }} /> : null;
}
case BoxTypes.CUSTOM_TEXT: {
const { title, content } = metadata ?? {};
return title && content ? (
<CustomTextSection {...{ title, content }} />
) : null;
}
default:
return null;
}
};

const getTitle = (type: BoxType, metadata?: BoxMetadata) => {
if (metadata?.title) return metadata?.title.toString();
switch (type) {
case BoxTypes.DASHBOARD_LASTEST_CONTENT:
return 'Latest Content';
case BoxTypes.DASHBOARD_XP_INFO:
return 'XP';
case BoxTypes.DASHBOARD_SEEDS_INFO:
return 'Seed';
case BoxTypes.DASHBOARD_CALENDER:
return 'Calendar';
case BoxTypes.DASHBOARD_LEADERBOARD:
return 'Leaderboard';
case BoxTypes.DASHBOARD_COMPLETED_QUESTS:
return 'Submitted Quests';
case BoxTypes.DASHBOARD_CREATED_QUESTS:
return 'Posted Quest Submissions';
default:
return '';
}
};

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea refactoring this

export const DashboardSection = forwardRef<HTMLDivElement, Props>(
({ metadata, type, player, editing = false, onRemoveBox }, ref) => {
const key = createBoxKey(type, metadata);
const title = getTitle(type, metadata);
if (!player) return null;

return (
Expand All @@ -88,29 +72,15 @@ export const DashboardSection = forwardRef<HTMLDivElement, Props>(
minH="100%"
boxShadow="md"
pos="relative"
padding={type !== BoxTypes.EMBEDDED_URL ? 6 : 0}
paddingRight={
type === BoxTypes.DASHBOARD_LASTEST_CONTENT ? 4 : undefined
}
>
<Flex
display={isBoxResizable(type) ? 'flex' : 'block'}
minH={isBoxResizable(type) ? '100%' : undefined}
overflow={isBoxResizable(type) ? 'hidden' : undefined}
w="100%"
minH="100%"
h="auto"
direction="column"
overflowY={
isBoxResizable(type) && type !== BoxTypes.EMBEDDED_URL
? 'auto'
: 'hidden'
}
overflowX="hidden"
pointerEvents={editing ? 'none' : 'initial'}
>
{title && (
<Text fontSize="lg" fontWeight="bold" textTransform="uppercase">
{title}
</Text>
)}
<DashboardSectionInner
{...{
metadata,
Expand Down
123 changes: 68 additions & 55 deletions packages/web/components/Dashboard/LatestContent.tsx
Original file line number Diff line number Diff line change
@@ -1,66 +1,79 @@
import { Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from '@metafam/ds';
import {
Flex,
Tab,
TabList,
TabPanel,
TabPanels,
Tabs,
Text,
} from '@metafam/ds';
import React from 'react';

import { Listen } from './LatestContentTabs/Listen';
import { Read } from './LatestContentTabs/Read';
import { Watch } from './LatestContentTabs/Watch';

export const LatestContent: React.FC = () => (
<Flex grow={1} w="100%" mt={4} overflowY="hidden">
<Tabs
size="lg"
variant="line"
colorScheme="gray.600"
isFitted
w="100%"
sx={{
p: {
fontSize: 'md',
pb: 2,
mr: 'auto',
},
ul: {
fontSize: 'sm',
pb: 2,
pl: 6,
},
}}
>
<Flex direction="column" w="100%" h="100%">
<TabList borderBottomWidth={0} pr={4} pl={0}>
{['Read', 'Listen', 'Watch'].map((title) => (
<Tab
key={title}
color="gray.600"
_selected={{ color: 'white', borderBottomColor: 'white' }}
_focus={{
boxShadow: 'none',
backgroundColor: 'transparent',
}}
_active={{
boxShadow: 'none',
backgroundColor: 'transparent',
}}
>
{title}
</Tab>
))}
</TabList>
<Flex direction="column" p={6} pr={4} minH="100%" w="100%">
<Text fontSize="lg" fontWeight="bold" textTransform="uppercase">
Latest Content
</Text>
<Flex h="100%" w="100%" mt={4} overflowY="hidden" grow={1}>
<Tabs
size="lg"
variant="line"
colorScheme="gray.600"
isFitted
w="100%"
sx={{
p: {
fontSize: 'md',
pb: 2,
mr: 'auto',
},
ul: {
fontSize: 'sm',
pb: 2,
pl: 6,
},
}}
>
<Flex direction="column" w="100%" h="100%">
<TabList borderBottomWidth={0} pr={4} pl={0}>
{['Read', 'Listen', 'Watch'].map((title) => (
<Tab
key={title}
color="gray.600"
_selected={{ color: 'white', borderBottomColor: 'white' }}
_focus={{
boxShadow: 'none',
backgroundColor: 'transparent',
}}
_active={{
boxShadow: 'none',
backgroundColor: 'transparent',
}}
>
{title}
</Tab>
))}
</TabList>

<Flex grow={1} w="100%" overflowY="auto" mt={1}>
<TabPanels w="100%" h="100%">
<TabPanel p={0}>
<Read />
</TabPanel>
<TabPanel p={0}>
<Listen />
</TabPanel>
<TabPanel p={0}>
<Watch />
</TabPanel>
</TabPanels>
<Flex grow={1} w="100%" overflowY="auto" mt={1}>
<TabPanels w="100%" h="100%">
<TabPanel p={0}>
<Read />
</TabPanel>
<TabPanel p={0}>
<Listen />
</TabPanel>
<TabPanel p={0}>
<Watch />
</TabPanel>
</TabPanels>
</Flex>
</Flex>
</Flex>
</Tabs>
</Tabs>
</Flex>
</Flex>
);
Loading