diff --git a/package.json b/package.json index c07c7ee..0631ca8 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "@emotion/babel-plugin": "^11.10.5", "@trivago/prettier-plugin-sort-imports": "^3.3.1", "@types/flat": "^5.0.2", + "@types/json-schema": "^7.0.11", "@types/lodash": "^4.14.185", "@types/react": "^18.0.17", "@types/react-dom": "^18.0.6", @@ -36,38 +37,45 @@ "vite": "^4.0.3" }, "dependencies": { - "@ant-design/icons": "^4.8.0", - "@ant-design/pro-components": "^2.3.52", + "@ant-design/icons": "^5.0.1", + "@ant-design/pro-components": "^2.3.57", + "@modou/asset-vfs": "workspace:^1.0.0", + "@modou/code-editor": "workspace:^1.0.0", "@modou/compile-core": "workspace:^0.0.1", "@modou/core": "workspace:^1.0.0", "@modou/css-in-js": "workspace:^1.0.0", + "@modou/evaluate": "workspace:^1.0.0", + "@modou/flow-nodes": "workspace:^0.0.1", "@modou/meta-vfs": "workspace:^0.0.1", "@modou/reactivity": "workspace:^0.0.1", "@modou/reactivity-react": "workspace:^0.0.1", "@modou/refine": "workspace:^1.0.0", "@modou/render": "workspace:^1.0.0", "@modou/setters": "workspace:^1.0.0", + "@modou/shared": "workspace:^1.0.0", "@modou/simulator": "workspace:^1.0.0", + "@modou/state-manager": "workspace:^0.0.1", "@modou/widgets-antd": "workspace:^1.0.0", "@recoiljs/refine": "^0.1.1", "@vue/compiler-core": "^3.2.45", - "@vue/reactivity": "^3.2.45", "@vue/runtime-core": "^3.2.45", "ahooks": "^3.7.0", - "antd": "^5.1.5", + "antd": "^5.2.2", "axios": "^1.1.3", "flat": "^5.0.2", "handlebars": "^4.7.7", - "immer": "^9.0.15", - "immutability-helper": "^3.1.1", + "immer": "^9.0.19", + "json-schema": "^0.4.0", "lodash": "^4.17.21", + "mitt": "^3.0.0", + "quicktype-core": "^21.0.2", "react": "^18.2.0", "react-dnd": "^16.0.1", "react-dnd-html5-backend": "^16.0.1", "react-dom": "^18.2.0", "react-json-view": "^1.21.3", "react-router-dom": "^6.3.0", - "reactflow": "^11.3.0", + "reactflow": "^11.5.1", "ts-pattern": "^4.0.5" }, "config": { diff --git a/packages/asset-vfs/package.json b/packages/asset-vfs/package.json new file mode 100644 index 0000000..80fea0e --- /dev/null +++ b/packages/asset-vfs/package.json @@ -0,0 +1,12 @@ +{ + "name": "@modou/asset-vfs", + "version": "1.0.0", + "description": "", + "main": "src", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/packages/asset-vfs/src/AssetVFS.ts b/packages/asset-vfs/src/AssetVFS.ts new file mode 100644 index 0000000..e67ec6f --- /dev/null +++ b/packages/asset-vfs/src/AssetVFS.ts @@ -0,0 +1,19 @@ +import { makeAutoObservable } from '@modou/reactivity' + +import { AppFactory } from './app-factory' + +export class AssetVFS { + constructor({ appFactory }: { appFactory: AppFactory }) { + this.appFactory = appFactory + if (AssetVFS.instance) { + return AssetVFS.instance + } else { + makeAutoObservable(this) + AssetVFS.instance = this + } + } + + appFactory: AppFactory + + private static instance: AssetVFS +} diff --git a/packages/core/src/contexts/AppFactoryContext.ts b/packages/asset-vfs/src/app-factory/AppFactoryContext.ts similarity index 54% rename from packages/core/src/contexts/AppFactoryContext.ts rename to packages/asset-vfs/src/app-factory/AppFactoryContext.ts index 8c4359a..c3bda4f 100644 --- a/packages/core/src/contexts/AppFactoryContext.ts +++ b/packages/asset-vfs/src/app-factory/AppFactoryContext.ts @@ -1,5 +1,20 @@ -import { createContext, memo } from 'react' +import { createContext, memo, useContext } from 'react' +import { WidgetGroupEnum } from '@modou/core' +import { + BranchNode, + LoopNode, + RunScriptNode, + StartNode, + branchNodeInterpreter, + branchNodeNodeMetadata, + loopNodeInterpreter, + loopNodeMetadata, + runScriptNodeInterpreter, + runScriptNodeMetadata, + startNodeInterpreter, + startNodeMetadata, +} from '@modou/flow-nodes' import { ArraySetter, BooleanSetter, @@ -23,8 +38,8 @@ import { tableWidgetMetadata, } from '@modou/widgets-antd' -import { AppFactory } from '../app-factory' -import { WidgetGroupEnum } from '../types' +import { AssetVFS } from '../AssetVFS' +import { AppFactory } from './index' export const defaultAppFactory = AppFactory.create({ widgets: [ @@ -62,6 +77,28 @@ export const defaultAppFactory = AppFactory.create({ ...widget, component: memo(widget.component), })), + flowNodes: [ + { + component: StartNode, + metadata: startNodeMetadata, + interpreter: startNodeInterpreter, + }, + { + component: BranchNode, + metadata: branchNodeNodeMetadata, + interpreter: branchNodeInterpreter, + }, + { + component: LoopNode, + metadata: loopNodeMetadata, + interpreter: loopNodeInterpreter, + }, + { + component: RunScriptNode, + metadata: runScriptNodeMetadata, + interpreter: runScriptNodeInterpreter, + }, + ], setters: { [SetterTypeEnum.Select]: SelectSetter, [SetterTypeEnum.String]: StringSetter, @@ -70,5 +107,10 @@ export const defaultAppFactory = AppFactory.create({ [SetterTypeEnum.Array]: ArraySetter, }, }) +const assetVFS = new AssetVFS({ appFactory: defaultAppFactory }) +export const AppFactoryContext = createContext(assetVFS.appFactory) -export const AppFactoryContext = createContext(defaultAppFactory) +export const useAppFactory = () => { + const appFactory = useContext(AppFactoryContext) + return { appFactory } +} diff --git a/packages/core/src/app-factory/index.ts b/packages/asset-vfs/src/app-factory/index.ts similarity index 50% rename from packages/core/src/app-factory/index.ts rename to packages/asset-vfs/src/app-factory/index.ts index 51d1e6c..4d949ef 100644 --- a/packages/core/src/app-factory/index.ts +++ b/packages/asset-vfs/src/app-factory/index.ts @@ -1,11 +1,14 @@ import { isEqual, unionWith } from 'lodash' import { FC } from 'react' +import { WidgetGroupEnum, WidgetMetadata } from '@modou/core' +import { + FlowNodeInterpreter, + FlowNodeMetadata, +} from '@modou/core/src/flow-node' +import { mrToJsonSchema, schemeToDefs } from '@modou/refine' import { BaseSetterProps } from '@modou/setters/src/types' -import { WidgetGroupEnum } from '../types' -import { WidgetMetadata } from '../widget' - interface Widget { // FIXME 完善WidgetBaseState定义 component: FC @@ -13,19 +16,31 @@ interface Widget { group: WidgetGroupEnum } -type WidgetRegistry = Record +// TODO 完善 stateTypeDefs 类型 +type WidgetRegistry = Record< + string, + Widget & { stateTypeDefs: Record } +> type SetterElement = FC> +interface FlowNode { + component: FC + metadata: FlowNodeMetadata + interpreter: FlowNodeInterpreter +} + +type FlowNodeRegistry = Record + +interface AppFactoryParams { + // TODO 增加 package 信息 增加umd引入 + widgets: Widget[] + flowNodes: FlowNode[] + setters: Record +} + export class AppFactory { - constructor({ - widgets, - setters, - }: { - // TODO 增加 package 信息 增加umd引入 - widgets: Widget[] - setters: Record - }) { + constructor({ widgets, setters, flowNodes }: AppFactoryParams) { this._widgetRegistry = unionWith( widgets, ({ metadata: preMetadata }, { metadata: curMetadata }) => @@ -33,13 +48,23 @@ export class AppFactory { ).reduce((pre, cur) => { pre[cur.metadata.type] = { ...cur, + stateTypeDefs: schemeToDefs(mrToJsonSchema(cur.metadata.mrStateScheme)), } return pre }, {}) + this._flowNodeRegistry = unionWith( + flowNodes, + ({ metadata: preMetadata }, { metadata: curMetadata }) => + isEqual(preMetadata.type, curMetadata.type), + ).reduce((pre, cur) => { + pre[cur.metadata.type] = cur + return pre + }, {}) this._setterRegistry = setters } private readonly _widgetRegistry: WidgetRegistry + private readonly _flowNodeRegistry: FlowNodeRegistry private readonly _setterRegistry: Record>> get widgetByType(): WidgetRegistry { @@ -53,6 +78,17 @@ export class AppFactory { }) } + get flowNodeByType(): FlowNodeRegistry { + return new Proxy(this._flowNodeRegistry, { + get(target: FlowNodeRegistry, p: string | symbol, receiver: any): any { + if (Reflect.has(target, p)) { + return Reflect.get(target, p) + } + return new Error(`流节点【${p.toString()}】未注册`) + }, + }) + } + get setterByType() { return new Proxy(this._setterRegistry, { get( @@ -68,11 +104,7 @@ export class AppFactory { }) } - static create(params: { - // TODO 增加 package 信息 增加umd引入 - widgets: Widget[] - setters: Record - }) { + static create(params: AppFactoryParams) { return new AppFactory(params) } } diff --git a/packages/asset-vfs/src/index.ts b/packages/asset-vfs/src/index.ts new file mode 100644 index 0000000..b563652 --- /dev/null +++ b/packages/asset-vfs/src/index.ts @@ -0,0 +1,11 @@ +// TODO 优化 asset vfs + +// TODO 解决 不同 window 环境下使用不同React的问题 + +export { AssetVFS } from './AssetVFS' +export { AppFactory } from './app-factory' +export { + AppFactoryContext, + useAppFactory, + defaultAppFactory, +} from './app-factory/AppFactoryContext' diff --git a/packages/canvas-designer/src/components/CanvasDesigner.tsx b/packages/canvas-designer/src/components/CanvasDesigner.tsx index e7f51a2..762ff0c 100644 --- a/packages/canvas-designer/src/components/CanvasDesigner.tsx +++ b/packages/canvas-designer/src/components/CanvasDesigner.tsx @@ -1,8 +1,9 @@ -import { FC, ReactElement } from 'react' +import { FC, ReactElement, useEffect, useState } from 'react' import { DndProvider } from 'react-dnd' import { HTML5Backend } from 'react-dnd-html5-backend' import { PageFile } from '@modou/meta-vfs' +import { CanvasStateProvider, PageState } from '@modou/state-manager' import { CanvasDesignerFileProvider } from '../contexts/CanvasDesignerFileContext' import { CanvasDesignerStoreProvider } from '../contexts/CanvasDesignerStoreContext' @@ -14,13 +15,25 @@ interface CanvasDesignerProps { } export const CanvasDesigner: FC = ({ file, children }) => { - return ( + const [canvasState, setCanvasState] = useState() + + useEffect(() => { + // TODO 添加到 AppState + if (canvasState?.file === file) { + return + } + setCanvasState(new PageState(file)) + }, [canvasState?.file, file]) + + return canvasState ? ( - - - {children} - - + + + + {children} + + + - ) + ) : null } diff --git a/packages/canvas-designer/src/components/CanvasDesignerCanvas.tsx b/packages/canvas-designer/src/components/CanvasDesignerCanvas.tsx index dace003..378de98 100644 --- a/packages/canvas-designer/src/components/CanvasDesignerCanvas.tsx +++ b/packages/canvas-designer/src/components/CanvasDesignerCanvas.tsx @@ -10,9 +10,10 @@ import { useState, } from 'react' -import { useAppManager } from '@modou/core' import { mcss } from '@modou/css-in-js' +import { useAppManager } from '@modou/meta-vfs' import { observer } from '@modou/reactivity-react' +import { useAppState, useCanvasState } from '@modou/state-manager' import { SimulatorInstanceContext } from '../contexts' import { useCanvasDesignerFile } from '../contexts/CanvasDesignerFileContext' @@ -25,6 +26,8 @@ export const _CanvasDesignerCanvas: FC<{ const iframeRef = useRef(null) const [initializedIframe, setInitializedIframe] = useState(false) const { appManager } = useAppManager() + const { appState } = useAppState() + const { canvasState } = useCanvasState() const { canvasDesignerFile } = useCanvasDesignerFile() useEffect(() => { @@ -37,7 +40,9 @@ export const _CanvasDesignerCanvas: FC<{ iframeRef.current?.contentWindow?.reactRenderHost.updateFile( canvasDesignerFile, ) - }, [initializedIframe, appManager, canvasDesignerFile]) + iframeRef.current?.contentWindow?.reactRenderHost.updateState(canvasState) + iframeRef.current?.contentWindow?.reactRenderHost.updateAppState(appState) + }, [initializedIframe, appManager, canvasDesignerFile, appState, canvasState]) const canvasRef = useRef(null) const [designerIndicatorStyle, setDesignerIndicatorStyle] = useState({ @@ -103,8 +108,11 @@ const classes = { spinWrapper: mcss` height: 100%; width: 100%; + top: 0; + left: 0; display: flex; justify-content: center; align-items: center; + position: absolute; `, } diff --git a/packages/canvas-designer/src/components/CanvasDesignerOutlineTree.tsx b/packages/canvas-designer/src/components/CanvasDesignerOutlineTree.tsx index e202305..9a18452 100644 --- a/packages/canvas-designer/src/components/CanvasDesignerOutlineTree.tsx +++ b/packages/canvas-designer/src/components/CanvasDesignerOutlineTree.tsx @@ -12,6 +12,7 @@ import { observer } from '@modou/reactivity-react' import { useCanvasDesignerFile } from '../contexts/CanvasDesignerFileContext' import { useCanvasDesignerStore } from '../contexts/CanvasDesignerStoreContext' +import { emitter } from '../event-bus' import { OutlineTreeNode, OutlineTreeNodeSlot, @@ -81,9 +82,9 @@ const _CanvasDesignerOutlineTree: FC = () => { [WidgetBaseProps, string] >(dropNode.nodeType) .with('slot', () => [ - canvasDesignerFile.widgetMap.get( - (dropNode as unknown as OutlineTreeNodeSlot).slot.widgetId, - )?.meta!, + canvasDesignerFile.widgetMap[ + (dropNode as unknown as OutlineTreeNodeSlot).slot.widgetId + ]?.meta, (dropNode as unknown as OutlineTreeNodeSlot).slot.path, ]) .with('widget', () => [ @@ -133,6 +134,9 @@ const _CanvasDesignerOutlineTree: FC = () => { break default: } + setTimeout(() => { + emitter.emit('onWidgetElementChange') + }) } // TODO 支持大纲树和其画布及面板组件互相拖拽 IMPORTANT @@ -140,7 +144,6 @@ const _CanvasDesignerOutlineTree: FC = () => { // useEffect(() => { // console.log('tree.ref', ref.current?.state.flattenNodes) // }) - // FIXME 跨层架拖拽 return (
@@ -169,7 +172,7 @@ const _CanvasDesignerOutlineTree: FC = () => { blockNode // onDragEnter={onDragEnter} onDrop={onDrop} - treeData={[toJS(pageOutlineTree)]} + treeData={[pageOutlineTree]} />
) diff --git a/packages/canvas-designer/src/components/CanvasDesignerPropsPanel.tsx b/packages/canvas-designer/src/components/CanvasDesignerPropsPanel.tsx index 4f71453..338a274 100644 --- a/packages/canvas-designer/src/components/CanvasDesignerPropsPanel.tsx +++ b/packages/canvas-designer/src/components/CanvasDesignerPropsPanel.tsx @@ -1,5 +1,4 @@ import { Button, Divider, Form, Input } from 'antd' -import produce from 'immer' import { omit } from 'lodash' import { ComponentProps, @@ -10,9 +9,9 @@ import { useState, } from 'react' -import { AppFactoryContext } from '@modou/core' +import { AppFactoryContext } from '@modou/asset-vfs' import { cx, mcss } from '@modou/css-in-js' -import { PageFileMeta, WidgetFileMeta } from '@modou/meta-vfs' +import { toJS } from '@modou/reactivity' import { observer } from '@modou/reactivity-react' import { SETTER_KEY } from '@modou/setters' import { SetterTypeEnum } from '@modou/setters/src/constants' @@ -23,16 +22,15 @@ import { useCanvasDesignerStore } from '../contexts/CanvasDesignerStoreContext' const useRenderFormItem = ({ widgetId }: { widgetId: string }) => { const { canvasDesignerFile } = useCanvasDesignerFile() - const widget = canvasDesignerFile.widgetMap.get(widgetId)! + const widget = canvasDesignerFile.widgetMap[widgetId] const widgetFactory = useContext(AppFactoryContext) - const widgetMetadata = useMemo(() => { - return widgetFactory.widgetByType[widget.meta.type].metadata + const { metadata: widgetMetadata } = useMemo(() => { + return widgetFactory.widgetByType[widget.meta.type] }, [widgetFactory.widgetByType, widget.meta.type]) const propsDef = ( widgetMetadata.jsonPropsSchema.properties .props as unknown as typeof widgetMetadata.jsonPropsSchema ).properties - const formRender = Object.entries(propsDef) .filter(([, value]) => Reflect.has(value, SETTER_KEY)) .map(([key, value]) => { @@ -47,26 +45,20 @@ const useRenderFormItem = ({ widgetId }: { widgetId: string }) => { label={setterOptions.label} > {NativeSetter ? ( - { - widget.updateMeta( - produce((draft) => { - draft.props[key] = value - }), - ) - }} - /> + ) : ( { - widget.updateMeta( - produce((draft) => { - draft.props[key] = value - }), - ) + widget.updateMeta((preValue) => ({ + ...preValue, + props: { + ...preValue.props, + [key]: value, + }, + })) }} /> )} @@ -110,11 +102,10 @@ const _WidgetPropsPanel: FC = () => { { - widget.updateMeta( - produce((draft) => { - draft.name = value - }), - ) + widget.updateMeta((prevValue) => ({ + ...prevValue, + name: value, + })) }} placeholder="请输入组件名称" /> @@ -150,11 +141,10 @@ const _PagePropsPanel: FC = () => { { - canvasDesignerFile.updateMeta( - produce((draft) => { - draft.name = value - }), - ) + canvasDesignerFile.updateMeta((preValue) => ({ + ...preValue, + name: value, + })) }} value={canvasDesignerFile.meta.name} /> diff --git a/packages/canvas-designer/src/components/CanvasDesignerWidgetStencil.tsx b/packages/canvas-designer/src/components/CanvasDesignerWidgetStencil.tsx index 4e26465..cad8bbe 100644 --- a/packages/canvas-designer/src/components/CanvasDesignerWidgetStencil.tsx +++ b/packages/canvas-designer/src/components/CanvasDesignerWidgetStencil.tsx @@ -3,16 +3,15 @@ import { isEmpty } from 'lodash' import { FC, useContext, useEffect, useMemo, useRef } from 'react' import { useDrag } from 'react-dnd' +import { AppFactory, AppFactoryContext } from '@modou/asset-vfs' import { - AppFactory, - AppFactoryContext, WidgetBaseProps, WidgetGroupEnum, WidgetMetadata, - generateId, getWidgetGroupLabel, } from '@modou/core' import { mcss } from '@modou/css-in-js' +import { generateId } from '@modou/shared' import { WidgetDragType } from '../types' @@ -29,7 +28,7 @@ const WidgetBlock: FC<{ const newWidget: WidgetBaseProps = { ...widget, id: generateId(), - name: `${widget.name}-${generateId(4)}`, + name: `${widget.name}_${generateId(4)}`, } return { type: WidgetDragType.Add, @@ -76,20 +75,36 @@ const WidgetBlock: FC<{ backgroundColor: 'rgba(0,0,0,.04)', }} > -
{metadata.icon}
-
{metadata.name}
+
+
{metadata.icon}
+
{metadata.name}
+
-
{metadata.icon}
-
{metadata.name}
+
+
{metadata.name}
+
{metadata.icon}
+
) diff --git a/packages/canvas-designer/src/components/DesignerIndicator/indicatoir/DropIndicator.tsx b/packages/canvas-designer/src/components/DesignerIndicator/indicatoir/DropIndicator.tsx index 3625f39..f3728b7 100644 --- a/packages/canvas-designer/src/components/DesignerIndicator/indicatoir/DropIndicator.tsx +++ b/packages/canvas-designer/src/components/DesignerIndicator/indicatoir/DropIndicator.tsx @@ -1,22 +1,28 @@ -import { useMount, useMutationObserver } from 'ahooks' +import { useMemoizedFn, useMount, useMutationObserver } from 'ahooks' import { Col, Row, Typography } from 'antd' import { CSSProperties, FC, + ForwardRefRenderFunction, + forwardRef, useCallback, useContext, useEffect, + useImperativeHandle, useMemo, + useRef, useState, } from 'react' -import { AppFactoryContext } from '@modou/core' +import { AppFactoryContext } from '@modou/asset-vfs' +import { useWidgetMetaSlots } from '@modou/core' import { mcss, useTheme } from '@modou/css-in-js' import { observer } from '@modou/reactivity-react' import { SimulatorInstanceContext } from '../../../contexts' import { useCanvasDesignerFile } from '../../../contexts/CanvasDesignerFileContext' import { useCanvasDesignerStore } from '../../../contexts/CanvasDesignerStoreContext' +import { emitter } from '../../../event-bus' import { useElementRect, useWidgetDrop } from '../../../hooks' import { DropIndicatorPositionEnum } from '../../../store' import { @@ -32,7 +38,14 @@ interface DropElement { slotPath: string } -const _WidgetDrop: FC = ({ widgetId, slotPath }) => { +interface WidgetDropInstance { + forceUpdateStyle: () => void +} + +const _WidgetDrop: ForwardRefRenderFunction = ( + { widgetId, slotPath }, + ref, +) => { const { canvasDesignerFile } = useCanvasDesignerFile() const { canvasDesignerStore } = useCanvasDesignerStore() const appFactory = useContext(AppFactoryContext) @@ -55,6 +68,9 @@ const _WidgetDrop: FC = ({ widgetId, slotPath }) => { }) const [styleUpdater, setStyleUpdater] = useState(0) + const forceUpdateStyle = useMemoizedFn(() => { + setStyleUpdater((prevState) => prevState + 1) + }) const { style } = useElementRect(element, { deps: [styleUpdater], }) @@ -63,12 +79,17 @@ const _WidgetDrop: FC = ({ widgetId, slotPath }) => { // void Promise.resolve().then(() => { // setStyleUpdater((prevState) => prevState + 1) // }) - setTimeout(() => { - setStyleUpdater((prevState) => prevState + 1) - }) - }, [canvasDesignerFile.widgets]) + setTimeout(forceUpdateStyle) + }, [canvasDesignerFile.widgets, forceUpdateStyle]) - const dropIndicatorStyle: CSSProperties = useMemo(() => { + useEffect(() => { + emitter.on('onWidgetElementChange', forceUpdateStyle) + return () => { + emitter.off('onWidgetElementChange', forceUpdateStyle) + } + }) + + const dropIndicatorStyle: CSSProperties = (() => { if (!canvasDesignerStore.dropIndicator.show) { return {} } @@ -105,15 +126,21 @@ const _WidgetDrop: FC = ({ widgetId, slotPath }) => { default: return {} } - }, [ - canvasDesignerStore.dropIndicator.position, - canvasDesignerStore.dropIndicator.show, - ]) + })() const theme = useTheme() const widgetMetadata = appFactory.widgetByType[widget.meta.type].metadata + useImperativeHandle(ref, () => ({ + forceUpdateStyle, + })) + + const allSlots = useWidgetMetaSlots({ + widgetMeta: widgetMetadata, + widget, + }) + return ( <> {isEmptySlot ? ( @@ -125,13 +152,16 @@ const _WidgetDrop: FC = ({ widgetId, slotPath }) => { > - {widgetMetadata.name}-{widgetMetadata.slots[slotPath].name} + {widgetMetadata.name}-{allSlots[slotPath].name} ) : null} {isActive ? ( - +
= ({ widgetId, slotPath }) => { ) } -const WidgetDrop = observer(_WidgetDrop) +const WidgetDrop = observer(forwardRef(_WidgetDrop)) const widgetDropClasses = { emptyWrapper: mcss` border: 1px solid rgba(0,0,0,.1); @@ -173,6 +203,8 @@ const _DropIndicator: FC = () => { >([]) const simulatorInstance = useContext(SimulatorInstanceContext) + const widgetDropRef = useRef(null) + const initDrop = useCallback(() => { const elements = [ ...(simulatorInstance?.document?.querySelectorAll('[data-widget-id]') ?? @@ -188,20 +220,36 @@ const _DropIndicator: FC = () => { ) }, [simulatorInstance?.document]) useMount(initDrop) - useMutationObserver(initDrop, simulatorInstance.document?.body, { - childList: true, - subtree: true, - }) + useMutationObserver( + () => { + initDrop() + widgetDropRef.current?.forceUpdateStyle?.() + }, + simulatorInstance.document?.body, + { + childList: true, + subtree: true, + attributes: true, + }, + ) const dropElementsRendered = useMemo(() => { - return dropElements.filter(({ widgetId }) => - canvasDesignerFile.widgetMap.has(widgetId), - ) + // 去除列表模式下重复的widgetId + const usedWidgetIdMap: Record = {} + return dropElements.filter(({ widgetId, slotPath }) => { + const res = + !usedWidgetIdMap[widgetId + slotPath] && + canvasDesignerFile.widgetMap[widgetId] + usedWidgetIdMap[widgetId + slotPath] = true + return res + }) }, [canvasDesignerFile.widgetMap, dropElements]) return ( <> - {dropElementsRendered.map(({ widgetId, slotPath }) => ( + {dropElementsRendered.map(({ widgetId, slotPath }, index) => ( diff --git a/packages/canvas-designer/src/components/DesignerIndicator/indicatoir/SelectedIndicator.tsx b/packages/canvas-designer/src/components/DesignerIndicator/indicatoir/SelectedIndicator.tsx index 7c6cccc..a34047f 100644 --- a/packages/canvas-designer/src/components/DesignerIndicator/indicatoir/SelectedIndicator.tsx +++ b/packages/canvas-designer/src/components/DesignerIndicator/indicatoir/SelectedIndicator.tsx @@ -1,3 +1,4 @@ +import { useMemoizedFn, useMutationObserver } from 'ahooks' import { CSSProperties, FC, @@ -8,8 +9,8 @@ import { } from 'react' import { useDrag } from 'react-dnd' -import { useAppManager } from '@modou/core' import { mcss, useTheme } from '@modou/css-in-js' +import { useAppManager } from '@modou/meta-vfs' import { observer } from '@modou/reactivity-react' import { SimulatorInstanceContext } from '../../../contexts' @@ -38,17 +39,24 @@ const _SelectIndicatorContent: FC = () => { }) const [display, setDisplay] = useState(false) const [styleUpdater, setStyleUpdater] = useState(0) + const simulatorInstance = useContext(SimulatorInstanceContext) + + const forceUpdateStyle = useMemoizedFn(() => { + setStyleUpdater((prevState) => prevState + 1) + }) // TODO 是否使用 watch deep useEffect(() => { // void Promise.resolve().then(() => { // setStyleUpdater((prevState) => prevState + 1) // }) - setTimeout(() => { - setStyleUpdater((prevState) => prevState + 1) - }) - }, [canvasDesignerFile.widgets]) - const simulatorInstance = useContext(SimulatorInstanceContext) + setTimeout(forceUpdateStyle) + }, [canvasDesignerFile.widgets, forceUpdateStyle]) + useMutationObserver(forceUpdateStyle, simulatorInstance.document?.body, { + childList: true, + subtree: true, + attributes: true, + }) useEffect(() => { if (!selectedWidgetId) { setDisplay(false) @@ -86,7 +94,7 @@ const _SelectIndicatorContent: FC = () => { display: display ? 'block' : 'none', } - const widget = appManager.widgetMap.get(selectedWidgetId)! + const widget = appManager.widgetMap[selectedWidgetId] // TODO 使用element const [{ isDragging }, drag, preview] = useDrag( () => ({ @@ -94,7 +102,7 @@ const _SelectIndicatorContent: FC = () => { item: () => { return { type: WidgetDragType.Move, - widget, + widget: widget.meta, } }, collect: (monitor) => ({ diff --git a/packages/canvas-designer/src/components/DesignerIndicator/indicatoir/SelectedToolBox.tsx b/packages/canvas-designer/src/components/DesignerIndicator/indicatoir/SelectedToolBox.tsx index 83416d9..44b5211 100644 --- a/packages/canvas-designer/src/components/DesignerIndicator/indicatoir/SelectedToolBox.tsx +++ b/packages/canvas-designer/src/components/DesignerIndicator/indicatoir/SelectedToolBox.tsx @@ -3,8 +3,8 @@ import { Button } from 'antd' import { FC, useContext, useEffect, useRef } from 'react' import { useDrag } from 'react-dnd' -import { useAppManager } from '@modou/core' import { mcss, useTheme } from '@modou/css-in-js' +import { useAppManager } from '@modou/meta-vfs' import { observer } from '@modou/reactivity-react' import { SimulatorInstanceContext } from '../../../contexts' @@ -19,18 +19,16 @@ const _SelectedToolBox: FC = () => { const { canvasDesignerFile } = useCanvasDesignerFile() const { widgetRelationById } = canvasDesignerFile - console.log('widgetRelationById', widgetRelationById) - const selectedWidgetId = canvasDesignerStore.selectedWidgetId const wrapperRef = useRef(null) - const widget = appManager.widgetMap.get(selectedWidgetId)! + const widget = appManager.widgetMap[selectedWidgetId] const [{ isDragging }, drag, preview] = useDrag( () => ({ type: widget.meta.type, item: () => { return { type: WidgetDragType.Move, - widget, + widget: widget.meta, } }, collect: (monitor) => ({ @@ -95,11 +93,14 @@ const _SelectedToolBox: FC = () => { icon={} /> + {/* + */}
) diff --git a/packages/canvas-designer/src/event-bus/index.ts b/packages/canvas-designer/src/event-bus/index.ts new file mode 100644 index 0000000..f4c51a8 --- /dev/null +++ b/packages/canvas-designer/src/event-bus/index.ts @@ -0,0 +1,9 @@ +import mitt from 'mitt' + +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +type Events = { + // eslint-disable-next-line @typescript-eslint/no-invalid-void-type + onWidgetElementChange: void +} + +export const emitter = mitt() diff --git a/packages/canvas-designer/src/hooks/useElementRect.ts b/packages/canvas-designer/src/hooks/useElementRect.ts index 6421e3f..43c72ae 100644 --- a/packages/canvas-designer/src/hooks/useElementRect.ts +++ b/packages/canvas-designer/src/hooks/useElementRect.ts @@ -28,6 +28,7 @@ export const useElementRect = ( height: rect.height, }) }, [element, ...(options.deps as any[])]) + // }) const style: CSSProperties = { width: `${elementRect.width ?? 0}px`, height: `${elementRect.height ?? 0}px`, diff --git a/packages/canvas-designer/src/hooks/usePageOutlineTree.tsx b/packages/canvas-designer/src/hooks/usePageOutlineTree.tsx index a70d686..a1f9234 100644 --- a/packages/canvas-designer/src/hooks/usePageOutlineTree.tsx +++ b/packages/canvas-designer/src/hooks/usePageOutlineTree.tsx @@ -2,13 +2,10 @@ import { head, isEmpty } from 'lodash' import { DataNode } from 'rc-tree/es/interface' import { useContext } from 'react' -import { - AppFactoryContext, - WidgetBaseProps, - WidgetSlot, - useAppManager, -} from '@modou/core' -import { PageFile } from '@modou/meta-vfs' +import { AppFactoryContext } from '@modou/asset-vfs' +import { WidgetBaseProps, WidgetSlot } from '@modou/core' +import { PageFile, useAppManager } from '@modou/meta-vfs' +import { toJS } from '@modou/reactivity' import { useCanvasDesignerFile } from '../contexts/CanvasDesignerFileContext' @@ -33,11 +30,8 @@ export type OutlineTreeNode = DataNode & { export const usePageOutlineTree = () => { const { canvasDesignerFile } = useCanvasDesignerFile() const { appManager } = useAppManager() - const rootWidget = appManager.widgetMap.get( - canvasDesignerFile.meta.rootWidgetId, - )! + const rootWidget = appManager.widgetMap[canvasDesignerFile.meta.rootWidgetId] const appFactory = useContext(AppFactoryContext) - const parentTreeNodes: OutlineTreeNode[] = [ { key: canvasDesignerFile.meta.id, @@ -51,7 +45,7 @@ export const usePageOutlineTree = () => { const nodeStack: Array< | ({ _type: 'widget' } & WidgetBaseProps) | ({ _type: 'slot'; path: string; widgetId: string } & WidgetSlot) - > = [{ _type: 'widget', ...rootWidget.meta }] + > = [{ _type: 'widget', ...toJS(rootWidget.meta) }] while (nodeStack.length !== 0) { const curNode = nodeStack.pop()! if (curNode._type === 'widget') { @@ -65,8 +59,12 @@ export const usePageOutlineTree = () => { const parentTreeNode = parentTreeNodes.pop() parentTreeNode?.children.unshift(curTreeNode) const widgetDef = appFactory.widgetByType[curNode.type] - if (!isEmpty(widgetDef.metadata.slots)) { - Object.entries(widgetDef.metadata.slots).forEach(([path, slot]) => { + const allSlots = { + ...widgetDef.metadata.slots, + ...(curNode.dynamicSlots as unknown as Record), + } + if (!isEmpty(allSlots)) { + Object.entries(allSlots).forEach(([path, slot]) => { nodeStack.push({ _type: 'slot', path, widgetId: curNode.id, ...slot }) parentTreeNodes.push(curTreeNode) }) @@ -84,14 +82,13 @@ export const usePageOutlineTree = () => { } const parentTreeNode = parentTreeNodes.pop() parentTreeNode?.children.unshift(curTreeNode) - const curSlot = appManager.widgetMap.get(curNode.widgetId)?.meta.slots?.[ - curNode.path - ] + const curSlot = + appManager.widgetMap[curNode.widgetId]?.slots?.[curNode.path] if (curSlot && !isEmpty(curSlot)) { curSlot.forEach((widgetId: string) => { nodeStack.push({ _type: 'widget', - ...appManager.widgetMap.get(widgetId)?.meta!, + ...toJS(appManager.widgetMap[widgetId]?.meta), }) parentTreeNodes.push(curTreeNode) }) diff --git a/packages/canvas-designer/src/hooks/useWidgetDrop.ts b/packages/canvas-designer/src/hooks/useWidgetDrop.ts index 00d34b0..c67fb11 100644 --- a/packages/canvas-designer/src/hooks/useWidgetDrop.ts +++ b/packages/canvas-designer/src/hooks/useWidgetDrop.ts @@ -4,11 +4,12 @@ import { useContext, useEffect, useRef } from 'react' import { useDrop } from 'react-dnd' import { match } from 'ts-pattern' -import { AppFactoryContext } from '@modou/core' +import { AppFactoryContext } from '@modou/asset-vfs' import { WidgetFileMeta } from '@modou/meta-vfs' import { useCanvasDesignerFile } from '../contexts/CanvasDesignerFileContext' import { useCanvasDesignerStore } from '../contexts/CanvasDesignerStoreContext' +import { emitter } from '../event-bus' import { DropIndicator, DropIndicatorInsertPositionEnum, @@ -124,9 +125,9 @@ export const useWidgetDrop = ({ // 普通组件只能放置兄弟节点 const dropType: DropType = slotPath ? DropType.Slot : DropType.Widget const widgetFactory = useContext(AppFactoryContext) - const widget = canvasDesignerFile.widgetMap.get(widgetId)! + const widget = canvasDesignerFile.widgetMap[widgetId]! - const isEmptySlot = !!slotPath && isEmpty(widget.meta.slots[slotPath]) + const isEmptySlot = !!slotPath && isEmpty(widget.slots[slotPath]) const getDropIndicator = useMemoizedFn(() => { return canvasDesignerStore.dropIndicator @@ -174,7 +175,7 @@ export const useWidgetDrop = ({ sourceWidgetId: item.widget.id, targetWidgetId: parent.props.id, targetSlotPath: parentSlotPath, - targetPosition: parent.props.slots[parentSlotPath].findIndex( + targetPosition: parent.file.slots[parentSlotPath].findIndex( (widgetId) => widget.meta.id === widgetId, ), }) @@ -186,10 +187,9 @@ export const useWidgetDrop = ({ sourceWidgetId: item.widget.id, targetWidgetId: parent.props.id, targetSlotPath: parentSlotPath, - targetPosition: - parent.props.slots[parentSlotPath].findIndex( - (widgetId) => widget.meta.id === widgetId, - ) + 1, + targetPosition: parent.file.slots[parentSlotPath].findIndex( + (widgetId) => widget.meta.id === widgetId, + ), }) } break @@ -198,7 +198,7 @@ export const useWidgetDrop = ({ sourceWidgetId: item.widget.id, targetWidgetId: widget.meta.id, targetSlotPath: slotPath, - targetPosition: widget.meta.slots[slotPath].length, + targetPosition: widget.slots[slotPath].length, }) break default: @@ -211,7 +211,7 @@ export const useWidgetDrop = ({ sourceWidgetMeta: item.widget, targetWidgetId: parent.props.id, targetSlotPath: parentSlotPath, - targetPosition: parent.props.slots[parentSlotPath].findIndex( + targetPosition: parent.file.slots[parentSlotPath].findIndex( (widgetId) => widget.meta.id === widgetId, ), }) @@ -224,7 +224,7 @@ export const useWidgetDrop = ({ targetWidgetId: parent.props.id, targetSlotPath: parentSlotPath, targetPosition: - parent.props.slots[parentSlotPath].findIndex( + parent.file.slots[parentSlotPath].findIndex( (widgetId) => widget.meta.id === widgetId, ) + 1, }) @@ -235,12 +235,16 @@ export const useWidgetDrop = ({ sourceWidgetMeta: item.widget, targetWidgetId: widget.meta.id, targetSlotPath: slotPath, - targetPosition: widget.meta.slots[slotPath].length, + targetPosition: widget.slots[slotPath].length, }) break default: } } + setTimeout(() => { + emitter.emit('onWidgetElementChange') + }) + return { widget: widget.meta } }, hover: (item, monitor) => { diff --git a/packages/canvas-designer/src/hooks/useWidgetSelected.ts b/packages/canvas-designer/src/hooks/useWidgetSelected.ts index 17d6647..079d545 100644 --- a/packages/canvas-designer/src/hooks/useWidgetSelected.ts +++ b/packages/canvas-designer/src/hooks/useWidgetSelected.ts @@ -9,11 +9,15 @@ export const useWidgetSelected = (canvasRef: RefObject) => { // TODO 由 select id 驱动 style useEventListener( 'click', - (event) => { + (event: MouseEvent) => { const rawElement = getSlotRawElement( event.target as unknown as HTMLElement, canvasRef.current, ) + // FIXME 下面代码解决 ProFormText 相同name点击事件会触发多次的问题 + if (event.offsetY < 0 || event.offsetX < 0) { + return + } const selectedWidgetId = getWidgetIdFromElement(rawElement as HTMLElement) canvasDesignerStore.setSelectedWidgetId(selectedWidgetId) }, diff --git a/packages/code-editor/package.json b/packages/code-editor/package.json index 5f8005e..ef59caf 100644 --- a/packages/code-editor/package.json +++ b/packages/code-editor/package.json @@ -19,9 +19,6 @@ "liuleiytu@gmail.com" ], "license": "MIT", - "files": [ - "dist" - ], "commitlint": { "extends": [ "@commitlint/config-conventional" diff --git a/packages/code-editor/src/CodeEditor/autocomplete/AutocompleteSortRules.ts b/packages/code-editor/src/CodeEditor/autocomplete/AutocompleteSortRules.ts index 9bf3cd6..763c469 100644 --- a/packages/code-editor/src/CodeEditor/autocomplete/AutocompleteSortRules.ts +++ b/packages/code-editor/src/CodeEditor/autocomplete/AutocompleteSortRules.ts @@ -1,5 +1,4 @@ -import { FieldEntityInformation } from '@modou/code-editor/CodeEditor/common/editor-config' - +import { FieldEntityInformation } from '../common/editor-config' import { AutocompleteDataType, Completion, @@ -164,7 +163,7 @@ class DataTreeFunctionRule implements AutocompleteRule { static threshold = 1 << RuleWeight.DataTreeFunction computeScore(completion: Completion): number { let score = 0 - if (!(completion.origin === 'DATA_TREE.APPSMITH.FUNCTIONS')) return score + if (!(completion.origin === 'DATA_TREE.MDOUDOU.FUNCTIONS')) return score score += DataTreeFunctionRule.threshold const rankInSortedList = DataTreeFunctionSortOrder.indexOf(completion.text) + 1 @@ -221,7 +220,7 @@ class PriorityMatchRule implements AutocompleteRule { const relevantText = completion.text.split('.').pop() const priorities = PriorityOrder[ - currentFieldInfo.expectedType || AutocompleteDataType.UNKNOWN + currentFieldInfo.expectedType ?? AutocompleteDataType.UNKNOWN ] if (relevantText && priorities.includes(relevantText)) score += PriorityMatchRule.threshold diff --git a/packages/code-editor/src/CodeEditor/autocomplete/CodeMirrorTernService.ts b/packages/code-editor/src/CodeEditor/autocomplete/CodeMirrorTernService.ts index 5fbd789..2c10663 100644 --- a/packages/code-editor/src/CodeEditor/autocomplete/CodeMirrorTernService.ts +++ b/packages/code-editor/src/CodeEditor/autocomplete/CodeMirrorTernService.ts @@ -1,18 +1,15 @@ import CodeMirror, { Hint, Hints, Pos, cmpPos } from 'codemirror' import { Def, Document } from 'tern' -import { AutocompleteSorter } from '@modou/code-editor/CodeEditor/autocomplete/AutocompleteSortRules' -import { TernWorkerServer } from '@modou/code-editor/CodeEditor/autocomplete/TernWorkerServer' -import { getCompletionsForKeyword } from '@modou/code-editor/CodeEditor/autocomplete/keywordCompletion' -import { - ENTITY_TYPE, - FieldEntityInformation, -} from '@modou/code-editor/CodeEditor/common/editor-config' -import { DEFS } from '@modou/code-editor/CodeEditor/tern/defs' +import { ENTITY_TYPE, FieldEntityInformation } from '../common/editor-config' +import { DEFS } from '../tern/defs' import { getDynamicStringSegments, isDynamicValue, -} from '@modou/code-editor/CodeEditor/utils/DynamicBindingUtils' +} from '../utils/DynamicBindingUtils' +import { AutocompleteSorter } from './AutocompleteSortRules' +import { TernWorkerServer } from './TernWorkerServer' +import { getCompletionsForKeyword } from './keywordCompletion' export enum AutocompleteDataType { OBJECT = 'OBJECT', @@ -272,7 +269,7 @@ class CodeMirrorTernService { completions, this.fieldEntityInformation, this.defEntityInformation.get( - this.fieldEntityInformation.entityName || '', + this.fieldEntityInformation.entityName ?? '', ), shouldComputeBestMatch, ) diff --git a/packages/code-editor/src/CodeEditor/autocomplete/TernWorkerServer.ts b/packages/code-editor/src/CodeEditor/autocomplete/TernWorkerServer.ts index ffba208..decac08 100644 --- a/packages/code-editor/src/CodeEditor/autocomplete/TernWorkerServer.ts +++ b/packages/code-editor/src/CodeEditor/autocomplete/TernWorkerServer.ts @@ -1,9 +1,6 @@ import { Def } from 'tern' -import { - CallbackFn, - TernWorkerAction, -} from '@modou/code-editor/CodeEditor/autocomplete/types' +import { CallbackFn, TernWorkerAction } from './types' const ternWorker = new Worker( new URL('../works/tern/tern.worker.ts', import.meta.url), diff --git a/packages/code-editor/src/CodeEditor/autocomplete/customTreeTypeDefCreator.ts b/packages/code-editor/src/CodeEditor/autocomplete/customTreeTypeDefCreator.ts index 6505201..462298c 100644 --- a/packages/code-editor/src/CodeEditor/autocomplete/customTreeTypeDefCreator.ts +++ b/packages/code-editor/src/CodeEditor/autocomplete/customTreeTypeDefCreator.ts @@ -1,7 +1,7 @@ import { Def } from 'tern' -import { generateTypeDef } from '@modou/code-editor/CodeEditor/autocomplete/dataTreeTypeDefCreator' -import { TruthyPrimitiveTypes } from '@modou/code-editor/CodeEditor/utils/TypeHelpers' +import { TruthyPrimitiveTypes } from '../utils/TypeHelpers' +import { generateTypeDef } from './dataTreeTypeDefCreator' export type AdditionalDynamicDataTree = Record< string, diff --git a/packages/code-editor/src/CodeEditor/autocomplete/dataTreeTypeDefCreator.ts b/packages/code-editor/src/CodeEditor/autocomplete/dataTreeTypeDefCreator.ts index 7590320..4a8f2e8 100644 --- a/packages/code-editor/src/CodeEditor/autocomplete/dataTreeTypeDefCreator.ts +++ b/packages/code-editor/src/CodeEditor/autocomplete/dataTreeTypeDefCreator.ts @@ -1,7 +1,7 @@ import { isObject, uniqueId } from 'lodash' import { Def } from 'tern' -import { Types, getType } from '@modou/code-editor/CodeEditor/utils/TypeHelpers' +import { Types, getType } from '../utils/TypeHelpers' export type ExtraDef = Record diff --git a/packages/code-editor/src/CodeEditor/autocomplete/dataTypeSortRules.ts b/packages/code-editor/src/CodeEditor/autocomplete/dataTypeSortRules.ts index 969680d..67da3cd 100644 --- a/packages/code-editor/src/CodeEditor/autocomplete/dataTypeSortRules.ts +++ b/packages/code-editor/src/CodeEditor/autocomplete/dataTypeSortRules.ts @@ -1,4 +1,4 @@ -import { AutocompleteDataType } from '@modou/code-editor/CodeEditor/autocomplete/CodeMirrorTernService' +import { AutocompleteDataType } from './CodeMirrorTernService' export const PriorityOrder: Record = { STRING: ['selectedRow', 'data', 'text'], diff --git a/packages/code-editor/src/CodeEditor/common/codeEditorUtils.ts b/packages/code-editor/src/CodeEditor/common/codeEditorUtils.ts index 74699a5..32d9acb 100644 --- a/packages/code-editor/src/CodeEditor/common/codeEditorUtils.ts +++ b/packages/code-editor/src/CodeEditor/common/codeEditorUtils.ts @@ -1,6 +1,6 @@ import CodeMirror from 'codemirror' -import { getDynamicStringSegments } from '@modou/code-editor/CodeEditor/utils/DynamicBindingUtils' +import { getDynamicStringSegments } from '../utils/DynamicBindingUtils' const computeCursorIndex = (editor: CodeMirror.Editor) => { const cursor = editor.getCursor() diff --git a/packages/code-editor/src/CodeEditor/common/data-tree.ts b/packages/code-editor/src/CodeEditor/common/data-tree.ts index db556ec..d4a6b2e 100644 --- a/packages/code-editor/src/CodeEditor/common/data-tree.ts +++ b/packages/code-editor/src/CodeEditor/common/data-tree.ts @@ -13,8 +13,8 @@ interface Page { userPermissions?: string[] } -interface DataTreeAppsmith { - ENTITY_TYPE: ENTITY_TYPE.APPSMITH +interface DataTreeMoDou { + ENTITY_TYPE: ENTITY_TYPE.MDOUDOU user: { name: string age: number @@ -28,6 +28,6 @@ export interface WidgetEvalTree extends WidgetBaseProps { interface DataTreeWidget extends WidgetEvalTree {} -export type DataTreeObjectEntity = DataTreeWidget | DataTreeAppsmith +export type DataTreeObjectEntity = DataTreeWidget | DataTreeMoDou export type DataTreeEntity = DataTreeObjectEntity | Page[] diff --git a/packages/code-editor/src/CodeEditor/common/editor-config.ts b/packages/code-editor/src/CodeEditor/common/editor-config.ts index f8bd782..50e3096 100644 --- a/packages/code-editor/src/CodeEditor/common/editor-config.ts +++ b/packages/code-editor/src/CodeEditor/common/editor-config.ts @@ -1,8 +1,8 @@ import CodeMirror from 'codemirror' -import { AutocompleteDataType } from '@modou/code-editor/CodeEditor/autocomplete/CodeMirrorTernService' -import { DataTreeEntity } from '@modou/code-editor/CodeEditor/common/data-tree' -import { TruthyPrimitiveTypes } from '@modou/code-editor/CodeEditor/utils/TypeHelpers' +import { AutocompleteDataType } from '../autocomplete/CodeMirrorTernService' +import { TruthyPrimitiveTypes } from '../utils/TypeHelpers' +import { DataTreeEntity } from './data-tree' export enum CodeEditorModeEnum { Text = 'text/plain', @@ -61,7 +61,7 @@ export type MarkHelper = (editor: CodeMirror.Editor) => void export enum ENTITY_TYPE { ACTION = 'ACTION', WIDGET = 'WIDGET', - APPSMITH = 'APPSMITH', + MDOUDOU = 'MDOUDOU', JSACTION = 'JSACTION', } diff --git a/packages/code-editor/src/CodeEditor/common/hintHelpers.ts b/packages/code-editor/src/CodeEditor/common/hintHelpers.ts index f04bd4d..4e6e413 100644 --- a/packages/code-editor/src/CodeEditor/common/hintHelpers.ts +++ b/packages/code-editor/src/CodeEditor/common/hintHelpers.ts @@ -1,12 +1,9 @@ import CodeMirror from 'codemirror' -import { CodeMirrorTernServiceInstance } from '@modou/code-editor/CodeEditor/autocomplete/CodeMirrorTernService' -import { checkIfCursorInsideBinding } from '@modou/code-editor/CodeEditor/common/codeEditorUtils' -import { - ENTITY_TYPE, - HintHelper, -} from '@modou/code-editor/CodeEditor/common/editor-config' -import { KeyboardShortcuts } from '@modou/code-editor/CodeEditor/constants/KeyboardShortcuts' +import { CodeMirrorTernServiceInstance } from '../autocomplete/CodeMirrorTernService' +import { KeyboardShortcuts } from '../constants/KeyboardShortcuts' +import { checkIfCursorInsideBinding } from './codeEditorUtils' +import { ENTITY_TYPE, HintHelper } from './editor-config' export const bindingHint: HintHelper = (editor) => { editor.setOption('extraKeys', { @@ -19,7 +16,7 @@ export const bindingHint: HintHelper = (editor) => { CodeMirrorTernServiceInstance.showType(cm) }, [KeyboardShortcuts.CodeEditor.OpenDocsLink]: (cm: CodeMirror.Editor) => { - CodeMirrorTernServiceInstance.showDocs(cm) + void CodeMirrorTernServiceInstance.showDocs(cm) }, }) return { diff --git a/packages/code-editor/src/CodeEditor/common/mark-helpers.ts b/packages/code-editor/src/CodeEditor/common/mark-helpers.ts index d3ef403..22fd813 100644 --- a/packages/code-editor/src/CodeEditor/common/mark-helpers.ts +++ b/packages/code-editor/src/CodeEditor/common/mark-helpers.ts @@ -1,8 +1,8 @@ import CodeMirror from 'codemirror' import { isEmpty } from 'lodash' -import { MarkHelper } from '@modou/code-editor/CodeEditor/common/editor-config' -import { AUTOCOMPLETE_MATCH_REGEX } from '@modou/code-editor/CodeEditor/constants/bindings' +import { AUTOCOMPLETE_MATCH_REGEX } from '../constants/bindings' +import { MarkHelper } from './editor-config' export const bindingMarker: MarkHelper = (editor: CodeMirror.Editor) => { editor.eachLine((line: CodeMirror.LineHandle) => { diff --git a/packages/code-editor/src/CodeEditor/common/modes.ts b/packages/code-editor/src/CodeEditor/common/modes.ts index 471e630..2ff7406 100644 --- a/packages/code-editor/src/CodeEditor/common/modes.ts +++ b/packages/code-editor/src/CodeEditor/common/modes.ts @@ -4,7 +4,7 @@ import 'codemirror/addon/mode/multiplex' import 'codemirror/mode/javascript/javascript' import 'codemirror/mode/sql/sql' -import { CodeEditorModeEnum } from '@modou/code-editor/CodeEditor/common/editor-config' +import { CodeEditorModeEnum } from './editor-config' CodeMirror.defineMode(CodeEditorModeEnum.TextWithJs, (config) => CodeMirror.multiplexingMode( diff --git a/packages/code-editor/src/CodeEditor/demo.tsx b/packages/code-editor/src/CodeEditor/demo.tsx index e18723e..f7851c5 100644 --- a/packages/code-editor/src/CodeEditor/demo.tsx +++ b/packages/code-editor/src/CodeEditor/demo.tsx @@ -5,13 +5,20 @@ import { mock_code_editor_props } from '@modou/code-editor/CodeEditor/mock' const Demo: FC = () => { return ( + // @ts-expect-error { + console.log(value) + }} /> ) } diff --git a/packages/code-editor/src/CodeEditor/index.tsx b/packages/code-editor/src/CodeEditor/index.tsx index 293307d..6a23611 100644 --- a/packages/code-editor/src/CodeEditor/index.tsx +++ b/packages/code-editor/src/CodeEditor/index.tsx @@ -1,10 +1,15 @@ -import { useMemoizedFn, useMount } from 'ahooks' -import { Typography } from 'antd' +import { useDebounceFn, useMemoizedFn, useMount } from 'ahooks' import CodeMirror from 'codemirror' import React, { type FC, useEffect, useRef, useState } from 'react' +import { Def } from 'tern' -import { CodeMirrorTernServiceInstance } from '@modou/code-editor/CodeEditor/autocomplete/CodeMirrorTernService' -import { updateCustomDef } from '@modou/code-editor/CodeEditor/autocomplete/customDefUtils' +// import { ExpectedValueExample } from '@modou/code-editor/CodeEditor/utils/validation/common' +import { injectGlobal, mcss } from '@modou/css-in-js' +import { MDTernDefs } from '@modou/refine' + +import { CodeMirrorTernServiceInstance } from './autocomplete/CodeMirrorTernService' +import { updateCustomDef } from './autocomplete/customDefUtils' +import './common/code-mirror-libs' import { CodeEditorModeEnum, DataTree, @@ -13,19 +18,11 @@ import { MarkHelper, isCloseKey, isModifierKey, -} from '@modou/code-editor/CodeEditor/common/editor-config' -import { bindingHint } from '@modou/code-editor/CodeEditor/common/hintHelpers' -import { bindingMarker } from '@modou/code-editor/CodeEditor/common/mark-helpers' -import { - mock_code_editor_props, - mock_dyn_def, - mock_entityInfo, -} from '@modou/code-editor/CodeEditor/mock' -// import { ExpectedValueExample } from '@modou/code-editor/CodeEditor/utils/validation/common' -import { injectGlobal, mcss } from '@modou/css-in-js' - -import './common/code-mirror-libs' +} from './common/editor-config' +import { bindingHint } from './common/hintHelpers' +import { bindingMarker } from './common/mark-helpers' import './common/modes' +import { mock_code_editor_props, mock_entityInfo } from './mock' // export type CodeEditorExpected = { // type: string @@ -48,13 +45,23 @@ const startAutocomplete = ( }) } +interface CodeEditorProps { + onChange: (value: string) => void + value: string + getDataTreeDefs?: () => MDTernDefs + mode: CodeEditorModeEnum +} + export const CodeEditor: FC< { hinting?: HintHelper[] - } & typeof mock_code_editor_props + } & typeof mock_code_editor_props & + CodeEditorProps > = (_props) => { const props: typeof _props = { ..._props, + mode: _props.mode || CodeEditorModeEnum.TextWithJs, + getDataTreeDefs: _props.getDataTreeDefs ?? (() => ({})), hinting: _props.hinting ?? [bindingHint], ...mock_code_editor_props, } @@ -62,10 +69,10 @@ export const CodeEditor: FC< const cmRef = useRef(null) const hintersRef = useRef(null) - const [isFocused, setaIsFocused] = useState(false) + const [isFocused, setIsFocused] = useState(false) const handleEditorFocus = useMemoizedFn((cm: CodeMirror.Editor) => { - setaIsFocused(true) + setIsFocused(true) const { ch, line, sticky } = cm.getCursor() // Check if it is a user focus if ( @@ -78,6 +85,11 @@ export const CodeEditor: FC< } if (!cm.state.completionActive) { + CodeMirrorTernServiceInstance.updateDef( + 'DATA_TREE', + props.getDataTreeDefs?.() as unknown as Def, + mock_entityInfo, + ) updateCustomDef(props.additionalDynamicData) const entityInformation = props.entityInformation @@ -97,7 +109,7 @@ export const CodeEditor: FC< // } }) const handleEditorBlur = useMemoizedFn(() => { - setaIsFocused(false) + setIsFocused(false) }) const handleAutocompleteVisibility = useMemoizedFn( @@ -159,12 +171,13 @@ export const CodeEditor: FC< if (!cmWrapperRef.current) { return } + if (!cmRef.current) { cmRef.current = CodeMirror(cmWrapperRef.current, { autoRefresh: true, viewportMargin: 10, - value: `{{const a = 123}}`, - mode: CodeEditorModeEnum.TextWithJs, + value: props.value, + mode: props.mode, tabindex: -1, tabSize: 2, autoCloseBrackets: true, @@ -189,13 +202,30 @@ export const CodeEditor: FC< } }) + const { run: debounceOnChange } = useDebounceFn(props.onChange, { + wait: 300, + }) + const handleChange = useMemoizedFn(() => { if (!cmRef.current) { return } updateMarkings(cmRef.current, [bindingMarker]) + debounceOnChange(cmRef.current?.getValue()) }) + useEffect(() => { + if (!cmRef.current) { + return + } + const editorValue = cmRef.current.getValue() + if (editorValue !== props.value) { + cmRef.current.setValue(props.value) + // when input gets updated on focus out clear undo/redo from codeMirror History + cmRef.current.clearHistory() + } + }, [props.value]) + useEffect(() => { cmRef.current?.on('change', handleChange) cmRef.current?.on('keyup', handleAutocompleteKeyup) @@ -204,8 +234,8 @@ export const CodeEditor: FC< return () => { cmRef.current?.off('change', handleChange) cmRef.current?.off('keyup', handleAutocompleteKeyup) - cmRef.current?.on('focus', handleEditorFocus) - cmRef.current?.on('blur', handleEditorBlur) + cmRef.current?.off('focus', handleEditorFocus) + cmRef.current?.off('blur', handleEditorBlur) } }, [ handleAutocompleteKeyup, @@ -222,10 +252,18 @@ export const CodeEditor: FC< setTimeout(() => { CodeMirrorTernServiceInstance.updateDef( 'DATA_TREE', - mock_dyn_def, + props.getDataTreeDefs?.() as unknown as Def, mock_entityInfo, ) }) + + // setTimeout(() => { + // CodeMirrorTernServiceInstance.updateDef( + // 'DATA_TREE', + // mock_dyn_def_1, + // mock_entityInfo, + // ) + // }, 5000) }) // mock end @@ -234,6 +272,7 @@ export const CodeEditor: FC<
+ {/*
预期结构 @@ -250,6 +289,7 @@ export const CodeEditor: FC< EVALUATED VALUE
+*/} ) } @@ -270,8 +310,8 @@ const classes = { height: 100%; padding: 0; border-radius: 4px; - background: #EDF2F7; - color: #1A202C; + background: #EDF2F7!important; + color: rgba(0, 0, 0, 0.88)!important; transition-property: background-color, color, opacity; transition-duration: 0.2s; line-height: 20px; @@ -282,9 +322,10 @@ const classes = { } &-cursor { - border-right: none; - border-left-width: 2px; - border-left-color: #153c15; + border-right: none!important; + border-left-width: 2px!important; + //border-left-color: #153c15; + border-left-color: #153c15!important; } .binding-brackets { @@ -306,11 +347,11 @@ const classes = { `, } -const evaluatedClasses = { - wrapper: mcss` - border: 1px solid green; - `, -} +// const evaluatedClasses = { +// wrapper: mcss` +// border: 1px solid green; +// `, +// } injectGlobal` .CodeMirror{ @@ -364,9 +405,9 @@ injectGlobal` } &::before { - position: static; - margin-right: 4px; - border-radius: 100%; + position: static!important; + margin-right: 4px!important; + border-radius: 100% !important; } &:hover { diff --git a/packages/code-editor/src/CodeEditor/mock.ts b/packages/code-editor/src/CodeEditor/mock.ts index 63a9102..650d722 100644 --- a/packages/code-editor/src/CodeEditor/mock.ts +++ b/packages/code-editor/src/CodeEditor/mock.ts @@ -1,14 +1,65 @@ -import { AutocompleteDataType } from '@modou/code-editor/CodeEditor/autocomplete/CodeMirrorTernService' -import { - ENTITY_TYPE, - FieldEntityInformation, -} from '@modou/code-editor/CodeEditor/common/editor-config' +import { AutocompleteDataType } from './autocomplete/CodeMirrorTernService' +import { ENTITY_TYPE, FieldEntityInformation } from './common/editor-config' export const mock_dyn_def = { '!name': 'DATA_TREE', + '!define': { + '0_1421776803503454': { + dataIndex: { + '!type': 'string', + '!doc': '', + }, + title: { + '!type': 'string', + '!doc': '', + }, + valueType: { + '!type': 'string', + '!doc': '', + }, + buildIn: { + '!type': 'bool', + '!doc': '', + }, + align: { + '!type': 'string', + '!doc': '', + }, + fixed: { + '!type': 'string', + '!doc': '', + }, + width: { + '!type': 'number', + '!doc': '', + }, + mappedValue: { + '!type': 'any', + '!doc': '', + }, + hideInTable: { + '!type': 'bool', + '!doc': '', + }, + }, + }, + 表格_fbsm: { + dataSource: { + '!type': '[any]', + '!doc': '数据源', + }, + size: { + '!type': 'string', + '!doc': '设置表格尺寸', + }, + columns: { + '!type': '[0_1421776803503454]', + '!doc': '表格列', + }, + }, 按钮1: { '!doc': '我是一个按钮', - '!url': 'https://docs.appsmith.com/widget-reference/button', + '!url': 'https://docs.modou.com/widget-reference/button', isVisible: { '!type': 'bool', '!doc': 'Boolean value indicating if the widget is in visible state', @@ -27,16 +78,132 @@ export const mock_dyn_def = { Input1: { '!doc': 'An input text field is used to capture a users textual input such as their names, numbers, emails etc. Inputs are used in forms and can have custom validations.', - '!url': 'https://docs.appsmith.com/widget-reference/input', + '!url': 'https://docs.modou.com/widget-reference/input', + text: { + '!type': 'string', + '!doc': 'The text value of the input', + '!url': 'https://docs.modou.com/widget-reference/input', + }, + inputText: { + '!type': 'string', + '!doc': 'The unformatted text value of the input', + '!url': 'https://docs.modou.com/widget-reference/input', + }, + isValid: 'bool', + isVisible: { + '!type': 'bool', + '!doc': 'Boolean value indicating if the widget is in visible state', + }, + isDisabled: 'bool', + }, + 'Input1.text': { + '!type': 'string', + '!doc': 'The text value of the input', + '!url': 'https://docs.modou.com/widget-reference/input', + }, + 'Input1.inputText': { + '!type': 'string', + '!doc': 'The unformatted text value of the input', + '!url': 'https://docs.modou.com/widget-reference/input', + }, + 'Input1.isValid': 'bool', + 'Input1.isVisible': { + '!type': 'bool', + '!doc': 'Boolean value indicating if the widget is in visible state', + }, + 'Input1.isDisabled': 'bool', + modou: { + user: { + email: 'string', + workspaceIds: '[string]', + username: 'string', + name: 'string', + role: 'string', + useCase: 'string', + enableTelemetry: 'bool', + idToken: {}, + emptyInstance: 'bool', + accountNonExpired: 'bool', + accountNonLocked: 'bool', + credentialsNonExpired: 'bool', + isAnonymous: 'bool', + isEnabled: 'bool', + isSuperUser: 'bool', + isConfigurable: 'bool', + }, + URL: { + fullPath: 'string', + host: 'string', + hostname: 'string', + queryParams: {}, + protocol: 'string', + pathname: 'string', + port: 'string', + hash: 'string', + }, + store: {}, + geolocation: { + canBeRequested: 'bool', + currentPosition: {}, + '!doc': + "The user's geo location information. Only available when requested", + '!url': 'https://docs.modou.com/v/v1.2.1/framework-reference/geolocation', + getCurrentPosition: + 'fn(onSuccess: fn() -> void, onError: fn() -> void, options: object) -> void', + watchPosition: 'fn(options: object) -> void', + clearWatch: 'fn() -> void', + }, + mode: 'string', + theme: { + colors: { + primaryColor: 'string', + backgroundColor: 'string', + }, + borderRadius: { + appBorderRadius: 'string', + }, + boxShadow: { + appBoxShadow: 'string', + }, + fontFamily: { + appFont: 'string', + }, + }, + }, +} +export const mock_dyn_def_1 = { + '!name': 'DATA_TREE', + 按钮2: { + '!doc': '我是一个按钮', + '!url': 'https://docs.modou.com/widget-reference/button', + isVisible: { + '!type': 'bool', + '!doc': 'Boolean value indicating if the widget is in visible state', + }, + text: 'string', + isDisabled: 'bool', + recaptchaToken: 'string', + }, + '按钮2.isVisible': { + '!type': 'bool', + '!doc': 'Boolean value indicating if the widget is in visible state', + }, + '按钮2.text': 'string', + '按钮2.isDisabled': 'bool', + '按钮2.recaptchaToken': 'string', + Input1: { + '!doc': + 'An input text field is used to capture a users textual input such as their names, numbers, emails etc. Inputs are used in forms and can have custom validations.', + '!url': 'https://docs.modou.com/widget-reference/input', text: { '!type': 'string', '!doc': 'The text value of the input', - '!url': 'https://docs.appsmith.com/widget-reference/input', + '!url': 'https://docs.modou.com/widget-reference/input', }, inputText: { '!type': 'string', '!doc': 'The unformatted text value of the input', - '!url': 'https://docs.appsmith.com/widget-reference/input', + '!url': 'https://docs.modou.com/widget-reference/input', }, isValid: 'bool', isVisible: { @@ -48,12 +215,12 @@ export const mock_dyn_def = { 'Input1.text': { '!type': 'string', '!doc': 'The text value of the input', - '!url': 'https://docs.appsmith.com/widget-reference/input', + '!url': 'https://docs.modou.com/widget-reference/input', }, 'Input1.inputText': { '!type': 'string', '!doc': 'The unformatted text value of the input', - '!url': 'https://docs.appsmith.com/widget-reference/input', + '!url': 'https://docs.modou.com/widget-reference/input', }, 'Input1.isValid': 'bool', 'Input1.isVisible': { @@ -61,7 +228,7 @@ export const mock_dyn_def = { '!doc': 'Boolean value indicating if the widget is in visible state', }, 'Input1.isDisabled': 'bool', - appsmith: { + modou: { user: { email: 'string', workspaceIds: '[string]', @@ -96,8 +263,7 @@ export const mock_dyn_def = { currentPosition: {}, '!doc': "The user's geo location information. Only available when requested", - '!url': - 'https://docs.appsmith.com/v/v1.2.1/framework-reference/geolocation', + '!url': 'https://docs.modou.com/v/v1.2.1/framework-reference/geolocation', getCurrentPosition: 'fn(onSuccess: fn() -> void, onError: fn() -> void, options: object) -> void', watchPosition: 'fn(options: object) -> void', @@ -120,7 +286,6 @@ export const mock_dyn_def = { }, }, }, - '!define': {}, } export const mock_entityInfo = new Map() @@ -133,9 +298,9 @@ mock_entityInfo.set('Button1', { type: 'WIDGET', subType: 'BUTTON_WIDGET', }) -mock_entityInfo.set('appsmith', { - type: 'APPSMITH', - subType: 'APPSMITH', +mock_entityInfo.set('modou', { + type: 'MODOU', + subType: 'MODOU', }) export const mock_code_editor_props = { @@ -1191,7 +1356,7 @@ export const mock_code_editor_props = { ], }, }, - appsmith: { + modou: { user: { email: 'liuleiytu@gmail.com', workspaceIds: ['63a210adcb0c41354d0ff121'], @@ -1212,9 +1377,9 @@ export const mock_code_editor_props = { }, URL: { fullPath: - 'https://dev.appsmith.com/applications/63a210adcb0c41354d0ff122/pages/63a210adcb0c41354d0ff125/edit', - host: 'dev.appsmith.com', - hostname: 'dev.appsmith.com', + 'https://dev.modou.com/applications/63a210adcb0c41354d0ff122/pages/63a210adcb0c41354d0ff125/edit', + host: 'dev.modou.com', + hostname: 'dev.modou.com', queryParams: {}, protocol: 'https:', pathname: @@ -1243,7 +1408,7 @@ export const mock_code_editor_props = { appFont: 'System Default', }, }, - ENTITY_TYPE: 'APPSMITH', + ENTITY_TYPE: 'MODOU', __evaluation__: { errors: { 'user.name': [], diff --git a/packages/code-editor/src/CodeEditor/utils/DynamicBindingUtils.ts b/packages/code-editor/src/CodeEditor/utils/DynamicBindingUtils.ts index 7b8558c..fd7360d 100644 --- a/packages/code-editor/src/CodeEditor/utils/DynamicBindingUtils.ts +++ b/packages/code-editor/src/CodeEditor/utils/DynamicBindingUtils.ts @@ -1,4 +1,4 @@ -import { DATA_BIND_REGEX } from '@modou/code-editor/CodeEditor/constants/bindings' +import { DATA_BIND_REGEX } from '../constants/bindings' export const isDynamicValue = (value: string): boolean => DATA_BIND_REGEX.test(value) diff --git a/packages/code-editor/src/CodeEditor/works/tern/tern.worker.ts b/packages/code-editor/src/CodeEditor/works/tern/tern.worker.ts index ab1eec4..b97ebc6 100644 --- a/packages/code-editor/src/CodeEditor/works/tern/tern.worker.ts +++ b/packages/code-editor/src/CodeEditor/works/tern/tern.worker.ts @@ -19,8 +19,8 @@ const startServer = (defs: Def[], plugins = {}, scripts?: string[]) => { }) } +const HEAD_EXPRESSION_REG = /^{{((\n|.)*)}}$/ self.onmessage = (e) => { - console.warn('self.onmessage=>', e.data) const data = e.data switch (data.type) { case TernWorkerAction.INIT: @@ -31,9 +31,15 @@ self.onmessage = (e) => { return server.delFile(data.name) case TernWorkerAction.REQUEST: return server.request(data.body, (err, resData: any) => { - const searchVal = last( - ((head(e.data.body.files) as { text: string }).text ?? '').split('.'), - ) + let searchVal = + last( + ((head(e.data.body.files) as { text: string }).text ?? '').split( + '.', + ), + ) ?? '' + if (HEAD_EXPRESSION_REG.test(searchVal)) { + searchVal = searchVal.match(HEAD_EXPRESSION_REG)?.[1] ?? '' + } if (resData) { resData.completions = resData.completions.filter((item: any) => item.name.includes(searchVal), @@ -60,9 +66,12 @@ const getFile = (file: string, c: CallbackFn) => { pending[nextId] = c } +const _consoleLog = self.console.log self.console = { ...self.console, - log(v) { - postMessage({ type: TernWorkerAction.DEBUG, message: v }) + log(...args) { + _consoleLog(...args) + // TODO 传递全部的args + postMessage({ type: TernWorkerAction.DEBUG, message: args[0] }) }, } diff --git a/packages/code-editor/src/index.ts b/packages/code-editor/src/index.ts index 0b90787..3231664 100644 --- a/packages/code-editor/src/index.ts +++ b/packages/code-editor/src/index.ts @@ -1 +1,3 @@ +export { CodeEditorModeEnum } from './CodeEditor/common/editor-config' + export { CodeEditor } from './CodeEditor' diff --git a/packages/core/package.json b/packages/core/package.json index f1622fe..66a2259 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -2,7 +2,7 @@ "name": "@modou/core", "version": "1.0.0", "description": "", - "main": "src/index.ts", + "main": "src", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, @@ -10,6 +10,5 @@ "author": "", "license": "ISC", "dependencies": { - "nanoid": "^4.0.0" } } diff --git a/packages/core/src/contexts/WidgetsDeepPositionContext.ts b/packages/core/src/contexts/WidgetsDeepPositionContext.ts deleted file mode 100644 index e69de29..0000000 diff --git a/packages/core/src/contexts/index.ts b/packages/core/src/contexts/index.ts deleted file mode 100644 index 83c0d52..0000000 --- a/packages/core/src/contexts/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './AppFactoryContext' -export * from './AppManagerContext' diff --git a/packages/core/src/flow-node/FlowNodeMetadata.ts b/packages/core/src/flow-node/FlowNodeMetadata.ts new file mode 100644 index 0000000..5bd121c --- /dev/null +++ b/packages/core/src/flow-node/FlowNodeMetadata.ts @@ -0,0 +1,145 @@ +import { mapValues } from 'lodash' +import { ReactNode } from 'react' + +import { + JsonSchema7ObjectType, + MRScheme, + MRTypeAny, + mr, + mrToJsonSchema, + schemeToJsonDefault, +} from '@modou/refine' +import { + MRArraySetterType, + MRBooleanSetterType, + MRNumberSetterType, + MRSelectSetterType, + MRStringSetterType, + SETTER_KEY, +} from '@modou/setters' +import { generateId } from '@modou/shared' + +import { MDVersion } from '../types' +import { FlowNodeBaseProps, FlowNodePortNameEnum } from './types' + +// TODO 合并 Widget 类型定义 +export interface MRFlowNodeProps { + [k: string]: { + setter?: + | MRNumberSetterType + | MRStringSetterType + | MRBooleanSetterType + | MRSelectSetterType + | MRArraySetterType + def: MRTypeAny + } +} +interface BaseFlowNodeMetadata { + version: MDVersion + icon: ReactNode + name: string + type: string + mrPropsScheme: PropsMRScheme +} +export class FlowNodeMetadata + implements BaseFlowNodeMetadata +{ + constructor({ + type, + icon, + name, + version, + mrPropsScheme, + }: BaseFlowNodeMetadata) { + this.type = type + this.icon = icon + this.name = name + this.version = version + this.mrPropsScheme = mrPropsScheme + this.jsonPropsSchema = mrToJsonSchema( + mrPropsScheme, + ) as unknown as JsonSchema7ObjectType + } + + type + icon + name + version + mrPropsScheme + jsonPropsSchema: JsonSchema7ObjectType + + static createMetadata( + metadata: BaseFlowNodeMetadata, + ) { + return new FlowNodeMetadata(metadata) + } + + static createMRFlowNodeProps({ + name, + type, + props, + defaultSources, + }: { + name: string + type: string + props: T + defaultSources?: Array<{ name: string }> + }) { + type PropsRawShape = { + [K in keyof S]: S[K]['def'] + } + const propsRawShape: PropsRawShape = mapValues(props, (prop) => { + if (prop.setter) { + return prop.def._extra({ + [SETTER_KEY]: prop.setter, + }) + } + return prop.def + }) + + return mr.object({ + id: mr.string(), + type: mr.literal(type), + name: mr.literal(name), + position: mr.object({ + x: mr.number().default(100), + y: mr.number().default(100), + }), + // 作为 source + sources: mr + .array( + mr.object({ + name: mr.string(), + }), + ) + .default( + defaultSources ?? [ + { + name: FlowNodePortNameEnum.SOURCE, + }, + ], + ), + // 作为 target + targets: mr + .array( + mr.object({ + name: mr.string(), + }), + ) + .default([ + { + name: FlowNodePortNameEnum.TARGET, + }, + ]), + props: mr.object(propsRawShape), + }) + } + + static mrSchemeToDefaultJson( + scheme: Parameters[0], + ): FlowNodeBaseProps { + const props = schemeToJsonDefault(scheme) as unknown as FlowNodeBaseProps + props.id = generateId() + return props + } +} diff --git a/packages/core/src/flow-node/index.ts b/packages/core/src/flow-node/index.ts new file mode 100644 index 0000000..c477abf --- /dev/null +++ b/packages/core/src/flow-node/index.ts @@ -0,0 +1,2 @@ +export * from './FlowNodeMetadata' +export * from './types' diff --git a/packages/core/src/flow-node/types.ts b/packages/core/src/flow-node/types.ts new file mode 100644 index 0000000..de3589d --- /dev/null +++ b/packages/core/src/flow-node/types.ts @@ -0,0 +1,15 @@ +import { mr } from '@modou/refine' + +import { FlowNodeMetadata, MRFlowNodeProps } from './FlowNodeMetadata' + +export type FlowNodeBaseProps = mr.infer< + ReturnType> +> +export enum FlowNodePortNameEnum { + SOURCE = 'SOURCE', + TARGET = 'TARGET', +} + +export type FlowNodeInterpreter

= ( + props: P, +) => void diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index afb6772..a870f86 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,5 +1,4 @@ export * from './widget' -export * from './app-factory' +export * from './flow-node' export * from './types' -export * from './contexts' export * from './utils' diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 019a5f2..43d0bca 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -1,12 +1,5 @@ -import { customAlphabet } from 'nanoid' - import { WidgetGroupEnum } from '../types' -const nanoid = customAlphabet('abcdefghigklmnopqrstuvwxyz', 12) -export const generateId = (size: number = 12): string => { - return nanoid(size) -} - export const getWidgetGroupLabel = (group: WidgetGroupEnum) => { switch (group) { case WidgetGroupEnum.Button: diff --git a/packages/core/src/widget/WidgetMetadata.ts b/packages/core/src/widget/WidgetMetadata.ts index 9f2a570..61837d0 100644 --- a/packages/core/src/widget/WidgetMetadata.ts +++ b/packages/core/src/widget/WidgetMetadata.ts @@ -11,7 +11,6 @@ import { schemeToJsonDefault, } from '@modou/refine' import { - BaseSetterProps, MRArraySetterType, MRBooleanSetterType, MRNumberSetterType, @@ -19,6 +18,7 @@ import { MRStringSetterType, SETTER_KEY, } from '@modou/setters' +import { BaseWidgetSetterProps } from '@modou/setters/src/types' import { InferWidgetState } from '@modou/widgets-antd' import { MDVersion } from '../types' @@ -43,10 +43,11 @@ interface BaseWidgetMetadata< name: string slots: WidgetSlots // TODO FIX TYPE - setters: Record>> + setters: Record>> mrPropsScheme: PropsMRScheme mrStateScheme: StateMRScheme - initState: ( + // TODO 通过 props 和 state 的类型对比判断 initState 是否是必传的 + initState?: ( widget: mr.infer, ) => Omit< InferWidgetState, @@ -54,6 +55,7 @@ interface BaseWidgetMetadata< | 'renderSlotPaths' | 'renderSlots' | 'updateState' + | 'instance' > } @@ -101,7 +103,20 @@ export class WidgetMetadata< // TODO FIX TYPE this.setters = setters this.icon = icon - this.initState = initState + // TODO IMPORTANT 完善类型定义 + this.initState = ((...args: [{ id: string }]) => { + const { id } = args[0] + return { + instance: { + id, + widgetId: id, + initialized: false, + }, + ...(initState?.(...args) ?? {}), + } + }) as unknown as Required< + BaseWidgetMetadata + >['initState'] } version @@ -114,7 +129,7 @@ export class WidgetMetadata< jsonStateSchema: JsonSchema7ObjectType initState slots: WidgetSlots - setters: Record>> + setters: Record>> static createMetadata< PropsMRScheme extends MRScheme, @@ -160,6 +175,15 @@ export class WidgetMetadata< name: mr.literal(name), props: mr.object(propsRawShape), slots: mr.object(slots ?? ({} as unknown as S)), + dynamicSlots: mr + .record( + mr.object({ + name: mr.string(), + children: mr.array(mr.string()), + accept: mr.array(mr.string()).optional(), + }), + ) + .default({}), }) } diff --git a/packages/core/src/widget/hooks/index.ts b/packages/core/src/widget/hooks/index.ts new file mode 100644 index 0000000..95b1f9f --- /dev/null +++ b/packages/core/src/widget/hooks/index.ts @@ -0,0 +1 @@ +export { useWidgetMetaSlots } from './useWidgetMetaSlots' diff --git a/packages/core/src/widget/hooks/useWidgetMetaSlots.ts b/packages/core/src/widget/hooks/useWidgetMetaSlots.ts new file mode 100644 index 0000000..c80f31e --- /dev/null +++ b/packages/core/src/widget/hooks/useWidgetMetaSlots.ts @@ -0,0 +1,17 @@ +import { WidgetFile } from '@modou/meta-vfs' + +import { WidgetMetadata } from '../WidgetMetadata' + +// TODO 直接从 file 取文件,已有代码,待替换 +export const useWidgetMetaSlots = ({ + widgetMeta, + widget, +}: { + widgetMeta: WidgetMetadata + widget: WidgetFile +}) => { + return { + ...(widgetMeta.slots || {}), + ...widget.meta.dynamicSlots, + } +} diff --git a/packages/core/src/widget/index.ts b/packages/core/src/widget/index.ts index 0994fea..eea59e0 100644 --- a/packages/core/src/widget/index.ts +++ b/packages/core/src/widget/index.ts @@ -1,2 +1,3 @@ export * from './WidgetMetadata' export * from './types' +export * from './hooks' diff --git a/packages/css-in-js/src/theme.tsx b/packages/css-in-js/src/theme.tsx index 930cc30..dff7517 100644 --- a/packages/css-in-js/src/theme.tsx +++ b/packages/css-in-js/src/theme.tsx @@ -8,6 +8,7 @@ import { FC, ReactNode, useMemo } from 'react' interface MDTheme { colorPrimary: string colorError: string + colorText: string } interface ThemeProviderProps { @@ -21,6 +22,7 @@ export const ThemeProvider: FC = ({ children }) => { return { colorPrimary: token.colorPrimary, colorError: token.colorError, + colorText: token.colorText, } }, [token]) return {children} diff --git a/packages/evaluate/package.json b/packages/evaluate/package.json new file mode 100644 index 0000000..78ecb93 --- /dev/null +++ b/packages/evaluate/package.json @@ -0,0 +1,12 @@ +{ + "name": "@modou/evaluate", + "version": "1.0.0", + "description": "", + "main": "src", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/packages/render/src/utils/evaluate.ts b/packages/evaluate/src/index.ts similarity index 50% rename from packages/render/src/utils/evaluate.ts rename to packages/evaluate/src/index.ts index 6277bce..570c921 100644 --- a/packages/render/src/utils/evaluate.ts +++ b/packages/evaluate/src/index.ts @@ -1,12 +1,13 @@ -import { PageState } from '@modou/state-manager' - const EXPRESSION_REG = /[\s\S]*{{[\s\S]*}}[\s\S]*/m export const isExpression = (str: unknown): str is string => { return typeof str === 'string' && EXPRESSION_REG.test(str) } // TODO 完善 store 类型 -export const evalExpression = (expression: string, canvasState: PageState) => { +export const evalExpression = ( + expression: string, + state: Record, +) => { try { // eslint-disable-next-line @typescript-eslint/no-implied-eval,no-new-func,no-useless-call return new Function( @@ -14,8 +15,20 @@ export const evalExpression = (expression: string, canvasState: PageState) => { `with(state){ return ${expression} }`, - ).call(null, canvasState.subState.widget) + // TODO state 打平收集到 stateManager + ).call(null, state) } catch (e) { return String((e as SyntaxError)?.message) ?? 'Error' } } + +export const evalScript = (script: string, state: Record) => { + // eslint-disable-next-line @typescript-eslint/no-implied-eval,no-new-func,no-useless-call + return new Function( + 'state', + `with(state){ + ${script} + }`, + // TODO state 打平收集到 stateManager + ).call(null, state) +} diff --git a/packages/flow-designer/.dumirc.ts b/packages/flow-designer/.dumirc.ts new file mode 100644 index 0000000..8cd21a7 --- /dev/null +++ b/packages/flow-designer/.dumirc.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'dumi'; + +export default defineConfig({ + outputPath: 'docs-dist', + themeConfig: { + name: '@modou/flow-designer', + }, +}); diff --git a/packages/flow-designer/.fatherrc.ts b/packages/flow-designer/.fatherrc.ts new file mode 100644 index 0000000..dae25c8 --- /dev/null +++ b/packages/flow-designer/.fatherrc.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'father'; + +export default defineConfig({ + // more father config: https://github.com/umijs/father/blob/master/docs/config.md + esm: { output: 'dist' }, +}); diff --git a/packages/flow-designer/.gitignore b/packages/flow-designer/.gitignore new file mode 100644 index 0000000..cf89318 --- /dev/null +++ b/packages/flow-designer/.gitignore @@ -0,0 +1,6 @@ +node_modules +/dist +.dumi/tmp +.dumi/tmp-test +.dumi/tmp-production +.DS_Store diff --git a/packages/flow-designer/.husky/commit-msg b/packages/flow-designer/.husky/commit-msg new file mode 100755 index 0000000..5b0b354 --- /dev/null +++ b/packages/flow-designer/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +npx commitlint --edit "${1}" diff --git a/packages/flow-designer/.husky/pre-commit b/packages/flow-designer/.husky/pre-commit new file mode 100755 index 0000000..d24fdfc --- /dev/null +++ b/packages/flow-designer/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +npx lint-staged diff --git a/packages/flow-designer/LICENSE b/packages/flow-designer/LICENSE new file mode 100644 index 0000000..b0c7139 --- /dev/null +++ b/packages/flow-designer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/flow-designer/README.md b/packages/flow-designer/README.md new file mode 100644 index 0000000..063a8cb --- /dev/null +++ b/packages/flow-designer/README.md @@ -0,0 +1,40 @@ +# @modou/flow-designer + +[![NPM version](https://img.shields.io/npm/v/@modou/flow-designer.svg?style=flat)](https://npmjs.org/package/@modou/flow-designer) +[![NPM downloads](http://img.shields.io/npm/dm/@modou/flow-designer.svg?style=flat)](https://npmjs.org/package/@modou/flow-designer) + +A react library developed with dumi + +## Usage + +TODO + +## Options + +TODO + +## Development + +```bash +# install dependencies +$ pnpm install + +# develop library by docs demo +$ pnpm start + +# build library source code +$ pnpm run build + +# build library source code in watch mode +$ pnpm run build:watch + +# build docs +$ pnpm run docs:build + +# check your project for potential problems +$ pnpm run doctor +``` + +## LICENSE + +MIT diff --git a/packages/flow-designer/docs/guide.md b/packages/flow-designer/docs/guide.md new file mode 100644 index 0000000..5504ddf --- /dev/null +++ b/packages/flow-designer/docs/guide.md @@ -0,0 +1 @@ +This is a guide example. diff --git a/packages/flow-designer/docs/index.md b/packages/flow-designer/docs/index.md new file mode 100644 index 0000000..2cb82d8 --- /dev/null +++ b/packages/flow-designer/docs/index.md @@ -0,0 +1,22 @@ +--- +hero: + title: library + description: A react library developed with dumi + actions: + - text: Hello + link: / + - text: World + link: / +features: + - title: Hello + emoji: 💎 + description: Put hello description here + - title: World + emoji: 🌈 + description: Put world description here + - title: '!' + emoji: 🚀 + description: Put ! description here +--- + +@modou/flow-designer diff --git a/packages/flow-designer/package.json b/packages/flow-designer/package.json new file mode 100644 index 0000000..d046a87 --- /dev/null +++ b/packages/flow-designer/package.json @@ -0,0 +1,67 @@ +{ + "name": "@modou/flow-designer", + "version": "0.0.1", + "description": "A react library developed with dumi", + "main": "src", + "scripts": { + "start": "pnpm run dev", + "dev": "dumi dev", + "build": "father build", + "build:watch": "father dev", + "docs:build": "dumi build", + "doctor": "father doctor", + "lint": "pnpm run lint:es && npm run lint:css", + "lint:css": "stylelint \"{src,test}/**/*.{css,less}\"", + "lint:es": "eslint \"{src,test}/**/*.{js,jsx,ts,tsx}\"", + "prepublishOnly": "father doctor && npm run build" + }, + "authors": [], + "license": "MIT", + "commitlint": { + "extends": [ + "@commitlint/config-conventional" + ] + }, + "lint-staged": { + "*.{md,json}": [ + "prettier --write --no-error-on-unmatched-pattern" + ], + "*.{css,less}": [ + "stylelint --fix", + "prettier --write" + ], + "*.{js,jsx}": [ + "eslint --fix", + "prettier --write" + ], + "*.{ts,tsx}": [ + "eslint --fix", + "prettier --parser=typescript --write" + ] + }, + "publishConfig": { + "access": "public" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "devDependencies": { + "@commitlint/cli": "^17.1.2", + "@commitlint/config-conventional": "^17.1.0", + "@umijs/lint": "^4.0.0", + "dumi": "^2.0.2", + "eslint": "^8.23.0", + "father": "^4.1.0", + "husky": "^8.0.1", + "lint-staged": "^13.0.3", + "prettier": "^2.7.1", + "prettier-plugin-organize-imports": "^3.0.0", + "prettier-plugin-packagejson": "^2.2.18", + "react": "^18.0.0", + "react-dom": "^18.0.0", + "stylelint": "^14.9.1" + }, + "dependencies": { + } +} diff --git a/packages/flow-designer/src/FlowDesigner/components/ContextMenu.tsx b/packages/flow-designer/src/FlowDesigner/components/ContextMenu.tsx new file mode 100644 index 0000000..4fe82eb --- /dev/null +++ b/packages/flow-designer/src/FlowDesigner/components/ContextMenu.tsx @@ -0,0 +1,116 @@ +import { useClickAway } from 'ahooks' +import { Card } from 'antd' +import { FC, MutableRefObject, useRef } from 'react' +import { OnConnectStartParams, useReactFlow } from 'reactflow' + +import { useAppFactory } from '@modou/asset-vfs' +import { FlowNodeMetadata } from '@modou/core' +import { mcss } from '@modou/css-in-js' +import { FlowNodeEnum } from '@modou/flow-nodes' +import { FlowEdgeFile } from '@modou/meta-vfs/src/FlowEdgeFile' +import { FlowFile } from '@modou/meta-vfs/src/FlowFile' +import { FlowNodeFile } from '@modou/meta-vfs/src/FlowNodeFile' +import { generateId } from '@modou/shared' + +export const ContextMenu: FC<{ + position: { x: number; y: number } + open: boolean + setOpen: (open: boolean) => void + file: FlowFile + connectionSourceRef: MutableRefObject +}> = ({ position, open, file, setOpen, connectionSourceRef }) => { + const { appFactory } = useAppFactory() + + const { project } = useReactFlow() + + const flowNodes = Object.values(appFactory.flowNodeByType).filter( + (node) => + ![FlowNodeEnum.START_NODE].includes( + node.metadata.type as unknown as FlowNodeEnum, + ), + ) + + const wrapperRef = useRef(null) + + useClickAway((e) => { + setOpen(false) + }, wrapperRef) + + return open ? ( + + {flowNodes.map((node) => ( +

{ + const newNode = FlowNodeFile.create( + { + ...FlowNodeMetadata.mrSchemeToDefaultJson( + appFactory.flowNodeByType[node.metadata.type].metadata + .jsonPropsSchema, + ), + id: generateId(), + position: project({ + x: position.x, + y: position.y, + }), + }, + file, + ) + if ( + connectionSourceRef && + connectionSourceRef.current?.handleType === 'source' + ) { + FlowEdgeFile.create( + { + name: '', + id: generateId(), + source: connectionSourceRef.current + ?.nodeId as unknown as string, + target: newNode.meta.id, + sourceHandle: connectionSourceRef.current + ?.handleId as unknown as string, + targetHandle: newNode.meta.targets[0] + ?.name as unknown as string, + }, + file, + ) + } + connectionSourceRef.current = null + setOpen(false) + }} + > + {node.metadata.name} +
+ ))} + + ) : null +} + +const classes = { + wrapper: mcss` + position: absolute; + left: 0; + top: 0; + z-index: 999999; + .ant-card-body{ + padding: 0; + } + `, + node: mcss` + font-size: 12px; + padding: 4px 16px; + cursor: default; + &+&{ + border-top: 1px solid rgba(0,0,0,.1); + } + `, +} diff --git a/packages/flow-designer/src/FlowDesigner/components/FunctionIcon.tsx b/packages/flow-designer/src/FlowDesigner/components/FunctionIcon.tsx new file mode 100644 index 0000000..299dcf1 --- /dev/null +++ b/packages/flow-designer/src/FlowDesigner/components/FunctionIcon.tsx @@ -0,0 +1,26 @@ +import React from 'react' + +export const FunctionIcon = () => { + return ( + + + + + + ) +} diff --git a/packages/flow-designer/src/FlowDesigner/components/TurboEdge.tsx b/packages/flow-designer/src/FlowDesigner/components/TurboEdge.tsx new file mode 100644 index 0000000..fc23499 --- /dev/null +++ b/packages/flow-designer/src/FlowDesigner/components/TurboEdge.tsx @@ -0,0 +1,39 @@ +import { FC } from 'react' +import { EdgeProps, getBezierPath } from 'reactflow' + +export const TurboEdge: FC = ({ + id, + sourceX, + sourceY, + targetX, + targetY, + sourcePosition, + targetPosition, + style = {}, + markerEnd, +}) => { + const xEqual = sourceX === targetX + const yEqual = sourceY === targetY + + const [edgePath] = getBezierPath({ + // we need this little hack in order to display the gradient for a straight line + sourceX: xEqual ? sourceX + 0.0001 : sourceX, + sourceY: yEqual ? sourceY + 0.0001 : sourceY, + sourcePosition, + targetX, + targetY, + targetPosition, + }) + + return ( + <> + + + ) +} diff --git a/packages/flow-designer/src/FlowDesigner/components/TurboNode.tsx b/packages/flow-designer/src/FlowDesigner/components/TurboNode.tsx new file mode 100644 index 0000000..0600868 --- /dev/null +++ b/packages/flow-designer/src/FlowDesigner/components/TurboNode.tsx @@ -0,0 +1,30 @@ +import React, { FC, ReactNode, memo } from 'react' +import { Handle, NodeProps, Position } from 'reactflow' + +export interface TurboNodeData { + title: string + icon?: ReactNode + subline?: string +} + +const _TurboNode: FC> = ({ data }) => { + return ( + <> +
+
+
+ {data.icon &&
{data.icon}
} +
+
{data.title}
+ {data.subline &&
{data.subline}
} +
+
+ + +
+
+ + ) +} + +export const TurboNode = memo(_TurboNode) diff --git a/packages/flow-designer/src/FlowDesigner/components/index.ts b/packages/flow-designer/src/FlowDesigner/components/index.ts new file mode 100644 index 0000000..eafb9ef --- /dev/null +++ b/packages/flow-designer/src/FlowDesigner/components/index.ts @@ -0,0 +1,3 @@ +export { TurboEdge } from './TurboEdge' +export { TurboNode } from './TurboNode' +export { FunctionIcon } from './FunctionIcon' diff --git a/packages/flow-designer/src/FlowDesigner/index.md b/packages/flow-designer/src/FlowDesigner/index.md new file mode 100644 index 0000000..b588583 --- /dev/null +++ b/packages/flow-designer/src/FlowDesigner/index.md @@ -0,0 +1,16 @@ +# FlowDesigner + +This is an example component. + +```jsx +import { FlowDesigner } from '@modou/flow-designer'; +export default () =>
+``` + +## NodeType +| NodeType | 说明 | +|---------------|------| +| BranchNode | 分支 | +| LoopNode | 循环 | +| RunScriptNode | 执行脚本 | +| StartNode | 开始节点 | diff --git a/packages/flow-designer/src/FlowDesigner/index.tsx b/packages/flow-designer/src/FlowDesigner/index.tsx new file mode 100644 index 0000000..cfd36c7 --- /dev/null +++ b/packages/flow-designer/src/FlowDesigner/index.tsx @@ -0,0 +1,335 @@ +import { useMemoizedFn } from 'ahooks' +import { mapValues } from 'lodash' +import { ComponentProps, FC, useMemo, useRef, useState } from 'react' +import ReactFlow, { + Controls, + OnConnectStartParams, + ReactFlowProvider, +} from 'reactflow' + +import { useAppFactory } from '@modou/asset-vfs' +import { mcss } from '@modou/css-in-js' +import { FlowFile } from '@modou/meta-vfs/src/FlowFile' +import { observer } from '@modou/reactivity-react' +import { getMonitorWall } from '@modou/shared' + +import { TurboEdge, TurboNode } from './components' +import { ContextMenu } from './components/ContextMenu' + +type ReactFlowProps = Required> + +const NODE_TYPES = { + turbo: TurboNode, +} + +const edgeTypes = { + turbo: TurboEdge, +} + +const defaultEdgeOptions = { + type: 'turbo', + // markerEnd: 'edge-circle', +} +export const UOFlowDesigner: FC<{ file: FlowFile }> = ({ file }) => { + const { appFactory } = useAppFactory() + + const [contextMenuOpen, setContextMenuOpen] = useState(false) + const [contextMenuPosition, setContextMenuPosition] = useState({ x: 0, y: 0 }) + + const nodeTypes = useMemo(() => { + return { + ...NODE_TYPES, + ...mapValues(appFactory.flowNodeByType, (f) => f.component), + } + // 不要添加依赖 + }, []) + + const onContextMenu = useMemoizedFn< + Required>['onContextMenu'] + >((e) => { + const target = e.target as unknown as HTMLDivElement + if (!target.classList.contains('react-flow__pane')) { + return + } + + const { left, top } = getMonitorWall(target) + + setContextMenuPosition({ + x: e.clientX - left, + y: e.clientY - top, + }) + setTimeout(() => { + setContextMenuOpen(true) + }) + e.preventDefault() + }) + + const connectionRef = useRef(false) + const connectionSourceRef = useRef(null) + const onConnect = useMemoizedFn((connection) => { + connectionRef.current = true + connectionSourceRef.current = null + file.onReactFlowConnect(connection) + }) + const onConnectStart = useMemoizedFn( + (e, params) => { + connectionSourceRef.current = params + }, + ) + const onConnectEnd = useMemoizedFn((e) => { + if (connectionRef.current) { + return + } + connectionRef.current = false + onContextMenu(e as unknown as any) + }) + + // const reactFlow = useReactFlow() + // useKeyPress('Backspace', () => { + // console.log('12312', reactFlow.getEdges()) + // }) + + return ( +
+ <> + + + + + + + + + + + + + + + + + +
+ ) +} + +const _FlowDesigner = observer(UOFlowDesigner) + +export const FlowDesigner: FC> = ( + props, +) => { + return ( + + <_FlowDesigner {...props} /> + + ) +} + +export const classes = { + wrapper: mcss` + position: relative; + height: 100%; + `, + reactFlow: mcss` + //--bg-color: rgb(17, 17, 17); + --bg-color: rgb(255,255,255); + //--text-color: rgb(243, 244, 246); + --text-color: rgb(0, 0, 0); + --node-border-radius: 10px; + --node-box-shadow: 10px 0 15px rgba(42, 138, 246, 0.3), -10px 0 15px rgba(233, 42, 103, 0.3); + background-color: var(--bg-color); + color: var(--text-color); + + .react-flow__node-turbo { + border-radius: var(--node-border-radius); + display: flex; + height: 70px; + min-width: 150px; + font-weight: 500; + letter-spacing: -0.2px; + box-shadow: var(--node-box-shadow); + } + + .react-flow__node-turbo .wrapper { + overflow: hidden; + display: flex; + padding: 2px; + position: relative; + border-radius: var(--node-border-radius); + flex-grow: 1; + } + + .gradient:before { + content: ''; + position: absolute; + padding-bottom: calc(100% * 1.41421356237); + width: calc(100% * 1.41421356237); + background: conic-gradient( + from -160deg at 50% 50%, + #e92a67 0deg, + #a853ba 120deg, + #2a8af6 240deg, + #e92a67 360deg + ); + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + border-radius: 100%; + } + + .react-flow__node-turbo.selected .wrapper.gradient:before { + content: ''; + background: conic-gradient( + from -160deg at 50% 50%, + #e92a67 0deg, + #a853ba 120deg, + #2a8af6 240deg, + rgba(42, 138, 246, 0) 360deg + ); + animation: spinner 4s linear infinite; + transform: translate(-50%, -50%) rotate(0deg); + z-index: -1; + } + + @keyframes spinner { + 100% { + transform: translate(-50%, -50%) rotate(-360deg); + } + } + + .react-flow__node-turbo .inner { + background: var(--bg-color); + padding: 16px 20px; + border-radius: var(--node-border-radius); + display: flex; + flex-direction: column; + justify-content: center; + flex-grow: 1; + position: relative; + } + + .react-flow__node-turbo .icon { + margin-right: 8px; + } + + .react-flow__node-turbo .body { + display: flex; + } + + .react-flow__node-turbo .title { + font-size: 16px; + margin-bottom: 2px; + line-height: 1; + } + + .react-flow__node-turbo .subline { + font-size: 12px; + color: #777; + } + + .react-flow__node-turbo .cloud { + border-radius: 100%; + width: 30px; + height: 30px; + right: 0; + position: absolute; + top: 0; + transform: translate(50%, -50%); + display: flex; + transform-origin: center center; + padding: 2px; + overflow: hidden; + box-shadow: var(--node-box-shadow); + z-index: 1; + } + + .react-flow__node-turbo .cloud div { + background-color: var(--bg-color); + flex-grow: 1; + border-radius: 100%; + display: flex; + justify-content: center; + align-items: center; + position: relative; + } + + .react-flow__node:focus { + outline: none; + } + + .react-flow__edge .react-flow__edge-path { + stroke: url(#edge-gradient); + stroke-width: 2; + stroke-opacity: 0.75; + } + + .react-flow__controls button { + background-color: var(--bg-color); + color: var(--text-color); + border: 1px solid #95679e; + border-bottom: none; + } + + .react-flow__controls button:hover { + background-color: rgb(37, 37, 37); + } + + .react-flow__controls button:first-child { + border-radius: 5px 5px 0 0; + } + + .react-flow__controls button:last-child { + border-bottom: 1px solid #95679e; + border-radius: 0 0 5px 5px; + } + + .react-flow__controls button path { + fill: var(--text-color); + } + + .react-flow__attribution { + background: rgba(200, 200, 200, 0.2); + } + + .react-flow__attribution a { + color: #95679e; + } + `, +} diff --git a/packages/flow-designer/src/index.ts b/packages/flow-designer/src/index.ts new file mode 100644 index 0000000..872a513 --- /dev/null +++ b/packages/flow-designer/src/index.ts @@ -0,0 +1 @@ +export { FlowDesigner } from './FlowDesigner' diff --git a/packages/flow-designer/tsconfig.json b/packages/flow-designer/tsconfig.json new file mode 100644 index 0000000..5a9d13d --- /dev/null +++ b/packages/flow-designer/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "strict": true, + "declaration": true, + "skipLibCheck": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "baseUrl": "./", + "paths": { + "@@/*": [".dumi/tmp/*"], + "@modou/flow-designer": ["src"], + "@modou/flow-designer/*": ["src/*", "*"] + } + }, + "include": [".dumi/**/*", ".dumirc.ts", "src/**/*"] +} diff --git a/packages/flow-nodes/.dumirc.ts b/packages/flow-nodes/.dumirc.ts new file mode 100644 index 0000000..346fd8b --- /dev/null +++ b/packages/flow-nodes/.dumirc.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'dumi'; + +export default defineConfig({ + outputPath: 'docs-dist', + themeConfig: { + name: '@modou/flow-nodes', + }, +}); diff --git a/packages/flow-nodes/.fatherrc.ts b/packages/flow-nodes/.fatherrc.ts new file mode 100644 index 0000000..dae25c8 --- /dev/null +++ b/packages/flow-nodes/.fatherrc.ts @@ -0,0 +1,6 @@ +import { defineConfig } from 'father'; + +export default defineConfig({ + // more father config: https://github.com/umijs/father/blob/master/docs/config.md + esm: { output: 'dist' }, +}); diff --git a/packages/flow-nodes/.gitignore b/packages/flow-nodes/.gitignore new file mode 100644 index 0000000..cf89318 --- /dev/null +++ b/packages/flow-nodes/.gitignore @@ -0,0 +1,6 @@ +node_modules +/dist +.dumi/tmp +.dumi/tmp-test +.dumi/tmp-production +.DS_Store diff --git a/packages/flow-nodes/.husky/commit-msg b/packages/flow-nodes/.husky/commit-msg new file mode 100755 index 0000000..5b0b354 --- /dev/null +++ b/packages/flow-nodes/.husky/commit-msg @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +npx commitlint --edit "${1}" diff --git a/packages/flow-nodes/.husky/pre-commit b/packages/flow-nodes/.husky/pre-commit new file mode 100755 index 0000000..d24fdfc --- /dev/null +++ b/packages/flow-nodes/.husky/pre-commit @@ -0,0 +1,4 @@ +#!/usr/bin/env sh +. "$(dirname -- "$0")/_/husky.sh" + +npx lint-staged diff --git a/packages/flow-nodes/LICENSE b/packages/flow-nodes/LICENSE new file mode 100644 index 0000000..b0c7139 --- /dev/null +++ b/packages/flow-nodes/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/flow-nodes/README.md b/packages/flow-nodes/README.md new file mode 100644 index 0000000..d09bc54 --- /dev/null +++ b/packages/flow-nodes/README.md @@ -0,0 +1,40 @@ +# @modou/flow-nodes + +[![NPM version](https://img.shields.io/npm/v/@modou/flow-nodes.svg?style=flat)](https://npmjs.org/package/@modou/flow-nodes) +[![NPM downloads](http://img.shields.io/npm/dm/@modou/flow-nodes.svg?style=flat)](https://npmjs.org/package/@modou/flow-nodes) + +A react library developed with dumi + +## Usage + +TODO + +## Options + +TODO + +## Development + +```bash +# install dependencies +$ pnpm install + +# develop library by docs demo +$ pnpm start + +# build library source code +$ pnpm run build + +# build library source code in watch mode +$ pnpm run build:watch + +# build docs +$ pnpm run docs:build + +# check your project for potential problems +$ pnpm run doctor +``` + +## LICENSE + +MIT diff --git a/packages/flow-nodes/docs/guide.md b/packages/flow-nodes/docs/guide.md new file mode 100644 index 0000000..5504ddf --- /dev/null +++ b/packages/flow-nodes/docs/guide.md @@ -0,0 +1 @@ +This is a guide example. diff --git a/packages/flow-nodes/docs/index.md b/packages/flow-nodes/docs/index.md new file mode 100644 index 0000000..05066c5 --- /dev/null +++ b/packages/flow-nodes/docs/index.md @@ -0,0 +1,22 @@ +--- +hero: + title: library + description: A react library developed with dumi + actions: + - text: Hello + link: / + - text: World + link: / +features: + - title: Hello + emoji: 💎 + description: Put hello description here + - title: World + emoji: 🌈 + description: Put world description here + - title: '!' + emoji: 🚀 + description: Put ! description here +--- + +@modou/flow-nodes diff --git a/packages/flow-nodes/package.json b/packages/flow-nodes/package.json new file mode 100644 index 0000000..3143276 --- /dev/null +++ b/packages/flow-nodes/package.json @@ -0,0 +1,64 @@ +{ + "name": "@modou/flow-nodes", + "version": "0.0.1", + "description": "A react library developed with dumi", + "main": "src", + "scripts": { + "start": "pnpm run dev", + "dev": "dumi dev", + "build": "father build", + "build:watch": "father dev", + "docs:build": "dumi build", + "doctor": "father doctor", + "lint": "pnpm run lint:es && npm run lint:css", + "lint:css": "stylelint \"{src,test}/**/*.{css,less}\"", + "lint:es": "eslint \"{src,test}/**/*.{js,jsx,ts,tsx}\"" + }, + "authors": [], + "license": "MIT", + "commitlint": { + "extends": [ + "@commitlint/config-conventional" + ] + }, + "lint-staged": { + "*.{md,json}": [ + "prettier --write --no-error-on-unmatched-pattern" + ], + "*.{css,less}": [ + "stylelint --fix", + "prettier --write" + ], + "*.{js,jsx}": [ + "eslint --fix", + "prettier --write" + ], + "*.{ts,tsx}": [ + "eslint --fix", + "prettier --parser=typescript --write" + ] + }, + "publishConfig": { + "access": "public" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + }, + "devDependencies": { + "@commitlint/cli": "^17.1.2", + "@commitlint/config-conventional": "^17.1.0", + "@umijs/lint": "^4.0.0", + "dumi": "^2.0.2", + "eslint": "^8.23.0", + "father": "^4.1.0", + "husky": "^8.0.1", + "lint-staged": "^13.0.3", + "prettier": "^2.7.1", + "prettier-plugin-organize-imports": "^3.0.0", + "prettier-plugin-packagejson": "^2.2.18", + "react": "^18.0.0", + "react-dom": "^18.0.0", + "stylelint": "^14.9.1" + } +} diff --git a/packages/flow-nodes/src/BranchNode/CaseBlock.tsx b/packages/flow-nodes/src/BranchNode/CaseBlock.tsx new file mode 100644 index 0000000..940afd1 --- /dev/null +++ b/packages/flow-nodes/src/BranchNode/CaseBlock.tsx @@ -0,0 +1,123 @@ +import { DeleteOutlined } from '@ant-design/icons' +import { Button, Space } from 'antd' +import { FC, useMemo } from 'react' +import { Handle, Position } from 'reactflow' + +import { CodeEditor, CodeEditorModeEnum } from '@modou/code-editor' +import { cx, mcss } from '@modou/css-in-js' + +import { nodeClasses } from '../_/styles' +import { DEFAULT_LASE_ELSE_SCRIPT } from './constants' + +interface Branch { + script: string + port: string +} +export const CaseBlock: FC<{ + branch: Branch + onChangeBranch: (branch: Branch) => void + onDeleteBranch: (branch: Branch) => void + isHead: boolean + isLast: boolean + onlyOne: boolean +}> = ({ branch, isHead, isLast, onChangeBranch, onlyOne, onDeleteBranch }) => { + const hiddenCodeEditor = useMemo(() => { + return isLast && branch.script === DEFAULT_LASE_ELSE_SCRIPT + }, [branch.script, isLast]) + return ( +
+
+ {isHead &&
If
} + {!isHead && !isLast &&
Else If
} + {!onlyOne && isLast && ( +
+ + { + onChangeBranch({ + ...branch, + script: DEFAULT_LASE_ELSE_SCRIPT, + }) + }} + > + Else + + { + onChangeBranch({ + ...branch, + script: '', + }) + }} + > + Else If + + +
+ )} + + {!onlyOne && ( + + )} +
+ {!hiddenCodeEditor && ( +
+ {/* @ts-expect-error */} + { + onChangeBranch({ + ...branch, + script: value, + }) + }} + /> +
+ )} + +
+ ) +} + +const classes = { + wrapper: mcss` + background-color: rgba(0,0,0,.08); + border-radius: 6px; + padding: 6px; + position: relative; + `, + header: mcss` + margin-bottom: 4px; + display: flex; + align-items: center; + justify-content: space-between; + `, + label: mcss` + font-weight: bold; + .inactive{ + color: #888888; + } + `, + sourcePort: mcss` + right: -21px!important; + `, +} diff --git a/packages/flow-nodes/src/BranchNode/constants.ts b/packages/flow-nodes/src/BranchNode/constants.ts new file mode 100644 index 0000000..e213201 --- /dev/null +++ b/packages/flow-nodes/src/BranchNode/constants.ts @@ -0,0 +1 @@ +export const DEFAULT_LASE_ELSE_SCRIPT = '1===1&&66&77&&true' diff --git a/packages/flow-nodes/src/BranchNode/demo.tsx b/packages/flow-nodes/src/BranchNode/demo.tsx new file mode 100644 index 0000000..3f581b8 --- /dev/null +++ b/packages/flow-nodes/src/BranchNode/demo.tsx @@ -0,0 +1,27 @@ +import { FC } from 'react' + +import { + BranchNode, + MRSchemeBranchNodeProps, + branchNodeNodeMetadata, +} from '@modou/flow-nodes' +import { mr } from '@modou/refine' + +import { FlowCanvas } from '../_/components/FlowCanvas' +import { FlowNodeProps } from '../_/types' +import { branchNodeInterpreter } from './interpreter' + +const Demo: FC = () => { + return ( + >, + ) => { + console.log(branchNodeInterpreter(props)) + }} + /> + ) +} +export default Demo diff --git a/packages/flow-nodes/src/BranchNode/index.md b/packages/flow-nodes/src/BranchNode/index.md new file mode 100644 index 0000000..a040f76 --- /dev/null +++ b/packages/flow-nodes/src/BranchNode/index.md @@ -0,0 +1,5 @@ +# BranchNode + +执行脚本节点 + + diff --git a/packages/flow-nodes/src/BranchNode/index.tsx b/packages/flow-nodes/src/BranchNode/index.tsx new file mode 100644 index 0000000..b07579f --- /dev/null +++ b/packages/flow-nodes/src/BranchNode/index.tsx @@ -0,0 +1,109 @@ +import { Button, Space } from 'antd' +import { type FC } from 'react' +import { NodeProps } from 'reactflow' + +import { + FlowNodeFile, + FlowNodeFileMeta, +} from '@modou/meta-vfs/src/FlowNodeFile' +import { runInAction } from '@modou/reactivity' +import { observer } from '@modou/reactivity-react' +import { mr } from '@modou/refine' +import { generateId } from '@modou/shared' + +import { FlowNodeHandles } from '../_/components/FlowNodeHandles' +import { FlowNodeWrapper } from '../_/components/FlowNodeWrapper' +import { CaseBlock } from './CaseBlock' +import { DEFAULT_LASE_ELSE_SCRIPT } from './constants' +import { MRSchemeBranchNodeProps, branchNodeNodeMetadata } from './metadata' + +const UOBranchNode: FC< + NodeProps< + FlowNodeFile>> + > +> = (props) => { + console.log('_BranchNode') + + return ( + <> + + + {props.data.meta.props.branches.map((branch, index, branches) => ( + { + runInAction(() => { + props.data.meta.props.branches = + props.data.meta.props.branches.map((br) => { + if (br.port !== branch.port) { + return br + } + return branch + }) + }) + }} + onDeleteBranch={(branch) => { + runInAction(() => { + props.data.meta.props.branches = + props.data.meta.props.branches + .filter((br) => br.port !== branch.port) + .map((br, index) => { + if ( + index === 0 && + br.script === DEFAULT_LASE_ELSE_SCRIPT + ) { + return { + ...br, + script: '', + } + } + return br + }) + props.data.meta.sources = props.data.meta.sources.filter( + (s) => s.name !== branch.port, + ) + }) + }} + /> + ))} + + + + + + ) +} +export const BranchNode = observer(UOBranchNode) diff --git a/packages/flow-nodes/src/BranchNode/interpreter.ts b/packages/flow-nodes/src/BranchNode/interpreter.ts new file mode 100644 index 0000000..4051088 --- /dev/null +++ b/packages/flow-nodes/src/BranchNode/interpreter.ts @@ -0,0 +1,21 @@ +import { type FlowNodeInterpreter } from '@modou/core' +import { evalScript } from '@modou/evaluate' +import { MRSchemeBranchNodeProps } from '@modou/flow-nodes' +import { mr } from '@modou/refine' + +export const branchNodeInterpreter: FlowNodeInterpreter< + mr.infer +> = (props) => { + // 1、迭代条件 + // 2、找到为真的条件 + // 3、找到为真的条件对应的 source port + // 4、找到 source port 对应的 target node + // 5、执行target node + // 返回 + const branches = props.props.branches + for (const branch of branches) { + if (evalScript(`return ${branch.script}`, {})) { + return + } + } +} diff --git a/packages/flow-nodes/src/BranchNode/metadata.tsx b/packages/flow-nodes/src/BranchNode/metadata.tsx new file mode 100644 index 0000000..eeeb7ba --- /dev/null +++ b/packages/flow-nodes/src/BranchNode/metadata.tsx @@ -0,0 +1,36 @@ +import { AndroidOutlined } from '@ant-design/icons' + +import { FlowNodeMetadata, FlowNodePortNameEnum } from '@modou/core' +import { mr } from '@modou/refine' + +import { FlowNodeEnum } from '../_/types' + +export const MRSchemeBranchNodeProps = FlowNodeMetadata.createMRFlowNodeProps({ + type: FlowNodeEnum.BRANCH_NODE, + name: '条件分支', + props: { + branches: { + def: mr + .array( + mr.object({ + script: mr.string().default(''), + port: mr.string(), + }), + ) + .default([ + { + script: '', + port: FlowNodePortNameEnum.SOURCE, + }, + ]), + }, + }, +}) + +export const branchNodeNodeMetadata = FlowNodeMetadata.createMetadata({ + version: '0.0.1', + type: FlowNodeEnum.BRANCH_NODE, + name: '条件分支', + icon: , + mrPropsScheme: MRSchemeBranchNodeProps, +}) diff --git a/packages/flow-nodes/src/LoopNode/demo.tsx b/packages/flow-nodes/src/LoopNode/demo.tsx new file mode 100644 index 0000000..ac847a5 --- /dev/null +++ b/packages/flow-nodes/src/LoopNode/demo.tsx @@ -0,0 +1,10 @@ +import { FC } from 'react' + +import { LoopNode, loopNodeMetadata } from '@modou/flow-nodes' + +import { FlowCanvas } from '../_/components/FlowCanvas' + +const Demo: FC = () => { + return +} +export default Demo diff --git a/packages/flow-nodes/src/LoopNode/index.md b/packages/flow-nodes/src/LoopNode/index.md new file mode 100644 index 0000000..ff0a944 --- /dev/null +++ b/packages/flow-nodes/src/LoopNode/index.md @@ -0,0 +1,5 @@ +# LoopNode + +循环节点 + + diff --git a/packages/flow-nodes/src/LoopNode/index.tsx b/packages/flow-nodes/src/LoopNode/index.tsx new file mode 100644 index 0000000..927abb4 --- /dev/null +++ b/packages/flow-nodes/src/LoopNode/index.tsx @@ -0,0 +1,62 @@ +import { type FC } from 'react' +import { Handle, NodeProps, Position } from 'reactflow' + +import { CodeEditor, CodeEditorModeEnum } from '@modou/code-editor' +import { + FlowNodeFile, + FlowNodeFileMeta, +} from '@modou/meta-vfs/src/FlowNodeFile' +import { runInAction } from '@modou/reactivity' +import { observer } from '@modou/reactivity-react' +import { mr } from '@modou/refine' + +import { FlowNodeHandles } from '../_/components/FlowNodeHandles' +import { FlowNodeWrapper } from '../_/components/FlowNodeWrapper' +import { nodeClasses } from '../_/styles' +import { ScopedFlowNodePortNameEnum } from '../_/types' +import { MRSchemeLoopNodeProps, loopNodeMetadata } from './metadata' + +const UOLoopNode: FC< + NodeProps< + FlowNodeFile>> + > +> = (props) => { + const loopBodyPort = props.data.meta.sources.find( + (s) => s.name === ScopedFlowNodePortNameEnum.LOOP_BODY, + )! + return ( + <> + +
+
循环体
+ {/* @ts-expect-error */} + { + runInAction(() => { + props.data.meta.props.iterable = value + }) + }} + /> +
+
+ port.name !== ScopedFlowNodePortNameEnum.LOOP_BODY, + )} + targets={props.data.meta.targets} + isConnectable={props.isConnectable} + /> + + + ) +} +export const LoopNode = observer(UOLoopNode) diff --git a/packages/flow-nodes/src/LoopNode/interpreter.ts b/packages/flow-nodes/src/LoopNode/interpreter.ts new file mode 100644 index 0000000..c2d8a6d --- /dev/null +++ b/packages/flow-nodes/src/LoopNode/interpreter.ts @@ -0,0 +1,17 @@ +import { FlowNodeInterpreter } from '@modou/core' +import { evalScript } from '@modou/evaluate' +import { MRSchemeLoopNodeProps } from '@modou/flow-nodes' +import { mr } from '@modou/refine' + +export const loopNodeInterpreter: FlowNodeInterpreter< + mr.infer +> = async (props) => { + const iterable = props.props.iterable + const res = await Promise.all( + evalScript(`return ${iterable}`, {}).map((item: any) => { + console.log('item', item) + return 'TODO' + }), + ) + return res +} diff --git a/packages/flow-nodes/src/LoopNode/metadata.tsx b/packages/flow-nodes/src/LoopNode/metadata.tsx new file mode 100644 index 0000000..a767dc6 --- /dev/null +++ b/packages/flow-nodes/src/LoopNode/metadata.tsx @@ -0,0 +1,33 @@ +import { AndroidOutlined } from '@ant-design/icons' + +import { FlowNodeMetadata, FlowNodePortNameEnum } from '@modou/core' +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { mr } from '@modou/refine' + +import { FlowNodeEnum, ScopedFlowNodePortNameEnum } from '../_/types' + +export const MRSchemeLoopNodeProps = FlowNodeMetadata.createMRFlowNodeProps({ + type: FlowNodeEnum.LOOP_NODE, + name: '循环', + defaultSources: [ + { + name: FlowNodePortNameEnum.SOURCE, + }, + { + name: ScopedFlowNodePortNameEnum.LOOP_BODY, + }, + ], + props: { + iterable: { + def: mr.string().default(''), + }, + }, +}) + +export const loopNodeMetadata = FlowNodeMetadata.createMetadata({ + version: '0.0.1', + type: FlowNodeEnum.LOOP_NODE, + name: '循环', + icon: , + mrPropsScheme: MRSchemeLoopNodeProps, +}) diff --git a/packages/flow-nodes/src/RunScriptNode/demo.tsx b/packages/flow-nodes/src/RunScriptNode/demo.tsx new file mode 100644 index 0000000..3fd46e6 --- /dev/null +++ b/packages/flow-nodes/src/RunScriptNode/demo.tsx @@ -0,0 +1,29 @@ +import { FC } from 'react' + +import { + MRSchemeRunScriptNodeProps, + RunScriptNode, + runScriptNodeMetadata, +} from '@modou/flow-nodes' +import { mr } from '@modou/refine' + +import { FlowCanvas } from '../_/components/FlowCanvas' +import { FlowNodeProps } from '../_/types' +import { runScriptNodeInterpreter } from './interpreter' + +const Demo: FC = () => { + return ( + <> + >, + ) => { + console.log(runScriptNodeInterpreter(props)) + }} + /> + + ) +} +export default Demo diff --git a/packages/flow-nodes/src/RunScriptNode/index.md b/packages/flow-nodes/src/RunScriptNode/index.md new file mode 100644 index 0000000..f0180a4 --- /dev/null +++ b/packages/flow-nodes/src/RunScriptNode/index.md @@ -0,0 +1,5 @@ +# RunScriptNode + +执行脚本节点 + + diff --git a/packages/flow-nodes/src/RunScriptNode/index.tsx b/packages/flow-nodes/src/RunScriptNode/index.tsx new file mode 100644 index 0000000..a45fad2 --- /dev/null +++ b/packages/flow-nodes/src/RunScriptNode/index.tsx @@ -0,0 +1,46 @@ +import { type FC } from 'react' +import { NodeProps } from 'reactflow' + +import { CodeEditor, CodeEditorModeEnum } from '@modou/code-editor' +import { + FlowNodeFile, + FlowNodeFileMeta, +} from '@modou/meta-vfs/src/FlowNodeFile' +import { runInAction } from '@modou/reactivity' +import { observer } from '@modou/reactivity-react' +import { mr } from '@modou/refine' + +import { FlowNodeHandles } from '../_/components/FlowNodeHandles' +import { FlowNodeWrapper } from '../_/components/FlowNodeWrapper' +import { MRSchemeRunScriptNodeProps, runScriptNodeMetadata } from './metadata' + +const UORunScriptNode: FC< + NodeProps< + FlowNodeFile>> + > +> = (props) => { + console.log('_RunScriptNode') + + return ( + <> + + {/* @ts-expect-error */} + { + runInAction(() => { + props.data.meta.props.script = value + }) + }} + /> + + + + ) +} +export const RunScriptNode = observer(UORunScriptNode) diff --git a/packages/flow-nodes/src/RunScriptNode/interpreter.ts b/packages/flow-nodes/src/RunScriptNode/interpreter.ts new file mode 100644 index 0000000..9b2ed7d --- /dev/null +++ b/packages/flow-nodes/src/RunScriptNode/interpreter.ts @@ -0,0 +1,11 @@ +import { FlowNodeInterpreter } from '@modou/core' +import { evalScript } from '@modou/evaluate' +import { MRSchemeRunScriptNodeProps } from '@modou/flow-nodes' +import { mr } from '@modou/refine' + +export const runScriptNodeInterpreter: FlowNodeInterpreter< + mr.infer +> = (props) => { + const script = props.props.script + return evalScript(script, {}) +} diff --git a/packages/flow-nodes/src/RunScriptNode/metadata.tsx b/packages/flow-nodes/src/RunScriptNode/metadata.tsx new file mode 100644 index 0000000..6e4e866 --- /dev/null +++ b/packages/flow-nodes/src/RunScriptNode/metadata.tsx @@ -0,0 +1,26 @@ +import { AndroidOutlined } from '@ant-design/icons' + +import { FlowNodeMetadata } from '@modou/core' +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { mr } from '@modou/refine' + +import { FlowNodeEnum } from '../_/types' + +export const MRSchemeRunScriptNodeProps = + FlowNodeMetadata.createMRFlowNodeProps({ + type: FlowNodeEnum.RUN_SCRIPT_NODE, + name: '执行脚本', + props: { + script: { + def: mr.string().default(''), + }, + }, + }) + +export const runScriptNodeMetadata = FlowNodeMetadata.createMetadata({ + version: '0.0.1', + type: FlowNodeEnum.RUN_SCRIPT_NODE, + name: '执行脚本', + icon: , + mrPropsScheme: MRSchemeRunScriptNodeProps, +}) diff --git a/packages/flow-nodes/src/StartNode/demo.tsx b/packages/flow-nodes/src/StartNode/demo.tsx new file mode 100644 index 0000000..1cdec3f --- /dev/null +++ b/packages/flow-nodes/src/StartNode/demo.tsx @@ -0,0 +1,10 @@ +import { FC } from 'react' + +import { StartNode, startNodeMetadata } from '@modou/flow-nodes' + +import { FlowCanvas } from '../_/components/FlowCanvas' + +const Demo: FC = () => { + return +} +export default Demo diff --git a/packages/flow-nodes/src/StartNode/index.md b/packages/flow-nodes/src/StartNode/index.md new file mode 100644 index 0000000..76a674b --- /dev/null +++ b/packages/flow-nodes/src/StartNode/index.md @@ -0,0 +1,5 @@ +# StartNode + +开始节点 + + diff --git a/packages/flow-nodes/src/StartNode/index.tsx b/packages/flow-nodes/src/StartNode/index.tsx new file mode 100644 index 0000000..4f33672 --- /dev/null +++ b/packages/flow-nodes/src/StartNode/index.tsx @@ -0,0 +1,25 @@ +import { type FC } from 'react' +import { NodeProps } from 'reactflow' + +import { FlowNodeFile } from '@modou/meta-vfs/src/FlowNodeFile' +import { observer } from '@modou/reactivity-react' + +import { FlowNodeHandles } from '../_/components/FlowNodeHandles' +import { FlowNodeWrapper } from '../_/components/FlowNodeWrapper' +import { startNodeMetadata } from './metadata' + +const UOStartNode: FC> = (props) => { + return ( + <> + + + + ) +} +export const StartNode = observer(UOStartNode) + +// const classes = {} diff --git a/packages/flow-nodes/src/StartNode/interpreter.ts b/packages/flow-nodes/src/StartNode/interpreter.ts new file mode 100644 index 0000000..c2f7c12 --- /dev/null +++ b/packages/flow-nodes/src/StartNode/interpreter.ts @@ -0,0 +1,9 @@ +import { FlowNodeInterpreter } from '@modou/core' +import { MRSchemeStartNodeProps } from '@modou/flow-nodes' +import { mr } from '@modou/refine' + +export const startNodeInterpreter: FlowNodeInterpreter< + mr.infer +> = (props) => { + // 直接执行下一步流程 +} diff --git a/packages/flow-nodes/src/StartNode/metadata.tsx b/packages/flow-nodes/src/StartNode/metadata.tsx new file mode 100644 index 0000000..0a7031b --- /dev/null +++ b/packages/flow-nodes/src/StartNode/metadata.tsx @@ -0,0 +1,21 @@ +import { AndroidOutlined } from '@ant-design/icons' + +import { FlowNodeMetadata } from '@modou/core' +// eslint-disable-next-line @typescript-eslint/no-unused-vars +import { mr } from '@modou/refine' + +import { FlowNodeEnum } from '../_/types' + +export const MRSchemeStartNodeProps = FlowNodeMetadata.createMRFlowNodeProps({ + type: FlowNodeEnum.START_NODE, + name: '开始', + props: {}, +}) + +export const startNodeMetadata = FlowNodeMetadata.createMetadata({ + version: '0.0.1', + type: FlowNodeEnum.START_NODE, + name: '开始', + icon: , + mrPropsScheme: MRSchemeStartNodeProps, +}) diff --git a/packages/flow-nodes/src/_/components/FlowCanvas.tsx b/packages/flow-nodes/src/_/components/FlowCanvas.tsx new file mode 100644 index 0000000..4dd1476 --- /dev/null +++ b/packages/flow-nodes/src/_/components/FlowCanvas.tsx @@ -0,0 +1,148 @@ +import { + ComponentProps, + ComponentType, + FC, + useCallback, + useEffect, + useMemo, +} from 'react' +import ReactFlow, { + Background, + Controls, + NodeTypes, + OnConnect, + ReactFlowProvider, + addEdge, + useEdgesState, + useNodesState, + useReactFlow, +} from 'reactflow' +import 'reactflow/dist/style.css' + +import { FlowNodeMetadata } from '@modou/core' +import { mcss } from '@modou/css-in-js' + +import { FlowNodeProps, OnChangeNode } from '../types' + +const _FlowCanvas: FC<{ + flowNode: ComponentType + meta: FlowNodeMetadata + runInterpreter: (props: FlowNodeProps) => void +}> = ({ flowNode, meta, runInterpreter }) => { + const reactFlowInstance = useReactFlow() + + const [nodes, setNodes] = useNodesState([]) + + const [edges, setEdges] = useEdgesState([]) + + useEffect(() => { + const defaultProps = FlowNodeMetadata.mrSchemeToDefaultJson( + meta.jsonPropsSchema, + ) + + const _onChangeNode: OnChangeNode = (node) => { + setNodes((nds) => + nds.map((nd) => { + if (nd.id !== node.id) { + return nd + } + return { + ...nd, + ...node, + } + }), + ) + } + + setNodes([ + { + id: defaultProps.id, + type: defaultProps.type, + data: { + ...defaultProps, + _onChangeNode, + }, + position: defaultProps.position, + }, + { + id: 'demo_demo', + data: { + name: 'demo', + id: 'demo_demo', + type: 'demo', + sources: [], + targets: [], + position: { x: 500, y: 500 }, + _onChangeNode, + } as any, + position: { x: 500, y: 500 }, + }, + ]) + }, []) + + const onConnect = useCallback( + (params) => + setEdges((eds) => + addEdge({ ...params, style: { stroke: 'green' } }, eds), + ), + [setEdges], + ) + + const nodeTypes: NodeTypes = useMemo( + () => ({ [meta.type]: flowNode }), + [flowNode, meta.type], + ) + + useEffect(() => { + console.log('FlowCanvas 重新渲染了') + }) + + return ( +
+ + + + + + + +
+ ) +} + +export const FlowCanvas: FC> = (props) => { + return ( + + <_FlowCanvas {...props} /> + + ) +} + +const classes = { + wrapper: mcss` + height: 600px; + `, +} diff --git a/packages/flow-nodes/src/_/components/FlowNodeHandles.tsx b/packages/flow-nodes/src/_/components/FlowNodeHandles.tsx new file mode 100644 index 0000000..70d5055 --- /dev/null +++ b/packages/flow-nodes/src/_/components/FlowNodeHandles.tsx @@ -0,0 +1,42 @@ +import { FC } from 'react' +import { Handle, Position } from 'reactflow' + +import { FlowNodeFile } from '@modou/meta-vfs/src/FlowNodeFile' +import { observer } from '@modou/reactivity-react' + +import { nodeClasses } from '../styles' + +type Targets = FlowNodeFile['meta']['targets'] +type Sources = FlowNodeFile['meta']['sources'] +const UOFlowNodeHandles: FC<{ + targets: Targets + sources: Sources + isConnectable: boolean +}> = ({ targets, sources, isConnectable }) => { + return ( + <> + {targets.map((target) => ( + + ))} + {sources.map((source) => ( + + ))} + + ) +} + +export const FlowNodeHandles = observer(UOFlowNodeHandles) diff --git a/packages/flow-nodes/src/_/components/FlowNodeWrapper.tsx b/packages/flow-nodes/src/_/components/FlowNodeWrapper.tsx new file mode 100644 index 0000000..24d76ca --- /dev/null +++ b/packages/flow-nodes/src/_/components/FlowNodeWrapper.tsx @@ -0,0 +1,121 @@ +import { DeleteOutlined } from '@ant-design/icons' +import { Button, Input } from 'antd' +import { FC, ReactNode, useState } from 'react' +import { NodeProps } from 'reactflow' + +import { type FlowNodeMetadata } from '@modou/core' +import { cx, mcss } from '@modou/css-in-js' +import { FlowNodeFile } from '@modou/meta-vfs/src/FlowNodeFile' +import { runInAction } from '@modou/reactivity' +import { observer } from '@modou/reactivity-react' + +enum NameMode { + Edit, + Readonly, +} +const UOFlowNodeWrapper: FC<{ + children?: ReactNode + meta: FlowNodeMetadata + node: NodeProps> +}> = ({ children, meta, node }) => { + const [nameMode, setNameMode] = useState(NameMode.Readonly) + return ( +
+
+
+ {meta.icon} +
+ {nameMode === NameMode.Readonly && ( + setNameMode(NameMode.Edit)} + > + {node.data.meta.name} + + )} + {nameMode === NameMode.Edit && ( + setNameMode(NameMode.Readonly)} + // TODO 失去焦点的时候保存 + onChange={(e) => { + runInAction(() => { + node.data.meta.name = e.target.value + }) + }} + /> + )} +
+ {meta.name} +
+ +
+ +
+
+ {children &&
{children}
} +
+ ) +} + +export const FlowNodeWrapper = observer(UOFlowNodeWrapper) + +const classes = { + wrapper: mcss` + border: 1px solid #1677ff; + border-radius: 8px; + //height: 40px; + width: 220px; + //overflow: hidden; + font-size: 12px; + box-sizing: border-box; + `, + header: mcss` + background-color: #888888; + height: 28px; + display: flex; + align-items: center; + justify-content: space-between; + padding: 0 8px; + color: white; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + &.all-border-radius{ + border-radius: 8px; + } + `, + headerLeft: mcss` + display: flex; + align-items: center; + + `, + nameWrapper: mcss` + padding: 0 4px; + border-right: 1px solid white; + margin-right: 4px; + `, + readOnlyName: mcss` + &:hover{ + border-bottom: 1px dashed white; + } + `, + body: mcss` + background-color: white; + padding: 8px; + border-bottom-left-radius: 8px; + border-bottom-right-radius: 8px; + `, +} diff --git a/packages/flow-nodes/src/_/hooks/index.ts b/packages/flow-nodes/src/_/hooks/index.ts new file mode 100644 index 0000000..6b9687e --- /dev/null +++ b/packages/flow-nodes/src/_/hooks/index.ts @@ -0,0 +1 @@ +export { useFlowNodeId } from './useFlowNodeId' diff --git a/packages/flow-nodes/src/_/hooks/useFlowNodeId.ts b/packages/flow-nodes/src/_/hooks/useFlowNodeId.ts new file mode 100644 index 0000000..a3deb27 --- /dev/null +++ b/packages/flow-nodes/src/_/hooks/useFlowNodeId.ts @@ -0,0 +1,5 @@ +import { useNodeId } from 'reactflow' + +export const useFlowNodeId = () => { + return useNodeId() as unknown as string +} diff --git a/packages/flow-nodes/src/_/styles/index.ts b/packages/flow-nodes/src/_/styles/index.ts new file mode 100644 index 0000000..a2cd97c --- /dev/null +++ b/packages/flow-nodes/src/_/styles/index.ts @@ -0,0 +1,38 @@ +import { mcss } from '@modou/css-in-js' + +export const nodeClasses = { + // node: mcss` + // border-radius: 8px; + // display: flex; + // height: 60px; + // width: 300px; + // letter-spacing: -0.2px; + // box-shadow: var(--node-box-shadow); + // `, + nodeTargetPort: mcss` + &.react-flow__handle{ + background-color: #535bf2; + height: 10px; + width: 10px; + } + &.react-flow__handle-left{ + left: -12px; + } + &.react-flow__handle-top{ + top: -12px; + } + `, + nodeSourcePort: mcss` + &.react-flow__handle{ + background-color: seagreen; + height: 10px; + width: 10px; + } + &.react-flow__handle-right{ + right: -12px; + } + &.react-flow__handle-bottom{ + bottom: -12px; + } + `, +} diff --git a/packages/flow-nodes/src/_/types/index.ts b/packages/flow-nodes/src/_/types/index.ts new file mode 100644 index 0000000..c5b6d6d --- /dev/null +++ b/packages/flow-nodes/src/_/types/index.ts @@ -0,0 +1,35 @@ +import { Node } from 'reactflow' + +import { FlowNodeBaseProps } from '@modou/core/src/flow-node/types' + +import { branchNodeInterpreter } from '../../BranchNode/interpreter' + +export enum FlowNodeEnum { + // 开始 + START_NODE = 'START_NODE', + // 循环 + LOOP_NODE = 'LOOP_NODE', + // 条件分支 + BRANCH_NODE = 'BRANCH_NODE', + // 执行脚本 + RUN_SCRIPT_NODE = 'RUN_SCRIPT_NODE', + // 调用子流程 + INVOKE_SUB_FLOW = 'INVOKE_SUB_FLOW', +} + +export enum FlowNodeStatus { + Expand, + Collapse, +} + +export enum ScopedFlowNodePortNameEnum { + LOOP_BODY = 'LOOP_BODY', +} + +export type FlowNodeProps = + T & { + _onChangeNode: OnChangeNode + } +export type OnChangeNode = ( + node: Partial> & { id: string }, +) => void diff --git a/packages/flow-nodes/src/index.ts b/packages/flow-nodes/src/index.ts new file mode 100644 index 0000000..8f99953 --- /dev/null +++ b/packages/flow-nodes/src/index.ts @@ -0,0 +1,17 @@ +export * from './StartNode' +export * from './StartNode/metadata' +export * from './StartNode/interpreter' + +export * from './RunScriptNode' +export * from './RunScriptNode/metadata' +export * from './RunScriptNode/interpreter' + +export * from './LoopNode' +export * from './LoopNode/metadata' +export * from './LoopNode/interpreter' + +export * from './BranchNode' +export * from './BranchNode/metadata' +export * from './BranchNode/interpreter' + +export { FlowNodeEnum } from './_/types' diff --git a/packages/flow-nodes/tsconfig.json b/packages/flow-nodes/tsconfig.json new file mode 100644 index 0000000..a4e40b0 --- /dev/null +++ b/packages/flow-nodes/tsconfig.json @@ -0,0 +1,16 @@ +{ + "compilerOptions": { + "strict": true, + "declaration": true, + "skipLibCheck": true, + "esModuleInterop": true, + "jsx": "react-jsx", + "baseUrl": "./", + "paths": { + "@@/*": [".dumi/tmp/*"], + "@modou/flow-nodes": ["src"], + "@modou/flow-nodes/*": ["src/*", "*"] + } + }, + "include": [".dumi/**/*", ".dumirc.ts", "src/**/*"] +} diff --git a/packages/meta-vfs/README.md b/packages/meta-vfs/README.md index e767825..32ab99e 100644 --- a/packages/meta-vfs/README.md +++ b/packages/meta-vfs/README.md @@ -1,9 +1,9 @@ -# @modou/file +# @modou/meta-vfs ## TODO 增加单元测试 -[![NPM version](https://img.shields.io/npm/v/@modou/file.svg?style=flat)](https://npmjs.org/package/@modou/file) -[![NPM downloads](http://img.shields.io/npm/dm/@modou/file.svg?style=flat)](https://npmjs.org/package/@modou/file) +[![NPM version](https://img.shields.io/npm/v/@modou/meta-vfs.svg?style=flat)](https://npmjs.org/package/@modou/meta-vfs) +[![NPM downloads](http://img.shields.io/npm/dm/@modou/meta-vfs.svg?style=flat)](https://npmjs.org/package/@modou/meta-vfs) diff --git a/packages/meta-vfs/docs/index.md b/packages/meta-vfs/docs/index.md index f3d82e1..8eedcdc 100644 --- a/packages/meta-vfs/docs/index.md +++ b/packages/meta-vfs/docs/index.md @@ -19,4 +19,4 @@ features: description: Put ! description here --- -@modou/file +@modou/meta-vfs diff --git a/packages/meta-vfs/package.json b/packages/meta-vfs/package.json index 7955180..9f22f0a 100644 --- a/packages/meta-vfs/package.json +++ b/packages/meta-vfs/package.json @@ -67,5 +67,8 @@ "react": "^18.0.0", "react-dom": "^18.0.0", "stylelint": "^14.9.1" + }, + "dependencies": { + "mobx-persist-store": "^1.1.3" } } diff --git a/packages/meta-vfs/src/AppFile/index.ts b/packages/meta-vfs/src/AppFile/index.ts index 673a0f7..fe84eea 100644 --- a/packages/meta-vfs/src/AppFile/index.ts +++ b/packages/meta-vfs/src/AppFile/index.ts @@ -1,4 +1,4 @@ -import { flatten, isArray, toPairs } from 'lodash' +import { isArray, keyBy } from 'lodash' import { action, @@ -11,33 +11,62 @@ import { import { BaseFile, BaseFileMap, BaseFileMete } from '../BaseFile' import { EntityFile } from '../EntityFile' import { EntityRelationFile } from '../EntityRelationFile' +import { FlowFile } from '../FlowFile' import { PageFile } from '../PageFile' +import { emitters } from '../event-bus' import { FileTypeEnum } from '../types' export type AppFileMeta = BaseFileMete interface FileMap extends BaseFileMap { readonly pages: PageFile[] readonly entities: EntityFile[] + readonly flows: FlowFile[] } export class AppFile extends BaseFile { protected constructor(meta: AppFileMeta) { super({ fileType: FileTypeEnum.App, meta, parentFile: null }) makeObservable(this, { subFileMap: observable, - entities: computed, - pages: computed, - entityMap: computed, - entityRelationMap: computed, - entityRelationsByTargetEntityNameMap: computed, - entityRelationsBySourceEntityNameMap: computed, + entities: computed.struct, + pages: computed.struct, + entityMap: computed.struct, + entityRelationMap: computed.struct, + entityRelationsByTargetEntityNameMap: computed.struct, + entityRelationsBySourceEntityNameMap: computed.struct, deleteEntity: action, deletePage: action, }) + emitters.on('updateFileMeta', this.persistMeta) + + // if (typeof window !== 'undefined') { + // window.addEventListener('visibilitychange', () => { + // if (this.meta.id) { + // window.localStorage.removeItem(this.meta.id) + // window.localStorage.setItem( + // this.meta.id, + // JSON.stringify(this.toJSON()), + // ) + // } + // }) + // } } subFileMap: FileMap = { pages: [], entities: [], + flows: [], + } + + private readonly persistMeta = () => { + if (typeof window === 'undefined') { + return + } + window.localStorage.setItem(this.meta.id, JSON.stringify(this.toJSON())) + } + + protected disposer = () => { + super.disposer() + emitters.off('updateFileMeta', this.persistMeta) } get pages() { @@ -51,7 +80,7 @@ export class AppFile extends BaseFile { }) } - deletePage(pageId: string) { + deletePage = (pageId: string) => { const oldPages = [...this.subFileMap.pages] this.subFileMap.pages.length = 0 this.subFileMap.pages.push( @@ -59,6 +88,25 @@ export class AppFile extends BaseFile { ) } + get flows() { + return this.subFileMap.flows + } + + set flows(flows) { + runInAction(() => { + this.subFileMap.flows.length = 0 + this.subFileMap.flows.push(...flows) + }) + } + + deleteFlow = (flowId: string) => { + const oldFlows = [...this.subFileMap.flows] + this.subFileMap.flows.length = 0 + this.subFileMap.flows.push( + ...oldFlows.filter((flow) => flow.meta.id !== flowId), + ) + } + get entities() { return this.subFileMap.entities } @@ -71,10 +119,10 @@ export class AppFile extends BaseFile { } get entityMap() { - return new Map(this.entities.map((entity) => [entity.meta.id, entity])) + return keyBy(this.entities, (entity) => entity.meta.id) } - deleteEntity(entityId: string) { + deleteEntity = (entityId: string) => { const oldEntities = [...this.subFileMap.entities] this.subFileMap.entities.length = 0 this.subFileMap.entities.push( @@ -83,57 +131,49 @@ export class AppFile extends BaseFile { } get entityRelationMap() { - return new Map( - flatten( - this.entities.map((entity) => - entity.subFileMap.entityRelations.map((relation) => [ - relation.meta.id, - relation, - ]), + return this.entities.reduce>( + (pre, cur) => ({ + ...pre, + ...keyBy( + cur.subFileMap.entityRelations, + (relation) => relation.meta.id, ), - ), + }), + {}, ) } get entityRelationsByTargetEntityNameMap() { - return new Map( - toPairs( - [...this.entityRelationMap.values()].reduce< - Record - >((pre, cur) => { - // TODO 如果是ManyToOne 的 Lookup 则不在对方双向生成关系 - // if ( - // cur.type === EntityRelationTypeEnum.Lookup && - // cur.relationType === EntityRelationLookupRelationTypeEnum.ManyToOne - // ) { - // return pre - // } - const targetEntityName = cur.meta.targetEntity - if (!isArray(pre[targetEntityName])) { - pre[targetEntityName] = [] - } - pre[targetEntityName].push(cur) - return pre - }, {}), - ), - ) + return Object.values(this.entityRelationMap).reduce< + Record + >((pre, cur) => { + // TODO 如果是ManyToOne 的 Lookup 则不在对方双向生成关系 + // if ( + // cur.type === EntityRelationTypeEnum.Lookup && + // cur.relationType === EntityRelationLookupRelationTypeEnum.ManyToOne + // ) { + // return pre + // } + const targetEntityName = cur.meta.targetEntity + if (!isArray(pre[targetEntityName])) { + pre[targetEntityName] = [] + } + pre[targetEntityName].push(cur) + return pre + }, {}) } get entityRelationsBySourceEntityNameMap() { - return new Map( - toPairs( - [...this.entityRelationMap.values()].reduce< - Record - >((pre, cur) => { - const sourceEntityName = cur.meta.sourceEntity - if (!isArray(pre[sourceEntityName])) { - pre[sourceEntityName] = [] - } - pre[sourceEntityName].push(cur) - return pre - }, {}), - ), - ) + return Object.values(this.entityRelationMap).reduce< + Record + >((pre, cur) => { + const sourceEntityName = cur.meta.sourceEntity + if (!isArray(pre[sourceEntityName])) { + pre[sourceEntityName] = [] + } + pre[sourceEntityName].push(cur) + return pre + }, {}) } toJSON() { @@ -155,23 +195,26 @@ export class AppFile extends BaseFile { id: json.id, version: json.version, }) - const entities = json.entities.map((entity) => + json.entities.map((entity) => EntityFile.formJSON( entity as unknown as ReturnType, appFile, ), ) - const pages = json.pages.map((page) => + json.pages.map((page) => PageFile.formJSON( page as unknown as ReturnType, appFile, ), ) - runInAction(() => { - appFile.subFileMap.entities.push(...entities) - appFile.subFileMap.pages.push(...pages) - }) return appFile }) } + + // static formLocal(appId: string) { + // const appMeta = JSON.parse( + // localStorage.getItem(appId) ?? '{}', + // ) as unknown as ReturnType + // return this.formJSON(appMeta) + // } } diff --git a/packages/meta-vfs/src/AppManager/index.ts b/packages/meta-vfs/src/AppManager/index.ts index 0729d5a..a2a4074 100644 --- a/packages/meta-vfs/src/AppManager/index.ts +++ b/packages/meta-vfs/src/AppManager/index.ts @@ -1,13 +1,22 @@ import { flatten } from 'lodash' -import { makeAutoObservable } from '@modou/reactivity' +import { computed, makeObservable } from '@modou/reactivity' import { AppFile } from '../AppFile' +import { WidgetFile } from '../WidgetFile' export class AppManager { constructor(appFile: AppFile) { this.root = appFile - makeAutoObservable(this) + makeObservable(this, { + // root: observable, + app: computed, + pageMap: computed.struct, + entityMap: computed.struct, + widgetMap: computed.struct, + entityFieldMap: computed.struct, + entityRelationMap: computed.struct, + }) } private readonly root: AppFile @@ -26,13 +35,9 @@ export class AppManager { get widgetMap() { // TODO 优化 直接从page.widgetMap 取值 - return new Map( - flatten( - this.app.pages.map((page) => - page.subFileMap.widgets.map((widget) => [widget.meta.id, widget]), - ), - ), - ) + return this.app.pages.reduce>((pre, cur) => { + return { ...pre, ...cur.widgetMap } + }, {}) } get entityFieldMap() { diff --git a/packages/core/src/contexts/AppManagerContext.tsx b/packages/meta-vfs/src/AppManagerContext.tsx similarity index 89% rename from packages/core/src/contexts/AppManagerContext.tsx rename to packages/meta-vfs/src/AppManagerContext.tsx index 764495a..42404c5 100644 --- a/packages/core/src/contexts/AppManagerContext.tsx +++ b/packages/meta-vfs/src/AppManagerContext.tsx @@ -1,6 +1,6 @@ import { FC, ReactNode, createContext, useContext } from 'react' -import { AppManager } from '@modou/meta-vfs/src/AppManager' +import { AppManager } from './AppManager' const AppManagerContext = createContext(null) diff --git a/packages/meta-vfs/src/BaseFile/index.ts b/packages/meta-vfs/src/BaseFile/index.ts index 3faa66a..f990158 100644 --- a/packages/meta-vfs/src/BaseFile/index.ts +++ b/packages/meta-vfs/src/BaseFile/index.ts @@ -1,8 +1,16 @@ -import { isFunction, mapValues } from 'lodash' +import { get, isFunction, mapValues } from 'lodash' import { MDVersion } from '@modou/core' -import { FileTypeEnum, UpdateParams } from '@modou/file' -import { action, makeObservable, observable } from '@modou/reactivity' +import { FileTypeEnum, UpdateParams } from '@modou/meta-vfs' +import { + action, + makeObservable, + observable, + reaction, + toJS, +} from '@modou/reactivity' + +import { emitters } from '../event-bus' export type BaseFileMete = { readonly id: string @@ -37,6 +45,52 @@ export abstract class BaseFile< parentFile: observable, updateMeta: action, }) + // 监听meta变化 包括subFile移动 + const disposer = reaction( + () => toJS(this.meta), + (newValue) => { + emitters.emit('updateFileMeta', newValue) + }, + { + fireImmediately: true, + delay: 300, + // equals: comparer.structural, + }, + ) + // 监听删除和添加 + const disposerSubFiles = reaction( + () => + mapValues(this.subFileMap, (files) => + files.reduce>>((pre, cur) => { + pre[cur.meta.id] = cur + return pre + }, {}), + ), + (newValue, prevValue) => { + // 如果是删除 调用子文件的disposer方法 + // 找出删除的文件 + if (!prevValue) { + return + } + Object.entries(prevValue).forEach(([fileType, filesMap]) => + Object.entries(filesMap).forEach(([fileId, file]) => { + if (!get(newValue, [fileType, fileId])) { + file.disposer() + } + }), + ) + }, + { + fireImmediately: true, + delay: 300, + }, + ) + this.disposer = () => { + // 文件被删除了 + emitters.emit('updateFileMeta', null) + disposer() + disposerSubFiles() + } } updateMeta(meta: UpdateParams) { @@ -51,6 +105,8 @@ export abstract class BaseFile< fileType: FileTypeEnum meta: T + protected disposer: () => void + protected subFileMapToJson() { return mapValues(this.subFileMap, (dir) => // TODO 是否有些特殊的file需求sort diff --git a/packages/meta-vfs/src/EntityFile/index.ts b/packages/meta-vfs/src/EntityFile/index.ts index 6946e78..4f3bca5 100644 --- a/packages/meta-vfs/src/EntityFile/index.ts +++ b/packages/meta-vfs/src/EntityFile/index.ts @@ -121,20 +121,18 @@ export class EntityFile extends BaseFile { omit(json, ['fileType', 'entityFields', 'entityRelations']), parentFile, ) - const entityFields = json.entityFields.map((entityField) => + json.entityFields.map((entityField) => EntityFieldFile.formJSON( entityField as unknown as ReturnType, entityFile, ), ) - const entityRelations = json.entityRelations.map((entityRelation) => + json.entityRelations.map((entityRelation) => EntityRelationFile.formJSON( entityRelation as unknown as ReturnType, entityFile, ), ) - entityFile.subFileMap.entityFields.push(...entityFields) - entityFile.subFileMap.entityRelations.push(...entityRelations) return entityFile }) } diff --git a/packages/meta-vfs/src/FlowEdgeFile/index.ts b/packages/meta-vfs/src/FlowEdgeFile/index.ts new file mode 100644 index 0000000..744ba45 --- /dev/null +++ b/packages/meta-vfs/src/FlowEdgeFile/index.ts @@ -0,0 +1,85 @@ +import { omit } from 'lodash' +import { Edge } from 'reactflow' + +import { FileTypeEnum } from '@modou/meta-vfs' +import { makeObservable, observable, runInAction } from '@modou/reactivity' + +import { BaseFile, BaseFileMete } from '../BaseFile' +import { FlowFile } from '../FlowFile' + +export type FlowEdgeFileMeta = BaseFileMete<{ + id: string + source: string + target: string + sourceHandle: string + targetHandle: string +}> +export class FlowEdgeFile extends BaseFile<{}, FlowEdgeFileMeta, FlowFile> { + constructor(meta: FlowEdgeFileMeta, parentFile: FlowFile) { + super({ + fileType: FileTypeEnum.FlowEdge, + meta, + parentFile, + }) + makeObservable(this, { + subFileMap: observable, + }) + } + + // TODO 添加 private 参考mobx文档 是支持的 + + tempMeta: Partial = {} + get reactFlowMeta(): Edge { + return { + ...this.tempMeta, + ...this.meta, + data: this, + // type: 'turbo', + // markerEnd: 'edge-circle', + } + } + + set reactFlowMeta(edge: Edge) { + runInAction(() => { + this.meta.source = edge.source + this.meta.target = edge.target + this.meta.sourceHandle = edge.sourceHandle! + this.meta.targetHandle = edge.targetHandle! + this.tempMeta = edge + }) + } + + subFileMap = {} + toJSON() { + return { + ...this.meta, + ...this.subFileMapToJson(), + fileType: this.fileType, + } + } + + static create(meta: Omit, parentFile: FlowFile) { + return runInAction(() => { + const flowEdgeFile = new FlowEdgeFile( + { + ...meta, + version: '0.0.1', + // FIXME ts type + } as unknown as FlowEdgeFileMeta, + parentFile, + ) + parentFile.flowEdges.push(flowEdgeFile) + return flowEdgeFile + }) + } + + static formJSON( + json: ReturnType, + parentFile: FlowFile, + ): FlowEdgeFile { + return FlowEdgeFile.create( + omit(json, 'fileType') as FlowEdgeFileMeta, + parentFile, + ) + } +} diff --git a/packages/meta-vfs/src/FlowFile/index.ts b/packages/meta-vfs/src/FlowFile/index.ts new file mode 100644 index 0000000..d1d48ef --- /dev/null +++ b/packages/meta-vfs/src/FlowFile/index.ts @@ -0,0 +1,189 @@ +import { omit } from 'lodash' +import { + Connection, + type Edge, + EdgeChange, + type NodeChange, + addEdge, + applyEdgeChanges, + applyNodeChanges, +} from 'reactflow' + +import { FileTypeEnum, PageFile } from '@modou/meta-vfs' +import { + action, + computed, + makeObservable, + observable, + runInAction, +} from '@modou/reactivity' + +import { BaseFile, BaseFileMap, BaseFileMete } from '../BaseFile' +import { FlowEdgeFile } from '../FlowEdgeFile' +import { FlowNodeFile } from '../FlowNodeFile' + +export type FlowFileMeta = BaseFileMete<{ + readonly name: string +}> + +interface FileMap extends BaseFileMap { + readonly flowNodes: FlowNodeFile[] + readonly flowEdges: FlowEdgeFile[] +} + +export class FlowFile extends BaseFile { + protected constructor(meta: FlowFileMeta, parentFile: PageFile) { + super({ fileType: FileTypeEnum.Flow, meta, parentFile }) + makeObservable(this, { + subFileMap: observable, + flowNodes: computed, + flowEdges: computed, + reactFlowNodes: computed.struct, + deleteNode: action, + deleteEdge: action, + onReactFlowEdgesChange: action, + onReactFlowNodesChange: action, + onReactFlowConnect: action, + }) + } + + subFileMap: FileMap = { + flowNodes: [], + flowEdges: [], + } + + get flowNodes() { + return this.subFileMap.flowNodes + } + + get flowEdges() { + return this.subFileMap.flowEdges + } + + get reactFlowNodes() { + return this.flowNodes.map((n) => n.reactFlowMeta) + } + + onReactFlowNodesChange = (changes: NodeChange[]) => { + runInAction(() => { + const res = applyNodeChanges(changes, this.reactFlowNodes) + this.subFileMap.flowNodes.length = 0 + res.forEach((node) => { + node.data.reactFlowMeta = node + this.subFileMap.flowNodes.push(node.data) + }) + }) + } + + get reactFlowEdges() { + return this.flowEdges.map((edge) => edge.reactFlowMeta) + } + + private readonly updateReactFlowEdges = ( + edges: Array>, + ) => { + runInAction(() => { + this.subFileMap.flowEdges.length = 0 + edges.forEach((edge) => { + if (edge.data) { + edge.data.reactFlowMeta = { + ...edge.data.meta, + ...edge, + } + this.subFileMap.flowEdges.push(edge.data) + } else { + edge.data = FlowEdgeFile.create( + { + name: '', + id: edge.id, + source: edge.source, + sourceHandle: edge.sourceHandle!, + target: edge.target, + targetHandle: edge.targetHandle!, + }, + this, + ) + } + }) + }) + } + + deleteNode(nodeId: string) { + const edges = this.subFileMap.flowEdges.filter( + (edge) => edge.meta.target !== nodeId && edge.meta.source !== nodeId, + ) + this.subFileMap.flowEdges.length = 0 + this.subFileMap.flowEdges.push(...edges) + const nodes = this.subFileMap.flowNodes.filter( + (node) => node.meta.id !== nodeId, + ) + this.subFileMap.flowNodes.length = 0 + this.subFileMap.flowNodes.push(...nodes) + } + + deleteEdge(edgeId: string) { + const edges = this.subFileMap.flowEdges.filter( + (edge) => edge.meta.id !== edgeId, + ) + this.subFileMap.flowEdges.length = 0 + this.subFileMap.flowEdges.push(...edges) + } + + onReactFlowEdgesChange = (changes: EdgeChange[]) => { + const edges = applyEdgeChanges(changes, this.reactFlowEdges) + this.updateReactFlowEdges(edges) + } + + onReactFlowConnect = (connection: Connection) => { + const edges = addEdge(connection, this.reactFlowEdges) + this.updateReactFlowEdges(edges) + } + + toJSON() { + return { + ...this.meta, + ...this.subFileMapToJson(), + fileType: this.fileType, + } + } + + // TODO 所有 version 提到 create 函数内部 + static create(meta: Omit, parentFile: PageFile) { + return runInAction(() => { + const flowFile = new FlowFile( + { + ...meta, + version: '0.0.1', + }, + parentFile, + ) + parentFile.flows.push(flowFile) + return flowFile + }) + } + + static formJSON( + json: ReturnType, + parentFile: PageFile, + ): FlowFile { + return runInAction(() => { + const flowFile = FlowFile.create( + omit(json, ['fileType', 'flowNodes']), + parentFile, + ) + json.flowNodes.map((flowNode) => + FlowNodeFile.formJSON( + flowNode as unknown as ReturnType, + flowFile, + ), + ) + json.flowEdges.map((flowEdge) => + FlowEdgeFile.formJSON( + flowEdge as unknown as ReturnType, + flowFile, + ), + ) + return flowFile + }) + } +} diff --git a/packages/meta-vfs/src/FlowNodeFile/index.ts b/packages/meta-vfs/src/FlowNodeFile/index.ts new file mode 100644 index 0000000..ad1b637 --- /dev/null +++ b/packages/meta-vfs/src/FlowNodeFile/index.ts @@ -0,0 +1,90 @@ +import { omit } from 'lodash' +import { type Node } from 'reactflow' + +import { FlowNodeBaseProps } from '@modou/core' +import { FileTypeEnum } from '@modou/meta-vfs' +import { + computed, + makeObservable, + observable, + runInAction, +} from '@modou/reactivity' + +import { BaseFile, BaseFileMete } from '../BaseFile' +import { FlowFile } from '../FlowFile' + +export type FlowNodeFileMeta = + BaseFileMete + +export class FlowNodeFile< + T extends FlowNodeFileMeta = FlowNodeFileMeta, +> extends BaseFile<{}, T, FlowFile> { + constructor(meta: T, parentFile: FlowFile) { + super({ + fileType: FileTypeEnum.FlowNode, + meta, + parentFile, + }) + makeObservable(this, { + subFileMap: observable, + tempMeta: observable, + reactFlowMeta: computed.struct, + }) + } + + // TODO 添加 private 参考mobx文档 是支持的 + tempMeta: Partial = {} + get reactFlowMeta(): Node { + return { + ...this.tempMeta, + id: this.meta.id, + type: this.meta.type, + position: this.meta.position, + data: this, + } + } + + set reactFlowMeta(node: Node) { + runInAction(() => { + this.meta.position = node.position + this.tempMeta = node + }) + } + + subFileMap = {} + toJSON() { + return { + ...this.meta, + ...this.subFileMapToJson(), + fileType: this.fileType, + } + } + + static create( + meta: Omit, + parentFile: FlowFile, + ) { + return runInAction(() => { + const entityFieldFile = new FlowNodeFile( + { + ...meta, + version: '0.0.1', + // FIXME ts type + } as unknown as FlowNodeFileMeta, + parentFile, + ) + parentFile.flowNodes.push(entityFieldFile) + return entityFieldFile + }) + } + + static formJSON( + json: ReturnType, + parentFile: FlowFile, + ): FlowNodeFile { + return FlowNodeFile.create( + omit(json, 'fileType') as FlowNodeFileMeta, + parentFile, + ) + } +} diff --git a/packages/meta-vfs/src/Foo/index.md b/packages/meta-vfs/src/Foo/index.md deleted file mode 100644 index 64aef1a..0000000 --- a/packages/meta-vfs/src/Foo/index.md +++ /dev/null @@ -1,9 +0,0 @@ -# Foo - -This is an example component. - -```jsx -import { Foo } from '@modou/meta-vfs'; - -export default () => -``` diff --git a/packages/meta-vfs/src/Foo/index.tsx b/packages/meta-vfs/src/Foo/index.tsx deleted file mode 100644 index 4f980bb..0000000 --- a/packages/meta-vfs/src/Foo/index.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import React, { type FC } from 'react' - -import { generateId } from '@modou/core' -import { autorun, runInAction } from '@modou/reactivity' -import { observer } from '@modou/reactivity-react' - -import { AppFile } from '../AppFile' -import { PageFile } from '../PageFile' -import { WidgetFile } from '../WidgetFile' - -const appFile = AppFile.create({ - name: 'App_Demo', - version: '0.0.0', - id: generateId(), -}) - -runInAction(() => { - const page1 = PageFile.create( - { - name: 'Page_Demo_1', - version: '0.0.0', - id: generateId(), - rootWidgetId: '0', - }, - appFile, - ) - const page2 = PageFile.create( - { - name: 'Page_Demo_2', - version: '0.0.0', - id: generateId(), - rootWidgetId: '0', - }, - appFile, - ) - // const appFile = AppFile.formJSON(mock_metadata as any) - WidgetFile.create( - { - name: '按钮_1', - version: '0.0.0', - id: generateId(), - slots: {}, - props: { - title: '提交', - }, - type: 'Button', - }, - page1, - ) - WidgetFile.create( - { - name: '输入框_2', - version: '0.0.0', - id: generateId(), - slots: {}, - props: { - title: '提交', - }, - type: 'Input', - }, - page2, - ) -}) -const test = () => { - // console.log(JSON.stringify(appFile)) - // console.log(appFile) - appFile.pages[0].meta.name = '撒阿萨' + Math.random() - // [0].meta.name = '撒阿萨' + Math.random() -} - -autorun(() => { - // console.log('autorun', appFile.test[0]) -}) -// autorun(() => { -// console.log('autorun', appFile.meta.name) -// }) -// effect(() => { -// console.log(appFile.pages[0]?.meta?.name) -// }) -// effect(() => { -// console.log(appFile.pages[0]) -// }) - -const Foo: FC<{ title: string }> = observer(() => ( -
- -
-)) - -export default Foo diff --git a/packages/meta-vfs/src/PageFile/index.ts b/packages/meta-vfs/src/PageFile/index.ts index f2e6191..4cc78cc 100644 --- a/packages/meta-vfs/src/PageFile/index.ts +++ b/packages/meta-vfs/src/PageFile/index.ts @@ -1,5 +1,4 @@ -import produce from 'immer' -import { isEmpty, isFunction, isObject, omit } from 'lodash' +import { isEmpty, isFunction, isObject, keyBy, omit } from 'lodash' import { WidgetBaseProps } from '@modou/core' import { @@ -9,9 +8,11 @@ import { observable, runInAction, } from '@modou/reactivity' +import { MDTernDefs } from '@modou/refine' import { AppFile } from '../AppFile' import { BaseFile, BaseFileMap, BaseFileMete } from '../BaseFile' +import { FlowFile } from '../FlowFile' import { WidgetFile, WidgetFileMeta } from '../WidgetFile' import { FileTypeEnum, UpdateParams } from '../types' @@ -21,6 +22,7 @@ export interface PageFileMeta extends BaseFileMete { export interface RelationWidget { props: WidgetBaseProps + file: WidgetFile slotPath: string parent?: RelationWidget } @@ -29,6 +31,7 @@ type WidgetRelationById = Record interface FileMap extends BaseFileMap { readonly widgets: WidgetFile[] + readonly flows: FlowFile[] } export class PageFile extends BaseFile { @@ -37,25 +40,43 @@ export class PageFile extends BaseFile { makeObservable(this, { subFileMap: observable, widgets: computed, - widgetMap: computed, - widgetRelationById: computed, + widgetMap: computed.struct, + widgetRelationById: computed.struct, addWidget: action, deleteWidget: action, updateWidgets: action, moveWidget: action, + flows: computed, }) } subFileMap: FileMap = { widgets: [], + flows: [], } get widgets() { return this.subFileMap.widgets } + get flows() { + return this.subFileMap.flows + } + + get flowMap() { + return keyBy(this.subFileMap.flows, (flow) => flow.meta.id) + } + + deleteFlow = (flowId: string) => { + const oldFlows = [...this.subFileMap.flows] + this.subFileMap.flows.length = 0 + this.subFileMap.flows.push( + ...oldFlows.filter((flow) => flow.meta.id !== flowId), + ) + } + get widgetMap() { - return new Map(this.widgets.map((widget) => [widget.meta.id, widget])) + return keyBy(this.subFileMap.widgets, (widget) => widget.meta.id) } get widgetRelationById(): WidgetRelationById { @@ -64,16 +85,18 @@ export class PageFile extends BaseFile { if (!Reflect.has(pre, widgetId)) { pre[widgetId] = { props: cur.meta, + file: cur, slotPath: '', } } const parent = pre[widgetId] - if (!isEmpty(cur.meta.slots)) { - Object.entries(cur.meta.slots).forEach(([slotPath, slotChildren]) => { + if (!isEmpty(cur.slots)) { + Object.entries(cur.slots).forEach(([slotPath, slotChildren]) => { slotChildren.forEach((widgetId) => { if (!Reflect.has(pre, widgetId)) { pre[widgetId] = { - props: this.widgetMap.get(widgetId)?.meta!, + props: this.widgetMap[widgetId].meta, + file: this.widgetMap[widgetId], parent, slotPath, } @@ -88,6 +111,35 @@ export class PageFile extends BaseFile { }, {}) } + getWidgetStateDefs = () => { + const DEFINE_KEY = '!define' + const defs: MDTernDefs = { + '!name': 'DATA_TREE', + [DEFINE_KEY]: {}, + // @ts-expect-error + i: { + '!type': 'number', + '!doc': '组件当前所在上下文的层级', + }, + // TODO 只有表格下面的组件才加currentRow + currentRow: { + '!doc': '当前行数据', + }, + } + this.subFileMap.widgets.forEach((widget) => { + Reflect.set( + defs, + widget.meta.name, + omit(widget.stateTypeDefs, DEFINE_KEY), + ) + defs[DEFINE_KEY] = { + ...defs[DEFINE_KEY], + ...widget.stateTypeDefs[DEFINE_KEY], + } + }) + return defs + } + addWidget({ sourceWidgetMeta, targetPosition, @@ -99,13 +151,12 @@ export class PageFile extends BaseFile { targetPosition: number targetSlotPath: string }) { - const sourceWidget = WidgetFile.create(sourceWidgetMeta, this) - const targetWidget = this.widgetMap.get(targetWidgetId) + const targetWidget = this.widgetMap[targetWidgetId] if (!targetWidget) { return } - this.widgets.push(sourceWidget) - targetWidget.meta.slots[targetSlotPath].splice( + const sourceWidget = WidgetFile.create(sourceWidgetMeta, this) + targetWidget.slots[targetSlotPath].splice( targetPosition, 0, sourceWidget.meta.id, @@ -123,99 +174,84 @@ export class PageFile extends BaseFile { targetPosition: number targetSlotPath: string }) { - this.updateWidgets( - produce((draft) => { - let sourceIndex = -1 - draft.forEach((widget) => { - // 删除 - if (isObject(widget.meta.slots)) { - Object.keys(widget.meta.slots).forEach((slotPath) => { - if (sourceIndex !== -1) { - return - } - sourceIndex = widget.meta.slots[slotPath].findIndex( - (slotWidgetId) => slotWidgetId === sourceWidgetId, - ) - if (sourceIndex !== -1) { - widget.meta.slots[slotPath].splice(sourceIndex, 1) - } - }) + let sourceIndex = -1 + this.subFileMap.widgets.forEach((widget) => { + // 删除 + if (isObject(widget.slots)) { + Object.keys(widget.slots).forEach((slotPath) => { + if (sourceIndex !== -1) { + return } - // 添加 - if (widget.meta.id === targetWidgetId) { - // 如果是同一个parent的同一个slot内移动 - const isSameParentSlot = - this.widgetRelationById[sourceWidgetId]?.parent?.props.id === - targetWidgetId && - this.widgetRelationById[sourceWidgetId].slotPath === - targetSlotPath - if (isSameParentSlot) { - widget.meta.slots[targetSlotPath].splice( - sourceIndex < targetPosition - ? targetPosition - 1 - : targetPosition, - 0, - sourceWidgetId, - ) - } else { - widget.meta.slots[targetSlotPath].splice( - targetPosition, - 0, - sourceWidgetId, - ) - } + sourceIndex = widget.slots[slotPath].findIndex( + (slotWidgetId) => slotWidgetId === sourceWidgetId, + ) + if (sourceIndex !== -1) { + widget.slots[slotPath].splice(sourceIndex, 1) } }) - }), - ) + } + // 添加 + if (widget.meta.id === targetWidgetId) { + // 如果是同一个parent的同一个slot内移动 + const isSameParentSlot = + this.widgetRelationById[sourceWidgetId]?.parent?.props.id === + targetWidgetId && + this.widgetRelationById[sourceWidgetId].slotPath === targetSlotPath + if (isSameParentSlot) { + widget.slots[targetSlotPath].splice( + sourceIndex < targetPosition ? targetPosition - 1 : targetPosition, + 0, + sourceWidgetId, + ) + } else { + widget.slots[targetSlotPath].splice(targetPosition, 0, sourceWidgetId) + } + } + }) } deleteWidget(widgetId: string, deleteRaw: boolean = true) { - this.updateWidgets( - produce((draft) => { - draft.forEach((widget) => { - if (isObject(widget.meta.slots)) { - Object.keys(widget.meta.slots).forEach((slotPath) => { - const deletedIndex = widget.meta.slots[slotPath].findIndex( - (slotWidgetId) => slotWidgetId === widgetId, - ) - if (deletedIndex !== -1) { - widget.meta.slots[slotPath].splice(deletedIndex, 1) - } - }) - } - }) - const deletedWidgetIds: string[] = [widgetId] - const deletedWidgets: WidgetFileMeta[] = [ - draft.find( - (widget) => widget.meta.id === widgetId, - ) as unknown as WidgetFileMeta, - ] - while (!isEmpty(deletedWidgets)) { - const currentDeletedWidget = deletedWidgets.shift() as WidgetBaseProps - if (isObject(currentDeletedWidget.slots)) { - Object.values(currentDeletedWidget.slots).forEach((widgetIds) => { - widgetIds.forEach((slotWidgetId) => { - deletedWidgetIds.push(slotWidgetId) - deletedWidgets.push( - draft.find( - (widget) => widget.meta.id === slotWidgetId, - ) as unknown as WidgetFileMeta, - ) - }) - }) - } - } - deletedWidgetIds.forEach((deletedWidgetId) => { - const deletedIndex = draft.findIndex( - (widget) => widget.meta.id === deletedWidgetId, + this.subFileMap.widgets.forEach((widget) => { + if (isObject(widget.slots)) { + Object.keys(widget.slots).forEach((slotPath) => { + const deletedIndex = widget.slots[slotPath].findIndex( + (slotWidgetId) => slotWidgetId === widgetId, ) if (deletedIndex !== -1) { - draft.splice(deletedIndex, 1) + widget.slots[slotPath].splice(deletedIndex, 1) } }) - }), - ) + } + }) + const deletedWidgetIds: string[] = [widgetId] + const deletedWidgets: WidgetFileMeta[] = [ + this.subFileMap.widgets.find( + (widget) => widget.meta.id === widgetId, + ) as unknown as WidgetFileMeta, + ] + while (!isEmpty(deletedWidgets)) { + const currentDeletedWidget = deletedWidgets.shift() as WidgetBaseProps + if (isObject(currentDeletedWidget.slots)) { + Object.values(currentDeletedWidget.slots).forEach((widgetIds) => { + widgetIds.forEach((slotWidgetId) => { + deletedWidgetIds.push(slotWidgetId) + deletedWidgets.push( + this.subFileMap.widgets.find( + (widget) => widget.meta.id === slotWidgetId, + ) as unknown as WidgetFileMeta, + ) + }) + }) + } + } + deletedWidgetIds.forEach((deletedWidgetId) => { + const deletedIndex = this.subFileMap.widgets.findIndex( + (widget) => widget.meta.id === deletedWidgetId, + ) + if (deletedIndex !== -1) { + this.subFileMap.widgets.splice(deletedIndex, 1) + } + }) } // TODO 改成 setWidgets @@ -260,13 +296,18 @@ export class PageFile extends BaseFile { omit(json, ['fileType', 'widgets']), parentFile, ) - const widgets = json.widgets.map((widget) => + json.widgets.map((widget) => WidgetFile.formJSON( widget as unknown as ReturnType, pageFile, ), ) - pageFile.subFileMap.widgets.push(...widgets) + json.flows.map((flow) => + FlowFile.formJSON( + flow as unknown as ReturnType, + pageFile, + ), + ) return pageFile }) } diff --git a/packages/meta-vfs/src/WidgetFile/index.ts b/packages/meta-vfs/src/WidgetFile/index.ts index ab324d4..3fcf7aa 100644 --- a/packages/meta-vfs/src/WidgetFile/index.ts +++ b/packages/meta-vfs/src/WidgetFile/index.ts @@ -1,21 +1,166 @@ -import { omit } from 'lodash' +import { + InterpolationNode, + SimpleExpressionNode, + TextNode, + baseParse, +} from '@vue/compiler-core' +import { flatten } from 'flat' +import { head, last, mapValues, omit } from 'lodash' +import { AssetVFS } from '@modou/asset-vfs' import { WidgetBaseProps } from '@modou/core' -import { makeObservable, observable, runInAction } from '@modou/reactivity' +import { isExpression } from '@modou/evaluate' +import { + computed, + makeObservable, + observable, + runInAction, + toJS, +} from '@modou/reactivity' +import { ParseNodeTypes } from '@modou/render/src/types' import { BaseFile, BaseFileMete } from '../BaseFile' import { PageFile } from '../PageFile' import { FileTypeEnum } from '../types' -export type WidgetFileMeta = BaseFileMete -export class WidgetFile extends BaseFile<{}, WidgetFileMeta, PageFile> { - protected constructor(meta: WidgetFileMeta, parentFile: PageFile) { +type MetaPath = string + +// enum MetaDepTypeEnum { +// Widget, +// } + +// interface MetaDep { +// type: MetaDepTypeEnum +// path: string +// } +interface MetaValExpression { + type: 'Expression' + raw: string + ast: ReturnType + evalString: string +} + +interface MetaValNormal { + type: 'Normal' + raw: any +} +type MetaVal = MetaValExpression | MetaValNormal + +export type WidgetFileMeta = + BaseFileMete + +export class WidgetFile< + T extends WidgetBaseProps = WidgetBaseProps, +> extends BaseFile<{}, WidgetFileMeta, PageFile> { + protected constructor(meta: WidgetFileMeta, parentFile: PageFile) { super({ fileType: FileTypeEnum.Widget, meta, parentFile }) + const assetVFS = new AssetVFS({} as any) + + this.stateTypeDefs = + assetVFS.appFactory.widgetByType[meta.type].stateTypeDefs makeObservable(this, { subFileMap: observable, + // depMap: computed.struct, + flattenedMetaValMap: computed.struct, + slots: computed.struct, }) } + stateTypeDefs: Record + get flattenedMetaValMap(): Record { + return mapValues( + flatten(toJS(this.meta.props)), + (value) => { + if (isExpression(value)) { + // TODO 替换 baseParse 因为{{{name:'小明'}}} 解析失败 + const ast = baseParse(value) + let evalString: string + if ( + (ast.children.length === 1 && + // @ts-expect-error + head(ast.children)?.type === ParseNodeTypes.INTERPOLATION) || + (ast.children.length === 2 && + // @ts-expect-error + head(ast.children)?.type === ParseNodeTypes.INTERPOLATION && + // @ts-expect-error + last(ast.children)?.type === ParseNodeTypes.TEXT && + (last(ast.children) as unknown as TextNode)?.content.trim() === + '}') + ) { + evalString = + ( + (head(ast.children) as unknown as InterpolationNode) + ?.content as unknown as SimpleExpressionNode + ).content || '' + if ( + // @ts-expect-error + last(ast.children)?.type === ParseNodeTypes.TEXT && + (last(ast.children) as unknown as TextNode)?.content.trim() === + '}' + ) { + evalString += + (last(ast.children) as unknown as TextNode)?.content ?? '' + } + } else { + evalString = `\`${ast.children + .map((item) => { + // @ts-expect-error + if (item.type === ParseNodeTypes.TEXT) { + return (item as unknown as TextNode).content + // @ts-expect-error + } else if (item.type === ParseNodeTypes.INTERPOLATION) { + const content = ( + (item as unknown as InterpolationNode) + .content as unknown as SimpleExpressionNode + ).content + if (content) { + return `\${${content}}` + } + return '' + } + // TODO 适配其他类型 + return item.loc.source + }) + .join('')}\`` + } + return { + type: 'Expression', + raw: value, + ast, + evalString, + } + } else { + return { + type: 'Normal', + raw: value, + } + } + }, + ) + } + + get slots() { + return { + ...this.meta.slots, + ...mapValues(this.meta.dynamicSlots || {}, (v) => v.children), + } + } + + // TODO state 修改为主动 watch dep + // 按钮-{{colDSL.span}} + // get depMap(): Record { + // return mapValues( + // pickBy( + // flatten, Record>(this.meta.props), + // (value) => isExpression(value), + // ), + // (value) => ({ + // type: MetaDepTypeEnum.Widget, + // path: value, + // }), + // ) + // } + subFileMap = {} toJSON() { @@ -28,7 +173,7 @@ export class WidgetFile extends BaseFile<{}, WidgetFileMeta, PageFile> { static create(meta: Omit, parentFile: PageFile) { return runInAction(() => { - const widgetFile = new WidgetFile( + const widgetFile = new WidgetFile( { ...meta, version: '0.0.1', diff --git a/packages/meta-vfs/src/event-bus/index.ts b/packages/meta-vfs/src/event-bus/index.ts new file mode 100644 index 0000000..2785908 --- /dev/null +++ b/packages/meta-vfs/src/event-bus/index.ts @@ -0,0 +1,3 @@ +import mitt from 'mitt' + +export const emitters = mitt<{ updateFileMeta?: any }>() diff --git a/packages/meta-vfs/src/index.ts b/packages/meta-vfs/src/index.ts index d97c299..86c7a5b 100644 --- a/packages/meta-vfs/src/index.ts +++ b/packages/meta-vfs/src/index.ts @@ -1,14 +1,13 @@ import { configure } from '@modou/reactivity' configure({ - useProxies: 'never', + useProxies: 'always', enforceActions: 'always', // computedRequiresReaction: true, // observableRequiresReaction: true, - reactionRequiresObservable: true, + reactionRequiresObservable: false, }) -export { default as Foo } from './Foo' export * from './AppFile' export * from './PageFile' export * from './EntityFile' @@ -18,6 +17,8 @@ export * from './WidgetFile' export * from './types' // export * from './AppManager' + +export * from './AppManagerContext' // export class AppManager { // constructor(app: AppFile) { // this.app = app diff --git a/packages/meta-vfs/tsconfig.json b/packages/meta-vfs/tsconfig.json index 7f78f05..da4f821 100644 --- a/packages/meta-vfs/tsconfig.json +++ b/packages/meta-vfs/tsconfig.json @@ -12,8 +12,8 @@ "baseUrl": "./", "paths": { "@@/*": [".dumi/tmp/*"], - "@modou/file": ["src"], - "@modou/file/*": ["src/*", "*"] + "@modou/meta-vfs": ["src"], + "@modou/meta-vfs/*": ["src/*", "*"] }, }, "include": [".dumi/**/*", ".dumirc.ts", "src/**/*"] diff --git a/packages/reactivity-react/src/index.ts b/packages/reactivity-react/src/index.ts index 2e535d8..03adf69 100644 --- a/packages/reactivity-react/src/index.ts +++ b/packages/reactivity-react/src/index.ts @@ -40,3 +40,5 @@ export function useStaticRendering(enable: boolean) { } enableStaticRendering(enable) } + +// export { useComputed } from './useComputed' diff --git a/packages/reactivity-react/src/observer.ts b/packages/reactivity-react/src/observer.ts index 0bbdf56..5f636fa 100644 --- a/packages/reactivity-react/src/observer.ts +++ b/packages/reactivity-react/src/observer.ts @@ -83,12 +83,15 @@ export function observer

( ) } - if (ReactMemoSymbol && baseComponent['$$typeof'] === ReactMemoSymbol) { + // @ts-expect-error + if (ReactMemoSymbol && baseComponent.$$typeof === ReactMemoSymbol) { throw new Error( - `[mobx-react-lite] You are trying to use \`observer\` on a function component wrapped in either another \`observer\` or \`React.memo\`. The observer already applies 'React.memo' for you.`, + `[mobx-react-lite] You are trying to use \`observer\` on a function component` + + ` wrapped in either another \`observer\` or \`React.memo\`. The observer already applies 'React.memo' for you.`, ) } - // The working of observer is explained step by step in this talk: https://www.youtube.com/watch?v=cPF4iBedoF0&feature=youtu.be&t=1307 + // The working of observer is explained step by step in this talk: + // https://www.youtube.com/watch?v=cPF4iBedoF0&feature=youtu.be&t=1307 if (isUsingStaticRendering()) { return baseComponent } @@ -102,9 +105,11 @@ export function observer

( // so we can patch render and apply memo if ( ReactForwardRefSymbol && + // @ts-expect-error baseComponent['$$typeof'] === ReactForwardRefSymbol ) { useForwardRef = true + // @ts-expect-error render = baseComponent['render'] if (typeof render !== 'function') { throw new Error( diff --git a/packages/reactivity-react/src/useComputed.ts b/packages/reactivity-react/src/useComputed.ts new file mode 100644 index 0000000..f8b8a07 --- /dev/null +++ b/packages/reactivity-react/src/useComputed.ts @@ -0,0 +1,9 @@ +// import { useLocalObservable } from './useLocalObservable' + +// export const useComputed = (factory: () => T): T => { +// return useLocalObservable(() => ({ +// get value() { +// return factory() +// }, +// })).value +// } diff --git a/packages/reactivity-react/src/utils/createReactionCleanupTrackingUsingFinalizationRegister.ts b/packages/reactivity-react/src/utils/createReactionCleanupTrackingUsingFinalizationRegister.ts index 89b8f87..c808ae0 100644 --- a/packages/reactivity-react/src/utils/createReactionCleanupTrackingUsingFinalizationRegister.ts +++ b/packages/reactivity-react/src/utils/createReactionCleanupTrackingUsingFinalizationRegister.ts @@ -1,4 +1,4 @@ -import { Reaction } from '@modou/reactivity' +import { ReactionType } from '@modou/reactivity' import { FinalizationRegistry as FinalizationRegistryMaybeUndefined } from './FinalizationRegistryWrapper' import { @@ -29,7 +29,7 @@ export function createReactionCleanupTrackingUsingFinalizationRegister( return { addReactionToTrack( reactionTrackingRef: React.MutableRefObject, - reaction: Reaction, + reaction: ReactionType, objectRetainedByReact: object, ) { const token = globalCleanupTokensCounter++ diff --git a/packages/reactivity-react/src/utils/createTimerBasedReactionCleanupTracking.ts b/packages/reactivity-react/src/utils/createTimerBasedReactionCleanupTracking.ts index e51c341..5c27604 100644 --- a/packages/reactivity-react/src/utils/createTimerBasedReactionCleanupTracking.ts +++ b/packages/reactivity-react/src/utils/createTimerBasedReactionCleanupTracking.ts @@ -1,4 +1,4 @@ -import { Reaction } from '@modou/reactivity' +import { ReactionType } from '@modou/reactivity' import { CLEANUP_TIMER_LOOP_MILLIS, @@ -110,7 +110,7 @@ export function createTimerBasedReactionCleanupTracking(): ReactionCleanupTracki return { addReactionToTrack( reactionTrackingRef: React.MutableRefObject, - reaction: Reaction, + reaction: ReactionType, /** * On timer based implementation we don't really need this object, * but we keep the same api diff --git a/packages/reactivity-react/src/utils/printDebugValue.ts b/packages/reactivity-react/src/utils/printDebugValue.ts index 1dc0afa..3ff7bcf 100644 --- a/packages/reactivity-react/src/utils/printDebugValue.ts +++ b/packages/reactivity-react/src/utils/printDebugValue.ts @@ -1,5 +1,5 @@ -import { Reaction, getDependencyTree } from '@modou/reactivity' +import { ReactionType, getDependencyTree } from '@modou/reactivity' -export function printDebugValue(v: Reaction) { +export function printDebugValue(v: ReactionType) { return getDependencyTree(v) } diff --git a/packages/reactivity-react/src/utils/reactionCleanupTrackingCommon.ts b/packages/reactivity-react/src/utils/reactionCleanupTrackingCommon.ts index dde7154..06c0c43 100644 --- a/packages/reactivity-react/src/utils/reactionCleanupTrackingCommon.ts +++ b/packages/reactivity-react/src/utils/reactionCleanupTrackingCommon.ts @@ -1,6 +1,6 @@ -import { Reaction } from '@modou/reactivity' +import { Reaction, ReactionType } from '@modou/reactivity' -export function createTrackingData(reaction: typeof Reaction) { +export function createTrackingData(reaction: ReactionType) { const trackingData: IReactionTracking = { reaction, mounted: false, @@ -22,7 +22,7 @@ export interface ReactionCleanupTracking { */ addReactionToTrack( reactionTrackingRef: React.MutableRefObject, - reaction: typeof Reaction, + reaction: ReactionType, objectRetainedByReact: object, ): IReactionTracking recordReactionAsCommitted( @@ -34,7 +34,7 @@ export interface ReactionCleanupTracking { export interface IReactionTracking { /** The Reaction created during first render, which may be leaked */ - reaction: typeof Reaction + reaction: ReactionType /** * The time (in ticks) at which point we should dispose of the reaction * if this component hasn't yet been fully mounted. diff --git a/packages/reactivity/src/index.ts b/packages/reactivity/src/index.ts index b79ab24..9d0c4aa 100644 --- a/packages/reactivity/src/index.ts +++ b/packages/reactivity/src/index.ts @@ -1,12 +1,14 @@ import * as _mobx from 'mobx' +const MD_MOBX_PATH = '__md_mobx__' + let mobx: typeof _mobx = _mobx // TODO 判断浏览器环境还是Node环境 -if (window.top?.__md_mobx__) { - mobx = window.top.__md_mobx__ +if (window.top && Reflect.has(window.top, MD_MOBX_PATH)) { + mobx = Reflect.get(window.top, MD_MOBX_PATH) } -window.__md_mobx__ = mobx +Reflect.set(window, MD_MOBX_PATH, mobx) export const action = mobx.action export const autorun = mobx.autorun @@ -18,13 +20,22 @@ export const makeAutoObservable = mobx.makeAutoObservable export const makeObservable = mobx.makeObservable export const observable = mobx.observable export const observe = mobx.observe +export const reaction = mobx.reaction +export const remove = mobx.remove export const runInAction = mobx.runInAction export const set = mobx.set export const toJS = mobx.toJS +export const isObservableArray = mobx.isObservableArray +export const isObservableMap = mobx.isObservableMap +export const isObservableObject = mobx.isObservableObject export const trace = mobx.trace export const untracked = mobx.untracked export const Reaction = mobx.Reaction export const when = mobx.when export const getDependencyTree = mobx.getDependencyTree -export { type IReactionDisposer, type AnnotationsMap } from 'mobx' +export { + type IReactionDisposer, + type AnnotationsMap, + Reaction as ReactionType, +} from 'mobx' diff --git a/packages/refine/src/index.ts b/packages/refine/src/index.ts index 5c5032c..2a5f6e3 100644 --- a/packages/refine/src/index.ts +++ b/packages/refine/src/index.ts @@ -24,7 +24,8 @@ export type MRDefault = ZodDefault export { default as mrToJsonSchema } from '@lowcode-modou/zod-to-json-schema' -export * from './scheme-to-json-default' +export { schemeToJsonDefault } from './scheme-to-json-default' +export { schemeToDefs, type MDTernDefs } from './scheme-to-defs' export type { JsonSchema7Type } from '@lowcode-modou/zod-to-json-schema/src/parseDef' export type { JsonSchema7ObjectType } from '@lowcode-modou/zod-to-json-schema/src/parsers/object' diff --git a/packages/refine/src/scheme-to-defs/constants/index.ts b/packages/refine/src/scheme-to-defs/constants/index.ts new file mode 100644 index 0000000..e69f286 --- /dev/null +++ b/packages/refine/src/scheme-to-defs/constants/index.ts @@ -0,0 +1,2 @@ +export const OMIT_DEF_KEYS = ['renderSlots', 'renderSlotPaths', 'instance'] +export const SETTER_KEY = 'x-setter' diff --git a/packages/refine/src/scheme-to-defs/index.ts b/packages/refine/src/scheme-to-defs/index.ts new file mode 100644 index 0000000..ebb5159 --- /dev/null +++ b/packages/refine/src/scheme-to-defs/index.ts @@ -0,0 +1,10 @@ +import { JsonSchema7Type } from '@lowcode-modou/zod-to-json-schema/src/parseDef' + +import { parseScheme } from './parsers/parseScheme' +import { MDTernDefs } from './types' + +export type { MDTernDefs } from './types' + +export const schemeToDefs = (scheme: JsonSchema7Type): MDTernDefs => { + return parseScheme(scheme) || {} +} diff --git a/packages/refine/src/scheme-to-defs/parsers/parseAny.ts b/packages/refine/src/scheme-to-defs/parsers/parseAny.ts new file mode 100644 index 0000000..53a0202 --- /dev/null +++ b/packages/refine/src/scheme-to-defs/parsers/parseAny.ts @@ -0,0 +1,13 @@ +import { JsonSchema7Type } from '@lowcode-modou/zod-to-json-schema/src/parseDef' + +import { SETTER_KEY } from '../constants' +import { MixedMDTernDefs } from '../types' + +export const parseAny = (schema: JsonSchema7Type): MixedMDTernDefs => { + return { + defs: { + '!type': 'any', + '!doc': Reflect.get(schema, SETTER_KEY)?.description ?? '', + }, + } +} diff --git a/packages/refine/src/scheme-to-defs/parsers/parseArray.ts b/packages/refine/src/scheme-to-defs/parsers/parseArray.ts new file mode 100644 index 0000000..9bda34c --- /dev/null +++ b/packages/refine/src/scheme-to-defs/parsers/parseArray.ts @@ -0,0 +1,24 @@ +import { JsonSchema7ArrayType } from '@lowcode-modou/zod-to-json-schema/src/parsers/array' +import { isEmpty } from 'lodash' + +import { SETTER_KEY } from '../constants' +import { MixedMDTernDefs } from '../types' +import { selectParser } from './parseScheme' + +export const parseArray = ( + schema: JsonSchema7ArrayType & { default?: any[] }, +): MixedMDTernDefs => { + const itemKey = Math.random().toString().replace('.', '_') + const items = schema.items as unknown as JsonSchema7ArrayType + const scopedDefs = selectParser(items) + return { + defs: { + '!type': `[${isEmpty(scopedDefs.defs) ? 'any' : itemKey}]`, + '!doc': Reflect.get(schema, SETTER_KEY)?.description ?? '', + }, + scopedDefs: { + [itemKey]: scopedDefs.defs, + ...scopedDefs.scopedDefs, + }, + } +} diff --git a/packages/refine/src/scheme-to-defs/parsers/parseBoolean.ts b/packages/refine/src/scheme-to-defs/parsers/parseBoolean.ts new file mode 100644 index 0000000..15d4891 --- /dev/null +++ b/packages/refine/src/scheme-to-defs/parsers/parseBoolean.ts @@ -0,0 +1,13 @@ +import { JsonSchema7Type } from '@lowcode-modou/zod-to-json-schema/src/parseDef' + +import { SETTER_KEY } from '../constants' +import { MixedMDTernDefs } from '../types' + +export const parseBoolean = (schema: JsonSchema7Type): MixedMDTernDefs => { + return { + defs: { + '!type': 'bool', + '!doc': Reflect.get(schema, SETTER_KEY)?.description ?? '', + }, + } +} diff --git a/packages/refine/src/scheme-to-defs/parsers/parseNumber.ts b/packages/refine/src/scheme-to-defs/parsers/parseNumber.ts new file mode 100644 index 0000000..5a99997 --- /dev/null +++ b/packages/refine/src/scheme-to-defs/parsers/parseNumber.ts @@ -0,0 +1,13 @@ +import { JsonSchema7Type } from '@lowcode-modou/zod-to-json-schema/src/parseDef' + +import { SETTER_KEY } from '../constants' +import { MixedMDTernDefs } from '../types' + +export const parseNumber = (schema: JsonSchema7Type): MixedMDTernDefs => { + return { + defs: { + '!type': 'number', + '!doc': Reflect.get(schema, SETTER_KEY)?.description ?? '', + }, + } +} diff --git a/packages/refine/src/scheme-to-defs/parsers/parseObject.ts b/packages/refine/src/scheme-to-defs/parsers/parseObject.ts new file mode 100644 index 0000000..85bce50 --- /dev/null +++ b/packages/refine/src/scheme-to-defs/parsers/parseObject.ts @@ -0,0 +1,34 @@ +import { JsonSchema7Type } from '@lowcode-modou/zod-to-json-schema/src/parseDef' +import { JsonSchema7ObjectType } from '@lowcode-modou/zod-to-json-schema/src/parsers/object' +import { omit } from 'lodash' + +import { OMIT_DEF_KEYS, SETTER_KEY } from '../constants' +import { MDTernDefs, MixedMDTernDefs } from '../types' +import { JsonSchema7TypeType, selectParser } from './parseScheme' + +export const parseObject = ( + schema: JsonSchema7ObjectType & { default?: Record }, +): MixedMDTernDefs => { + const defs: MDTernDefs = {} + + const doc = Reflect.get(schema, SETTER_KEY)?.description + if (doc) { + defs['!doc'] = doc + } + + let scopedDefs: Record = {} + Object.entries(omit(schema.properties, OMIT_DEF_KEYS) || {}).forEach( + ([key, value]) => { + const mixedDefs = selectParser( + value as JsonSchema7Type & { type: JsonSchema7TypeType }, + ) + Reflect.set(defs, key, mixedDefs.defs) + scopedDefs = { ...mixedDefs.scopedDefs } + }, + ) + + return { + defs, + scopedDefs, + } +} diff --git a/packages/refine/src/scheme-to-defs/parsers/parseScheme.ts b/packages/refine/src/scheme-to-defs/parsers/parseScheme.ts new file mode 100644 index 0000000..51165f5 --- /dev/null +++ b/packages/refine/src/scheme-to-defs/parsers/parseScheme.ts @@ -0,0 +1,58 @@ +import { JsonSchema7Type } from '@lowcode-modou/zod-to-json-schema/src/parseDef' +import { JsonSchema7ArrayType } from '@lowcode-modou/zod-to-json-schema/src/parsers/array' +import { JsonSchema7BooleanType } from '@lowcode-modou/zod-to-json-schema/src/parsers/boolean' +import { JsonSchema7NumberType } from '@lowcode-modou/zod-to-json-schema/src/parsers/number' +import { JsonSchema7ObjectType } from '@lowcode-modou/zod-to-json-schema/src/parsers/object' +import { JsonSchema7StringType } from '@lowcode-modou/zod-to-json-schema/src/parsers/string' +import { omit } from 'lodash' + +import { MDTernDefs, MixedMDTernDefs } from '../types' +import { parseAny } from './parseAny' +import { parseArray } from './parseArray' +import { parseBoolean } from './parseBoolean' +import { parseNumber } from './parseNumber' +import { parseObject } from './parseObject' +import { parseString } from './parseString' + +export type JsonSchema7TypeType = + | 'object' + | 'array' + | 'string' + | 'number' + | 'boolean' + | unknown + +export const selectParser = ( + scheme: JsonSchema7Type & { type: JsonSchema7TypeType }, +): MixedMDTernDefs => { + switch (scheme.type) { + case 'object': + return parseObject(scheme as JsonSchema7ObjectType) + case 'array': + return parseArray(scheme as JsonSchema7ArrayType) + case 'string': + return parseString(scheme as JsonSchema7StringType) + case 'number': + return parseNumber(scheme as JsonSchema7NumberType) + case 'boolean': + return parseBoolean(scheme as JsonSchema7BooleanType) + default: + return parseAny(scheme) + // throw new Error(`unknown json scheme type ${String(scheme.type)}`) + } +} + +export const parseScheme = (scheme: JsonSchema7Type): MDTernDefs => { + const mixedDefs = selectParser( + scheme as unknown as JsonSchema7Type & { type: JsonSchema7TypeType }, + ) + const defs: MDTernDefs = { + ...mixedDefs.defs, + } + + if (mixedDefs.scopedDefs) { + defs['!define'] = mixedDefs.scopedDefs + } + + return defs +} diff --git a/packages/refine/src/scheme-to-defs/parsers/parseString.ts b/packages/refine/src/scheme-to-defs/parsers/parseString.ts new file mode 100644 index 0000000..c761266 --- /dev/null +++ b/packages/refine/src/scheme-to-defs/parsers/parseString.ts @@ -0,0 +1,13 @@ +import { JsonSchema7StringType } from '@lowcode-modou/zod-to-json-schema/src/parsers/string' + +import { SETTER_KEY } from '../constants' +import { MixedMDTernDefs } from '../types' + +export const parseString = (schema: JsonSchema7StringType): MixedMDTernDefs => { + return { + defs: { + '!type': 'string', + '!doc': Reflect.get(schema, SETTER_KEY)?.description ?? '', + }, + } +} diff --git a/packages/refine/src/scheme-to-defs/types/index.ts b/packages/refine/src/scheme-to-defs/types/index.ts new file mode 100644 index 0000000..9bec190 --- /dev/null +++ b/packages/refine/src/scheme-to-defs/types/index.ts @@ -0,0 +1,19 @@ +export type MDTernDefs = { + '!doc'?: string + '!type'?: string + '!url'?: string + '!define'?: Record + '!name'?: string +} & { + // [k: string]: MDTernDefs + [K in `!${string}`]?: string +} & { + // TODO 定义 非特定字符串开头 + // https://stackoverflow.com/questions/73306648/typescript-type-for-object-key-to-not-start-with-specific-character + [K in `!!${string}`]?: MDTernDefs +} + +export interface MixedMDTernDefs { + defs: MDTernDefs + scopedDefs?: Record +} diff --git a/packages/refine/src/scheme-to-json-default/parsers/parseObject.ts b/packages/refine/src/scheme-to-json-default/parsers/parseObject.ts index f7114e7..f301fbf 100644 --- a/packages/refine/src/scheme-to-json-default/parsers/parseObject.ts +++ b/packages/refine/src/scheme-to-json-default/parsers/parseObject.ts @@ -12,6 +12,9 @@ export const parseObject = ( ) ? cloneDeep(schema.default!) : DEFAULT_TO_EMPTY + if (!schema.properties) { + return {} + } Object.entries(schema.properties).forEach(([key, value]) => { const defaultItem = parseScheme(value) if (defaultItem === DEFAULT_TO_EMPTY) { diff --git a/packages/render/package.json b/packages/render/package.json index b8a28dc..df0c208 100644 --- a/packages/render/package.json +++ b/packages/render/package.json @@ -10,6 +10,5 @@ "author": "", "license": "ISC", "dependencies": { - "@modou/state-manager": "workspace:^0.0.1" } } diff --git a/packages/render/src/components/ReactRender.tsx b/packages/render/src/components/ReactRender.tsx index 6c42c72..fe18dcf 100644 --- a/packages/render/src/components/ReactRender.tsx +++ b/packages/render/src/components/ReactRender.tsx @@ -1,23 +1,25 @@ +import { ErrorBoundary } from '@ant-design/pro-components' import { ConfigProvider } from 'antd' import zhCN from 'antd/locale/zh_CN' import { FC, useContext } from 'react' import * as React from 'react' -import { AppFactoryContext, AppManagerProvider } from '@modou/core' -import { StateManagerProvider } from '@modou/state-manager/src/contexts' +import { AppFactoryContext } from '@modou/asset-vfs' +import { AppManagerProvider } from '@modou/meta-vfs' +import { observer } from '@modou/reactivity-react' +import { AppStateProvider, CanvasStateProvider } from '@modou/state-manager' import { CanvasFileContextProvider } from '../contexts' -import { useInitRender, useInitStateManager } from '../hooks' +import { useInitRender } from '../hooks' import { MoDouRenderProps } from '../types' import { ReactRenderTolerant } from './ReactRenderTolerant' // TODO 支持页面的 State 缓存 比如详情页面回退到列表页面 -export const ReactRender: FC = (props) => { +const _ReactRender: FC = (props) => { const appFactory = useContext(AppFactoryContext) - const { file, appManager } = useInitRender() - const { stateManager } = useInitStateManager({ app: appManager?.app, file }) - console.log('stateManager', file, appManager, stateManager) - if (!file || !appManager || !stateManager) { + const { file, appManager, appState, state } = useInitRender() + + if (!file || !appManager || !appState || !state) { return null } return ( @@ -25,13 +27,19 @@ export const ReactRender: FC = (props) => { {/* TODO 去除点(.)保持跟其他Context统一 */} - + - + + + + + - + ) } + +export const ReactRender = observer(_ReactRender) diff --git a/packages/render/src/components/ReactRenderTolerant.tsx b/packages/render/src/components/ReactRenderTolerant.tsx index 0683e4a..ab3eefe 100644 --- a/packages/render/src/components/ReactRenderTolerant.tsx +++ b/packages/render/src/components/ReactRenderTolerant.tsx @@ -2,9 +2,13 @@ import { Spin } from 'antd' import { FC } from 'react' import * as React from 'react' -import { useAppManager } from '@modou/core' import { mcss } from '@modou/css-in-js' +import { useAppManager } from '@modou/meta-vfs' import { observer } from '@modou/reactivity-react' +import { + DEFAULT_WIDGET_VARIABLES, + WidgetVariablesProvider, +} from '@modou/state-manager' import { useCanvasFile } from '../contexts' import { MoDouRenderProps } from '../types' @@ -14,9 +18,11 @@ const _ReactRenderTolerant: FC = ({ host = 'browser' }) => { const { canvasFile } = useCanvasFile() const { appManager } = useAppManager() // FIXME 判断当前所在环境 是否从props更新widgets 可以加一个 host - const rootWidget = appManager.widgetMap.get(canvasFile.meta.rootWidgetId) + const rootWidget = appManager.widgetMap[canvasFile.meta.rootWidgetId] return rootWidget ? ( - + + + ) : (

diff --git a/packages/render/src/components/WidgetErrorBoundary.tsx b/packages/render/src/components/WidgetErrorBoundary.tsx new file mode 100644 index 0000000..6fa42e5 --- /dev/null +++ b/packages/render/src/components/WidgetErrorBoundary.tsx @@ -0,0 +1,73 @@ +import { Typography } from 'antd' +import { Component, ReactNode } from 'react' + +interface Props { + children: ReactNode + widgetName: string + widgetId: string +} +interface State { + hasError: boolean + errorMessage: string +} +const initialState = { hasError: false, errorMessage: '' } + +export class WidgetErrorBoundary extends Component { + constructor(props: Readonly) { + super(props) + this.state = { ...initialState } + } + + updatedWithError = false + + // eslint-disable-next-line n/handle-callback-err + static getDerivedStateFromError(error: Error) { + // 更新 state 使下一次渲染能够显示降级后的 UI + return { hasError: true, errorMessage: error.message } + } + + // componentDidCatch(error, errorInfo) { + // // 你同样可以将错误日志上报给服务器 + // // logErrorToMyService(error, errorInfo) + // } + + componentDidUpdate( + prevProps: Readonly, + prevState: Readonly, + snapshot?: any, + ) { + // 已经存在错误,并且是第一次由于 error 而引发的 render/update,那么设置 flag=true,不会重置 + if (this.state.hasError && !this.updatedWithError) { + this.updatedWithError = true + return + } + // 已经存在错误,并且是普通的组件 render,则检查 resetKeys 是否有改动,改了就重置 + if (this.state.hasError) { + this.reset() + } + } + + reset = () => { + this.updatedWithError = false + this.setState(initialState) + } + + render() { + if (this.state.hasError) { + // 你可以自定义降级后的 UI 并渲染 + return ( +
+ + 【{this.props.widgetName}】渲染错误 + +
+ + 错误信息:{this.state.errorMessage} + +
+ ) + } + + return this.props.children + } +} diff --git a/packages/render/src/components/WidgetVirtual.tsx b/packages/render/src/components/WidgetVirtual.tsx index 1e12ead..eb46af2 100644 --- a/packages/render/src/components/WidgetVirtual.tsx +++ b/packages/render/src/components/WidgetVirtual.tsx @@ -1,131 +1,102 @@ -import { - InterpolationNode, - SimpleExpressionNode, - TextNode, - baseParse, -} from '@vue/compiler-core' -import { useMount } from 'ahooks' -import { flatten } from 'flat' -import { get, keys, set } from 'lodash' -import { FC, useContext, useRef } from 'react' +import { FC, useContext, useEffect, useState } from 'react' import * as React from 'react' -import { AppFactoryContext, useAppManager } from '@modou/core' -import { - IReactionDisposer, - autorun, - runInAction, - toJS, -} from '@modou/reactivity' +import { AppFactoryContext } from '@modou/asset-vfs' +import { useAppManager } from '@modou/meta-vfs' +import { toJS } from '@modou/reactivity' import { observer, useLocalObservable } from '@modou/reactivity-react' -import { WidgetState } from '@modou/state-manager' -import { useStateManager } from '@modou/state-manager/src/contexts' +import { + WidgetState, + useCanvasState, + useWidgetVariables, +} from '@modou/state-manager' -import { ParseNodeTypes } from '../types' -import { evalExpression, isExpression } from '../utils/evaluate' +import { WidgetErrorBoundary } from './WidgetErrorBoundary' const _WidgetVirtual: FC<{ widgetId: string }> = ({ widgetId }) => { + // trace(true) + // 按钮-{{colDSL.span}} + const { widgetVariables } = useWidgetVariables() + const { appManager } = useAppManager() - const { canvasState } = useStateManager() - const widget = appManager.widgetMap.get(widgetId)! + const { canvasState } = useCanvasState() + const widget = appManager.widgetMap[widgetId] const appFactory = useContext(AppFactoryContext) - let widgetState = canvasState.subState.widget[widget.meta.id] - if (!widgetState) { - widgetState = new WidgetState(widget, appFactory) - canvasState.subState.widget[widget.meta.id] = widgetState - } const widgetDef = appFactory.widgetByType[widget.meta.type] // TODO any 替换 state 定义 const WidgetComponent = widgetDef.component - const state = useLocalObservable(() => ({ - get renderSlots() { - return Object.entries(widgetDef.metadata.slots || {}) - .map(([slotPath, slot]) => { - return { - key: slotPath, - elements: widget.meta.slots[slotPath]?.map((widgetId) => ( - - )), - } - }) - .reduce>((pre, { key, elements }) => { - // FIXME 不知道为什么类型不对😂 - // pre[key] = elements - Reflect.set(pre, key, elements) - return pre - }, {}) - }, - get renderSlotPaths() { - return Object.keys(this.renderSlots).reduce>( - (pre, cur) => { - pre[cur] = cur - return pre - }, - {}, - ) - }, - })) + const localState = useLocalObservable(() => { + return { + get allSlots() { + return { + ...(widgetDef.metadata.slots || {}), + ...widget.meta.dynamicSlots, + } + }, + get renderSlots() { + return Object.entries(this.allSlots) + .map(([slotPath, slot]) => { + return { + key: slotPath, + elements: widget.slots[slotPath]?.map((widgetId) => ( + + )), + } + }) + .reduce>((pre, { key, elements }) => { + // FIXME 不知道为什么类型不对😂 + // pre[key] = elements + Reflect.set(pre, key, elements) + return pre + }, {}) + }, + get renderSlotPaths() { + return Object.keys(this.renderSlots).reduce>( + (pre, cur) => { + pre[cur] = cur + return pre + }, + {}, + ) + }, + } + }) - const watchStopsRef = useRef>({}) + const [widgetState, setWidgetState] = useState() - useMount(() => { - const flattenStateKeys = keys(flatten(widgetState.state)) - flattenStateKeys.forEach((path) => { - if (Reflect.has(watchStopsRef.current, path)) { - return - } - watchStopsRef.current[path] = autorun(() => { - const fullPath = `${path}` - const rawPropVal = get(widget.meta.props, fullPath) - if (isExpression(rawPropVal)) { - const ast = baseParse(rawPropVal) - const expression = `\`${ast.children - .map((item) => { - // @ts-expect-error - if (item.type === ParseNodeTypes.TEXT) { - return (item as unknown as TextNode).content - // @ts-expect-error - } else if (item.type === ParseNodeTypes.INTERPOLATION) { - const content = ( - (item as unknown as InterpolationNode) - .content as unknown as SimpleExpressionNode - ).content - if (content) { - return `\${${content}}` - } - return '' - } - // TODO 适配其他类型 - return item.loc.source - }) - .join('')}\`` - runInAction(() => { - set( - widgetState.state, - fullPath, - evalExpression(expression, canvasState), - ) - }) - } else { - set(widgetState.state, `${path}`, rawPropVal) - } - }) + useEffect(() => { + if (widget === widgetState?.file) { + return + } + const _widgetState = new WidgetState(widget, { + appFactory, + widgetVariables, + canvasState, }) - }) + setWidgetState(_widgetState) + return () => { + widgetState?.disposer() + } + // 不要添加多余deps,保持为空 + }, [appFactory, canvasState, widget, widgetState, widgetVariables]) + + useEffect(() => { + widgetState?.updateWidgetVariables(widgetVariables) + }, [widgetState, widgetVariables]) - // FIXME 会导致重新渲染 // FIXME 完善组件类型 - // console.log('toJS(widgetState)', toJS(widgetState.state)) - return ( - - ) + return widgetState?.state.instance.initialized ? ( + + + + ) : null } export const WidgetVirtual = observer(_WidgetVirtual) diff --git a/packages/render/src/constants/index.ts b/packages/render/src/constants/index.ts index d1ecd7a..a171c1a 100644 --- a/packages/render/src/constants/index.ts +++ b/packages/render/src/constants/index.ts @@ -1,2 +1,4 @@ export const APP_MANAGER_SYMBOL = Symbol('AppManager') export const FILE_SYMBOL = Symbol('File') +export const APP_STATE_SYMBOL = Symbol('AppState') +export const STATE_SYMBOL = Symbol('State') diff --git a/packages/render/src/hooks/index.ts b/packages/render/src/hooks/index.ts index 5faee6d..95253b2 100644 --- a/packages/render/src/hooks/index.ts +++ b/packages/render/src/hooks/index.ts @@ -1,2 +1 @@ export { useInitRender } from './useInitRender' -export { useInitStateManager } from './useInitStateManager' diff --git a/packages/render/src/hooks/useInitRender.ts b/packages/render/src/hooks/useInitRender.ts index 3d3093d..1f77294 100644 --- a/packages/render/src/hooks/useInitRender.ts +++ b/packages/render/src/hooks/useInitRender.ts @@ -2,15 +2,28 @@ import { useMemoizedFn } from 'ahooks' import { useEffect, useState } from 'react' import { AppManager, PageFile } from '@modou/meta-vfs' +import { AppState, PageState } from '@modou/state-manager' -import { APP_MANAGER_SYMBOL, FILE_SYMBOL } from '../constants' +import { + APP_MANAGER_SYMBOL, + APP_STATE_SYMBOL, + FILE_SYMBOL, + STATE_SYMBOL, +} from '../constants' import { ReactRenderHost } from '../utils' -import { UpdateAppManager, UpdateFile } from '../utils/ReactRenderHost' +import { + UpdateAppManager, + UpdateAppState, + UpdateFile, + UpdateState, +} from '../utils/ReactRenderHost' export const useInitRender = () => { // TODO 判断 host type 浏览器 or 模拟器 const [appManager, updateAppManager] = useState() const [file, updateFile] = useState() + const [appState, updateAppState] = useState() + const [state, updateState] = useState() const _updateAppManager: UpdateAppManager = useMemoizedFn((appManager) => { if (!Reflect.get(window, APP_MANAGER_SYMBOL)) { Reflect.set(window, APP_MANAGER_SYMBOL, appManager) @@ -23,14 +36,28 @@ export const useInitRender = () => { } updateFile(file) }) + const _updateAppState: UpdateAppState = useMemoizedFn((appState) => { + if (!Reflect.get(window, APP_STATE_SYMBOL)) { + Reflect.set(window, APP_STATE_SYMBOL, appState) + } + updateAppState(appState) + }) + const _updateState: UpdateState = useMemoizedFn((state) => { + if (!Reflect.get(window, STATE_SYMBOL)) { + Reflect.set(window, STATE_SYMBOL, state) + } + updateState(state) + }) useEffect(() => { if (!window.reactRenderHost) { window.reactRenderHost = new ReactRenderHost({ updateAppManager: _updateAppManager, updateFile: _updateFile, + updateState: _updateState, + updateAppState: _updateAppState, }) } - }, [_updateAppManager, _updateFile]) + }, [_updateAppManager, _updateFile, _updateState, _updateAppState]) useEffect(() => { if (Reflect.get(window, APP_MANAGER_SYMBOL)) { @@ -39,10 +66,18 @@ export const useInitRender = () => { if (Reflect.get(window, FILE_SYMBOL)) { updateFile(Reflect.get(window, FILE_SYMBOL)) } - }) + if (Reflect.get(window, APP_STATE_SYMBOL)) { + updateAppState(Reflect.get(window, APP_STATE_SYMBOL)) + } + if (Reflect.get(window, STATE_SYMBOL)) { + updateState(Reflect.get(window, STATE_SYMBOL)) + } + }, []) return { file, appManager, + appState, + state, } } diff --git a/packages/render/src/hooks/useInitStateManager.ts b/packages/render/src/hooks/useInitStateManager.ts deleted file mode 100644 index ef50ea0..0000000 --- a/packages/render/src/hooks/useInitStateManager.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { useEffect, useState } from 'react' - -import { AppFile, PageFile } from '@modou/meta-vfs' -import { AppState, PageState, StateManager } from '@modou/state-manager' - -export const useInitStateManager = ({ - app, - file, -}: { - app?: AppFile - file?: PageFile -}) => { - const [stateManager, setStateManager] = useState() - useEffect(() => { - if (!app || !file) { - return - } - setStateManager( - new StateManager({ - appState: new AppState(app), - canvasState: new PageState(file), - }), - ) - }, [app, file]) - return { - stateManager, - } -} diff --git a/packages/render/src/utils/ReactRenderHost.ts b/packages/render/src/utils/ReactRenderHost.ts index 71f5b90..31b30ef 100644 --- a/packages/render/src/utils/ReactRenderHost.ts +++ b/packages/render/src/utils/ReactRenderHost.ts @@ -1,20 +1,32 @@ import { AppManager, PageFile } from '@modou/meta-vfs' +import { AppState, PageState } from '@modou/state-manager' export type UpdateAppManager = (appManager: AppManager) => void export type UpdateFile = (file: PageFile) => void +export type UpdateAppState = (appState: AppState) => void +export type UpdateState = (file: PageState) => void + export class ReactRenderHost { constructor({ updateAppManager, updateFile, + updateAppState, + updateState, }: { updateAppManager: UpdateAppManager updateFile: UpdateFile + updateAppState: UpdateAppState + updateState: UpdateState }) { this.updateAppManager = updateAppManager this.updateFile = updateFile + this.updateAppState = updateAppState + this.updateState = updateState } updateAppManager updateFile + updateAppState + updateState } diff --git a/packages/setters/src/components/number.tsx b/packages/setters/src/components/number.tsx index e68968f..2b13d62 100644 --- a/packages/setters/src/components/number.tsx +++ b/packages/setters/src/components/number.tsx @@ -14,12 +14,20 @@ import { BaseMRSetterOptions, BaseSetterProps } from '../types' // } export interface MRNumberSetterType extends BaseMRSetterOptions { type: SetterTypeEnum.Number + min?: number + max?: number } -type Props = BaseSetterProps +type Props = BaseSetterProps -export const NumberSetter: FC = ({ value, onChange }) => { +export const NumberSetter: FC = ({ value, onChange, options }) => { return ( - onChange(val as number)} /> + onChange(val as number)} + style={{ width: '100%' }} + min={options?.min ?? -Infinity} + max={options?.max ?? Infinity} + /> ) } diff --git a/packages/setters/src/components/select.tsx b/packages/setters/src/components/select.tsx index 501fbe6..eff37de 100644 --- a/packages/setters/src/components/select.tsx +++ b/packages/setters/src/components/select.tsx @@ -1,8 +1,8 @@ import { Select } from 'antd' import { FC } from 'react' -import { SETTER_KEY, SetterTypeEnum } from '../constants' -import { BaseMRSetterOptions, BaseSetterProps, MRSetter } from '../types' +import { SetterTypeEnum } from '../constants' +import { BaseMRSetterOptions, BaseSetterProps } from '../types' interface SelectSetterOption { label: string diff --git a/packages/setters/src/components/string.tsx b/packages/setters/src/components/string.tsx index 518321f..a6e7ac2 100644 --- a/packages/setters/src/components/string.tsx +++ b/packages/setters/src/components/string.tsx @@ -1,6 +1,7 @@ -import { useDebounceFn, useMemoizedFn } from 'ahooks' -import { Input } from 'antd' -import { FC, useEffect, useState } from 'react' +import { isEmpty, isString } from 'lodash' +import { FC } from 'react' + +import { CodeEditor } from '@modou/code-editor' import { SetterTypeEnum } from '../constants' import { BaseMRSetterOptions, BaseSetterProps } from '../types' @@ -12,23 +13,50 @@ export interface MRStringSetterType extends BaseMRSetterOptions { type Props = BaseSetterProps -export const StringSetter: FC = ({ value, onChange, options }) => { - const Component = options?.textArea ? Input.TextArea : Input - const [realValue, setRealValue] = useState(value) - const { run } = useDebounceFn(onChange, { wait: 200 }) - const debounceSetValue = useMemoizedFn( - (valOrUpdater: ((currVal: any) => any) | any): void => { - setRealValue(valOrUpdater) - run(valOrUpdater) - }, - ) - useEffect(() => { - setRealValue(value) - }, [value]) +// export const StringSetter: FC = ({ value, onChange, options }) => { +// const Component = options?.textArea ? Input.TextArea : Input +// const [realValue, setRealValue] = useState(value) +// const { run } = useDebounceFn(onChange, { wait: 300 }) +// const debounceSetValue = useMemoizedFn( +// (valOrUpdater: ((currVal: any) => any) | any): void => { +// setRealValue(valOrUpdater) +// run(valOrUpdater) +// }, +// ) +// useEffect(() => { +// setRealValue(value) +// }, [value]) +// return ( +// debounceSetValue(val.target.value)} +// /> +// ) +// } + +export const StringSetter: FC = ({ + value, + onChange, + options, + getDataTreeDefs, +}) => { + // TODO 优化 array and object 默认值 + if (!isString(value)) { + if (isEmpty(value)) { + // why use ' ' not use '' + // ' ' is hack CodeEditor empty value line height + // TODO FIXME + value = ' ' + } else { + value = `{{${JSON.stringify(value)}}` + } + } return ( - debounceSetValue(val.target.value)} + // @ts-expect-error + ) } diff --git a/packages/setters/src/types/index.ts b/packages/setters/src/types/index.ts index 5626d29..af5bc87 100644 --- a/packages/setters/src/types/index.ts +++ b/packages/setters/src/types/index.ts @@ -1,4 +1,6 @@ -import { MRType } from '@modou/refine' +import { WidgetBaseProps } from '@modou/core' +import { WidgetFile, WidgetFileMeta } from '@modou/meta-vfs' +import { MDTernDefs, MRType } from '@modou/refine' import { SETTER_KEY, SetterTypeEnum } from '../constants' @@ -16,6 +18,12 @@ export interface BaseSetterProps { value: T onChange: (value: T) => void options?: O + getDataTreeDefs?: () => MDTernDefs +} + +export interface BaseWidgetSetterProps { + // TODO widget meta + widget: WidgetFile> } export interface BaseMRSetterOptions { diff --git a/packages/shared/package.json b/packages/shared/package.json new file mode 100644 index 0000000..9e7bec9 --- /dev/null +++ b/packages/shared/package.json @@ -0,0 +1,15 @@ +{ + "name": "@modou/shared", + "version": "1.0.0", + "description": "", + "main": "src", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "nanoid": "^4.0.1" + } +} diff --git a/packages/shared/src/dom/index.ts b/packages/shared/src/dom/index.ts new file mode 100644 index 0000000..3c68e12 --- /dev/null +++ b/packages/shared/src/dom/index.ts @@ -0,0 +1,15 @@ +// 获取DOM到窗口 X Y 轴距离 +export const getMonitorWall = (element: HTMLElement) => { + let ele: HTMLElement | null = element + let left = ele.offsetLeft + let top = ele.offsetTop + while (ele.offsetParent !== null) { + ele = ele.offsetParent as unknown as HTMLElement + left += ele.offsetLeft + top += ele.offsetTop + } + return { + left, + top, + } +} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts new file mode 100644 index 0000000..3a3c15a --- /dev/null +++ b/packages/shared/src/index.ts @@ -0,0 +1,8 @@ +import { customAlphabet } from 'nanoid' + +export * from './dom' + +const nanoid = customAlphabet('abcdefghigklmnopqrstuvwxyz', 12) +export const generateId = (size: number = 12): string => { + return nanoid(size) +} diff --git a/packages/state-manager/src/PageState/index.ts b/packages/state-manager/src/PageState/index.ts index dc7184a..dcfc09c 100644 --- a/packages/state-manager/src/PageState/index.ts +++ b/packages/state-manager/src/PageState/index.ts @@ -1,5 +1,7 @@ +import { isArray } from 'lodash' + import { PageFile } from '@modou/meta-vfs' -import { makeAutoObservable } from '@modou/reactivity' +import { computed, makeObservable, observable } from '@modou/reactivity' import { WidgetState } from '../WidgetState' @@ -10,7 +12,12 @@ export class PageState { id: file.meta.id, name: file.meta.name, } - makeAutoObservable(this) + makeObservable(this, { + file: observable, + state: observable, + subState: observable, + subWidgetNameState: computed.struct, + }) } file: PageFile @@ -20,9 +27,25 @@ export class PageState { name: string } + // TODO 组件销毁的时候删除subState subState: { - widget: Record + // TODO 动态定义WidgetState[][][]类型 + widget: Record } = { widget: {}, } + + get subWidgetNameState() { + return Object.values(this.subState.widget).reduce< + Record + >((prev, cur) => { + if (isArray(cur)) { + // TODO 暂时支持一级嵌套 v_i 代支持v_ri + prev[(cur[0] as unknown as WidgetState).file.meta.name] = cur + } else { + prev[cur.file.meta.name] = cur + } + return prev + }, {}) + } } diff --git a/packages/state-manager/src/StateManager/index.ts b/packages/state-manager/src/StateManager/index.ts deleted file mode 100644 index e88c91e..0000000 --- a/packages/state-manager/src/StateManager/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { makeAutoObservable } from '@modou/reactivity' - -import { AppState } from '../AppState' -import { PageState } from '../PageState' - -export class StateManager { - constructor({ - appState, - canvasState, - }: { - appState: AppState - canvasState: PageState - }) { - this.app = appState - this.canvas = canvasState - makeAutoObservable(this) - } - - app: AppState - canvas: PageState -} diff --git a/packages/state-manager/src/WidgetState/index.ts b/packages/state-manager/src/WidgetState/index.ts index eefeed0..d2e2eb0 100644 --- a/packages/state-manager/src/WidgetState/index.ts +++ b/packages/state-manager/src/WidgetState/index.ts @@ -1,8 +1,33 @@ -import { isFunction } from 'lodash' +import { flatten } from 'flat' +import { + get, + head, + isArray, + isFunction, + isNumber, + mapValues, + omit, + set, + take, + takeRight, + unset, +} from 'lodash' -import { AppFactory } from '@modou/core' +import { AppFactory } from '@modou/asset-vfs' +import { evalExpression } from '@modou/evaluate' import { WidgetFile } from '@modou/meta-vfs' -import { makeAutoObservable } from '@modou/reactivity' +import { + IReactionDisposer, + autorun, + makeAutoObservable, + reaction, + remove, + runInAction, + toJS, +} from '@modou/reactivity' + +import { PageState } from '../PageState' +import { WidgetVariables } from '../contexts' // TODO 完善类型定义 type BaseWidgetState = Record & { @@ -15,28 +40,185 @@ type BaseWidgetState = Record & { export class WidgetState { // TODO 定义 State 类型 & 提取 appFactory 到mobx // TODO 尝试 把 widgetDef 放到 file 上 - constructor(file: WidgetFile, appFactory: AppFactory) { + constructor( + file: WidgetFile, + { + appFactory, + widgetVariables, + canvasState, + }: { + appFactory: AppFactory + widgetVariables: WidgetVariables + canvasState: PageState + }, + ) { const widgetDef = appFactory.widgetByType[file.meta.type] this.file = file + this.widgetVariables = widgetVariables + this.canvasState = canvasState + const rawMeta = toJS(file.meta) + // TODO 这个地方就要 eval this.state = { - ...file.meta.props, - ...widgetDef.metadata.initState(file.meta), + ...rawMeta.props, + // TODO initState 传递的参数应该是初始化的state 或者 计算后的 props + ...widgetDef.metadata.initState(rawMeta), } as unknown as BaseWidgetState makeAutoObservable(this) + + let stopReaction: IReactionDisposer + let stopExps: IReactionDisposer[] = [] + // 监听vi变化 + const stopReactionVI = reaction( + () => this.widgetVariables.i, + (vi, oldValue) => { + stopReaction?.() + runInAction(() => { + // TODO 支持 v_ri + // ri [1,2,3,4,5] + // i 5 + if (isNumber(vi)) { + if (!isArray(canvasState.subState.widget[this.file.meta.id])) { + set(canvasState.subState.widget, [this.file.meta.id], []) + } + } + + if (isNumber(vi)) { + // @ts-expect-error + canvasState.subState.widget[this.file.meta.id][vi] = this + } else { + canvasState.subState.widget[this.file.meta.id] = this + } + }) + // 监听props变化 + stopReaction = reaction( + () => this.file.flattenedMetaValMap, + (value, prev) => { + // TODO 单个监听flattenedMetaValMap并判断是否已经监听和暂停被删除的监听 + // TODO 重新执行initState 并 diff + stopExps.forEach((stop) => stop()) + stopExps = [] + // TODO 需要更加准确的判断需要删除的数组路径 + // 处理 数组 类型的 state 在props删除后不会更新的问题 + // 比如 {columns:[{a:1},{a:2}]} => {columns:[{a:1}]} state 的第一项并不会删除 + const newValPaths = Object.keys(value) + const newValHeadPath = newValPaths.map((path) => + head(path.split('.')), + ) + const omitPaths = Object.keys(omit(prev, newValPaths)).filter( + (path) => !newValHeadPath.includes(path), + ) + runInAction(() => { + if (omitPaths.length > 0) { + // 处理 数组 类型的 state 在props删除后不会更新的问题 + // 比如 {columns:[{a:1},{a:2}]} => {columns:[{a:1}]} state 的第一项并不会删除 + omitPaths.forEach((path) => { + unset(this.state, path) + const pathArr = path.split('.') + // TODO 需要更加准确的判断需要删除的数组路径 + if (isNumber(+pathArr[pathArr.length - 2])) { + remove( + get(this.state, take(pathArr, pathArr.length - 2)), + head(takeRight(pathArr, 2)), + ) + } + }) + } + }) + // TODO 如果state是数组的时候如何删除多余的数组 + Object.entries(value).forEach(([path, val]) => { + if (val.type === 'Normal') { + if (prev?.[path]?.raw !== val.raw) { + runInAction(() => { + set(this.state, path, val.raw) + }) + } + } else { + stopExps.push( + // TODO Autorun 不靠谱 + autorun(() => { + const newVal = evalExpression(val.evalString, { + ...mapValues(canvasState.subWidgetNameState, (widget) => { + if (isArray(widget)) { + // TODO 支持v_ri + return widget.map( + (w) => (w as unknown as WidgetState).state, + ) + } else { + return widget.state + } + }), + ...widgetVariables, + }) + runInAction(() => { + set(this.state, path, newVal) + }) + }), + ) + } + // TODO 如果是异步的 expression 如何处理 + runInAction(() => { + this.state.instance.initialized = true + }) + }) + }, + { + fireImmediately: true, + }, + ) + }, + { fireImmediately: true }, + ) + this.disposer = () => { + runInAction(() => { + stopReactionVI() + stopReaction() + stopExps.forEach((stop) => stop()) + // stopExps = [] + // TODO 处理 v_i 和 v_ri + remove(canvasState.subState.widget, this.file.meta.id) + }) + } } file: WidgetFile + canvasState: PageState + // TODO 完善 Widget State 定义 state: BaseWidgetState - updateState( + widgetVariables: WidgetVariables + + updateState = ( state: BaseWidgetState | ((oldState: BaseWidgetState) => BaseWidgetState), - ) { + ) => { + let _state: BaseWidgetState if (isFunction(state)) { - this.state = state(this.state) + _state = state(this.state) } else { - this.state = state + _state = state } + + const fNewState: Record = flatten(_state, { safe: true }) + const fState: BaseWidgetState = flatten(this.state, { safe: true }) + // TODO 优化性能 + // 删除不存在的 props + Object.entries(fState).forEach(([path, value]) => { + if (!Reflect.has(fNewState, path)) { + Reflect.deleteProperty(fState, path) + } + }) + Object.entries(fNewState).forEach(([path, value]) => { + const oldValue = get(fState, path) + if (value !== oldValue) { + set(this.state, path, value) + } + }) } + + updateWidgetVariables = (widgetVariables: WidgetVariables) => { + this.widgetVariables = widgetVariables + } + + disposer: () => void } diff --git a/packages/state-manager/src/contexts/AppStateContext.tsx b/packages/state-manager/src/contexts/AppStateContext.tsx new file mode 100644 index 0000000..6151303 --- /dev/null +++ b/packages/state-manager/src/contexts/AppStateContext.tsx @@ -0,0 +1,20 @@ +import { FC, ReactNode, createContext, useContext } from 'react' + +import { AppState } from '../AppState' + +const AppStateContext = createContext(null) +export const AppStateProvider: FC<{ + value: AppState + children: ReactNode +}> = ({ value, children }) => { + return ( + + {children} + + ) +} + +export const useAppState = () => { + const appState = useContext(AppStateContext) as unknown as AppState + return { appState } +} diff --git a/packages/state-manager/src/contexts/CanvasStateContext.tsx b/packages/state-manager/src/contexts/CanvasStateContext.tsx new file mode 100644 index 0000000..864a295 --- /dev/null +++ b/packages/state-manager/src/contexts/CanvasStateContext.tsx @@ -0,0 +1,20 @@ +import { FC, ReactNode, createContext, useContext } from 'react' + +import { PageState } from '../PageState' + +const CanvasStateContext = createContext(null) +export const CanvasStateProvider: FC<{ + value: PageState + children: ReactNode +}> = ({ value, children }) => { + return ( + + {children} + + ) +} + +export const useCanvasState = () => { + const canvasState = useContext(CanvasStateContext) as unknown as PageState + return { canvasState } +} diff --git a/packages/state-manager/src/contexts/StateManagerContext.tsx b/packages/state-manager/src/contexts/StateManagerContext.tsx deleted file mode 100644 index 8d6e1a2..0000000 --- a/packages/state-manager/src/contexts/StateManagerContext.tsx +++ /dev/null @@ -1,26 +0,0 @@ -import { FC, ReactNode, createContext, useContext } from 'react' - -import { StateManager } from '../StateManager' - -const StateManagerContext = createContext(null) -export const StateManagerProvider: FC<{ - value: StateManager - children: ReactNode -}> = ({ value, children }) => { - return ( - - {children} - - ) -} - -export const useStateManager = () => { - const stateManager = useContext( - StateManagerContext, - ) as unknown as StateManager - return { - stateManager, - appState: stateManager.app, - canvasState: stateManager.canvas, - } -} diff --git a/packages/state-manager/src/contexts/WidgetVariablesContext.tsx b/packages/state-manager/src/contexts/WidgetVariablesContext.tsx new file mode 100644 index 0000000..e0f47d2 --- /dev/null +++ b/packages/state-manager/src/contexts/WidgetVariablesContext.tsx @@ -0,0 +1,27 @@ +import { FC, ReactNode, createContext, useContext } from 'react' + +export interface WidgetVariables { + i?: number + currentRow?: Record +} + +export const DEFAULT_WIDGET_VARIABLES: WidgetVariables = {} + +const WidgetVariablesContext = createContext({ + ...DEFAULT_WIDGET_VARIABLES, +}) +export const WidgetVariablesProvider: FC<{ + value: WidgetVariables + children: ReactNode +}> = ({ value, children }) => { + return ( + + {children} + + ) +} + +export const useWidgetVariables = () => { + const widgetVariables = useContext(WidgetVariablesContext) + return { widgetVariables } +} diff --git a/packages/state-manager/src/contexts/index.ts b/packages/state-manager/src/contexts/index.ts index 8fe0bb1..3d8649d 100644 --- a/packages/state-manager/src/contexts/index.ts +++ b/packages/state-manager/src/contexts/index.ts @@ -1 +1,3 @@ -export * from './StateManagerContext' +export * from './WidgetVariablesContext' +export * from './AppStateContext' +export * from './CanvasStateContext' diff --git a/packages/state-manager/src/index.ts b/packages/state-manager/src/index.ts index 9e9c101..396a472 100644 --- a/packages/state-manager/src/index.ts +++ b/packages/state-manager/src/index.ts @@ -1,4 +1,4 @@ export { AppState } from './AppState' export { PageState } from './PageState' export { WidgetState } from './WidgetState' -export { StateManager } from './StateManager' +export * from './contexts' diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9ec0c76..6578461 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,32 +9,38 @@ importers: .: specifiers: - '@ant-design/icons': ^4.8.0 - '@ant-design/pro-components': ^2.3.52 + '@ant-design/icons': ^5.0.1 + '@ant-design/pro-components': ^2.3.57 '@emotion/babel-plugin': ^11.10.5 + '@modou/asset-vfs': workspace:^1.0.0 + '@modou/code-editor': workspace:^1.0.0 '@modou/compile-core': workspace:^0.0.1 '@modou/core': workspace:^1.0.0 '@modou/css-in-js': workspace:^1.0.0 + '@modou/evaluate': workspace:^1.0.0 + '@modou/flow-nodes': workspace:^0.0.1 '@modou/meta-vfs': workspace:^0.0.1 '@modou/reactivity': workspace:^0.0.1 '@modou/reactivity-react': workspace:^0.0.1 '@modou/refine': workspace:^1.0.0 '@modou/render': workspace:^1.0.0 '@modou/setters': workspace:^1.0.0 + '@modou/shared': workspace:^1.0.0 '@modou/simulator': workspace:^1.0.0 + '@modou/state-manager': workspace:^0.0.1 '@modou/widgets-antd': workspace:^1.0.0 '@recoiljs/refine': ^0.1.1 '@trivago/prettier-plugin-sort-imports': ^3.3.1 '@types/flat': ^5.0.2 + '@types/json-schema': ^7.0.11 '@types/lodash': ^4.14.185 '@types/react': ^18.0.17 '@types/react-dom': ^18.0.6 '@vitejs/plugin-react': ^3.0.1 '@vue/compiler-core': ^3.2.45 - '@vue/reactivity': ^3.2.45 '@vue/runtime-core': ^3.2.45 ahooks: ^3.7.0 - antd: ^5.1.5 + antd: ^5.2.2 autoprefixer: ^10.4.8 axios: ^1.1.3 commitizen: 4.2.5 @@ -50,10 +56,12 @@ importers: eslint-plugin-react-hooks: ^4.6.0 flat: ^5.0.2 handlebars: ^4.7.7 - immer: ^9.0.15 - immutability-helper: ^3.1.1 + immer: ^9.0.19 + json-schema: ^0.4.0 lodash: ^4.17.21 + mitt: ^3.0.0 prettier: ^2.8.1 + quicktype-core: ^21.0.2 rc-tree: ^5.7.2 react: ^18.2.0 react-dnd: ^16.0.1 @@ -61,49 +69,57 @@ importers: react-dom: ^18.2.0 react-json-view: ^1.21.3 react-router-dom: ^6.3.0 - reactflow: ^11.3.0 + reactflow: ^11.5.1 rollup-plugin-visualizer: ^5.8.2 ts-pattern: ^4.0.5 typescript: ^4.7.4 vite: ^4.0.3 dependencies: - '@ant-design/icons': 4.8.0_biqbaboplfbrettd7655fr4n2y - '@ant-design/pro-components': 2.3.52_5kykwr4aqmxcvhhfl2j2qf3uya + '@ant-design/icons': 5.0.1_biqbaboplfbrettd7655fr4n2y + '@ant-design/pro-components': 2.3.57_xan5c2tkf4stwpqcwacropitz4 + '@modou/asset-vfs': link:packages/asset-vfs + '@modou/code-editor': link:packages/code-editor '@modou/compile-core': link:packages/compiler-core '@modou/core': link:packages/core '@modou/css-in-js': link:packages/css-in-js + '@modou/evaluate': link:packages/evaluate + '@modou/flow-nodes': link:packages/flow-nodes '@modou/meta-vfs': link:packages/meta-vfs '@modou/reactivity': link:packages/reactivity '@modou/reactivity-react': link:packages/reactivity-react '@modou/refine': link:packages/refine '@modou/render': link:packages/render '@modou/setters': link:packages/setters + '@modou/shared': link:packages/shared '@modou/simulator': link:packages/simulator + '@modou/state-manager': link:packages/state-manager '@modou/widgets-antd': link:widgets/antd '@recoiljs/refine': 0.1.1 '@vue/compiler-core': 3.2.45 - '@vue/reactivity': 3.2.45 '@vue/runtime-core': 3.2.45 ahooks: 3.7.1_react@18.2.0 - antd: 5.1.5_biqbaboplfbrettd7655fr4n2y + antd: 5.2.2_biqbaboplfbrettd7655fr4n2y axios: 1.1.3 flat: 5.0.2 handlebars: 4.7.7 - immer: 9.0.15 - immutability-helper: 3.1.1 + immer: 9.0.19 + json-schema: 0.4.0 lodash: 4.17.21 + mitt: 3.0.0 + quicktype-core: 21.0.2 react: 18.2.0 react-dnd: 16.0.1_iapumuv4e6jcjznwuxpf4tt22e react-dnd-html5-backend: 16.0.1_kfajotxmcqjtgrepibrwmj5zwu react-dom: 18.2.0_react@18.2.0 react-json-view: 1.21.3_rj7ozvcq3uehdlnj3cbwzbi5ce react-router-dom: 6.4.2_biqbaboplfbrettd7655fr4n2y - reactflow: 11.3.0_rekktpi253ghbdqoi5qfkp32ay + reactflow: 11.5.1_ytuas6axp6gvse4ghxb2libdjm ts-pattern: 4.0.5 devDependencies: '@emotion/babel-plugin': 11.10.5 '@trivago/prettier-plugin-sort-imports': 3.3.1_prettier@2.8.1 '@types/flat': 5.0.2 + '@types/json-schema': 7.0.11 '@types/lodash': 4.14.186 '@types/react': 18.0.21 '@types/react-dom': 18.0.6 @@ -126,6 +142,9 @@ importers: typescript: 4.8.4 vite: 4.0.3 + packages/asset-vfs: + specifiers: {} + packages/canvas-designer: specifiers: {} @@ -206,10 +225,7 @@ importers: stylelint: 14.16.1 packages/core: - specifiers: - nanoid: ^4.0.0 - dependencies: - nanoid: 4.0.0 + specifiers: {} packages/css-in-js: specifiers: @@ -219,6 +235,73 @@ importers: '@emotion/css': 11.10.0 '@emotion/react': 11.10.4 + packages/evaluate: + specifiers: {} + + packages/flow-designer: + specifiers: + '@commitlint/cli': ^17.1.2 + '@commitlint/config-conventional': ^17.1.0 + '@umijs/lint': ^4.0.0 + dumi: ^2.0.2 + eslint: ^8.23.0 + father: ^4.1.0 + husky: ^8.0.1 + lint-staged: ^13.0.3 + prettier: ^2.7.1 + prettier-plugin-organize-imports: ^3.0.0 + prettier-plugin-packagejson: ^2.2.18 + react: ^18.0.0 + react-dom: ^18.0.0 + stylelint: ^14.9.1 + devDependencies: + '@commitlint/cli': 17.3.0 + '@commitlint/config-conventional': 17.3.0 + '@umijs/lint': 4.0.40_tj73zugfbqmkftfxkbg3ziygai + dumi: 2.0.18_j6ognzfrot7iwqpseop24xzp7y + eslint: 8.25.0 + father: 4.1.1 + husky: 8.0.2 + lint-staged: 13.1.0 + prettier: 2.8.1 + prettier-plugin-organize-imports: 3.2.1_prettier@2.8.1 + prettier-plugin-packagejson: 2.3.0_prettier@2.8.1 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + stylelint: 14.16.1 + + packages/flow-nodes: + specifiers: + '@commitlint/cli': ^17.1.2 + '@commitlint/config-conventional': ^17.1.0 + '@umijs/lint': ^4.0.0 + dumi: ^2.0.2 + eslint: ^8.23.0 + father: ^4.1.0 + husky: ^8.0.1 + lint-staged: ^13.0.3 + prettier: ^2.7.1 + prettier-plugin-organize-imports: ^3.0.0 + prettier-plugin-packagejson: ^2.2.18 + react: ^18.0.0 + react-dom: ^18.0.0 + stylelint: ^14.9.1 + devDependencies: + '@commitlint/cli': 17.3.0 + '@commitlint/config-conventional': 17.3.0 + '@umijs/lint': 4.0.40_tj73zugfbqmkftfxkbg3ziygai + dumi: 2.0.18_j6ognzfrot7iwqpseop24xzp7y + eslint: 8.25.0 + father: 4.1.1 + husky: 8.0.2 + lint-staged: 13.1.0 + prettier: 2.8.1 + prettier-plugin-organize-imports: 3.2.1_prettier@2.8.1 + prettier-plugin-packagejson: 2.3.0_prettier@2.8.1 + react: 18.2.0 + react-dom: 18.2.0_react@18.2.0 + stylelint: 14.16.1 + packages/meta-vfs: specifiers: '@commitlint/cli': ^17.1.2 @@ -229,12 +312,15 @@ importers: father: ^4.1.0 husky: ^8.0.1 lint-staged: ^13.0.3 + mobx-persist-store: ^1.1.3 prettier: ^2.7.1 prettier-plugin-organize-imports: ^3.0.0 prettier-plugin-packagejson: ^2.2.18 react: ^18.0.0 react-dom: ^18.0.0 stylelint: ^14.9.1 + dependencies: + mobx-persist-store: 1.1.3 devDependencies: '@commitlint/cli': 17.3.0 '@commitlint/config-conventional': 17.3.0 @@ -329,17 +415,23 @@ importers: '@lowcode-modou/zod-to-json-schema': 0.0.19_@lowcode-modou+zod@0.0.14 packages/render: - specifiers: - '@modou/state-manager': workspace:^0.0.1 - dependencies: - '@modou/state-manager': link:../state-manager + specifiers: {} packages/setters: specifiers: {} + packages/shared: + specifiers: + nanoid: ^4.0.1 + dependencies: + nanoid: 4.0.1 + packages/simulator: specifiers: {} + packages/state-manager: + specifiers: {} + websites/runtime: specifiers: {} @@ -347,13 +439,13 @@ importers: specifiers: '@ahooksjs/use-url-state': ^3.5.0 '@modou/canvas-designer': workspace:^1.0.0 + '@modou/flow-designer': workspace:^0.0.1 mitt: ^3.0.0 - reactflow: ^11.2.0 dependencies: '@ahooksjs/use-url-state': 3.5.0 '@modou/canvas-designer': link:../../packages/canvas-designer + '@modou/flow-designer': link:../../packages/flow-designer mitt: 3.0.0 - reactflow: 11.2.0 widgets/antd: specifiers: @@ -396,17 +488,17 @@ packages: /@ant-design/colors/6.0.0: resolution: {integrity: sha512-qAZRvPzfdWHtfameEGP2Qvuf838NhergR35o+EuVyB5XvSA98xod5r4utvi4TJ3ywmevm290g9nsCG5MryrdWQ==} dependencies: - '@ctrl/tinycolor': 3.4.1 + '@ctrl/tinycolor': 3.6.0 dev: false /@ant-design/colors/7.0.0: resolution: {integrity: sha512-iVm/9PfGCbC0dSMBrz7oiEXZaaGH7ceU40OJEfKmyuzR9R5CRimJYPlRiFtMQGQcbNMea/ePcoIebi4ASGYXtg==} dependencies: - '@ctrl/tinycolor': 3.4.1 + '@ctrl/tinycolor': 3.6.0 dev: false - /@ant-design/cssinjs/1.5.0_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-auGB6HK318lJOFMMcXTlYZuNL3mrx0wPkoFzOG4GkzqlOfvdjkroAXj5tuaM8mAoPkFPGqXYewICu0REex1phw==} + /@ant-design/cssinjs/1.5.6_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-1S7LUPC9BMyQ/CUYgzfePJJwEfsbVHJe3Tpd9zhujTxRM/6LYpN9N4FTaPHVqpnPazm0S2vG0WBkh2T5Erwuug==} peerDependencies: react: '>=16.0.0' react-dom: '>=16.0.0' @@ -441,8 +533,8 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false - /@ant-design/icons/5.0.0_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-dPUlZtsIffBrDyFwM+tuO4PWi1DPgGgd10ASistPhLpr1WyssadfhChWs/mICVkCm5PaRm/foMkE1x3xw4OPZw==} + /@ant-design/icons/5.0.1_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-ZyF4ksXCcdtwA/1PLlnFLcF/q8/MhwxXhKHh4oCHDA4Ip+ZzAHoICtyp4wZWfiCVDP0yuz3HsjyvuldHFb3wjA==} engines: {node: '>=8'} peerDependencies: react: '>=16.0.0' @@ -457,17 +549,17 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false - /@ant-design/pro-card/2.1.10_5kykwr4aqmxcvhhfl2j2qf3uya: - resolution: {integrity: sha512-edajGd2/35w73tLC7yMZf2/8tWg4Wz9pmN6jQgFo+QIRXoaHd5zpeQaOMxZojeqzCxTH0Qt60YpeLp/S+ahupQ==} + /@ant-design/pro-card/2.1.14_xan5c2tkf4stwpqcwacropitz4: + resolution: {integrity: sha512-zIIxaP3tR8j2I6LyvslJBOUBMqQNar9a1KX/jXkYJVle72wz0l67BCW2NaGg9UwJMsCIkIuf24Dfcpy/YKpc+w==} peerDependencies: antd: '>=4.23.0' react: '>=16.9.0' dependencies: '@ant-design/icons': 4.8.0_biqbaboplfbrettd7655fr4n2y - '@ant-design/pro-provider': 2.4.0_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-utils': 2.5.4_5kykwr4aqmxcvhhfl2j2qf3uya + '@ant-design/pro-provider': 2.4.3_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-utils': 2.5.8_xan5c2tkf4stwpqcwacropitz4 '@babel/runtime': 7.20.7 - antd: 5.1.5_biqbaboplfbrettd7655fr4n2y + antd: 5.2.2_biqbaboplfbrettd7655fr4n2y classnames: 2.3.2 omit.js: 2.0.2 rc-resize-observer: 1.2.0_biqbaboplfbrettd7655fr4n2y @@ -477,25 +569,25 @@ packages: - react-dom dev: false - /@ant-design/pro-components/2.3.52_5kykwr4aqmxcvhhfl2j2qf3uya: - resolution: {integrity: sha512-df51Xi59QRXXIc5uMcO/7/tPjNziGt1w40860yCztFBNCoNc9B+xvDwsuRWgvRL1yqM4SZL1Al7T392CtyESSg==} + /@ant-design/pro-components/2.3.57_xan5c2tkf4stwpqcwacropitz4: + resolution: {integrity: sha512-zXHAToBHjHQLrORO4reqknDcMeM+7Vgtqwb40AS+ZUD8uo3s+l8+YiV+BoxZDMbmxsQ0VzANU3610ZmSqUMkqg==} peerDependencies: antd: '>=4.23.0' react: '>=16.9.0' react-dom: '>=16.9.0' dependencies: - '@ant-design/pro-card': 2.1.10_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-descriptions': 2.0.44_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-field': 2.2.5_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-form': 2.5.4_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-layout': 7.6.1_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-list': 2.0.45_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-provider': 2.4.0_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-skeleton': 2.0.7_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-table': 3.2.12_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-utils': 2.5.4_5kykwr4aqmxcvhhfl2j2qf3uya + '@ant-design/pro-card': 2.1.14_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-descriptions': 2.0.48_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-field': 2.2.9_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-form': 2.5.8_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-layout': 7.8.2_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-list': 2.1.1_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-provider': 2.4.3_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-skeleton': 2.0.8_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-table': 3.3.1_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-utils': 2.5.8_xan5c2tkf4stwpqcwacropitz4 '@babel/runtime': 7.20.7 - antd: 5.1.5_biqbaboplfbrettd7655fr4n2y + antd: 5.2.2_biqbaboplfbrettd7655fr4n2y react: 18.2.0 react-dom: 18.2.0_react@18.2.0 transitivePeerDependencies: @@ -503,18 +595,18 @@ packages: - rc-field-form dev: false - /@ant-design/pro-descriptions/2.0.44_5kykwr4aqmxcvhhfl2j2qf3uya: - resolution: {integrity: sha512-UEwZbvjOBTxWvPGv8qb3i+Q5czuHFYwRokSgVzVyS2el6xLYr4ZjG0a2CQsjbBnIb0MvZCfnkIn+W0XD/bjS3g==} + /@ant-design/pro-descriptions/2.0.48_xan5c2tkf4stwpqcwacropitz4: + resolution: {integrity: sha512-Y5edlJlnvRWF/8BqIcmJP3xSCvi1UT7uMDjFLdHhcIfWJsBgYfdLq7wupY/pN3rRJ0jCvVJUpVRMotdzAuK8ig==} peerDependencies: antd: '>=4.23.0' react: '>=16.9.0' dependencies: - '@ant-design/pro-field': 2.2.5_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-form': 2.5.4_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-skeleton': 2.0.7_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-utils': 2.5.4_5kykwr4aqmxcvhhfl2j2qf3uya + '@ant-design/pro-field': 2.2.9_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-form': 2.5.8_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-skeleton': 2.0.8_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-utils': 2.5.8_xan5c2tkf4stwpqcwacropitz4 '@babel/runtime': 7.20.7 - antd: 5.1.5_biqbaboplfbrettd7655fr4n2y + antd: 5.2.2_biqbaboplfbrettd7655fr4n2y rc-util: 5.27.1_biqbaboplfbrettd7655fr4n2y react: 18.2.0 use-json-comparison: 1.0.6_react@18.2.0 @@ -524,18 +616,18 @@ packages: - react-dom dev: false - /@ant-design/pro-field/2.2.5_5kykwr4aqmxcvhhfl2j2qf3uya: - resolution: {integrity: sha512-7Rp0uRhdCUnOFKXkId5wCjjQJBWo0sMhOQs10y40/hNpGpTEpVuEmJo2Jx4Sj2hj/g663WOUSF0YBK7PhFtTFA==} + /@ant-design/pro-field/2.2.9_xan5c2tkf4stwpqcwacropitz4: + resolution: {integrity: sha512-Rl9QcdgbQUOfXmpwI1ln4HqxYI4rxIzcyglAPw9wzAfj3hMPglDDy6VzsaSk/bCWAm49rJcRiwyOIp4vuYXOZg==} peerDependencies: antd: '>=4.23.0' react: '>=16.9.0' dependencies: '@ant-design/icons': 4.8.0_biqbaboplfbrettd7655fr4n2y - '@ant-design/pro-provider': 2.4.0_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-utils': 2.5.4_5kykwr4aqmxcvhhfl2j2qf3uya + '@ant-design/pro-provider': 2.4.3_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-utils': 2.5.8_xan5c2tkf4stwpqcwacropitz4 '@babel/runtime': 7.20.7 '@chenshuai2144/sketch-color': 1.0.8_react@18.2.0 - antd: 5.1.5_biqbaboplfbrettd7655fr4n2y + antd: 5.2.2_biqbaboplfbrettd7655fr4n2y classnames: 2.3.2 dayjs: 1.11.5 lodash.tonumber: 4.0.3 @@ -547,8 +639,8 @@ packages: - react-dom dev: false - /@ant-design/pro-form/2.5.4_5kykwr4aqmxcvhhfl2j2qf3uya: - resolution: {integrity: sha512-Udzs2BLLHK9xhryB0GqE4FvC0KVjiD88z0NqSkJT1AfFMbBn5ERUh2TJ87vfSc8CS4UPGGeU868YzXVMR9AkbA==} + /@ant-design/pro-form/2.5.8_xan5c2tkf4stwpqcwacropitz4: + resolution: {integrity: sha512-iBxb7vWOPjlkpfZjZHKnVlgA1ebE7yjuqkXJSQ9AYW2HB1UfXVYcI7xgrj0nkx6EdtL1sf3Rhl2OwKYpRIB9iQ==} peerDependencies: '@types/lodash.merge': ^4.6.7 antd: '>=4.23.0' @@ -560,12 +652,13 @@ packages: optional: true dependencies: '@ant-design/icons': 4.8.0_biqbaboplfbrettd7655fr4n2y - '@ant-design/pro-field': 2.2.5_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-provider': 2.4.0_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-utils': 2.5.4_5kykwr4aqmxcvhhfl2j2qf3uya + '@ant-design/pro-field': 2.2.9_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-provider': 2.4.3_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-utils': 2.5.8_xan5c2tkf4stwpqcwacropitz4 '@babel/runtime': 7.20.7 + '@chenshuai2144/sketch-color': 1.0.8_react@18.2.0 '@umijs/use-params': 1.0.9_react@18.2.0 - antd: 5.1.5_biqbaboplfbrettd7655fr4n2y + antd: 5.2.2_biqbaboplfbrettd7655fr4n2y classnames: 2.3.2 lodash.merge: 4.6.2 omit.js: 2.0.2 @@ -577,20 +670,20 @@ packages: use-media-antd-query: 1.1.0_react@18.2.0 dev: false - /@ant-design/pro-layout/7.6.1_5kykwr4aqmxcvhhfl2j2qf3uya: - resolution: {integrity: sha512-MNmOALRmVbAUbsbJSmUdXyKgvxyTG1pp9YYwway5AqKz28PdfDp6HhqrvCE7zRnxIaJWv9Vb6IZ/XqstI5zDeg==} + /@ant-design/pro-layout/7.8.2_xan5c2tkf4stwpqcwacropitz4: + resolution: {integrity: sha512-SvFNyFCu+Ar6EqVBFAdDKdgHTzJonRFTtf+/opr3zJ46jFFoiuxE5qaJqmtOM+Plrvt272yPWUs/yN6R31t6CA==} peerDependencies: antd: '>=4.23.0' react: '>=16.9.0' react-dom: '>=16.9.0' dependencies: '@ant-design/icons': 4.8.0_biqbaboplfbrettd7655fr4n2y - '@ant-design/pro-provider': 2.4.0_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-utils': 2.5.4_5kykwr4aqmxcvhhfl2j2qf3uya + '@ant-design/pro-provider': 2.4.3_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-utils': 2.5.8_xan5c2tkf4stwpqcwacropitz4 '@babel/runtime': 7.20.7 '@umijs/route-utils': 4.0.1 '@umijs/use-params': 1.0.9_react@18.2.0 - antd: 5.1.5_biqbaboplfbrettd7655fr4n2y + antd: 5.2.2_biqbaboplfbrettd7655fr4n2y classnames: 2.3.2 lodash.merge: 4.6.2 omit.js: 2.0.2 @@ -600,71 +693,69 @@ packages: react: 18.2.0 react-dom: 18.2.0_react@18.2.0 swr: 2.0.0_react@18.2.0 - unstated-next: 1.1.0 use-json-comparison: 1.0.6_react@18.2.0 use-media-antd-query: 1.1.0_react@18.2.0 warning: 4.0.3 dev: false - /@ant-design/pro-list/2.0.45_5kykwr4aqmxcvhhfl2j2qf3uya: - resolution: {integrity: sha512-jGcTqL+ziAKZ71NERmbfFST+BoOIyVy6NQNDvTNa2V/r2ZYR+eLStEcRfTp0MyWaWillXJqBIi3jNhDUIXWVhg==} + /@ant-design/pro-list/2.1.1_xan5c2tkf4stwpqcwacropitz4: + resolution: {integrity: sha512-afOlH+Kms4eZzUvmA38lGsqobg+PXHqZtBducfgAJ+QgLxBu172TLKuG5wioTdMCm2DSxIKQ3+rQn0O9ZBdDqw==} peerDependencies: antd: '>=4.23.0' react: '>=16.9.0' react-dom: '>=16.9.0' dependencies: '@ant-design/icons': 4.8.0_biqbaboplfbrettd7655fr4n2y - '@ant-design/pro-card': 2.1.10_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-field': 2.2.5_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-table': 3.2.12_5kykwr4aqmxcvhhfl2j2qf3uya + '@ant-design/pro-card': 2.1.14_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-field': 2.2.9_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-table': 3.3.1_xan5c2tkf4stwpqcwacropitz4 '@babel/runtime': 7.20.7 - antd: 5.1.5_biqbaboplfbrettd7655fr4n2y + antd: 5.2.2_biqbaboplfbrettd7655fr4n2y classnames: 2.3.2 dayjs: 1.11.5 rc-resize-observer: 1.2.0_biqbaboplfbrettd7655fr4n2y rc-util: 4.21.1 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 - unstated-next: 1.1.0 use-media-antd-query: 1.1.0_react@18.2.0 transitivePeerDependencies: - '@types/lodash.merge' - rc-field-form dev: false - /@ant-design/pro-provider/2.4.0_5kykwr4aqmxcvhhfl2j2qf3uya: - resolution: {integrity: sha512-toz3F7moI6o99DCaEJRQv/jYrQXYlxiM3RpTb5L+zXYwc4hFxfv8Jj2Qm6XMX0DRZgCsDt5KK7R+9c9LzoNxbg==} + /@ant-design/pro-provider/2.4.3_xan5c2tkf4stwpqcwacropitz4: + resolution: {integrity: sha512-BefEAY9XUE1U5SxLAzA9se4e2VOykMRDyev+cGzz4bewdon/sbX82dcw300rl1n5IB9r+K3wYCzFXX+KaneFSA==} peerDependencies: antd: '>=4.23.0' react: '>=16.9.0' react-dom: '>=16.9.0' dependencies: - '@ant-design/cssinjs': 1.5.0_biqbaboplfbrettd7655fr4n2y + '@ant-design/cssinjs': 1.5.6_biqbaboplfbrettd7655fr4n2y '@babel/runtime': 7.20.7 - '@ctrl/tinycolor': 3.4.1 - antd: 5.1.5_biqbaboplfbrettd7655fr4n2y + '@ctrl/tinycolor': 3.6.0 + antd: 5.2.2_biqbaboplfbrettd7655fr4n2y rc-util: 5.27.1_biqbaboplfbrettd7655fr4n2y react: 18.2.0 react-dom: 18.2.0_react@18.2.0 swr: 2.0.0_react@18.2.0 dev: false - /@ant-design/pro-skeleton/2.0.7_5kykwr4aqmxcvhhfl2j2qf3uya: - resolution: {integrity: sha512-7RoCUVgWDTGrWeNuFlscsxrrXc5jshYXk6gjkQKospR0hQ1AK/aWlkttAAaqDFZlvPxSyLu8w11TmquRuYcNcg==} + /@ant-design/pro-skeleton/2.0.8_xan5c2tkf4stwpqcwacropitz4: + resolution: {integrity: sha512-Rb5Fpd6s1q5EemdO43o4w/Eab7ze6Gd3mOdMPHAp2dK5qm3Wj6Qa6Dr94zcsHom2VaZfaPVTatatHxdR+7byOQ==} peerDependencies: antd: '>=4.23.0' react: '>=16.9.0' react-dom: '>=16.9.0' dependencies: '@babel/runtime': 7.20.7 - antd: 5.1.5_biqbaboplfbrettd7655fr4n2y + antd: 5.2.2_biqbaboplfbrettd7655fr4n2y react: 18.2.0 react-dom: 18.2.0_react@18.2.0 use-media-antd-query: 1.1.0_react@18.2.0 dev: false - /@ant-design/pro-table/3.2.12_5kykwr4aqmxcvhhfl2j2qf3uya: - resolution: {integrity: sha512-jhfKBtU5DnOlj0eL/Tz3D5j9DZt6KeWCcaJOA/FDr/ZTj5ROUThhK5DqzeLjR9O/DTBCgiwB8Vpiqt0uWPWlXw==} + /@ant-design/pro-table/3.3.1_xan5c2tkf4stwpqcwacropitz4: + resolution: {integrity: sha512-NQak8whJklHUrJlQMs6z2nQ9qPdrDXEbJWG/btxE6kFcfANydLRCdsHVmJADZ7NWv+ZvnAiXAzaR1pJeuZxu5A==} peerDependencies: antd: '>=4.23.0' rc-field-form: ^1.22.0 @@ -672,13 +763,13 @@ packages: react-dom: '>=16.9.0' dependencies: '@ant-design/icons': 4.8.0_biqbaboplfbrettd7655fr4n2y - '@ant-design/pro-card': 2.1.10_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-field': 2.2.5_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-form': 2.5.4_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-provider': 2.4.0_5kykwr4aqmxcvhhfl2j2qf3uya - '@ant-design/pro-utils': 2.5.4_5kykwr4aqmxcvhhfl2j2qf3uya + '@ant-design/pro-card': 2.1.14_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-field': 2.2.9_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-form': 2.5.8_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-provider': 2.4.3_xan5c2tkf4stwpqcwacropitz4 + '@ant-design/pro-utils': 2.5.8_xan5c2tkf4stwpqcwacropitz4 '@babel/runtime': 7.20.7 - antd: 5.1.5_biqbaboplfbrettd7655fr4n2y + antd: 5.2.2_biqbaboplfbrettd7655fr4n2y classnames: 2.3.2 dayjs: 1.11.5 omit.js: 2.0.2 @@ -687,23 +778,22 @@ packages: react: 18.2.0 react-dom: 18.2.0_react@18.2.0 react-sortable-hoc: 2.0.0_biqbaboplfbrettd7655fr4n2y - unstated-next: 1.1.0 use-json-comparison: 1.0.6_react@18.2.0 transitivePeerDependencies: - '@types/lodash.merge' dev: false - /@ant-design/pro-utils/2.5.4_5kykwr4aqmxcvhhfl2j2qf3uya: - resolution: {integrity: sha512-fwnGRcYQbJwIyTWvioCw443qJ2Jq/TdkR/LFoewJqQK8zeZ7I62pEgLqxK0l0sffZpN1cKAIQfydqvcT3kl+mQ==} + /@ant-design/pro-utils/2.5.8_xan5c2tkf4stwpqcwacropitz4: + resolution: {integrity: sha512-iAiNjKBj8chgDsHIn/d757RFEhRLhBPT4iSH5MI7k+hZTHbEMsjYjNgtR9Du5doIP+ACe62hcuELTr6aXt80nQ==} peerDependencies: antd: '>=4.23.0' react: '>=16.9.0' react-dom: '>=16.9.0' dependencies: '@ant-design/icons': 4.8.0_biqbaboplfbrettd7655fr4n2y - '@ant-design/pro-provider': 2.4.0_5kykwr4aqmxcvhhfl2j2qf3uya + '@ant-design/pro-provider': 2.4.3_xan5c2tkf4stwpqcwacropitz4 '@babel/runtime': 7.20.7 - antd: 5.1.5_biqbaboplfbrettd7655fr4n2y + antd: 5.2.2_biqbaboplfbrettd7655fr4n2y classnames: 2.3.2 dayjs: 1.11.5 rc-util: 5.27.1_biqbaboplfbrettd7655fr4n2y @@ -1784,8 +1874,8 @@ packages: postcss-selector-parser: 6.0.11 dev: true - /@ctrl/tinycolor/3.4.1: - resolution: {integrity: sha512-ej5oVy6lykXsvieQtqZxCOaLT+xD4+QNarq78cIYISHmZXshCvROLudpQN3lfL8G0NL7plMSSK+zlyvCaIJ4Iw==} + /@ctrl/tinycolor/3.6.0: + resolution: {integrity: sha512-/Z3l6pXthq0JvMYdUFyX9j0MaCltlIn6mfh9jLyQwg5aPKxkyNa0PTHtU1AlFXLNk55ZuAeJRcpvq+tmLfKmaQ==} engines: {node: '>=10'} dev: false @@ -2287,6 +2377,10 @@ packages: tslib: 2.4.0 dev: true + /@glideapps/ts-necessities/2.1.2: + resolution: {integrity: sha512-tLjfhinr6doUBcWi7BWnkT2zT6G5UhiZftsiIH6xVvykeXE+FU7Wr0MyqwmqideWlDD5rG+VjVLptLviGo04CA==} + dev: false + /@humanwhocodes/config-array/0.10.7: resolution: {integrity: sha512-MDl6D6sBsaV452/QSdX+4CXIjZhIcI0PELsxUjk4U828yd58vk3bTIvk/6w5FY+4hIy9sLW0sfrV7K7Kc++j/w==} engines: {node: '>=10.10.0'} @@ -2765,8 +2859,8 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false - /@rc-component/tour/1.1.0_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-Cy45VnNEDq6DLF5eKonIflObDfofbPq7AJpSf18qLN+j9+wW+sNlRv3JnCMDUsCdhSlnM4+yJ1RMokKp9GCpOQ==} + /@rc-component/tour/1.6.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-b/s7LCb7bW4wxpWfZyNpl7khHUzSyObSlsLaIScRGd+W/v1wFVk8F7gRytl/z8ik9ZSXbLWx9EvexIuHoO/RcQ==} engines: {node: '>=8.x'} peerDependencies: react: '>=16.9.0' @@ -2793,57 +2887,28 @@ packages: resolution: {integrity: sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA==} dev: false - /@reactflow/background/11.0.4: - resolution: {integrity: sha512-AACdU1B63BJCL0mO6I2ceehkv0ijKxzhh4UMc0JQs8sSgbAQjvnMSt/VcjIJ1e8V4o/wjPejj3xWm2Jxsv0Qhw==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - dependencies: - '@babel/runtime': 7.20.7 - '@reactflow/core': 11.2.0 - classcat: 5.0.4 - zustand: 4.1.1 - transitivePeerDependencies: - - immer - dev: false - - /@reactflow/background/11.0.5_rekktpi253ghbdqoi5qfkp32ay: - resolution: {integrity: sha512-rQKqD3gCjhsy6tuKkRrSuCgZj66tdfdX6t58d73+84+lud1O3f+vj/JfFZC9ee5ZY4R/5kVzOpKOAPaGQZmhig==} + /@reactflow/background/11.1.3_ytuas6axp6gvse4ghxb2libdjm: + resolution: {integrity: sha512-U5h43dobrdYy+PdczS3wZRjDphjYemH4VPKZoi5HCeWsevqHqFSmQE+GC8RNXmWxYObqUA9EGSWlfCtWSg0HKQ==} peerDependencies: react: '>=17' react-dom: '>=17' dependencies: - '@babel/runtime': 7.20.7 - '@reactflow/core': 11.3.0_rekktpi253ghbdqoi5qfkp32ay + '@reactflow/core': 11.5.0_ytuas6axp6gvse4ghxb2libdjm classcat: 5.0.4 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 - zustand: 4.1.1_immer@9.0.15+react@18.2.0 - transitivePeerDependencies: - - immer - dev: false - - /@reactflow/controls/11.0.4: - resolution: {integrity: sha512-NLycgcW+hS2CcZiUqvTOXkug+UMmq7dzORxdzxosvBI0b1La+xNXi+UxVMlMAYrNZH3lm+SWyftksXQ/r010bQ==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - dependencies: - '@babel/runtime': 7.20.7 - '@reactflow/core': 11.2.0 - classcat: 5.0.4 + zustand: 4.3.2_immer@9.0.19+react@18.2.0 transitivePeerDependencies: - immer dev: false - /@reactflow/controls/11.0.5_rekktpi253ghbdqoi5qfkp32ay: - resolution: {integrity: sha512-a8rWtFnEZk7HyWbaWWjsaFssr5q++p9iCKVzuvYUuXFveJwTofRdkjq2nKlrszrMJLNeNZwDi9mYIwVqqds9aQ==} + /@reactflow/controls/11.1.3_ytuas6axp6gvse4ghxb2libdjm: + resolution: {integrity: sha512-lqh8Nymr+YzqhzjdXg5MhqhP2cZF5w+MErtB9aBUjAn/8zJbEz1bqzFxfmo/xFugtrK2ujW4HBix15zjv/6gzQ==} peerDependencies: react: '>=17' react-dom: '>=17' dependencies: - '@babel/runtime': 7.20.7 - '@reactflow/core': 11.3.0_rekktpi253ghbdqoi5qfkp32ay + '@reactflow/core': 11.5.0_ytuas6axp6gvse4ghxb2libdjm classcat: 5.0.4 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 @@ -2851,27 +2916,8 @@ packages: - immer dev: false - /@reactflow/core/11.2.0: - resolution: {integrity: sha512-8rYp9bQON7MMtHZ3opU37Bfp4NaGGrJFeu+tcokz9bQZcGo43oCKl8xkURn8HlC8FnpVwpQ7e3DBdsOtnEbaEg==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - dependencies: - '@types/d3': 7.4.0 - '@types/d3-drag': 3.0.1 - '@types/d3-selection': 3.0.3 - '@types/d3-zoom': 3.0.1 - classcat: 5.0.4 - d3-drag: 3.0.0 - d3-selection: 3.0.0 - d3-zoom: 3.0.0 - zustand: 4.1.1 - transitivePeerDependencies: - - immer - dev: false - - /@reactflow/core/11.3.0_rekktpi253ghbdqoi5qfkp32ay: - resolution: {integrity: sha512-RmxP/Vy7aImu4WfFYcvG25zWJirriMupaz25qX+75jbtakTKZHCTurA7jI6NgubUY1OlwcXqG9surCQgSu6+hQ==} + /@reactflow/core/11.5.0_ytuas6axp6gvse4ghxb2libdjm: + resolution: {integrity: sha512-3IhqgeuomNYyurgBRB+08+xdS2pjQfYDWVAuJhNW6U4sR0BHRPzPuAhztNXPNpTFk2K75revFB3/uPJ/aZblcw==} peerDependencies: react: '>=17' react-dom: '>=17' @@ -2886,37 +2932,18 @@ packages: d3-zoom: 3.0.0 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 - zustand: 4.1.1_immer@9.0.15+react@18.2.0 - transitivePeerDependencies: - - immer - dev: false - - /@reactflow/minimap/11.1.0: - resolution: {integrity: sha512-igquYRc9QdV5hu7KZiBjQgynvy/oMH5+i5eDvI0aLSkLC68NVnHFFpEZlk4bSVav+nGeWbHOZK/tryd3Sa39TQ==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - dependencies: - '@babel/runtime': 7.20.7 - '@reactflow/core': 11.2.0 - '@types/d3-selection': 3.0.3 - '@types/d3-zoom': 3.0.1 - classcat: 5.0.4 - d3-selection: 3.0.0 - d3-zoom: 3.0.0 - zustand: 4.1.1 + zustand: 4.3.2_immer@9.0.19+react@18.2.0 transitivePeerDependencies: - immer dev: false - /@reactflow/minimap/11.2.0_rekktpi253ghbdqoi5qfkp32ay: - resolution: {integrity: sha512-CORkxIVI+LNOk1fHRPSwiBFBXglt6gfHU+V5hBzF8ZsOhs0gxgHDTw+2ktvMsRkSKnnT58uPXEvdseksVonI2g==} + /@reactflow/minimap/11.3.3_ytuas6axp6gvse4ghxb2libdjm: + resolution: {integrity: sha512-EHyiWglFO/CMSERmkVRKtC2kKHWBYrOg2naXP5WD2rh24gm73B8Al1PJ7INuiaQHAEbS1iOw/uKi0up4spu8Ww==} peerDependencies: react: '>=17' react-dom: '>=17' dependencies: - '@babel/runtime': 7.20.7 - '@reactflow/core': 11.3.0_rekktpi253ghbdqoi5qfkp32ay + '@reactflow/core': 11.5.0_ytuas6axp6gvse4ghxb2libdjm '@types/d3-selection': 3.0.3 '@types/d3-zoom': 3.0.1 classcat: 5.0.4 @@ -2924,23 +2951,22 @@ packages: d3-zoom: 3.0.0 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 - zustand: 4.1.1_immer@9.0.15+react@18.2.0 + zustand: 4.3.2_immer@9.0.19+react@18.2.0 transitivePeerDependencies: - immer dev: false - /@reactflow/node-toolbar/1.0.0_rekktpi253ghbdqoi5qfkp32ay: - resolution: {integrity: sha512-UP/X7Y9rfPVDEipOPP/MXWOK02+Yzuo3PIEPFDebDtUPDLu5EKeTENKAgpihy/YG5X2Bi2eh5XRs0bc3CsmRiw==} + /@reactflow/node-toolbar/1.1.3_ytuas6axp6gvse4ghxb2libdjm: + resolution: {integrity: sha512-D45OGjyo9F8kjKe66RS/qIsipEezlVn1dWGLsYZBkL3K/1zw/aSK/StJHdHLhonWRnDwPZyrcfCu9wbpZxDZlQ==} peerDependencies: react: '>=17' react-dom: '>=17' dependencies: - '@babel/runtime': 7.20.7 - '@reactflow/core': 11.3.0_rekktpi253ghbdqoi5qfkp32ay + '@reactflow/core': 11.5.0_ytuas6axp6gvse4ghxb2libdjm classcat: 5.0.4 react: 18.2.0 react-dom: 18.2.0_react@18.2.0 - zustand: 4.1.1_immer@9.0.15+react@18.2.0 + zustand: 4.3.2_immer@9.0.19+react@18.2.0 transitivePeerDependencies: - immer dev: false @@ -3730,6 +3756,10 @@ packages: resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} dev: true + /@types/urijs/1.19.19: + resolution: {integrity: sha512-FDJNkyhmKLw7uEvTxx5tSXfPeQpO0iy73Ry+PmYZJvQy0QIWX8a7kJ4kLWRf+EbTPJEPDSgPXHaM7pzr5lmvCg==} + dev: false + /@types/yargs-parser/21.0.0: resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} dev: true @@ -4899,6 +4929,13 @@ packages: through: 2.3.8 dev: true + /abort-controller/3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + dependencies: + event-target-shim: 5.0.1 + dev: false + /acorn-import-assertions/1.8.0_acorn@8.8.0: resolution: {integrity: sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==} peerDependencies: @@ -5101,36 +5138,36 @@ packages: engines: {node: '>=12'} dev: true - /antd/5.1.5_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-f1gfmDG8CApTu1h6gtM7i/krMTP6WH55w7pUPAMHlp9jsviKR+ElPqSQeLZV3UjuxVNn0DWvGwWEJ0Rv/kbeEQ==} + /antd/5.2.2_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-IHuMyGvgI+pPmoZNJFfhVykb0eW6+iO/39/9qPE0ub9gKAnI4vaK+hA5li6aX+mjM/EqgckdWgLpaIP98kGrvw==} peerDependencies: react: '>=16.9.0' react-dom: '>=16.9.0' dependencies: '@ant-design/colors': 7.0.0 - '@ant-design/cssinjs': 1.5.0_biqbaboplfbrettd7655fr4n2y - '@ant-design/icons': 5.0.0_biqbaboplfbrettd7655fr4n2y + '@ant-design/cssinjs': 1.5.6_biqbaboplfbrettd7655fr4n2y + '@ant-design/icons': 5.0.1_biqbaboplfbrettd7655fr4n2y '@ant-design/react-slick': 1.0.0_react@18.2.0 '@babel/runtime': 7.20.7 - '@ctrl/tinycolor': 3.4.1 + '@ctrl/tinycolor': 3.6.0 '@rc-component/mutate-observer': 1.0.0_biqbaboplfbrettd7655fr4n2y - '@rc-component/tour': 1.1.0_biqbaboplfbrettd7655fr4n2y + '@rc-component/tour': 1.6.0_biqbaboplfbrettd7655fr4n2y classnames: 2.3.2 copy-to-clipboard: 3.3.2 dayjs: 1.11.5 qrcode.react: 3.1.0_react@18.2.0 rc-cascader: 3.8.0_biqbaboplfbrettd7655fr4n2y rc-checkbox: 2.3.2_biqbaboplfbrettd7655fr4n2y - rc-collapse: 3.4.2_biqbaboplfbrettd7655fr4n2y + rc-collapse: 3.5.2_biqbaboplfbrettd7655fr4n2y rc-dialog: 9.0.2_biqbaboplfbrettd7655fr4n2y rc-drawer: 6.1.2_biqbaboplfbrettd7655fr4n2y rc-dropdown: 4.0.1_biqbaboplfbrettd7655fr4n2y rc-field-form: 1.27.2_biqbaboplfbrettd7655fr4n2y rc-image: 5.13.0_biqbaboplfbrettd7655fr4n2y - rc-input: 0.1.4_biqbaboplfbrettd7655fr4n2y + rc-input: 0.2.2_biqbaboplfbrettd7655fr4n2y rc-input-number: 7.4.0_biqbaboplfbrettd7655fr4n2y - rc-mentions: 1.13.1_biqbaboplfbrettd7655fr4n2y - rc-menu: 9.8.0_biqbaboplfbrettd7655fr4n2y + rc-mentions: 2.0.0_biqbaboplfbrettd7655fr4n2y + rc-menu: 9.8.2_biqbaboplfbrettd7655fr4n2y rc-motion: 2.6.2_biqbaboplfbrettd7655fr4n2y rc-notification: 5.0.0_biqbaboplfbrettd7655fr4n2y rc-pagination: 3.2.0_biqbaboplfbrettd7655fr4n2y @@ -5138,15 +5175,15 @@ packages: rc-progress: 3.4.1_biqbaboplfbrettd7655fr4n2y rc-rate: 2.9.2_biqbaboplfbrettd7655fr4n2y rc-resize-observer: 1.2.0_biqbaboplfbrettd7655fr4n2y - rc-segmented: 2.1.0_biqbaboplfbrettd7655fr4n2y + rc-segmented: 2.1.2_biqbaboplfbrettd7655fr4n2y rc-select: 14.2.0_biqbaboplfbrettd7655fr4n2y - rc-slider: 10.0.1_biqbaboplfbrettd7655fr4n2y + rc-slider: 10.1.1_biqbaboplfbrettd7655fr4n2y rc-steps: 6.0.0_biqbaboplfbrettd7655fr4n2y rc-switch: 4.0.0_biqbaboplfbrettd7655fr4n2y rc-table: 7.30.3_biqbaboplfbrettd7655fr4n2y - rc-tabs: 12.5.5_biqbaboplfbrettd7655fr4n2y - rc-textarea: 0.4.6_biqbaboplfbrettd7655fr4n2y - rc-tooltip: 5.2.2_biqbaboplfbrettd7655fr4n2y + rc-tabs: 12.5.6_biqbaboplfbrettd7655fr4n2y + rc-textarea: 1.0.1_biqbaboplfbrettd7655fr4n2y + rc-tooltip: 5.3.1_biqbaboplfbrettd7655fr4n2y rc-tree: 5.7.2_biqbaboplfbrettd7655fr4n2y rc-tree-select: 5.6.0_biqbaboplfbrettd7655fr4n2y rc-trigger: 5.3.4_biqbaboplfbrettd7655fr4n2y @@ -5496,7 +5533,6 @@ packages: /base64-js/1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: true /big.js/5.2.2: resolution: {integrity: sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==} @@ -5573,6 +5609,10 @@ packages: resolution: {integrity: sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==} dev: true + /browser-or-node/2.1.1: + resolution: {integrity: sha512-8CVjaLJGuSKMVTxJ2DpBl5XnlNDiT4cQFeuCJJrvJmts9YrTZDizTX7PjC2s6W4x+MBGZeEY6dGMrF04/6Hgqg==} + dev: false + /browserify-aes/1.2.0: resolution: {integrity: sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==} dependencies: @@ -5683,6 +5723,13 @@ packages: ieee754: 1.2.1 dev: true + /buffer/6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + dev: false + /builtin-status-codes/3.0.0: resolution: {integrity: sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==} dev: true @@ -6019,6 +6066,10 @@ packages: - supports-color dev: true + /collection-utils/1.0.1: + resolution: {integrity: sha512-LA2YTIlR7biSpXkKYwwuzGjwL5rjWEZVOSnvdUc7gObvWe4WkjxOpfrdhoP7Hs09YWDVfg0Mal9BpAqLfVEzQg==} + dev: false + /color-convert/1.9.3: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: @@ -8267,10 +8318,14 @@ packages: engines: {node: '>=0.10.0'} dev: true + /event-target-shim/5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + dev: false + /events/3.3.0: resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} engines: {node: '>=0.8.x'} - dev: true /evp_bytestokey/1.0.3: resolution: {integrity: sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==} @@ -9602,7 +9657,6 @@ packages: /ieee754/1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - dev: true /iferr/0.1.5: resolution: {integrity: sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA==} @@ -9626,12 +9680,8 @@ packages: queue: 6.0.1 dev: true - /immer/9.0.15: - resolution: {integrity: sha512-2eB/sswms9AEUSkOm4SbV5Y7Vmt/bKRwByd52jfLkW4OLYeaTP3EEiJ9agqU0O/tq6Dk62Zfj+TJSqfm1rLVGQ==} - dev: false - - /immutability-helper/3.1.1: - resolution: {integrity: sha512-Q0QaXjPjwIju/28TsugCHNEASwoCcJSyJV3uO1sOIQGI0jKgm9f41Lvz0DZj3n46cNCyAZTsEYoY4C2bVRUzyQ==} + /immer/9.0.19: + resolution: {integrity: sha512-eY+Y0qcsB4TZKwgQzLaE/lqYMlKhv5J9dyd2RhhtGhNo2njPXDqU9XPfcNfa3MIDsdtZt5KlkIsirlo4dHsWdQ==} dev: false /immutable/4.1.0: @@ -10089,6 +10139,10 @@ packages: engines: {node: '>=10'} dev: true + /is-url/1.2.4: + resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} + dev: false + /is-utf8/0.2.1: resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==} dev: true @@ -10245,6 +10299,10 @@ packages: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} dev: true + /js-base64/2.6.4: + resolution: {integrity: sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==} + dev: false + /js-cookie/2.2.1: resolution: {integrity: sha512-HvdH2LzI/EAZcUwA8+0nKNtWHqS+ZmijLA30RwZA0bo7ToCckjK5MkGhjED9KoRcXO6BaGI3I9UIzSA1FKFPOQ==} dev: false @@ -10298,6 +10356,10 @@ packages: resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} dev: true + /json-schema/0.4.0: + resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} + dev: false + /json-stable-stringify-without-jsonify/1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} dev: true @@ -11331,6 +11393,12 @@ packages: minimist: 1.2.6 dev: true + /mobx-persist-store/1.1.3: + resolution: {integrity: sha512-P5B4CNCgAlKZ5dLqwcKYx9qmSc2b53iQTBKkH//7bXzgbsg0+bG4LJ0rPeIx/8/J5lduzqC7/oT+kuNaeHMMqQ==} + peerDependencies: + mobx: '*' + dev: false + /mobx/6.7.0: resolution: {integrity: sha512-1kBLBdSNG2bA522HQdbsTvwAwYf9hq9FWxmlhX7wTsJUAI54907J+ozfGW+LoYUo06vjit748g6QH1AAGLNebw==} dev: false @@ -11381,8 +11449,8 @@ packages: hasBin: true dev: true - /nanoid/4.0.0: - resolution: {integrity: sha512-IgBP8piMxe/gf73RTQx7hmnhwz0aaEXYakvqZyE302IXW3HyVNhdNGC+O2MwMAVhLEnvXlvKtGbtJf6wvHihCg==} + /nanoid/4.0.1: + resolution: {integrity: sha512-udKGtCCUafD3nQtJg9wBhRP3KMbPglUsgV5JVsXhvyBs/oefqb4sqMEhKBBgqZncYowu58p1prsZQBYvAj/Gww==} engines: {node: ^14 || ^16 || >=18} hasBin: true dev: false @@ -11809,9 +11877,12 @@ packages: - supports-color dev: true + /pako/0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + dev: false + /pako/1.0.11: resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} - dev: true /parallel-transform/1.2.0: resolution: {integrity: sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==} @@ -12034,7 +12105,6 @@ packages: /pluralize/8.0.0: resolution: {integrity: sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA==} engines: {node: '>=4'} - dev: true /point-in-polygon/1.1.0: resolution: {integrity: sha512-3ojrFwjnnw8Q9242TzgXuTD+eKiutbzyslcq1ydfu82Db2y+Ogbmyrkpv0Hgj31qwT3lbS9+QAAO/pIQM35XRw==} @@ -12910,7 +12980,6 @@ packages: /process/0.11.10: resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} engines: {node: '>= 0.6.0'} - dev: true /promise-inflight/1.0.1_bluebird@3.7.2: resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} @@ -13068,6 +13137,28 @@ packages: engines: {node: '>=8'} dev: true + /quicktype-core/21.0.2: + resolution: {integrity: sha512-iNVLms/N+XsDQF/YfhAY2VKE5jSEkEOoRJUaZuyzjknB/1upT1fBtDU3+9o6bnFm1TD0B1WaEfA2kjl340h0lQ==} + dependencies: + '@glideapps/ts-necessities': 2.1.2 + '@types/urijs': 1.19.19 + browser-or-node: 2.1.1 + collection-utils: 1.0.1 + cross-fetch: 3.1.5 + is-url: 1.2.4 + js-base64: 2.6.4 + lodash: 4.17.21 + pako: 1.0.11 + pluralize: 8.0.0 + readable-stream: 4.3.0 + unicode-properties: 1.4.1 + urijs: 1.19.11 + wordwrap: 1.0.0 + yaml: 2.2.1 + transitivePeerDependencies: + - encoding + dev: false + /railroad-diagrams/1.0.0: resolution: {integrity: sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==} dev: true @@ -13161,8 +13252,8 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false - /rc-collapse/3.4.2_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-jpTwLgJzkhAgp2Wpi3xmbTbbYExg6fkptL67Uu5LCRVEj6wqmy0DHTjjeynsjOLsppHGHu41t1ELntZ0lEvS/Q==} + /rc-collapse/3.5.2_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-/TNiT3DW1t3sUCiVD/DPUYooJZ3BLA93/2rZsB3eM2bGJCCla2X9D2E4tgm7LGMQGy5Atb2lMUn2FQuvQNvavQ==} peerDependencies: react: '>=16.9.0' react-dom: '>=16.9.0' @@ -13173,7 +13264,6 @@ packages: rc-util: 5.27.1_biqbaboplfbrettd7655fr4n2y react: 18.2.0 react-dom: 18.2.0_react@18.2.0 - shallowequal: 1.1.0 dev: false /rc-dialog/9.0.2_biqbaboplfbrettd7655fr4n2y: @@ -13263,8 +13353,8 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false - /rc-input/0.1.4_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-FqDdNz+fV2dKNgfXzcSLKvC+jEs1709t7nD+WdfjrdSaOcefpgc7BUJYadc3usaING+b7ediMTfKxuJBsEFbXA==} + /rc-input/0.2.2_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-xgkVcFgtRO0Hl9hmvslZhObNyxbSpTmy3nR1Tk4XrjjZ9lFJ7GcJBy6ss30Pdb0oX36cHzLN8I7VCjBGeRNB9A==} peerDependencies: react: '>=16.0.0' react-dom: '>=16.0.0' @@ -13276,16 +13366,17 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false - /rc-mentions/1.13.1_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-FCkaWw6JQygtOz0+Vxz/M/NWqrWHB9LwqlY2RtcuFqWJNFK9njijOOzTSsBGANliGufVUzx/xuPHmZPBV0+Hgw==} + /rc-mentions/2.0.0_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-58NSeM6R5MrgYAhR2TH27JgAN7ivp3iBTmty3q6gvrrGHelPMdGxpJ5aH7AIlodCrPWLAm1lT4XoiuI4s9snXA==} peerDependencies: react: '>=16.9.0' react-dom: '>=16.9.0' dependencies: '@babel/runtime': 7.20.7 classnames: 2.3.2 - rc-menu: 9.8.0_biqbaboplfbrettd7655fr4n2y - rc-textarea: 0.4.6_biqbaboplfbrettd7655fr4n2y + rc-input: 0.2.2_biqbaboplfbrettd7655fr4n2y + rc-menu: 9.8.2_biqbaboplfbrettd7655fr4n2y + rc-textarea: 1.0.1_biqbaboplfbrettd7655fr4n2y rc-trigger: 5.3.4_biqbaboplfbrettd7655fr4n2y rc-util: 5.27.1_biqbaboplfbrettd7655fr4n2y react: 18.2.0 @@ -13309,8 +13400,8 @@ packages: shallowequal: 1.1.0 dev: true - /rc-menu/9.8.0_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-ftO5ExUMMSprzinxtHQz11KIRWY6P/tOs5ZexboWJ835LVQAMXuqDFqkjB/BXVC8WAKjj9t3eZmmweZp+guoYw==} + /rc-menu/9.8.2_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-EahOJVjLuEnJsThoPN+mGnVm431RzVzDLZWHRS/YnXTQULa7OsgdJa/Y7qXxc3Z5sz8mgT6xYtgpmBXLxrZFaQ==} peerDependencies: react: '>=16.9.0' react-dom: '>=16.9.0' @@ -13323,7 +13414,6 @@ packages: rc-util: 5.27.1_biqbaboplfbrettd7655fr4n2y react: 18.2.0 react-dom: 18.2.0_react@18.2.0 - shallowequal: 1.1.0 dev: false /rc-motion/2.6.2_biqbaboplfbrettd7655fr4n2y: @@ -13445,8 +13535,8 @@ packages: react-dom: 18.2.0_react@18.2.0 resize-observer-polyfill: 1.5.1 - /rc-segmented/2.1.0_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-hUlonro+pYoZcwrH6Vm56B2ftLfQh046hrwif/VwLIw1j3zGt52p5mREBwmeVzXnSwgnagpOpfafspzs1asjGw==} + /rc-segmented/2.1.2_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-qGo1bCr83ESXpXVOCXjFe1QJlCAQXyi9KCiy8eX3rIMYlTeJr/ftySIaTnYsitL18SvWf5ZEHsfqIWoX0EMfFQ==} peerDependencies: react: '>=16.0.0' react-dom: '>=16.0.0' @@ -13477,8 +13567,8 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false - /rc-slider/10.0.1_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-igTKF3zBet7oS/3yNiIlmU8KnZ45npmrmHlUUio8PNbIhzMcsh+oE/r2UD42Y6YD2D/s+kzCQkzQrPD6RY435Q==} + /rc-slider/10.1.1_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-gn8oXazZISEhnmRinI89Z/JD/joAaM35jp+gDtIVSTD/JJMCCBqThqLk1SVJmvtfeiEF/kKaFY0+qt4SDHFUDw==} engines: {node: '>=8.x'} peerDependencies: react: '>=16.9.0' @@ -13489,7 +13579,6 @@ packages: rc-util: 5.27.1_biqbaboplfbrettd7655fr4n2y react: 18.2.0 react-dom: 18.2.0_react@18.2.0 - shallowequal: 1.1.0 dev: false /rc-steps/6.0.0_biqbaboplfbrettd7655fr4n2y: @@ -13553,8 +13642,8 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: true - /rc-tabs/12.5.5_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-Y0k+JK4IN2cr0+MstkYK6MryvURhUc8JvHDCXujbUA6zHVTnWeTikOspGgvHPrlfZRl7WS+DPyMdEFE6RwlueQ==} + /rc-tabs/12.5.6_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-aArXHzxK7YICxe+622CZ8FlO5coMi8P7E6tXpseCPKm1gdTjUt0LrQK1/AxcrRXZXG3K4QqhlKmET0+cX5DQaQ==} engines: {node: '>=8.x'} peerDependencies: react: '>=16.9.0' @@ -13563,7 +13652,7 @@ packages: '@babel/runtime': 7.20.7 classnames: 2.3.2 rc-dropdown: 4.0.1_biqbaboplfbrettd7655fr4n2y - rc-menu: 9.8.0_biqbaboplfbrettd7655fr4n2y + rc-menu: 9.8.2_biqbaboplfbrettd7655fr4n2y rc-motion: 2.6.2_biqbaboplfbrettd7655fr4n2y rc-resize-observer: 1.2.0_biqbaboplfbrettd7655fr4n2y rc-util: 5.27.1_biqbaboplfbrettd7655fr4n2y @@ -13571,23 +13660,23 @@ packages: react-dom: 18.2.0_react@18.2.0 dev: false - /rc-textarea/0.4.6_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-HEKCu8nouXXayqYelQnhQm8fdH7v92pAQvfVCz+jhIPv2PHTyBxVrmoZJMn3B8cU+wdyuvRGkshngO3/TzBn4w==} + /rc-textarea/1.0.1_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-dtIm96apjJpCUcCeTtbnLGJaVlqbOqVgN0P9z+bqMSi7rcV5QVeUtBnG+jQTGk/uD183Z7jbhc8Dx7G3luDCwg==} peerDependencies: react: '>=16.9.0' react-dom: '>=16.9.0' dependencies: '@babel/runtime': 7.20.7 classnames: 2.3.2 + rc-input: 0.2.2_biqbaboplfbrettd7655fr4n2y rc-resize-observer: 1.2.0_biqbaboplfbrettd7655fr4n2y rc-util: 5.27.1_biqbaboplfbrettd7655fr4n2y react: 18.2.0 react-dom: 18.2.0_react@18.2.0 - shallowequal: 1.1.0 dev: false - /rc-tooltip/5.2.2_biqbaboplfbrettd7655fr4n2y: - resolution: {integrity: sha512-jtQzU/18S6EI3lhSGoDYhPqNpWajMtS5VV/ld1LwyfrDByQpYmw/LW6U7oFXXLukjfDHQ7Ju705A82PRNFWYhg==} + /rc-tooltip/5.3.1_biqbaboplfbrettd7655fr4n2y: + resolution: {integrity: sha512-e6H0dMD38EPaSPD2XC8dRfct27VvT2TkPdoBSuNl3RRZ5tspiY/c5xYEmGC0IrABvMBgque4Mr2SMZuliCvoiQ==} peerDependencies: react: '>=16.9.0' react-dom: '>=16.9.0' @@ -13993,31 +14082,17 @@ packages: react: 18.2.0 dev: false - /reactflow/11.2.0: - resolution: {integrity: sha512-wrpcpY1J1QY87meJXrd7Y1jZQ9s0VvXudpeMrXeq1HEgYOyYmZrg2oxv4ck9m9liq0Urz9z/eWrIQTkdb/6P5A==} - peerDependencies: - react: '>=17' - react-dom: '>=17' - dependencies: - '@reactflow/background': 11.0.4 - '@reactflow/controls': 11.0.4 - '@reactflow/core': 11.2.0 - '@reactflow/minimap': 11.1.0 - transitivePeerDependencies: - - immer - dev: false - - /reactflow/11.3.0_rekktpi253ghbdqoi5qfkp32ay: - resolution: {integrity: sha512-KU3Uwww0hjvF0Er5sA0Qy+e3KPJOn5bLpyxVooyef0MLDk/W5ybCmMvKKnML705GtJWChzHPNzHST5hCf5OflA==} + /reactflow/11.5.1_ytuas6axp6gvse4ghxb2libdjm: + resolution: {integrity: sha512-njujIQ5zoCREB5WCKWDZLQJOU6e4eY6M3eOvyMboqFUIbiUQwqzQ5FtM8njTCUZA1cYBGQLE2zGwdxa3RhYVbw==} peerDependencies: react: '>=17' react-dom: '>=17' dependencies: - '@reactflow/background': 11.0.5_rekktpi253ghbdqoi5qfkp32ay - '@reactflow/controls': 11.0.5_rekktpi253ghbdqoi5qfkp32ay - '@reactflow/core': 11.3.0_rekktpi253ghbdqoi5qfkp32ay - '@reactflow/minimap': 11.2.0_rekktpi253ghbdqoi5qfkp32ay - '@reactflow/node-toolbar': 1.0.0_rekktpi253ghbdqoi5qfkp32ay + '@reactflow/background': 11.1.3_ytuas6axp6gvse4ghxb2libdjm + '@reactflow/controls': 11.1.3_ytuas6axp6gvse4ghxb2libdjm + '@reactflow/core': 11.5.0_ytuas6axp6gvse4ghxb2libdjm + '@reactflow/minimap': 11.3.3_ytuas6axp6gvse4ghxb2libdjm + '@reactflow/node-toolbar': 1.1.3_ytuas6axp6gvse4ghxb2libdjm react: 18.2.0 react-dom: 18.2.0_react@18.2.0 transitivePeerDependencies: @@ -14063,6 +14138,16 @@ packages: util-deprecate: 1.0.2 dev: true + /readable-stream/4.3.0: + resolution: {integrity: sha512-MuEnA0lbSi7JS8XM+WNJlWZkHAAdm7gETHdFK//Q/mChGyj2akEFtdLZh32jSdkWGbRwCW9pn6g3LWDdDeZnBQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + dev: false + /readdirp/3.6.0: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} @@ -15357,6 +15442,10 @@ packages: setimmediate: 1.0.5 dev: true + /tiny-inflate/1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + dev: false + /tinycolor2/1.4.2: resolution: {integrity: sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==} dev: false @@ -15728,6 +15817,20 @@ packages: resolution: {integrity: sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==} dev: true + /unicode-properties/1.4.1: + resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==} + dependencies: + base64-js: 1.5.1 + unicode-trie: 2.0.0 + dev: false + + /unicode-trie/2.0.0: + resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} + dependencies: + pako: 0.2.9 + tiny-inflate: 1.0.3 + dev: false + /unified/10.1.2: resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==} dependencies: @@ -15818,10 +15921,6 @@ packages: engines: {node: '>= 10.0.0'} dev: true - /unstated-next/1.1.0: - resolution: {integrity: sha512-AAn47ZncPvgBGOvMcn8tSRxsrqwf2VdAPxLASTuLJvZt4rhKfDvUkmYZLGfclImSfTVMv7tF4ynaVxin0JjDCA==} - dev: false - /unzip-response/2.0.1: resolution: {integrity: sha512-N0XH6lqDtFH84JxptQoZYmloF4nzrQqqrAymNj+/gW60AO2AZgOcf4O/nUXJcYfyQkqvMo9lSupBZmmgvuVXlw==} engines: {node: '>=4'} @@ -15860,6 +15959,10 @@ packages: punycode: 2.1.1 dev: true + /urijs/1.19.11: + resolution: {integrity: sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==} + dev: false + /url-parse-lax/1.0.0: resolution: {integrity: sha512-BVA4lR5PIviy2PMseNd2jbFQ+jwSwQGdJejf5ctd1rEXt0Ypd7yanUK9+lYechVlN5VaTJGsu2U/3MDDu6KgBA==} engines: {node: '>=0.10.0'} @@ -15937,12 +16040,6 @@ packages: react: 18.2.0 dev: false - /use-sync-external-store/1.2.0: - resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - dev: false - /use-sync-external-store/1.2.0_react@18.2.0: resolution: {integrity: sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==} peerDependencies: @@ -16323,6 +16420,11 @@ packages: engines: {node: '>= 14'} dev: true + /yaml/2.2.1: + resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==} + engines: {node: '>= 14'} + dev: false + /yargs-parser/20.2.9: resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} engines: {node: '>=10'} @@ -16368,23 +16470,8 @@ packages: commander: 2.20.3 dev: true - /zustand/4.1.1: - resolution: {integrity: sha512-h4F3WMqsZgvvaE0n3lThx4MM81Ls9xebjvrABNzf5+jb3/03YjNTSgZXeyrvXDArMeV9untvWXRw1tY+ntPYbA==} - engines: {node: '>=12.7.0'} - peerDependencies: - immer: '>=9.0' - react: '>=16.8' - peerDependenciesMeta: - immer: - optional: true - react: - optional: true - dependencies: - use-sync-external-store: 1.2.0 - dev: false - - /zustand/4.1.1_immer@9.0.15+react@18.2.0: - resolution: {integrity: sha512-h4F3WMqsZgvvaE0n3lThx4MM81Ls9xebjvrABNzf5+jb3/03YjNTSgZXeyrvXDArMeV9untvWXRw1tY+ntPYbA==} + /zustand/4.3.2_immer@9.0.19+react@18.2.0: + resolution: {integrity: sha512-rd4haDmlwMTVWVqwvgy00ny8rtti/klRoZjFbL/MAcDnmD5qSw/RZc+Vddstdv90M5Lv6RPgWvm1Hivyn0QgJw==} engines: {node: '>=12.7.0'} peerDependencies: immer: '>=9.0' @@ -16395,7 +16482,7 @@ packages: react: optional: true dependencies: - immer: 9.0.15 + immer: 9.0.19 react: 18.2.0 use-sync-external-store: 1.2.0_react@18.2.0 dev: false diff --git a/websites/workspace/.gitignore b/websites/workspace/.gitignore index a547bf3..b43362b 100644 --- a/websites/workspace/.gitignore +++ b/websites/workspace/.gitignore @@ -22,3 +22,4 @@ dist-ssr *.njsproj *.sln *.sw? +stats.html diff --git a/websites/workspace/package.json b/websites/workspace/package.json index bff2861..c8052c1 100644 --- a/websites/workspace/package.json +++ b/websites/workspace/package.json @@ -12,7 +12,7 @@ "dependencies": { "@ahooksjs/use-url-state": "^3.5.0", "@modou/canvas-designer": "workspace:^1.0.0", - "mitt": "^3.0.0", - "reactflow": "^11.2.0" + "@modou/flow-designer": "workspace:^0.0.1", + "mitt": "^3.0.0" } } diff --git a/websites/workspace/src/components/CanvasFlow/FlowCanvas.tsx b/websites/workspace/src/components/CanvasFlow/FlowCanvas.tsx new file mode 100644 index 0000000..82a6ba4 --- /dev/null +++ b/websites/workspace/src/components/CanvasFlow/FlowCanvas.tsx @@ -0,0 +1,21 @@ +import { FC } from 'react' + +import { mcss } from '@modou/css-in-js' +import { FlowDesigner } from '@modou/flow-designer' +import { FlowFile } from '@modou/meta-vfs/src/FlowFile' + +export const FlowCanvas: FC<{ file: FlowFile }> = ({ file }) => { + return ( +
+ +
+ ) +} + +const classes = { + wrapper: mcss` + height: 100%; + flex: 1; + border: 1px solid red; + `, +} diff --git a/websites/workspace/src/components/CanvasFlow/FlowList.tsx b/websites/workspace/src/components/CanvasFlow/FlowList.tsx new file mode 100644 index 0000000..60e3b44 --- /dev/null +++ b/websites/workspace/src/components/CanvasFlow/FlowList.tsx @@ -0,0 +1,187 @@ +import { MoreOutlined, PlusOutlined } from '@ant-design/icons' +import { ModalForm, ProFormText } from '@ant-design/pro-components' +import { Button, Dropdown, Form, List, Typography } from 'antd' +import { ComponentProps, Dispatch, FC, SetStateAction, useContext } from 'react' +import { useParams } from 'react-router-dom' + +import { AppFactoryContext } from '@modou/asset-vfs' +import { FlowNodeMetadata } from '@modou/core' +import { mcss } from '@modou/css-in-js' +import { FlowNodeEnum } from '@modou/flow-nodes' +import { useAppManager } from '@modou/meta-vfs' +import { FlowFile, FlowFileMeta } from '@modou/meta-vfs/src/FlowFile' +import { FlowNodeFile } from '@modou/meta-vfs/src/FlowNodeFile' +import { Observer, observer } from '@modou/reactivity-react' +import { generateId } from '@modou/shared' + +import { PageRouterParamsKey } from '../../types' + +enum FlowActionEnum { + Delete = 'Delete', + Copy = 'Copy', +} +const _FlowList: FC<{ + activeFlowId: string + setActiveFlowId: Dispatch> +}> = ({ activeFlowId, setActiveFlowId }) => { + const { appManager } = useAppManager() + const { pageId } = useParams() + const page = appManager.pageMap.get(pageId as string)! + + const [form] = Form.useForm>() + + const renderListItem: ComponentProps>['renderItem'] = ( + flow, + ) => ( + + {() => ( + { + setActiveFlowId(flow.meta.id) + }} + > +
+ {flow.meta.id === activeFlowId ? ( + {flow.meta.name} + ) : ( + {flow.meta.name} + )} + { + switch (key) { + case FlowActionEnum.Delete: + page.deleteFlow(flow.meta.id) + break + default: + } + }, + items: [ + { + label: 复制, + key: FlowActionEnum.Copy, + }, + { + label: ( + 删除 + ), + key: FlowActionEnum.Delete, + }, + ], + }} + > + + +
+
+ )} +
+ ) + const appFactory = useContext(AppFactoryContext) + + return ( +
+
+ > + form={form} + layout="horizontal" + title="创建流" + modalProps={{ + mask: false, + }} + onFinish={async (formData) => { + await form.validateFields() + const newFlow = FlowFile.create( + { + id: generateId(), + name: formData.name, + }, + page, + ) + FlowNodeFile.create( + { + ...FlowNodeMetadata.mrSchemeToDefaultJson( + appFactory.flowNodeByType[FlowNodeEnum.START_NODE].metadata + .jsonPropsSchema, + ), + id: generateId(), + position: { + x: 100, + y: 100, + }, + }, + newFlow, + ) + form.resetFields() + return true + }} + trigger={ + + } + > + + +
+
+ item.meta.id} + renderItem={renderListItem} + /> +
+
+ ) +} +export const FlowList = observer(_FlowList) + +const classes = { + wrapper: mcss` + width: 220px; + border: 1px solid green; + background-color: white; + padding: 0 16px 16px; + `, + createWrapper: mcss` + text-align: right; + `, + listItem: mcss` + margin-left: -16px; + margin-right: -16px; + padding-left: 16px; + padding-right: 16px; + border: none !important; + cursor: default; + &:hover { + background-color: rgba(0, 0, 0, 0.1); + .mm_p-list_item_more_action { + display: block !important; + } + } + `, + listItemContent: mcss` + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; + a { + cursor: default !important; + } + `, + moreAction: mcss` + display: none; + `, +} diff --git a/websites/workspace/src/components/CanvasFlow/index.tsx b/websites/workspace/src/components/CanvasFlow/index.tsx new file mode 100644 index 0000000..e4b3cc9 --- /dev/null +++ b/websites/workspace/src/components/CanvasFlow/index.tsx @@ -0,0 +1,55 @@ +import useUrlState from '@ahooksjs/use-url-state' +import { useMemoizedFn } from 'ahooks' +import { Empty } from 'antd' +import { FC } from 'react' + +import { mcss } from '@modou/css-in-js' +import { PageFile } from '@modou/meta-vfs' +import { observer } from '@modou/reactivity-react' + +import { FlowCanvas } from './FlowCanvas' +import { FlowList } from './FlowList' + +export const UOCanvasFlow: FC<{ + parentFile: PageFile +}> = ({ parentFile }) => { + const [urlState, setUrlState] = useUrlState<{ flowId: string }>({ + flowId: '', + }) + const flowFile = parentFile.flowMap[urlState.flowId] + const setActiveFlowId = useMemoizedFn((flowId) => { + setUrlState({ + flowId, + }) + }) + return ( +
+ + {flowFile ? ( + + ) : ( + + )} +
+ ) +} +export const CanvasFlow = observer(UOCanvasFlow) + +const classes = { + wrapper: mcss` + height: 100%; + width: 100%; + display: flex; + flex-direction: row; + `, + empty: mcss` + height: 100%; + flex: 1; + display: flex; + align-items: center; + justify-content: center; + `, +} diff --git a/websites/workspace/src/components/index.ts b/websites/workspace/src/components/index.ts index e0a4a4b..8e0f5ad 100644 --- a/websites/workspace/src/components/index.ts +++ b/websites/workspace/src/components/index.ts @@ -1,2 +1,3 @@ export * from './AppSpin' export * from './IconFont' +export * from './CanvasFlow' diff --git a/websites/workspace/src/features/app/components/AppHome.tsx b/websites/workspace/src/features/app/components/AppHome.tsx index b15e39b..bb83401 100644 --- a/websites/workspace/src/features/app/components/AppHome.tsx +++ b/websites/workspace/src/features/app/components/AppHome.tsx @@ -6,8 +6,8 @@ import { head, isEmpty } from 'lodash' import { FC, useEffect } from 'react' import { useNavigate, useParams } from 'react-router-dom' -import { useAppManager } from '@modou/core' import { mcss } from '@modou/css-in-js' +import { useAppManager } from '@modou/meta-vfs' import { observer } from '@modou/reactivity-react' const _AppHome: FC = () => { diff --git a/websites/workspace/src/features/app/components/ModuleManagerPage.tsx b/websites/workspace/src/features/app/components/ModuleManagerPage.tsx index 328926b..069f22b 100644 --- a/websites/workspace/src/features/app/components/ModuleManagerPage.tsx +++ b/websites/workspace/src/features/app/components/ModuleManagerPage.tsx @@ -8,10 +8,16 @@ import { ComponentProps, FC } from 'react' import { createPortal } from 'react-dom' import { useNavigate, useParams } from 'react-router-dom' -import { Page, WidgetMetadata, generateId, useAppManager } from '@modou/core' +import { WidgetMetadata } from '@modou/core' import { mcss } from '@modou/css-in-js' -import { PageFile, WidgetFile } from '@modou/meta-vfs' +import { + PageFile, + PageFileMeta, + WidgetFile, + useAppManager, +} from '@modou/meta-vfs' import { Observer, observer } from '@modou/reactivity-react' +import { generateId } from '@modou/shared' import { rowWidgetMetadata } from '@modou/widgets-antd' enum PageActionEnum { @@ -30,7 +36,7 @@ const _ModuleManagerPage: FC<{ const dataSource = pages.filter((page) => { return page.meta.name.includes(searchVal) }) - const [form] = Form.useForm>() + const [form] = Form.useForm>() const renderListItem: ComponentProps>['renderItem'] = ( page, @@ -107,7 +113,7 @@ const _ModuleManagerPage: FC<{ <> {itemAddRef && createPortal( - > + > form={form} layout="horizontal" title="创建页面" @@ -117,7 +123,6 @@ const _ModuleManagerPage: FC<{ onFinish={async (formData) => { await form.validateFields() console.log(formData) - // TODO 完善 addPage const rootWidgetId = generateId() const page = PageFile.create( { diff --git a/websites/workspace/src/features/app/routers/App.tsx b/websites/workspace/src/features/app/routers/App.tsx index 5702bc9..7eed949 100644 --- a/websites/workspace/src/features/app/routers/App.tsx +++ b/websites/workspace/src/features/app/routers/App.tsx @@ -4,7 +4,7 @@ import { generateRouterPath } from '@/utils/router' import { CopyOutlined, DatabaseOutlined } from '@ant-design/icons' import { useMount } from 'ahooks' import { Layout, Menu } from 'antd' -import { ComponentProps, FC, useCallback, useState } from 'react' +import { ComponentProps, FC, useCallback, useEffect, useState } from 'react' import { Outlet, matchPath, @@ -13,16 +13,16 @@ import { useParams, } from 'react-router-dom' -import { AppManagerProvider } from '@modou/core' import { mcss } from '@modou/css-in-js' -import { AppManager } from '@modou/meta-vfs' +import { AppFile, AppManager, AppManagerProvider } from '@modou/meta-vfs' import { runInAction } from '@modou/reactivity' +import { AppState, AppStateProvider } from '@modou/state-manager' import { ModuleManager } from '../components' import { AppHeader } from '../components/AppHeader' import { AppHome } from '../components/AppHome' import { ModuleEnum } from '../types' -import { mock_appFile } from './mock' +import { generateMockAppFile } from './mock' const menuItems: ComponentProps['items'] = [ { @@ -38,8 +38,15 @@ const menuItems: ComponentProps['items'] = [ ] const appManager = runInAction(() => { - return new AppManager(mock_appFile) + try { + // @ts-expect-error + const appJson = JSON.parse(window.localStorage.getItem('app_id_mock')) + return new AppManager(AppFile.formJSON(appJson)) + } catch { + return new AppManager(generateMockAppFile()) + } }) + export const App: FC = () => { const params = useParams() const [module, setModule] = useState('') @@ -90,47 +97,58 @@ export const App: FC = () => { const isAppHome = matchPath(ROUTER_PATH.APP, pathname) - return ( - - {isAppHome ? ( - - ) : ( - - - - - - - - { - setVisibleModuleManger(false) - updateModule() - }} - module={module} - visible={visibleModuleManger} - /> - - + const [appState, setAppState] = useState() + + useEffect(() => { + if (appState?.file === appManager.app) { + return + } + setAppState(new AppState(appManager.app)) + }, [appState?.file]) + + return appState ? ( + + + {isAppHome ? ( + + ) : ( + + + + + + + + { + setVisibleModuleManger(false) + updateModule() + }} + module={module} + visible={visibleModuleManger} + /> + + + - - )} - - ) + )} + + + ) : null } const classes = { diff --git a/websites/workspace/src/features/app/routers/mock.ts b/websites/workspace/src/features/app/routers/mock.ts index aff445c..abc9e96 100644 --- a/websites/workspace/src/features/app/routers/mock.ts +++ b/websites/workspace/src/features/app/routers/mock.ts @@ -1,4 +1,4 @@ -import { WidgetBaseProps, WidgetMetadata, generateId } from '@modou/core' +import { WidgetBaseProps, WidgetMetadata } from '@modou/core' import { AppFile, EntityFieldEnum, @@ -11,6 +11,7 @@ import { PageFile, WidgetFile, } from '@modou/meta-vfs' +import { generateId } from '@modou/shared' import { buttonWidgetMetadata, colWidgetMetadata, @@ -21,277 +22,289 @@ const MOCK_PAGE_ID = 'MOCK_PAGE_ID' const MOCK_ROOT_WIDGET_ID = 'MOCK_ROOT_WIDGET_ID' // mock app start -export const mock_appFile = AppFile.create({ - id: 'app_id_mock', - name: 'app_name_mock', - version: '0.0.1', -}) -// mock app end - -// mock page start -const buttonDSL: WidgetBaseProps = { - ...WidgetMetadata.mrSchemeToDefaultJson(buttonWidgetMetadata.jsonPropsSchema), - id: generateId(), -} - -const colDSL: WidgetBaseProps = { - ...WidgetMetadata.mrSchemeToDefaultJson(colWidgetMetadata.jsonPropsSchema), - id: generateId(), - slots: { - children: [buttonDSL.id], - }, -} +export const generateMockAppFile = () => { + const mock_appFile = AppFile.create({ + id: 'app_id_mock', + name: 'app_name_mock', + version: '0.0.1', + }) + // mock page start + const defaultButton = WidgetMetadata.mrSchemeToDefaultJson( + buttonWidgetMetadata.jsonPropsSchema, + ) + const buttonDSL: WidgetBaseProps = { + ...defaultButton, + props: { + ...defaultButton.props, + title: '按钮-{{colDSL.span}}-大漠孤烟直哈哈', + }, + id: 'buttonDSL', + } -const rowDSL: WidgetBaseProps = { - ...WidgetMetadata.mrSchemeToDefaultJson(rowWidgetMetadata.jsonPropsSchema), - id: MOCK_ROOT_WIDGET_ID, - slots: { - children: [colDSL.id], - }, -} + const colDSL: WidgetBaseProps = { + ...WidgetMetadata.mrSchemeToDefaultJson(colWidgetMetadata.jsonPropsSchema), + id: 'colDSL', + slots: { + children: [buttonDSL.id], + }, + } -const MOCK_WIDGETS = [rowDSL, colDSL, buttonDSL] + const rowDSL: WidgetBaseProps = { + ...WidgetMetadata.mrSchemeToDefaultJson(rowWidgetMetadata.jsonPropsSchema), + id: MOCK_ROOT_WIDGET_ID, + slots: { + children: [colDSL.id], + }, + } -const pageFile1 = PageFile.create( - { - name: '大漠孤烟直', - id: MOCK_PAGE_ID, - rootWidgetId: MOCK_ROOT_WIDGET_ID, - }, - mock_appFile, -) + const MOCK_WIDGETS = [rowDSL, colDSL, buttonDSL] -// const pageFile2 = PageFile.create( -// { -// name: '测试', -// id: MOCK_PAGE_ID + '___', -// version: '0.0.0', -// rootWidgetId: MOCK_ROOT_WIDGET_ID + MOCK_PAGE_ID + '___', -// }, -// mock_appFile, -// ) -// const pageFile3 = PageFile.create( -// { -// name: '长河落日圆', -// id: MOCK_PAGE_ID + '________', -// version: '0.0.0', -// rootWidgetId: MOCK_ROOT_WIDGET_ID + MOCK_PAGE_ID + '________', -// }, -// mock_appFile, -// ) -MOCK_WIDGETS.forEach((widget) => - WidgetFile.create( + const pageFile1 = PageFile.create( { - ...widget, - id: widget.id, + name: '大漠孤烟直', + id: MOCK_PAGE_ID, + rootWidgetId: MOCK_ROOT_WIDGET_ID, }, - pageFile1, - ), -) -// MOCK_WIDGETS.forEach((widget) => -// WidgetFile.create( -// { -// ...widget, -// version: '0.0.1', -// id: widget.id + pageFile2.meta.id, -// }, -// pageFile2, -// ), -// ) -// MOCK_WIDGETS.forEach((widget) => -// WidgetFile.create( -// { -// ...widget, -// version: '0.0.1', -// id: widget.id + pageFile3.meta.id, -// }, -// pageFile3, -// ), -// ) -// mock page end -const MOCK_FIELDS: EntityFieldFileMeta[] = [ - { - id: generateId(), - type: EntityFieldEnum.AutoNumber, - name: EntityFieldEnum.AutoNumber, - title: EntityFieldEnum.AutoNumber, - description: EntityFile.getEntityFieldTypeLabel(EntityFieldEnum.AutoNumber), - required: false, - config: {}, - version: '0.0.1', - }, - { - id: generateId(), - type: EntityFieldEnum.Date, - name: EntityFieldEnum.Date, - title: EntityFieldEnum.Date, - description: EntityFile.getEntityFieldTypeLabel(EntityFieldEnum.Date), - required: false, - config: {}, - version: '0.0.1', - }, - { - id: generateId(), - type: EntityFieldEnum.DateTime, - name: EntityFieldEnum.DateTime, - title: EntityFieldEnum.DateTime, - description: EntityFile.getEntityFieldTypeLabel(EntityFieldEnum.DateTime), - required: false, - config: {}, - version: '0.0.1', - }, - { - id: generateId(), - type: EntityFieldEnum.Email, - name: EntityFieldEnum.Email, - title: EntityFieldEnum.Email, - description: EntityFile.getEntityFieldTypeLabel(EntityFieldEnum.Email), - required: false, - config: {}, - version: '0.0.1', - }, - { - id: generateId(), - type: EntityFieldEnum.Image, - name: EntityFieldEnum.Image, - title: EntityFieldEnum.Image, - description: EntityFile.getEntityFieldTypeLabel(EntityFieldEnum.Image), - required: false, - config: {}, - version: '0.0.1', - }, - { - id: generateId(), - type: EntityFieldEnum.LongText, - name: EntityFieldEnum.LongText, - title: EntityFieldEnum.LongText, - description: EntityFile.getEntityFieldTypeLabel(EntityFieldEnum.LongText), - required: false, - config: {}, - version: '0.0.1', - }, - { - id: generateId(), - type: EntityFieldEnum.Number, - name: EntityFieldEnum.Number, - title: EntityFieldEnum.Number, - description: EntityFile.getEntityFieldTypeLabel(EntityFieldEnum.Number), - required: false, - config: {}, - version: '0.0.1', - }, - { - id: generateId(), - type: EntityFieldEnum.PhoneNumber, - name: EntityFieldEnum.PhoneNumber, - title: EntityFieldEnum.PhoneNumber, - description: EntityFile.getEntityFieldTypeLabel( - EntityFieldEnum.PhoneNumber, + mock_appFile, + ) + + // const pageFile2 = PageFile.create( + // { + // name: '测试', + // id: MOCK_PAGE_ID + '___', + // version: '0.0.0', + // rootWidgetId: MOCK_ROOT_WIDGET_ID + MOCK_PAGE_ID + '___', + // }, + // mock_appFile, + // ) + // const pageFile3 = PageFile.create( + // { + // name: '长河落日圆', + // id: MOCK_PAGE_ID + '________', + // version: '0.0.0', + // rootWidgetId: MOCK_ROOT_WIDGET_ID + MOCK_PAGE_ID + '________', + // }, + // mock_appFile, + // ) + MOCK_WIDGETS.forEach((widget) => + WidgetFile.create( + { + ...widget, + id: widget.id, + }, + pageFile1, ), - required: false, - config: {}, - version: '0.0.1', - }, - { - id: generateId(), - type: EntityFieldEnum.Text, - name: EntityFieldEnum.Text, - title: EntityFieldEnum.Text, - description: EntityFile.getEntityFieldTypeLabel(EntityFieldEnum.Text), - required: false, - config: {}, - version: '0.0.1', - }, - { - id: generateId(), - type: EntityFieldEnum.URL, - name: EntityFieldEnum.URL, - title: EntityFieldEnum.URL, - description: EntityFile.getEntityFieldTypeLabel(EntityFieldEnum.URL), - required: false, - config: {}, - version: '0.0.1', - }, - { - id: generateId(), - type: EntityFieldEnum.Enum, - name: EntityFieldEnum.Enum, - title: EntityFieldEnum.Enum, - description: EntityFile.getEntityFieldTypeLabel(EntityFieldEnum.Enum), - required: false, - config: { - enumCode: '', - multiple: true, + ) + // MOCK_WIDGETS.forEach((widget) => + // WidgetFile.create( + // { + // ...widget, + // version: '0.0.1', + // id: widget.id + pageFile2.meta.id, + // }, + // pageFile2, + // ), + // ) + // MOCK_WIDGETS.forEach((widget) => + // WidgetFile.create( + // { + // ...widget, + // version: '0.0.1', + // id: widget.id + pageFile3.meta.id, + // }, + // pageFile3, + // ), + // ) + // mock page end + const MOCK_FIELDS: EntityFieldFileMeta[] = [ + { + id: generateId(), + type: EntityFieldEnum.AutoNumber, + name: EntityFieldEnum.AutoNumber, + title: EntityFieldEnum.AutoNumber, + description: EntityFile.getEntityFieldTypeLabel( + EntityFieldEnum.AutoNumber, + ), + required: false, + config: {}, + version: '0.0.1', }, - version: '0.0.1', - }, -] -// mock entity start -const entityFile1 = EntityFile.create( - { - version: '0.0.1', - id: 'user', - name: 'user', - title: '人员', - description: '人员', - position: { - x: 400, - y: 100, + { + id: generateId(), + type: EntityFieldEnum.Date, + name: EntityFieldEnum.Date, + title: EntityFieldEnum.Date, + description: EntityFile.getEntityFieldTypeLabel(EntityFieldEnum.Date), + required: false, + config: {}, + version: '0.0.1', }, - }, - mock_appFile, -) - -MOCK_FIELDS.forEach((field) => { - EntityFieldFile.create( { - ...field, + id: generateId(), + type: EntityFieldEnum.DateTime, + name: EntityFieldEnum.DateTime, + title: EntityFieldEnum.DateTime, + description: EntityFile.getEntityFieldTypeLabel(EntityFieldEnum.DateTime), + required: false, + config: {}, + version: '0.0.1', }, - entityFile1, + { + id: generateId(), + type: EntityFieldEnum.Email, + name: EntityFieldEnum.Email, + title: EntityFieldEnum.Email, + description: EntityFile.getEntityFieldTypeLabel(EntityFieldEnum.Email), + required: false, + config: {}, + version: '0.0.1', + }, + { + id: generateId(), + type: EntityFieldEnum.Image, + name: EntityFieldEnum.Image, + title: EntityFieldEnum.Image, + description: EntityFile.getEntityFieldTypeLabel(EntityFieldEnum.Image), + required: false, + config: {}, + version: '0.0.1', + }, + { + id: generateId(), + type: EntityFieldEnum.LongText, + name: EntityFieldEnum.LongText, + title: EntityFieldEnum.LongText, + description: EntityFile.getEntityFieldTypeLabel(EntityFieldEnum.LongText), + required: false, + config: {}, + version: '0.0.1', + }, + { + id: generateId(), + type: EntityFieldEnum.Number, + name: EntityFieldEnum.Number, + title: EntityFieldEnum.Number, + description: EntityFile.getEntityFieldTypeLabel(EntityFieldEnum.Number), + required: false, + config: {}, + version: '0.0.1', + }, + { + id: generateId(), + type: EntityFieldEnum.PhoneNumber, + name: EntityFieldEnum.PhoneNumber, + title: EntityFieldEnum.PhoneNumber, + description: EntityFile.getEntityFieldTypeLabel( + EntityFieldEnum.PhoneNumber, + ), + required: false, + config: {}, + version: '0.0.1', + }, + { + id: generateId(), + type: EntityFieldEnum.Text, + name: EntityFieldEnum.Text, + title: EntityFieldEnum.Text, + description: EntityFile.getEntityFieldTypeLabel(EntityFieldEnum.Text), + required: false, + config: {}, + version: '0.0.1', + }, + { + id: generateId(), + type: EntityFieldEnum.URL, + name: EntityFieldEnum.URL, + title: EntityFieldEnum.URL, + description: EntityFile.getEntityFieldTypeLabel(EntityFieldEnum.URL), + required: false, + config: {}, + version: '0.0.1', + }, + { + id: generateId(), + type: EntityFieldEnum.Enum, + name: EntityFieldEnum.Enum, + title: EntityFieldEnum.Enum, + description: EntityFile.getEntityFieldTypeLabel(EntityFieldEnum.Enum), + required: false, + config: { + enumCode: '', + multiple: true, + }, + version: '0.0.1', + }, + ] + // mock entity start + const entityFile1 = EntityFile.create( + { + version: '0.0.1', + id: 'user', + name: 'user', + title: '人员', + description: '人员', + position: { + x: 400, + y: 100, + }, + }, + mock_appFile, ) -}) -const entityFile2 = EntityFile.create( - { - version: '0.0.1', - id: 'department', - name: 'department', - title: '部门', - description: '部门', - position: { - x: 800, - y: 100, - }, - }, - mock_appFile, -) + MOCK_FIELDS.forEach((field) => { + EntityFieldFile.create( + { + ...field, + }, + entityFile1, + ) + }) -EntityRelationFile.create( - { - id: generateId(), - name: 'department', - title: '部门', - type: EntityRelationTypeEnum.Lookup, - relationType: EntityRelationLookupRelationTypeEnum.ManyToOne, - description: '部门', - sourceEntity: 'user', - targetEntity: 'department', - targetName: 'user', - targetTitle: '用户', - targetDescription: '用户', - version: '0.0.1', - }, - entityFile1, -) + const entityFile2 = EntityFile.create( + { + version: '0.0.1', + id: 'department', + name: 'department', + title: '部门', + description: '部门', + position: { + x: 800, + y: 100, + }, + }, + mock_appFile, + ) -MOCK_FIELDS.forEach((field) => { - EntityFieldFile.create( + EntityRelationFile.create( { - ...field, - required: true, - id: field.id + entityFile2.meta.id, + id: generateId(), + name: 'department', + title: '部门', + type: EntityRelationTypeEnum.Lookup, + relationType: EntityRelationLookupRelationTypeEnum.ManyToOne, + description: '部门', + sourceEntity: 'user', + targetEntity: 'department', + targetName: 'user', + targetTitle: '用户', + targetDescription: '用户', + version: '0.0.1', }, - entityFile2, + entityFile1, ) -}) -// mock entity end + + MOCK_FIELDS.forEach((field) => { + EntityFieldFile.create( + { + ...field, + required: true, + id: field.id + entityFile2.meta.id, + }, + entityFile2, + ) + }) + // mock entity end + + return mock_appFile +} +// mock app end diff --git a/websites/workspace/src/features/entity/components/EntitiesER.tsx b/websites/workspace/src/features/entity/components/EntitiesER.tsx index 4c7bb28..ca7848a 100644 --- a/websites/workspace/src/features/entity/components/EntitiesER.tsx +++ b/websites/workspace/src/features/entity/components/EntitiesER.tsx @@ -14,8 +14,8 @@ import ReactFlow, { useNodesState, } from 'reactflow' -import { useAppManager } from '@modou/core' import { mcss, useTheme } from '@modou/css-in-js' +import { useAppManager } from '@modou/meta-vfs' import { runInAction } from '@modou/reactivity' import { observer } from '@modou/reactivity-react' @@ -37,8 +37,7 @@ const useEntityNodeState = () => { type: 'EntityNode', data: { entity, - passiveEntityRelations: - passiveEntityRelations.get(entity.meta.name) || [], + passiveEntityRelations: passiveEntityRelations[entity.meta.name] || [], }, position: entity.meta.position, })), @@ -81,7 +80,7 @@ const useEntityNodeState = () => { data: { entity, passiveEntityRelations: - passiveEntityRelations.get(entity.meta.name) || [], + passiveEntityRelations[entity.meta.name] || [], }, position: entity.meta.position, } @@ -92,7 +91,7 @@ const useEntityNodeState = () => { data: { entity, passiveEntityRelations: - passiveEntityRelations.get(entity.meta.name) || [], + passiveEntityRelations[entity.meta.name] || [], }, position: entity.meta.position, } diff --git a/websites/workspace/src/features/entity/components/EntitiesList.tsx b/websites/workspace/src/features/entity/components/EntitiesList.tsx index 4be7c5e..8a1e22a 100644 --- a/websites/workspace/src/features/entity/components/EntitiesList.tsx +++ b/websites/workspace/src/features/entity/components/EntitiesList.tsx @@ -2,9 +2,8 @@ import { Button, Space, Table } from 'antd' import { ColumnsType } from 'antd/es/table' import { FC } from 'react' -import { useAppManager } from '@modou/core' import { mcss } from '@modou/css-in-js' -import { EntityFileMeta } from '@modou/meta-vfs' +import { EntityFileMeta, useAppManager } from '@modou/meta-vfs' import { observer } from '@modou/reactivity-react' const _EntitiesList: FC<{ diff --git a/websites/workspace/src/features/entity/components/EntityFieldCreator.tsx b/websites/workspace/src/features/entity/components/EntityFieldCreator.tsx index 603c9cd..530aa8c 100644 --- a/websites/workspace/src/features/entity/components/EntityFieldCreator.tsx +++ b/websites/workspace/src/features/entity/components/EntityFieldCreator.tsx @@ -15,13 +15,14 @@ import { useState, } from 'react' -import { generateId, useAppManager } from '@modou/core' import { EntityFieldEnum, EntityFieldFile, EntityFieldFileMeta, EntityFile, + useAppManager, } from '@modou/meta-vfs' +import { generateId } from '@modou/shared' const buildEntityFieldConfig = ( entityFieldType: EntityFieldEnum, @@ -188,7 +189,7 @@ const _EntityFieldCreator: ForwardRefRenderFunction< onFinish={async (formData) => { switch (mode) { case 'create': { - const entity = app.entityMap.get(entityId)! + const entity = app.entityMap[entityId]! EntityFieldFile.create( { ...formData, @@ -199,7 +200,7 @@ const _EntityFieldCreator: ForwardRefRenderFunction< break } case 'edit': { - const entity = app.entityMap.get(entityId)! + const entity = app.entityMap[entityId]! entity.entityFields.forEach((field) => { if (field.meta.id === entityFieldId) { field.updateMeta({ diff --git a/websites/workspace/src/features/entity/components/EntityFields.tsx b/websites/workspace/src/features/entity/components/EntityFields.tsx index d9ebf18..7701250 100644 --- a/websites/workspace/src/features/entity/components/EntityFields.tsx +++ b/websites/workspace/src/features/entity/components/EntityFields.tsx @@ -2,8 +2,7 @@ import { Button, Space, Table } from 'antd' import { ColumnsType } from 'antd/es/table' import { ComponentProps, FC, useRef } from 'react' -import { useAppManager } from '@modou/core' -import { EntityFieldFileMeta } from '@modou/meta-vfs' +import { EntityFieldFileMeta, useAppManager } from '@modou/meta-vfs' import { observer } from '@modou/reactivity-react' import { EntityFieldCreator } from './EntityFieldCreator' @@ -16,7 +15,7 @@ const _EntityFields: FC<{ useRef(null) const { app } = useAppManager() - const entity = app.entityMap.get(entityId)! + const entity = app.entityMap[entityId]! const columns: ColumnsType = [ { diff --git a/websites/workspace/src/features/entity/components/EntityModuleActions.tsx b/websites/workspace/src/features/entity/components/EntityModuleActions.tsx index 011ab56..af98325 100644 --- a/websites/workspace/src/features/entity/components/EntityModuleActions.tsx +++ b/websites/workspace/src/features/entity/components/EntityModuleActions.tsx @@ -2,7 +2,7 @@ import { useMount } from 'ahooks' import { FC, ReactNode, useState } from 'react' import { createPortal } from 'react-dom' -import { generateId } from '@modou/core' +import { generateId } from '@modou/shared' const ENTITY_MODULE_ACTION_PORTAL_ID = generateId() export const EntityModuleActionWrapper: FC<{ children: ReactNode }> = ({ diff --git a/websites/workspace/src/features/entity/hooks/useEntityCreator.ts b/websites/workspace/src/features/entity/hooks/useEntityCreator.ts index b894592..e96507c 100644 --- a/websites/workspace/src/features/entity/hooks/useEntityCreator.ts +++ b/websites/workspace/src/features/entity/hooks/useEntityCreator.ts @@ -1,8 +1,8 @@ import { useBoolean } from 'ahooks' import { Form } from 'antd' -import { generateId, useAppManager } from '@modou/core' -import { EntityFile, EntityFileMeta } from '@modou/meta-vfs' +import { EntityFile, EntityFileMeta, useAppManager } from '@modou/meta-vfs' +import { generateId } from '@modou/shared' export const useEntityCreator = () => { const [open, { setFalse, setTrue }] = useBoolean(false) @@ -12,7 +12,7 @@ export const useEntityCreator = () => { const onSubmitEntity = (entity: EntityFileMeta) => { // 编辑 if (entity.id) { - app.entityMap.get(entity.id)!.updateMeta(entity) + app.entityMap[entity.id].updateMeta(entity) } else { const maxX = Math.max( ...app.entities.map((entity) => entity.meta.position.x), diff --git a/websites/workspace/src/features/entity/routers/Entity.tsx b/websites/workspace/src/features/entity/routers/Entity.tsx index 43dd8e1..6869914 100644 --- a/websites/workspace/src/features/entity/routers/Entity.tsx +++ b/websites/workspace/src/features/entity/routers/Entity.tsx @@ -4,8 +4,8 @@ import { Card } from 'antd' import { FC } from 'react' import { useParams } from 'react-router-dom' -import { useAppManager } from '@modou/core' import { mcss } from '@modou/css-in-js' +import { useAppManager } from '@modou/meta-vfs' import { observer } from '@modou/reactivity-react' import { @@ -50,7 +50,7 @@ const _Entity: FC = () => { }) const { app } = useAppManager() const { entityId } = useParams() - const entity = app.entityMap.get(entityId!)! + const entity = app.entityMap[entityId!] const { open, setFalse, setTrue, onSubmitEntity, form } = useEntityCreator() const buildTabPanel = (tabKey: EntityModuleTabKeyEnum) => { diff --git a/websites/workspace/src/features/page/routers/Page.tsx b/websites/workspace/src/features/page/routers/Page.tsx index 361c5cc..2dbe0b3 100644 --- a/websites/workspace/src/features/page/routers/Page.tsx +++ b/websites/workspace/src/features/page/routers/Page.tsx @@ -6,16 +6,15 @@ import { head } from 'lodash' import { FC, useLayoutEffect } from 'react' import { useNavigate, useParams } from 'react-router-dom' +import { AppFactoryContext, defaultAppFactory } from '@modou/asset-vfs' import { CanvasDesigner } from '@modou/canvas-designer' -import { - AppFactoryContext, - defaultAppFactory, - useAppManager, -} from '@modou/core' import { mcss } from '@modou/css-in-js' +import { useAppManager } from '@modou/meta-vfs' import { observer } from '@modou/reactivity-react' import { SimulatorPC } from '@modou/simulator' +import { CanvasFlow } from '../../../components' + const _Page: FC = () => { const { pageId, appId } = useParams() const { appManager } = useAppManager() @@ -36,19 +35,20 @@ const _Page: FC = () => { } }, [appId, appManager.app.pages, navigate, page]) - return ( - - - {page && ( - - - - - - )} - - - ) + return page ? ( + + + + + + + + + + + + + ) : null } export const Page = observer(_Page) @@ -57,6 +57,10 @@ const classes = { height: 100%; `, container: mcss` - height: 100%; + height: 50%; + `, + flowWrapper: mcss` + height: 50%; + display: flex; `, } diff --git a/websites/workspace/src/styles/index.css.ts b/websites/workspace/src/styles/index.css.ts index 1de55ae..03e2b7c 100644 --- a/websites/workspace/src/styles/index.css.ts +++ b/websites/workspace/src/styles/index.css.ts @@ -12,12 +12,15 @@ injectGlobal` // 优化滚动条样式 injectGlobal` ::-webkit-scrollbar { - width: 4px; + width: 6px; + height: 6px; } /* 设置滚动条的背景颜色 */ ::-webkit-scrollbar-track { background: #f1f1f1; + width: 6px; + height: 6px; } /* 设置滚动条的滑块(滑轮)的颜色 */ diff --git a/websites/workspace/vite.config.ts b/websites/workspace/vite.config.ts index e04230e..1f77244 100644 --- a/websites/workspace/vite.config.ts +++ b/websites/workspace/vite.config.ts @@ -1,9 +1,9 @@ import react from '@vitejs/plugin-react' import * as fs from 'fs' import { resolve } from 'path' -// import { visualizer } from "rollup-plugin-visualizer"; // import basicSsl from '@vitejs/plugin-basic-ssl' import * as path from 'path' +import { visualizer } from 'rollup-plugin-visualizer' import { defineConfig } from 'vite' // https://vitejs.dev/config/ @@ -44,7 +44,10 @@ export default defineConfig({ }, }), // basicSsl() - // visualizer() + visualizer({ + gzipSize: true, + title: '墨斗-打包分析', + }), ], resolve: { alias: { diff --git a/widgets/antd/package.json b/widgets/antd/package.json index 17e67c2..0fc86a1 100644 --- a/widgets/antd/package.json +++ b/widgets/antd/package.json @@ -3,13 +3,13 @@ "version": "1.0.0", "description": "墨斗默认组件库", "scripts": { - "start": "npm run dev", + "start": "pnpm run dev", "dev": "dumi dev", "build": "father build", "build:watch": "father dev", "docs:build": "dumi build", "doctor": "father doctor", - "lint": "npm run lint:es && npm run lint:css", + "lint": "pnpm run lint:es && npm run lint:css", "lint:css": "stylelint \"{src,test}/**/*.{css,less}\"", "lint:es": "eslint \"{src,test}/**/*.{js,jsx,ts,tsx}\"", "prepublishOnly": "father doctor && npm run build" diff --git a/widgets/antd/src/ButtonWidget/constants.ts b/widgets/antd/src/ButtonWidget/constants.ts index e599706..ae9025b 100644 --- a/widgets/antd/src/ButtonWidget/constants.ts +++ b/widgets/antd/src/ButtonWidget/constants.ts @@ -64,7 +64,7 @@ export const typeOptions: MRSelectOptions = [ value: ButtonWidgetTypeEnum.Text, }, { - label: '连接按钮', + label: '链接按钮', value: ButtonWidgetTypeEnum.Link, }, { diff --git a/widgets/antd/src/ButtonWidget/index.tsx b/widgets/antd/src/ButtonWidget/index.tsx index f75a12b..41bcbc8 100644 --- a/widgets/antd/src/ButtonWidget/index.tsx +++ b/widgets/antd/src/ButtonWidget/index.tsx @@ -24,6 +24,7 @@ export const ButtonWidget: FC< }) return (
} + className={classes.wrapper} + style={{ color: theme.colorText }} + > +
+
+ + + onChange({ + ...value, + title: e.target.value, + }) + } + /> + + {value.buildIn && ( + + + popupClassName={classes.selectPopup} + showSearch + filterOption={(input, option) => { + return (`${option?.label}` ?? '') + .toLowerCase() + .includes(input.toLowerCase()) + }} + options={Object.entries(ColumnValueTypeLabelMap).map( + ([value, label]) => ({ + label, + value, + }), + )} + value={value.valueType} + onChange={(v) => + onChange({ + ...value, + valueType: v, + }) + } + /> + + )} + {/* + + onChange({ + ...value, + mappedValue: e.target.value, + }) + } + /> + */} + + + onChange({ + ...value, + align: e.target.value, + }) + } + value={value.align} + > + + + + + + + + + + + + + + + + + + + + onChange({ + ...value, + fixed: e.target.value, + }) + } + value={value.fixed} + > + + + + + + + + + + + + + + + + + + + + onChange({ + ...value, + width: v, + }) + } + /> + +
+
+ + ) : null } > {children} ) } + +const classes = { + tooltipOverlay: mcss` + width: ${320 + 150}px; + max-width: 1000px; + padding-right: 150px; + `, + wrapper: mcss` + `, + + header: mcss` + `, + content: mcss` + .ant-form-item{ + margin-bottom: 12px; + } + .ant-radio-group{ + width: 100%; + display: flex; + .ant-radio-button-wrapper{ + flex: 1; + text-align: center; + } + } + `, + rotate_90: mcss` + transform: rotate(-90deg); + `, + selectPopup: mcss` + z-index: 999999; + `, +} diff --git a/widgets/antd/src/TableWidget/setters/ColumnsSetter/ColumnsSetter.tsx b/widgets/antd/src/TableWidget/setters/ColumnsSetter/ColumnsSetter.tsx index 1b47f6c..fab02e2 100644 --- a/widgets/antd/src/TableWidget/setters/ColumnsSetter/ColumnsSetter.tsx +++ b/widgets/antd/src/TableWidget/setters/ColumnsSetter/ColumnsSetter.tsx @@ -1,74 +1,79 @@ import { DeleteOutlined, + EyeInvisibleOutlined, + EyeOutlined, HolderOutlined, SettingOutlined, } from '@ant-design/icons' -import { useMount, useUpdate } from 'ahooks' -import { Button, Form, Space, Table, Tooltip } from 'antd' +import { useMemoizedFn, useMount, useUpdate } from 'ahooks' +import { Button, Space, Table } from 'antd' import { ColumnsType } from 'antd/es/table' -import produce from 'immer' -import update from 'immutability-helper' +import { JSONSchema7 } from 'json-schema' +import { isBoolean, isEmpty } from 'lodash' import { - FC, - HTMLAttributes, - useCallback, - useMemo, - useRef, - useState, -} from 'react' + InputData, + jsonInputForTargetLanguage, + quicktype, +} from 'quicktype-core' +import { FC, HTMLAttributes, useRef, useState } from 'react' import { createPortal } from 'react-dom' -import { generateId } from '@modou/core' import { mcss, useTheme } from '@modou/css-in-js' -import { BaseSetterProps } from '@modou/setters' +import { remove, runInAction, toJS } from '@modou/reactivity' +import { Observer, observer } from '@modou/reactivity-react' +import { BaseWidgetSetterProps } from '@modou/setters/src/types' +import { InterWidgetProps } from '../../../_' +import { MRSchemeTableWidgetProps } from '../../metadata' +import { MOCK_TABLE_DATA } from '../../mock' import { ColumnValueTypeEnum } from '../../types' import { ColumnSetting } from './ColumnSetting' import { DraggableBodyRow } from './DraggableBodyRow' import { TableWidgetColumn } from './types' +import { generateBuildInColumn, generateDefaultColumn } from './utils' -const generateDefaultColumn = (): TableWidgetColumn => { - return { - dataIndex: generateId(4), - title: '自定义列', - valueType: ColumnValueTypeEnum.text, - buildIn: false, - } -} +async function quicktypeJSON( + targetLanguage: string, + typeName: string, + jsonString: string, +) { + const jsonInput = jsonInputForTargetLanguage(targetLanguage) -export const ColumnsSetter: FC> = ({ - value = [], - onChange, -}) => { - const dataSource = useMemo( - () => - value.map((v, index) => ({ - ...v, - key: v.dataIndex || index, - })), - [value], - ) + // We could add multiple samples for the same desired + // type, or many sources for other types. Here we're + // just making one type from one piece of sample JSON. + await jsonInput.addSource({ + name: typeName, + samples: [jsonString], + }) + + const inputData = new InputData() + inputData.addInput(jsonInput) + + return await quicktype({ + inputData, + lang: targetLanguage, + }) +} +const UOColumnsSetter: FC< + BaseWidgetSetterProps> +> = ({ widget }) => { const components = { body: { row: DraggableBodyRow, }, } - const moveRow = useCallback( - (dragIndex: number, hoverIndex: number) => { - const dragRow = value[dragIndex] - onChange( - update(value, { - $splice: [ - [dragIndex, 1], - [hoverIndex, 0, dragRow], - ], - }), - ) - }, - [onChange, value], - ) + const moveRow = useMemoizedFn((dragIndex: number, hoverIndex: number) => { + const dragRow = widget.meta.props.columns[dragIndex] + runInAction(() => { + widget.meta.props.columns[dragIndex] = + widget.meta.props.columns[hoverIndex] + widget.meta.props.columns[hoverIndex] = dragRow + }) + }) + const wrapperRef = useRef(null) const theme = useTheme() @@ -82,33 +87,51 @@ export const ColumnsSetter: FC> = ({ ?.previousElementSibling const [currentColumnIndex, setCurrentColumnIndex] = useState(0) - const addColumn = () => { - onChange([...value, generateDefaultColumn()]) - setCurrentColumnIndex(value.length) - } - const removeColumn = (index: number) => { - onChange( - produce(value, (draft) => { - draft.splice(index, 1) - }), - ) - } + const addColumn = useMemoizedFn(() => { + runInAction(() => { + const newColumn = generateDefaultColumn(toJS(widget.meta.props.columns)) + const newDynamicSlot: typeof widget.meta.dynamicSlots['1'] = { + name: newColumn.title || newColumn.dataIndex, + children: [], + } + // {{[{id:1}]}} + // widget.meta.dynamicSlots = { + // ...widget.meta.dynamicSlots, + // [newColumn.dataIndex]: newDynamicSlot, + // } + // 销毁被删除组件的 state 按钮-{{表格_hcep.size}} + widget.meta.dynamicSlots[newColumn.dataIndex] = newDynamicSlot + widget.meta.props.columns.push(newColumn) + }) + setCurrentColumnIndex(widget.meta.props.columns.length - 1) + }) + const removeColumn = useMemoizedFn((index: number) => { + runInAction(() => { + const toRemovedColumn = widget.meta.props.columns[index] + widget.meta.props.columns.splice(index, 1) + setTimeout(() => { + remove(widget.meta.dynamicSlots, toRemovedColumn.dataIndex) + }) + }) + }) const editColumn = (index: number) => { setCurrentColumnIndex(index) } - const currentColumn = value[currentColumnIndex] - const updateCurrentColumn = useCallback( - (newColumn: TableWidgetColumn) => { - onChange( - produce(value, (draft) => { - draft[currentColumnIndex] = newColumn - }), - ) - }, - [currentColumnIndex, onChange, value], - ) + const currentColumn = isEmpty(widget.meta.props.columns) + ? ({} as unknown as TableWidgetColumn) + : widget.meta.props.columns[currentColumnIndex] + const updateCurrentColumn = useMemoizedFn((newColumn: TableWidgetColumn) => { + runInAction(() => { + widget.meta.props.columns[currentColumnIndex] = newColumn + }) + }) + + const columnsDataSource = widget.meta.props.columns.map((v, index) => ({ + ...v, + key: v.dataIndex || index, + })) const columns: ColumnsType = [ { @@ -118,17 +141,21 @@ export const ColumnsSetter: FC> = ({ align: 'center', render: (value) => { return ( -
- - - {value} - -
+ + {() => ( +
+ + + {value} + +
+ )} +
) }, }, @@ -137,21 +164,131 @@ export const ColumnsSetter: FC> = ({ dataIndex: 'operation', key: 'operation', align: 'center', - render: (value, record, index) => ( - <> - - - - - + render: (_value, record, index) => ( + + {() => ( + <> + + + + {!record.buildIn && ( + + )} + + {record.buildIn && ( + + )} + + )} + ), }, ] + const testQuickTypeTest = useMemoizedFn(async () => { + const res = await quicktypeJSON( + 'JSON Schema', + 'Root', + JSON.stringify([ + { + name: '张三', + age: 20, + sex: '男', + }, + { + name: '李四', + age: 50, + sex: '女', + }, + ]), + ) + const scheme: JSONSchema7 = JSON.parse(res.lines.join('\n')) + + const columns: TableWidgetColumn[] = [] + + const schemeRoot: JSONSchema7 = scheme.definitions! + .RootElement as unknown as JSONSchema7 + + const usedDataIndex = widget.meta.props.columns.map((v) => v.dataIndex) + Object.entries(schemeRoot.properties!).forEach(([prop, scheme]) => { + // format [date-time, uri] + if (isBoolean(scheme)) { + return + } + if (usedDataIndex.includes(prop)) { + return + } + switch (scheme.type) { + case 'string': + case 'null': { + if (scheme.format === 'date-time') { + columns.push( + generateBuildInColumn({ + dataIndex: prop, + valueType: ColumnValueTypeEnum.dateTime, + }), + ) + } else { + columns.push( + generateBuildInColumn({ + dataIndex: prop, + valueType: ColumnValueTypeEnum.text, + }), + ) + } + break + } + case 'boolean': + columns.push( + generateBuildInColumn({ + dataIndex: prop, + valueType: ColumnValueTypeEnum.checkbox, + }), + ) + break + case 'integer': + columns.push( + generateBuildInColumn({ + dataIndex: prop, + valueType: ColumnValueTypeEnum.digit, + }), + ) + break + default: + } + }) + + runInAction(() => { + widget.meta.props.columns.push(...columns) + }) + }) + return (
> = ({ '--border-color': theme.colorPrimary, }} > + {operationWrapper && createPortal( @@ -170,9 +308,10 @@ export const ColumnsSetter: FC> = ({ operationWrapper, )} + pagination={false} size={'small'} columns={columns} - dataSource={dataSource} + dataSource={columnsDataSource} components={components} onRow={(_, index) => { const attr = { @@ -186,14 +325,15 @@ export const ColumnsSetter: FC> = ({ ) } +export const ColumnsSetter = observer(UOColumnsSetter) const classes = { wrapper: mcss` .ant-table tr.drop-over-downward td { - border-bottom: 2px dashed var(--border-color); + border-bottom: 2px dashed var(--border-color) !important; } .ant-table tr.drop-over-upward td { - border-top: 2px dashed var(--border-color); + border-top: 2px dashed var(--border-color) !important; } `, } diff --git a/widgets/antd/src/TableWidget/setters/ColumnsSetter/utils/index.ts b/widgets/antd/src/TableWidget/setters/ColumnsSetter/utils/index.ts new file mode 100644 index 0000000..fad07a3 --- /dev/null +++ b/widgets/antd/src/TableWidget/setters/ColumnsSetter/utils/index.ts @@ -0,0 +1,54 @@ +import { + ColumnAlignEnum, + ColumnFixedEnum, + ColumnValueTypeEnum, +} from '../../../types' +import { TableWidgetColumn } from '../types' + +const CUSTOM_COLUMN_DATA_INDEX_PREFIX = 'custom_column_' +const CUSTOM_COLUMN_TITLE_PREFIX = '自定义列-' +const NORMAL_WIDTH = -1 +// TODO 从 mr 生成默认值 +export const generateDefaultColumn = ( + columns: TableWidgetColumn[], +): TableWidgetColumn => { + const customColumns = columns.filter((c) => !c.buildIn) + const customColumnNumbers = customColumns.map( + (c) => +c.dataIndex.replace(CUSTOM_COLUMN_DATA_INDEX_PREFIX, ''), + ) + const maxCustomColumnNumber = + customColumnNumbers.length === 0 ? 1 : Math.max(...customColumnNumbers) + 1 + return { + dataIndex: `${CUSTOM_COLUMN_DATA_INDEX_PREFIX}${maxCustomColumnNumber}`, + title: `${CUSTOM_COLUMN_TITLE_PREFIX}${maxCustomColumnNumber}`, + valueType: ColumnValueTypeEnum.text, + buildIn: false, + align: ColumnAlignEnum.left, + fixed: ColumnFixedEnum.false, + width: NORMAL_WIDTH, + mappedValue: '开始-{{Math.random()}}-结束', + hideInTable: false, + } +} +// TODO 从 mr 生成 默认值 +export const generateBuildInColumn = ({ + dataIndex, + valueType, + title, +}: { + dataIndex: string + valueType: ColumnValueTypeEnum + title?: string +}): TableWidgetColumn => { + return { + dataIndex, + title: title ?? dataIndex, + valueType, + buildIn: true, + align: ColumnAlignEnum.left, + fixed: ColumnFixedEnum.false, + width: NORMAL_WIDTH, + mappedValue: null, + hideInTable: false, + } +} diff --git a/widgets/antd/src/TableWidget/types.ts b/widgets/antd/src/TableWidget/types.ts index 1b86c59..47f9937 100644 --- a/widgets/antd/src/TableWidget/types.ts +++ b/widgets/antd/src/TableWidget/types.ts @@ -1,4 +1,80 @@ export enum ColumnValueTypeEnum { + password = 'password', + money = 'money', + textarea = 'textarea', + date = 'date', + dateTime = 'dateTime', + dateWeek = 'dateWeek', + dateMonth = 'dateMonth', + dateQuarter = 'dateQuarter', + dateYear = 'dateYear', + dateRange = 'dateRange', + dateTimeRange = 'dateTimeRange', + time = 'time', + timeRange = 'timeRange', text = 'text', select = 'select', + treeSelect = 'treeSelect', + checkbox = 'checkbox', + rate = 'rate', + radio = 'radio', + radioButton = 'radioButton', + progress = 'progress', + percent = 'percent', + digit = 'digit', + second = 'second', + avatar = 'avatar', + code = 'code', + switch = 'switch', + fromNow = 'fromNow', + image = 'image', + jsonCode = 'jsonCode', + color = 'color', + cascader = 'cascader', +} +export const ColumnValueTypeLabelMap: Record = { + password: '密码输入框', + money: '金额输入框', + textarea: '文本域', + date: '日期', + dateTime: '日期时间', + dateWeek: '周', + dateMonth: '月', + dateQuarter: '季度输入', + dateYear: '年份输入', + dateRange: '日期区间', + dateTimeRange: '日期时间区间', + time: '时间', + timeRange: '时间区间', + text: '文本框', + select: '下拉框', + treeSelect: '树形下拉框', + checkbox: '多选框', + rate: '星级组件', + radio: '单选框', + radioButton: '按钮单选框', + progress: '进度条', + percent: '百分比组件', + digit: '数字输入框', + second: '秒格式化', + avatar: '头像', + code: '代码框', + switch: '开关', + fromNow: '相对于当前时间', + image: '图片', + jsonCode: '代码框,但是带了 json 格式化', + color: '颜色选择器', + cascader: '级联选择器', +} + +export enum ColumnAlignEnum { + left = 'left', + center = 'center', + right = 'right', +} + +export enum ColumnFixedEnum { + left = 'left', + right = 'right', + false = '', } diff --git a/widgets/antd/src/_/types.ts b/widgets/antd/src/_/types.ts index 691d11a..3c1602b 100644 --- a/widgets/antd/src/_/types.ts +++ b/widgets/antd/src/_/types.ts @@ -1,6 +1,6 @@ // import { SelectSetter } from '@modou/setters' // export type MRSelectOptions = Parameters[0]['options'] -import { FC, ReactNode } from 'react' +import { ReactNode } from 'react' import { MRTypeAny, mr } from '@modou/refine'