mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2025-04-12 21:50:23 +08:00
feat: improve the editor's performance
avoid the unnecessary render and add the lazy load to some widgets
This commit is contained in:
parent
24e7b87c89
commit
67fb8a0065
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import { css } from '@emotion/css';
|
||||
import { IconButton, Table, Thead, Tbody, Tr, Th, Td } from '@chakra-ui/react';
|
||||
import { AddIcon } from '@chakra-ui/icons';
|
||||
@ -27,13 +27,74 @@ const TableRowStyle = css`
|
||||
}
|
||||
`;
|
||||
|
||||
type Props = WidgetProps & {
|
||||
type ArrayTableProps = WidgetProps & {
|
||||
itemSpec: JSONSchema7;
|
||||
};
|
||||
type RowProps = ArrayTableProps & {
|
||||
itemValue: any;
|
||||
itemIndex: number;
|
||||
};
|
||||
|
||||
export const ArrayTable: React.FC<Props> = props => {
|
||||
const { value, itemSpec, spec, level, path, children, onChange } = props;
|
||||
const DEFAULT_KEYS = ['index'];
|
||||
|
||||
const TableRow: React.FC<RowProps> = props => {
|
||||
const { value, itemSpec, spec, level, path, children, itemValue, itemIndex, onChange } =
|
||||
props;
|
||||
const { expressionOptions, displayedKeys = [] } = spec.widgetOptions || {};
|
||||
const keys = displayedKeys.length ? displayedKeys : DEFAULT_KEYS;
|
||||
const mergedSpec = useMemo(
|
||||
() =>
|
||||
mergeWidgetOptionsIntoSpec(
|
||||
{
|
||||
...itemSpec,
|
||||
title: '',
|
||||
},
|
||||
{
|
||||
expressionOptions,
|
||||
}
|
||||
),
|
||||
[itemSpec, expressionOptions]
|
||||
);
|
||||
const nextPath = useMemo(() => path.concat(String(itemIndex)), [path, itemIndex]);
|
||||
const onPopoverWidgetChange = useCallback(
|
||||
(newItemValue: any) => {
|
||||
const newValue = [...value];
|
||||
newValue[itemIndex] = newItemValue;
|
||||
onChange(newValue);
|
||||
},
|
||||
[itemIndex, onChange, value]
|
||||
);
|
||||
|
||||
return (
|
||||
<Tr className={TableRowStyle}>
|
||||
<Td key="setting">
|
||||
<PopoverWidget
|
||||
{...props}
|
||||
value={itemValue}
|
||||
spec={mergedSpec}
|
||||
path={nextPath}
|
||||
level={level + 1}
|
||||
onChange={onPopoverWidgetChange}
|
||||
>
|
||||
{typeof children === 'function' ? children(props, itemValue, itemIndex) : null}
|
||||
</PopoverWidget>
|
||||
</Td>
|
||||
{keys.map((key: string) => {
|
||||
const propertyValue =
|
||||
key === 'index' ? itemValue[key] ?? itemIndex : itemValue[key];
|
||||
|
||||
return <Td key={key}>{propertyValue}</Td>;
|
||||
})}
|
||||
<Td key="button">
|
||||
<ArrayButtonGroup index={itemIndex} value={value} onChange={onChange} />
|
||||
</Td>
|
||||
</Tr>
|
||||
);
|
||||
};
|
||||
|
||||
export const ArrayTable: React.FC<ArrayTableProps> = props => {
|
||||
const { value, itemSpec, spec, onChange } = props;
|
||||
const { displayedKeys = [] } = spec.widgetOptions || {};
|
||||
const keys = displayedKeys.length ? displayedKeys : ['index'];
|
||||
|
||||
return (
|
||||
@ -63,43 +124,12 @@ export const ArrayTable: React.FC<Props> = props => {
|
||||
</Thead>
|
||||
<Tbody>
|
||||
{value.map((itemValue: any, itemIndex: number) => (
|
||||
<Tr key={itemIndex} className={TableRowStyle}>
|
||||
<Td key="setting">
|
||||
<PopoverWidget
|
||||
{...props}
|
||||
value={itemValue}
|
||||
spec={mergeWidgetOptionsIntoSpec(
|
||||
{
|
||||
...itemSpec,
|
||||
title: '',
|
||||
},
|
||||
{
|
||||
expressionOptions,
|
||||
}
|
||||
)}
|
||||
path={path.concat(String(itemIndex))}
|
||||
level={level + 1}
|
||||
onChange={(newItemValue: any) => {
|
||||
const newValue = [...value];
|
||||
newValue[itemIndex] = newItemValue;
|
||||
onChange(newValue);
|
||||
}}
|
||||
>
|
||||
{typeof children === 'function'
|
||||
? children(props, itemValue, itemIndex)
|
||||
: null}
|
||||
</PopoverWidget>
|
||||
</Td>
|
||||
{keys.map((key: string) => {
|
||||
const propertyValue =
|
||||
key === 'index' ? itemValue[key] ?? itemIndex : itemValue[key];
|
||||
|
||||
return <Td key={key}>{propertyValue}</Td>;
|
||||
})}
|
||||
<Td key="button">
|
||||
<ArrayButtonGroup index={itemIndex} value={value} onChange={onChange} />
|
||||
</Td>
|
||||
</Tr>
|
||||
<TableRow
|
||||
{...props}
|
||||
key={itemIndex}
|
||||
itemValue={itemValue}
|
||||
itemIndex={itemIndex}
|
||||
/>
|
||||
))}
|
||||
</Tbody>
|
||||
</Table>
|
||||
|
@ -93,6 +93,10 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
|
||||
|
||||
return params;
|
||||
}, [formik.values.method.name]);
|
||||
const parametersPath = useMemo(()=> path.concat('method', 'parameters'), [path]);
|
||||
const parametersSpec = useMemo(()=> mergeWidgetOptionsIntoSpec(paramsSpec, { onlySetValue: true }), [paramsSpec]);
|
||||
const disabledPath = useMemo(()=> path.concat('disabled'), [path]);
|
||||
const disabledSpec = useMemo(()=> Type.Boolean({ widgetOptions: { isShowAsideExpressionButton: true } }), []);
|
||||
|
||||
const updateMethods = useCallback(
|
||||
(componentId: string) => {
|
||||
@ -127,18 +131,29 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
|
||||
}
|
||||
}, [value, updateMethods]);
|
||||
|
||||
const onTargetComponentChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
const onTargetComponentChange = useCallback((e: React.ChangeEvent<HTMLSelectElement>) => {
|
||||
updateMethods(e.target.value);
|
||||
formik.handleChange(e);
|
||||
formik.setFieldValue('method', { name: '', parameters: {} });
|
||||
};
|
||||
}, [updateMethods, formik]);
|
||||
const onSubmit = useCallback(() => {
|
||||
formik.submitForm();
|
||||
}, [formik]);
|
||||
const onParametersChange = useCallback(json => {
|
||||
formik.setFieldValue('method.parameters', json);
|
||||
formik.submitForm();
|
||||
}, [formik]);
|
||||
const onDisabledChange = useCallback(value => {
|
||||
formik.setFieldValue('disabled', value);
|
||||
formik.submitForm();
|
||||
}, [formik]);
|
||||
|
||||
const typeField = (
|
||||
<FormControl>
|
||||
<FormLabel>Event Type</FormLabel>
|
||||
<Select
|
||||
name="type"
|
||||
onBlur={() => formik.submitForm()}
|
||||
onBlur={onSubmit}
|
||||
onChange={formik.handleChange}
|
||||
placeholder="Select Event Type"
|
||||
value={formik.values.type}
|
||||
@ -156,7 +171,7 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
|
||||
<FormLabel>Target Component</FormLabel>
|
||||
<Select
|
||||
name="componentId"
|
||||
onBlur={() => formik.submitForm()}
|
||||
onBlur={onSubmit}
|
||||
onChange={onTargetComponentChange}
|
||||
placeholder="Select Target Component"
|
||||
value={formik.values.componentId}
|
||||
@ -174,7 +189,7 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
|
||||
<FormLabel>Method</FormLabel>
|
||||
<Select
|
||||
name="method.name"
|
||||
onBlur={() => formik.submitForm()}
|
||||
onBlur={onSubmit}
|
||||
onChange={formik.handleChange}
|
||||
placeholder="Select Method"
|
||||
value={formik.values.method.name}
|
||||
@ -193,15 +208,12 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
|
||||
<FormLabel>Parameters</FormLabel>
|
||||
<RecordWidget
|
||||
component={component}
|
||||
path={path.concat('method', 'parameters')}
|
||||
path={parametersPath}
|
||||
level={level + 1}
|
||||
spec={mergeWidgetOptionsIntoSpec(paramsSpec, { onlySetValue: true })}
|
||||
spec={parametersSpec}
|
||||
services={services}
|
||||
value={formik.values.method.parameters}
|
||||
onChange={json => {
|
||||
formik.setFieldValue('method.parameters', json);
|
||||
formik.submitForm();
|
||||
}}
|
||||
onChange={onParametersChange}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
@ -211,7 +223,7 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
|
||||
<FormLabel>Wait Type</FormLabel>
|
||||
<Select
|
||||
name="wait.type"
|
||||
onBlur={() => formik.submitForm()}
|
||||
onBlur={onSubmit}
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.wait?.type}
|
||||
>
|
||||
@ -227,7 +239,7 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
|
||||
<FormLabel>Wait Time</FormLabel>
|
||||
<Input
|
||||
name="wait.time"
|
||||
onBlur={() => formik.submitForm()}
|
||||
onBlur={onSubmit}
|
||||
onChange={formik.handleChange}
|
||||
value={formik.values.wait?.time}
|
||||
/>
|
||||
@ -239,14 +251,11 @@ export const EventWidget: React.FC<WidgetProps<EventWidgetOptionsType>> = observ
|
||||
<FormLabel>Disabled</FormLabel>
|
||||
<SpecWidget
|
||||
{...props}
|
||||
spec={Type.Boolean({ widgetOptions: { isShowAsideExpressionButton: true } })}
|
||||
spec={disabledSpec}
|
||||
level={level + 1}
|
||||
path={['disabled']}
|
||||
path={disabledPath}
|
||||
value={formik.values.disabled}
|
||||
onChange={value => {
|
||||
formik.setFieldValue('disabled', value);
|
||||
formik.submitForm();
|
||||
}}
|
||||
onChange={onDisabledChange}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
|
@ -1,4 +1,10 @@
|
||||
import React, { useState, useEffect, useCallback, useImperativeHandle } from 'react';
|
||||
import React, {
|
||||
useState,
|
||||
useEffect,
|
||||
useMemo,
|
||||
useCallback,
|
||||
useImperativeHandle,
|
||||
} from 'react';
|
||||
import {
|
||||
Popover,
|
||||
PopoverTrigger,
|
||||
@ -38,10 +44,20 @@ export const PopoverWidget = React.forwardRef<
|
||||
>((props, ref) => {
|
||||
const { spec, path, children } = props;
|
||||
const isObjectChildren = children && typeof children === 'object';
|
||||
const [isInit, setIsInit] = useState(false);
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const mergedSpec = useMemo(
|
||||
() => ({
|
||||
...spec,
|
||||
widget:
|
||||
spec.widget === `${CORE_VERSION}/${CoreWidgetName.Popover}` ? '' : spec.widget,
|
||||
}),
|
||||
[spec]
|
||||
);
|
||||
|
||||
const handleOpen = useCallback(() => {
|
||||
setIsOpen(true);
|
||||
setIsInit(true);
|
||||
emitter.emit('other-popover-close', path);
|
||||
}, [path]);
|
||||
const handleClickTrigger = useCallback(event => {
|
||||
@ -132,17 +148,13 @@ export const PopoverWidget = React.forwardRef<
|
||||
<PopoverContent onClick={handleClickContent}>
|
||||
<PopoverArrow />
|
||||
<PopoverBody maxHeight="400px" overflow="auto">
|
||||
{isObjectChildren && 'body' in children ? (
|
||||
(children as Children).body
|
||||
) : (
|
||||
<SpecWidget
|
||||
{...props}
|
||||
spec={{
|
||||
...spec,
|
||||
widget: spec.widget === `${CORE_VERSION}/${CoreWidgetName.Popover}` ? '' : spec.widget,
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{isInit ? (
|
||||
isObjectChildren && 'body' in children ? (
|
||||
(children as Children).body
|
||||
) : (
|
||||
<SpecWidget {...props} spec={mergedSpec} />
|
||||
)
|
||||
) : null}
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Portal>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useCallback } from 'react';
|
||||
import { EventWidget } from '@sunmao-ui/editor-sdk';
|
||||
import {
|
||||
Box,
|
||||
@ -44,9 +44,13 @@ export const EventHandlerForm: React.FC<Props> = props => {
|
||||
onUp,
|
||||
onDown,
|
||||
} = props;
|
||||
const [hasOpened, setHasOpened] = React.useState<boolean>(false);
|
||||
const onAccordionChange = useCallback(() => {
|
||||
setHasOpened(true);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Accordion width="100%" allowMultiple>
|
||||
<Accordion width="100%" allowMultiple onChange={onAccordionChange}>
|
||||
<AccordionItem>
|
||||
<h2>
|
||||
<AccordionButton>
|
||||
@ -57,45 +61,47 @@ export const EventHandlerForm: React.FC<Props> = props => {
|
||||
</AccordionButton>
|
||||
</h2>
|
||||
<AccordionPanel pb={4} pt={2} padding={0}>
|
||||
<Box position="relative" width="100%">
|
||||
<VStack className={formWrapperCSS}>
|
||||
<EventWidget
|
||||
component={component}
|
||||
spec={spec}
|
||||
value={handler}
|
||||
path={[]}
|
||||
level={1}
|
||||
services={services}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</VStack>
|
||||
<Box position="absolute" right="4" top="4">
|
||||
<IconButton
|
||||
aria-label="up event handler"
|
||||
icon={<ArrowUpIcon />}
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
disabled={index === 0}
|
||||
onClick={onUp}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label="down event handler"
|
||||
icon={<ArrowDownIcon />}
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
disabled={index === size - 1}
|
||||
onClick={onDown}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label="remove event handler"
|
||||
colorScheme="red"
|
||||
icon={<CloseIcon />}
|
||||
onClick={onRemove}
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
/>
|
||||
{hasOpened ? (
|
||||
<Box position="relative" width="100%">
|
||||
<VStack className={formWrapperCSS}>
|
||||
<EventWidget
|
||||
component={component}
|
||||
spec={spec}
|
||||
value={handler}
|
||||
path={[]}
|
||||
level={1}
|
||||
services={services}
|
||||
onChange={onChange}
|
||||
/>
|
||||
</VStack>
|
||||
<Box position="absolute" right="4" top="4">
|
||||
<IconButton
|
||||
aria-label="up event handler"
|
||||
icon={<ArrowUpIcon />}
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
disabled={index === 0}
|
||||
onClick={onUp}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label="down event handler"
|
||||
icon={<ArrowDownIcon />}
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
disabled={index === size - 1}
|
||||
onClick={onDown}
|
||||
/>
|
||||
<IconButton
|
||||
aria-label="remove event handler"
|
||||
colorScheme="red"
|
||||
icon={<CloseIcon />}
|
||||
onClick={onRemove}
|
||||
size="xs"
|
||||
variant="ghost"
|
||||
/>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
) : null}
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
|
@ -145,6 +145,12 @@ export const Editor: React.FC<Props> = observer(
|
||||
}
|
||||
}, [isDisplayApp]);
|
||||
const onPreview = useCallback(() => setPreview(true), []);
|
||||
const onSelectComponent = useCallback(id => {
|
||||
editorStore.setSelectedComponentId(id);
|
||||
}, [editorStore]);
|
||||
const onRightTabChange = useCallback(activatedTab => {
|
||||
setToolMenuTab(activatedTab);
|
||||
}, []);
|
||||
|
||||
const renderMain = () => {
|
||||
const appBox = (
|
||||
@ -216,9 +222,7 @@ export const Editor: React.FC<Props> = observer(
|
||||
<StructureTree
|
||||
components={components}
|
||||
selectedComponentId={selectedComponentId}
|
||||
onSelectComponent={id => {
|
||||
editorStore.setSelectedComponentId(id);
|
||||
}}
|
||||
onSelectComponent={onSelectComponent}
|
||||
services={services}
|
||||
/>
|
||||
</TabPanel>
|
||||
@ -257,9 +261,8 @@ export const Editor: React.FC<Props> = observer(
|
||||
display="flex"
|
||||
flexDirection="column"
|
||||
index={toolMenuTab}
|
||||
onChange={activatedTab => {
|
||||
setToolMenuTab(activatedTab);
|
||||
}}
|
||||
onChange={onRightTabChange}
|
||||
isLazy
|
||||
>
|
||||
<TabList background="gray.50">
|
||||
<Tab>Inspect</Tab>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useMemo, useState, useCallback } from 'react';
|
||||
import { Box, Text, VStack } from '@chakra-ui/react';
|
||||
import { ComponentSchema } from '@sunmao-ui/core';
|
||||
import { ComponentItemView } from './ComponentItemView';
|
||||
@ -110,14 +110,20 @@ export const ComponentTree: React.FC<Props> = props => {
|
||||
isExpanded,
|
||||
]);
|
||||
|
||||
const onClickRemove = () => {
|
||||
const onClickRemove = useCallback(() => {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
genOperation(registry, 'removeComponent', {
|
||||
componentId: component.id,
|
||||
})
|
||||
);
|
||||
};
|
||||
}, [component.id, eventBus, registry]);
|
||||
const onClickItem = useCallback(() => {
|
||||
onSelectComponent(component.id);
|
||||
}, [component.id, onSelectComponent]);
|
||||
const onToggleExpanded = useCallback(() => setIsExpanded(prev => !prev), []);
|
||||
const onDragStart = useCallback(() => setIsDragging(true), []);
|
||||
const onDragEnd = useCallback(() => setIsDragging(false), []);
|
||||
|
||||
return (
|
||||
<VStack
|
||||
@ -141,15 +147,13 @@ export const ComponentTree: React.FC<Props> = props => {
|
||||
id={component.id}
|
||||
title={component.id}
|
||||
isSelected={component.id === selectedComponentId}
|
||||
onClick={() => {
|
||||
onSelectComponent(component.id);
|
||||
}}
|
||||
onClick={onClickItem}
|
||||
onClickRemove={onClickRemove}
|
||||
noChevron={slots.length === 0}
|
||||
isExpanded={isExpanded}
|
||||
onToggleExpanded={() => setIsExpanded(prev => !prev)}
|
||||
onDragStart={() => setIsDragging(true)}
|
||||
onDragEnd={() => setIsDragging(false)}
|
||||
onToggleExpanded={onToggleExpanded}
|
||||
onDragStart={onDragStart}
|
||||
onDragEnd={onDragEnd}
|
||||
depth={depth}
|
||||
/>
|
||||
</DropComponentWrapper>
|
||||
|
Loading…
x
Reference in New Issue
Block a user