mirror of
https://github.com/smartxworks/sunmao-ui.git
synced 2024-11-21 03:15:49 +08:00
feat: auto detect the raw properties types
This commit is contained in:
parent
8a7028ce56
commit
a25a86b48d
@ -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', {
|
||||
|
@ -32,14 +32,14 @@ export const FALLBACK_METADATA: ComponentMetadata = {
|
||||
};
|
||||
|
||||
export const getComponentProps = <
|
||||
T,
|
||||
T extends Record<string, unknown>,
|
||||
TState,
|
||||
TMethods,
|
||||
TSlots extends Record<string, SlotSpec>,
|
||||
KStyleSlot extends string,
|
||||
KEvent extends string
|
||||
>(
|
||||
props: T & ComponentImplProps<TState, TMethods, TSlots, KStyleSlot, KEvent>
|
||||
props: T & ComponentImplProps<T, TState, TMethods, TSlots, KStyleSlot, KEvent>
|
||||
) => {
|
||||
const {
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
|
@ -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 => (
|
||||
<TableTd
|
||||
index={i}
|
||||
component={component}
|
||||
key={column.key}
|
||||
item={item}
|
||||
onClickItem={() => selectItem(item)}
|
||||
rawColumn={
|
||||
(
|
||||
component.properties.columns as Static<
|
||||
typeof ColumnsPropertySpec
|
||||
>
|
||||
)[j]
|
||||
}
|
||||
rawColumns={component.properties.columns}
|
||||
column={column}
|
||||
services={services}
|
||||
app={app}
|
||||
|
@ -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<typeof ColumnSpec>;
|
||||
rawColumn: Static<typeof ColumnsPropertySpec>[0];
|
||||
rawColumns: string | PropsBeforeEvaled<Static<typeof ColumnsPropertySpec>>;
|
||||
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,
|
||||
|
@ -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",
|
||||
|
@ -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<CProperties = Record<string, unknown>> = {
|
||||
id: string;
|
||||
type: string;
|
||||
// do runtime type check
|
||||
properties: Record<string, unknown>;
|
||||
properties: PropsBeforeEvaled<CProperties>;
|
||||
traits: TraitSchema[];
|
||||
// scopes TBD
|
||||
};
|
||||
|
||||
export type TraitSchema = {
|
||||
export type TraitSchema<TProperties = Record<string, unknown>> = {
|
||||
type: string;
|
||||
// do runtime type check
|
||||
properties: Record<string, unknown>;
|
||||
properties: PropsBeforeEvaled<TProperties>;
|
||||
};
|
||||
|
||||
export type RuntimeTraitSchema = TraitSchema & {
|
||||
parsedType: VersionAndName;
|
||||
}
|
||||
export type RuntimeTraitSchema<TProperties = Record<string, unknown>> =
|
||||
TraitSchema<TProperties> & {
|
||||
parsedType: VersionAndName;
|
||||
};
|
||||
|
||||
type VersionAndName = {
|
||||
version: string;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type RuntimeComponentSchema = Omit<ComponentSchema, 'traits'> & {
|
||||
export type RuntimeComponentSchema<CProperties = Record<string, unknown>> = Omit<
|
||||
ComponentSchema<CProperties>,
|
||||
'traits'
|
||||
> & {
|
||||
parsedType: VersionAndName;
|
||||
traits: RuntimeTraitSchema[];
|
||||
};
|
||||
|
@ -7,6 +7,7 @@
|
||||
"composite": true,
|
||||
"rootDir": "src",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"declaration": true,
|
||||
"declarationDir": "lib"
|
||||
},
|
||||
|
@ -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<T extends Record<string, any>>(obj: T): T {
|
||||
function evalObject<T extends Record<string, any> = Record<string, any>>(
|
||||
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);
|
||||
}
|
||||
|
@ -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<string | number>;
|
||||
}) => void,
|
||||
path: Array<string | number> = []
|
||||
): T {
|
||||
): PropsAfterEvaled<T> {
|
||||
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<T>;
|
||||
}
|
||||
|
||||
deepEval<T extends Record<string, unknown>>(obj: T, options: EvalOptions = {}): T {
|
||||
deepEval<T extends Record<string, unknown> | any[]>(
|
||||
obj: T,
|
||||
options: EvalOptions = {}
|
||||
): PropsAfterEvaled<T> {
|
||||
// just eval
|
||||
const evaluated = this.mapValuesDeep(obj, ({ value }) => {
|
||||
if (typeof value !== 'string') {
|
||||
@ -168,9 +172,9 @@ export class StateManager {
|
||||
return evaluated;
|
||||
}
|
||||
|
||||
deepEvalAndWatch<T extends Record<string, unknown>>(
|
||||
deepEvalAndWatch<T extends Record<string, unknown> | any[]>(
|
||||
obj: T,
|
||||
watcher: (params: { result: T }) => void,
|
||||
watcher: (params: { result: PropsAfterEvaled<T> }) => void,
|
||||
options: EvalOptions = {}
|
||||
) {
|
||||
const stops: ReturnType<typeof watch>[] = [];
|
||||
@ -179,7 +183,7 @@ export class StateManager {
|
||||
const evaluated = this.deepEval(obj, options);
|
||||
|
||||
// watch change
|
||||
let resultCache: T = evaluated;
|
||||
let resultCache: PropsAfterEvaled<T> = evaluated;
|
||||
this.mapValuesDeep(obj, ({ value, path }) => {
|
||||
const isDynamicExpression =
|
||||
typeof value === 'string' &&
|
||||
|
@ -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<Static<typeof EventHandlerSpec>, 'type'>,
|
||||
rawHandler: Static<typeof EventHandlerSpec>,
|
||||
handler: Omit<Static<typeof EventCallBackHandlerSpec>, 'type'>,
|
||||
rawHandlers: string | PropsBeforeEvaled<Static<typeof CallbackSpec>>,
|
||||
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<string, Array<() => void>> = {};
|
||||
const rawHandlers = trait.properties.handlers as Static<typeof EventHandlerSpec>[];
|
||||
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
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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<typeof EventHandlerSpec>,
|
||||
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<typeof EventHandlerSpec>,
|
||||
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<typeof EventHandlerSpec>,
|
||||
services
|
||||
)();
|
||||
const rawOnError = trait.properties.onError;
|
||||
|
||||
onError?.forEach((_, index) => {
|
||||
generateCallback(onError[index], rawOnError, index, services)();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
@ -10,8 +10,11 @@ import { UIServices, ComponentParamsFromApp } from './application';
|
||||
import { TraitResult } from './trait';
|
||||
|
||||
// TODO: (type-safe), remove fallback type
|
||||
export type ImplWrapperProps<KSlot extends string = string> = {
|
||||
component: RuntimeComponentSchema;
|
||||
export type ImplWrapperProps<
|
||||
TProps extends Record<string, unknown> = Record<string, unknown>,
|
||||
KSlot extends string = string
|
||||
> = {
|
||||
component: RuntimeComponentSchema<TProps>;
|
||||
childrenMap: ChildrenMap<KSlot>;
|
||||
services: UIServices;
|
||||
isInModule: boolean;
|
||||
@ -21,12 +24,13 @@ export type ImplWrapperProps<KSlot extends string = string> = {
|
||||
} & ComponentParamsFromApp;
|
||||
|
||||
export type ComponentImplProps<
|
||||
TProps extends Record<string, unknown>,
|
||||
TState,
|
||||
TMethods,
|
||||
TSlots extends Record<string, SlotSpec>,
|
||||
KStyleSlot extends string,
|
||||
KEvent extends string
|
||||
> = ImplWrapperProps &
|
||||
> = ImplWrapperProps<TProps, string> &
|
||||
TraitResult<KStyleSlot, KEvent>['props'] &
|
||||
RuntimeFunctions<TState, TMethods, TSlots> & {
|
||||
elementRef?: React.MutableRefObject<any>;
|
||||
@ -34,13 +38,15 @@ export type ComponentImplProps<
|
||||
};
|
||||
|
||||
export type ComponentImpl<
|
||||
TProps = any,
|
||||
TProps extends Record<string, unknown> = Record<string, unknown>,
|
||||
TState = any,
|
||||
TMethods = Record<string, any>,
|
||||
TSlots extends Record<string, SlotSpec> = Record<string, any>,
|
||||
KStyleSlot extends string = string,
|
||||
KEvent extends string = string
|
||||
> = React.FC<TProps & ComponentImplProps<TState, TMethods, TSlots, KStyleSlot, KEvent>>;
|
||||
> = React.FC<
|
||||
TProps & ComponentImplProps<TProps, TState, TMethods, TSlots, KStyleSlot, KEvent>
|
||||
>;
|
||||
|
||||
export type ImplementedRuntimeComponent<
|
||||
KMethodName extends string,
|
||||
@ -48,7 +54,7 @@ export type ImplementedRuntimeComponent<
|
||||
KSlot extends string,
|
||||
KEvent extends string
|
||||
> = RuntimeComponent<KMethodName, KStyleSlot, KSlot, KEvent> & {
|
||||
impl: ComponentImpl;
|
||||
impl: ComponentImpl<any>;
|
||||
};
|
||||
|
||||
export type ChildrenMap<KSlot extends string> = Record<
|
||||
|
@ -14,10 +14,10 @@ export type TraitResult<KStyleSlot extends string, KEvent extends string> = {
|
||||
unmount?: boolean;
|
||||
};
|
||||
|
||||
export type TraitImpl<T = any> = (
|
||||
props: T &
|
||||
export type TraitImpl<TProperties = any> = (
|
||||
props: TProperties &
|
||||
RuntimeFunctions<unknown, unknown, any> & {
|
||||
trait: RuntimeTraitSchema;
|
||||
trait: RuntimeTraitSchema<TProperties>;
|
||||
componentId: string;
|
||||
services: UIServices;
|
||||
evalListItem?: boolean;
|
||||
|
@ -33,7 +33,9 @@ export function implementRuntimeComponent<
|
||||
options: T
|
||||
): (
|
||||
impl: ComponentImpl<
|
||||
Static<T['spec']['properties']>,
|
||||
Static<T['spec']['properties']> extends Record<string, unknown>
|
||||
? Static<T['spec']['properties']>
|
||||
: Record<string, unknown>,
|
||||
Static<T['spec']['state']>,
|
||||
ToMap<T['spec']['methods']>,
|
||||
T['spec']['slots'],
|
||||
|
@ -1,2 +1,3 @@
|
||||
export * from './spec';
|
||||
export * from './condition';
|
||||
export * from './utils';
|
||||
|
12
packages/shared/src/types/utils.ts
Normal file
12
packages/shared/src/types/utils.ts
Normal file
@ -0,0 +1,12 @@
|
||||
// add the string type to every properties because it may be a expression
|
||||
export type PropsBeforeEvaled<T> = T extends Record<string, any> | any[]
|
||||
? { [K in keyof T]: PropsBeforeEvaled<T[K]> | string }
|
||||
: T;
|
||||
// remove the string type for every properties
|
||||
export type PropsAfterEvaled<T> = T extends Record<string, any> | any[]
|
||||
? {
|
||||
[K in keyof T]: T[K] extends string | infer O
|
||||
? PropsAfterEvaled<O>
|
||||
: PropsAfterEvaled<T[K]>;
|
||||
}
|
||||
: T;
|
Loading…
Reference in New Issue
Block a user