Merge pull request #87 from Yuyz0112/main

Improve Editor UX
This commit is contained in:
tanbowensg 2021-10-18 10:32:11 +08:00 committed by GitHub
commit c2fe8d7231
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 241 additions and 80 deletions

View File

@ -1,4 +1,4 @@
import { FormControl, FormLabel, Input } from '@chakra-ui/react';
import { FormControl, FormLabel, Input, Box } from '@chakra-ui/react';
import { Application } from '@meta-ui/core';
import React from 'react';
import { eventBus } from '../../eventBus';
@ -31,10 +31,10 @@ export const ComponentForm: React.FC<Props> = props => {
});
return (
<div>
<Box p={4}>
<div>Component Form</div>
<div>ID: {selectedComponent?.id}</div>
<form>{fields}</form>
</div>
</Box>
);
};

View File

@ -1,43 +1,82 @@
import React, { ReactElement } from 'react';
import React from 'react';
import {
Tabs,
TabList,
Tab,
TabPanels,
TabPanel,
SimpleGrid,
Flex,
Box,
} from '@chakra-ui/react';
import { registry } from '../../metaUI';
export const ComponentList: React.FC = () => {
const componentList = React.useMemo(() => {
const groups: ReactElement[] = [];
registry.components.forEach((componentMap, version) => {
const components: ReactElement[] = [];
componentMap.forEach(c => {
const onDragStart = (e: any) => {
e.dataTransfer.setData('component', `${c.version}/${c.metadata.name}`);
};
const cEle = (
<div
key={c.metadata.name}
className="droppable-element"
draggable={true}
unselectable="on"
onDragStart={onDragStart}
>
{`${c.version}/${c.metadata.name}`}
</div>
);
components.push(cEle);
});
const cGroupEle = (
<div key={version}>
<div>
<strong>{version}</strong>
</div>
{components}
</div>
);
groups.push(cGroupEle);
});
return groups;
}, []);
return <div>{componentList}</div>;
return (
<Tabs>
<TabList>
{Array.from(registry.components.keys()).map(version => (
<Tab key={version}>{version}</Tab>
))}
</TabList>
<TabPanels>
{Array.from(registry.components.keys()).map(version => (
<TabPanel key={version} overflow="auto">
<SimpleGrid columns={4} spacing={1}>
{Array.from(registry.components.get(version)!.values()).map(c => {
const onDragStart = (e: any) => {
e.dataTransfer.setData('component', `${c.version}/${c.metadata.name}`);
};
const cEle = (
<Flex
key={c.metadata.name}
flexDirection="column"
align="center"
justify="start">
<Flex
className="droppable-element"
background="gray.100"
width="60px"
height="60px"
borderRadius="md"
align="center"
justify="center"
transition="ease 0.2s"
_hover={{
transform: 'scale(1.05)',
background: 'gray.200',
}}
p={2}
draggable
unselectable="on"
onDragStart={onDragStart}>
<svg
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="2693"
width="30"
height="30">
<path
d="M972.09863 1016.986301H259.506849c-19.638356 0-35.068493-15.430137-35.068493-35.068493V768.70137c-8.416438 5.610959-18.235616 9.819178-28.054794 12.624657h-1.40274c-12.624658 4.208219-26.652055 5.610959-39.276712 5.610959-77.150685 0-138.871233-63.123288-138.871233-138.871233 0-77.150685 63.123288-138.871233 138.871233-138.871232 14.027397 0 28.054795 2.805479 42.082191 7.013698 1.40274 0 2.805479 1.40274 4.20822 1.40274 7.013699 2.805479 14.027397 5.610959 19.638356 8.416438l2.805479 1.40274V269.326027c0-19.638356 15.430137-35.068493 35.068493-35.068493h228.646576l-4.20822-8.416438c-7.013699-11.221918-12.624658-22.443836-15.430137-35.068493v-1.40274c-4.208219-12.624658-5.610959-26.652055-5.610958-39.276712C462.90411 72.942466 526.027397 11.221918 601.775342 11.221918c77.150685 0 138.871233 63.123288 138.871233 138.871233 0 14.027397-2.805479 28.054795-7.013698 42.082191 0 1.40274-1.40274 2.805479-1.40274 4.20822-2.805479 7.013699-5.610959 14.027397-8.416438 19.638356l-7.013699 16.832877h255.29863c19.638356 0 35.068493 15.430137 35.068493 35.068493v314.213698c0 11.221918-5.610959 22.443836-15.430137 29.457535-9.819178 7.013699-22.443836 7.013699-33.665753 2.805479L897.753425 587.747945c-1.40274 0-1.40274-1.40274-2.80548-1.40274-2.805479-1.40274-7.013699-4.208219-11.221918-5.610958h-1.402739c-7.013699-2.805479-14.027397-2.805479-21.041096-2.80548-37.873973 0-68.734247 30.860274-68.734247 68.734247s30.860274 68.734247 68.734247 68.734246c7.013699 0 14.027397-1.40274 19.638356-2.805479 7.013699-1.40274 12.624658-5.610959 18.235616-8.416439 1.40274-1.40274 2.805479-1.40274 4.20822-2.805479l53.304109-25.249315c11.221918-5.610959 23.846575-4.208219 33.665754 1.40274s16.832877 18.235616 16.832876 29.457534V981.917808c0 19.638356-15.430137 35.068493-35.068493 35.068493z m-677.523288-70.136986h642.454795v-182.356164h-1.40274c-11.221918 7.013699-22.443836 12.624658-35.068493 16.832876h-1.40274c-12.624658 4.208219-26.652055 5.610959-39.276712 5.610959-77.150685 0-138.871233-63.123288-138.871233-138.871233 0-77.150685 63.123288-138.871233 138.871233-138.871232 14.027397 0 28.054795 2.805479 42.082192 7.013698 1.40274 0 2.805479 1.40274 4.208219 1.40274 7.013699 2.805479 14.027397 5.610959 19.638356 8.416438l9.819178 4.208219v-224.438356H662.093151c-11.221918 0-22.443836-5.610959-29.457535-15.430137-7.013699-9.819178-7.013699-22.443836-2.805479-33.665753l29.457534-67.331507c0-1.40274 1.40274-1.40274 1.40274-2.805479 1.40274-2.805479 4.208219-7.013699 5.610959-11.221918v-1.40274c2.805479-7.013699 2.805479-14.027397 2.805479-21.041096 0-37.873973-30.860274-68.734247-68.734246-68.734246s-68.734247 30.860274-68.734247 68.734246c0 7.013699 1.40274 14.027397 2.80548 19.638356 1.40274 7.013699 5.610959 12.624658 8.416438 18.235617 1.40274 1.40274 1.40274 2.805479 2.805479 4.208219l28.054795 60.317808c5.610959 11.221918 4.208219 23.846575-1.40274 33.665754s-18.235616 16.832877-29.457534 16.832876H294.575342v274.936987c0 11.221918-5.610959 22.443836-15.430137 29.457534-9.819178 7.013699-22.443836 7.013699-33.665753 2.805479L192.175342 589.150685c-1.40274 0-1.40274-1.40274-2.805479-1.40274-2.805479-1.40274-7.013699-4.208219-11.221918-5.610959h-1.40274c-7.013699-2.805479-14.027397-2.805479-21.041095-2.805479-37.873973 0-68.734247 30.860274-68.734247 68.734246s30.860274 68.734247 68.734247 68.734247c7.013699 0 14.027397-1.40274 19.638356-2.805479 7.013699-1.40274 12.624658-5.610959 18.235616-8.416439 1.40274-1.40274 2.805479-1.40274 4.208219-2.805479l46.290411-22.443836c11.221918-5.610959 23.846575-4.208219 33.665754 1.40274 9.819178 7.013699 16.832877 18.235616 16.832876 29.457534v235.660274z"
p-id="2694"></path>
</svg>
</Flex>
<Box
p={2}
whiteSpace="pre-wrap"
textAlign="center"
fontSize="x-small">
{c.metadata.displayName}
</Box>
</Flex>
);
return cEle;
})}
</SimpleGrid>
</TabPanel>
))}
</TabPanels>
</Tabs>
);
};

View File

@ -1,8 +1,9 @@
import { useMemo, useState } from 'react';
import { GridCallbacks } from '@meta-ui/runtime';
import { Box } from '@chakra-ui/react';
import { Box, Tabs, TabList, Tab, TabPanels, TabPanel } from '@chakra-ui/react';
import { css } from '@emotion/react';
import { App } from '../metaUI';
import { last } from 'lodash';
import { App, stateStore } from '../metaUI';
import { StructureTree } from './StructureTree';
import {
CreateComponentOperation,
@ -11,35 +12,40 @@ import {
import { eventBus } from '../eventBus';
import { ComponentForm } from './ComponentForm';
import { ComponentList } from './ComponentsList';
import { EditorHeader } from './EditorHeader';
import { PreviewModal } from './PreviewModal';
import { useAppModel } from '../operations/useAppModel';
import { KeyboardEventWrapper } from './KeyboardEventWrapper';
let count = 0;
export const Editor = () => {
const [selectedComponentId, setSelectedComponentId] = useState('');
const [scale, setScale] = useState(100);
const [preview, setPreview] = useState(false);
const { app } = useAppModel();
const Wrapper: React.FC<{ id: string }> = useMemo(() => {
return props => {
const style = css`
height: 100%;
box-shadow: 0 0 ${props.id === selectedComponentId ? 1 : 0}px red;
`;
const onClick = (e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation();
setSelectedComponentId(() => props.id);
};
return (
<div onClick={onClick} css={style}>
<Box
onClick={onClick}
css={style}
boxShadow={props.id === selectedComponentId ? 'outline' : undefined}>
{props.children}
</div>
</Box>
);
};
}, [selectedComponentId]);
const gridCallbacks: GridCallbacks = {
onDragStop(id, layout) {
console.log('dragstop');
eventBus.send(
'operation',
new ModifyComponentPropertyOperation(id, 'layout', layout)
@ -47,7 +53,8 @@ export const Editor = () => {
},
onDrop(id, layout, item, e) {
const component = e.dataTransfer?.getData('component') || '';
const componentId = `component${count++}`;
const componentName = last(component.split('/'));
const componentId = `${componentName}_${count++}`;
eventBus.send(
'operation',
new CreateComponentOperation(id, 'container', component, componentId)
@ -71,27 +78,86 @@ export const Editor = () => {
return (
<KeyboardEventWrapper selectedComponentId={selectedComponentId}>
<Box display="flex" height="100vh" width="100vw">
<Box flex="1">
<StructureTree app={app} onSelectComponent={id => setSelectedComponentId(id)} />
</Box>
<Box flex="1">
<strong>Drag Component to canvas!</strong>
<ComponentList />
</Box>
<Box flex="3" borderRight="2px solid black">
<App
options={app}
debugEvent={false}
debugStore={false}
gridCallbacks={gridCallbacks}
componentWrapper={Wrapper}
/>
</Box>
<Box flex="1" borderRight="2px solid black">
<ComponentForm app={app} selectedId={selectedComponentId} />
<Box display="flex" height="100vh" width="100vw" flexDirection="column">
<EditorHeader
scale={scale}
setScale={setScale}
onPreview={() => setPreview(true)}
/>
<Box display="flex" flex="1">
<Box width="280px" borderRightWidth="1px" borderColor="gray.200">
<Tabs
align="center"
height="100%"
display="flex"
flexDirection="column"
textAlign="left">
<TabList background="gray.50">
<Tab>UI Tree</Tab>
<Tab>State</Tab>
</TabList>
<TabPanels flex="1" overflow="auto">
<TabPanel p={0}>
<StructureTree
app={app}
onSelectComponent={id => setSelectedComponentId(id)}
/>
</TabPanel>
<TabPanel p={0}>
<pre>{JSON.stringify(stateStore, null, 2)}</pre>
</TabPanel>
</TabPanels>
</Tabs>
</Box>
<Box flex="1" background="gray.50" p={4}>
<Box
widht="100%"
height="100%"
background="white"
transform={`scale(${scale / 100})`}>
<App
options={app}
debugEvent={false}
debugStore={false}
gridCallbacks={gridCallbacks}
componentWrapper={Wrapper}
/>
</Box>
</Box>
<Box width="320px" borderLeftWidth="1px" borderColor="gray.200">
<Tabs
align="center"
textAlign="left"
height="100%"
display="flex"
flexDirection="column">
<TabList background="gray.50">
<Tab>Inspect</Tab>
<Tab>Insert</Tab>
</TabList>
<TabPanels flex="1" overflow="auto">
<TabPanel p={0}>
<ComponentForm app={app} selectedId={selectedComponentId} />
</TabPanel>
<TabPanel p={0}>
<ComponentList />
</TabPanel>
</TabPanels>
</Tabs>
</Box>
</Box>
</Box>
{preview && (
<PreviewModal onClose={() => setPreview(false)}>
<Box width="100%" height="100%">
<App
options={JSON.parse(JSON.stringify(app))}
debugEvent={false}
debugStore={false}
/>
</Box>
</PreviewModal>
)}
</KeyboardEventWrapper>
);
};

View File

@ -0,0 +1,30 @@
import React from 'react';
import { Flex, Button, Box } from '@chakra-ui/react';
export const EditorHeader: React.FC<{
scale: number;
setScale: (v: number) => void;
onPreview: () => void;
}> = ({ scale, setScale, onPreview }) => {
return (
<Flex p={2} borderBottomWidth="2px" borderColor="gray.200" align="center">
<Flex flex="1" />
<Flex flex="1" align="center" justify="center">
<Button size="sm" disabled={scale <= 50} onClick={() => setScale(scale - 10)}>
-
</Button>
<Box fontSize="sm" mx="2" width={10} textAlign="center">
{scale}%
</Box>
<Button size="sm" disabled={scale >= 100} onClick={() => setScale(scale + 10)}>
+
</Button>
</Flex>
<Flex flex="1" justify="end">
<Button colorScheme="blue" onClick={onPreview}>
preview
</Button>
</Flex>
</Flex>
);
};

View File

@ -0,0 +1 @@
export * from './EditorHeader';

View File

@ -0,0 +1,25 @@
import React from 'react';
import {
Modal,
ModalOverlay,
ModalHeader,
ModalContent,
ModalCloseButton,
ModalBody,
} from '@chakra-ui/react';
export const PreviewModal: React.FC<{ onClose: () => void }> = ({
onClose,
children,
}) => {
return (
<Modal onClose={onClose} size="full" isOpen>
<ModalOverlay />
<ModalContent>
<ModalHeader>Preview App</ModalHeader>
<ModalCloseButton />
<ModalBody>{children}</ModalBody>
</ModalContent>
</Modal>
);
};

View File

@ -0,0 +1 @@
export * from './PreviewModal';

View File

@ -30,10 +30,10 @@ function genComponent(
): ApplicationComponent {
const { version, name } = parseType(type);
const cImpl = registry.getComponent(version, name);
const initProperties = cImpl.metadata.defaultProperties;
const initProperties = cImpl.metadata.exampleProperties;
count++;
return {
id: id || `${name}${count}`,
id: id || `${name}_${count}`,
type: type,
properties: initProperties,
traits: [genSlotTrait(parentId, slot)],

View File

@ -87,8 +87,7 @@ const FormControlImpl: ComponentImplementation<{
<FormControl
isRequired={isRequired}
isInvalid={!hideInvalid && (isInvalid || (!inputValue && isRequired))}
css={FormControlCSS}
>
css={FormControlCSS}>
<div css={FormControlContentCSS}>
<FormLabel css={FormLabelCSS}>{label}</FormLabel>
<Slot css={FormItemCSS} slotsMap={slotsMap} slot="content" />
@ -117,7 +116,7 @@ export default {
name: 'formControl',
isResizable: false,
isDraggable: true,
displayName: 'FormControl',
displayName: 'Form Control',
description: 'chakra-ui formControl',
exampleProperties: {
label: 'name',

View File

@ -57,8 +57,7 @@ const NumberInput: ComponentImplementation<Static<typeof PropsSchema>> = ({
clampValueOnBlur={clampValueOnBlur}
allowMouseWheel={allowMouseWheel}
size={size}
onChange={onChange}
>
onChange={onChange}>
<NumberInputField />
<NumberInputStepper>
<NumberIncrementStepper {...customerIncrement} />
@ -106,7 +105,7 @@ export default {
metadata: {
name: 'number_input',
description: 'chakra_ui number input',
displayName: 'NumberInput',
displayName: 'Number Input',
isDraggable: true,
isResizable: true,
exampleProperties: {

View File

@ -43,8 +43,7 @@ const Radio: ComponentImplementation<Static<typeof PropsSchema>> = ({
name={name}
size={size}
spacing={spacing}
colorScheme={colorScheme}
>
colorScheme={colorScheme}>
<Text value={text} />
</BaseRadio>
);

View File

@ -48,8 +48,10 @@ export default {
isDraggable: true,
isResizable: true,
exampleProperties: {
raw: 'confirm',
format: 'plain',
text: {
raw: 'text',
format: 'plain',
},
},
exampleSize: [2, 1],
},