mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2025-02-23 17:49:49 +08:00
refactor appModalManager & appStorage
This commit is contained in:
parent
16faffde83
commit
b108682f8c
134
packages/editor/src/AppStorage.ts
Normal file
134
packages/editor/src/AppStorage.ts
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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' },
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
|
@ -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;
|
||||
|
@ -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>,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user