Skip to content

Commit

Permalink
Fix: content block render (#2241)
Browse files Browse the repository at this point in the history
* chore(ui): update content block render logic

* chore(settings): update base layout style

* refactor(lib): update content block renderer logic

* refactor(lib): content block extension

* chore(ui): revert hook

* chore(ui): update content block store

* chore(ui): fix logic

* refactor(ui): update render logic
- add comments
- remove redundant state value

* chore(ui): comment for test case

* refactor(ui): update click handler

* chore(ui): revert comment
  • Loading branch information
josenriagu committed May 7, 2024
1 parent 694a95a commit a3bbeb7
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 142 deletions.
Expand Up @@ -14,11 +14,12 @@ const EditorPage: React.FC<unknown> = () => {
const { getRoutingPlugin } = useRootComponentProps();
const navigateTo = React.useRef(getRoutingPlugin().navigateTo);
const { t } = useTranslation();

return (
<HelmetProvider>
<Stack fullWidth={true}>
<Helmet>
<title>Beam Editor</title>
<title>{t('Beam Editor')}</title>
</Helmet>
{!authenticatedDID && (
<Stack>
Expand Down
3 changes: 1 addition & 2 deletions ui/apps/akasha/src/extensions/beam-editor/footer/index.tsx
Expand Up @@ -58,8 +58,7 @@ export const Footer: React.FC<TFooterProps> = props => {
justify="between"
align="center"
direction="row"
customStyle="rounded-b-xl"
background={{ light: 'white', dark: 'grey2' }}
customStyle="rounded-b-2xl"
>
{/* render content based on the value of uiState */}
{uiState === 'blocks' && (
Expand Down
1 change: 1 addition & 0 deletions ui/apps/akasha/src/extensions/beam-editor/header.tsx
Expand Up @@ -42,6 +42,7 @@ export const Header: React.FC<HeaderProps> = props => {
justify={uiState === 'editor' ? 'between' : 'center'}
direction="row"
align="center"
customStyle="rounded-t-2xl"
>
<Text variant="h4">{renderTitle()}</Text>
{uiState === 'editor' && (
Expand Down
9 changes: 9 additions & 0 deletions ui/apps/extensions/src/plugins/content-block-store.ts
Expand Up @@ -9,6 +9,15 @@ import { hasOwn } from '@akashaorg/ui-awf-hooks';
import { AkashaContentBlockLabeledValue } from '@akashaorg/typings/lib/sdk/graphql-types-new';
import { BaseStore } from './base-store';

/**
* When app-loader loads the applications config (the return object of the register function);
* it checks if the config has the 'contentBlocks' property.
* if it does, it will emit the 'ContentBlockEvents.RegisterContentBlock' event.
*
* This class ContentBlockStore listens for this event and stores the received data into the blocks property (this.#blocks).
*
* The getMatchingBlocks method takes as param, 'blockInfo', iterates over the blocks and tries to find the block(s) matching the 'propertyType' and the 'appName' of the passed 'blockInfo'
*/
export class ContentBlockStore extends BaseStore {
static instance: ContentBlockStore;
#blocks: ContentBlockRegisterEvent['data'];
Expand Down
2 changes: 1 addition & 1 deletion ui/apps/settings-app/src/components/pages/base-layout.tsx
Expand Up @@ -11,7 +11,7 @@ export interface IBaseLayout {
const BaseLayout: React.FC<PropsWithChildren<IBaseLayout>> = props => {
const { title, children } = props;
return (
<Card padding={0}>
<Card padding={0} margin="mb-4">
<Stack padding="p-4" customStyle="border(b-1 solid grey8 dark:grey5)">
<Text variant="h5" align="center">
{title}
Expand Down
2 changes: 1 addition & 1 deletion ui/lib/extensions/src/react/content-block/block-parcel.tsx
Expand Up @@ -6,7 +6,7 @@ import { BlockInstanceMethods, ContentBlockModes } from '@akashaorg/typings/lib/
import { ParcelConfigObject } from 'single-spa';
import { useRootComponentProps } from '@akashaorg/ui-awf-hooks';

type BlockParcelProps = {
export type BlockParcelProps = {
matchingBlock: MatchingBlock & { config: ParcelConfigObject };
blockId: string;
index: number;
Expand Down
@@ -1,28 +1,27 @@
import React, { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import Text from '@akashaorg/design-system-core/lib/components/Text';
import TextLine from '@akashaorg/design-system-core/lib/components/TextLine';
import Stack from '@akashaorg/design-system-core/lib/components/Stack';
import Button from '@akashaorg/design-system-core/lib/components/Button';
import { ParcelConfigObject } from 'single-spa';
import { hasOwn, useRootComponentProps } from '@akashaorg/ui-awf-hooks';
import { ContentBlockModes, BlockInstanceMethods } from '@akashaorg/typings/lib/ui';
import { BlockInstanceMethods, ContentBlockModes } from '@akashaorg/typings/lib/ui';
import { GetContentBlockByIdQuery } from '@akashaorg/typings/lib/sdk/graphql-operation-types-new';
import { useGetContentBlockByIdLazyQuery } from '@akashaorg/ui-awf-hooks/lib/generated/apollo';
import { ParcelConfigObject } from 'single-spa';
import { BlockParcel } from './block-parcel';
import { MatchingBlock } from './common.types';
import { resolveConfigs } from './resolve-configs';
import { BlockError, BlockErrorCard } from './block-error-card';
import { RenderError } from './render-error';
import { RenderBlock } from './render-block';
import { BlockError } from '../block-error-card';
import { resolveConfigs } from '../resolve-configs';
import { MatchingBlock } from '../common.types';

export type ContentBlockExtensionProps = {
blockRef?: React.RefObject<BlockInstanceMethods>;
fetchError?: BlockError;
contentLoadError?: BlockError;
installButtonLabel: string;
notInstalledTitle: string;
notInstalledDescription1: string;
notInstalledDescription2: string;
//Cache to prevent re-loading block config with the same block id
// cache to prevent re-loading block config with the same block id
cacheBlockConfig?: boolean;
onError?: (error: Error) => void;
onClickInstall: (e: React.SyntheticEvent) => void;
} & (
| {
blockID: string;
Expand All @@ -41,29 +40,31 @@ export const ContentBlockExtension: React.FC<ContentBlockExtensionProps> = props
blockRef,
fetchError,
contentLoadError,
installButtonLabel,
notInstalledTitle,
notInstalledDescription1,
notInstalledDescription2,
cacheBlockConfig,
onError,
onClickInstall,
...remainingProps
} = props;
const { logger, getExtensionsPlugin } = useRootComponentProps();
const contentBlockStoreRef = useRef(getExtensionsPlugin()?.contentBlockStore);
const [hasContentLoadError, setHasContentLoadError] = useState(false);
const [state, setState] = useState<{
parcels: (MatchingBlock & { config: ParcelConfigObject })[];
isMatched: boolean;
}>({
parcels: [],
isMatched: false,
});
const [fetchBlockInfo, blockInfoQuery] = useGetContentBlockByIdLazyQuery();
// fetch data error
const fetchDataError = useMemo(() => {
return hasOwn(remainingProps, 'blockData')
? remainingProps?.error
: blockInfoQuery.error?.message;
}, [remainingProps, blockInfoQuery]);
// block data
const blockData = useMemo(() => {
if (hasOwn(remainingProps, 'blockData')) {
if (remainingProps.blockData && hasOwn(remainingProps.blockData, 'id')) {
Expand All @@ -75,44 +76,45 @@ export const ContentBlockExtension: React.FC<ContentBlockExtensionProps> = props
}
return null;
}, [remainingProps, blockInfoQuery]);

// find matching blocks
const matchingBlocks: MatchingBlock[] = useMemo(() => {
if (hasOwn(remainingProps, 'blockData') && remainingProps?.matchingBlocks)
return remainingProps.matchingBlocks;
return !blockData ? [] : contentBlockStoreRef.current.getMatchingBlocks(blockData);
}, [blockData, remainingProps]);

useLayoutEffect(() => {
if (
matchingBlocks &&
matchingBlocks.length &&
matchingBlocks.length !== state.parcels.length &&
!state.isMatched
) {
if (matchingBlocks && matchingBlocks.length !== state.parcels.length) {
resolveConfigs({ matchingBlocks, mode: ContentBlockModes.READONLY, cache: cacheBlockConfig })
.then(newBlocks => {
setState({
parcels: newBlocks,
isMatched: true,
});
})
.catch(err => {
setHasContentLoadError(true);
logger.error('failed to load content blocks', err);
});
} else if (
matchingBlocks &&
!matchingBlocks.length &&
!state.isMatched &&
blockInfoQuery.called &&
!blockInfoQuery.loading
) {
} else if (blockInfoQuery.called && !blockInfoQuery.loading) {
setState({
parcels: [],
isMatched: true,
});
}
}, [logger, blockInfoQuery.called, blockInfoQuery.loading, matchingBlocks, state]);
}, [
logger,
blockInfoQuery.called,
blockInfoQuery.loading,
matchingBlocks,
state,
cacheBlockConfig,
]);

/**
* remainingProps could have either just the blockID or other necessary data passed from the parent
* Each of the variables and hooks below evaluates the props available in the remainingProps and acts accordingly.
*/
// if only blockID, fetch the necessary data
useEffect(() => {
if (hasOwn(remainingProps, 'blockID')) {
fetchBlockInfo({
Expand All @@ -126,7 +128,6 @@ export const ContentBlockExtension: React.FC<ContentBlockExtensionProps> = props
useEffect(() => {
return () => {
setState({
isMatched: false,
parcels: [],
});
};
Expand All @@ -140,74 +141,40 @@ export const ContentBlockExtension: React.FC<ContentBlockExtensionProps> = props

if (hasContentLoadError || fetchDataError) {
return (
<Stack spacing="gap-y-2">
{hasContentLoadError && (
<BlockErrorCard
errorTitle={contentLoadError.errorTitle}
errorDescription={contentLoadError.errorDescription}
/>
)}
{fetchDataError && (
<BlockErrorCard
errorTitle={fetchError.errorTitle}
errorDescription={fetchError.errorDescription}
refreshLabel={hasOwn(remainingProps, 'blockData') ? remainingProps.refreshLabel : ''}
onRefresh={() => {
if (hasOwn(remainingProps, 'blockID')) {
blockInfoQuery.refetch({
id: remainingProps?.blockID,
});
}

if (hasOwn(remainingProps, 'blockData')) {
remainingProps.onRefresh?.();
}
}}
/>
)}
</Stack>
<RenderError
fetchError={fetchError}
contentLoadError={contentLoadError}
fetchDataError={fetchDataError}
hasContentLoadError={hasContentLoadError}
refreshLabel={hasOwn(remainingProps, 'refreshLabel') ? remainingProps.refreshLabel : ''}
handleRefresh={() => {
if (hasOwn(remainingProps, 'blockID')) {
blockInfoQuery.refetch({
id: remainingProps?.blockID,
});
}
if (hasOwn(remainingProps, 'blockData')) {
remainingProps.onRefresh?.();
}
}}
/>
);
}

return (
<>
{!state.parcels.length && !state.isMatched && (
<Stack fullWidth={true} spacing="gap-y-1" customStyle="mb-2">
<TextLine width="w-full" animated />
<TextLine width="w-2/3" animated />
</Stack>
)}
{!state.parcels.length && state.isMatched && appInfo && (
<Stack
spacing="gap-y-2"
padding="p-4"
background={{ light: 'grey9', dark: 'grey1' }}
customStyle="rounded-[20px]"
>
<Stack direction="row" spacing="gap-x-1">
<Text variant="button-sm">
{appInfo.displayName} {notInstalledTitle}
</Text>
<Button variant="text" label={'Install'} customStyle="ml-auto" />
</Stack>
<Text variant="footnotes2" weight="normal">
{notInstalledDescription1} {appInfo.displayName} {notInstalledDescription2}
</Text>
</Stack>
)}
{state.parcels.map((matchingBlock, index) => {
return (
<BlockParcel
key={index}
mode={ContentBlockModes.READONLY}
matchingBlock={matchingBlock}
blockId={blockData?.id}
index={index}
blockRef={blockRef}
onError={onError}
/>
);
})}
</>
<RenderBlock
state={state}
appInfo={appInfo}
blockId={blockData?.id}
blockRef={blockRef}
blockData={blockData}
matchingBlocks={matchingBlocks}
notInstalledTitle={notInstalledTitle}
installButtonLabel={installButtonLabel}
notInstalledDescription1={notInstalledDescription1}
notInstalledDescription2={notInstalledDescription2}
onError={onError}
onClickInstall={onClickInstall}
/>
);
};

0 comments on commit a3bbeb7

Please sign in to comment.