From a25a86b48dd51dc0c3c41db3eb3a845f98f91c8d Mon Sep 17 00:00:00 2001 From: MrWindlike Date: Thu, 16 Jun 2022 21:06:11 +0800 Subject: [PATCH] feat: auto detect the raw properties types --- .../arco-lib/src/components/Table/Table.tsx | 14 ++++--- packages/arco-lib/src/sunmao-helper.ts | 4 +- .../src/components/Table/Table.tsx | 12 +----- .../src/components/Table/TableTd.tsx | 16 +++++-- packages/core/package.json | 1 + packages/core/src/application.ts | 22 ++++++---- packages/core/tsconfig.json | 1 + .../components/_internal/ModuleRenderer.tsx | 8 ++-- packages/runtime/src/services/StateManager.ts | 16 ++++--- packages/runtime/src/traits/core/Event.tsx | 41 ++++++++++++++---- packages/runtime/src/traits/core/Fetch.tsx | 42 ++++++------------- packages/runtime/src/types/component.ts | 18 +++++--- packages/runtime/src/types/trait.ts | 6 +-- packages/runtime/src/utils/buildKit.ts | 4 +- packages/shared/src/types/index.ts | 1 + packages/shared/src/types/utils.ts | 12 ++++++ 16 files changed, 131 insertions(+), 87 deletions(-) create mode 100644 packages/shared/src/types/utils.ts diff --git a/packages/arco-lib/src/components/Table/Table.tsx b/packages/arco-lib/src/components/Table/Table.tsx index 644dd1a0..a65668fe 100644 --- a/packages/arco-lib/src/components/Table/Table.tsx +++ b/packages/arco-lib/src/components/Table/Table.tsx @@ -309,12 +309,14 @@ export const Table = implementRuntimeComponent({ switch (evaledColumn.type) { case 'button': const handleClick = () => { - const rawColumn = (component.properties.columns as ColumnProperty[])[i]; - if (!rawColumn.btnCfg) return; - const evaledButtonConfig = services.stateManager.deepEval( - rawColumn.btnCfg, - evalOptions - ); + const rawColumns = component.properties.columns; + const evaledColumns = + typeof rawColumns === 'string' + ? (services.stateManager.maskedEval(rawColumns) as ColumnProperty[]) + : services.stateManager.deepEval(rawColumns, evalOptions); + const evaledButtonConfig = evaledColumns[i].btnCfg; + + if (!evaledButtonConfig) return; evaledButtonConfig.handlers.forEach(handler => { services.apiService.send('uiMethod', { diff --git a/packages/arco-lib/src/sunmao-helper.ts b/packages/arco-lib/src/sunmao-helper.ts index c9d9a795..a0e33dcc 100644 --- a/packages/arco-lib/src/sunmao-helper.ts +++ b/packages/arco-lib/src/sunmao-helper.ts @@ -32,14 +32,14 @@ export const FALLBACK_METADATA: ComponentMetadata = { }; export const getComponentProps = < - T, + T extends Record, TState, TMethods, TSlots extends Record, KStyleSlot extends string, KEvent extends string >( - props: T & ComponentImplProps + props: T & ComponentImplProps ) => { const { /* eslint-disable @typescript-eslint/no-unused-vars */ diff --git a/packages/chakra-ui-lib/src/components/Table/Table.tsx b/packages/chakra-ui-lib/src/components/Table/Table.tsx index adb98b9a..20a8d535 100644 --- a/packages/chakra-ui-lib/src/components/Table/Table.tsx +++ b/packages/chakra-ui-lib/src/components/Table/Table.tsx @@ -15,8 +15,6 @@ import { TablePagination } from './Pagination'; import { TableTd } from './TableTd'; import { implementTable } from './spec'; -import { ColumnsPropertySpec } from './TableTypes'; -import { Static } from '@sinclair/typebox'; type SortRule = { key: string; @@ -187,20 +185,14 @@ export const TableImpl = implementTable( }} > {isMultiSelect ? checkbox : undefined} - {columns.map((column, j) => ( + {columns.map(column => ( selectItem(item)} - rawColumn={ - ( - component.properties.columns as Static< - typeof ColumnsPropertySpec - > - )[j] - } + rawColumns={component.properties.columns} column={column} services={services} app={app} diff --git a/packages/chakra-ui-lib/src/components/Table/TableTd.tsx b/packages/chakra-ui-lib/src/components/Table/TableTd.tsx index be72e157..5ab5abf6 100644 --- a/packages/chakra-ui-lib/src/components/Table/TableTd.tsx +++ b/packages/chakra-ui-lib/src/components/Table/TableTd.tsx @@ -11,18 +11,20 @@ import { ExpressionError, ImplWrapper, } from '@sunmao-ui/runtime'; +import { PropsBeforeEvaled } from '@sunmao-ui/shared'; export const TableTd: React.FC<{ index: number; item: any; column: Static; - rawColumn: Static[0]; + rawColumns: string | PropsBeforeEvaled>; onClickItem: () => void; services: UIServices; component: RuntimeComponentSchema; app: RuntimeApplication; }> = props => { - const { item, index, component, column, rawColumn, onClickItem, services, app } = props; + const { item, index, component, column, rawColumns, onClickItem, services, app } = + props; const evalOptions = { evalListItem: true, scopeObject: { @@ -61,8 +63,14 @@ export const TableTd: React.FC<{ case 'button': const onClick = () => { onClickItem(); - rawColumn.buttonConfig.handlers.forEach(handler => { - const evaledHandler = services.stateManager.deepEval(handler, evalOptions); + const evaledColumns = + typeof rawColumns === 'string' + ? (services.stateManager.maskedEval(rawColumns, evalOptions) as Static< + typeof ColumnsPropertySpec + >) + : services.stateManager.deepEval(rawColumns, evalOptions); + + evaledColumns[index].buttonConfig.handlers.forEach(evaledHandler => { services.apiService.send('uiMethod', { componentId: evaledHandler.componentId, name: evaledHandler.method.name, diff --git a/packages/core/package.json b/packages/core/package.json index 3f1407df..f9ee51b8 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -35,6 +35,7 @@ "lodash": "^4.17.21" }, "devDependencies": { + "@sunmao-ui/shared": "^0.1.0", "@swc/core": "^1.2.121", "@types/jest": "^26.0.23", "@types/json-schema": "^7.0.7", diff --git a/packages/core/src/application.ts b/packages/core/src/application.ts index 609fa414..8efd14d2 100644 --- a/packages/core/src/application.ts +++ b/packages/core/src/application.ts @@ -1,6 +1,6 @@ import { Metadata } from './metadata'; import { parseVersion, Version } from './version'; - +import { type PropsBeforeEvaled } from '@sunmao-ui/shared'; // spec export type Application = { @@ -12,31 +12,35 @@ export type Application = { }; }; -export type ComponentSchema = { +export type ComponentSchema> = { id: string; type: string; // do runtime type check - properties: Record; + properties: PropsBeforeEvaled; traits: TraitSchema[]; // scopes TBD }; -export type TraitSchema = { +export type TraitSchema> = { type: string; // do runtime type check - properties: Record; + properties: PropsBeforeEvaled; }; -export type RuntimeTraitSchema = TraitSchema & { - parsedType: VersionAndName; -} +export type RuntimeTraitSchema> = + TraitSchema & { + parsedType: VersionAndName; + }; type VersionAndName = { version: string; name: string; }; -export type RuntimeComponentSchema = Omit & { +export type RuntimeComponentSchema> = Omit< + ComponentSchema, + 'traits' +> & { parsedType: VersionAndName; traits: RuntimeTraitSchema[]; }; diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index 9feeba8a..07eed940 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -7,6 +7,7 @@ "composite": true, "rootDir": "src", "module": "ESNext", + "moduleResolution": "Node", "declaration": true, "declarationDir": "lib" }, diff --git a/packages/runtime/src/components/_internal/ModuleRenderer.tsx b/packages/runtime/src/components/_internal/ModuleRenderer.tsx index 0af58e74..2f76ebdd 100644 --- a/packages/runtime/src/components/_internal/ModuleRenderer.tsx +++ b/packages/runtime/src/components/_internal/ModuleRenderer.tsx @@ -11,7 +11,7 @@ import { import { ImplWrapper } from './ImplWrapper'; import { watch } from '../../utils/watchReactivity'; import { ImplementedRuntimeModule, UIServices } from '../../types'; -import { EventHandlerSpec, ModuleRenderSpec } from '@sunmao-ui/shared'; +import { EventHandlerSpec, ModuleRenderSpec, PropsAfterEvaled } from '@sunmao-ui/shared'; import { resolveChildrenMap } from '../../utils/resolveChildrenMap'; import { initStateAndMethod } from '../../utils/initStateAndMethod'; import { ExpressionError } from '../../services/StateManager'; @@ -42,9 +42,11 @@ const ModuleRendererContent = React.forwardRef< scopeObject: evalScope, }) as string | ExpressionError; - function evalObject>(obj: T): T { + function evalObject = Record>( + obj: T + ): PropsAfterEvaled<{ obj: T }>['obj'] { const evalOptions = { evalListItem: true, scopeObject: evalScope }; - return services.stateManager.mapValuesDeep({ obj }, ({ value }) => { + return services.stateManager.mapValuesDeep(obj, ({ value }) => { if (typeof value === 'string') { return services.stateManager.maskedEval(value, evalOptions); } diff --git a/packages/runtime/src/services/StateManager.ts b/packages/runtime/src/services/StateManager.ts index 9139baa0..c792ac31 100644 --- a/packages/runtime/src/services/StateManager.ts +++ b/packages/runtime/src/services/StateManager.ts @@ -13,6 +13,7 @@ import { consoleError, ConsoleType, ExpChunk, + PropsAfterEvaled, } from '@sunmao-ui/shared'; dayjs.extend(relativeTime); @@ -142,7 +143,7 @@ export class StateManager { path: Array; }) => void, path: Array = [] - ): T { + ): PropsAfterEvaled { return mapValues(obj, (val, key: string | number) => { return isArray(val) ? val.map((innerVal, idx) => { @@ -153,10 +154,13 @@ export class StateManager { : isPlainObject(val) ? this.mapValuesDeep(val as unknown as T, fn, path.concat(key)) : fn({ value: val, key, obj, path: path.concat(key) }); - }) as T; + }) as PropsAfterEvaled; } - deepEval>(obj: T, options: EvalOptions = {}): T { + deepEval | any[]>( + obj: T, + options: EvalOptions = {} + ): PropsAfterEvaled { // just eval const evaluated = this.mapValuesDeep(obj, ({ value }) => { if (typeof value !== 'string') { @@ -168,9 +172,9 @@ export class StateManager { return evaluated; } - deepEvalAndWatch>( + deepEvalAndWatch | any[]>( obj: T, - watcher: (params: { result: T }) => void, + watcher: (params: { result: PropsAfterEvaled }) => void, options: EvalOptions = {} ) { const stops: ReturnType[] = []; @@ -179,7 +183,7 @@ export class StateManager { const evaluated = this.deepEval(obj, options); // watch change - let resultCache: T = evaluated; + let resultCache: PropsAfterEvaled = evaluated; this.mapValuesDeep(obj, ({ value, path }) => { const isDynamicExpression = typeof value === 'string' && diff --git a/packages/runtime/src/traits/core/Event.tsx b/packages/runtime/src/traits/core/Event.tsx index f5359b8c..f6cce871 100644 --- a/packages/runtime/src/traits/core/Event.tsx +++ b/packages/runtime/src/traits/core/Event.tsx @@ -2,25 +2,42 @@ import { Static, Type } from '@sinclair/typebox'; import { debounce, throttle, delay } from 'lodash'; import { CallbackMap, UIServices } from '../../types'; import { implementRuntimeTrait } from '../../utils/buildKit'; -import { EventHandlerSpec, CORE_VERSION, CoreTraitName } from '@sunmao-ui/shared'; +import { + EventHandlerSpec, + EventCallBackHandlerSpec, + CORE_VERSION, + CoreTraitName, + PropsBeforeEvaled, +} from '@sunmao-ui/shared'; +const HandlersSpec = Type.Array(EventHandlerSpec); +const CallbackSpec = Type.Array(EventCallBackHandlerSpec); export const EventTraitPropertiesSpec = Type.Object({ - handlers: Type.Array(EventHandlerSpec), + handlers: HandlersSpec, }); export const generateCallback = ( - handler: Omit, 'type'>, - rawHandler: Static, + handler: Omit, 'type'>, + rawHandlers: string | PropsBeforeEvaled>, + index: number, services: UIServices, slotProps?: unknown, evalListItem?: boolean ) => { + const { stateManager } = services; const send = () => { // Eval before sending event to assure the handler object is evaled from the latest state. - const evaledHandler = services.stateManager.deepEval(rawHandler, { + const evalOptions = { scopeObject: { $slot: slotProps }, evalListItem, - }); + }; + const evaledHandlers = + typeof rawHandlers === 'string' + ? (stateManager.maskedEval(rawHandlers, evalOptions) as Static< + typeof EventCallBackHandlerSpec + >[]) + : stateManager.deepEval(rawHandlers); + const evaledHandler = evaledHandlers[index]; if (evaledHandler.disabled && typeof evaledHandler.disabled === 'boolean') { return; @@ -61,15 +78,23 @@ export default implementRuntimeTrait({ })(() => { return ({ trait, handlers, services, slotProps, evalListItem }) => { const callbackQueueMap: Record void>> = {}; - const rawHandlers = trait.properties.handlers as Static[]; + const rawHandlers = trait.properties.handlers; // setup current handlers for (const i in handlers) { const handler = handlers[i]; + if (!callbackQueueMap[handler.type]) { callbackQueueMap[handler.type] = []; } callbackQueueMap[handler.type].push( - generateCallback(handler, rawHandlers[i], services, slotProps, evalListItem) + generateCallback( + handler, + rawHandlers, + Number(i), + services, + slotProps, + evalListItem + ) ); } diff --git a/packages/runtime/src/traits/core/Fetch.tsx b/packages/runtime/src/traits/core/Fetch.tsx index 0f4c863d..af434367 100644 --- a/packages/runtime/src/traits/core/Fetch.tsx +++ b/packages/runtime/src/traits/core/Fetch.tsx @@ -1,8 +1,7 @@ -import { Static, Type } from '@sinclair/typebox'; +import { Type } from '@sinclair/typebox'; import { consoleWarn, ConsoleType, - EventHandlerSpec, EventCallBackHandlerSpec, CoreTraitName, CORE_VERSION, @@ -155,15 +154,10 @@ export default implementRuntimeTrait({ error: undefined, }, }); - const rawOnComplete = trait.properties.onComplete as Static< - typeof FetchTraitPropertiesSpec - >['onComplete']; - rawOnComplete?.forEach((rawHandler, index) => { - generateCallback( - onComplete![index], - rawHandler as Static, - services - )(); + const rawOnComplete = trait.properties.onComplete; + + onComplete?.forEach((_, index) => { + generateCallback(onComplete[index], rawOnComplete, index, services)(); }); } else { // TODO: Add FetchError class and remove console info @@ -177,15 +171,10 @@ export default implementRuntimeTrait({ error, }, }); - const rawOnError = trait.properties.onError as Static< - typeof FetchTraitPropertiesSpec - >['onError']; - rawOnError?.forEach((rawHandler, index) => { - generateCallback( - onError![index], - rawHandler as Static, - services - )(); + const rawOnError = trait.properties.onError; + + onError?.forEach((_, index) => { + generateCallback(onError[index], rawOnError, index, services)(); }); } }, @@ -201,15 +190,10 @@ export default implementRuntimeTrait({ error: error.toString(), }, }); - const rawOnError = trait.properties.onError as Static< - typeof FetchTraitPropertiesSpec - >['onError']; - rawOnError?.forEach((rawHandler, index) => { - generateCallback( - onError![index], - rawHandler as Static, - services - )(); + const rawOnError = trait.properties.onError; + + onError?.forEach((_, index) => { + generateCallback(onError[index], rawOnError, index, services)(); }); } ); diff --git a/packages/runtime/src/types/component.ts b/packages/runtime/src/types/component.ts index 3e920a77..f42db3ee 100644 --- a/packages/runtime/src/types/component.ts +++ b/packages/runtime/src/types/component.ts @@ -10,8 +10,11 @@ import { UIServices, ComponentParamsFromApp } from './application'; import { TraitResult } from './trait'; // TODO: (type-safe), remove fallback type -export type ImplWrapperProps = { - component: RuntimeComponentSchema; +export type ImplWrapperProps< + TProps extends Record = Record, + KSlot extends string = string +> = { + component: RuntimeComponentSchema; childrenMap: ChildrenMap; services: UIServices; isInModule: boolean; @@ -21,12 +24,13 @@ export type ImplWrapperProps = { } & ComponentParamsFromApp; export type ComponentImplProps< + TProps extends Record, TState, TMethods, TSlots extends Record, KStyleSlot extends string, KEvent extends string -> = ImplWrapperProps & +> = ImplWrapperProps & TraitResult['props'] & RuntimeFunctions & { elementRef?: React.MutableRefObject; @@ -34,13 +38,15 @@ export type ComponentImplProps< }; export type ComponentImpl< - TProps = any, + TProps extends Record = Record, TState = any, TMethods = Record, TSlots extends Record = Record, KStyleSlot extends string = string, KEvent extends string = string -> = React.FC>; +> = React.FC< + TProps & ComponentImplProps +>; export type ImplementedRuntimeComponent< KMethodName extends string, @@ -48,7 +54,7 @@ export type ImplementedRuntimeComponent< KSlot extends string, KEvent extends string > = RuntimeComponent & { - impl: ComponentImpl; + impl: ComponentImpl; }; export type ChildrenMap = Record< diff --git a/packages/runtime/src/types/trait.ts b/packages/runtime/src/types/trait.ts index 571c0170..90de41be 100644 --- a/packages/runtime/src/types/trait.ts +++ b/packages/runtime/src/types/trait.ts @@ -14,10 +14,10 @@ export type TraitResult = { unmount?: boolean; }; -export type TraitImpl = ( - props: T & +export type TraitImpl = ( + props: TProperties & RuntimeFunctions & { - trait: RuntimeTraitSchema; + trait: RuntimeTraitSchema; componentId: string; services: UIServices; evalListItem?: boolean; diff --git a/packages/runtime/src/utils/buildKit.ts b/packages/runtime/src/utils/buildKit.ts index 7ec862dc..7e595c9a 100644 --- a/packages/runtime/src/utils/buildKit.ts +++ b/packages/runtime/src/utils/buildKit.ts @@ -33,7 +33,9 @@ export function implementRuntimeComponent< options: T ): ( impl: ComponentImpl< - Static, + Static extends Record + ? Static + : Record, Static, ToMap, T['spec']['slots'], diff --git a/packages/shared/src/types/index.ts b/packages/shared/src/types/index.ts index 6e533121..0aa73efa 100644 --- a/packages/shared/src/types/index.ts +++ b/packages/shared/src/types/index.ts @@ -1,2 +1,3 @@ export * from './spec'; export * from './condition'; +export * from './utils'; diff --git a/packages/shared/src/types/utils.ts b/packages/shared/src/types/utils.ts new file mode 100644 index 00000000..28771726 --- /dev/null +++ b/packages/shared/src/types/utils.ts @@ -0,0 +1,12 @@ +// add the string type to every properties because it may be a expression +export type PropsBeforeEvaled = T extends Record | any[] + ? { [K in keyof T]: PropsBeforeEvaled | string } + : T; +// remove the string type for every properties +export type PropsAfterEvaled = T extends Record | any[] + ? { + [K in keyof T]: T[K] extends string | infer O + ? PropsAfterEvaled + : PropsAfterEvaled; + } + : T;