mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2025-02-23 17:49:49 +08:00
Merge pull request #115 from webzard-io/runtime
add playground to editor
This commit is contained in:
commit
25b234c233
@ -4,6 +4,12 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>meta-ui runtime example: basic grid layout</title>
|
||||
<style>
|
||||
#root {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
@ -38,6 +38,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"@meta-ui/vite-plugins": "^1.0.0",
|
||||
"@types/codemirror": "^5.60.5",
|
||||
"@types/lodash-es": "^4.17.5",
|
||||
"@vitejs/plugin-react": "^1.0.1",
|
||||
|
16
packages/editor/playground.html
Normal file
16
packages/editor/playground.html
Normal file
@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" style="overflow: hidden">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>meta-ui playground</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module">
|
||||
import renderPlayground from './src/playground.tsx';
|
||||
import examples from '@example.json';
|
||||
renderPlayground(examples);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -6,7 +6,6 @@ import { TSchema } from '@sinclair/typebox';
|
||||
import { Application } from '@meta-ui/core';
|
||||
import { parseType, parseTypeBox } from '@meta-ui/runtime';
|
||||
import { eventBus } from '../../eventBus';
|
||||
import { registry } from '../../metaUI';
|
||||
import {
|
||||
ModifyComponentIdOperation,
|
||||
ModifyComponentPropertyOperation,
|
||||
@ -15,8 +14,13 @@ import {
|
||||
import { EventTraitForm } from './EventTraitForm';
|
||||
import { GeneralTraitFormList } from './GeneralTraitFormList';
|
||||
import { FetchTraitForm } from './FetchTraitForm';
|
||||
import { Registry } from '@meta-ui/runtime/lib/services/registry';
|
||||
|
||||
type Props = { selectedId: string; app: Application };
|
||||
type Props = {
|
||||
registry: Registry;
|
||||
selectedId: string;
|
||||
app: Application;
|
||||
};
|
||||
|
||||
export const renderField = (properties: {
|
||||
key: string;
|
||||
@ -58,7 +62,7 @@ export const renderField = (properties: {
|
||||
};
|
||||
|
||||
export const ComponentForm: React.FC<Props> = props => {
|
||||
const { selectedId, app } = props;
|
||||
const { selectedId, app, registry } = props;
|
||||
|
||||
const selectedComponent = app.spec.components.find(c => c.id === selectedId);
|
||||
if (!selectedComponent) {
|
||||
@ -110,9 +114,9 @@ export const ComponentForm: React.FC<Props> = props => {
|
||||
/>
|
||||
</FormControl>
|
||||
{propertyFields.length > 0 ? propertyForm : null}
|
||||
<EventTraitForm component={selectedComponent} />
|
||||
<EventTraitForm component={selectedComponent} registry={registry} />
|
||||
<FetchTraitForm component={selectedComponent} />
|
||||
<GeneralTraitFormList component={selectedComponent} />
|
||||
<GeneralTraitFormList component={selectedComponent} registry={registry} />
|
||||
</VStack>
|
||||
);
|
||||
};
|
||||
|
@ -13,12 +13,13 @@ import { Static } from '@sinclair/typebox';
|
||||
import { CloseIcon } from '@chakra-ui/icons';
|
||||
import { useFormik } from 'formik';
|
||||
import { EventHandlerSchema } from '@meta-ui/runtime';
|
||||
import { registry } from '../../../metaUI';
|
||||
import { useAppModel } from '../../../operations/useAppModel';
|
||||
import { formWrapperCSS } from '../style';
|
||||
import { KeyValueEditor } from '../../KeyValueEditor';
|
||||
import { Registry } from '@meta-ui/runtime/lib/services/registry';
|
||||
|
||||
type Props = {
|
||||
registry: Registry;
|
||||
eventTypes: string[];
|
||||
handler: Static<typeof EventHandlerSchema>;
|
||||
onChange: (hanlder: Static<typeof EventHandlerSchema>) => void;
|
||||
@ -27,7 +28,7 @@ type Props = {
|
||||
};
|
||||
|
||||
export const EventHandlerForm: React.FC<Props> = props => {
|
||||
const { handler, eventTypes, onChange, onRemove, hideEventType } = props;
|
||||
const { handler, eventTypes, onChange, onRemove, hideEventType, registry } = props;
|
||||
const { app } = useAppModel();
|
||||
const [methods, setMethods] = useState<string[]>([]);
|
||||
|
||||
|
@ -6,21 +6,22 @@ import produce from 'immer';
|
||||
import { ApplicationComponent } from '@meta-ui/core';
|
||||
import { EventHandlerSchema } from '@meta-ui/runtime';
|
||||
import { eventBus } from '../../../eventBus';
|
||||
import { registry } from '../../../metaUI';
|
||||
import {
|
||||
AddTraitOperation,
|
||||
ModifyTraitPropertyOperation,
|
||||
} from '../../../operations/Operations';
|
||||
import { EventHandlerForm } from './EventHandlerForm';
|
||||
import { Registry } from '@meta-ui/runtime/lib/services/registry';
|
||||
|
||||
type EventHandler = Static<typeof EventHandlerSchema>;
|
||||
|
||||
type Props = {
|
||||
registry: Registry;
|
||||
component: ApplicationComponent;
|
||||
};
|
||||
|
||||
export const EventTraitForm: React.FC<Props> = props => {
|
||||
const { component } = props;
|
||||
const { component, registry } = props;
|
||||
|
||||
const handlers: EventHandler[] = useMemo(() => {
|
||||
return component.traits.find(t => t.type === 'core/v1/event')?.properties
|
||||
@ -63,46 +64,48 @@ export const EventTraitForm: React.FC<Props> = props => {
|
||||
}
|
||||
};
|
||||
|
||||
const handlerForms = (handlers || []).map((h, i) => {
|
||||
const onChange = (handler: EventHandler) => {
|
||||
const newHanlders = produce(handlers!, draft => {
|
||||
draft[i] = handler;
|
||||
});
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new ModifyTraitPropertyOperation(
|
||||
component.id,
|
||||
'core/v1/event',
|
||||
'handlers',
|
||||
newHanlders
|
||||
)
|
||||
);
|
||||
};
|
||||
const handlerForms = () =>
|
||||
(handlers || []).map((h, i) => {
|
||||
const onChange = (handler: EventHandler) => {
|
||||
const newHanlders = produce(handlers!, draft => {
|
||||
draft[i] = handler;
|
||||
});
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new ModifyTraitPropertyOperation(
|
||||
component.id,
|
||||
'core/v1/event',
|
||||
'handlers',
|
||||
newHanlders
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const onRemove = () => {
|
||||
const newHanlders = produce(handlers!, draft => {
|
||||
draft.splice(i, 1);
|
||||
});
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new ModifyTraitPropertyOperation(
|
||||
component.id,
|
||||
'core/v1/event',
|
||||
'handlers',
|
||||
newHanlders
|
||||
)
|
||||
const onRemove = () => {
|
||||
const newHanlders = produce(handlers!, draft => {
|
||||
draft.splice(i, 1);
|
||||
});
|
||||
eventBus.send(
|
||||
'operation',
|
||||
new ModifyTraitPropertyOperation(
|
||||
component.id,
|
||||
'core/v1/event',
|
||||
'handlers',
|
||||
newHanlders
|
||||
)
|
||||
);
|
||||
};
|
||||
return (
|
||||
<EventHandlerForm
|
||||
key={i}
|
||||
handler={h}
|
||||
eventTypes={eventTypes}
|
||||
onChange={onChange}
|
||||
onRemove={onRemove}
|
||||
registry={registry}
|
||||
/>
|
||||
);
|
||||
};
|
||||
return (
|
||||
<EventHandlerForm
|
||||
key={i}
|
||||
handler={h}
|
||||
eventTypes={eventTypes}
|
||||
onChange={onChange}
|
||||
onRemove={onRemove}
|
||||
/>
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<VStack width="full">
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { AddIcon, ChevronDownIcon } from '@chakra-ui/icons';
|
||||
import { IconButton, Menu, MenuButton, MenuItem, MenuList } from '@chakra-ui/react';
|
||||
import { Registry } from '@meta-ui/runtime/lib/services/registry';
|
||||
import { useMemo } from 'react';
|
||||
import { registry } from '../../../metaUI';
|
||||
|
||||
type Props = {
|
||||
registry: Registry;
|
||||
onAddTrait: (traitType: string) => void;
|
||||
};
|
||||
|
||||
export const AddTraitButton: React.FC<Props> = props => {
|
||||
const { onAddTrait } = props;
|
||||
const { onAddTrait, registry } = props;
|
||||
|
||||
const traitTypes = useMemo(() => {
|
||||
return registry.getAllTraitTypes();
|
||||
|
@ -5,16 +5,17 @@ import { CloseIcon } from '@chakra-ui/icons';
|
||||
import { TSchema } from '@sinclair/typebox';
|
||||
import { renderField } from '../ComponentForm';
|
||||
import { formWrapperCSS } from '../style';
|
||||
import { registry } from '../../../metaUI';
|
||||
import { Registry } from '@meta-ui/runtime/lib/services/registry';
|
||||
|
||||
type Props = {
|
||||
registry: Registry;
|
||||
component: ApplicationComponent;
|
||||
trait: ComponentTrait;
|
||||
onRemove: () => void;
|
||||
};
|
||||
|
||||
export const GeneralTraitForm: React.FC<Props> = props => {
|
||||
const { trait, component, onRemove } = props;
|
||||
const { trait, component, onRemove, registry } = props;
|
||||
|
||||
const tImpl = registry.getTraitByType(trait.type);
|
||||
const properties = Object.assign(
|
||||
|
@ -5,16 +5,17 @@ import { TSchema } from '@sinclair/typebox';
|
||||
import { AddTraitButton } from './AddTraitButton';
|
||||
import { GeneralTraitForm } from './GeneralTraitForm';
|
||||
import { eventBus } from '../../../eventBus';
|
||||
import { registry } from '../../../metaUI';
|
||||
import { AddTraitOperation, RemoveTraitOperation } from '../../../operations/Operations';
|
||||
import { ignoreTraitsList } from '../../../constants';
|
||||
import { Registry } from '@meta-ui/runtime/lib/services/registry';
|
||||
|
||||
type Props = {
|
||||
registry: Registry;
|
||||
component: ApplicationComponent;
|
||||
};
|
||||
|
||||
export const GeneralTraitFormList: React.FC<Props> = props => {
|
||||
const { component } = props;
|
||||
const { component, registry } = props;
|
||||
|
||||
const onAddTrait = (type: string) => {
|
||||
const traitSpec = registry.getTraitByType(type).spec;
|
||||
@ -36,6 +37,7 @@ export const GeneralTraitFormList: React.FC<Props> = props => {
|
||||
component={component}
|
||||
trait={t}
|
||||
onRemove={onRemoveTrait}
|
||||
registry={registry}
|
||||
/>
|
||||
);
|
||||
});
|
||||
@ -44,7 +46,7 @@ export const GeneralTraitFormList: React.FC<Props> = props => {
|
||||
<VStack width="full" alignItems="start">
|
||||
<HStack width="full" justify="space-between">
|
||||
<strong>Traits</strong>
|
||||
<AddTraitButton onAddTrait={onAddTrait} />
|
||||
<AddTraitButton onAddTrait={onAddTrait} registry={registry} />
|
||||
</HStack>
|
||||
{traitFields}
|
||||
</VStack>
|
||||
|
@ -10,9 +10,13 @@ import {
|
||||
Box,
|
||||
} from '@chakra-ui/react';
|
||||
import { encodeDragDataTransfer, DROP_EXAMPLE_SIZE_PREFIX } from '@meta-ui/runtime';
|
||||
import { registry } from '../../metaUI';
|
||||
import { Registry } from '@meta-ui/runtime/lib/services/registry';
|
||||
|
||||
export const ComponentList: React.FC = () => {
|
||||
type Props = {
|
||||
registry: Registry;
|
||||
};
|
||||
|
||||
export const ComponentList: React.FC<Props> = ({ registry }) => {
|
||||
return (
|
||||
<Tabs>
|
||||
<TabList>
|
||||
|
@ -1,8 +1,7 @@
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { GridCallbacks, DIALOG_CONTAINER_ID } from '@meta-ui/runtime';
|
||||
import { GridCallbacks, DIALOG_CONTAINER_ID, initMetaUI } from '@meta-ui/runtime';
|
||||
import produce from 'immer';
|
||||
import { Box, Tabs, TabList, Tab, TabPanels, TabPanel } from '@chakra-ui/react';
|
||||
import { App, stateStore } from '../metaUI';
|
||||
import { StructureTree } from './StructureTree';
|
||||
import {
|
||||
CreateComponentOperation,
|
||||
@ -11,14 +10,29 @@ import {
|
||||
import { eventBus, SelectComponentEvent } from '../eventBus';
|
||||
import { ComponentForm } from './ComponentForm';
|
||||
import { ComponentList } from './ComponentsList';
|
||||
import { appModelManager, useAppModel } from '../operations/useAppModel';
|
||||
import { useAppModel } from '../operations/useAppModel';
|
||||
import { EditorHeader } from './EditorHeader';
|
||||
import { PreviewModal } from './PreviewModal';
|
||||
import { KeyboardEventWrapper } from './KeyboardEventWrapper';
|
||||
import { ComponentWrapper } from './ComponentWrapper';
|
||||
import { StateEditor } from './CodeEditor';
|
||||
import { AppModelManager } from '../operations/AppModelManager';
|
||||
|
||||
export const Editor = () => {
|
||||
type ReturnOfInit = ReturnType<typeof initMetaUI>;
|
||||
|
||||
type Props = {
|
||||
App: ReturnOfInit['App'];
|
||||
registry: ReturnOfInit['registry'];
|
||||
stateStore: ReturnOfInit['stateManager']['store'];
|
||||
appModelManager: AppModelManager;
|
||||
};
|
||||
|
||||
export const Editor: React.FC<Props> = ({
|
||||
App,
|
||||
registry,
|
||||
stateStore,
|
||||
appModelManager,
|
||||
}) => {
|
||||
const [selectedComponentId, setSelectedComponentId] = useState('');
|
||||
const [scale, setScale] = useState(100);
|
||||
const [preview, setPreview] = useState(false);
|
||||
@ -76,7 +90,7 @@ export const Editor = () => {
|
||||
|
||||
return (
|
||||
<KeyboardEventWrapper selectedComponentId={selectedComponentId}>
|
||||
<Box display="flex" height="100vh" width="100vw" flexDirection="column">
|
||||
<Box display="flex" height="100%" width="100%" flexDirection="column">
|
||||
<EditorHeader
|
||||
scale={scale}
|
||||
setScale={setScale}
|
||||
@ -102,6 +116,7 @@ export const Editor = () => {
|
||||
app={app}
|
||||
selectedComponentId={selectedComponentId}
|
||||
onSelectComponent={id => setSelectedComponentId(id)}
|
||||
registry={registry}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel p={0} height="100%">
|
||||
@ -140,10 +155,14 @@ export const Editor = () => {
|
||||
</TabList>
|
||||
<TabPanels flex="1" overflow="auto">
|
||||
<TabPanel p={0}>
|
||||
<ComponentForm app={app} selectedId={selectedComponentId} />
|
||||
<ComponentForm
|
||||
app={app}
|
||||
selectedId={selectedComponentId}
|
||||
registry={registry}
|
||||
/>
|
||||
</TabPanel>
|
||||
<TabPanel p={0}>
|
||||
<ComponentList />
|
||||
<ComponentList registry={registry} />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
|
@ -12,6 +12,9 @@ export const KeyboardEventWrapper: React.FC<Props> = props => {
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Box, Text, VStack } from '@chakra-ui/react';
|
||||
import { ApplicationComponent } from '@meta-ui/core';
|
||||
import { Registry } from '@meta-ui/runtime/lib/services/registry';
|
||||
import React, { useMemo, useState } from 'react';
|
||||
import { eventBus } from '../../eventBus';
|
||||
import { registry } from '../../metaUI';
|
||||
import {
|
||||
CreateComponentOperation,
|
||||
RemoveComponentOperation,
|
||||
@ -13,6 +13,7 @@ import { DropComponentWrapper } from './DropComponentWrapper';
|
||||
import { ChildrenMap } from './StructureTree';
|
||||
|
||||
type Props = {
|
||||
registry: Registry;
|
||||
component: ApplicationComponent;
|
||||
childrenMap: ChildrenMap;
|
||||
selectedComponentId: string;
|
||||
@ -20,7 +21,8 @@ type Props = {
|
||||
};
|
||||
|
||||
export const ComponentTree: React.FC<Props> = props => {
|
||||
const { component, childrenMap, selectedComponentId, onSelectComponent } = props;
|
||||
const { component, childrenMap, selectedComponentId, onSelectComponent, registry } =
|
||||
props;
|
||||
const slots = registry.getComponentByType(component.type).spec.slots;
|
||||
const [isExpanded, setIsExpanded] = useState(true);
|
||||
|
||||
@ -41,6 +43,7 @@ export const ComponentTree: React.FC<Props> = props => {
|
||||
childrenMap={childrenMap}
|
||||
selectedComponentId={selectedComponentId}
|
||||
onSelectComponent={onSelectComponent}
|
||||
registry={registry}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -9,18 +9,20 @@ import {
|
||||
import { ComponentItemView } from './ComponentItemView';
|
||||
import { ComponentTree } from './ComponentTree';
|
||||
import { DropComponentWrapper } from './DropComponentWrapper';
|
||||
import { Registry } from '@meta-ui/runtime/lib/services/registry';
|
||||
|
||||
export type ChildrenMap = Map<string, SlotsMap>;
|
||||
type SlotsMap = Map<string, ApplicationComponent[]>;
|
||||
|
||||
type Props = {
|
||||
registry: Registry;
|
||||
app: Application;
|
||||
selectedComponentId: string;
|
||||
onSelectComponent: (id: string) => void;
|
||||
};
|
||||
|
||||
export const StructureTree: React.FC<Props> = props => {
|
||||
const { app, selectedComponentId, onSelectComponent } = props;
|
||||
const { app, selectedComponentId, onSelectComponent, registry } = props;
|
||||
const topLevelComponents: ApplicationComponent[] = [];
|
||||
const childrenMap: ChildrenMap = new Map();
|
||||
|
||||
@ -52,6 +54,7 @@ export const StructureTree: React.FC<Props> = props => {
|
||||
childrenMap={childrenMap}
|
||||
selectedComponentId={selectedComponentId}
|
||||
onSelectComponent={onSelectComponent}
|
||||
registry={registry}
|
||||
/>
|
||||
));
|
||||
const dataSourcesEles = dataSources.map(dummy => {
|
||||
|
@ -1,16 +1,32 @@
|
||||
import { ChakraProvider } from '@chakra-ui/react';
|
||||
import { Application } from '@meta-ui/core';
|
||||
import { initMetaUI } from '@meta-ui/runtime';
|
||||
import { StrictMode } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import 'react-grid-layout/css/styles.css';
|
||||
import 'react-resizable/css/styles.css';
|
||||
|
||||
import { Editor } from './components/Editor';
|
||||
import { DefaultAppSchema } from './constants';
|
||||
import { AppModelManager } from './operations/AppModelManager';
|
||||
|
||||
export default function renderApp(app: Application = DefaultAppSchema) {
|
||||
const metaUI = initMetaUI();
|
||||
|
||||
const App = metaUI.App;
|
||||
const registry = metaUI.registry;
|
||||
const stateStore = metaUI.stateManager.store;
|
||||
const appModelManager = new AppModelManager(app, registry);
|
||||
|
||||
export default function renderApp() {
|
||||
ReactDOM.render(
|
||||
<StrictMode>
|
||||
<ChakraProvider>
|
||||
<Editor />
|
||||
<Editor
|
||||
App={App}
|
||||
registry={registry}
|
||||
stateStore={stateStore}
|
||||
appModelManager={appModelManager}
|
||||
/>
|
||||
</ChakraProvider>
|
||||
</StrictMode>,
|
||||
document.getElementById('root')
|
||||
|
@ -1,7 +0,0 @@
|
||||
import { initMetaUI } from '@meta-ui/runtime';
|
||||
|
||||
const metaUI = initMetaUI();
|
||||
|
||||
export const App = metaUI.App;
|
||||
export const registry = metaUI.registry;
|
||||
export const stateStore = metaUI.stateManager.store;
|
@ -13,9 +13,9 @@ import {
|
||||
ModifyTraitPropertiesOperation,
|
||||
} from './Operations';
|
||||
import { produce } from 'immer';
|
||||
import { registry } from '../metaUI';
|
||||
import { eventBus } from '../eventBus';
|
||||
import { set, isEqual } from 'lodash-es';
|
||||
import { Registry } from '@meta-ui/runtime/lib/services/registry';
|
||||
|
||||
function genSlotTrait(parentId: string, slot: string): ComponentTrait {
|
||||
return {
|
||||
@ -30,6 +30,7 @@ function genSlotTrait(parentId: string, slot: string): ComponentTrait {
|
||||
}
|
||||
|
||||
function genComponent(
|
||||
registry: Registry,
|
||||
type: string,
|
||||
id: string,
|
||||
parentId?: string,
|
||||
@ -50,17 +51,21 @@ function genComponent(
|
||||
export class AppModelManager {
|
||||
private undoStack: Operations[] = [];
|
||||
private app: Application;
|
||||
private registry: Registry;
|
||||
|
||||
constructor(app: Application) {
|
||||
constructor(app: Application, registry: Registry) {
|
||||
const appFromLS = localStorage.getItem('schema');
|
||||
if (appFromLS) {
|
||||
this.app = JSON.parse(appFromLS);
|
||||
} else {
|
||||
this.app = app;
|
||||
}
|
||||
this.registry = registry;
|
||||
|
||||
eventBus.on('undo', () => this.undo());
|
||||
eventBus.on('operation', o => this.apply(o));
|
||||
|
||||
this.updateApp(this.app);
|
||||
}
|
||||
|
||||
getApp() {
|
||||
@ -95,6 +100,7 @@ export class AppModelManager {
|
||||
case 'createComponent':
|
||||
const createO = o as CreateComponentOperation;
|
||||
const newComponent = genComponent(
|
||||
this.registry,
|
||||
createO.componentType,
|
||||
createO.componentId || this.genId(createO.componentType),
|
||||
createO.parentId,
|
||||
|
@ -2,12 +2,22 @@ import { Application } from '@meta-ui/core';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { DefaultAppSchema } from '../constants';
|
||||
import { eventBus } from '../eventBus';
|
||||
import { AppModelManager } from './AppModelManager';
|
||||
|
||||
export const appModelManager = new AppModelManager(DefaultAppSchema);
|
||||
function getDefaultAppFromLS() {
|
||||
try {
|
||||
const appFromLS = localStorage.getItem('schema');
|
||||
if (appFromLS) {
|
||||
return JSON.parse(appFromLS);
|
||||
}
|
||||
return DefaultAppSchema;
|
||||
} catch (error) {
|
||||
console.warn(error);
|
||||
return DefaultAppSchema;
|
||||
}
|
||||
}
|
||||
|
||||
export function useAppModel() {
|
||||
const [app, setApp] = useState<Application>(appModelManager.getApp());
|
||||
const [app, setApp] = useState<Application>(getDefaultAppFromLS());
|
||||
|
||||
useEffect(() => {
|
||||
const onAppChange = (app: Application) => {
|
||||
|
105
packages/editor/src/playground.tsx
Normal file
105
packages/editor/src/playground.tsx
Normal file
@ -0,0 +1,105 @@
|
||||
import { Flex, Box, ChakraProvider, Button } from '@chakra-ui/react';
|
||||
import { Application } from '@meta-ui/core';
|
||||
import { initMetaUI } from '@meta-ui/runtime';
|
||||
import { Registry } from '@meta-ui/runtime/lib/services/registry';
|
||||
import { StrictMode, useMemo, useState } from 'react';
|
||||
import ReactDOM from 'react-dom';
|
||||
import 'react-grid-layout/css/styles.css';
|
||||
import 'react-resizable/css/styles.css';
|
||||
|
||||
import { Editor } from './components/Editor';
|
||||
import { AppModelManager } from './operations/AppModelManager';
|
||||
|
||||
type Example = {
|
||||
name: string;
|
||||
value: {
|
||||
app: Application;
|
||||
modules?: Parameters<Registry['registerModule']>[0][];
|
||||
};
|
||||
};
|
||||
|
||||
const Playground: React.FC<{ examples: Example[] }> = ({ examples }) => {
|
||||
const [example, setExample] = useState<Example | null>(examples[0]);
|
||||
|
||||
const { App, registry, stateStore, appModelManager } = useMemo(() => {
|
||||
if (!example) {
|
||||
return {};
|
||||
}
|
||||
const metaUI = initMetaUI();
|
||||
const App = metaUI.App;
|
||||
const registry = metaUI.registry;
|
||||
const stateStore = metaUI.stateManager.store;
|
||||
|
||||
const { app, modules = [] } = example.value;
|
||||
modules.forEach(m => {
|
||||
registry.registerModule(m);
|
||||
});
|
||||
localStorage.removeItem('schema');
|
||||
const appModelManager = new AppModelManager(app, registry);
|
||||
|
||||
return {
|
||||
App,
|
||||
registry,
|
||||
stateStore,
|
||||
appModelManager,
|
||||
};
|
||||
}, [example]);
|
||||
|
||||
return (
|
||||
<Flex width="100vw" height="100vh">
|
||||
<Box shadow="md">
|
||||
<Box width="200px" height="100%" overflow="auto" pl={2}>
|
||||
{examples.map(e => (
|
||||
<Button
|
||||
variant={example === e ? 'solid' : 'ghost'}
|
||||
key={e.name}
|
||||
onClick={() => {
|
||||
/**
|
||||
* Currently, the data flow between the useAppModel and
|
||||
* the AppModelManager is a little wierd.
|
||||
* When initialize the AppModelManager, it will notify
|
||||
* the useAppModel hook, which will cause the Editor
|
||||
* component update.
|
||||
* React does not like this and throw Error to complain
|
||||
* the Editor and the Playground components are updating
|
||||
* together.
|
||||
* So we set example to null, which unmount the editor
|
||||
* first. Then we can re-create the editor with new example
|
||||
* spec.
|
||||
*/
|
||||
setExample(null);
|
||||
setTimeout(() => {
|
||||
setExample(e);
|
||||
}, 0);
|
||||
}}
|
||||
>
|
||||
{e.name}
|
||||
</Button>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
<Box flex="1">
|
||||
{appModelManager && (
|
||||
<Editor
|
||||
key={example!.name}
|
||||
App={App!}
|
||||
registry={registry!}
|
||||
stateStore={stateStore!}
|
||||
appModelManager={appModelManager}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default function renderPlayground(examples: Example[]) {
|
||||
ReactDOM.render(
|
||||
<StrictMode>
|
||||
<ChakraProvider>
|
||||
<Playground examples={examples} />
|
||||
</ChakraProvider>
|
||||
</StrictMode>,
|
||||
document.getElementById('root')
|
||||
);
|
||||
}
|
@ -1,5 +1,6 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { virtualExamplePlugin } from '@meta-ui/vite-plugins';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
@ -20,6 +21,7 @@ export default defineConfig({
|
||||
],
|
||||
},
|
||||
}),
|
||||
virtualExamplePlugin(),
|
||||
],
|
||||
define: {
|
||||
// https://github.com/satya164/react-simple-code-editor/issues/86
|
||||
|
@ -58,6 +58,7 @@
|
||||
"@babel/preset-env": "^7.15.6",
|
||||
"@babel/preset-react": "^7.14.5",
|
||||
"@babel/preset-typescript": "^7.15.0",
|
||||
"@meta-ui/vite-plugins": "^1.0.0",
|
||||
"@testing-library/react": "^12.1.0",
|
||||
"@types/lodash": "^4.14.170",
|
||||
"@types/lodash-es": "^4.17.5",
|
||||
|
@ -1,45 +1,6 @@
|
||||
import { defineConfig, Plugin } from 'vite';
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
function virtualExamplePlugin(): Plugin {
|
||||
const virtualFileId = '@example.json';
|
||||
|
||||
const exampleDir = path.join(__dirname, '../../examples');
|
||||
const examples = [];
|
||||
|
||||
function walk(dirOrFile: string, frags: string[]) {
|
||||
if (fs.statSync(dirOrFile).isDirectory()) {
|
||||
for (const subDir of fs.readdirSync(dirOrFile)) {
|
||||
walk(path.join(dirOrFile, subDir), frags.concat(subDir));
|
||||
}
|
||||
} else {
|
||||
if (path.extname(dirOrFile) !== '.json') {
|
||||
return;
|
||||
}
|
||||
const value = JSON.parse(fs.readFileSync(dirOrFile, 'utf-8'));
|
||||
const name = frags.join('/');
|
||||
examples.push({ name, value });
|
||||
}
|
||||
}
|
||||
|
||||
walk(exampleDir, []);
|
||||
|
||||
return {
|
||||
name: 'virtual-example-plugin',
|
||||
resolveId(id) {
|
||||
if (id === virtualFileId) {
|
||||
return virtualFileId;
|
||||
}
|
||||
},
|
||||
load(id) {
|
||||
if (id === virtualFileId) {
|
||||
return JSON.stringify(examples);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
import { virtualExamplePlugin } from '@meta-ui/vite-plugins';
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
|
11
packages/vite-plugins/README.md
Normal file
11
packages/vite-plugins/README.md
Normal file
@ -0,0 +1,11 @@
|
||||
# `@meta-ui/vite-plugins`
|
||||
|
||||
> TODO: description
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
const vitePlugins = require('@meta-ui/vite-plugins');
|
||||
|
||||
// TODO: DEMONSTRATE API
|
||||
```
|
44
packages/vite-plugins/index.js
Normal file
44
packages/vite-plugins/index.js
Normal file
@ -0,0 +1,44 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function virtualExamplePlugin() {
|
||||
const virtualFileId = '@example.json';
|
||||
|
||||
const exampleDir = path.join(__dirname, '../../examples');
|
||||
const examples = [];
|
||||
|
||||
function walk(dirOrFile, frags) {
|
||||
if (fs.statSync(dirOrFile).isDirectory()) {
|
||||
for (const subDir of fs.readdirSync(dirOrFile)) {
|
||||
walk(path.join(dirOrFile, subDir), frags.concat(subDir));
|
||||
}
|
||||
} else {
|
||||
if (path.extname(dirOrFile) !== '.json') {
|
||||
return;
|
||||
}
|
||||
const value = JSON.parse(fs.readFileSync(dirOrFile, 'utf-8'));
|
||||
const name = frags.join('/');
|
||||
examples.push({ name, value });
|
||||
}
|
||||
}
|
||||
|
||||
walk(exampleDir, []);
|
||||
|
||||
return {
|
||||
name: 'virtual-example-plugin',
|
||||
resolveId(id) {
|
||||
if (id === virtualFileId) {
|
||||
return virtualFileId;
|
||||
}
|
||||
},
|
||||
load(id) {
|
||||
if (id === virtualFileId) {
|
||||
return JSON.stringify(examples);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
virtualExamplePlugin,
|
||||
};
|
21
packages/vite-plugins/package.json
Normal file
21
packages/vite-plugins/package.json
Normal file
@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@meta-ui/vite-plugins",
|
||||
"version": "1.0.0",
|
||||
"description": "vite plugins for meta-ui",
|
||||
"author": "meta-ui developers",
|
||||
"homepage": "https://github.com/webzard-io/meta-ui#readme",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"main": "index.js",
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/webzard-io/meta-ui.git"
|
||||
},
|
||||
"scripts": {
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user