refactor structureTree

This commit is contained in:
Bowen Tan 2021-10-14 14:35:32 +08:00
parent ef869d5ad6
commit 45753ee0af
5 changed files with 162 additions and 66 deletions

View File

@ -92,8 +92,7 @@ export const Editor = () => {
height="100%"
display="flex"
flexDirection="column"
textAlign="left"
>
textAlign="left">
<TabList background="gray.50">
<Tab>UI Tree</Tab>
<Tab>State</Tab>
@ -102,6 +101,7 @@ export const Editor = () => {
<TabPanel p={0}>
<StructureTree
app={app}
selectedComponentId={selectedComponentId}
onSelectComponent={id => setSelectedComponentId(id)}
/>
</TabPanel>
@ -116,8 +116,7 @@ export const Editor = () => {
widht="100%"
height="100%"
background="white"
transform={`scale(${scale / 100})`}
>
transform={`scale(${scale / 100})`}>
{appComponent}
</Box>
</Box>
@ -127,8 +126,7 @@ export const Editor = () => {
textAlign="left"
height="100%"
display="flex"
flexDirection="column"
>
flexDirection="column">
<TabList background="gray.50">
<Tab>Inspect</Tab>
<Tab>Insert</Tab>

View File

@ -0,0 +1,27 @@
import { DeleteIcon } from '@chakra-ui/icons';
import { HStack, IconButton, Text } from '@chakra-ui/react';
type Props = {
title: string;
isSelected: boolean;
onClick: () => void;
onClickRemove: () => void;
};
export const ComponentItemView: React.FC<Props> = props => {
const { title, isSelected, onClick, onClickRemove } = props;
return (
<HStack width="full" justify="space-between">
<Text color={isSelected ? 'red.500' : 'black'} onClick={onClick}>
{title}
</Text>
<IconButton
variant="ghost"
size="sm"
aria-label="remove"
icon={<DeleteIcon />}
onClick={onClickRemove}
/>
</HStack>
);
};

View File

@ -0,0 +1,87 @@
import { Box, Text } from '@chakra-ui/react';
import { ApplicationComponent } from '@meta-ui/core';
import React, { useMemo } from 'react';
import { eventBus } from '../../eventBus';
import { registry } from '../../metaUI';
import {
CreateComponentOperation,
RemoveComponentOperation,
} from '../../operations/Operations';
import { ComponentItemView } from './ComponentItemView';
import { ChildrenMap } from './StructureTree';
type Props = {
component: ApplicationComponent;
childrenMap: ChildrenMap;
selectedComponentId: string;
onSelectComponent: (id: string) => void;
};
export const ComponentTree: React.FC<Props> = props => {
const { component, childrenMap, selectedComponentId, onSelectComponent } = props;
const slots = registry.getComponentByType(component.type).spec.slots;
const slotsEle = useMemo(() => {
if (slots.length === 0) {
return null;
}
const slotsMap = childrenMap.get(component.id);
return slots.map(slot => {
let slotContent;
const slotChildren = slotsMap?.get(slot);
if (slotChildren && slotChildren.length > 0) {
slotContent = slotChildren.map(c => {
return (
<ComponentTree
key={c.id}
component={c}
childrenMap={childrenMap}
selectedComponentId={selectedComponentId}
onSelectComponent={onSelectComponent}
/>
);
});
} else {
slotContent = (
<Text fontSize="sm" color="gray.500">
Empty
</Text>
);
}
const onDrop = (e: React.DragEvent) => {
const creatingComponent = e.dataTransfer?.getData('component') || '';
eventBus.send(
'operation',
new CreateComponentOperation(component.id, slot, creatingComponent)
);
};
return (
<Box key={slot} paddingLeft="6">
<Text color="gray.500" fontWeight="medium" onDrop={onDrop}>
Slot: {slot}
</Text>
{slotContent}
</Box>
);
});
}, [component, childrenMap]);
const onClickRemove = () => {
eventBus.send('operation', new RemoveComponentOperation(component.id));
};
return (
<Box key={component.id} width="full">
<ComponentItemView
title={component.id}
isSelected={component.id === selectedComponentId}
onClick={() => {
onSelectComponent(component.id);
}}
onClickRemove={onClickRemove}
/>
{slotsEle}
</Box>
);
};

View File

@ -1,17 +1,13 @@
import React, { DragEvent } from 'react';
import { Application } from '@meta-ui/core';
import { IconButton } from '@chakra-ui/react';
import { DeleteIcon } from '@chakra-ui/icons';
import React from 'react';
import { Application, ApplicationComponent } from '@meta-ui/core';
import { eventBus } from '../../eventBus';
import {
CreateComponentOperation,
RemoveComponentOperation,
} from '../../operations/Operations';
import { css } from '@emotion/react';
import { RemoveComponentOperation } from '../../operations/Operations';
import { ComponentItemView } from './ComponentItemView';
import { ComponentTree } from './ComponentTree';
import { Text, VStack } from '@chakra-ui/react';
type ChildrenMap = Map<string, SlotsMap>;
type SlotsMap = Map<string, Array<Application['spec']['components'][0]>>;
type Component = Application['spec']['components'][0];
export type ChildrenMap = Map<string, SlotsMap>;
type SlotsMap = Map<string, ApplicationComponent[]>;
type Props = {
app: Application;
@ -21,7 +17,7 @@ type Props = {
export const StructureTree: React.FC<Props> = props => {
const { app, selectedComponentId, onSelectComponent } = props;
const topLevelComponents: Component[] = [];
const topLevelComponents: ApplicationComponent[] = [];
const childrenMap: ChildrenMap = new Map();
const components = app.spec.components.filter(c => c.type !== 'core/v1/dummy');
@ -45,60 +41,42 @@ export const StructureTree: React.FC<Props> = props => {
}
});
function genTreeItem(component: Component) {
const slots = childrenMap.get(component.id);
let slotsEle;
if (slots) {
slotsEle = Array.from(slots.keys()).map(slot => {
const children = slots.get(slot)!.map(genTreeItem);
const onDrop = (e: DragEvent) => {
const creatingComponent = e.dataTransfer?.getData('component') || '';
console.log('createComponent', component.id, slot, creatingComponent);
eventBus.send(
'operation',
new CreateComponentOperation(component.id, slot, creatingComponent)
);
};
return (
<div key={slot}>
<div onDrop={onDrop}>slot: {slot}</div>
{children}
</div>
);
});
}
const topEles = topLevelComponents.map(c => (
<ComponentTree
key={c.id}
component={c}
childrenMap={childrenMap}
selectedComponentId={selectedComponentId}
onSelectComponent={onSelectComponent}
/>
));
const dataSourcesEles = dataSources.map(dummy => {
const onClickRemove = () => {
eventBus.send('operation', new RemoveComponentOperation(component.id));
eventBus.send('operation', new RemoveComponentOperation(dummy.id));
};
return (
<div key={component.id} style={{ paddingLeft: '16px' }}>
<strong
css={css`
color: ${component.id === selectedComponentId ? 'red' : 'black'};
`}
onClick={() => {
onSelectComponent(component.id);
}}
>
{component.id}
</strong>
<IconButton aria-label="remove" icon={<DeleteIcon />} onClick={onClickRemove} />
{slotsEle}
</div>
<ComponentItemView
key={dummy.id}
title={dummy.id}
isSelected={dummy.id === selectedComponentId}
onClick={() => {
onSelectComponent(dummy.id);
}}
onClickRemove={onClickRemove}
/>
);
}
const topEles = topLevelComponents.map(genTreeItem);
const dataSourcesEles = dataSources.map(genTreeItem);
});
return (
<div>
<strong>Components</strong>
<VStack spacing="2" padding="4" alignItems="start">
<Text fontSize="lg" fontWeight="bold">
Components
</Text>
{topEles}
<strong>DataSources</strong>
<Text fontSize="lg" fontWeight="bold">
DataSources
</Text>
{dataSourcesEles}
</div>
</VStack>
);
};

View File

@ -42,6 +42,7 @@ import {
ComponentImplementationProps,
TraitImplementation,
} from 'src/types/RuntimeSchema';
import { parseType } from '../utils/parseType';
export type ComponentImplementation<T = any> = React.FC<T & ComponentImplementationProps>;
@ -77,6 +78,11 @@ export class Registry {
return c;
}
getComponentByType(type: string): ImplementedRuntimeComponent {
const { version, name } = parseType(type);
return this.getComponent(version, name);
}
registerTrait(t: ImplementedRuntimeTrait) {
if (this.traits.get(t.version)?.has(t.metadata.name)) {
throw new Error(