From 0c6dfdd06beeacc2bf47e8b070cb9c79b6400b38 Mon Sep 17 00:00:00 2001 From: Bowen Tan Date: Mon, 9 Jan 2023 18:14:15 +0800 Subject: [PATCH] feat(editor): event widget support choose $module as target --- .../src/components/Widgets/EventWidget.tsx | 55 +++++++++++++++++-- packages/editor-sdk/src/types/editor.ts | 5 ++ packages/runtime/src/services/StateManager.ts | 6 +- packages/runtime/src/utils/runEventHandler.ts | 6 +- packages/shared/src/constants/expression.ts | 1 + packages/shared/src/specs/module.ts | 5 ++ 6 files changed, 70 insertions(+), 8 deletions(-) diff --git a/packages/editor-sdk/src/components/Widgets/EventWidget.tsx b/packages/editor-sdk/src/components/Widgets/EventWidget.tsx index a4eb370f..4706fec7 100644 --- a/packages/editor-sdk/src/components/Widgets/EventWidget.tsx +++ b/packages/editor-sdk/src/components/Widgets/EventWidget.tsx @@ -14,6 +14,8 @@ import { CoreWidgetName, generateDefaultValueFromSpec, MountEvents, + GLOBAL_MODULE_ID, + ModuleEventMethodSpec, } from '@sunmao-ui/shared'; import { JSONSchema7Object } from 'json-schema'; import { PREVENT_POPOVER_WIDGET_CLOSE_CLASS } from '../../constants/widget'; @@ -32,7 +34,7 @@ declare module '../../types/widget' { export const EventWidget: React.FC> = observer(props => { const { value, path, level, component, spec, services, onChange } = props; const { registry, editorStore, appModelManager } = services; - const { components } = editorStore; + const { components, currentEditingTarget } = editorStore; const utilMethods = useMemo(() => registry.getAllUtilMethods(), [registry]); const [methods, setMethods] = useState([]); @@ -62,9 +64,22 @@ export const EventWidget: React.FC> = observer(prop }, [registry] ); + const eventTypes = useMemo(() => { - return [...registry.getComponentByType(component.type).spec.events, ...MountEvents]; - }, [component.type, registry]); + let moduleEvents: string[] = []; + if (component.type === 'core/v1/moduleContainer') { + // if component is moduleContainer, add module events to it + const moduleType = component.properties.type as string; + const moduleSpec = registry.getModuleByType(moduleType); + moduleEvents = moduleSpec.spec.events; + } + return [ + ...registry.getComponentByType(component.type).spec.events, + ...moduleEvents, + ...MountEvents, + ]; + }, [component.properties.type, component.type, registry]); + const hasParams = useMemo( () => Object.keys(formik.values.method.parameters ?? {}).length, [formik.values.method.parameters] @@ -79,6 +94,8 @@ export const EventWidget: React.FC> = observer(prop const targetMethod = registry.getUtilMethodByType(methodType)!; spec = targetMethod.spec.parameters; + } else if (componentId === GLOBAL_MODULE_ID) { + spec = ModuleEventMethodSpec; } else { const targetComponent = appModelManager.appModel.getComponentById(componentId); const targetMethod = (findMethodsByComponent(targetComponent) ?? []).find( @@ -140,19 +157,42 @@ export const EventWidget: React.FC> = observer(prop utilMethod => `${utilMethod.version}/${utilMethod.metadata.name}` ) ); + } else if ( + componentId === GLOBAL_MODULE_ID && + currentEditingTarget.kind === 'module' + ) { + // if user is editing module, show the events of module spec as method + const moduleType = `${currentEditingTarget.version}/${currentEditingTarget.name}`; + let methodNames: string[] = []; + if (moduleType) { + const moduleSpec = services.registry.getModuleByType(moduleType); + + if (moduleSpec) { + methodNames = moduleSpec.spec.events; + } + } + setMethods(methodNames); } else { + // if user is editing application, show methods of component const component = components.find(c => c.id === componentId); if (component) { const methodNames: string[] = findMethodsByComponent(component).map( ({ name }) => name ); - setMethods(methodNames); } } }, - [components, utilMethods, findMethodsByComponent] + [ + currentEditingTarget.kind, + currentEditingTarget.version, + currentEditingTarget.name, + utilMethods, + services.registry, + components, + findMethodsByComponent, + ] ); useEffect(() => { @@ -235,6 +275,11 @@ export const EventWidget: React.FC> = observer(prop style={{ width: '100%' }} value={formik.values.componentId === '' ? undefined : formik.values.componentId} > + {currentEditingTarget.kind === 'module' ? ( + + {GLOBAL_MODULE_ID} + + ) : undefined} {[{ id: GLOBAL_UTIL_METHOD_ID }].concat(components).map(c => ( {c.id} diff --git a/packages/editor-sdk/src/types/editor.ts b/packages/editor-sdk/src/types/editor.ts index 24df5b88..5e14690f 100644 --- a/packages/editor-sdk/src/types/editor.ts +++ b/packages/editor-sdk/src/types/editor.ts @@ -7,6 +7,11 @@ export interface EditorServicesInterface extends UIServices { registry: RegistryInterface; editorStore: { components: ComponentSchema[]; + currentEditingTarget: { + kind: 'app' | 'module'; + version: string; + name: string; + }; }; appModelManager: { appModel: any; diff --git a/packages/runtime/src/services/StateManager.ts b/packages/runtime/src/services/StateManager.ts index 8f944058..155dc6f2 100644 --- a/packages/runtime/src/services/StateManager.ts +++ b/packages/runtime/src/services/StateManager.ts @@ -18,7 +18,7 @@ dayjs.locale('zh-cn'); type EvalOptions = { scopeObject?: Record; overrideScope?: boolean; - fallbackWhenError?: (exp: string) => any; + fallbackWhenError?: (exp: string, err: Error) => any; // when ignoreEvalError is true, the eval process will continue after error happens in nests expression. ignoreEvalError?: boolean; slotKey?: string; @@ -128,7 +128,9 @@ export class StateManager { consoleError(ConsoleType.Expression, raw, expressionError.message); } - return fallbackWhenError ? fallbackWhenError(raw) : expressionError; + return fallbackWhenError + ? fallbackWhenError(raw, expressionError) + : expressionError; } return undefined; } diff --git a/packages/runtime/src/utils/runEventHandler.ts b/packages/runtime/src/utils/runEventHandler.ts index 115bcaa6..702bfa1f 100644 --- a/packages/runtime/src/utils/runEventHandler.ts +++ b/packages/runtime/src/utils/runEventHandler.ts @@ -1,6 +1,6 @@ import { Static, Type } from '@sinclair/typebox'; import { debounce, throttle, delay } from 'lodash'; -import { EventCallBackHandlerSpec } from '@sunmao-ui/shared'; +import { EventCallBackHandlerSpec, MODULE_ID_EXP } from '@sunmao-ui/shared'; import { type PropsBeforeEvaled } from '@sunmao-ui/core'; import { UIServices } from '../types'; @@ -18,6 +18,10 @@ export const runEventHandler = ( // Eval before sending event to assure the handler object is evaled from the latest state. const evalOptions = { slotKey, + // keep MODULE_ID_EXP when error + fallbackWhenError(exp: string, err: Error) { + return exp === MODULE_ID_EXP ? exp : err; + }, }; const evaledHandlers = stateManager.deepEval(rawHandlers, evalOptions) as Static< typeof EventCallBackHandlerSpec diff --git a/packages/shared/src/constants/expression.ts b/packages/shared/src/constants/expression.ts index 8de94eca..4435aeb1 100644 --- a/packages/shared/src/constants/expression.ts +++ b/packages/shared/src/constants/expression.ts @@ -7,6 +7,7 @@ export const LIST_ITEM_INDEX_EXP = '$i'; export const SLOT_PROPS_EXP = '$slot'; export const GLOBAL_UTIL_METHOD_ID = '$utils'; export const GLOBAL_MODULE_ID = '$module'; +export const MODULE_ID_EXP = '{{$moduleId}}'; export const ExpressionKeywords = [ LIST_ITEM_EXP, diff --git a/packages/shared/src/specs/module.ts b/packages/shared/src/specs/module.ts index 389f747b..7d6649db 100644 --- a/packages/shared/src/specs/module.ts +++ b/packages/shared/src/specs/module.ts @@ -1,6 +1,7 @@ import { EventHandlerSpec } from './event'; import { Type } from '@sinclair/typebox'; import { CORE_VERSION, CoreWidgetName } from '../constants/core'; +import { MODULE_ID_EXP } from '../constants'; export const ModuleRenderSpec = Type.Object( { @@ -27,3 +28,7 @@ export const ModuleRenderSpec = Type.Object( widget: 'core/v1/module', } ); + +export const ModuleEventMethodSpec = Type.Object({ + moduleId: Type.Literal(MODULE_ID_EXP), +});