Skip to content

Commit

Permalink
✨ feat: 实现 AssetStore 与 EditorStore 的状态自动同步
Browse files Browse the repository at this point in the history
  • Loading branch information
arvinxx committed Jun 14, 2023
1 parent b7b0633 commit 7135040
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 19 deletions.
14 changes: 9 additions & 5 deletions src/ComponentAsset/ComponentAsset.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
/*eslint no-invalid-this: "error"*/
import { getDefaultValueFromSchema } from '@c2d2c/utils';
import { FC, PropsWithChildren, ReactNode } from 'react';
import { StoreApi } from 'zustand';
import type { UseBoundStore } from 'zustand/react';

import type { EditorMode } from '../ProEditor';
import type { AssetModels, CanvasRule, CodeEmitter, ComponentAssetParams } from './types';
import { DataProvider, EmitterEnv } from './types';

import { createAssetStore } from './store';
import { createAssetStore, WithoutCallSignature } from './store';

export class ComponentAsset<Config = any, Props = any> {
/**
Expand Down Expand Up @@ -49,11 +50,11 @@ export class ComponentAsset<Config = any, Props = any> {

defaultConfig: Partial<Config>;
componentStore: UseBoundStore<any>;
componentStoreApi: () => WithoutCallSignature<StoreApi<any>>;
configSelector: (s: Config) => Partial<Config>;
codeEmitter: CodeEmitter<Config, Props>;

isStarterMode: (store: any) => boolean = () => {
throw Error('暂未实现 emptyModeSelector 方法,请在初始化时传入 emptyModeSelector');
};
isStarterMode: (store: any) => boolean = () => false;

constructor(params: ComponentAssetParams<Config>) {
this.id = params.id;
Expand All @@ -66,15 +67,18 @@ export class ComponentAsset<Config = any, Props = any> {
this.defaultConfig = params.defaultConfig;
}

const { createStore, Provider } = createAssetStore(params.createStore, {
const { createStore, Provider, useStoreApi } = createAssetStore(params.createStore, {
initialState: params.defaultConfig,
...params.storeOptions,
});

// 初始化 store
this.componentStore = createStore();
this.componentStoreApi = useStoreApi;
this.AssetProvider = Provider;

this.configSelector =
params.storeOptions?.partial || ((s: Config) => JSON.parse(JSON.stringify(s)));
// 交互规则
this.rules = params.ui.rules;

Expand Down
24 changes: 14 additions & 10 deletions src/ComponentAsset/store/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { DevtoolsOptions } from 'zustand/middleware';
import { PublicProEditorStore } from '@/ProEditor/store';

export interface AssetStoreOptions<T = any> {
initialState: T;
devtools?: false | DevtoolsOptions;
initialState?: T;
devtools?: boolean | DevtoolsOptions;
partial?: (state: T) => any;
}

export type CreateAssetStore<T> = StateCreator<
Expand All @@ -23,23 +24,26 @@ export const createAssetStore = <T>(
options?: AssetStoreOptions<T>,
) => {
const store = () => {
const devtoolsOptions = options?.devtools;
const devtools = optionalDevtools(!!devtoolsOptions);
const devtoolsOptions =
options?.devtools === false
? {}
: options?.devtools === true
? { name: 'ProEditor-AssetStore' }
: options?.devtools;

const devtools = optionalDevtools(!(options?.devtools === false));

const initialState = options?.initialState || {};

return create<T>()(
devtools(
(...params) => ({ ...createStore(...params), ...initialState }),
!devtoolsOptions ? {} : devtoolsOptions,
),
devtools((...params) => ({ ...createStore(...params), ...initialState }), devtoolsOptions),
);
};

return { Provider, createStore: store };
return { Provider, createStore: store, useStoreApi };
};

type WithoutCallSignature<T> = {
export type WithoutCallSignature<T> = {
[K in keyof T]: T[K];
};

Expand Down
22 changes: 19 additions & 3 deletions src/ComponentAsset/types/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,8 +55,9 @@ export type JSONSchemaVersion = string;
* JSON Schema v7
* @see https://tools.ietf.org/html/draft-handrews-json-schema-validation-01
*/
export type JSONSchemaDefinition = JSONSchema | boolean;
export interface JSONSchema {
export type JSONSchemaDefinition = JSONSchema;

export interface JSONSchema<T extends Record<string, any> = any> {
$id?: string | undefined;
$ref?: string | undefined;
$schema?: JSONSchemaVersion | undefined;
Expand Down Expand Up @@ -112,6 +113,9 @@ export interface JSONSchema {
minProperties?: number | undefined;
required?: string[] | undefined;
properties?:
| {
[item in keyof T]: JSONSchemaDefinition;
}
| {
[key: string]: JSONSchemaDefinition;
}
Expand Down Expand Up @@ -169,8 +173,20 @@ export interface JSONSchema {
*/
title?: string | undefined;
description?: string | undefined;
default?: JSONSchemaType | undefined;
default?: any | undefined;
readOnly?: boolean | undefined;
writeOnly?: boolean | undefined;
examples?: JSONSchemaType | undefined;

// 为渲染提供的扩展字段
renderType?: string;
renderProps?: any;
renderOptions?: any;
enumNames?: string[];
enumOptions?: EnumOption[];
category?: string;
}
interface EnumOption {
label?: string;
value?: string;
}
48 changes: 48 additions & 0 deletions src/ProEditor/components/AssetStoreUpdater/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import isEqual from 'fast-deep-equal';
import { memo, useEffect } from 'react';
import { createStoreUpdater } from 'zustand-utils';
import { shallow } from 'zustand/shallow';

import { useProEditor } from '../../hooks/useProEditor';
import { useStore, useStoreApi } from '../../store';

const AssetStoreUpdater = memo(() => {
const instance = useProEditor();

const [useAssetStoreApi, useAssetStore, configSelector] = useStore(
(s) => [
s.componentAsset.componentStoreApi,
s.componentAsset.componentStore,
s.componentAsset.configSelector,
],
shallow,
);
const assetStoreApi = useAssetStoreApi();

// 将 instance 的方法全部同步到 assetStore
useEffect(() => {
assetStoreApi.setState({ ...instance });
}, []);

// 将计算后的默认值传给面板
// 用等式做一次优化,不然每次都会重新计算
const defaultConfig = useStore((s) => s.componentAsset.getDefaultConfig(s.mode), isEqual);

const proEditorStoreApi = useStoreApi();
const useStoreUpdater = createStoreUpdater(proEditorStoreApi);
// 用 defaultConfig 更新一次config
useStoreUpdater('config', defaultConfig, []);

// 将 assetStore 的 config 自动同步到 proEditorStore
const assetConfig = useAssetStore(configSelector, isEqual);
useEffect(() => {
if (typeof assetConfig === 'undefined') return;
if (isEqual(assetConfig, proEditorStoreApi.getState().config)) return;

proEditorStoreApi.setState({ config: assetConfig });
}, [assetConfig]);

return null;
});

export default AssetStoreUpdater;
4 changes: 3 additions & 1 deletion src/ProEditor/container/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { shallow } from 'zustand/shallow';
import NavBar from '../components/NavBar';

import AssetEmpty from '../components/AssetEmpty';
import AssetStoreUpdater from '../components/AssetStoreUpdater';
import ConfigPanel from '../components/ConfigPanel';
import Stage from '../components/Stage';

Expand All @@ -24,7 +25,7 @@ export interface ProEditorAppProps {
* 自定义错误兜底形态
*/
ErrorBoundary?: FC;
showEditorDevtools?: boolean;
__STORE_DEVTOOLS__?: boolean;
/**
* 代码复制回调
*/
Expand Down Expand Up @@ -71,6 +72,7 @@ export const ProEditor: FC<ProEditorAppProps> = memo((props) => {
<ErrorBoundary onExportConfig={exportConfig}>
<AssetProvider createStore={() => componentAsset.componentStore}>
{!DataProvider ? children : <DataProvider>{children}</DataProvider>}
<AssetStoreUpdater />
</AssetProvider>
</ErrorBoundary>
);
Expand Down

0 comments on commit 7135040

Please sign in to comment.