feat(server): Render loop for skeleton and loading.

This commit is contained in:
Gervwyk 2022-04-13 18:47:13 +02:00
parent b1d4212ddd
commit 3ec944b752
8 changed files with 286 additions and 29 deletions

View File

@ -33,7 +33,7 @@ function reducer(state, action) {
}
// TODO: inc every second
const ProgressBarController = ({ id, ProgressBar, children, lowdefy }) => {
const ProgressBarController = ({ id, ProgressBar, content, lowdefy }) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<ProgressBar
@ -46,7 +46,7 @@ const ProgressBarController = ({ id, ProgressBar, children, lowdefy }) => {
properties={{ ...state }}
user={lowdefy.user}
content={{
content: () => children({ state, dispatch }),
content: () => content.content({ state, dispatch }),
}}
/>
);

View File

@ -21,7 +21,7 @@ import { ErrorBoundary } from '@lowdefy/block-utils';
import CategorySwitch from './CategorySwitch.js';
import MountEvents from '../MountEvents.js';
const Block = ({ block, Blocks, context, isRoot, lowdefy, parentLoading }) => {
const Block = ({ block, Blocks, context, isRoot, loading, lowdefy, parentLoading }) => {
const [updates, setUpdate] = useState(0);
lowdefy._internal.updaters[block.id] = () => setUpdate(updates + 1);
return (
@ -36,13 +36,13 @@ const Block = ({ block, Blocks, context, isRoot, lowdefy, parentLoading }) => {
block.triggerEvent({ name: 'onMount' });
}}
>
{(loading) => (
{(eventLoading) => (
<CategorySwitch
block={block}
Blocks={Blocks}
context={context}
isRoot={isRoot}
loading={loading}
loading={eventLoading || loading || block.eval.loading}
lowdefy={lowdefy}
updates={updates}
/>

View File

@ -17,15 +17,53 @@
import React from 'react';
import { BlockLayout } from '@lowdefy/layout';
import { makeCssClass } from '@lowdefy/block-utils';
import { type } from '@lowdefy/helpers';
import Container from './Container.js';
import List from './List.js';
import LoadingBlock from './LoadingBlock.js';
const CategorySwitch = ({ block, Blocks, context, lowdefy }) => {
if (!block.eval) return null; // Renderer updates before eval is executed for the first time on lists. See #520
const CategorySwitch = ({ block, Blocks, context, loading, lowdefy }) => {
if (!block.eval) return null; // TODO: check Renderer updates before eval is executed for the first time on lists. See #520
if (block.eval.visible === false)
return <div id={`vs-${block.blockId}`} style={{ display: 'none' }} />;
const Component = lowdefy._internal.blockComponents[block.type];
let Component = lowdefy._internal.blockComponents[block.type];
// block skeleton:
// undefined, null, true - Try render component skeleton
// object - Render skeleton
// false - Render component
if (loading && block.eval.skeleton !== false && !type.isNone(block.eval.skeleton)) {
if (!type.isObject(block.eval.skeleton) && block.eval.skeleton !== true) {
throw new Error(
`Config Error: Invalid skeleton definition at block id ${block.blockId}. Skeleton config must be a boolean or an object.`
);
}
return (
<LoadingBlock
blockLayout={block.eval.layout}
context={context}
lowdefy={lowdefy}
skeleton={block.eval.skeleton}
/>
);
}
// component skeleton:
// false - Render component
// object - Render component skeleton
if (loading && Component.meta.skeleton !== false && !type.isNone(Component.meta.skeleton)) {
if (!type.isObject(Component.meta.skeleton) && Component.meta.skeleton !== true) {
throw new Error(`Block Error: Block type ${block.type} has an invalid skeleton definition.`);
}
return (
<LoadingBlock
skeleton={Component.meta.skeleton}
context={context}
lowdefy={lowdefy}
layout={block.eval.layout || {}}
/>
);
}
switch (Component.meta.category) {
case 'list':
return (
@ -34,6 +72,7 @@ const CategorySwitch = ({ block, Blocks, context, lowdefy }) => {
Blocks={Blocks}
Component={Component}
context={context}
loading={loading}
lowdefy={lowdefy}
/>
);
@ -44,6 +83,7 @@ const CategorySwitch = ({ block, Blocks, context, lowdefy }) => {
Blocks={Blocks}
Component={Component}
context={context}
loading={loading}
lowdefy={lowdefy}
/>
);
@ -69,6 +109,7 @@ const CategorySwitch = ({ block, Blocks, context, lowdefy }) => {
components={lowdefy._internal.components}
events={block.eval.events}
key={block.blockId}
loading={loading}
menus={lowdefy.menus}
pageId={lowdefy.pageId}
properties={block.eval.properties}
@ -100,6 +141,7 @@ const CategorySwitch = ({ block, Blocks, context, lowdefy }) => {
components={lowdefy._internal.components}
events={block.eval.events}
key={block.blockId}
loading={loading}
menus={lowdefy.menus}
pageId={lowdefy.pageId}
properties={block.eval.properties}

View File

@ -20,7 +20,7 @@ import { makeCssClass } from '@lowdefy/block-utils';
import Block from './Block.js';
const Container = ({ block, Blocks, Component, context, lowdefy }) => {
const Container = ({ block, Blocks, Component, context, loading, lowdefy }) => {
const content = {};
// eslint-disable-next-line prefer-destructuring
const areas = Blocks.subBlocks[block.id][0].areas;
@ -44,6 +44,7 @@ const Container = ({ block, Blocks, Component, context, lowdefy }) => {
Blocks={Blocks.subBlocks[block.id][0]}
block={bl}
context={context}
loading={loading}
lowdefy={lowdefy}
/>
))}
@ -71,6 +72,7 @@ const Container = ({ block, Blocks, Component, context, lowdefy }) => {
content={content}
events={block.eval.events}
key={block.blockId}
loading={loading}
menus={lowdefy.menus}
pageId={lowdefy.pageId}
properties={block.eval.properties}

View File

@ -20,7 +20,7 @@ import { makeCssClass } from '@lowdefy/block-utils';
import Block from './Block.js';
const List = ({ block, Blocks, Component, context, lowdefy }) => {
const List = ({ block, Blocks, Component, context, loading, lowdefy }) => {
const content = {};
const contentList = [];
Blocks.subBlocks[block.id].forEach((SBlock) => {
@ -44,6 +44,7 @@ const List = ({ block, Blocks, Component, context, lowdefy }) => {
Blocks={SBlock}
block={bl}
context={context}
loading={loading}
lowdefy={lowdefy}
/>
))}
@ -78,6 +79,7 @@ const List = ({ block, Blocks, Component, context, lowdefy }) => {
events={block.eval.events}
key={block.blockId}
list={contentList}
loading={loading}
menus={lowdefy.menus}
pageId={lowdefy.pageId}
properties={block.eval.properties}

View File

@ -1,22 +1,79 @@
import React from 'react';
// import { Loading, makeCssClass } from '@lowdefy/block-utils';
// import { get } from '@lowdefy/helpers';
// import { BlockLayout } from '@lowdefy/layout';
/*
Copyright 2020-2022 Lowdefy, Inc
const LoadingBlock = ({ block, lowdefy }) => (
<div>LoadingBlock</div>
// <BlockLayout
// id={`bl-loading-${block.blockId}`}
// blockStyle={get(block, 'eval.style') || get(block, 'meta.loading.style', { default: {} })}
// highlightBorders={lowdefy.lowdefyGlobal.highlightBorders}
// layout={get(block, 'eval.layout') || get(block, 'meta.loading.layout', { default: {} })}
// makeCssClass={makeCssClass}
// >
// <Loading
// properties={get(block, 'meta.loading.properties')}
// type={get(block, 'meta.loading.type')}
// />
// </BlockLayout>
);
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { BlockLayout } from '@lowdefy/layout';
import { makeCssClass } from '@lowdefy/block-utils';
import LoadingContainer from './LoadingContainer.js';
import LoadingList from './LoadingList.js';
const LoadingBlock = ({ blockLayout, blockId, context, lowdefy, skeleton }) => {
let Component = lowdefy._internal.blockComponents[skeleton.type];
if (!Component) {
// default to box when a skeleton block is not found - should be a basic or loader block.
// TODO: Always throw a warning if blocks other than basic or loader are used in a skeleton.
Component = lowdefy._internal.blockComponents.Box;
}
const layout = skeleton.layout || blockLayout || {};
switch (Component.meta.category) {
case 'list':
return (
<LoadingList
blockId={blockId}
Component={Component}
context={context}
layout={layout}
lowdefy={lowdefy}
skeleton={skeleton}
/>
);
case 'container':
return (
<LoadingContainer
blockId={blockId}
Component={Component}
context={context}
layout={layout}
lowdefy={lowdefy}
skeleton={skeleton}
/>
);
default:
return (
<BlockLayout
blockStyle={skeleton.style}
highlightBorders={lowdefy.lowdefyGlobal.highlightBorders}
id={`s-bl-${blockId}-${skeleton.id}`}
layout={layout}
makeCssClass={makeCssClass}
>
<Component
basePath={lowdefy.basePath}
blockId={blockId}
components={lowdefy._internal.components}
key={`s-${blockId}-${skeleton.id}`}
menus={lowdefy.menus}
methods={{ makeCssClass }}
pageId={lowdefy.pageId}
properties={skeleton.properties}
user={lowdefy.user}
/>
</BlockLayout>
);
}
};
export default LoadingBlock;

View File

@ -0,0 +1,76 @@
/*
Copyright 2020-2022 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { Area, BlockLayout, layoutParamsToArea } from '@lowdefy/layout';
import { makeCssClass } from '@lowdefy/block-utils';
import LoadingBlock from './LoadingBlock.js';
const LoadingContainer = ({ blockId, Component, context, layout, lowdefy, skeleton }) => {
const content = {};
// eslint-disable-next-line prefer-destructuring
Object.keys(skeleton.areas).forEach((areaKey, i) => {
content[areaKey] = (areaStyle) => (
<Area
area={layoutParamsToArea({
area: skeleton.areas[areaKey] || {},
areaKey,
layout,
})}
areaStyle={[areaStyle, skeleton.areas[areaKey] && skeleton.areas[areaKey].style]}
highlightBorders={lowdefy.lowdefyGlobal.highlightBorders}
id={`s-ar-${blockId}-${skeleton.id}-${areaKey}`}
key={`s-ar-${blockId}-${skeleton.id}-${areaKey}-${i}`}
makeCssClass={makeCssClass}
>
{skeleton.areas[areaKey].blocks.map((skl, k) => (
<LoadingBlock
blockId={blockId}
context={context}
key={`s-co-${skl.id}-${k}`}
lowdefy={lowdefy}
skeleton={skl}
/>
))}
</Area>
);
});
return (
<BlockLayout
blockStyle={skeleton.style}
highlightBorders={lowdefy.lowdefyGlobal.highlightBorders}
id={`s-bl-${blockId}-${skeleton.id}`}
layout={layout}
makeCssClass={makeCssClass}
>
<Component
basePath={lowdefy.basePath}
blockId={blockId}
components={lowdefy._internal.components}
content={content}
key={skeleton.id}
menus={lowdefy.menus}
methods={{ makeCssClass }}
pageId={lowdefy.pageId}
properties={skeleton.properties}
user={lowdefy.user}
/>
</BlockLayout>
);
};
export default LoadingContainer;

View File

@ -0,0 +1,78 @@
/*
Copyright 2020-2022 Lowdefy, Inc
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
import React from 'react';
import { Area, BlockLayout, layoutParamsToArea } from '@lowdefy/layout';
import { makeCssClass } from '@lowdefy/block-utils';
import LoadingBlock from './LoadingBlock.js';
const LoadingList = ({ blockId, Component, context, layout, lowdefy, skeleton }) => {
const content = {};
const contentList = [];
new Array(3).forEach(() => {
Object.keys(skeleton.areas).forEach((areaKey, i) => {
content[areaKey] = (areaStyle) => (
<Area
area={layoutParamsToArea({
area: skeleton.areas[areaKey] || {},
areaKey,
layout,
})}
areaStyle={[areaStyle, skeleton.areas[areaKey] && skeleton.areas[areaKey].style]}
highlightBorders={lowdefy.lowdefyGlobal.highlightBorders}
id={`s-ar-${blockId}-${skeleton.id}-${areaKey}`}
key={`s-ar-${blockId}-${skeleton.id}-${areaKey}-${i}`}
makeCssClass={makeCssClass}
>
{skeleton.areas[areaKey].blocks.map((skl, k) => (
<LoadingBlock
blockId={blockId}
context={context}
key={`s-co-${skl.id}-${k}`}
lowdefy={lowdefy}
skeleton={skl}
/>
))}
</Area>
);
});
contentList.push({ ...content });
});
return (
<BlockLayout
blockStyle={skeleton.style}
highlightBorders={lowdefy.lowdefyGlobal.highlightBorders}
id={`s-bl-${blockId}-${skeleton.id}`}
layout={layout}
makeCssClass={makeCssClass}
>
<Component
basePath={lowdefy.basePath}
blockId={blockId}
components={lowdefy._internal.components}
list={contentList}
menus={lowdefy.menus}
methods={{ makeCssClass }}
pageId={lowdefy.pageId}
properties={skeleton.properties}
user={lowdefy.user}
/>
</BlockLayout>
);
};
export default LoadingList;