remove editor global instances

This commit is contained in:
Bowen Tan 2022-01-14 17:53:25 +08:00
parent 509a2585f1
commit 1c6cca1450
55 changed files with 981 additions and 867 deletions

View File

@ -1,9 +1,10 @@
import { AppModel } from '../../src/AppModel/AppModel';
import { ComponentId, ComponentType } from '../../src/AppModel/IAppModel';
import { registry } from '../sevices';
import { AppSchema, DuplicatedIdSchema } from './mock';
describe('AppModel test', () => {
const appModel = new AppModel(AppSchema.spec.components);
const appModel = new AppModel(AppSchema.spec.components, registry);
describe('resolve tree', () => {
it('resolve Tree corectlly', () => {
expect(appModel.allComponents.length).toBe(10);
@ -27,7 +28,7 @@ describe('AppModel test', () => {
it('detect duplicated dd', () => {
try {
new AppModel(DuplicatedIdSchema);
new AppModel(DuplicatedIdSchema, registry);
} catch (e: any) {
expect(e.message).toBe('Duplicate component id: hstack1');
}
@ -74,7 +75,7 @@ describe('AppModel test', () => {
expect(newComponent.appModel).toBe(appModel);
});
it('can append component from other appModel', () => {
const appModel2 = new AppModel(AppSchema.spec.components);
const appModel2 = new AppModel(AppSchema.spec.components, registry);
const newComponent2 = appModel2.createComponent('core/v1/text' as ComponentType);
expect(newComponent2.appModel).not.toBe(appModel);
appModel.appendChild(newComponent2);
@ -82,7 +83,7 @@ describe('AppModel test', () => {
});
describe('remove component', () => {
const appModel = new AppModel(AppSchema.spec.components);
const appModel = new AppModel(AppSchema.spec.components, registry);
const origin = appModel.toSchema();
appModel.removeComponent('text1' as any);
it('remove component', () => {

View File

@ -8,10 +8,11 @@ import {
import { AppSchema, EventHanlderMockSchema } from './mock';
import { produce } from 'immer';
import { get } from 'lodash-es';
import { registry } from '../sevices';
describe('ComponentModel test', () => {
it('compute component property', () => {
const appModel = new AppModel(AppSchema.spec.components);
const appModel = new AppModel(AppSchema.spec.components, registry);
const button1 = appModel.getComponentById('button1' as ComponentId)!;
expect(button1.allComponents.length).toEqual(1);
expect(button1.appModel).toEqual(appModel);
@ -31,7 +32,7 @@ describe('ComponentModel test', () => {
});
describe('update component property', () => {
const appModel = new AppModel(AppSchema.spec.components);
const appModel = new AppModel(AppSchema.spec.components, registry);
const origin = appModel.toSchema();
const text1 = appModel.getComponentById('text1' as any);
text1!.updateComponentProperty('value', { raw: 'hello', format: 'md' });
@ -57,7 +58,7 @@ describe('update component property', () => {
});
describe('update event trait handlers(array) property', () => {
const appModel = new AppModel(EventHanlderMockSchema);
const appModel = new AppModel(EventHanlderMockSchema, registry);
const button1 = appModel.getComponentById('button1' as any)!;
const oldHandlers = button1.traits[0].rawProperties.handlers;
const newHandlers = produce(oldHandlers, (draft: any) => {
@ -74,7 +75,7 @@ describe('update event trait handlers(array) property', () => {
});
describe('append to another component', () => {
const appModel = new AppModel(AppSchema.spec.components);
const appModel = new AppModel(AppSchema.spec.components, registry);
const origin = appModel.toSchema();
const newComponent = appModel.createComponent('core/v1/text' as ComponentType);
const parent = appModel.getComponentById('vstack1' as any)!;
@ -112,7 +113,7 @@ describe('append to another component', () => {
});
describe('append component to top level', () => {
const appModel = new AppModel(AppSchema.spec.components);
const appModel = new AppModel(AppSchema.spec.components, registry);
const origin = appModel.toSchema();
const text1 = appModel.getComponentById('text1' as any)!;
it('append component to top level', () => {
@ -126,7 +127,7 @@ describe('append to another component', () => {
describe('append component as child to self', () => {
it('append component to top level', () => {
const appModel = new AppModel(AppSchema.spec.components);
const appModel = new AppModel(AppSchema.spec.components, registry);
const origin = appModel.toSchema();
const hstack1 = appModel.getComponentById('hstack1' as any)!;
const text1 = appModel.getComponentById('text1' as any)!;
@ -138,7 +139,7 @@ describe('append component as child to self', () => {
});
describe('add trait', () => {
const appModel = new AppModel(AppSchema.spec.components);
const appModel = new AppModel(AppSchema.spec.components, registry);
const origin = appModel.toSchema();
const text1 = appModel.getComponentById('text1' as any);
text1!.addTrait('core/v1/state' as TraitType, { key: 'value' });
@ -156,7 +157,7 @@ describe('add trait', () => {
});
describe('remove trait', () => {
const appModel = new AppModel(AppSchema.spec.components);
const appModel = new AppModel(AppSchema.spec.components, registry);
const origin = appModel.toSchema();
const text1 = appModel.getComponentById('text1' as any)!;
text1!.removeTrait(text1.traits[0].id);
@ -175,7 +176,7 @@ describe('remove trait', () => {
describe('change component id', () => {
const newId = 'newHstack1' as ComponentId;
const appModel = new AppModel(AppSchema.spec.components);
const appModel = new AppModel(AppSchema.spec.components, registry);
const origin = appModel.toSchema();
const hStack1 = appModel.getComponentById('hstack1' as ComponentId)!;
hStack1.changeId(newId);
@ -210,7 +211,7 @@ describe('change component id', () => {
});
describe('move component', () => {
const appModel = new AppModel(AppSchema.spec.components);
const appModel = new AppModel(AppSchema.spec.components, registry);
const origin = appModel.toSchema();
const hstack1 = appModel.getComponentById('hstack1' as ComponentId)!;
const text1 = appModel.getComponentById('text1' as ComponentId)!;

View File

@ -0,0 +1,4 @@
import { initSunmaoEditor } from '../src';
import { sunmaoChakraUILib } from '@sunmao-ui/chakra-ui-lib';
export const { registry } = initSunmaoEditor();
registry.installLib(sunmaoChakraUILib);

View File

@ -1,6 +1,6 @@
import { registry } from '../../src/setup';
import { OrphanComponentSchema } from './mock';
import { SchemaValidator } from '../../src/validator';
import { registry } from '../sevices';
const schemaValidator = new SchemaValidator(registry);

View File

@ -1,10 +1,10 @@
import { registry } from '../../src/setup';
import {
ComponentInvalidSchema,
ComponentPropertyExpressionSchema,
ComponentWrongPropertyExpressionSchema,
} from './mock';
import { SchemaValidator } from '../../src/validator';
import { registry } from '../sevices';
const schemaValidator = new SchemaValidator(registry);

View File

@ -1,10 +1,10 @@
import { registry } from '../../src/setup';
import {
TraitInvalidSchema,
EventTraitSchema,
EventTraitTraitMethodSchema,
} from './mock';
import { SchemaValidator } from '../../src/validator';
import { registry } from '../sevices';
const schemaValidator = new SchemaValidator(registry);

View File

@ -1,4 +1,5 @@
import { ComponentSchema, parseType } from '@sunmao-ui/core';
import { Registry } from '@sunmao-ui/runtime';
import { ComponentModel } from './ComponentModel';
import {
ComponentId,
@ -16,7 +17,7 @@ export class AppModel implements IAppModel {
private componentMap: Record<ComponentId, IComponentModel> = {};
private componentsCount = 0;
constructor(components: ComponentSchema[]) {
constructor(components: ComponentSchema[], private registry: Registry) {
this.schema = components;
this.componentsCount = components.length;
this.resolveTree(components);
@ -55,8 +56,8 @@ export class AppModel implements IAppModel {
}
createComponent(type: ComponentType, id?: ComponentId): IComponentModel {
const component = genComponent(type, id || this.genId(type));
return new ComponentModel(this, component);
const component = genComponent(this.registry, type, id || this.genId(type));
return new ComponentModel(this, component, this.registry);
}
getComponentById(componentId: ComponentId): IComponentModel | undefined {
@ -93,7 +94,7 @@ export class AppModel implements IAppModel {
if (this.componentMap[c.id as ComponentId]) {
throw new Error(`Duplicate component id: ${c.id}`);
} else {
const comp = new ComponentModel(this, c);
const comp = new ComponentModel(this, c, this.registry);
this.componentMap[c.id as ComponentId] = comp;
return comp;
}

View File

@ -1,5 +1,6 @@
import { merge } from 'lodash-es';
import { parseTypeBox, Registry } from '@sunmao-ui/runtime';
import { ComponentSchema, MethodSchema, RuntimeComponent } from '@sunmao-ui/core';
import { registry } from '../setup';
import { genComponent, genTrait } from './utils';
import {
ComponentId,
@ -17,8 +18,6 @@ import {
} from './IAppModel';
import { TraitModel } from './TraitModel';
import { FieldModel } from './FieldModel';
import { merge } from 'lodash-es';
import { parseTypeBox } from '@sunmao-ui/runtime';
const SlotTraitType: TraitType = 'core/v1/slot' as TraitType;
@ -41,17 +40,21 @@ export class ComponentModel implements IComponentModel {
stateExample: Record<string, any> = {};
_isDirty = false;
constructor(public appModel: IAppModel, private schema: ComponentSchema) {
constructor(
public appModel: IAppModel,
private schema: ComponentSchema,
private registry: Registry
) {
this.schema = schema;
this.id = schema.id as ComponentId;
this.type = schema.type as ComponentType;
this.spec = registry.getComponentByType(this.type) as any;
this.spec = this.registry.getComponentByType(this.type) as any;
this.traits = schema.traits.map(t => new TraitModel(t, this));
this.genStateExample()
this.traits = schema.traits.map(t => new TraitModel(t, this, this.registry));
this.genStateExample();
this.parentId = this._slotTrait?.rawProperties.container.id;
this.parentSlot = this._slotTrait?.rawProperties.container.slot;
this.parentSlot = this._slotTrait?.rawProperties.container.slot;
this.properties = new FieldModel(schema.properties);
}
@ -120,7 +123,7 @@ export class ComponentModel implements IComponentModel {
this._isDirty = false;
const newProperties = this.rawProperties;
const newTraits = this.traits.map(t => t.toSchema());
const newSchema = genComponent(this.type, this.id, newProperties, newTraits);
const newSchema = genComponent(this.registry, this.type, this.id, newProperties, newTraits);
this.schema = newSchema;
}
return this.schema;
@ -133,10 +136,10 @@ export class ComponentModel implements IComponentModel {
addTrait(traitType: TraitType, properties: Record<string, unknown>): ITraitModel {
const traitSchema = genTrait(traitType, properties);
const trait = new TraitModel(traitSchema, this);
const trait = new TraitModel(traitSchema, this, this.registry);
this.traits.push(trait);
this._isDirty = true;
this.genStateExample()
this.genStateExample();
return trait;
}
@ -178,7 +181,7 @@ export class ComponentModel implements IComponentModel {
if (traitIndex === -1) return;
this.traits.splice(traitIndex, 1);
this._isDirty = true;
this.genStateExample()
this.genStateExample();
}
changeId(newId: ComponentId) {

View File

@ -1,5 +1,5 @@
import { TraitSchema, RuntimeTrait } from '@sunmao-ui/core';
import { registry } from '../setup';
import { Registry } from '@sunmao-ui/runtime';
import {
IComponentModel,
TraitType,
@ -20,22 +20,26 @@ export class TraitModel implements ITraitModel {
properties: IFieldModel;
_isDirty = false;
constructor(trait: TraitSchema, public parent: IComponentModel) {
constructor(
trait: TraitSchema,
public parent: IComponentModel,
private registry: Registry
) {
this.schema = trait;
this.parent = parent;
this.type = trait.type as TraitType;
this.id = `${this.parent.id}_trait${traitIdCount++}` as TraitId;
this.spec = registry.getTraitByType(this.type);
this.spec = this.registry.getTraitByType(this.type);
this.properties = new FieldModel(trait.properties);
this.properties = new FieldModel(trait.properties);
}
get rawProperties() {
return this.properties.rawValue
return this.properties.rawValue;
}
get methods() {
return this.spec ? this.spec.spec.methods : []
return this.spec ? this.spec.spec.methods : [];
}
toSchema(): TraitSchema {
@ -46,7 +50,7 @@ export class TraitModel implements ITraitModel {
}
updateProperty(key: string, value: any) {
this.properties.update({[key]: value})
this.properties.update({ [key]: value });
this._isDirty = true;
this.parent._isDirty = true;
}

View File

@ -1,11 +1,12 @@
import { ComponentSchema, TraitSchema } from '@sunmao-ui/core';
import { registry } from '../setup';
import { Registry } from '@sunmao-ui/runtime';
export function genComponent(
registry: Registry,
type: string,
id: string,
properties?: Record<string, unknown>,
traits: TraitSchema[] = []
traits: TraitSchema[] = [],
): ComponentSchema {
const cImpl = registry.getComponentByType(type);
const initProperties = properties || cImpl.metadata.exampleProperties;

View File

@ -1,11 +1,12 @@
import { cloneDeep } from 'lodash-es';
import { action, makeAutoObservable, observable, reaction, toJS } from 'mobx';
import { ComponentSchema, createModule } from '@sunmao-ui/core';
import { eventBus } from './eventBus';
import { Registry, StateManager } from '@sunmao-ui/runtime';
import { EventBusType } from './eventBus';
import { AppStorage } from './AppStorage';
import { registry, stateManager } from './setup';
import { SchemaValidator } from './validator';
import { addModuleId } from './utils/addModuleId';
import { cloneDeep } from 'lodash-es';
type EditingTarget = {
kind: 'app' | 'module';
@ -13,7 +14,7 @@ type EditingTarget = {
name: string;
};
class EditorStore {
export class EditorStore {
components: ComponentSchema[] = [];
// currentEditingComponents, it could be app's or module's components
_selectedComponentId = '';
@ -30,8 +31,49 @@ class EditorStore {
lastSavedComponentsVersion = 0;
appStorage = new AppStorage();
schemaValidator = new SchemaValidator(registry);
schemaValidator: SchemaValidator;
constructor(
private eventBus: EventBusType,
private registry: Registry,
private stateManager: StateManager
) {
this.schemaValidator = new SchemaValidator(this.registry)
makeAutoObservable(this, {
components: observable.shallow,
setComponents: action,
setDragOverComponentId: action,
});
this.eventBus.on('selectComponent', id => {
this.setSelectedComponentId(id);
});
// listen the change by operations, and save newComponents
this.eventBus.on('componentsChange', components => {
this.setComponents(components);
this.setCurrentComponentsVersion(this.currentComponentsVersion + 1);
if (this.validateResult.length === 0) {
this.saveCurrentComponents();
}
});
// when switch app or module, components should refresh
reaction(
() => this.currentEditingTarget,
target => {
if (target.name) {
this.setCurrentComponentsVersion(0);
this.setLastSavedComponentsVersion(0);
this.clearSunmaoGlobalState();
this.eventBus.send('componentsRefresh', this.originComponents);
this.setComponents(this.originComponents);
}
}
);
this.updateCurrentEditingTarget('app', this.app.version, this.app.metadata.name);
}
get app() {
return this.appStorage.app;
}
@ -65,43 +107,6 @@ class EditorStore {
return this.currentComponentsVersion === this.lastSavedComponentsVersion;
}
constructor() {
makeAutoObservable(this, {
components: observable.shallow,
setComponents: action,
setDragOverComponentId: action,
});
eventBus.on('selectComponent', id => {
this.setSelectedComponentId(id);
});
// listen the change by operations, and save newComponents
eventBus.on('componentsChange', components => {
this.setComponents(components);
this.setCurrentComponentsVersion(this.currentComponentsVersion + 1);
if (this.validateResult.length === 0) {
this.saveCurrentComponents();
}
});
// when switch app or module, components should refresh
reaction(
() => this.currentEditingTarget,
target => {
if (target.name) {
this.setCurrentComponentsVersion(0);
this.setLastSavedComponentsVersion(0);
this.clearSunmaoGlobalState();
eventBus.send('componentsRefresh', this.originComponents);
this.setComponents(this.originComponents);
}
}
);
this.updateCurrentEditingTarget('app', this.app.version, this.app.metadata.name);
}
// origin components of app of module
// when switch app or module, components should refresh
get originComponents(): ComponentSchema[] {
@ -119,11 +124,11 @@ class EditorStore {
}
clearSunmaoGlobalState() {
stateManager.clear();
this.stateManager.clear();
// reregister all modules
this.modules.forEach(m => {
const modules = createModule(addModuleId(cloneDeep(m)));
registry.registerModule(modules, true);
this.registry.registerModule(modules, true);
});
}
@ -167,5 +172,3 @@ class EditorStore {
this.lastSavedComponentsVersion = val;
};
}
export const editorStore = new EditorStore();

View File

@ -1,23 +1,22 @@
import React from 'react';
import { flatten } from 'lodash-es';
import { observer } from 'mobx-react-lite';
import { FormControl, FormLabel, Input, Textarea, VStack } from '@chakra-ui/react';
import { TSchema } from '@sinclair/typebox';
import { parseType } from '@sunmao-ui/core';
import { parseTypeBox } from '@sunmao-ui/runtime';
import { eventBus } from '../../eventBus';
import { EventTraitForm } from './EventTraitForm';
import { GeneralTraitFormList } from './GeneralTraitFormList';
import { FetchTraitForm } from './FetchTraitForm';
import { Registry } from '@sunmao-ui/runtime/lib/services/registry';
import SchemaField from './JsonSchemaForm/SchemaField';
import { genOperation } from '../../operations';
import { editorStore } from '../../EditorStore';
import { observer } from 'mobx-react-lite';
import ErrorBoundary from '../ErrorBoundary';
import { StyleTraitForm } from './StyleTraitForm';
import { EditorServices } from '../../types';
type Props = {
registry: Registry;
services: EditorServices;
};
export const renderField = (properties: {
@ -27,20 +26,22 @@ export const renderField = (properties: {
fullKey: string;
selectedComponentId: string;
index: number;
services: EditorServices;
}) => {
const { value, type, fullKey, selectedComponentId, index } = properties;
const { value, type, fullKey, selectedComponentId, index, services } = properties;
const { eventBus, registry } = services;
if (typeof value !== 'object') {
const ref = React.createRef<HTMLTextAreaElement>();
const onBlur = () => {
const operation = type
? genOperation('modifyTraitProperty', {
? genOperation(registry, 'modifyTraitProperty', {
componentId: selectedComponentId,
traitIndex: index,
properties: {
[fullKey]: ref.current?.value,
},
})
: genOperation('modifyComponentProperty', {
: genOperation(registry, 'modifyComponentProperty', {
componentId: selectedComponentId,
properties: {
[fullKey]: ref.current?.value,
@ -65,6 +66,7 @@ export const renderField = (properties: {
type: type,
fullKey: `${fullKey}.${childKey}`,
selectedComponentId,
services,
});
})
);
@ -73,7 +75,8 @@ export const renderField = (properties: {
};
export const ComponentForm: React.FC<Props> = observer(props => {
const { registry } = props;
const { services } = props;
const { editorStore, registry, eventBus } = services;
const { selectedComponent, selectedComponentId } = editorStore;
if (!selectedComponentId) {
return <div>No components selected. Click on a component to select it.</div>;
@ -92,7 +95,7 @@ export const ComponentForm: React.FC<Props> = observer(props => {
const changeComponentId = (selectedComponentId: string, value: string) => {
eventBus.send(
'operation',
genOperation('modifyComponentId', {
genOperation(registry, 'modifyComponentId', {
componentId: selectedComponentId,
newId: value,
})
@ -136,7 +139,7 @@ export const ComponentForm: React.FC<Props> = observer(props => {
onChange={newFormData => {
eventBus.send(
'operation',
genOperation('modifyComponentProperty', {
genOperation(registry, 'modifyComponentProperty', {
componentId: selectedComponentId,
properties: newFormData,
})
@ -146,10 +149,10 @@ export const ComponentForm: React.FC<Props> = observer(props => {
/>
</VStack>
</VStack>
<EventTraitForm component={selectedComponent} registry={registry} />
<FetchTraitForm component={selectedComponent} registry={registry} />
<StyleTraitForm component={selectedComponent} registry={registry} />
<GeneralTraitFormList component={selectedComponent} registry={registry} />
<EventTraitForm component={selectedComponent} services={services} />
<FetchTraitForm component={selectedComponent} services={services} />
<StyleTraitForm component={selectedComponent} services={services} />
<GeneralTraitFormList component={selectedComponent} services={services} />
</VStack>
</ErrorBoundary>
);

View File

@ -13,31 +13,36 @@ import { Static } from '@sinclair/typebox';
import { CloseIcon } from '@chakra-ui/icons';
import { observer } from 'mobx-react-lite';
import { useFormik } from 'formik';
import { EventHandlerSchema, Registry } from '@sunmao-ui/runtime';
import { EventHandlerSchema } from '@sunmao-ui/runtime';
import { formWrapperCSS } from '../style';
import { KeyValueEditor } from '../../KeyValueEditor';
import { editorStore } from '../../../EditorStore';
import { EditorServices } from '../../../types';
type Props = {
registry: Registry;
eventTypes: readonly string[];
handler: Static<typeof EventHandlerSchema>;
onChange: (hanlder: Static<typeof EventHandlerSchema>) => void;
onRemove: () => void;
hideEventType?: boolean;
services: EditorServices;
};
export const EventHandlerForm: React.FC<Props> = observer(props => {
const { handler, eventTypes, onChange, onRemove, hideEventType, registry } = props;
const { handler, eventTypes, onChange, onRemove, hideEventType, services } = props;
const { registry, editorStore } = services;
const { components } = editorStore;
const [methods, setMethods] = useState<string[]>([]);
const updateMethods = useCallback((componentId: string) => {
const type = components.find(c => c.id === componentId)?.type;
if (type) {
const componentSpec = registry.getComponentByType(type).spec;
setMethods(Object.keys(componentSpec.methods));
}
}, [components, registry])
const updateMethods = useCallback(
(componentId: string) => {
const type = components.find(c => c.id === componentId)?.type;
if (type) {
const componentSpec = registry.getComponentByType(type).spec;
setMethods(Object.keys(componentSpec.methods));
}
},
[components, registry]
);
useEffect(() => {
if (handler.componentId) {

View File

@ -5,20 +5,20 @@ import { Static } from '@sinclair/typebox';
import produce from 'immer';
import { ComponentSchema } from '@sunmao-ui/core';
import { EventHandlerSchema } from '@sunmao-ui/runtime';
import { eventBus } from '../../../eventBus';
import { EventHandlerForm } from './EventHandlerForm';
import { Registry } from '@sunmao-ui/runtime/lib/services/registry';
import { genOperation } from '../../../operations';
import { EditorServices } from '../../../types';
type EventHandler = Static<typeof EventHandlerSchema>;
type Props = {
registry: Registry;
component: ComponentSchema;
services: EditorServices;
};
export const EventTraitForm: React.FC<Props> = props => {
const { component, registry } = props;
const { component, services } = props;
const { eventBus, registry } = services;
const handlers: EventHandler[] = useMemo(() => {
return component.traits.find(t => t.type === 'core/v1/event')?.properties
@ -48,7 +48,7 @@ export const EventTraitForm: React.FC<Props> = props => {
if (!handlers) {
eventBus.send(
'operation',
genOperation('createTrait', {
genOperation(registry, 'createTrait', {
componentId: component.id,
traitType: 'core/v1/event',
properties: { handlers: [newHandler] }
@ -59,7 +59,7 @@ export const EventTraitForm: React.FC<Props> = props => {
const index = component.traits.findIndex(t => t.type === 'core/v1/event');
eventBus.send(
'operation',
genOperation('modifyTraitProperty', {
genOperation(registry, 'modifyTraitProperty', {
componentId: component.id,
traitIndex: index,
properties: { handlers: [...handlers, newHandler] }
@ -77,7 +77,7 @@ export const EventTraitForm: React.FC<Props> = props => {
});
eventBus.send(
'operation',
genOperation('modifyTraitProperty', {
genOperation(registry, 'modifyTraitProperty', {
componentId: component.id,
traitIndex: index,
properties: {
@ -94,7 +94,7 @@ export const EventTraitForm: React.FC<Props> = props => {
});
eventBus.send(
'operation',
genOperation('modifyTraitProperty', {
genOperation(registry, 'modifyTraitProperty', {
componentId: component.id,
traitIndex: index,
properties: {
@ -110,7 +110,7 @@ export const EventTraitForm: React.FC<Props> = props => {
key={i}
onChange={onChange}
onRemove={onRemove}
registry={registry}
services={services}
/>
);
});

View File

@ -1,3 +1,5 @@
import { useFormik } from 'formik';
import produce from 'immer';
import {
Box,
FormControl,
@ -10,43 +12,40 @@ import {
} from '@chakra-ui/react';
import { Static } from '@sinclair/typebox';
import { AddIcon, CloseIcon } from '@chakra-ui/icons';
import { useFormik } from 'formik';
import produce from 'immer';
import { ComponentSchema } from '@sunmao-ui/core';
import { EventCallBackHandlerSchema, FetchTraitPropertiesSchema } from '@sunmao-ui/runtime';
import {
EventCallBackHandlerSchema,
FetchTraitPropertiesSchema,
} from '@sunmao-ui/runtime';
import { formWrapperCSS } from '../style';
import { KeyValueEditor } from '../../KeyValueEditor';
import { EventHandlerForm } from '../EventTraitForm/EventHandlerForm';
import { eventBus } from '../../../eventBus';
import { Registry } from '@sunmao-ui/runtime/lib/services/registry';
import { genOperation } from '../../../operations';
import { EditorServices } from '../../../types';
type EventHandler = Static<typeof EventCallBackHandlerSchema>;
type Props = {
component: ComponentSchema;
registry: Registry;
services: EditorServices;
};
const httpMethods = ['get', 'post', 'put', 'delete', 'patch'];
export const FetchTraitForm: React.FC<Props> = props => {
const { component, registry } = props;
const { component, services } = props;
const { registry, eventBus } = services;
const fetchTrait = component.traits.find(t => t.type === 'core/v1/fetch')
?.properties as Static<typeof FetchTraitPropertiesSchema>;
if (!fetchTrait) {
return null;
}
const formik = useFormik({
initialValues: fetchTrait,
onSubmit: values => {
const index = component.traits.findIndex(t => t.type === 'core/v1/fetch');
eventBus.send(
'operation',
genOperation('modifyTraitProperty', {
genOperation(registry, 'modifyTraitProperty', {
componentId: component.id,
traitIndex: index,
properties: values,
@ -55,6 +54,10 @@ export const FetchTraitForm: React.FC<Props> = props => {
},
});
if (!fetchTrait) {
return null;
}
const urlField = (
<FormControl>
<FormLabel>URL</FormLabel>
@ -161,11 +164,11 @@ export const FetchTraitForm: React.FC<Props> = props => {
<EventHandlerForm
key={i}
eventTypes={[]}
handler={{type: '', ...handler}}
handler={{ type: '', ...handler }}
hideEventType={true}
onChange={onChange}
onRemove={onRemove}
registry={registry}
services={services}
/>
);
})}
@ -197,7 +200,7 @@ export const FetchTraitForm: React.FC<Props> = props => {
const index = component.traits.findIndex(t => t.type === 'core/v1/fetch');
eventBus.send(
'operation',
genOperation('removeTrait', {
genOperation(registry, 'removeTrait', {
componentId: component.id,
index,
})

View File

@ -5,18 +5,19 @@ import { CloseIcon } from '@chakra-ui/icons';
import { TSchema } from '@sinclair/typebox';
import { renderField } from '../ComponentForm';
import { formWrapperCSS } from '../style';
import { Registry } from '@sunmao-ui/runtime/lib/services/registry';
import { EditorServices } from '../../../types';
type Props = {
registry: Registry;
component: ComponentSchema;
trait: TraitSchema;
traitIndex: number;
onRemove: () => void;
services: EditorServices;
};
export const GeneralTraitForm: React.FC<Props> = props => {
const { trait, traitIndex, component, onRemove, registry } = props;
const { trait, traitIndex, component, onRemove, services } = props;
const { registry } = services;
const tImpl = registry.getTraitByType(trait.type);
const properties = Object.assign(
@ -32,6 +33,7 @@ export const GeneralTraitForm: React.FC<Props> = props => {
fullKey: key,
type: trait.type,
selectedComponentId: component.id,
services,
});
});
return (

View File

@ -1,28 +1,29 @@
import { TSchema } from '@sinclair/typebox';
import { ComponentSchema } from '@sunmao-ui/core';
import { parseTypeBox } from '@sunmao-ui/runtime';
import { HStack, VStack } from '@chakra-ui/react';
import { TSchema } from '@sinclair/typebox';
import { AddTraitButton } from './AddTraitButton';
import { GeneralTraitForm } from './GeneralTraitForm';
import { eventBus } from '../../../eventBus';
import { ignoreTraitsList } from '../../../constants';
import { Registry } from '@sunmao-ui/runtime/lib/services/registry';
import { genOperation } from '../../../operations';
import { EditorServices } from '../../../types';
type Props = {
registry: Registry;
component: ComponentSchema;
services: EditorServices;
};
export const GeneralTraitFormList: React.FC<Props> = props => {
const { component, registry } = props;
const { component, services } = props;
const { eventBus, registry } = services;
const onAddTrait = (type: string) => {
const traitSpec = registry.getTraitByType(type).spec;
const initProperties = parseTypeBox(traitSpec.properties as TSchema);
eventBus.send(
'operation',
genOperation('createTrait', {
genOperation(registry, 'createTrait', {
componentId: component.id,
traitType: type,
properties: initProperties,
@ -30,31 +31,30 @@ export const GeneralTraitFormList: React.FC<Props> = props => {
);
};
const traitFields = component.traits
.map((trait, index) => {
if (ignoreTraitsList.includes(trait.type)) {
return null
}
const onRemoveTrait = () => {
eventBus.send(
'operation',
genOperation('removeTrait', {
componentId: component.id,
index,
})
);
};
return (
<GeneralTraitForm
key={index}
component={component}
trait={trait}
traitIndex={index}
onRemove={onRemoveTrait}
registry={registry}
/>
const traitFields = component.traits.map((trait, index) => {
if (ignoreTraitsList.includes(trait.type)) {
return null;
}
const onRemoveTrait = () => {
eventBus.send(
'operation',
genOperation(registry, 'removeTrait', {
componentId: component.id,
index,
})
);
});
};
return (
<GeneralTraitForm
key={index}
component={component}
trait={trait}
traitIndex={index}
onRemove={onRemoveTrait}
services={services}
/>
);
});
return (
<VStack width="full" alignItems="start">

View File

@ -1,4 +1,6 @@
import { useCallback, useMemo } from 'react';
import produce from 'immer';
import { AddIcon, CloseIcon } from '@chakra-ui/icons';
import {
FormControl,
FormLabel,
@ -9,17 +11,14 @@ import {
Select,
} from '@chakra-ui/react';
import { ComponentSchema } from '@sunmao-ui/core';
import { Registry } from '@sunmao-ui/runtime';
import { CssEditor } from '../../../components/CodeEditor';
import { eventBus } from '../../../eventBus';
import { genOperation } from '../../../operations';
import { AddIcon, CloseIcon } from '@chakra-ui/icons';
import produce from 'immer';
import { formWrapperCSS } from '../style';
import { EditorServices } from '../../../types';
type Props = {
registry: Registry;
component: ComponentSchema;
services: EditorServices;
};
type Styles = Array<{
@ -28,7 +27,8 @@ type Styles = Array<{
}>;
export const StyleTraitForm: React.FC<Props> = props => {
const { component, registry } = props;
const { component, services } = props;
const { eventBus, registry } = services;
const styleSlots = useMemo(() => {
return registry.getComponentByType(component.type).spec.styleSlots;
@ -39,12 +39,12 @@ export const StyleTraitForm: React.FC<Props> = props => {
}, [component]);
const styleTrait = component.traits[styleTraitIndex];
const styles = (styleTrait?.properties.styles as Styles) || [];
const ddddddd = styleTrait?.properties.styles as Styles | undefined;
const createStyleTrait = () => {
eventBus.send(
'operation',
genOperation('createTrait', {
genOperation(registry, 'createTrait', {
componentId: component.id,
traitType: 'core/v1/style',
properties: {
@ -63,7 +63,7 @@ export const StyleTraitForm: React.FC<Props> = props => {
(newStyles: Styles) => {
eventBus.send(
'operation',
genOperation('modifyTraitProperty', {
genOperation(registry, 'modifyTraitProperty', {
componentId: component.id,
traitIndex: styleTraitIndex,
properties: {
@ -72,16 +72,16 @@ export const StyleTraitForm: React.FC<Props> = props => {
})
);
},
[component, styleTraitIndex]
[component.id, eventBus, registry, styleTraitIndex]
);
const addStyle = useCallback(() => {
const newStyles: Styles = styles.concat({
const newStyles: Styles = (ddddddd || []).concat({
styleSlot: styleSlots[0],
style: '',
});
updateStyles(newStyles);
}, [updateStyles, styleSlots, styles]);
}, [updateStyles, styleSlots, ddddddd]);
const onClickCreate = () => {
if (!styleTrait) {
@ -93,67 +93,64 @@ export const StyleTraitForm: React.FC<Props> = props => {
const changeStyleContent = useCallback(
(i: number, value: string) => {
const newStyles = produce(styles, draft => {
if (!ddddddd) return;
const newStyles = produce(ddddddd, draft => {
draft[i].style = value;
});
updateStyles(newStyles);
},
[updateStyles, styles]
[updateStyles, ddddddd]
);
const changeStyleSlot = useCallback(
(i: number, newSlot: string) => {
const newStyles = produce(styles, draft => {
if (!ddddddd) return;
const newStyles = produce(ddddddd, draft => {
draft[i].styleSlot = newSlot;
});
updateStyles(newStyles);
},
[updateStyles, styles]
[updateStyles, ddddddd]
);
const styleForms = useMemo(
() =>
styles.map(({ style, styleSlot }, i) => {
if (styles.length === 0) {
return null;
}
const removeStyle = () => {
const newStyles = styles.filter((_, j) => j !== i);
updateStyles(newStyles);
};
return (
<VStack key={`${styleSlot}-${i}`} css={formWrapperCSS} spacing="2">
<FormControl id={styleSlot}>
<FormLabel marginInlineEnd="0">
<HStack width="full" justify="space-between">
<Text>Style Slot</Text>
<IconButton
aria-label="remove style"
size="sm"
variant="ghost"
colorScheme="red"
icon={<CloseIcon />}
onClick={removeStyle}
/>
</HStack>
</FormLabel>
<Select
value={styleSlot}
onChange={e => changeStyleSlot(i, e.target.value)}
>
{styleSlots.map(s => (
<option key={s} value={s}>
{s}
</option>
))}
</Select>
</FormControl>
<CssEditor defaultCode={style} onBlur={v => changeStyleContent(i, v)} />
</VStack>
);
}),
[styles, changeStyleContent, changeStyleSlot, updateStyles]
);
const styleForms = useMemo(() => {
if (!ddddddd) {
return null;
}
return ddddddd.map(({ style, styleSlot }, i) => {
const removeStyle = () => {
const newStyles = ddddddd.filter((_, j) => j !== i);
updateStyles(newStyles);
};
return (
<VStack key={`${styleSlot}-${i}`} css={formWrapperCSS} spacing="2">
<FormControl id={styleSlot}>
<FormLabel marginInlineEnd="0">
<HStack width="full" justify="space-between">
<Text>Style Slot</Text>
<IconButton
aria-label="remove style"
size="sm"
variant="ghost"
colorScheme="red"
icon={<CloseIcon />}
onClick={removeStyle}
/>
</HStack>
</FormLabel>
<Select value={styleSlot} onChange={e => changeStyleSlot(i, e.target.value)}>
{styleSlots.map(s => (
<option key={s} value={s}>
{s}
</option>
))}
</Select>
</FormControl>
<CssEditor defaultCode={style} onBlur={v => changeStyleContent(i, v)} />
</VStack>
);
});
}, [ddddddd, styleSlots, updateStyles, changeStyleSlot, changeStyleContent]);
return (
<VStack width="full">

View File

@ -1,12 +1,12 @@
/* eslint-disable react-hooks/rules-of-hooks */
import React, { useMemo, useState } from 'react';
import { observer } from 'mobx-react-lite';
import { Text } from '@chakra-ui/react';
import { css, cx } from '@emotion/css';
import { ComponentWrapperType } from '@sunmao-ui/runtime';
import { observer } from 'mobx-react-lite';
import { editorStore } from '../EditorStore';
import { registry } from '../setup';
import { eventBus } from '../eventBus';
import { genOperation } from '../operations';
import { Text } from '@chakra-ui/react';
import { EditorServices } from '../types';
type ComponentEditorState = 'drag' | 'select' | 'hover' | 'idle';
@ -113,163 +113,166 @@ const outlineMaskStyle = css`
}
`;
export const ComponentWrapper: ComponentWrapperType = observer(props => {
const { component, parentType } = props;
const {
selectedComponentId,
setSelectedComponentId,
hoverComponentId,
setHoverComponentId,
dragOverComponentId,
setDragOverComponentId,
} = editorStore;
export function useComponentWrapper(services: EditorServices): ComponentWrapperType {
const { editorStore, registry, eventBus } = services;
return observer(props => {
const { component, parentType } = props;
const {
selectedComponentId,
setSelectedComponentId,
hoverComponentId,
setHoverComponentId,
dragOverComponentId,
setDragOverComponentId,
} = editorStore;
const [slots, isDroppable] = useMemo(() => {
const slots = registry.getComponentByType(component.type).spec.slots;
return [slots, slots.length > 0];
}, [component.type]);
const [slots, isDroppable] = useMemo(() => {
const slots = registry.getComponentByType(component.type).spec.slots;
return [slots, slots.length > 0];
}, [component.type]);
const [currentSlot, setCurrentSlot] = useState<string>();
const [currentSlot, setCurrentSlot] = useState<string>();
const componentEditorState: ComponentEditorState = useMemo(() => {
if (dragOverComponentId === component.id) {
return 'drag';
} else if (selectedComponentId === component.id) {
return 'select';
} else if (hoverComponentId === component.id) {
return 'hover';
} else {
return 'idle';
}
}, [dragOverComponentId, selectedComponentId, hoverComponentId, component.id]);
const componentEditorState: ComponentEditorState = useMemo(() => {
if (dragOverComponentId === component.id) {
return 'drag';
} else if (selectedComponentId === component.id) {
return 'select';
} else if (hoverComponentId === component.id) {
return 'hover';
} else {
return 'idle';
}
}, [dragOverComponentId, selectedComponentId, hoverComponentId, component.id]);
const [inline, fullHeight, vertical] = useMemo(() => {
return [
inlineList.includes(component.type),
fullHeightList.includes(parentType),
verticalStackList.includes(component.type),
];
}, [component.type, parentType]);
const [inline, fullHeight, vertical] = useMemo(() => {
return [
inlineList.includes(component.type),
fullHeightList.includes(parentType),
verticalStackList.includes(component.type),
];
}, [component.type, parentType]);
const onClickWrapper = (e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation();
setSelectedComponentId(component.id);
};
const onMouseEnterWrapper = (e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation();
setHoverComponentId(component.id);
};
const onMouseLeaveWrapper = (e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation();
setHoverComponentId('');
};
const onClickWrapper = (e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation();
setSelectedComponentId(component.id);
};
const onMouseEnterWrapper = (e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation();
setHoverComponentId(component.id);
};
const onMouseLeaveWrapper = (e: React.MouseEvent<HTMLElement>) => {
e.stopPropagation();
setHoverComponentId('');
};
const onDragEnter = (e: React.DragEvent<HTMLElement>) => {
if (!isDroppable) {
return;
}
e.stopPropagation();
const enter = findRelatedWrapper(e.target as HTMLElement);
if (!enter) {
// if enter a non-wrapper element (seems won't happen)
setDragOverComponentId(dragOverComponentId);
setCurrentSlot(undefined);
return;
}
if (!enter.droppable) {
// if not droppable element
const onDragEnter = (e: React.DragEvent<HTMLElement>) => {
if (!isDroppable) {
return;
}
e.stopPropagation();
const enter = findRelatedWrapper(e.target as HTMLElement);
if (!enter) {
// if enter a non-wrapper element (seems won't happen)
setDragOverComponentId(dragOverComponentId);
setCurrentSlot(undefined);
return;
}
if (!enter.droppable) {
// if not droppable element
setDragOverComponentId('');
setCurrentSlot(undefined);
return;
}
// update dragover component id
if (dragOverComponentId !== enter.id) {
setDragOverComponentId(enter.id);
setCurrentSlot(enter.slot);
} else if (currentSlot !== enter.slot && enter.slot) {
setCurrentSlot(enter.slot);
}
};
const onDragLeave = (e: React.DragEvent<HTMLElement>) => {
// not processing leave event when no element is marked as dragover
if (!isDroppable || !dragOverComponentId) {
return;
}
e.stopPropagation();
const enter = findRelatedWrapper(e.relatedTarget as HTMLElement);
if (!enter) {
// if entered element is not a sunmao wrapper, set dragId to ''
setDragOverComponentId('');
setCurrentSlot(undefined);
} else if ((!enter.slot && !enter.droppable) || enter.id !== component.id) {
setCurrentSlot(undefined);
}
};
const onDragOver = (e: React.DragEvent<HTMLElement>) => {
e.stopPropagation();
if (!isDroppable) return;
e.preventDefault();
};
const onDrop = (e: React.DragEvent) => {
if (!isDroppable) return;
e.stopPropagation();
e.preventDefault();
setDragOverComponentId('');
setCurrentSlot(undefined);
return;
}
// update dragover component id
if (dragOverComponentId !== enter.id) {
setDragOverComponentId(enter.id);
setCurrentSlot(enter.slot);
} else if (currentSlot !== enter.slot && enter.slot) {
setCurrentSlot(enter.slot);
}
};
const creatingComponent = e.dataTransfer?.getData('component') || '';
eventBus.send(
'operation',
genOperation(registry, 'createComponent', {
componentType: creatingComponent,
parentId: component.id,
slot: currentSlot,
})
);
};
const onDragLeave = (e: React.DragEvent<HTMLElement>) => {
// not processing leave event when no element is marked as dragover
if (!isDroppable || !dragOverComponentId) {
return;
}
e.stopPropagation();
const enter = findRelatedWrapper(e.relatedTarget as HTMLElement);
if (!enter) {
// if entered element is not a sunmao wrapper, set dragId to ''
setDragOverComponentId('');
setCurrentSlot(undefined);
} else if ((!enter.slot && !enter.droppable) || enter.id !== component.id) {
setCurrentSlot(undefined);
}
};
const onDragOver = (e: React.DragEvent<HTMLElement>) => {
e.stopPropagation();
if (!isDroppable) return;
e.preventDefault();
};
const onDrop = (e: React.DragEvent) => {
if (!isDroppable) return;
e.stopPropagation();
e.preventDefault();
setDragOverComponentId('');
setCurrentSlot(undefined);
const creatingComponent = e.dataTransfer?.getData('component') || '';
eventBus.send(
'operation',
genOperation('createComponent', {
componentType: creatingComponent,
parentId: component.id,
slot: currentSlot,
})
return (
<div
data-component={component.id}
data-droppable={isDroppable}
onDragEnter={onDragEnter}
onDragLeave={onDragLeave}
onDrop={onDrop}
onClick={onClickWrapper}
onMouseEnter={onMouseEnterWrapper}
onMouseLeave={onMouseLeaveWrapper}
onDragOver={onDragOver}
className={cx(
ComponentWrapperStyle,
inline ? 'inline' : undefined,
fullHeight ? 'full-height' : undefined
)}
>
<Text className={cx(outlineMaskTextStyle, componentEditorState)}>
{component.id}
</Text>
{props.children}
{isDroppable && componentEditorState === 'drag' ? (
<div className={cx('slots-wrapper', vertical ? 'vertical' : undefined)}>
{slots.map(slot => {
return (
<SlotWrapper
componentId={component.id}
state={slot === currentSlot ? 'over' : 'sibling'}
key={slot}
slotId={slot}
></SlotWrapper>
);
})}
</div>
) : (
<div className={cx(outlineMaskStyle, 'component', componentEditorState)}></div>
)}
</div>
);
};
return (
<div
data-component={component.id}
data-droppable={isDroppable}
onDragEnter={onDragEnter}
onDragLeave={onDragLeave}
onDrop={onDrop}
onClick={onClickWrapper}
onMouseEnter={onMouseEnterWrapper}
onMouseLeave={onMouseLeaveWrapper}
onDragOver={onDragOver}
className={cx(
ComponentWrapperStyle,
inline ? 'inline' : undefined,
fullHeight ? 'full-height' : undefined
)}
>
<Text className={cx(outlineMaskTextStyle, componentEditorState)}>
{component.id}
</Text>
{props.children}
{isDroppable && componentEditorState === 'drag' ? (
<div className={cx('slots-wrapper', vertical ? 'vertical' : undefined)}>
{slots.map(slot => {
return (
<SlotWrapper
componentId={component.id}
state={slot === currentSlot ? 'over' : 'sibling'}
key={slot}
slotId={slot}
></SlotWrapper>
);
})}
</div>
) : (
<div className={cx(outlineMaskStyle, 'component', componentEditorState)}></div>
)}
</div>
);
});
});
}
const SlotWrapperTyle = css`
flex-grow: 1;

View File

@ -4,19 +4,18 @@ import { GridCallbacks, DIALOG_CONTAINER_ID, initSunmaoUI } from '@sunmao-ui/run
import { Box, Tabs, TabList, Tab, TabPanels, TabPanel, Flex } from '@chakra-ui/react';
import { observer } from 'mobx-react-lite';
import { StructureTree } from './StructureTree';
import { eventBus } from '../eventBus';
import { ComponentList } from './ComponentsList';
import { EditorHeader } from './EditorHeader';
import { KeyboardEventWrapper } from './KeyboardEventWrapper';
import { ComponentWrapper } from './ComponentWrapper';
import { useComponentWrapper } from './ComponentWrapper';
import { StateEditor, SchemaEditor } from './CodeEditor';
import { Explorer } from './Explorer';
import { editorStore } from '../EditorStore';
import { genOperation } from '../operations';
import { ComponentForm } from './ComponentForm';
import ErrorBoundary from './ErrorBoundary';
import { PreviewModal } from './PreviewModal';
import { WarningArea } from './WarningArea';
import { EditorServices } from '../types';
type ReturnOfInit = ReturnType<typeof initSunmaoUI>;
@ -24,198 +23,220 @@ type Props = {
App: ReturnOfInit['App'];
registry: ReturnOfInit['registry'];
stateStore: ReturnOfInit['stateManager']['store'];
services: EditorServices;
};
export const Editor: React.FC<Props> = observer(({ App, registry, stateStore }) => {
const { components, selectedComponentId, modules } = editorStore;
export const Editor: React.FC<Props> = observer(
({ App, registry, stateStore, services }) => {
const { eventBus, editorStore } = services;
const { components, selectedComponentId, modules } = editorStore;
const [scale, setScale] = useState(100);
const [preview, setPreview] = useState(false);
const [codeMode, setCodeMode] = useState(false);
const [code, setCode] = useState('');
const [scale, setScale] = useState(100);
const [preview, setPreview] = useState(false);
const [codeMode, setCodeMode] = useState(false);
const [code, setCode] = useState('');
const gridCallbacks: GridCallbacks = useMemo(() => {
return {
// drag an existing component
onDragStop(id, layout) {
eventBus.send(
'operation',
genOperation('modifyComponentProperty', {
componentId: id,
properties: { layout },
})
);
},
// drag a new component from tool box
onDrop(id, layout, _, e) {
const component = e.dataTransfer?.getData('component') || '';
eventBus.send(
'operation',
genOperation('createComponent', {
componentType: component,
parentId: id,
slot: 'content',
layout,
})
);
},
};
}, []);
const gridCallbacks: GridCallbacks = useMemo(() => {
return {
// drag an existing component
onDragStop(id, layout) {
eventBus.send(
'operation',
genOperation(registry, 'modifyComponentProperty', {
componentId: id,
properties: { layout },
})
);
},
// drag a new component from tool box
onDrop(id, layout, _, e) {
const component = e.dataTransfer?.getData('component') || '';
eventBus.send(
'operation',
genOperation(registry, 'createComponent', {
componentType: component,
parentId: id,
slot: 'content',
layout,
})
);
},
};
}, [eventBus, registry]);
const app = useMemo<Application>(() => {
return {
version: 'sunmao/v1',
kind: 'Application',
metadata: {
name: 'some App',
},
spec: {
components,
},
};
}, [components]);
const app = useMemo<Application>(() => {
return {
version: 'sunmao/v1',
kind: 'Application',
metadata: {
name: 'some App',
},
spec: {
components,
},
};
}, [components]);
const appComponent = useMemo(() => {
return (
<ErrorBoundary>
<App
options={app}
debugEvent={false}
debugStore={false}
gridCallbacks={gridCallbacks}
componentWrapper={ComponentWrapper}
/>
</ErrorBoundary>
);
}, [App, app, gridCallbacks]);
const ComponentWrapper = useMemo(() => {
return useComponentWrapper(services);
}, [services]);
const renderMain = () => {
const appBox = (
<Box flex="1" background="gray.50" p={1} overflow="hidden">
<Box
width="full"
height="full"
background="white"
overflow="auto"
transform={`scale(${scale / 100})`}
position="relative"
>
<Box id={DIALOG_CONTAINER_ID} width="full" height="full" position="absolute" />
<Box width="full" overflow="auto">
{appComponent}
</Box>
<WarningArea />
</Box>
</Box>
);
if (codeMode) {
const appComponent = useMemo(() => {
return (
<Flex width="100%" height="100%">
<Box flex="1">
<SchemaEditor defaultCode={JSON.stringify(app, null, 2)} onChange={setCode} />
<ErrorBoundary>
<App
options={app}
debugEvent={false}
debugStore={false}
gridCallbacks={gridCallbacks}
componentWrapper={ComponentWrapper}
/>
</ErrorBoundary>
);
}, [App, ComponentWrapper, app, gridCallbacks]);
const renderMain = () => {
const appBox = (
<Box flex="1" background="gray.50" p={1} overflow="hidden">
<Box
width="full"
height="full"
background="white"
overflow="auto"
transform={`scale(${scale / 100})`}
position="relative"
>
<Box
id={DIALOG_CONTAINER_ID}
width="full"
height="full"
position="absolute"
/>
<Box width="full" overflow="auto">
{appComponent}
</Box>
<WarningArea services={services} />
</Box>
</Box>
);
if (codeMode) {
return (
<Flex width="100%" height="100%">
<Box flex="1">
<SchemaEditor
defaultCode={JSON.stringify(app, null, 2)}
onChange={setCode}
/>
</Box>
{appBox}
</Flex>
);
}
return (
<>
<Box
width="280px"
minWidth="280px"
borderRightWidth="1px"
borderColor="gray.200"
>
<Tabs
align="center"
height="100%"
display="flex"
flexDirection="column"
textAlign="left"
isLazy
>
<TabList background="gray.50">
<Tab>Explorer</Tab>
<Tab>UI Tree</Tab>
<Tab>State</Tab>
</TabList>
<TabPanels flex="1" overflow="auto">
<TabPanel>
<Explorer services={services}/>
</TabPanel>
<TabPanel p={0}>
<StructureTree
components={components}
selectedComponentId={selectedComponentId}
onSelectComponent={id => editorStore.setSelectedComponentId(id)}
services={services}
/>
</TabPanel>
<TabPanel p={0} height="100%">
<StateEditor code={JSON.stringify(stateStore, null, 2)} />
</TabPanel>
</TabPanels>
</Tabs>
</Box>
{appBox}
</Flex>
<Box
width="320px"
minWidth="320px"
borderLeftWidth="1px"
borderColor="gray.200"
overflow="auto"
>
<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" background="gray.50">
<TabPanel p={0}>
<ComponentForm services={services} />
</TabPanel>
<TabPanel p={0}>
<ComponentList registry={registry} />
</TabPanel>
</TabPanels>
</Tabs>
</Box>
</>
);
}
return (
<>
<Box width="280px" minWidth="280px" borderRightWidth="1px" borderColor="gray.200">
<Tabs
align="center"
height="100%"
display="flex"
flexDirection="column"
textAlign="left"
isLazy
>
<TabList background="gray.50">
<Tab>Explorer</Tab>
<Tab>UI Tree</Tab>
<Tab>State</Tab>
</TabList>
<TabPanels flex="1" overflow="auto">
<TabPanel>
<Explorer />
</TabPanel>
<TabPanel p={0}>
<StructureTree
components={components}
selectedComponentId={selectedComponentId}
onSelectComponent={id => editorStore.setSelectedComponentId(id)}
registry={registry}
/>
</TabPanel>
<TabPanel p={0} height="100%">
<StateEditor code={JSON.stringify(stateStore, null, 2)} />
</TabPanel>
</TabPanels>
</Tabs>
</Box>
{appBox}
<Box
width="320px"
minWidth="320px"
borderLeftWidth="1px"
borderColor="gray.200"
overflow="auto"
>
<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" background="gray.50">
<TabPanel p={0}>
<ComponentForm registry={registry} />
</TabPanel>
<TabPanel p={0}>
<ComponentList registry={registry} />
</TabPanel>
</TabPanels>
</Tabs>
</Box>
</>
);
};
};
return (
<KeyboardEventWrapper
components={components}
selectedComponentId={selectedComponentId}
>
<Box display="flex" height="100%" width="100%" flexDirection="column">
<EditorHeader
scale={scale}
setScale={setScale}
onPreview={() => setPreview(true)}
codeMode={codeMode}
onCodeMode={v => {
setCodeMode(v);
if (!v && code) {
eventBus.send(
'operation',
genOperation('replaceApp', {
app: JSON.parse(code).spec.components,
})
);
}
}}
/>
<Box display="flex" flex="1" overflow="auto">
{renderMain()}
return (
<KeyboardEventWrapper
components={components}
selectedComponentId={selectedComponentId}
services={services}
>
<Box display="flex" height="100%" width="100%" flexDirection="column">
<EditorHeader
scale={scale}
setScale={setScale}
onPreview={() => setPreview(true)}
codeMode={codeMode}
onCodeMode={v => {
setCodeMode(v);
if (!v && code) {
eventBus.send(
'operation',
genOperation(registry, 'replaceApp', {
app: JSON.parse(code).spec.components,
})
);
}
}}
/>
<Box display="flex" flex="1" overflow="auto">
{renderMain()}
</Box>
</Box>
</Box>
{preview && (
<PreviewModal onClose={() => setPreview(false)} app={app} modules={modules} />
)}
</KeyboardEventWrapper>
);
});
{preview && (
<PreviewModal onClose={() => setPreview(false)} app={app} modules={modules} />
)}
</KeyboardEventWrapper>
);
}
);

View File

@ -2,8 +2,13 @@ import React from 'react';
import ErrorBoundary from '../ErrorBoundary';
import { ExplorerForm } from './ExplorerForm/ExplorerForm';
import { ExplorerTree } from './ExplorerTree';
import { EditorServices } from '../../types';
export const Explorer: React.FC = () => {
type Props = {
services: EditorServices;
};
export const Explorer: React.FC<Props> = ({ services }) => {
const [isEditingMode, setIsEditingMode] = React.useState(false);
const [formType, setFormType] = React.useState<'app' | 'module'>('app');
const [currentVersion, setCurrentVersion] = React.useState<string>('');
@ -25,9 +30,14 @@ export const Explorer: React.FC = () => {
version={currentVersion}
name={currentName}
onBack={onBack}
services={services}
/>
</ErrorBoundary>
);
}
return <ErrorBoundary><ExplorerTree onEdit={onEdit} /></ErrorBoundary>;
return (
<ErrorBoundary>
<ExplorerTree onEdit={onEdit} services={services} />
</ErrorBoundary>
);
};

View File

@ -2,7 +2,7 @@ import React from 'react';
import { FormControl, FormLabel, Input, VStack } from '@chakra-ui/react';
import { useFormik } from 'formik';
import { observer } from 'mobx-react-lite';
import { editorStore } from '../../../EditorStore';
import { EditorServices } from '../../../types';
type AppMetaDataFormData = {
name: string;
@ -11,36 +11,40 @@ type AppMetaDataFormData = {
type AppMetaDataFormProps = {
data: AppMetaDataFormData;
services: EditorServices;
};
export const AppMetaDataForm: React.FC<AppMetaDataFormProps> = observer(({ data }) => {
const onSubmit = (value: AppMetaDataFormData) => {
editorStore.appStorage.saveAppMetaDataInLS(value)
};
const formik = useFormik({
initialValues: data,
onSubmit,
});
return (
<VStack>
<FormControl isRequired>
<FormLabel>App Version</FormLabel>
<Input
name="version"
value={formik.values.version}
onChange={formik.handleChange}
onBlur={() => formik.submitForm()}
/>
</FormControl>
<FormControl isRequired>
<FormLabel>App Name</FormLabel>
<Input
name="name"
value={formik.values.name}
onChange={formik.handleChange}
onBlur={() => formik.submitForm()}
/>
</FormControl>
</VStack>
);
});
export const AppMetaDataForm: React.FC<AppMetaDataFormProps> = observer(
({ data, services }) => {
const { editorStore } = services;
const onSubmit = (value: AppMetaDataFormData) => {
editorStore.appStorage.saveAppMetaDataInLS(value);
};
const formik = useFormik({
initialValues: data,
onSubmit,
});
return (
<VStack>
<FormControl isRequired>
<FormLabel>App Version</FormLabel>
<Input
name="version"
value={formik.values.version}
onChange={formik.handleChange}
onBlur={() => formik.submitForm()}
/>
</FormControl>
<FormControl isRequired>
<FormLabel>App Name</FormLabel>
<Input
name="name"
value={formik.values.name}
onChange={formik.handleChange}
onBlur={() => formik.submitForm()}
/>
</FormControl>
</VStack>
);
}
);

View File

@ -4,17 +4,19 @@ import { Button, Text, VStack } from '@chakra-ui/react';
import { ArrowLeftIcon } from '@chakra-ui/icons';
import { AppMetaDataForm } from './AppMetaDataForm';
import { ModuleMetaDataForm } from './ModuleMetaDataForm';
import { editorStore } from '../../../EditorStore';
import { EditorServices } from '../../../types';
type Props = {
formType: 'app' | 'module';
version: string;
name: string;
onBack: () => void;
services: EditorServices;
};
export const ExplorerForm: React.FC<Props> = observer(
({ formType, version, name, onBack }) => {
({ formType, version, name, onBack, services }) => {
const { editorStore } = services;
let form;
switch (formType) {
case 'app':
@ -22,7 +24,7 @@ export const ExplorerForm: React.FC<Props> = observer(
name,
version,
};
form = <AppMetaDataForm data={appMetaData} />;
form = <AppMetaDataForm data={appMetaData} services={services} />;
break;
case 'module':
const moduleSpec = editorStore.appStorage.modules.find(
@ -33,7 +35,7 @@ export const ExplorerForm: React.FC<Props> = observer(
version,
stateMap: moduleSpec?.spec.stateMap || {},
};
form = <ModuleMetaDataForm initData={moduleMetaData} />;
form = <ModuleMetaDataForm services={services} initData={moduleMetaData} />;
break;
}
return (

View File

@ -2,8 +2,8 @@ import React from 'react';
import { FormControl, FormLabel, Input, VStack } from '@chakra-ui/react';
import { useFormik } from 'formik';
import { observer } from 'mobx-react-lite';
import { editorStore } from '../../../EditorStore';
import { KeyValueEditor } from '../../KeyValueEditor';
import { EditorServices } from '../../../types';
type ModuleMetaDataFormData = {
name: string;
@ -13,10 +13,12 @@ type ModuleMetaDataFormData = {
type ModuleMetaDataFormProps = {
initData: ModuleMetaDataFormData;
services: EditorServices;
};
export const ModuleMetaDataForm: React.FC<ModuleMetaDataFormProps> = observer(
({ initData }) => {
({ initData, services }) => {
const { editorStore } = services;
const onSubmit = (value: ModuleMetaDataFormData) => {
editorStore.appStorage.saveModuleMetaDataInLS(
{ originName: initData.name, originVersion: initData.version },

View File

@ -3,102 +3,107 @@ import React from 'react';
import { observer } from 'mobx-react-lite';
import { AddIcon, DeleteIcon, EditIcon } from '@chakra-ui/icons';
import { ImplementedRuntimeModule } from '@sunmao-ui/runtime';
import { editorStore } from '../../EditorStore';
import { EditorServices } from '../../types';
type ExplorerTreeProps = {
onEdit: (kind: 'app' | 'module', version: string, name: string) => void;
services: EditorServices;
};
function genItemId(kind: 'app' | 'module', version: string, name: string) {
return `${kind}-${version}-${name}`;
}
export const ExplorerTree: React.FC<ExplorerTreeProps> = observer(({ onEdit }) => {
const { app, modules, currentEditingTarget, updateCurrentEditingTarget } = editorStore;
const appItemId = genItemId('app', app.version, app.metadata.name);
const [selectedItem, setSelectedItem] = React.useState<string | undefined>(
genItemId(
currentEditingTarget.kind,
currentEditingTarget.version,
currentEditingTarget.name
)
);
export const ExplorerTree: React.FC<ExplorerTreeProps> = observer(
({ onEdit, services }) => {
const { editorStore } = services;
const { app, modules, currentEditingTarget, updateCurrentEditingTarget } =
editorStore;
const appItemId = genItemId('app', app.version, app.metadata.name);
const [selectedItem, setSelectedItem] = React.useState<string | undefined>(
genItemId(
currentEditingTarget.kind,
currentEditingTarget.version,
currentEditingTarget.name
)
);
const onClickApp = () => {
setSelectedItem(appItemId);
updateCurrentEditingTarget('app', app.version, app.metadata.name);
};
const onEditApp = () => {
onEdit('app', app.version, app.metadata.name);
};
const appEditable =
currentEditingTarget.kind === 'app' &&
currentEditingTarget.name === app.metadata.name &&
currentEditingTarget.version === app.version;
const appItem = (
<ExplorerTreeItem
key={app.metadata.name}
title={`${app.version}/${app.metadata.name}`}
onClick={onClickApp}
isActive={selectedItem === appItemId}
onEdit={onEditApp}
editable={appEditable}
/>
);
const moduleItems = modules.map((module: ImplementedRuntimeModule) => {
const moduleItemId = genItemId('module', module.version, module.metadata.name);
const onClickModule = () => {
setSelectedItem(moduleItemId);
updateCurrentEditingTarget('module', module.version, module.metadata.name);
const onClickApp = () => {
setSelectedItem(appItemId);
updateCurrentEditingTarget('app', app.version, app.metadata.name);
};
const onEditModule = () => {
onEdit('module', module.version, module.metadata.name);
const onEditApp = () => {
onEdit('app', app.version, app.metadata.name);
};
const onRemove = () => {
editorStore.appStorage.removeModule(module.version, module.metadata.name);
};
const editable =
currentEditingTarget.kind === 'module' &&
currentEditingTarget.name === module.metadata.name &&
currentEditingTarget.version === module.version;
return (
const appEditable =
currentEditingTarget.kind === 'app' &&
currentEditingTarget.name === app.metadata.name &&
currentEditingTarget.version === app.version;
const appItem = (
<ExplorerTreeItem
key={module.metadata.name}
title={`${module.version}/${module.metadata.name}`}
onClick={onClickModule}
onRemove={onRemove}
isActive={selectedItem === moduleItemId}
onEdit={onEditModule}
editable={editable}
key={app.metadata.name}
title={`${app.version}/${app.metadata.name}`}
onClick={onClickApp}
isActive={selectedItem === appItemId}
onEdit={onEditApp}
editable={appEditable}
/>
);
});
return (
<VStack alignItems="start">
<Text fontSize="lg" fontWeight="bold">
Applications
</Text>
{appItem}
<Divider />
<HStack width="full" justifyContent="space-between">
<Text fontSize="lg" fontWeight="bold">
Modules
</Text>
<IconButton
aria-label="create module"
size="xs"
icon={<AddIcon />}
onClick={() => editorStore.appStorage.createModule()}
const moduleItems = modules.map((module: ImplementedRuntimeModule) => {
const moduleItemId = genItemId('module', module.version, module.metadata.name);
const onClickModule = () => {
setSelectedItem(moduleItemId);
updateCurrentEditingTarget('module', module.version, module.metadata.name);
};
const onEditModule = () => {
onEdit('module', module.version, module.metadata.name);
};
const onRemove = () => {
editorStore.appStorage.removeModule(module.version, module.metadata.name);
};
const editable =
currentEditingTarget.kind === 'module' &&
currentEditingTarget.name === module.metadata.name &&
currentEditingTarget.version === module.version;
return (
<ExplorerTreeItem
key={module.metadata.name}
title={`${module.version}/${module.metadata.name}`}
onClick={onClickModule}
onRemove={onRemove}
isActive={selectedItem === moduleItemId}
onEdit={onEditModule}
editable={editable}
/>
</HStack>
{moduleItems}
</VStack>
);
});
);
});
return (
<VStack alignItems="start">
<Text fontSize="lg" fontWeight="bold">
Applications
</Text>
{appItem}
<Divider />
<HStack width="full" justifyContent="space-between">
<Text fontSize="lg" fontWeight="bold">
Modules
</Text>
<IconButton
aria-label="create module"
size="xs"
icon={<AddIcon />}
onClick={() => editorStore.appStorage.createModule()}
/>
</HStack>
{moduleItems}
</VStack>
);
}
);
type ExplorerTreeItemProps = {
title: string;

View File

@ -1,21 +1,24 @@
import React, { useRef } from 'react';
import { Box } from '@chakra-ui/react';
import { css } from '@emotion/css';
import { ComponentSchema } from '@sunmao-ui/core';
import React, { useRef } from 'react';
import { eventBus } from '../eventBus';
import { genOperation } from '../operations';
import { PasteManager } from '../operations/PasteManager';
import { EditorServices } from '../types';
type Props = {
selectedComponentId: string;
components: ComponentSchema[];
services: EditorServices;
};
export const KeyboardEventWrapper: React.FC<Props> = ({
selectedComponentId,
components,
services,
children,
}) => {
const { eventBus,registry } = services;
const pasteManager = useRef(new PasteManager());
const style = css`
&:focus {
@ -29,7 +32,7 @@ export const KeyboardEventWrapper: React.FC<Props> = ({
case 'Backspace':
eventBus.send(
'operation',
genOperation('removeComponent', {
genOperation(registry, 'removeComponent', {
componentId: selectedComponentId,
})
);
@ -60,7 +63,7 @@ export const KeyboardEventWrapper: React.FC<Props> = ({
pasteManager.current.setPasteComponents(selectedComponentId, components);
eventBus.send(
'operation',
genOperation('removeComponent', {
genOperation(registry, 'removeComponent', {
componentId: selectedComponentId,
})
);
@ -70,7 +73,7 @@ export const KeyboardEventWrapper: React.FC<Props> = ({
if (e.metaKey || e.ctrlKey) {
eventBus.send(
'operation',
genOperation('pasteComponent', {
genOperation(registry, 'pasteComponent', {
parentId: selectedComponentId,
slot: 'content',
rootComponentId: pasteManager.current.rootComponentId,

View File

@ -1,24 +1,24 @@
import React, { useMemo, useState } from 'react';
import { Box, Text, VStack } from '@chakra-ui/react';
import { ComponentSchema } from '@sunmao-ui/core';
import { Registry } from '@sunmao-ui/runtime/lib/services/registry';
import React, { useMemo, useState } from 'react';
import { eventBus } from '../../eventBus';
import { ComponentItemView } from './ComponentItemView';
import { DropComponentWrapper } from './DropComponentWrapper';
import { ChildrenMap } from './StructureTree';
import { genOperation } from '../../operations';
import { EditorServices } from '../../types';
type Props = {
registry: Registry;
component: ComponentSchema;
childrenMap: ChildrenMap;
selectedComponentId: string;
onSelectComponent: (id: string) => void;
services: EditorServices;
};
export const ComponentTree: React.FC<Props> = props => {
const { component, childrenMap, selectedComponentId, onSelectComponent, registry } =
const { component, childrenMap, selectedComponentId, onSelectComponent, services } =
props;
const { registry, eventBus } = services;
const slots = registry.getComponentByType(component.type).spec.slots;
const [isExpanded, setIsExpanded] = useState(true);
@ -39,7 +39,7 @@ export const ComponentTree: React.FC<Props> = props => {
childrenMap={childrenMap}
selectedComponentId={selectedComponentId}
onSelectComponent={onSelectComponent}
registry={registry}
services={services}
/>
);
});
@ -53,7 +53,7 @@ export const ComponentTree: React.FC<Props> = props => {
const onCreateComponent = (creatingComponent: string) => {
eventBus.send(
'operation',
genOperation('createComponent', {
genOperation(registry, 'createComponent', {
componentType: creatingComponent,
parentId: component.id,
slot,
@ -65,7 +65,7 @@ export const ComponentTree: React.FC<Props> = props => {
if (movingComponent === component.id) return;
eventBus.send(
'operation',
genOperation('moveComponent', {
genOperation(registry, 'moveComponent', {
fromId: movingComponent,
toId: component.id,
slot,
@ -74,7 +74,10 @@ export const ComponentTree: React.FC<Props> = props => {
};
const slotName = (
<DropComponentWrapper onCreateComponent={onCreateComponent} onMoveComponent={onMoveComponent}>
<DropComponentWrapper
onCreateComponent={onCreateComponent}
onMoveComponent={onMoveComponent}
>
<Text color="gray.500" fontWeight="medium">
Slot: {slot}
</Text>
@ -92,12 +95,12 @@ export const ComponentTree: React.FC<Props> = props => {
</Box>
);
});
}, [slots, childrenMap, component.id, selectedComponentId, onSelectComponent, registry]);
}, [slots, childrenMap, component.id, selectedComponentId, onSelectComponent, services, eventBus, registry]);
const onClickRemove = () => {
eventBus.send(
'operation',
genOperation('removeComponent', {
genOperation(registry, 'removeComponent', {
componentId: component.id,
})
);
@ -107,7 +110,7 @@ export const ComponentTree: React.FC<Props> = props => {
if (slots.length === 0) return;
eventBus.send(
'operation',
genOperation('createComponent', {
genOperation(registry, 'createComponent', {
componentType: creatingComponent,
parentId: component.id,
slot: slots[0],
@ -118,7 +121,7 @@ export const ComponentTree: React.FC<Props> = props => {
const onMoveUp = () => {
eventBus.send(
'operation',
genOperation('adjustComponentOrder', {
genOperation(registry, 'adjustComponentOrder', {
componentId: component.id,
orientation: 'up',
})
@ -128,7 +131,7 @@ export const ComponentTree: React.FC<Props> = props => {
const onMoveDown = () => {
eventBus.send(
'operation',
genOperation('adjustComponentOrder', {
genOperation(registry, 'adjustComponentOrder', {
componentId: component.id,
orientation: 'down',
})
@ -139,7 +142,7 @@ export const ComponentTree: React.FC<Props> = props => {
if (movingComponent === component.id) return;
eventBus.send(
'operation',
genOperation('moveComponent', {
genOperation(registry, 'moveComponent', {
fromId: movingComponent,
toId: component.id,
slot: slots[0],
@ -154,7 +157,10 @@ export const ComponentTree: React.FC<Props> = props => {
width="full"
alignItems="start"
>
<DropComponentWrapper onCreateComponent={onCreateComponent} onMoveComponent={onMoveComponent}>
<DropComponentWrapper
onCreateComponent={onCreateComponent}
onMoveComponent={onMoveComponent}
>
<ComponentItemView
id={component.id}
title={component.id}

View File

@ -1,27 +1,27 @@
import React, { useMemo } from 'react';
import { ComponentSchema } from '@sunmao-ui/core';
import { Box, Text, VStack } from '@chakra-ui/react';
import { eventBus } from '../../eventBus';
import { ComponentItemView } from './ComponentItemView';
import { ComponentTree } from './ComponentTree';
import { DropComponentWrapper } from './DropComponentWrapper';
import { Registry } from '@sunmao-ui/runtime/lib/services/registry';
import { genOperation as genOperation } from '../../operations';
import { resolveApplicationComponents } from '../../utils/resolveApplicationComponents';
import ErrorBoundary from '../ErrorBoundary';
import { EditorServices } from '../../types';
export type ChildrenMap = Map<string, SlotsMap>;
type SlotsMap = Map<string, ComponentSchema[]>;
type Props = {
registry: Registry;
components: ComponentSchema[];
selectedComponentId: string;
onSelectComponent: (id: string) => void;
services: EditorServices;
};
export const StructureTree: React.FC<Props> = props => {
const { components, selectedComponentId, onSelectComponent, registry } = props;
const { components, selectedComponentId, onSelectComponent, services } = props;
const { eventBus, registry } = services;
const [realComponents, dataSources] = useMemo(() => {
const _realComponent: ComponentSchema[] = [];
@ -37,7 +37,8 @@ export const StructureTree: React.FC<Props> = props => {
}, [components]);
const componentEles = useMemo(() => {
const { topLevelComponents, childrenMap } = resolveApplicationComponents(realComponents);
const { topLevelComponents, childrenMap } =
resolveApplicationComponents(realComponents);
return topLevelComponents.map(c => (
<ComponentTree
@ -46,17 +47,17 @@ export const StructureTree: React.FC<Props> = props => {
childrenMap={childrenMap}
selectedComponentId={selectedComponentId}
onSelectComponent={onSelectComponent}
registry={registry}
services={services}
/>
));
}, [realComponents, selectedComponentId, onSelectComponent, registry]);
}, [realComponents, selectedComponentId, onSelectComponent, services]);
const dataSourceEles = useMemo(() => {
return dataSources.map(dummy => {
const onClickRemove = () => {
eventBus.send(
'operation',
genOperation('removeComponent', {
genOperation(registry, 'removeComponent', {
componentId: dummy.id,
})
);
@ -75,14 +76,14 @@ export const StructureTree: React.FC<Props> = props => {
/>
);
});
}, [dataSources, selectedComponentId, onSelectComponent]);
}, [dataSources, selectedComponentId, eventBus, registry, onSelectComponent]);
return (
<VStack spacing="2" padding="5" alignItems="start">
<Text fontSize="lg" fontWeight="bold">
Components
</Text>
<RootItem />
<RootItem services={services} />
{componentEles}
<Text fontSize="lg" fontWeight="bold">
DataSources
@ -92,11 +93,12 @@ export const StructureTree: React.FC<Props> = props => {
);
};
function RootItem() {
function RootItem(props: { services: EditorServices }) {
const { eventBus, registry } = props.services;
const onCreateComponent = (creatingComponent: string) => {
eventBus.send(
'operation',
genOperation('createComponent', {
genOperation(registry, 'createComponent', {
componentType: creatingComponent,
})
);
@ -105,7 +107,7 @@ function RootItem() {
if (movingComponent === 'root') return;
eventBus.send(
'operation',
genOperation('moveComponent', {
genOperation(registry, 'moveComponent', {
fromId: movingComponent,
toId: '__root__',
slot: '__root__',

View File

@ -15,9 +15,14 @@ import {
} from '@chakra-ui/react';
import { observer } from 'mobx-react-lite';
import React, { useMemo } from 'react';
import { editorStore } from '../EditorStore';
import { EditorServices } from '../types';
export const WarningArea: React.FC = observer(() => {
type Props = {
services: EditorServices;
};
export const WarningArea: React.FC<Props> = observer(({ services }) => {
const { editorStore } = services;
const [isCollapsed, setIsCollapsed] = React.useState(true);
const errorItems = useMemo(() => {
if (isCollapsed) {
@ -39,7 +44,7 @@ export const WarningArea: React.FC = observer(() => {
</Tr>
);
});
}, [isCollapsed, editorStore.validateResult]);
}, [isCollapsed, editorStore]);
const savedBadge = useMemo(() => {
return <Badge colorScheme="green">Saved</Badge>;
@ -59,7 +64,7 @@ export const WarningArea: React.FC = observer(() => {
<Badge colorScheme="red">Unsave</Badge>
</HStack>
);
}, []);
}, [editorStore]);
return (
<VStack

View File

@ -14,10 +14,14 @@ export type EventNames = {
selectComponent: string;
};
const emitter = mitt<EventNames>();
export const eventBus = {
on: emitter.on,
off: emitter.off,
send: emitter.emit,
};
export const initEventBus = () => {
const emitter = mitt<EventNames>();
return {
on: emitter.on,
off: emitter.off,
send: emitter.emit,
};
}
export type EventBusType = ReturnType<typeof initEventBus>

View File

@ -0,0 +1 @@
export { initSunmaoEditor } from './init';

View File

@ -0,0 +1,71 @@
import { Editor as _Editor } from './components/Editor';
import { initSunmaoUI } from '@sunmao-ui/runtime';
import { AppModelManager } from './operations/AppModelManager';
import React from 'react';
import {
ChakraProvider,
extendTheme,
withDefaultSize,
withDefaultVariant,
} from '@chakra-ui/react';
import { initEventBus } from './eventBus';
import { EditorStore } from './EditorStore';
export function initSunmaoEditor() {
const editorTheme = extendTheme(
withDefaultSize({
size: 'sm',
components: [
'Input',
'NumberInput',
'Checkbox',
'Radio',
'Textarea',
'Select',
'Switch',
],
}),
withDefaultVariant({
variant: 'filled',
components: ['Input', 'NumberInput', 'Textarea', 'Select'],
})
);
const ui = initSunmaoUI();
const App = ui.App;
const registry = ui.registry;
const stateManager = ui.stateManager;
const eventBus = initEventBus();
const appModelManager = new AppModelManager(eventBus);
const editorStore = new EditorStore(eventBus, registry, stateManager);
const services = {
App,
registry: ui.registry,
apiService: ui.apiService,
stateManager,
appModelManager,
eventBus,
editorStore,
};
const Editor: React.FC = () => {
return (
<ChakraProvider theme={editorTheme}>
<_Editor
App={App}
registry={registry}
stateStore={stateManager.store}
services={services}
/>
</ChakraProvider>
);
};
return {
Editor,
registry,
onChange: () => null,
onSave: () => null,
};
}

View File

@ -1,16 +1,11 @@
import {
ChakraProvider,
extendTheme,
withDefaultSize,
withDefaultVariant,
} from '@chakra-ui/react';
import { Registry } from '@sunmao-ui/runtime/lib/services/registry';
import { StrictMode } from 'react';
import ReactDOM from 'react-dom';
import { Registry } from '@sunmao-ui/runtime';
import { initSunmaoEditor } from './init';
import { sunmaoChakraUILib } from '@sunmao-ui/chakra-ui-lib';
import './styles.css';
import { Editor } from './components/Editor';
import { App as _App, registry, stateManager, ui } from './setup';
type Options = Partial<{
components: Parameters<Registry['registerComponent']>[0][];
@ -19,33 +14,7 @@ type Options = Partial<{
container: Element;
}>;
const editorTheme = extendTheme(
withDefaultSize({
size: 'sm',
components: [
'Input',
'NumberInput',
'Checkbox',
'Radio',
'Textarea',
'Select',
'Switch',
],
}),
withDefaultVariant({
variant: 'filled',
components: ['Input', 'NumberInput', 'Textarea', 'Select'],
})
);
export const App: React.FC = () => {
return (
<ChakraProvider theme={editorTheme}>
<Editor App={_App} registry={registry} stateStore={stateManager.store} />
</ChakraProvider>
);
};
export { registry, ui };
const { Editor, registry } = initSunmaoEditor();
export default function renderApp(options: Options = {}) {
const {
@ -57,10 +26,11 @@ export default function renderApp(options: Options = {}) {
components.forEach(c => registry.registerComponent(c));
traits.forEach(t => registry.registerTrait(t));
modules.forEach(m => registry.registerModule(m));
registry.installLib(sunmaoChakraUILib);
ReactDOM.render(
<StrictMode>
<App />
<Editor />
</StrictMode>,
container
);

View File

@ -1,12 +1,12 @@
import { ComponentSchema } from '@sunmao-ui/core';
import { eventBus } from '../eventBus';
import { EventBusType } from '../eventBus';
import { IUndoRedoManager, IOperation, OperationList } from './type';
export class AppModelManager implements IUndoRedoManager {
components: ComponentSchema[] = [];
operationStack: OperationList<IOperation> = new OperationList();
constructor() {
constructor(private eventBus: EventBusType) {
eventBus.on('undo', () => this.undo());
eventBus.on('redo', () => this.redo());
eventBus.on('operation', o => this.do(o));
@ -18,7 +18,7 @@ export class AppModelManager implements IUndoRedoManager {
updateComponents(components: ComponentSchema[]) {
this.components = components;
eventBus.send('componentsChange', this.components);
this.eventBus.send('componentsChange', this.components);
}
do(operation: IOperation): void {

View File

@ -24,7 +24,7 @@ export class CreateComponentBranchOperation extends BaseBranchOperation<CreateCo
}
// insert a new component to schema
this.operationStack.insert(
new CreateComponentLeafOperation({
new CreateComponentLeafOperation(this.registry, {
componentId: this.context.componentId!,
componentType: this.context.componentType,
parentId: this.context.parentId as ComponentId,
@ -43,7 +43,7 @@ export class CreateComponentBranchOperation extends BaseBranchOperation<CreateCo
} else if (parentComponent.type === 'core/v1/grid_layout') {
this.operationStack.insert(
// update grid layout for the new created component, it was pushed into layout by react-grid-layout, so we need to find it and update its id
new ModifyComponentPropertiesLeafOperation({
new ModifyComponentPropertiesLeafOperation(this.registry, {
componentId: parentComponent.id,
properties: {
layout: (prev: Array<ReactGridLayout.Layout>) => {

View File

@ -12,11 +12,11 @@ export type ModifyComponentIdBranchOperationContext = {
export class ModifyComponentIdBranchOperation extends BaseBranchOperation<ModifyComponentIdBranchOperationContext> {
do(prev: ComponentSchema[]): ComponentSchema[] {
this.operationStack.insert(new ModifyComponentIdLeafOperation(this.context));
this.operationStack.insert(new ModifyComponentIdLeafOperation(this.registry, this.context));
// update selectid
this.operationStack.insert(
new UpdateSelectComponentLeafOperation({
new UpdateSelectComponentLeafOperation(this.registry, {
// TODO: need a way to get selectedComponent.id here
// componentId: ApplicationComponent[]Instance.selectedComponent?.id,
componentId: '',

View File

@ -17,7 +17,7 @@ export class MoveComponentBranchOperation extends BaseBranchOperation<MoveCompon
if (this.context.toId === '__root__') {
this.operationStack.insert(
new RemoveTraitLeafOperation({
new RemoveTraitLeafOperation(this.registry, {
componentId: this.context.fromId,
index: traitIndex,
})
@ -28,7 +28,7 @@ export class MoveComponentBranchOperation extends BaseBranchOperation<MoveCompon
};
if (traitIndex > -1) {
this.operationStack.insert(
new ModifyTraitPropertiesLeafOperation({
new ModifyTraitPropertiesLeafOperation(this.registry, {
componentId: this.context.fromId,
traitIndex,
properties: newSlotProperties,
@ -36,7 +36,7 @@ export class MoveComponentBranchOperation extends BaseBranchOperation<MoveCompon
);
} else {
this.operationStack.insert(
new CreateTraitLeafOperation({
new CreateTraitLeafOperation(this.registry, {
componentId: this.context.fromId,
traitType: 'core/v1/slot',
properties: newSlotProperties,

View File

@ -14,13 +14,13 @@ export type RemoveComponentBranchOperationContext = {
export class RemoveComponentBranchOperation extends BaseBranchOperation<RemoveComponentBranchOperationContext> {
do(prev: ComponentSchema[]): ComponentSchema[] {
const appModel = new AppModel(prev);
const appModel = new AppModel(prev, this.registry);
const parent = appModel.getComponentById(this.context.componentId as ComponentId);
if (parent && parent.type === 'core/v1/grid_layout') {
// modify layout property of parent grid layout component
this.operationStack.insert(
new ModifyComponentPropertiesLeafOperation({
new ModifyComponentPropertiesLeafOperation(this.registry, {
componentId: parent.id,
properties: {
layout: (prev: Array<ReactGridLayout.Layout>) => {
@ -41,7 +41,7 @@ export class RemoveComponentBranchOperation extends BaseBranchOperation<RemoveCo
// free component from schema
this.operationStack.insert(
new RemoveComponentLeafOperation({ componentId: this.context.componentId })
new RemoveComponentLeafOperation(this.registry, { componentId: this.context.componentId })
);
// do the operation in order

View File

@ -1,3 +1,4 @@
import { Registry } from '@sunmao-ui/runtime';
import {
CreateComponentBranchOperation,
CreateComponentBranchOperationContext,
@ -46,8 +47,9 @@ const OperationConstructors: Record<
type OperationTypes = keyof OperationConfigMaps;
type OperationConfigMap<TOperation, TContext> = {
constructor: new (context: TContext) => TOperation;
constructor: new (registry: Registry, context: TContext) => TOperation;
context: TContext;
registry: Registry;
};
type OperationConfigMaps = {
@ -96,11 +98,12 @@ type OperationConfigMaps = {
};
export const genOperation = <T extends OperationTypes>(
registry: Registry,
type: T,
context: OperationConfigMaps[T]['context']
context: OperationConfigMaps[T]['context'],
): IOperation => {
const OperationConstructor = OperationConstructors[
type
] as OperationConfigMaps[T]['constructor'];
return new OperationConstructor(context as any);
return new OperationConstructor(registry, context as any);
};

View File

@ -20,7 +20,7 @@ export class AdjustComponentOrderLeafOperation extends BaseLeafOperation<AdjustC
}
private move(prev: ComponentSchema[], orientation: 'up' | 'down'): ComponentSchema[] {
const appModel = new AppModel(prev);
const appModel = new AppModel(prev, this.registry);
const component = appModel.getComponentById(this.context.componentId as ComponentId);
if (!component) {
console.warn('component not found');

View File

@ -14,7 +14,7 @@ export class CreateComponentLeafOperation extends BaseLeafOperation<CreateCompon
private component!: IComponentModel;
do(prev: ComponentSchema[]): ComponentSchema[] {
const appModel = new AppModel(prev);
const appModel = new AppModel(prev, this.registry);
const component = appModel.createComponent(this.context.componentType as ComponentType, this.context.componentId as ComponentId);
if (this.context.parentId) {
const parent = appModel.getComponentById(this.context.parentId);
@ -34,7 +34,7 @@ export class CreateComponentLeafOperation extends BaseLeafOperation<CreateCompon
}
undo(prev: ComponentSchema[]): ComponentSchema[] {
const appModel = new AppModel(prev);
const appModel = new AppModel(prev, this.registry);
appModel.removeComponent(this.component.id)
return appModel.toSchema()
}

View File

@ -10,7 +10,7 @@ export type ModifyComponentIdLeafOperationContext = {
export class ModifyComponentIdLeafOperation extends BaseLeafOperation<ModifyComponentIdLeafOperationContext> {
do(prev: ComponentSchema[]): ComponentSchema[] {
const appModel = new AppModel(prev);
const appModel = new AppModel(prev, this.registry);
const component = appModel.getComponentById(this.context.componentId as ComponentId);
if (!component) {
console.warn('component not found');
@ -23,7 +23,7 @@ export class ModifyComponentIdLeafOperation extends BaseLeafOperation<ModifyComp
return this.do(prev);
}
undo(prev: ComponentSchema[]): ComponentSchema[] {
const appModel = new AppModel(prev);
const appModel = new AppModel(prev, this.registry);
const component = appModel.getComponentById(this.context.newId as ComponentId)!;
component.changeId(this.context.componentId as ComponentId);
return appModel.toSchema();

View File

@ -11,7 +11,7 @@ export type ModifyComponentPropertiesLeafOperationContext = {
export class ModifyComponentPropertiesLeafOperation extends BaseLeafOperation<ModifyComponentPropertiesLeafOperationContext> {
private previousState: Record<string, any> = {};
do(prev: ComponentSchema[]): ComponentSchema[] {
const appModel = new AppModel(prev);
const appModel = new AppModel(prev, this.registry);
const component = appModel.getComponentById(this.context.componentId as ComponentId);
if (component) {
for (const property in this.context.properties) {
@ -35,7 +35,7 @@ export class ModifyComponentPropertiesLeafOperation extends BaseLeafOperation<Mo
return newSchema;
}
redo(prev: ComponentSchema[]): ComponentSchema[] {
const appModel = new AppModel(prev);
const appModel = new AppModel(prev, this.registry);
const component = appModel.getComponentById(this.context.componentId as ComponentId);
if (!component) {
console.warn('component not found');
@ -48,7 +48,7 @@ export class ModifyComponentPropertiesLeafOperation extends BaseLeafOperation<Mo
return appModel.toSchema();
}
undo(prev: ComponentSchema[]): ComponentSchema[] {
const appModel = new AppModel(prev);
const appModel = new AppModel(prev, this.registry);
const component = appModel.getComponentById(this.context.componentId as ComponentId);
if (!component) {
console.warn('component not found');

View File

@ -15,12 +15,12 @@ export class PasteComponentLeafOperation extends BaseLeafOperation<PasteComponen
private componentCopy!: IComponentModel
do(prev: ComponentSchema[]): ComponentSchema[] {
const appModel = new AppModel(prev);
const appModel = new AppModel(prev, this.registry);
const targetParent = appModel.getComponentById(this.context.parentId as ComponentId);
if (!targetParent) {
return prev
}
const copyComponents = new AppModel(this.context.components);
const copyComponents = new AppModel(this.context.components, this.registry);
const component = copyComponents.getComponentById(this.context.rootComponentId as ComponentId);
if (!component){
return prev;
@ -39,7 +39,7 @@ export class PasteComponentLeafOperation extends BaseLeafOperation<PasteComponen
}
undo(prev: ComponentSchema[]): ComponentSchema[] {
const appModel = new AppModel(prev);
const appModel = new AppModel(prev, this.registry);
appModel.removeComponent(this.componentCopy.id);
return appModel.toSchema()
}

View File

@ -12,7 +12,7 @@ export class RemoveComponentLeafOperation extends BaseLeafOperation<RemoveCompon
private prevComponent?: IComponentModel;
do(prev: ComponentSchema[]): ComponentSchema[] {
const appModel = new AppModel(prev);
const appModel = new AppModel(prev, this.registry);
this.deletedComponent = appModel.getComponentById(
this.context.componentId as ComponentId
);
@ -29,7 +29,7 @@ export class RemoveComponentLeafOperation extends BaseLeafOperation<RemoveCompon
if (!this.deletedComponent) {
return prev;
}
const appModel = new AppModel(prev);
const appModel = new AppModel(prev, this.registry);
const parent = appModel.getComponentById(
this.deletedComponent.parentId as ComponentId
);

View File

@ -13,7 +13,7 @@ export class CreateTraitLeafOperation extends BaseLeafOperation<CreateTraitLeafO
private traitId!: TraitId;
do(prev: ComponentSchema[]): ComponentSchema[] {
const appModel = new AppModel(prev);
const appModel = new AppModel(prev, this.registry);
const component = appModel.getComponentById(this.context.componentId as ComponentId);
if (!component) {
return prev
@ -28,7 +28,7 @@ export class CreateTraitLeafOperation extends BaseLeafOperation<CreateTraitLeafO
}
undo(prev: ComponentSchema[]): ComponentSchema[] {
const appModel = new AppModel(prev);
const appModel = new AppModel(prev, this.registry);
const component = appModel.getComponentById(this.context.componentId as ComponentId);
if (!component) {
return prev

View File

@ -13,7 +13,7 @@ export type ModifyTraitPropertiesLeafOperationContext = {
export class ModifyTraitPropertiesLeafOperation extends BaseLeafOperation<ModifyTraitPropertiesLeafOperationContext> {
private previousState: Record<string, any> = {};
do(prev: ComponentSchema[]): ComponentSchema[] {
const appModel = new AppModel(prev);
const appModel = new AppModel(prev, this.registry);
const component = appModel.getComponentById(this.context.componentId as ComponentId);
if (!component) {
console.warn('component not found');
@ -35,7 +35,7 @@ export class ModifyTraitPropertiesLeafOperation extends BaseLeafOperation<Modify
return appModel.toSchema();
}
redo(prev: ComponentSchema[]): ComponentSchema[] {
const appModel = new AppModel(prev);
const appModel = new AppModel(prev, this.registry);
const component = appModel.getComponentById(this.context.componentId as ComponentId);
if (!component) {
console.warn('component not found');
@ -51,7 +51,7 @@ export class ModifyTraitPropertiesLeafOperation extends BaseLeafOperation<Modify
}
undo(prev: ComponentSchema[]): ComponentSchema[] {
const appModel = new AppModel(prev);
const appModel = new AppModel(prev, this.registry);
const component = appModel.getComponentById(this.context.componentId as ComponentId);
if (!component) {
console.warn('component not found');

View File

@ -11,7 +11,7 @@ export type RemoveTraitLeafOperationContext = {
export class RemoveTraitLeafOperation extends BaseLeafOperation<RemoveTraitLeafOperationContext> {
private deletedTrait!: ITraitModel;
do(prev: ComponentSchema[]): ComponentSchema[] {
const appModel = new AppModel(prev);
const appModel = new AppModel(prev, this.registry);
const component = appModel.getComponentById(this.context.componentId as ComponentId);
if (!component) {
console.warn('component not found');
@ -28,7 +28,7 @@ export class RemoveTraitLeafOperation extends BaseLeafOperation<RemoveTraitLeafO
}
undo(prev: ComponentSchema[]): ComponentSchema[] {
const appModel = new AppModel(prev);
const appModel = new AppModel(prev, this.registry);
const component = appModel.getComponentById(this.context.componentId as ComponentId);
if (!component) {
console.warn('component not found');

View File

@ -1,5 +1,4 @@
import { ComponentSchema } from '@sunmao-ui/core';
import { eventBus } from '../../eventBus';
import { BaseLeafOperation } from '../type';
export type UpdateSelectComponentLeafOperationContext = {
@ -8,25 +7,25 @@ export type UpdateSelectComponentLeafOperationContext = {
};
export class UpdateSelectComponentLeafOperation extends BaseLeafOperation<UpdateSelectComponentLeafOperationContext> {
private prevId!: string;
prevId!: string;
do(prev: ComponentSchema[]): ComponentSchema[] {
this.prevId = this.context.componentId || prev[0].id;
setTimeout(() => {
eventBus.send('selectComponent', this.context.newId);
// eventBus.send('selectComponent', this.context.newId);
});
return prev;
}
redo(prev: ComponentSchema[]): ComponentSchema[] {
setTimeout(() => {
eventBus.send('selectComponent', this.context.newId);
// eventBus.send('selectComponent', this.context.newId);
});
return prev;
}
undo(prev: ComponentSchema[]): ComponentSchema[] {
setTimeout(() => {
eventBus.send('selectComponent', this.prevId);
// eventBus.send('selectComponent', this.prevId);
});
return prev;
}

View File

@ -1,4 +1,5 @@
import { ComponentSchema } from '@sunmao-ui/core';
import { Registry } from '@sunmao-ui/runtime';
export const leafSymbol = Symbol('leaf');
export const branchSymbol = Symbol('branch');
@ -140,9 +141,11 @@ export interface IOperation<TContext = any> extends IUndoRedo {
*/
export abstract class BaseLeafOperation<TContext> implements IOperation<TContext> {
context: TContext;
registry: Registry;
type = leafSymbol;
constructor(context: TContext) {
constructor(registry: Registry, context: TContext) {
this.registry = registry;
this.context = context;
}
/**
@ -178,10 +181,12 @@ export abstract class BaseBranchOperation<TContext>
{
operationStack: OperationList;
context: TContext;
registry: Registry;
type = branchSymbol;
constructor(context: TContext) {
constructor(registry: Registry, context: TContext) {
this.context = context;
this.registry = registry;
this.operationStack = new OperationList();
}

View File

@ -1,9 +1,9 @@
import { ComponentSchema, parseType } from '@sunmao-ui/core';
import { Registry } from '@sunmao-ui/runtime';
import { isDraft, original } from 'immer';
import { get } from 'lodash-es';
import { registry } from '../setup';
export function genComponent(type: string, id: string): ComponentSchema {
export function genComponent(registry: Registry, type: string, id: string): ComponentSchema {
const { version, name } = parseType(type);
const cImpl = registry.getComponent(version, name);
const initProperties = cImpl.metadata.exampleProperties;

View File

@ -1,12 +1,12 @@
import { Flex, Box, ChakraProvider, Button } from '@chakra-ui/react';
import { Application } from '@sunmao-ui/core';
import { initSunmaoUI } from '@sunmao-ui/runtime';
import { Registry } from '@sunmao-ui/runtime/lib/services/registry';
import { StrictMode, useMemo, useState } from 'react';
import { StrictMode, useState } from 'react';
import ReactDOM from 'react-dom';
import './styles.css';
import { Editor } from './components/Editor';
import { initSunmaoEditor } from './init';
import { sunmaoChakraUILib } from '@sunmao-ui/chakra-ui-lib';
type Example = {
name: string;
@ -16,33 +16,13 @@ type Example = {
};
};
const { Editor, registry } = initSunmaoEditor();
registry.installLib(sunmaoChakraUILib);
const Playground: React.FC<{ examples: Example[] }> = ({ examples }) => {
const [example, setExample] = useState<Example | null>(examples[0]);
const { App, registry, stateStore } = useMemo(() => {
if (!example) {
return {};
}
const ui = initSunmaoUI();
const App = ui.App;
const registry = ui.registry;
const apiService = ui.apiService;
const stateStore = ui.stateManager.store;
const { modules = [] } = example.value;
modules.forEach(m => {
registry.registerModule(m);
});
localStorage.removeItem('schema');
return {
App,
registry,
stateStore,
apiService,
};
}, [example]);
return (
<Flex width="100vw" height="100vh">
<Box shadow="md">
@ -77,12 +57,7 @@ const Playground: React.FC<{ examples: Example[] }> = ({ examples }) => {
</Box>
</Box>
<Box flex="1">
<Editor
key={example!.name}
App={App!}
registry={registry!}
stateStore={stateStore!}
/>
<Editor />
</Box>
</Flex>
);

View File

@ -1,21 +0,0 @@
import { initSunmaoUI } from '@sunmao-ui/runtime';
import { sunmaoChakraUILib } from '@sunmao-ui/chakra-ui-lib';
import { AppModelManager } from './operations/AppModelManager';
const ui = initSunmaoUI();
const App = ui.App;
const registry = ui.registry;
const apiService = ui.apiService;
const stateManager = ui.stateManager;
const appModelManager = new AppModelManager();
registry.installLib(sunmaoChakraUILib);
export {
ui,
App,
registry,
apiService,
stateManager,
appModelManager,
};

View File

@ -0,0 +1,16 @@
import { initSunmaoUI, Registry, StateManager } from '@sunmao-ui/runtime';
import { EditorStore } from './EditorStore';
import { EventBusType } from './eventBus';
import { AppModelManager } from './operations/AppModelManager';
type ReturnOfInit = ReturnType<typeof initSunmaoUI>;
export type EditorServices = {
App: ReturnOfInit['App'];
registry: Registry;
apiService: ReturnOfInit['apiService'];
stateManager: StateManager;
appModelManager: AppModelManager;
eventBus: EventBusType;
editorStore: EditorStore;
};

View File

@ -52,7 +52,7 @@ export class SchemaValidator implements ISchemaValidator {
}
validate(components: ComponentSchema[]) {
const appModel = new AppModel(components);
const appModel = new AppModel(components, this.registry);
this.genComponentIdSpecMap(components);
this.result = [];
const baseContext = {