diff --git a/src/ComponentAsset/ComponentAsset.tsx b/src/ComponentAsset/ComponentAsset.tsx index 08f0392b..77febef8 100644 --- a/src/ComponentAsset/ComponentAsset.tsx +++ b/src/ComponentAsset/ComponentAsset.tsx @@ -52,6 +52,7 @@ export class ComponentAsset { componentStore: UseBoundStore; componentStoreApi: () => WithoutCallSignature>; configSelector: (s: Config) => Partial; + setConfig: (config: Config, set) => void; codeEmitter: CodeEmitter; isStarterMode: (store: any) => boolean = () => false; @@ -67,10 +68,10 @@ export class ComponentAsset { this.defaultConfig = params.defaultConfig; } - const { createStore, Provider, useStoreApi } = createAssetStore(params.createStore, { - initialState: params.defaultConfig, - ...params.storeOptions, - }); + const { createStore, Provider, useStoreApi } = createAssetStore( + params.createStore, + params.storeOptions, + ); // 初始化 store this.componentStore = createStore(); @@ -78,7 +79,9 @@ export class ComponentAsset { this.AssetProvider = Provider; this.configSelector = - params.storeOptions?.partial || ((s: Config) => JSON.parse(JSON.stringify(s))); + params.storeOptions?.getConfig || ((s: Config) => JSON.parse(JSON.stringify(s))); + this.setConfig = params.storeOptions?.setConfig || ((config: Config, set) => set(config)); + // 交互规则 this.rules = params.ui.rules; diff --git a/src/ComponentAsset/store/index.ts b/src/ComponentAsset/store/index.ts index e22487a1..a20e080d 100644 --- a/src/ComponentAsset/store/index.ts +++ b/src/ComponentAsset/store/index.ts @@ -5,9 +5,9 @@ import { DevtoolsOptions } from 'zustand/middleware'; import { PublicProEditorStore } from '@/ProEditor/store'; export interface AssetStoreOptions { - initialState?: T; devtools?: boolean | DevtoolsOptions; - partial?: (state: T) => any; + getConfig?: (state: any) => T; + setConfig?: (config: T, set) => void; } export type CreateAssetStore = StateCreator< @@ -33,11 +33,7 @@ export const createAssetStore = ( const devtools = optionalDevtools(!(options?.devtools === false)); - const initialState = options?.initialState || {}; - - return create()( - devtools((...params) => ({ ...createStore(...params), ...initialState }), devtoolsOptions), - ); + return create()(devtools(createStore, devtoolsOptions)); }; return { Provider, createStore: store, useStoreApi }; @@ -48,6 +44,6 @@ export type WithoutCallSignature = { }; export const createUseAssetStore = (): { - useStore: UseContextStore>; - useStoreApi: () => WithoutCallSignature>; + useStore: UseContextStore>; + useStoreApi: () => WithoutCallSignature>; } => ({ useStore, useStoreApi }); diff --git a/src/LevaPanel/Schema.tsx b/src/LevaPanel/Schema.tsx index 36b75b1d..dd1cf58b 100644 --- a/src/LevaPanel/Schema.tsx +++ b/src/LevaPanel/Schema.tsx @@ -38,7 +38,7 @@ const Schema: (props: SchemaProps) => ReactNode = memo( onChange?.({ [context.key]: value } as any, full as any); }, } as SchemaItem; - if (outConfig[key]) { + if (outConfig && outConfig[key]) { config['value'] = outConfig[key]; } return config; diff --git a/src/ProEditor/components/AssetStoreUpdater/index.tsx b/src/ProEditor/components/AssetStoreUpdater/index.tsx index 29c20fd4..be5d94d9 100644 --- a/src/ProEditor/components/AssetStoreUpdater/index.tsx +++ b/src/ProEditor/components/AssetStoreUpdater/index.tsx @@ -1,6 +1,5 @@ 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'; @@ -9,38 +8,51 @@ import { useStore, useStoreApi } from '../../store'; const AssetStoreUpdater = memo(() => { const instance = useProEditor(); - const [useAssetStoreApi, useAssetStore, configSelector] = useStore( + const [useAssetStoreApi, configSelector, setConfig, config] = useStore( (s) => [ s.componentAsset.componentStoreApi, - s.componentAsset.componentStore, s.componentAsset.configSelector, + s.componentAsset.setConfig, + s.config, ], shallow, ); const assetStoreApi = useAssetStoreApi(); - // 将 instance 的方法全部同步到 assetStore + const setState = (state, action: any) => (assetStoreApi.setState as any)(state, false, action); + useEffect(() => { - assetStoreApi.setState({ ...instance }); + setState(instance, { type: '⏬ 注入 editor 方法', payload: Object.keys(instance) }); }, []); // 将计算后的默认值传给面板 // 用等式做一次优化,不然每次都会重新计算 - const defaultConfig = useStore((s) => s.componentAsset.getDefaultConfig(s.mode), isEqual); + const defaultConfig = useStore( + (s) => + s.componentAsset.defaultConfig || configSelector(s.componentAsset.getDefaultConfig(s.mode)), + isEqual, + ); const proEditorStoreApi = useStoreApi(); - const useStoreUpdater = createStoreUpdater(proEditorStoreApi); + + const syncState = (state) => setState(state, { type: '🔄 从 Editor 同步状态', payload: state }); + // 用 defaultConfig 更新一次config - useStoreUpdater('config', defaultConfig, []); + useEffect(() => { + const state = { config: defaultConfig }; + proEditorStoreApi.setState(state); + proEditorStoreApi.getState().yjsDoc.updateHistoryData(state); + + setConfig(config, syncState); + }, []); - // 将 assetStore 的 config 自动同步到 proEditorStore - const assetConfig = useAssetStore(configSelector, isEqual); + // 将 proEditorStore 的 config 自动同步到 assetStore useEffect(() => { - if (typeof assetConfig === 'undefined') return; - if (isEqual(assetConfig, proEditorStoreApi.getState().config)) return; + const assetConfig = configSelector(assetStoreApi.getState()); + if (isEqual(assetConfig, config)) return; - proEditorStoreApi.setState({ config: assetConfig }); - }, [assetConfig]); + setConfig(config, syncState); + }, [config]); return null; }); diff --git a/src/ProEditor/components/Canvas/index.tsx b/src/ProEditor/components/Canvas/index.tsx index c3d80b50..72b26b38 100644 --- a/src/ProEditor/components/Canvas/index.tsx +++ b/src/ProEditor/components/Canvas/index.tsx @@ -5,24 +5,29 @@ import { memo } from 'react'; import { shallow } from 'zustand/shallow'; import FreeCanvas from '../../../FreeCanvas'; -import { useUpdateEditorAwareness } from '../../hooks/useEditorAwareness'; import { useStore } from '../../store'; import Component from './Component'; const Canvas: FC = memo(() => { - const [viewport, componentAsset] = useStore( - (s) => [s.editorAwareness.viewport, s.componentAsset], - shallow, - ); - const [enableCanvasInteraction, toggleCanvasInteraction] = useStore( - (s) => [s.enableCanvasInteraction, s.toggleCanvasInteraction], + const [ + viewport, + componentAsset, + enableCanvasInteraction, + toggleCanvasInteraction, + updateEditorAwareness, + ] = useStore( + (s) => [ + s.editorAwareness.viewport, + s.componentAsset, + s.enableCanvasInteraction, + s.toggleCanvasInteraction, + s.internalUpdateEditorAwareness, + ], shallow, ); const ErrorBoundary = componentAsset.ErrorBoundary; - const { updateEditorAwareness } = useUpdateEditorAwareness(); - return ( void; resetConfig: () => void; - updateConfig: (config: Partial, options?: ActionOptions) => void; + updateConfig: (config: Partial, replace?: boolean, options?: ActionOptions) => void; } export interface ConfigSlice extends ConfigPublicAction, ConfigSliceState { /** * 内部更新配置 **/ - internalUpdateConfig: (config: Partial, payload?: ActionPayload) => void; + internalUpdateConfig: (config: Partial, payload?: ActionPayload, replace?: boolean) => void; } export const configSlice: StateCreator< @@ -80,8 +80,14 @@ export const configSlice: StateCreator< yjsDoc: new DocWithHistoryManager<{ config: any }>(), }; + const undoLength = initialConfigState.yjsDoc.undoManager.undoStack.length; + + const redoLength = initialConfigState.yjsDoc.undoManager.redoStack.length; + return { ...initialConfigState, + undoLength, + redoLength, resetConfig: () => { set({ config: initialConfigState.config, props: initialConfigState.props }); }, @@ -89,10 +95,10 @@ export const configSlice: StateCreator< * 内部修改 config 方法 * 传给 ProTableStore 进行 config 同步 */ - internalUpdateConfig: (config, payload) => { + internalUpdateConfig: (config, payload, replace) => { const { onConfigChange, componentAsset } = get(); - const nextConfig = { ...get().config, ...config }; + const nextConfig = replace ? config : { ...get().config, ...config }; set({ config: nextConfig }, false, payload); @@ -114,8 +120,12 @@ export const configSlice: StateCreator< document.body.removeChild(eleLink); }, - updateConfig: (config, action) => { - get().internalUpdateConfig(config, { type: '外部 updateConfig 更新', payload: config }); + updateConfig: (config, replace, action) => { + get().internalUpdateConfig( + config, + { type: '外部 updateConfig 更新', payload: config }, + replace, + ); const useAction = merge({}, { recordHistory: true }, action); diff --git a/src/ProEditor/store/slices/general.ts b/src/ProEditor/store/slices/general.ts index a50df8fd..6efa6e54 100644 --- a/src/ProEditor/store/slices/general.ts +++ b/src/ProEditor/store/slices/general.ts @@ -1,3 +1,4 @@ +import { StackItem } from 'yjs/dist/src/utils/UndoManager'; import { StateCreator } from 'zustand'; import getPrefixCls from '@/_util/getPrefixCls'; @@ -33,6 +34,8 @@ const initialGeneralState: GeneralSliceState = { export interface GeneralPublicAction { undo: () => void; redo: () => void; + undoStack: () => StackItem[]; + redoStack: () => StackItem[]; } export interface GeneralSlice extends GeneralPublicAction, GeneralSliceState {} @@ -45,13 +48,19 @@ export const generalSlice: StateCreator< > = (set, get) => ({ ...initialGeneralState, + undoStack: () => { + return get().yjsDoc.undoManager.undoStack; + }, + redoStack: () => { + return get().yjsDoc.undoManager.redoStack; + }, undo: () => { const { yjsDoc, internalUpdateConfig } = get(); const stack = yjsDoc.undo(); const { config } = yjsDoc.getHistoryJSON(); - internalUpdateConfig(config, { type: 'history/undo', payload: stack }); + internalUpdateConfig(config, { type: 'history/undo', payload: stack }, true); }, redo: () => { const { yjsDoc, internalUpdateConfig } = get(); @@ -60,6 +69,6 @@ export const generalSlice: StateCreator< const { config } = yjsDoc.getHistoryJSON(); - internalUpdateConfig(config, { type: 'history/redo', payload: stack }); + internalUpdateConfig(config, { type: 'history/redo', payload: stack }, true); }, });