refactor appModalManager & appStorage

This commit is contained in:
Bowen Tan 2021-11-25 17:03:45 +08:00
parent 16faffde83
commit b108682f8c
8 changed files with 232 additions and 581 deletions

View File

@ -0,0 +1,134 @@
import { Application, ApplicationComponent } from '@sunmao-ui/core';
import { ImplementedRuntimeModule } from '@sunmao-ui/runtime';
import { produce } from 'immer';
import { eventBus } from './eventBus';
import { EmptyAppSchema } from './constants';
// function module2App(module: ImplementedRuntimeModule): Application {
// return {
// version: module.version,
// kind: 'Application',
// metadata: module.metadata,
// spec: {
// components: module.components,
// },
// moduleSpec: module.spec,
// } as Application;
// }
// function app2Module(app: Application): ImplementedRuntimeModule {
// return {
// version: app.version,
// kind: 'Module',
// metadata: app.metadata,
// components: app.spec.components,
// parsedVersion: {
// category: app.version,
// value: app.metadata.name,
// },
// spec: (app as any).moduleSpec,
// };
// }
export class AppStorage {
components: ApplicationComponent[] = [];
app: Application;
modules: ImplementedRuntimeModule[];
// this is current editing Model
private currentEditingName: string | undefined;
private currentEditingType: 'app' | 'module' = 'app';
static AppLSKey = 'schema';
static ModulesLSKey = 'modules';
constructor() {
this.app = this.getDefaultAppFromLS();
this.modules = this.getModulesFromLS();
this.updateCurrentId('app', this.app.metadata.name);
this.refreshComponents();
eventBus.on('componentsChange', (components: ApplicationComponent[]) => {
this.components = components;
this.saveInLS();
});
}
getDefaultAppFromLS(): Application {
try {
const appFromLS = localStorage.getItem(AppStorage.AppLSKey);
if (appFromLS) {
return JSON.parse(appFromLS);
}
return EmptyAppSchema;
} catch (error) {
console.warn(error);
return EmptyAppSchema;
}
}
getModulesFromLS(): ImplementedRuntimeModule[] {
try {
const modulesFromLS = localStorage.getItem(AppStorage.ModulesLSKey);
if (modulesFromLS) {
return JSON.parse(modulesFromLS);
}
return [];
} catch (error) {
return [];
}
}
updateCurrentId(type: 'app' | 'module', name: string) {
this.currentEditingType = type;
this.currentEditingName = name;
console.log('updateCurrentId', type, name);
this.refreshComponents();
}
// update components by currentEditingType & cache
private refreshComponents() {
switch (this.currentEditingType) {
case 'module':
const module = this.modules.find(
m => m.metadata.name === this.currentEditingName
);
const componentsOfModule = module?.components || [];
this.components = componentsOfModule;
console.log('componentsOfModule', componentsOfModule);
break;
case 'app':
const componentsOfApp = this.app.spec.components;
this.components = componentsOfApp;
break;
}
this.emitComponentsChange();
}
private emitComponentsChange() {
eventBus.send('componentsReload', this.components);
}
saveInLS() {
console.log('saveInLS', this.components)
switch (this.currentEditingType) {
case 'app':
const newApp = produce(this.app, draft => {
draft.spec.components = this.components;
});
this.app = newApp;
localStorage.setItem(AppStorage.AppLSKey, JSON.stringify(newApp));
break;
case 'module':
const i = this.modules.findIndex(
m => m.metadata.name === this.currentEditingName
);
// this.modules[i] = app2Module(this.app);
const newModules = produce(this.modules, draft => {
draft[i].components = this.components;
});
console.log('newModules', newModules);
this.modules = newModules
localStorage.setItem(AppStorage.ModulesLSKey, JSON.stringify(newModules));
}
}
}

View File

@ -19,6 +19,8 @@ import { ComponentWrapper } from './ComponentWrapper';
import { StateEditor, SchemaEditor } from './CodeEditor';
import { AppModelManager } from '../operations/AppModelManager';
import { Explorer } from './Explorer';
import { Application } from '../../../core/lib';
import { AppStorage } from '../AppStorage';
type ReturnOfInit = ReturnType<typeof initSunmaoUI>;
@ -28,6 +30,7 @@ type Props = {
stateStore: ReturnOfInit['stateManager']['store'];
apiService: ReturnOfInit['apiService'];
appModelManager: AppModelManager;
appStorage: AppStorage;
};
export const Editor: React.FC<Props> = ({
@ -35,10 +38,11 @@ export const Editor: React.FC<Props> = ({
registry,
stateStore,
appModelManager,
appStorage,
}) => {
const { app } = useAppModel();
const { components } = useAppModel(appModelManager);
const [selectedComponentId, setSelectedComponentId] = useState(
app.spec.components[0]?.id || ''
components?.[0]?.id || ''
);
const [scale, setScale] = useState(100);
const [preview, setPreview] = useState(false);
@ -83,6 +87,19 @@ export const Editor: React.FC<Props> = ({
};
}, []);
const app = useMemo<Application>(() => {
return {
version: 'sunmao/v1',
kind: 'Application',
metadata: {
name: 'some App',
},
spec: {
components,
},
};
}, [components]);
const appComponent = useMemo(() => {
return (
<App
@ -138,7 +155,7 @@ export const Editor: React.FC<Props> = ({
</TabList>
<TabPanels flex="1" overflow="auto">
<TabPanel>
<Explorer appModelManager={appModelManager} />
<Explorer appStorage={appStorage} />
</TabPanel>
<TabPanel p={0}>
<StructureTree

View File

@ -1,24 +1,20 @@
import { Divider, Text, VStack } from '@chakra-ui/react';
import React from 'react';
import { RuntimeModuleSpec } from '@sunmao-ui/core';
import {
AppModelManager,
getDefaultAppFromLS,
getModulesFromLS,
} from '../../operations/AppModelManager';
import { AppStorage } from '../../AppStorage';
type ExplorerProps = {
appModelManager: AppModelManager;
appStorage: AppStorage;
};
export const Explorer: React.FC<ExplorerProps> = ({ appModelManager }) => {
const app = getDefaultAppFromLS();
export const Explorer: React.FC<ExplorerProps> = ({ appStorage }) => {
const app = appStorage.app;
const appItemId = `app_${app.metadata.name}`;
const [selectedItem, setSelectedItem] = React.useState<string | undefined>(appItemId);
const onClickApp = (id: string) => {
setSelectedItem(id);
appModelManager.updateCurrentId('app', app.metadata.name);
appStorage.updateCurrentId('app', app.metadata.name);
};
const appItem = (
@ -31,12 +27,12 @@ export const Explorer: React.FC<ExplorerProps> = ({ appModelManager }) => {
/>
);
const modules: RuntimeModuleSpec[] = getModulesFromLS();
const modules: RuntimeModuleSpec[] = appStorage.modules
const moduleItems = modules.map((module: RuntimeModuleSpec) => {
const moduleItemId = `module_${module.metadata.name}`;
const onClickModule = (id: string) => {
setSelectedItem(id);
appModelManager.updateCurrentId('module', module.metadata.name);
appStorage.updateCurrentId('module', module.metadata.name);
};
return (
<ExplorerItem

View File

@ -2,7 +2,7 @@ import { Application } from '@sunmao-ui/core';
export const ignoreTraitsList = ['core/v1/slot', 'core/v1/event', 'core/v1/fetch'];
export const DefaultAppSchema: Application = {
export const EmptyAppSchema: Application = {
kind: 'Application',
version: 'example/v1',
metadata: {
@ -12,428 +12,13 @@ export const DefaultAppSchema: Application = {
spec: {
components: [
{
id: 'grid_layout1',
id: 'gridLayout1',
type: 'core/v1/grid_layout',
properties: {
layout: [
{
w: 10,
h: 15,
x: 0,
y: 0,
i: 'tabs1',
moved: false,
static: false,
isDraggable: true,
},
],
layout: [],
},
traits: [],
},
{
id: 'fetchUsers',
type: 'core/v1/dummy',
properties: {},
traits: [
{
type: 'core/v1/fetch',
properties: {
url: 'https://6177d4919c328300175f5b99.mockapi.io/users',
method: 'get',
lazy: false,
headers: {},
body: {},
onComplete: [],
},
},
],
},
{
id: 'usersTable',
type: 'chakra_ui/v1/table',
properties: {
data: '{{fetchUsers.fetch.data}}',
columns: [
{ key: 'username', title: '用户名', type: 'link' },
{ key: 'job', title: '职位', type: 'text' },
{ key: 'area', title: '地区', type: 'text' },
{
key: 'createdTime',
title: '创建时间',
displayValue: "{{dayjs($listItem.createdTime).format('LL')}}",
},
],
majorKey: 'id',
rowsPerPage: '3',
isMultiSelect: 'false',
},
traits: [
{
type: 'core/v1/slot',
properties: {
container: { id: 'tabContentVStack', slot: 'content' },
},
},
],
},
{
id: 'userInfoContainer',
type: 'chakra_ui/v1/vstack',
properties: { spacing: '2', align: 'stretch' },
traits: [
{
type: 'core/v1/slot',
properties: {
container: { id: 'tabContentVStack', slot: 'content' },
},
},
{
type: 'core/v1/style',
properties: {
styleSlot: 'content',
style: "{{!usersTable.selectedItem ? 'display: none' : ''}}",
},
},
],
},
{
id: 'userInfoTitle',
type: 'core/v1/text',
properties: { value: { raw: '**基本信息**', format: 'md' } },
traits: [
{
type: 'core/v1/slot',
properties: {
container: { id: 'userInfoContainer', slot: 'content' },
},
},
],
},
{
id: 'hstack1',
type: 'chakra_ui/v1/hstack',
properties: { spacing: '24px', hideBorder: '' },
traits: [
{
type: 'core/v1/slot',
properties: {
container: { id: 'userInfoContainer', slot: 'content' },
},
},
{
type: 'core/v1/style',
properties: {
styleSlot: 'content',
style: 'padding: 0; border: none',
},
},
],
},
{
id: 'usernameLabel',
type: 'core/v1/text',
properties: { value: { raw: '**用户名**', format: 'md' } },
traits: [
{
type: 'core/v1/slot',
properties: {
container: { id: 'hstack1', slot: 'content' },
},
},
],
},
{
id: 'usernameValue',
type: 'core/v1/text',
properties: {
value: {
raw: "{{usersTable.selectedItem ? usersTable.selectedItem.username : ''}}",
format: 'plain',
},
},
traits: [
{
type: 'core/v1/slot',
properties: {
container: { id: 'hstack1', slot: 'content' },
},
},
],
},
{
id: 'divider1',
type: 'chakra_ui/v1/divider',
properties: {},
traits: [
{
type: 'core/v1/slot',
properties: {
container: { id: 'userInfoContainer', slot: 'content' },
},
},
],
},
{
id: 'jobLabel',
type: 'core/v1/text',
properties: { value: { raw: '**职位**', format: 'md' } },
traits: [
{
type: 'core/v1/slot',
properties: {
container: { id: 'hstack2', slot: 'content' },
},
},
],
},
{
id: 'hstack2',
type: 'chakra_ui/v1/hstack',
properties: { spacing: '24px', hideBorder: '' },
traits: [
{
type: 'core/v1/slot',
properties: {
container: { id: 'userInfoContainer', slot: 'content' },
},
},
{
type: 'core/v1/style',
properties: {
styleSlot: 'content',
style: 'padding: 0; border: none',
},
},
],
},
{
id: 'areaLabel',
type: 'core/v1/text',
properties: { value: { raw: '**地区**', format: 'md' } },
traits: [
{
type: 'core/v1/slot',
properties: {
container: { id: 'hstack3', slot: 'content' },
},
},
],
},
{
id: 'areaValue',
type: 'core/v1/text',
properties: {
value: {
raw: "{{usersTable.selectedItem ? usersTable.selectedItem.area : ''}}",
format: 'plain',
},
},
traits: [
{
type: 'core/v1/slot',
properties: {
container: { id: 'hstack3', slot: 'content' },
},
},
],
},
{
id: 'divider2',
type: 'chakra_ui/v1/divider',
properties: {},
traits: [
{
type: 'core/v1/slot',
properties: {
container: { id: 'userInfoContainer', slot: 'content' },
},
},
],
},
{
id: 'createdTimeLabel',
type: 'core/v1/text',
properties: { value: { raw: '**创建时间**', format: 'md' } },
traits: [
{
type: 'core/v1/slot',
properties: {
container: { id: 'hstack4', slot: 'content' },
},
},
],
},
{
id: 'createdTimeValue',
type: 'core/v1/text',
properties: {
value: {
raw: "{{usersTable.selectedItem ? dayjs(usersTable.selectedItem.createdTime).format('LL') : ''}}",
format: 'plain',
},
},
traits: [
{
type: 'core/v1/slot',
properties: {
container: { id: 'hstack4', slot: 'content' },
},
},
{
type: 'core/v1/style',
properties: { string: { kind: {}, type: {} } },
},
],
},
{
id: 'hstack3',
type: 'chakra_ui/v1/hstack',
properties: { spacing: '24px', hideBorder: '' },
traits: [
{
type: 'core/v1/slot',
properties: {
container: { id: 'userInfoContainer', slot: 'content' },
},
},
{
type: 'core/v1/style',
properties: {
styleSlot: 'content',
style: 'padding: 0; border: none',
},
},
],
},
{
id: 'divider3',
type: 'chakra_ui/v1/divider',
properties: {},
traits: [
{
type: 'core/v1/slot',
properties: {
container: { id: 'userInfoContainer', slot: 'content' },
},
},
],
},
{
id: 'hstack4',
type: 'chakra_ui/v1/hstack',
properties: { spacing: '24px', hideBorder: '' },
traits: [
{
type: 'core/v1/slot',
properties: {
container: { id: 'userInfoContainer', slot: 'content' },
},
},
{
type: 'core/v1/style',
properties: {
styleSlot: 'content',
style: 'padding: 0; border: none',
},
},
],
},
{
id: 'tabs1',
type: 'chakra_ui/v1/tabs',
properties: {
tabNames: ['用户信息', '角色'],
initialSelectedTabIndex: 0,
},
traits: [
{
type: 'core/v1/slot',
properties: {
container: { id: 'grid_layout1', slot: 'content' },
},
},
],
},
{
id: 'tabContentVStack',
type: 'chakra_ui/v1/vstack',
properties: { spacing: '24px' },
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'tabs1', slot: 'content' } },
},
],
},
{
id: 'testtext',
type: 'core/v1/text',
properties: { value: { raw: '**测试角色**', format: 'md' } },
traits: [
{
type: 'core/v1/slot',
properties: { container: { id: 'tabs1', slot: 'content' } },
},
],
},
{
id: 'jobValue',
type: 'chakra_ui/v1/vstack',
properties: { spacing: '1', align: 'stretch' },
traits: [
{
type: 'core/v1/slot',
properties: {
container: { id: 'hstack2', slot: 'content' },
},
},
{
type: 'core/v1/style',
properties: {
styleSlot: 'content',
style: 'padding: 0; border: none',
},
},
],
},
{
id: 'link1',
type: 'chakra_ui/v1/link',
properties: {
text: {
raw: "{{usersTable.selectedItem ? usersTable.selectedItem.job : ''}}",
format: 'plain',
},
href: 'https://www.google.com',
},
traits: [
{
type: 'core/v1/slot',
properties: {
container: { id: 'jobValue', slot: 'content' },
},
},
],
},
{
id: 'link2',
type: 'chakra_ui/v1/link',
properties: {
text: {
raw: "{{usersTable.selectedItem ? usersTable.selectedItem.job : ''}}",
format: 'plain',
},
href: 'https://www.google.com',
},
traits: [
{
type: 'core/v1/slot',
properties: {
container: { id: 'jobValue', slot: 'content' },
},
},
],
},
],
},
};

View File

@ -1,5 +1,5 @@
import mitt from 'mitt';
import { Application } from '@sunmao-ui/core';
import { Application, ApplicationComponent } from '@sunmao-ui/core';
import { Operations } from './operations/Operations';
export const SelectComponentEvent = 'selectComponent';
@ -8,6 +8,8 @@ export const HoverComponentEvent = 'hoverComponent';
const emitter = mitt<{
operation: Operations;
undo: undefined;
componentsReload: ApplicationComponent[];
componentsChange: ApplicationComponent[];
appChange: Application;
[SelectComponentEvent]: string;
[HoverComponentEvent]: string;

View File

@ -5,6 +5,7 @@ import { StrictMode } from 'react';
import ReactDOM from 'react-dom';
import 'react-grid-layout/css/styles.css';
import 'react-resizable/css/styles.css';
import { AppStorage } from './AppStorage';
import { Editor } from './components/Editor';
import { AppModelManager } from './operations/AppModelManager';
@ -22,7 +23,8 @@ export default function renderApp(options: Options = {}) {
const registry = ui.registry;
const apiService = ui.apiService;
const stateStore = ui.stateManager.store;
const appModelManager = new AppModelManager(registry);
const appStorage = new AppStorage();
const appModelManager = new AppModelManager(registry, appStorage.components);
const { components = [], traits = [], modules = [] } = options;
components.forEach(c => registry.registerComponent(c));
@ -38,6 +40,7 @@ export default function renderApp(options: Options = {}) {
stateStore={stateStore}
appModelManager={appModelManager}
apiService={apiService}
appStorage={appStorage}
/>
</ChakraProvider>
</StrictMode>,

View File

@ -1,5 +1,5 @@
import { Application, ComponentTrait, ApplicationComponent } from '@sunmao-ui/core';
import { parseType, ImplementedRuntimeModule } from '@sunmao-ui/runtime';
import { ComponentTrait, ApplicationComponent } from '@sunmao-ui/core';
import { parseType } from '@sunmao-ui/runtime';
import {
Operations,
CreateComponentOperation,
@ -18,7 +18,6 @@ import { produce } from 'immer';
import { eventBus } from '../eventBus';
import { set, isEqual } from 'lodash-es';
import { Registry } from '@sunmao-ui/runtime/lib/services/registry';
import { DefaultAppSchema } from '../constants';
function genSlotTrait(parentId: string, slot: string): ComponentTrait {
return {
@ -51,140 +50,52 @@ function genComponent(
};
}
export function getDefaultAppFromLS(): Application {
try {
const appFromLS = localStorage.getItem('schema');
if (appFromLS) {
return JSON.parse(appFromLS);
}
return DefaultAppSchema;
} catch (error) {
console.warn(error);
return DefaultAppSchema;
}
}
export function getModulesFromLS() {
try {
const modulesFromLS = localStorage.getItem('modules');
if (modulesFromLS) {
return JSON.parse(modulesFromLS);
}
return [];
} catch (error) {
return [];
}
}
function module2App(module: ImplementedRuntimeModule): Application {
return {
version: module.version,
kind: 'Application',
metadata: module.metadata,
spec: {
components: module.components,
},
moduleSpec: module.spec,
} as Application;
}
function app2Module(app: Application): ImplementedRuntimeModule {
return {
version: app.version,
kind: 'Module',
metadata: app.metadata,
components: app.spec.components,
parsedVersion: {
category: app.version,
value: app.metadata.name,
},
spec: (app as any).moduleSpec,
};
}
export class AppModelManager {
private undoStack: Operations[] = [];
// this is current editing AppModel
private app: Application;
private modules: ImplementedRuntimeModule[];
private appCache: Application;
components: ApplicationComponent[] = [];
private registry: Registry;
private currentEditingId: string | undefined;
private currentEditingType: 'app' | 'module' = 'app';
constructor(registry: Registry) {
constructor(registry: Registry, components: ApplicationComponent[]) {
console.log('appmodalManger init', components)
this.registry = registry;
const appFromLS = getDefaultAppFromLS();
const modulesFromLS = getModulesFromLS();
this.updateComponents(components);
eventBus.on('undo', () => this.undo());
eventBus.on('operation', o => this.apply(o));
this.app = appFromLS;
this.appCache = appFromLS;
this.modules = modulesFromLS;
this.updateApp(appFromLS);
this.updateCurrentId('app', appFromLS.metadata.name);
eventBus.on('componentsReload', components => {
console.log('componentsReload', components)
this.updateComponents(components);
})
}
getApp() {
return this.app;
return this.components;
}
genId(componentType: string) {
const { name } = parseType(componentType);
const componentsCount = this.app.spec.components.filter(
const componentsCount = this.components.filter(
c => c.type === componentType
).length;
return `${name}${componentsCount + 1}`;
}
updateCurrentId(type: 'app' | 'module', name: string) {
this.currentEditingType = type;
this.currentEditingId = name;
console.log('updateCurrentId', type, name);
if (type === 'module') {
this.appCache = this.app;
const module = this.modules.find(m => m.metadata.name === name);
this.updateApp(module2App(module!));
console.log('moduleApp', this.app);
} else {
this.app = this.appCache
this.updateApp(this.app);
}
}
updateApp(app: Application, shouldSaveInLS = false) {
eventBus.send('appChange', app);
this.app = app;
if (shouldSaveInLS) {
this.saveInLS();
}
}
saveInLS() {
switch (this.currentEditingType) {
case 'app':
localStorage.setItem('schema', JSON.stringify(this.app));
break;
case 'module':
const i = this.modules.findIndex(m => m.metadata.name === this.currentEditingId);
this.modules[i] = app2Module(this.app);
console.log('saveModule', this.modules)
localStorage.setItem('modules', JSON.stringify(this.modules));
}
updateComponents(components: ApplicationComponent[]) {
this.components = components;
eventBus.send('componentsChange', this.components);
}
undo() {
if (this.undoStack.length === 0) {
return this.app;
return this.components;
}
const o = this.undoStack.pop()!;
this.apply(o, true);
}
apply(o: Operations, noEffect = false) {
let newApp = this.app;
let newComponents = this.components;
switch (o.kind) {
case 'createComponent':
const createO = o as CreateComponentOperation;
@ -199,28 +110,28 @@ export class AppModelManager {
const undoOperation = new RemoveComponentOperation(newComponent.id);
this.undoStack.push(undoOperation);
}
newApp = produce(this.app, draft => {
draft.spec.components.push(newComponent);
newComponents = produce(this.components, draft => {
draft.push(newComponent);
});
break;
case 'removeComponent':
const removeO = o as RemoveComponentOperation;
newApp = produce(this.app, draft => {
const i = draft.spec.components.findIndex(c => c.id === removeO.componentId);
draft.spec.components.splice(i, 1);
newComponents = produce(this.components, draft => {
const i = draft.findIndex(c => c.id === removeO.componentId);
draft.splice(i, 1);
});
break;
case 'modifyComponentProperty':
const mo = o as ModifyComponentPropertyOperation;
newApp = produce(this.app, draft => {
return draft.spec.components.forEach(c => {
newComponents = produce(this.components, draft => {
return draft.forEach(c => {
if (c.id === mo.componentId) {
set(c.properties, mo.propertyKey, mo.propertyValue);
}
});
});
if (!noEffect) {
const oldValue = this.app.spec.components.find(c => c.id === mo.componentId)
const oldValue = this.components.find(c => c.id === mo.componentId)
?.properties[mo.propertyKey];
const undoOperation = new ModifyComponentPropertyOperation(
mo.componentId,
@ -232,15 +143,15 @@ export class AppModelManager {
break;
case 'replaceComponentProperty':
const ro = o as ReplaceComponentPropertyOperation;
newApp = produce(this.app, draft => {
return draft.spec.components.forEach(c => {
newComponents = produce(this.components, draft => {
return draft.forEach(c => {
if (c.id === ro.componentId) {
c.properties = ro.properties;
}
});
});
if (!noEffect) {
const oldValue = this.app.spec.components.find(
const oldValue = this.components.find(
c => c.id === ro.componentId
)?.properties;
const undoOperation = new ReplaceComponentPropertyOperation(
@ -252,8 +163,8 @@ export class AppModelManager {
break;
case 'modifyComponentId':
const mIdo = o as ModifyComponentIdOperation;
newApp = produce(this.app, draft => {
return draft.spec.components.forEach(c => {
newComponents = produce(this.components, draft => {
return draft.forEach(c => {
if (c.id === mIdo.componentId) {
c.id = mIdo.value;
}
@ -270,8 +181,8 @@ export class AppModelManager {
case 'modifyTraitProperty':
const mto = o as ModifyTraitPropertyOperation;
let oldValue;
newApp = produce(this.app, draft => {
draft.spec.components.forEach(c => {
newComponents = produce(this.components, draft => {
draft.forEach(c => {
if (c.id === mto.componentId) {
c.traits.forEach(t => {
if (t.type === mto.traitType) {
@ -295,8 +206,8 @@ export class AppModelManager {
case 'modifyTraitProperties':
const mtpo = o as ModifyTraitPropertiesOperation;
let oldProperties;
newApp = produce(this.app, draft => {
draft.spec.components.forEach(c => {
newComponents = produce(this.components, draft => {
draft.forEach(c => {
if (c.id === mtpo.componentId) {
c.traits.forEach(t => {
if (t.type === mtpo.traitType) {
@ -319,8 +230,8 @@ export class AppModelManager {
case 'addTraitOperation':
const ato = o as AddTraitOperation;
let i = 0;
newApp = produce(this.app, draft => {
draft.spec.components.forEach(c => {
newComponents = produce(this.components, draft => {
draft.forEach(c => {
if (c.id === ato.componentId) {
c.traits.push({
type: ato.traitType,
@ -337,8 +248,8 @@ export class AppModelManager {
break;
case 'removeTraitOperation':
const rto = o as RemoveTraitOperation;
newApp = produce(this.app, draft => {
draft.spec.components.forEach(c => {
newComponents = produce(this.components, draft => {
draft.forEach(c => {
if (c.id === rto.componentId) {
c.traits.splice(rto.traitIndex, 1);
}
@ -347,16 +258,16 @@ export class AppModelManager {
break;
case 'sortComponent':
const sortO = o as SortComponentOperation;
newApp = produce(this.app, draft => {
const iIndex = draft.spec.components.findIndex(c => c.id === sortO.componentId);
const iComponent = this.app.spec.components[iIndex];
newComponents = produce(this.components, draft => {
const iIndex = draft.findIndex(c => c.id === sortO.componentId);
const iComponent = this.components[iIndex];
const iSlotTrait = iComponent.traits.find(t => t.type === 'core/v1/slot');
if (!iSlotTrait) return;
const findArray =
sortO.direction === 'up'
? this.app.spec.components.slice(0, iIndex).reverse()
: this.app.spec.components.slice(iIndex + 1);
? this.components.slice(0, iIndex).reverse()
: this.components.slice(iIndex + 1);
const jComponent = findArray.find(c => {
const jSlotTrait = c.traits.find(t => t.type === 'core/v1/slot');
@ -366,23 +277,23 @@ export class AppModelManager {
});
if (!jComponent) return;
const jIndex = this.app.spec.components.findIndex(c => c.id === jComponent.id);
const jIndex = this.components.findIndex(c => c.id === jComponent.id);
if (jIndex > -1) {
[draft.spec.components[iIndex], draft.spec.components[jIndex]] = [
draft.spec.components[jIndex],
draft.spec.components[iIndex],
[draft[iIndex], draft[jIndex]] = [
draft[jIndex],
draft[iIndex],
];
}
});
break;
case 'replaceApp': {
const rao = o as ReplaceAppOperation;
newApp = produce(this.app, () => {
newComponents = produce(this.components, () => {
return rao.app;
});
break;
}
}
this.updateApp(newApp, true);
this.updateComponents(newComponents);
}
}

View File

@ -1,23 +1,26 @@
import { Application } from '@sunmao-ui/core';
import { ApplicationComponent } from '@sunmao-ui/core';
import { useEffect, useState } from 'react';
import { DefaultAppSchema } from '../constants';
import { eventBus } from '../eventBus';
import { AppModelManager } from './AppModelManager';
export function useAppModel() {
const [app, setApp] = useState<Application>(DefaultAppSchema);
export function useAppModel(appModalManager: AppModelManager) {
const [components, setComponents] = useState<ApplicationComponent[]>(
appModalManager.components
);
useEffect(() => {
const onAppChange = (app: Application) => {
setApp(() => app);
const onComponents = (components: ApplicationComponent[]) => {
console.log('componentsChange', components);
setComponents(() => components);
};
eventBus.on('appChange', onAppChange);
eventBus.on('componentsChange', onComponents);
return () => {
eventBus.off('appChange', onAppChange);
eventBus.off('componentsChange', onComponents);
};
}, []);
return {
app,
components,
};
}