mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2024-11-27 08:39:59 +08:00
Merge pull request #401 from webzard-io/feat/windlike-patch
feat: improve the editor performance
This commit is contained in:
commit
7fe0cb8343
@ -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>
|
||||
|
@ -144,6 +144,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 = (
|
||||
@ -215,9 +221,7 @@ export const Editor: React.FC<Props> = observer(
|
||||
<StructureTree
|
||||
components={components}
|
||||
selectedComponentId={selectedComponentId}
|
||||
onSelectComponent={id => {
|
||||
editorStore.setSelectedComponentId(id);
|
||||
}}
|
||||
onSelectComponent={onSelectComponent}
|
||||
services={services}
|
||||
/>
|
||||
</TabPanel>
|
||||
@ -256,9 +260,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';
|
||||
@ -6,26 +6,43 @@ import { DropComponentWrapper } from './DropComponentWrapper';
|
||||
import { ChildrenMap } from './StructureTree';
|
||||
import { genOperation } from '../../operations';
|
||||
import { EditorServices } from '../../types';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
type Props = {
|
||||
component: ComponentSchema;
|
||||
parentId: string | undefined;
|
||||
slot: string | undefined;
|
||||
childrenMap: ChildrenMap;
|
||||
selectedComponentId: string;
|
||||
onSelectComponent: (id: string) => void;
|
||||
services: EditorServices;
|
||||
isAncestorDragging: boolean;
|
||||
depth: number;
|
||||
};
|
||||
type ComponentTreeProps = Props & {
|
||||
isSelected: boolean;
|
||||
};
|
||||
|
||||
export const ComponentTree: React.FC<Props> = props => {
|
||||
const observeSelected = (Component: React.FC<ComponentTreeProps>) => {
|
||||
const ObserveActive: React.FC<Props> = props => {
|
||||
const { services } = props;
|
||||
const { editorStore } = services;
|
||||
const { selectedComponentId } = editorStore;
|
||||
|
||||
return (
|
||||
<Component {...props} isSelected={selectedComponentId === props.component.id} />
|
||||
);
|
||||
};
|
||||
|
||||
return observer(ObserveActive);
|
||||
};
|
||||
|
||||
const ComponentTree = (props: ComponentTreeProps) => {
|
||||
const {
|
||||
component,
|
||||
childrenMap,
|
||||
parentId,
|
||||
slot,
|
||||
selectedComponentId,
|
||||
isSelected,
|
||||
onSelectComponent,
|
||||
services,
|
||||
isAncestorDragging,
|
||||
@ -47,13 +64,12 @@ export const ComponentTree: React.FC<Props> = props => {
|
||||
if (slotChildren && slotChildren.length > 0) {
|
||||
slotContent = slotChildren.map(c => {
|
||||
return (
|
||||
<ComponentTree
|
||||
<ComponentTreeWrapper
|
||||
key={c.id}
|
||||
component={c}
|
||||
parentId={component.id}
|
||||
slot={_slot}
|
||||
childrenMap={childrenMap}
|
||||
selectedComponentId={selectedComponentId}
|
||||
onSelectComponent={onSelectComponent}
|
||||
services={services}
|
||||
isAncestorDragging={isAncestorDragging || isDragging}
|
||||
@ -89,7 +105,7 @@ export const ComponentTree: React.FC<Props> = props => {
|
||||
return (
|
||||
<Box key={_slot} paddingLeft="3" width="full">
|
||||
{/* although component can have multiple slots, but for now, most components have only one slot
|
||||
so we hide slot name to save more view area */}
|
||||
so we hide slot name to save more view area */}
|
||||
{slots.length > 1 ? slotName : undefined}
|
||||
<VStack spacing="0" width="full" alignItems="start">
|
||||
{slotContent}
|
||||
@ -101,7 +117,6 @@ export const ComponentTree: React.FC<Props> = props => {
|
||||
slots,
|
||||
childrenMap,
|
||||
component.id,
|
||||
selectedComponentId,
|
||||
onSelectComponent,
|
||||
services,
|
||||
isAncestorDragging,
|
||||
@ -110,14 +125,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
|
||||
@ -140,16 +161,14 @@ export const ComponentTree: React.FC<Props> = props => {
|
||||
<ComponentItemView
|
||||
id={component.id}
|
||||
title={component.id}
|
||||
isSelected={component.id === selectedComponentId}
|
||||
onClick={() => {
|
||||
onSelectComponent(component.id);
|
||||
}}
|
||||
isSelected={isSelected}
|
||||
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>
|
||||
@ -157,3 +176,7 @@ export const ComponentTree: React.FC<Props> = props => {
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
||||
export const ComponentTreeWrapper: React.FC<Props> = observeSelected(
|
||||
React.memo(ComponentTree)
|
||||
);
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { Box } from '@chakra-ui/react';
|
||||
import React, { useMemo, useRef, useState } from 'react';
|
||||
import React, { useMemo, useRef, useState, useCallback } from 'react';
|
||||
import { genOperation } from '../../operations';
|
||||
import { EditorServices } from '../../types';
|
||||
|
||||
@ -31,83 +31,99 @@ export const DropComponentWrapper: React.FC<Props> = props => {
|
||||
const [dragDirection, setDragDirection] = useState<'prev' | 'next' | undefined>();
|
||||
const [isDragOver, setIsDragOver] = useState<boolean>(false);
|
||||
|
||||
const onDragOver = (e: React.DragEvent) => {
|
||||
if (!droppable) {
|
||||
return;
|
||||
}
|
||||
setIsDragOver(true);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const onDragOver = useCallback(
|
||||
(e: React.DragEvent) => {
|
||||
if (!droppable) {
|
||||
return;
|
||||
}
|
||||
setIsDragOver(true);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
if (isDropInOnly) return;
|
||||
if (isDropInOnly) return;
|
||||
|
||||
const rect = ref.current?.getBoundingClientRect();
|
||||
const rect = ref.current?.getBoundingClientRect();
|
||||
|
||||
if (!rect) return;
|
||||
if (!rect) return;
|
||||
|
||||
if (e.clientY < rect.top + rect.height / 2) {
|
||||
setDragDirection('prev');
|
||||
} else if (e.clientY >= rect.top + rect.height / 2) {
|
||||
setDragDirection('next');
|
||||
}
|
||||
};
|
||||
if (e.clientY < rect.top + rect.height / 2) {
|
||||
setDragDirection('prev');
|
||||
} else if (e.clientY >= rect.top + rect.height / 2) {
|
||||
setDragDirection('next');
|
||||
}
|
||||
},
|
||||
[droppable, isDropInOnly]
|
||||
);
|
||||
|
||||
const onDragLeave = () => {
|
||||
const onDragLeave = useCallback(() => {
|
||||
if (!droppable) {
|
||||
return;
|
||||
}
|
||||
setDragDirection(undefined);
|
||||
setIsDragOver(false);
|
||||
};
|
||||
}, [droppable]);
|
||||
|
||||
const onDrop = (e: React.DragEvent) => {
|
||||
if (!droppable) {
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
const creatingComponent = e.dataTransfer?.getData('component') || '';
|
||||
const movingComponent = e.dataTransfer?.getData('moveComponent') || '';
|
||||
const onDrop = useCallback(
|
||||
(e: React.DragEvent) => {
|
||||
if (!droppable) {
|
||||
return;
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
const creatingComponent = e.dataTransfer?.getData('component') || '';
|
||||
const movingComponent = e.dataTransfer?.getData('moveComponent') || '';
|
||||
|
||||
let targetParentId = parentId;
|
||||
let targetParentSlot = parentSlot;
|
||||
let targetId = componentId;
|
||||
if (dragDirection === 'next' && isExpanded && hasSlot) {
|
||||
targetParentId = componentId;
|
||||
targetParentSlot = 'content';
|
||||
targetId = undefined;
|
||||
}
|
||||
let targetParentId = parentId;
|
||||
let targetParentSlot = parentSlot;
|
||||
let targetId = componentId;
|
||||
if (dragDirection === 'next' && isExpanded && hasSlot) {
|
||||
targetParentId = componentId;
|
||||
targetParentSlot = 'content';
|
||||
targetId = undefined;
|
||||
}
|
||||
|
||||
// move component before or after currentComponent
|
||||
if (movingComponent) {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
genOperation(registry, 'moveComponent', {
|
||||
fromId: movingComponent,
|
||||
toId: targetParentId,
|
||||
slot: targetParentSlot,
|
||||
targetId: targetId,
|
||||
direction: dragDirection,
|
||||
})
|
||||
);
|
||||
}
|
||||
// move component before or after currentComponent
|
||||
if (movingComponent) {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
genOperation(registry, 'moveComponent', {
|
||||
fromId: movingComponent,
|
||||
toId: targetParentId,
|
||||
slot: targetParentSlot,
|
||||
targetId: targetId,
|
||||
direction: dragDirection,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// create component as children
|
||||
if (creatingComponent) {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
genOperation(registry, 'createComponent', {
|
||||
componentType: creatingComponent,
|
||||
parentId: targetParentId,
|
||||
slot: targetParentSlot,
|
||||
targetId: targetId,
|
||||
direction: dragDirection,
|
||||
})
|
||||
);
|
||||
}
|
||||
setDragDirection(undefined);
|
||||
setIsDragOver(false);
|
||||
};
|
||||
// create component as children
|
||||
if (creatingComponent) {
|
||||
eventBus.send(
|
||||
'operation',
|
||||
genOperation(registry, 'createComponent', {
|
||||
componentType: creatingComponent,
|
||||
parentId: targetParentId,
|
||||
slot: targetParentSlot,
|
||||
targetId: targetId,
|
||||
direction: dragDirection,
|
||||
})
|
||||
);
|
||||
}
|
||||
setDragDirection(undefined);
|
||||
setIsDragOver(false);
|
||||
},
|
||||
[
|
||||
droppable,
|
||||
dragDirection,
|
||||
isExpanded,
|
||||
hasSlot,
|
||||
componentId,
|
||||
parentId,
|
||||
parentSlot,
|
||||
eventBus,
|
||||
registry,
|
||||
]
|
||||
);
|
||||
|
||||
const boxShadow = useMemo(() => {
|
||||
if (isDropInOnly) return '';
|
||||
@ -124,7 +140,7 @@ export const DropComponentWrapper: React.FC<Props> = props => {
|
||||
ref={ref}
|
||||
width="full"
|
||||
boxShadow={boxShadow}
|
||||
background={ isDragOver ? '#ffc6c6' : undefined}
|
||||
background={isDragOver ? '#ffc6c6' : undefined}
|
||||
onDrop={onDrop}
|
||||
onDragOver={onDragOver}
|
||||
onDragLeave={onDragLeave}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import React, { useMemo, useRef, useEffect } from 'react';
|
||||
import { ComponentSchema } from '@sunmao-ui/core';
|
||||
import { Box, Text, VStack } from '@chakra-ui/react';
|
||||
import { ComponentTree } from './ComponentTree';
|
||||
import { ComponentTreeWrapper } from './ComponentTree';
|
||||
import { DropComponentWrapper } from './DropComponentWrapper';
|
||||
import { resolveApplicationComponents } from '../../utils/resolveApplicationComponents';
|
||||
import ErrorBoundary from '../ErrorBoundary';
|
||||
@ -33,27 +33,26 @@ export const StructureTree: React.FC<Props> = props => {
|
||||
resolveApplicationComponents(realComponents);
|
||||
|
||||
return topLevelComponents.map(c => (
|
||||
<ComponentTree
|
||||
<ComponentTreeWrapper
|
||||
key={c.id}
|
||||
component={c}
|
||||
parentId={undefined}
|
||||
slot={undefined}
|
||||
childrenMap={childrenMap}
|
||||
selectedComponentId={selectedComponentId}
|
||||
onSelectComponent={onSelectComponent}
|
||||
services={services}
|
||||
isAncestorDragging={false}
|
||||
depth={0}
|
||||
/>
|
||||
));
|
||||
}, [realComponents, selectedComponentId, onSelectComponent, services]);
|
||||
}, [realComponents, onSelectComponent, services]);
|
||||
|
||||
useEffect(() => {
|
||||
wrapperRef.current
|
||||
?.querySelector(`#tree-item-${selectedComponentId}`)
|
||||
?.scrollIntoView({
|
||||
behavior:'smooth',
|
||||
block:'center'
|
||||
block: 'nearest',
|
||||
inline: 'nearest'
|
||||
});
|
||||
}, [selectedComponentId]);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user