From e763766d756f89665efcfc8acc6a3804af8761ac Mon Sep 17 00:00:00 2001 From: Bowen Tan Date: Mon, 23 May 2022 16:08:53 +0800 Subject: [PATCH 1/9] rename ModuleRenderSpec --- packages/arco-lib/package.json | 1 + packages/arco-lib/src/generated/types/Table.ts | 2 +- packages/chakra-ui-lib/package.json | 1 + packages/chakra-ui-lib/src/components/List.tsx | 4 ++-- packages/chakra-ui-lib/src/components/Table/TableTypes.ts | 4 ++-- .../ComponentForm/EventTraitForm/EventHandlerForm.tsx | 2 +- .../ComponentForm/EventTraitForm/EventTraitForm.tsx | 3 +-- packages/editor/src/validator/rules/TraitRules.ts | 4 ++-- packages/runtime/src/components/_internal/ModuleRenderer.tsx | 4 ++-- packages/runtime/src/components/core/ModuleContainer.tsx | 4 ++-- packages/runtime/src/index.ts | 5 ----- packages/runtime/src/utils/initStateAndMethod.ts | 4 ++-- packages/shared/src/specs/module.ts | 2 +- 13 files changed, 18 insertions(+), 22 deletions(-) diff --git a/packages/arco-lib/package.json b/packages/arco-lib/package.json index c84a869f..4365ac0f 100644 --- a/packages/arco-lib/package.json +++ b/packages/arco-lib/package.json @@ -34,6 +34,7 @@ "@sunmao-ui/core": "^0.6.0", "@sunmao-ui/editor-sdk": "^0.2.0", "@sunmao-ui/runtime": "^0.6.0", + "@sunmao-ui/shared": "^0.1.0", "eslint-plugin-react-hooks": "^4.3.0", "lodash-es": "^4.17.21", "react": "^17.0.2", diff --git a/packages/arco-lib/src/generated/types/Table.ts b/packages/arco-lib/src/generated/types/Table.ts index 4b6fff5c..5d4bfcc6 100644 --- a/packages/arco-lib/src/generated/types/Table.ts +++ b/packages/arco-lib/src/generated/types/Table.ts @@ -1,6 +1,6 @@ import { Type } from '@sinclair/typebox'; import { StringUnion } from '../../sunmao-helper'; -import { EventHandlerSpec } from '@sunmao-ui/runtime'; +import { EventHandlerSpec } from '@sunmao-ui/shared'; import { Category } from '../../constants/category'; const PaginationSpec = Type.Object( diff --git a/packages/chakra-ui-lib/package.json b/packages/chakra-ui-lib/package.json index 8ef20133..4f7cb9c0 100644 --- a/packages/chakra-ui-lib/package.json +++ b/packages/chakra-ui-lib/package.json @@ -36,6 +36,7 @@ "@sunmao-ui/core": "^0.6.0", "@sunmao-ui/editor-sdk": "^0.2.0", "@sunmao-ui/runtime": "^0.6.0", + "@sunmao-ui/shared": "^0.1.0", "chakra-react-select": "^1.3.2", "framer-motion": "^4", "lodash-es": "^4.17.21", diff --git a/packages/chakra-ui-lib/src/components/List.tsx b/packages/chakra-ui-lib/src/components/List.tsx index 6cd31916..714d7fcd 100644 --- a/packages/chakra-ui-lib/src/components/List.tsx +++ b/packages/chakra-ui-lib/src/components/List.tsx @@ -4,9 +4,9 @@ import { implementRuntimeComponent, LIST_ITEM_EXP, LIST_ITEM_INDEX_EXP, - ModuleSpec, ModuleRenderer, } from '@sunmao-ui/runtime'; +import { ModuleRenderSpec } from '@sunmao-ui/shared'; import { css } from '@emotion/css'; import { BASIC } from './constants/category'; import { CORE_VERSION, CoreWidgetName } from '@sunmao-ui/editor-sdk'; @@ -17,7 +17,7 @@ const PropsSpec = Type.Object({ category: BASIC, widget: `${CORE_VERSION}/${CoreWidgetName.Expression}`, }), - template: ModuleSpec, + template: ModuleRenderSpec, }); const exampleProperties = { diff --git a/packages/chakra-ui-lib/src/components/Table/TableTypes.ts b/packages/chakra-ui-lib/src/components/Table/TableTypes.ts index 0cea3028..dd13b7d1 100644 --- a/packages/chakra-ui-lib/src/components/Table/TableTypes.ts +++ b/packages/chakra-ui-lib/src/components/Table/TableTypes.ts @@ -1,5 +1,5 @@ import { Type } from '@sinclair/typebox'; -import { ModuleSpec, EventCallBackHandlerSpec } from '@sunmao-ui/runtime'; +import { ModuleRenderSpec, EventCallBackHandlerSpec } from '@sunmao-ui/shared'; import { BASIC, APPEARANCE, BEHAVIOR } from '../constants/category'; export const MajorKeyPropertySpec = Type.String({ @@ -71,7 +71,7 @@ export const ColumnSpec = Type.Object( ], } ), - module: { ...ModuleSpec, conditions: [{ key: 'type', value: 'module' }] }, + module: { ...ModuleRenderSpec, conditions: [{ key: 'type', value: 'module' }] }, }, { title: 'Column', diff --git a/packages/editor/src/components/ComponentForm/EventTraitForm/EventHandlerForm.tsx b/packages/editor/src/components/ComponentForm/EventTraitForm/EventHandlerForm.tsx index c124deba..fcfc511c 100644 --- a/packages/editor/src/components/ComponentForm/EventTraitForm/EventHandlerForm.tsx +++ b/packages/editor/src/components/ComponentForm/EventTraitForm/EventHandlerForm.tsx @@ -13,7 +13,7 @@ import { import { Static } from '@sinclair/typebox'; import { JSONSchema7 } from 'json-schema'; import { CloseIcon, ArrowUpIcon, ArrowDownIcon } from '@chakra-ui/icons'; -import { EventHandlerSpec, EventCallBackHandlerSpec } from '@sunmao-ui/runtime'; +import { EventHandlerSpec, EventCallBackHandlerSpec } from '@sunmao-ui/shared'; import { ComponentSchema } from '@sunmao-ui/core'; import { formWrapperCSS } from '../style'; import { EditorServices } from '../../../types'; diff --git a/packages/editor/src/components/ComponentForm/EventTraitForm/EventTraitForm.tsx b/packages/editor/src/components/ComponentForm/EventTraitForm/EventTraitForm.tsx index dc821447..20961481 100644 --- a/packages/editor/src/components/ComponentForm/EventTraitForm/EventTraitForm.tsx +++ b/packages/editor/src/components/ComponentForm/EventTraitForm/EventTraitForm.tsx @@ -4,8 +4,7 @@ import { Button, VStack } from '@chakra-ui/react'; import { Static } from '@sinclair/typebox'; import produce from 'immer'; import { ComponentSchema } from '@sunmao-ui/core'; -import { EventHandlerSpec } from '@sunmao-ui/runtime'; -import { CORE_VERSION, CoreTraitName } from '@sunmao-ui/shared'; +import { CORE_VERSION, CoreTraitName, EventHandlerSpec } from '@sunmao-ui/shared'; import { genOperation } from '../../../operations'; import { EditorServices } from '../../../types'; import { EventHandlerForm } from './EventHandlerForm'; diff --git a/packages/editor/src/validator/rules/TraitRules.ts b/packages/editor/src/validator/rules/TraitRules.ts index 0a5628e0..2e00915e 100644 --- a/packages/editor/src/validator/rules/TraitRules.ts +++ b/packages/editor/src/validator/rules/TraitRules.ts @@ -4,10 +4,10 @@ import { TraitValidateContext, ValidateErrorResult, } from '../interfaces'; -import { EventHandlerSpec, GLOBAL_UTIL_METHOD_ID } from '@sunmao-ui/runtime'; +import { GLOBAL_UTIL_METHOD_ID } from '@sunmao-ui/runtime'; import { isExpression } from '../utils'; import { ComponentId, EventName } from '../../AppModel/IAppModel'; -import { CORE_VERSION, CoreTraitName } from '@sunmao-ui/shared'; +import { CORE_VERSION, CoreTraitName, EventHandlerSpec } from '@sunmao-ui/shared'; class EventHandlerValidatorRule implements TraitValidatorRule { kind: 'trait' = 'trait'; diff --git a/packages/runtime/src/components/_internal/ModuleRenderer.tsx b/packages/runtime/src/components/_internal/ModuleRenderer.tsx index e1049050..4d5fceec 100644 --- a/packages/runtime/src/components/_internal/ModuleRenderer.tsx +++ b/packages/runtime/src/components/_internal/ModuleRenderer.tsx @@ -11,12 +11,12 @@ import { import { ImplWrapper } from './ImplWrapper'; import { watch } from '../../utils/watchReactivity'; import { ImplementedRuntimeModule, UIServices } from '../../types'; -import { EventHandlerSpec, ModuleSpec } from '@sunmao-ui/shared'; +import { EventHandlerSpec, ModuleRenderSpec } from '@sunmao-ui/shared'; import { resolveChildrenMap } from '../../utils/resolveChildrenMap'; import { initStateAndMethod } from '../../utils/initStateAndMethod'; import { ExpressionError } from '../../services/StateManager'; -type Props = Static & { +type Props = Static & { evalScope?: Record; services: UIServices; app?: RuntimeApplication; diff --git a/packages/runtime/src/components/core/ModuleContainer.tsx b/packages/runtime/src/components/core/ModuleContainer.tsx index b6e1f31a..3e978df7 100644 --- a/packages/runtime/src/components/core/ModuleContainer.tsx +++ b/packages/runtime/src/components/core/ModuleContainer.tsx @@ -1,5 +1,5 @@ import { implementRuntimeComponent } from '../../utils/buildKit'; -import { ModuleSpec, CORE_VERSION, CoreComponentName } from '@sunmao-ui/shared'; +import { ModuleRenderSpec, CORE_VERSION, CoreComponentName } from '@sunmao-ui/shared'; import { ModuleRenderer } from '../_internal/ModuleRenderer'; export default implementRuntimeComponent({ @@ -20,7 +20,7 @@ export default implementRuntimeComponent({ }, }, spec: { - properties: ModuleSpec, + properties: ModuleRenderSpec, state: {}, methods: {}, slots: {}, diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index b72e37dd..a076aba6 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -68,11 +68,6 @@ export { ExpressionError } from './services/StateManager'; export type { StateManagerInterface } from './services/StateManager'; export { ModuleRenderer } from './components/_internal/ModuleRenderer'; export { default as Text, TextPropertySpec } from './components/_internal/Text'; -export { - EventHandlerSpec, - EventCallBackHandlerSpec, - ModuleSpec, -} from '@sunmao-ui/shared'; // TODO: check this export export { watch } from './utils/watchReactivity'; diff --git a/packages/runtime/src/utils/initStateAndMethod.ts b/packages/runtime/src/utils/initStateAndMethod.ts index f9b9dc3b..d163f959 100644 --- a/packages/runtime/src/utils/initStateAndMethod.ts +++ b/packages/runtime/src/utils/initStateAndMethod.ts @@ -3,7 +3,7 @@ import { Static, TSchema } from '@sinclair/typebox'; import { RegistryInterface } from '../services/Registry'; import { StateManagerInterface } from '../services/StateManager'; import { - ModuleSpec, + ModuleRenderSpec, parseTypeBox, CORE_VERSION, CoreComponentName, @@ -36,7 +36,7 @@ export function initSingleComponentState( stateManager.store[c.id] = state; if (c.type === `${CORE_VERSION}/${CoreComponentName.ModuleContainer}`) { - const moduleSchema = c.properties as Static; + const moduleSchema = c.properties as Static; try { const mSpec = registry.getModuleByType(moduleSchema.type).spec; const moduleInitState: Record = {}; diff --git a/packages/shared/src/specs/module.ts b/packages/shared/src/specs/module.ts index c97dea81..015cff81 100644 --- a/packages/shared/src/specs/module.ts +++ b/packages/shared/src/specs/module.ts @@ -2,7 +2,7 @@ import { EventHandlerSpec } from './event'; import { Type } from '@sinclair/typebox'; import { CORE_VERSION, CoreWidgetName } from '../constants/core'; -export const ModuleSpec = Type.Object( +export const ModuleRenderSpec = Type.Object( { id: Type.String({ title: 'Module ID', From de105e323039f2972eb9dd14c5c3845a50e79f6b Mon Sep 17 00:00:00 2001 From: Bowen Tan Date: Mon, 23 May 2022 17:28:56 +0800 Subject: [PATCH 2/9] add componentRenderer --- examples/list/listComponent.json | 224 ++++++++++++++++++ .../_internal/ComponentRenderer.tsx | 70 ++++++ packages/runtime/src/components/core/List.tsx | 88 +++++++ packages/runtime/src/services/Registry.tsx | 2 + 4 files changed, 384 insertions(+) create mode 100644 examples/list/listComponent.json create mode 100644 packages/runtime/src/components/_internal/ComponentRenderer.tsx create mode 100644 packages/runtime/src/components/core/List.tsx diff --git a/examples/list/listComponent.json b/examples/list/listComponent.json new file mode 100644 index 00000000..46c8882b --- /dev/null +++ b/examples/list/listComponent.json @@ -0,0 +1,224 @@ +{ + "modules": [{ + "version": "core/v1", + "kind": "Module", + "parsedVersion": { + "category": "core/v1", + "value": "littleItem" + }, + "metadata": { + "name": "littleItem" + }, + "spec": { + "properties": {}, + "events": ["onEdit"], + "stateMap": { + "value": "{{ $moduleId }}__input.value" + } + }, + "impl": [ + { + "id": "{{ $moduleId }}__hstack", + "type": "chakra_ui/v1/hstack", + "properties": {}, + "traits": [] + }, + { + "id": "{{ $moduleId }}__text", + "type": "core/v1/text", + "properties": { + "value": { + "raw": "**{{value}}**", + "format": "md" + } + }, + "traits": [ + { + "type": "core/v1/slot", + "properties": { + "container": { + "id": "{{ $moduleId }}__hstack", + "slot": "content" + } + } + } + ] + }, + { + "id": "{{ $moduleId }}__inputValueText", + "type": "core/v1/text", + "properties": { + "value": { + "raw": "**{{ {{ $moduleId }}__input.value }}**", + "format": "md" + } + }, + "traits": [ + { + "type": "core/v1/slot", + "properties": { + "container": { + "id": "{{ $moduleId }}__hstack", + "slot": "content" + } + } + } + ] + }, + { + "id": "{{ $moduleId }}__input", + "type": "chakra_ui/v1/input", + "properties": {}, + "traits": [ + { + "type": "core/v1/slot", + "properties": { + "container": { + "id": "{{ $moduleId }}__hstack", + "slot": "content" + } + } + } + ] + }, + { + "id": "{{ $moduleId }}__button", + "type": "chakra_ui/v1/button", + "properties": { + "text": { + "raw": "click{{value}}", + "format": "md" + } + }, + "traits": [ + { + "type": "core/v1/event", + "properties": { + "handlers": [ + { + "type": "onClick", + "componentId": "$module", + "method": { + "name": "onEdit", + "parameters": { + "moduleId": "{{$moduleId}}" + } + }, + "wait": {}, + "disabled": false + } + ] + } + }, + { + "type": "core/v1/slot", + "properties": { + "container": { + "id": "{{ $moduleId }}__hstack", + "slot": "content" + } + } + } + ] + } + ] + }], + "app": { + "version": "example/v1", + "metadata": { + "name": "list_component", + "description": "list component example" + }, + "spec": { + "components": [ + { + "id": "root", + "type": "chakra_ui/v1/root", + "properties": {}, + "traits": [ + { + "type": "core/v1/state", + "properties": { + "key": "listTitle", + "initialValue": "客户列表" + } + }, + { + "type": "core/v1/arrayState", + "properties": { + "key": "listData", + "initialValue": [ + { + "id": 1, + "name": "马云", + "email": "jack.ma@deck.com" + }, + { + "id": 2, + "name": "马化腾", + "email": "pony.ma@conversation.com" + }, + { + "id": 3, + "name": "李彦宏", + "email": "robin.li@response.com" + }, + { + "id": 4, + "name": "张一鸣", + "email": "yiming.zhang@example.com" + }, + { + "id": 5, + "name": "王兴", + "email": "xing.wang@widget.org" + } + ] + } + } + ] + }, + { + "id": "list", + "type": "core/v1/list", + "properties": { + "listData": "{{ root.listData }}", + "template": { + "id": "littleItem{{$listItem.id}}", + "type": "chakra_ui/v1/button", + "properties": { + "text": { + "raw": "{{$listItem.name}}", + "format": "md" + } + }, + "handlers": [ + { + "type": "onClick", + "componentId": "$utils", + "method": { + "name": "alert", + "parameters": "listen module event:{{ $listItem.name }}!" + }, + "wait": {}, + "disabled": false + } + ] + } + }, + "traits": [ + { + "type": "core/v1/slot", + "properties": { + "container": { + "id": "root", + "slot": "root" + } + } + } + ] + } + ] + } + } +} diff --git a/packages/runtime/src/components/_internal/ComponentRenderer.tsx b/packages/runtime/src/components/_internal/ComponentRenderer.tsx new file mode 100644 index 00000000..cad54a8b --- /dev/null +++ b/packages/runtime/src/components/_internal/ComponentRenderer.tsx @@ -0,0 +1,70 @@ +import { Static } from '@sinclair/typebox'; +import React, { useMemo } from 'react'; +import { parseType, RuntimeApplication, RuntimeComponentSchema } from '@sunmao-ui/core'; +import { ImplWrapper } from './ImplWrapper'; +import { UIServices } from '../../types'; +import { ModuleRenderSpec } from '@sunmao-ui/shared'; +import { initStateAndMethod } from '../../utils/initStateAndMethod'; + +type Props = Static & { + evalScope?: Record; + services: UIServices; + app?: RuntimeApplication; +}; + +export const ComponentRenderer = React.forwardRef((props, ref) => { + const { type } = props; + try { + return ; + } catch { + return
Cannot find Module {type}.
; + } +}); + +const ComponentRendererContent = React.forwardRef((props, ref) => { + const { properties, evalScope, services, app } = props; + const evalOptions = { evalListItem: true, scopeObject: evalScope }; + const componentId = services.stateManager.maskedEval(props.id, evalOptions) as string; + + function evalObject>(obj: T): T { + return services.stateManager.mapValuesDeep({ obj }, ({ value }) => { + if (typeof value === 'string') { + return services.stateManager.maskedEval(value, evalOptions); + } + return value; + }).obj; + } + + // first eval the property, handlers, id of module + const evaledProperties = evalObject(properties); + + const componentSchema: RuntimeComponentSchema = useMemo(() => { + return { + id: componentId, + properties: evaledProperties, + type: props.type, + traits: [], + parsedType: parseType(props.type), + }; + }, [componentId, evaledProperties, props.type]); + + const result = useMemo(() => { + // Must init components' state, otherwise store cannot listen these components' state changing + initStateAndMethod(services.registry, services.stateManager, [componentSchema]); + return ( + + ); + }, [services, componentSchema, app]); + + return ( +
+ {result} +
+ ); +}); diff --git a/packages/runtime/src/components/core/List.tsx b/packages/runtime/src/components/core/List.tsx new file mode 100644 index 00000000..514b64cc --- /dev/null +++ b/packages/runtime/src/components/core/List.tsx @@ -0,0 +1,88 @@ +import { Type } from '@sinclair/typebox'; +import { css } from '@emotion/css'; +import { LIST_ITEM_EXP, LIST_ITEM_INDEX_EXP } from '../../constants'; +import { implementRuntimeComponent } from '../../utils/buildKit'; +import { ModuleRenderSpec } from '@sunmao-ui/shared'; +import { ComponentRenderer } from '../_internal/ComponentRenderer'; + +const PropsSpec = Type.Object({ + listData: Type.Array(Type.Record(Type.String(), Type.String()), { + title: 'Data', + category: 'Basic', + widget: `core/v1/expression`, + }), + template: ModuleRenderSpec, +}); + +const exampleProperties = { + listData: [{ id: '1' }, { id: '2' }, { id: '3' }], + template: { + id: 'listItemName-{{$listItem.id}}', + type: 'custom/v1/myModule0', + properties: { + value: '{{$listItem.id}}', + }, + handlers: [], + }, +}; + +export default implementRuntimeComponent({ + version: 'core/v1', + metadata: { + name: 'list', + description: 'core list', + displayName: 'List', + isDraggable: true, + isResizable: true, + exampleProperties, + exampleSize: [6, 6], + annotations: { + category: 'Display', + }, + }, + spec: { + properties: PropsSpec, + methods: {}, + state: Type.Object({}), + slots: [], + styleSlots: ['content'], + events: [], + }, +})(({ listData, template, app, services, customStyle, elementRef }) => { + if (!listData) { + return null; + } + + const listItems = listData.map((listItem, i) => { + const evalScope = { + [LIST_ITEM_EXP]: listItem, + [LIST_ITEM_INDEX_EXP]: i, + }; + const listItemEle = ( +
  • + +
  • + ); + + return listItemEle; + }); + + return ( +
      + {listItems} +
    + ); +}); diff --git a/packages/runtime/src/services/Registry.tsx b/packages/runtime/src/services/Registry.tsx index 919f7ebf..d9f66d04 100644 --- a/packages/runtime/src/services/Registry.tsx +++ b/packages/runtime/src/services/Registry.tsx @@ -8,6 +8,7 @@ import CoreDummy from '../components/core/Dummy'; import CoreModuleContainer from '../components/core/ModuleContainer'; import CoreStack from '../components/core/Stack'; import CoreFileInput from '../components/core/FileInput'; +import CoreList from '../components/core/List'; // traits import CoreArrayState from '../traits/core/ArrayState'; @@ -242,6 +243,7 @@ export function initRegistry( registry.registerComponent(CoreModuleContainer); registry.registerComponent(CoreStack); registry.registerComponent(CoreFileInput); + registry.registerComponent(CoreList); registry.registerTrait(CoreState); registry.registerTrait(CoreArrayState); From 0c1b602fcbbd7b8fef7c4658cbb10f2825035421 Mon Sep 17 00:00:00 2001 From: Bowen Tan Date: Tue, 24 May 2022 14:14:09 +0800 Subject: [PATCH 3/9] feat(runtime): use slot pass component --- examples/list/listComponent.json | 194 +++++------------- .../src/components/Table/TableTd.tsx | 3 +- .../_internal/ComponentRenderer.tsx | 11 +- .../_internal/ImplWrapper/ImplWrapperMain.tsx | 28 ++- .../ImplWrapper/hooks/useRuntimeFunctions.ts | 6 +- .../components/_internal/ModuleRenderer.tsx | 2 +- packages/runtime/src/components/core/List.tsx | 60 +++--- packages/runtime/src/traits/core/Event.tsx | 13 +- packages/runtime/src/types/component.ts | 3 +- packages/runtime/src/types/trait.ts | 2 + 10 files changed, 128 insertions(+), 194 deletions(-) diff --git a/examples/list/listComponent.json b/examples/list/listComponent.json index 46c8882b..e082c411 100644 --- a/examples/list/listComponent.json +++ b/examples/list/listComponent.json @@ -1,128 +1,5 @@ { - "modules": [{ - "version": "core/v1", - "kind": "Module", - "parsedVersion": { - "category": "core/v1", - "value": "littleItem" - }, - "metadata": { - "name": "littleItem" - }, - "spec": { - "properties": {}, - "events": ["onEdit"], - "stateMap": { - "value": "{{ $moduleId }}__input.value" - } - }, - "impl": [ - { - "id": "{{ $moduleId }}__hstack", - "type": "chakra_ui/v1/hstack", - "properties": {}, - "traits": [] - }, - { - "id": "{{ $moduleId }}__text", - "type": "core/v1/text", - "properties": { - "value": { - "raw": "**{{value}}**", - "format": "md" - } - }, - "traits": [ - { - "type": "core/v1/slot", - "properties": { - "container": { - "id": "{{ $moduleId }}__hstack", - "slot": "content" - } - } - } - ] - }, - { - "id": "{{ $moduleId }}__inputValueText", - "type": "core/v1/text", - "properties": { - "value": { - "raw": "**{{ {{ $moduleId }}__input.value }}**", - "format": "md" - } - }, - "traits": [ - { - "type": "core/v1/slot", - "properties": { - "container": { - "id": "{{ $moduleId }}__hstack", - "slot": "content" - } - } - } - ] - }, - { - "id": "{{ $moduleId }}__input", - "type": "chakra_ui/v1/input", - "properties": {}, - "traits": [ - { - "type": "core/v1/slot", - "properties": { - "container": { - "id": "{{ $moduleId }}__hstack", - "slot": "content" - } - } - } - ] - }, - { - "id": "{{ $moduleId }}__button", - "type": "chakra_ui/v1/button", - "properties": { - "text": { - "raw": "click{{value}}", - "format": "md" - } - }, - "traits": [ - { - "type": "core/v1/event", - "properties": { - "handlers": [ - { - "type": "onClick", - "componentId": "$module", - "method": { - "name": "onEdit", - "parameters": { - "moduleId": "{{$moduleId}}" - } - }, - "wait": {}, - "disabled": false - } - ] - } - }, - { - "type": "core/v1/slot", - "properties": { - "container": { - "id": "{{ $moduleId }}__hstack", - "slot": "content" - } - } - } - ] - } - ] - }], + "modules": [], "app": { "version": "example/v1", "metadata": { @@ -182,29 +59,7 @@ "id": "list", "type": "core/v1/list", "properties": { - "listData": "{{ root.listData }}", - "template": { - "id": "littleItem{{$listItem.id}}", - "type": "chakra_ui/v1/button", - "properties": { - "text": { - "raw": "{{$listItem.name}}", - "format": "md" - } - }, - "handlers": [ - { - "type": "onClick", - "componentId": "$utils", - "method": { - "name": "alert", - "parameters": "listen module event:{{ $listItem.name }}!" - }, - "wait": {}, - "disabled": false - } - ] - } + "listData": "{{ root.listData }}" }, "traits": [ { @@ -217,6 +72,51 @@ } } ] + }, + { + "id": "listSlot", + "type": "chakra_ui/v1/button", + "properties": { + "text": { + "raw": "我是Slot{{$slot.$listItem.name}}", + "format": "md" + } + }, + "traits": [ + { + "type": "core/v1/event", + "properties": { + "handlers": [ + { + "type": "onClick", + "componentId": "$utils", + "method": { + "name": "chakra_ui/v1/openToast", + "parameters": { + "id": "createSuccessToast", + "title": "Tost", + "description": "data{{$slot.$listItem.name}}", + "position": "bottom-right", + "duration": null, + "isClosable": true + } + }, + "wait": {}, + "disabled": false + } + ] + } + }, + { + "type": "core/v1/slot", + "properties": { + "container": { + "id": "list", + "slot": "content" + } + } + } + ] } ] } diff --git a/packages/chakra-ui-lib/src/components/Table/TableTd.tsx b/packages/chakra-ui-lib/src/components/Table/TableTd.tsx index 25a0f952..66df26ef 100644 --- a/packages/chakra-ui-lib/src/components/Table/TableTd.tsx +++ b/packages/chakra-ui-lib/src/components/Table/TableTd.tsx @@ -1,3 +1,4 @@ +import React from 'react'; import { RuntimeApplication } from '@sunmao-ui/core'; import { Static } from '@sinclair/typebox'; import { ColumnSpec, ColumnsPropertySpec } from './TableTypes'; @@ -17,7 +18,7 @@ export const TableTd: React.FC<{ rawColumn: Static[0]; onClickItem: () => void; services: UIServices; - app?: RuntimeApplication; + app: RuntimeApplication; }> = props => { const { item, index, column, rawColumn, onClickItem, services, app } = props; const evalOptions = { diff --git a/packages/runtime/src/components/_internal/ComponentRenderer.tsx b/packages/runtime/src/components/_internal/ComponentRenderer.tsx index cad54a8b..087955c6 100644 --- a/packages/runtime/src/components/_internal/ComponentRenderer.tsx +++ b/packages/runtime/src/components/_internal/ComponentRenderer.tsx @@ -9,14 +9,16 @@ import { initStateAndMethod } from '../../utils/initStateAndMethod'; type Props = Static & { evalScope?: Record; services: UIServices; - app?: RuntimeApplication; + app: RuntimeApplication; + traits: any; }; export const ComponentRenderer = React.forwardRef((props, ref) => { const { type } = props; try { return ; - } catch { + } catch (e) { + console.log(e); return
    Cannot find Module {type}.
    ; } }); @@ -37,16 +39,17 @@ const ComponentRendererContent = React.forwardRef((props, // first eval the property, handlers, id of module const evaledProperties = evalObject(properties); + const evaledTraits = evalObject(props.traits); const componentSchema: RuntimeComponentSchema = useMemo(() => { return { id: componentId, properties: evaledProperties, type: props.type, - traits: [], + traits: evaledTraits, parsedType: parseType(props.type), }; - }, [componentId, evaledProperties, props.type]); + }, [componentId, evaledProperties, evaledTraits, props.type]); const result = useMemo(() => { // Must init components' state, otherwise store cannot listen these components' state changing diff --git a/packages/runtime/src/components/_internal/ImplWrapper/ImplWrapperMain.tsx b/packages/runtime/src/components/_internal/ImplWrapper/ImplWrapperMain.tsx index 5f87d4b7..81ee56fb 100644 --- a/packages/runtime/src/components/_internal/ImplWrapper/ImplWrapperMain.tsx +++ b/packages/runtime/src/components/_internal/ImplWrapper/ImplWrapperMain.tsx @@ -8,16 +8,23 @@ import { getSlotElements } from './hooks/useSlotChildren'; import { useGlobalHandlerMap } from './hooks/useGlobalHandlerMap'; import { useEleRef } from './hooks/useEleMap'; import { useGridLayout } from './hooks/useGridLayout'; +import { initStateAndMethod } from '../../../utils/initStateAndMethod'; export const ImplWrapperMain = React.forwardRef( function ImplWrapperMain(props, ref) { - const { component: c, children } = props; + const { component: c, children, slotProps, evalListItem } = props; const { registry, stateManager } = props.services; + console.log(c.id, slotProps); const Impl = registry.getComponent(c.parsedType.version, c.parsedType.name).impl; useGlobalHandlerMap(props); + if (!stateManager.store[c.id]) { + console.log('init了', c.id); + initStateAndMethod(registry, stateManager, [c]); + } + const { eleRef, onRef } = useEleRef(props); const { mergeState, subscribeMethods, executeTrait } = useRuntimeFunctions(props); @@ -28,7 +35,8 @@ export const ImplWrapperMain = React.forwardRef undefined, }) ) @@ -59,7 +67,8 @@ export const ImplWrapperMain = React.forwardRef undefined, } ); @@ -70,7 +79,7 @@ export const ImplWrapperMain = React.forwardRef executeTrait(trait, properties[i]))); return () => stops.forEach(s => s()); - }, [c.id, c.traits, executeTrait, stateManager, props.slotProps]); + }, [c.id, c.traits, executeTrait, stateManager, slotProps, evalListItem]); // reduce traitResults const propsFromTraits: TraitResult['props'] = useMemo(() => { @@ -97,7 +106,8 @@ export const ImplWrapperMain = React.forwardRef undefined, - scopeObject: { $slot: props.slotProps }, + evalListItem, + scopeObject: { $slot: slotProps }, }), propsFromTraits ); @@ -109,13 +119,17 @@ export const ImplWrapperMain = React.forwardRef { setEvaledComponentProperties({ ...newResult }); }, - { fallbackWhenError: () => undefined, scopeObject: { $slot: props.slotProps } } + { + evalListItem, + fallbackWhenError: () => undefined, + scopeObject: { $slot: slotProps }, + } ); // must keep this line, reason is the same as above setEvaledComponentProperties({ ...result }); return stop; - }, [c.properties, stateManager, props.slotProps]); + }, [c.properties, stateManager, slotProps]); useEffect(() => { const clearFunctions = propsFromTraits?.componentDidMount?.map(e => e()); diff --git a/packages/runtime/src/components/_internal/ImplWrapper/hooks/useRuntimeFunctions.ts b/packages/runtime/src/components/_internal/ImplWrapper/hooks/useRuntimeFunctions.ts index b956f8ab..51ea4b97 100644 --- a/packages/runtime/src/components/_internal/ImplWrapper/hooks/useRuntimeFunctions.ts +++ b/packages/runtime/src/components/_internal/ImplWrapper/hooks/useRuntimeFunctions.ts @@ -5,7 +5,7 @@ import { merge } from 'lodash'; import { HandlerMap } from '../../../../services/handler'; export function useRuntimeFunctions(props: ImplWrapperProps) { - const { component: c, services } = props; + const { component: c, services, slotProps, evalListItem } = props; const { stateManager, registry, globalHandlerMap } = services; const mergeState = useCallback( @@ -35,9 +35,11 @@ export function useRuntimeFunctions(props: ImplWrapperProps) { mergeState, subscribeMethods, services, + slotProps, + evalListItem, }); }, - [c.id, mergeState, registry, services, subscribeMethods] + [c.id, evalListItem, mergeState, registry, services, slotProps, subscribeMethods] ); return { mergeState, diff --git a/packages/runtime/src/components/_internal/ModuleRenderer.tsx b/packages/runtime/src/components/_internal/ModuleRenderer.tsx index 4d5fceec..1697f3bc 100644 --- a/packages/runtime/src/components/_internal/ModuleRenderer.tsx +++ b/packages/runtime/src/components/_internal/ModuleRenderer.tsx @@ -19,7 +19,7 @@ import { ExpressionError } from '../../services/StateManager'; type Props = Static & { evalScope?: Record; services: UIServices; - app?: RuntimeApplication; + app: RuntimeApplication; }; export const ModuleRenderer = React.forwardRef((props, ref) => { diff --git a/packages/runtime/src/components/core/List.tsx b/packages/runtime/src/components/core/List.tsx index 514b64cc..47e737f6 100644 --- a/packages/runtime/src/components/core/List.tsx +++ b/packages/runtime/src/components/core/List.tsx @@ -2,8 +2,7 @@ import { Type } from '@sinclair/typebox'; import { css } from '@emotion/css'; import { LIST_ITEM_EXP, LIST_ITEM_INDEX_EXP } from '../../constants'; import { implementRuntimeComponent } from '../../utils/buildKit'; -import { ModuleRenderSpec } from '@sunmao-ui/shared'; -import { ComponentRenderer } from '../_internal/ComponentRenderer'; +import { ImplWrapper } from '../_internal/ImplWrapper'; const PropsSpec = Type.Object({ listData: Type.Array(Type.Record(Type.String(), Type.String()), { @@ -11,19 +10,12 @@ const PropsSpec = Type.Object({ category: 'Basic', widget: `core/v1/expression`, }), - template: ModuleRenderSpec, + itemId: Type.String(), }); const exampleProperties = { listData: [{ id: '1' }, { id: '2' }, { id: '3' }], - template: { - id: 'listItemName-{{$listItem.id}}', - type: 'custom/v1/myModule0', - properties: { - value: '{{$listItem.id}}', - }, - handlers: [], - }, + itemId: 'listItemName-{{$listItem.id}}', }; export default implementRuntimeComponent({ @@ -44,32 +36,45 @@ export default implementRuntimeComponent({ properties: PropsSpec, methods: {}, state: Type.Object({}), - slots: [], + slots: { + content: { + slotProps: Type.Object({ + [LIST_ITEM_EXP]: Type.Any(), + [LIST_ITEM_INDEX_EXP]: Type.Number(), + }), + }, + }, styleSlots: ['content'], events: [], }, -})(({ listData, template, app, services, customStyle, elementRef }) => { +})(({ listData, app, services, customStyle, elementRef }) => { if (!listData) { return null; } + const childSchema = app.spec.components.find(c => c.id === 'listSlot')!; const listItems = listData.map((listItem, i) => { - const evalScope = { - [LIST_ITEM_EXP]: listItem, - [LIST_ITEM_INDEX_EXP]: i, + // const evalScope = { + // [LIST_ITEM_EXP]: listItem, + // [LIST_ITEM_INDEX_EXP]: i, + // }; + const _childSchema = { + ...childSchema, + id: `list_${childSchema.id}${i}`, }; const listItemEle = ( -
  • - -
  • + ); return listItemEle; @@ -78,6 +83,7 @@ export default implementRuntimeComponent({ return (
      , 'type'>, rawHandler: Static, - services: UIServices + services: UIServices, + slotProps?: unknown, + evalListItem?: boolean ) => { const send = () => { // Eval before sending event to assure the handler object is evaled from the latest state. - const evaledHandler = services.stateManager.deepEval(rawHandler); + const evaledHandler = services.stateManager.deepEval(rawHandler, { + scopeObject: { $slot: slotProps }, + evalListItem, + }); if (evaledHandler.disabled && typeof evaledHandler.disabled === 'boolean') { return; @@ -54,7 +59,7 @@ export default implementRuntimeTrait({ state: {}, }, })(() => { - return ({ trait, handlers, services }) => { + return ({ trait, handlers, services, slotProps, evalListItem }) => { const callbackQueueMap: Record void>> = {}; const rawHandlers = trait.properties.handlers as Static[]; // setup current handlers @@ -64,7 +69,7 @@ export default implementRuntimeTrait({ callbackQueueMap[handler.type] = []; } callbackQueueMap[handler.type].push( - generateCallback(handler, rawHandlers[i], services) + generateCallback(handler, rawHandlers[i], services, slotProps, evalListItem) ); } diff --git a/packages/runtime/src/types/component.ts b/packages/runtime/src/types/component.ts index b9f0cbc8..f6117305 100644 --- a/packages/runtime/src/types/component.ts +++ b/packages/runtime/src/types/component.ts @@ -15,7 +15,8 @@ export type ImplWrapperProps = { childrenMap: ChildrenMap; services: UIServices; isInModule: boolean; - app?: RuntimeApplication; + app: RuntimeApplication; + evalListItem?: boolean; slotProps?: unknown; } & ComponentParamsFromApp; diff --git a/packages/runtime/src/types/trait.ts b/packages/runtime/src/types/trait.ts index 0a05e3a6..571c0170 100644 --- a/packages/runtime/src/types/trait.ts +++ b/packages/runtime/src/types/trait.ts @@ -20,6 +20,8 @@ export type TraitImpl = ( trait: RuntimeTraitSchema; componentId: string; services: UIServices; + evalListItem?: boolean; + slotProps?: unknown; } ) => TraitResult; From 51c1ec448f1d6c93b0fe3f4811fe54c18cab7131 Mon Sep 17 00:00:00 2001 From: Bowen Tan Date: Thu, 2 Jun 2022 16:53:52 +0800 Subject: [PATCH 4/9] feat(chakra): add component table column --- .../src/components/Table/Table.tsx | 9 ++- .../src/components/Table/TableTd.tsx | 52 +++++++++++-- .../src/components/Table/TableTypes.ts | 10 +++ .../src/components/Table/spec.ts | 15 +++- .../_internal/ComponentRenderer.tsx | 73 ------------------ .../_internal/ImplWrapper/ImplWrapperMain.tsx | 4 +- packages/runtime/src/components/core/List.tsx | 74 ++++++++++++------- packages/runtime/src/index.ts | 1 + 8 files changed, 125 insertions(+), 113 deletions(-) delete mode 100644 packages/runtime/src/components/_internal/ComponentRenderer.tsx diff --git a/packages/chakra-ui-lib/src/components/Table/Table.tsx b/packages/chakra-ui-lib/src/components/Table/Table.tsx index 13181611..342e09bb 100644 --- a/packages/chakra-ui-lib/src/components/Table/Table.tsx +++ b/packages/chakra-ui-lib/src/components/Table/Table.tsx @@ -190,10 +190,17 @@ export const TableImpl = implementTable( {columns.map((column, j) => ( selectItem(item)} - rawColumn={(component.properties.columns as Static)[j]} + rawColumn={ + ( + component.properties.columns as Static< + typeof ColumnsPropertySpec + > + )[j] + } 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 66df26ef..be72e157 100644 --- a/packages/chakra-ui-lib/src/components/Table/TableTd.tsx +++ b/packages/chakra-ui-lib/src/components/Table/TableTd.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { RuntimeApplication } from '@sunmao-ui/core'; +import { RuntimeApplication, RuntimeComponentSchema } from '@sunmao-ui/core'; import { Static } from '@sinclair/typebox'; import { ColumnSpec, ColumnsPropertySpec } from './TableTypes'; import { Button, Link, Td, Text } from '@chakra-ui/react'; @@ -9,6 +9,7 @@ import { ModuleRenderer, UIServices, ExpressionError, + ImplWrapper, } from '@sunmao-ui/runtime'; export const TableTd: React.FC<{ @@ -18,9 +19,10 @@ export const TableTd: React.FC<{ rawColumn: Static[0]; onClickItem: () => void; services: UIServices; + component: RuntimeComponentSchema; app: RuntimeApplication; }> = props => { - const { item, index, column, rawColumn, onClickItem, services, app } = props; + const { item, index, component, column, rawColumn, onClickItem, services, app } = props; const evalOptions = { evalListItem: true, scopeObject: { @@ -71,10 +73,6 @@ export const TableTd: React.FC<{ content = ; break; case 'module': - const evalScope = { - [LIST_ITEM_EXP]: item, - [LIST_ITEM_INDEX_EXP]: index, - }; content = ( ); break; + case 'component': + const childrenSchema = app.spec.components.filter(c => { + return c.traits.find( + t => + t.type === 'core/v1/slot' && + (t.properties.container as any).id === component.id + ); + }); + + const childSchema = childrenSchema[column.componentSlotIndex]; + if (!childSchema) { + return ( +
      Cannot find child with index {column.componentSlotIndex} in slot.
      + ); + } + + const _childrenSchema = { + ...childSchema, + id: `${component.id}_${childSchema.id}_${index}`, + }; + + content = ( + + ); + break; } return ( diff --git a/packages/chakra-ui-lib/src/components/Table/TableTypes.ts b/packages/chakra-ui-lib/src/components/Table/TableTypes.ts index dd13b7d1..3b0e93b1 100644 --- a/packages/chakra-ui-lib/src/components/Table/TableTypes.ts +++ b/packages/chakra-ui-lib/src/components/Table/TableTypes.ts @@ -30,6 +30,7 @@ export const TableSizePropertySpec = Type.KeyOf( export const TdTypeSpec = Type.KeyOf( Type.Object({ text: Type.String(), + component: Type.String(), image: Type.String(), link: Type.String(), button: Type.String(), @@ -52,6 +53,15 @@ export const ColumnSpec = Type.Object( title: 'Display value', }), type: TdTypeSpec, + componentSlotIndex: Type.Number({ + title: 'Component Slot Index', + conditions: [ + { + key: 'type', + value: 'component', + }, + ], + }), buttonConfig: Type.Object( { text: Type.String({ diff --git a/packages/chakra-ui-lib/src/components/Table/spec.ts b/packages/chakra-ui-lib/src/components/Table/spec.ts index 5d141e65..8a684f10 100644 --- a/packages/chakra-ui-lib/src/components/Table/spec.ts +++ b/packages/chakra-ui-lib/src/components/Table/spec.ts @@ -1,5 +1,9 @@ import { Type } from '@sinclair/typebox'; -import { implementRuntimeComponent } from '@sunmao-ui/runtime'; +import { + implementRuntimeComponent, + LIST_ITEM_EXP, + LIST_ITEM_INDEX_EXP, +} from '@sunmao-ui/runtime'; import { ColumnsPropertySpec, DataPropertySpec, @@ -62,7 +66,14 @@ export const implementTable = implementRuntimeComponent({ properties: PropsSpec, state: TableStateSpec, methods: {}, - slots: {}, + slots: { + content: { + slotProps: Type.Object({ + [LIST_ITEM_EXP]: Type.Any(), + [LIST_ITEM_INDEX_EXP]: Type.Number(), + }), + }, + }, styleSlots: [], events: [], }, diff --git a/packages/runtime/src/components/_internal/ComponentRenderer.tsx b/packages/runtime/src/components/_internal/ComponentRenderer.tsx deleted file mode 100644 index 087955c6..00000000 --- a/packages/runtime/src/components/_internal/ComponentRenderer.tsx +++ /dev/null @@ -1,73 +0,0 @@ -import { Static } from '@sinclair/typebox'; -import React, { useMemo } from 'react'; -import { parseType, RuntimeApplication, RuntimeComponentSchema } from '@sunmao-ui/core'; -import { ImplWrapper } from './ImplWrapper'; -import { UIServices } from '../../types'; -import { ModuleRenderSpec } from '@sunmao-ui/shared'; -import { initStateAndMethod } from '../../utils/initStateAndMethod'; - -type Props = Static & { - evalScope?: Record; - services: UIServices; - app: RuntimeApplication; - traits: any; -}; - -export const ComponentRenderer = React.forwardRef((props, ref) => { - const { type } = props; - try { - return ; - } catch (e) { - console.log(e); - return
      Cannot find Module {type}.
      ; - } -}); - -const ComponentRendererContent = React.forwardRef((props, ref) => { - const { properties, evalScope, services, app } = props; - const evalOptions = { evalListItem: true, scopeObject: evalScope }; - const componentId = services.stateManager.maskedEval(props.id, evalOptions) as string; - - function evalObject>(obj: T): T { - return services.stateManager.mapValuesDeep({ obj }, ({ value }) => { - if (typeof value === 'string') { - return services.stateManager.maskedEval(value, evalOptions); - } - return value; - }).obj; - } - - // first eval the property, handlers, id of module - const evaledProperties = evalObject(properties); - const evaledTraits = evalObject(props.traits); - - const componentSchema: RuntimeComponentSchema = useMemo(() => { - return { - id: componentId, - properties: evaledProperties, - type: props.type, - traits: evaledTraits, - parsedType: parseType(props.type), - }; - }, [componentId, evaledProperties, evaledTraits, props.type]); - - const result = useMemo(() => { - // Must init components' state, otherwise store cannot listen these components' state changing - initStateAndMethod(services.registry, services.stateManager, [componentSchema]); - return ( - - ); - }, [services, componentSchema, app]); - - return ( -
      - {result} -
      - ); -}); diff --git a/packages/runtime/src/components/_internal/ImplWrapper/ImplWrapperMain.tsx b/packages/runtime/src/components/_internal/ImplWrapper/ImplWrapperMain.tsx index 81ee56fb..bae1ace9 100644 --- a/packages/runtime/src/components/_internal/ImplWrapper/ImplWrapperMain.tsx +++ b/packages/runtime/src/components/_internal/ImplWrapper/ImplWrapperMain.tsx @@ -14,14 +14,14 @@ export const ImplWrapperMain = React.forwardRef { +})(({ listData, component, app, services, customStyle, elementRef }) => { if (!listData) { return null; } - - const childSchema = app.spec.components.find(c => c.id === 'listSlot')!; - const listItems = listData.map((listItem, i) => { - // const evalScope = { - // [LIST_ITEM_EXP]: listItem, - // [LIST_ITEM_INDEX_EXP]: i, - // }; - const _childSchema = { - ...childSchema, - id: `list_${childSchema.id}${i}`, - }; - const listItemEle = ( - + const childrenSchema = app.spec.components.filter(c => { + return c.traits.find( + t => + t.type === 'core/v1/slot' && (t.properties.container as any).id === component.id ); + }); - return listItemEle; + if (childrenSchema.length === 0) { + return ( +
      + List has no children. Drag components into here to add listItems. +
      + ); + } + + const listItems = listData.map((listItem, i) => { + const _childrenSchema = childrenSchema.map(child => { + return { + ...child, + id: `${component.id}_${child.id}_${i}`, + }; + }); + + const childrenEles = _childrenSchema.map(child => { + return ( + + ); + }); + + return
    • {childrenEles}
    • ; }); return ( diff --git a/packages/runtime/src/index.ts b/packages/runtime/src/index.ts index a076aba6..ea2adea6 100644 --- a/packages/runtime/src/index.ts +++ b/packages/runtime/src/index.ts @@ -67,6 +67,7 @@ export type { RegistryInterface, SunmaoLib } from './services/Registry'; export { ExpressionError } from './services/StateManager'; export type { StateManagerInterface } from './services/StateManager'; export { ModuleRenderer } from './components/_internal/ModuleRenderer'; +export { ImplWrapper } from './components/_internal/ImplWrapper'; export { default as Text, TextPropertySpec } from './components/_internal/Text'; // TODO: check this export From ab9d8fd96f83e7ceac36226a7673009611306110 Mon Sep 17 00:00:00 2001 From: Bowen Tan Date: Mon, 6 Jun 2022 11:26:54 +0800 Subject: [PATCH 5/9] test(list): update list examples --- examples/list/listComponent.json | 51 +++-- examples/list/listModule.json | 311 ++++++++++++++++++------------- packages/runtime/index.html | 5 +- 3 files changed, 217 insertions(+), 150 deletions(-) diff --git a/examples/list/listComponent.json b/examples/list/listComponent.json index e082c411..980ab722 100644 --- a/examples/list/listComponent.json +++ b/examples/list/listComponent.json @@ -17,7 +17,7 @@ "type": "core/v1/state", "properties": { "key": "listTitle", - "initialValue": "客户列表" + "initialValue": "Customers List" } }, { @@ -27,28 +27,28 @@ "initialValue": [ { "id": 1, - "name": "马云", - "email": "jack.ma@deck.com" + "name": "Tom", + "email": "tom@deck.com" }, { "id": 2, - "name": "马化腾", - "email": "pony.ma@conversation.com" + "name": "Jack", + "email": "jack@conversation.com" }, { "id": 3, - "name": "李彦宏", - "email": "robin.li@response.com" + "name": "Pony", + "email": "pony@response.com" }, { "id": 4, - "name": "张一鸣", - "email": "yiming.zhang@example.com" + "name": "Peter", + "email": "peter@example.com" }, { "id": 5, - "name": "王兴", - "email": "xing.wang@widget.org" + "name": "John", + "email": "john@widget.org" } ] } @@ -74,11 +74,32 @@ ] }, { - "id": "listSlot", + "id": "listSlotText", + "type": "core/v1/text", + "properties": { + "value": { + "raw": "{{$slot.$listItem.name}}", + "format": "md" + } + }, + "traits": [ + { + "type": "core/v1/slot", + "properties": { + "container": { + "id": "list", + "slot": "content" + } + } + } + ] + }, + { + "id": "listSlotButton", "type": "chakra_ui/v1/button", "properties": { "text": { - "raw": "我是Slot{{$slot.$listItem.name}}", + "raw": "Click Me", "format": "md" } }, @@ -95,8 +116,8 @@ "parameters": { "id": "createSuccessToast", "title": "Tost", - "description": "data{{$slot.$listItem.name}}", - "position": "bottom-right", + "description": "Hello, {{$slot.$listItem.name}}!", + "position": "top", "duration": null, "isClosable": true } diff --git a/examples/list/listModule.json b/examples/list/listModule.json index 8d551515..f0758d28 100644 --- a/examples/list/listModule.json +++ b/examples/list/listModule.json @@ -1,133 +1,135 @@ { - "modules": [{ - "version": "core/v1", - "kind": "Module", - "parsedVersion": { - "category": "core/v1", - "value": "littleItem" - }, - "metadata": { - "name": "littleItem" - }, - "spec": { - "properties": {}, - "events": ["onEdit"], - "stateMap": { - "value": "{{ $moduleId }}__input.value" - } - }, - "impl": [ - { - "id": "{{ $moduleId }}__hstack", - "type": "chakra_ui/v1/hstack", + "modules": [ + { + "version": "custom/v1", + "kind": "Module", + "parsedVersion": { + "category": "custom/v1", + "value": "listItem" + }, + "metadata": { + "name": "listItem" + }, + "spec": { "properties": {}, - "traits": [] + "events": ["onEdit"], + "stateMap": { + "value": "{{ $moduleId }}__input.value" + } }, - { - "id": "{{ $moduleId }}__text", - "type": "core/v1/text", - "properties": { - "value": { - "raw": "**{{value}}**", - "format": "md" - } + "impl": [ + { + "id": "{{ $moduleId }}__hstack", + "type": "chakra_ui/v1/hstack", + "properties": {}, + "traits": [] }, - "traits": [ - { - "type": "core/v1/slot", - "properties": { - "container": { - "id": "{{ $moduleId }}__hstack", - "slot": "content" - } - } - } - ] - }, - { - "id": "{{ $moduleId }}__inputValueText", - "type": "core/v1/text", - "properties": { - "value": { - "raw": "**{{ {{ $moduleId }}__input.value }}**", - "format": "md" - } - }, - "traits": [ - { - "type": "core/v1/slot", - "properties": { - "container": { - "id": "{{ $moduleId }}__hstack", - "slot": "content" - } - } - } - ] - }, - { - "id": "{{ $moduleId }}__input", - "type": "chakra_ui/v1/input", - "properties": {}, - "traits": [ - { - "type": "core/v1/slot", - "properties": { - "container": { - "id": "{{ $moduleId }}__hstack", - "slot": "content" - } - } - } - ] - }, - { - "id": "{{ $moduleId }}__button", - "type": "chakra_ui/v1/button", - "properties": { - "text": { - "raw": "click{{value}}", - "format": "md" - } - }, - "traits": [ - { - "type": "core/v1/event", - "properties": { - "handlers": [ - { - "type": "onClick", - "componentId": "$module", - "method": { - "name": "onEdit", - "parameters": { - "moduleId": "{{$moduleId}}" - } - }, - "wait": {}, - "disabled": false - } - ] + { + "id": "{{ $moduleId }}__text", + "type": "core/v1/text", + "properties": { + "value": { + "raw": "**{{value}}**", + "format": "md" } }, - { - "type": "core/v1/slot", - "properties": { - "container": { - "id": "{{ $moduleId }}__hstack", - "slot": "content" + "traits": [ + { + "type": "core/v1/slot", + "properties": { + "container": { + "id": "{{ $moduleId }}__hstack", + "slot": "content" + } } } - } - ] - } - ] - }], + ] + }, + { + "id": "{{ $moduleId }}__inputValueText", + "type": "core/v1/text", + "properties": { + "value": { + "raw": "**{{ {{ $moduleId }}__input.value }}**", + "format": "md" + } + }, + "traits": [ + { + "type": "core/v1/slot", + "properties": { + "container": { + "id": "{{ $moduleId }}__hstack", + "slot": "content" + } + } + } + ] + }, + { + "id": "{{ $moduleId }}__input", + "type": "chakra_ui/v1/input", + "properties": {}, + "traits": [ + { + "type": "core/v1/slot", + "properties": { + "container": { + "id": "{{ $moduleId }}__hstack", + "slot": "content" + } + } + } + ] + }, + { + "id": "{{ $moduleId }}__button", + "type": "chakra_ui/v1/button", + "properties": { + "text": { + "raw": "click{{value}}", + "format": "md" + } + }, + "traits": [ + { + "type": "core/v1/event", + "properties": { + "handlers": [ + { + "type": "onClick", + "componentId": "$module", + "method": { + "name": "onEdit", + "parameters": { + "moduleId": "{{$moduleId}}" + } + }, + "wait": {}, + "disabled": false + } + ] + } + }, + { + "type": "core/v1/slot", + "properties": { + "container": { + "id": "{{ $moduleId }}__hstack", + "slot": "content" + } + } + } + ] + } + ] + } + ], "app": { - "version": "example/v1", + "version": "sunmao/v1", + "kind": "Application", "metadata": { - "name": "list_component", - "description": "list component example" + "name": "some App" }, "spec": { "components": [ @@ -140,7 +142,7 @@ "type": "core/v1/state", "properties": { "key": "listTitle", - "initialValue": "客户列表" + "initialValue": "Customers List" } }, { @@ -150,28 +152,28 @@ "initialValue": [ { "id": 1, - "name": "马云", - "email": "jack.ma@deck.com" + "name": "Tom", + "email": "tom@deck.com" }, { "id": 2, - "name": "马化腾", - "email": "pony.ma@conversation.com" + "name": "Jack", + "email": "jack@conversation.com" }, { "id": 3, - "name": "李彦宏", - "email": "robin.li@response.com" + "name": "Pony", + "email": "pony@response.com" }, { "id": 4, - "name": "张一鸣", - "email": "yiming.zhang@example.com" + "name": "Peter", + "email": "peter@example.com" }, { "id": 5, - "name": "王兴", - "email": "xing.wang@widget.org" + "name": "John", + "email": "john@widget.org" } ] } @@ -180,12 +182,12 @@ }, { "id": "list", - "type": "chakra_ui/v1/list", + "type": "core/v1/list", "properties": { "listData": "{{ root.listData }}", "template": { - "id": "littleItem{{$listItem.id}}", - "type": "core/v1/littleItem", + "id": "listItem{{$listItem.id}}", + "type": "custom/v1/listItem", "properties": { "value": "{{$listItem.name}}" }, @@ -214,6 +216,47 @@ } } ] + }, + { + "id": "moduleContainer2", + "type": "core/v1/moduleContainer", + "properties": { + "id": "listItem{{$slot.$listItem.id}}", + "type": "custom/v1/listItem", + "properties": { + "value": "{{$slot.$listItem.name}}" + }, + "handlers": [ + { + "type": "onEdit", + "componentId": "$utils", + "method": { + "name": "chakra_ui/v1/openToast", + "parameters": { + "position": "top", + "duration": 1000, + "title": "Hello,{{$slot.$listItem.name}}!", + "description": "", + "isClosable": false, + "variant": "subtle", + "status": "info", + "id": "" + } + } + } + ] + }, + "traits": [ + { + "type": "core/v1/slot", + "properties": { + "container": { + "id": "list", + "slot": "content" + } + } + } + ] } ] } diff --git a/packages/runtime/index.html b/packages/runtime/index.html index 148d069b..ed968c33 100644 --- a/packages/runtime/index.html +++ b/packages/runtime/index.html @@ -29,8 +29,11 @@ const rootEl = document.querySelector('#root'); const render = example => { ReactDOM.unmountComponentAtNode(rootEl); - const { App, registry } = initSunmaoUI({libs: [sunmaoChakraUILib]}); + const { App, registry } = initSunmaoUI({ libs: [sunmaoChakraUILib] }); const { app, modules = [] } = example.value; + console.log('example', example); + console.log('modules', modules); + window.registry = registry; modules.forEach(m => { registry.registerModule(m); }); From 63971a46aed75ee1994dc682db53cd0334355063 Mon Sep 17 00:00:00 2001 From: Bowen Tan Date: Mon, 6 Jun 2022 16:42:19 +0800 Subject: [PATCH 6/9] feat(arco-table): support render component and update example --- .../arco-lib/src/components/Pagination.tsx | 2 +- packages/arco-lib/src/components/Radio.tsx | 2 +- packages/arco-lib/src/components/Select.tsx | 2 +- packages/arco-lib/src/components/Switch.tsx | 2 +- .../arco-lib/src/components/Table/Table.tsx | 95 ++++++-- packages/arco-lib/src/components/Tabs.tsx | 2 +- packages/arco-lib/src/components/TextArea.tsx | 2 +- .../arco-lib/src/components/TreeSelect.tsx | 2 +- .../examples/pages/table/customComponent.ts | 209 ++++++++++++++++++ .../src/examples/pages/table/index.tsx | 7 + .../arco-lib/src/generated/types/Table.ts | 20 ++ 11 files changed, 314 insertions(+), 31 deletions(-) create mode 100644 packages/arco-lib/src/examples/pages/table/customComponent.ts diff --git a/packages/arco-lib/src/components/Pagination.tsx b/packages/arco-lib/src/components/Pagination.tsx index 40539e1d..d477de27 100644 --- a/packages/arco-lib/src/components/Pagination.tsx +++ b/packages/arco-lib/src/components/Pagination.tsx @@ -4,7 +4,7 @@ import { css, cx } from '@emotion/css'; import { Type, Static } from '@sinclair/typebox'; import { FALLBACK_METADATA, getComponentProps } from '../sunmao-helper'; import { PaginationPropsSpec as BasePaginationPropsSpec } from '../generated/types/Pagination'; -import { useStateValue } from 'src/hooks/useStateValue'; +import { useStateValue } from '../hooks/useStateValue'; const PaginationPropsSpec = Type.Object(BasePaginationPropsSpec); const PaginationStateSpec = Type.Object({ diff --git a/packages/arco-lib/src/components/Radio.tsx b/packages/arco-lib/src/components/Radio.tsx index 5e91c463..ca117969 100644 --- a/packages/arco-lib/src/components/Radio.tsx +++ b/packages/arco-lib/src/components/Radio.tsx @@ -5,7 +5,7 @@ import { Type, Static } from '@sinclair/typebox'; import { FALLBACK_METADATA, getComponentProps } from '../sunmao-helper'; import { RadioPropsSpec as BaseRadioPropsSpec } from '../generated/types/Radio'; import { useEffect } from 'react'; -import { useStateValue } from 'src/hooks/useStateValue'; +import { useStateValue } from '../hooks/useStateValue'; const RadioPropsSpec = Type.Object({ ...BaseRadioPropsSpec, diff --git a/packages/arco-lib/src/components/Select.tsx b/packages/arco-lib/src/components/Select.tsx index e2934f5f..bbdbf3a0 100644 --- a/packages/arco-lib/src/components/Select.tsx +++ b/packages/arco-lib/src/components/Select.tsx @@ -6,7 +6,7 @@ import { FALLBACK_METADATA, getComponentProps } from '../sunmao-helper'; import { SelectPropsSpec as BaseSelectPropsSpec } from '../generated/types/Select'; import { useEffect, useRef } from 'react'; import { SelectHandle } from '@arco-design/web-react/es/Select/interface'; -import { useStateValue } from 'src/hooks/useStateValue'; +import { useStateValue } from '../hooks/useStateValue'; const SelectPropsSpec = Type.Object({ ...BaseSelectPropsSpec, diff --git a/packages/arco-lib/src/components/Switch.tsx b/packages/arco-lib/src/components/Switch.tsx index 13d6fbac..23a89bb2 100644 --- a/packages/arco-lib/src/components/Switch.tsx +++ b/packages/arco-lib/src/components/Switch.tsx @@ -4,7 +4,7 @@ import { css } from '@emotion/css'; import { Type, Static } from '@sinclair/typebox'; import { FALLBACK_METADATA, getComponentProps } from '../sunmao-helper'; import { SwitchPropsSpec as BaseSwitchPropsSpec } from '../generated/types/Switch'; -import { useStateValue } from 'src/hooks/useStateValue'; +import { useStateValue } from '../hooks/useStateValue'; const SwitchPropsSpec = Type.Object({ ...BaseSwitchPropsSpec, diff --git a/packages/arco-lib/src/components/Table/Table.tsx b/packages/arco-lib/src/components/Table/Table.tsx index dc779bf3..4940b295 100644 --- a/packages/arco-lib/src/components/Table/Table.tsx +++ b/packages/arco-lib/src/components/Table/Table.tsx @@ -1,4 +1,11 @@ /* eslint-disable @typescript-eslint/ban-types */ +import React, { useEffect, useMemo, useRef, useState } from 'react'; +import { css } from '@emotion/css'; +import { sortBy } from 'lodash-es'; +import { ResizeCallbackData } from 'react-resizable'; +import { TableInstance } from '@arco-design/web-react/es/Table/table'; +import { ColumnProps } from '@arco-design/web-react/es/Table'; +import { RefInputType } from '@arco-design/web-react/es/Input/interface'; import { Button, Link, @@ -11,19 +18,14 @@ import { LIST_ITEM_INDEX_EXP, ModuleRenderer, implementRuntimeComponent, + ImplWrapper, } from '@sunmao-ui/runtime'; -import { css } from '@emotion/css'; -import { sortBy } from 'lodash-es'; import { Type, Static } from '@sinclair/typebox'; +import { ResizableTitle } from './ResizableTitle'; + import { FALLBACK_METADATA, getComponentProps } from '../../sunmao-helper'; import { TablePropsSpec, ColumnSpec } from '../../generated/types/Table'; -import React, { useEffect, useMemo, useRef, useState } from 'react'; -import { TableInstance } from '@arco-design/web-react/es/Table/table'; -import { ColumnProps } from '@arco-design/web-react/es/Table'; -import { useStateValue } from 'src/hooks/useStateValue'; -import { ResizableTitle } from './ResizableTitle'; -import { RefInputType } from '@arco-design/web-react/es/Input/interface'; -import { ResizeCallbackData } from 'react-resizable'; +import { useStateValue } from '../../hooks/useStateValue'; const TableStateSpec = Type.Object({ clickedRow: Type.Optional(Type.Any()), @@ -80,6 +82,7 @@ export const exampleProperties: Static = { type: 'text', filter: true, displayValue: '', + componentSlotIndex: 0, }, { title: 'Salary', @@ -89,6 +92,7 @@ export const exampleProperties: Static = { filter: false, type: 'text', displayValue: '', + componentSlotIndex: 0, }, { title: 'Link', @@ -97,19 +101,7 @@ export const exampleProperties: Static = { filter: true, sortDirections: ['ascend', 'descend'], displayValue: '', - }, - { - title: 'CustomComponent', - dataIndex: 'customComponent', - type: 'module', - filter: false, - module: { - id: 'clistItemName-{{$listItem.id}}', - handlers: [], - properties: [], - type: 'core/v1/text', - }, - displayValue: '', + componentSlotIndex: 0, }, ], data: Array(13) @@ -156,7 +148,14 @@ export const Table = implementRuntimeComponent({ properties: TablePropsSpec, state: TableStateSpec, methods: {}, - slots: {}, + slots: { + content: { + slotProps: Type.Object({ + [LIST_ITEM_EXP]: Type.Any(), + [LIST_ITEM_INDEX_EXP]: Type.Number(), + }), + }, + }, styleSlots: ['content'], events: ['onRowClick', 'onSearch', 'onPageChange', 'onFilter', 'onSort', 'onChange'], }, @@ -355,6 +354,46 @@ export const Table = implementRuntimeComponent({ /> ); break; + + case 'component': + const childrenSchema = app.spec.components.filter(c => { + return c.traits.find( + t => + t.type === 'core/v1/slot' && + (t.properties.container as any).id === component.id + ); + }); + + const childSchema = childrenSchema[evaledColumn.componentSlotIndex || 0]; + if (!childSchema) { + return ( +
      + Cannot find child with index {column.componentSlotIndex} in slot. +
      + ); + } + + const _childrenSchema = { + ...childSchema, + id: `${component.id}_${childSchema.id}_${index}`, + }; + + colItem = ( + + ); + break; default: const text = evaledColumn.displayValue || value; colItem = {text}; @@ -365,7 +404,15 @@ export const Table = implementRuntimeComponent({ return newColumn; }) ); - }, [cProps.columns]); + }, [ + app, + cProps.columns, + callbackMap, + component.id, + component.properties.columns, + services, + useDefaultFilter, + ]); const handleChange = ( pagination: PaginationProps, diff --git a/packages/arco-lib/src/components/Tabs.tsx b/packages/arco-lib/src/components/Tabs.tsx index 650f75d8..d488166e 100644 --- a/packages/arco-lib/src/components/Tabs.tsx +++ b/packages/arco-lib/src/components/Tabs.tsx @@ -5,7 +5,7 @@ import { Type, Static } from '@sinclair/typebox'; import { FALLBACK_METADATA, getComponentProps } from '../sunmao-helper'; import { TabsPropsSpec as BaseTabsPropsSpec } from '../generated/types/Tabs'; import { useEffect, useRef } from 'react'; -import { useStateValue } from 'src/hooks/useStateValue'; +import { useStateValue } from '../hooks/useStateValue'; const TabsPropsSpec = Type.Object(BaseTabsPropsSpec); const TabsStateSpec = Type.Object({ diff --git a/packages/arco-lib/src/components/TextArea.tsx b/packages/arco-lib/src/components/TextArea.tsx index 2e2fecbf..a3c41826 100644 --- a/packages/arco-lib/src/components/TextArea.tsx +++ b/packages/arco-lib/src/components/TextArea.tsx @@ -6,7 +6,7 @@ import { FALLBACK_METADATA, getComponentProps } from '../sunmao-helper'; import { TextAreaPropsSpec as BaseTextAreaPropsSpec } from '../generated/types/TextArea'; import { useEffect, useRef } from 'react'; import { RefInputType } from '@arco-design/web-react/es/Input/interface'; -import { useStateValue } from 'src/hooks/useStateValue'; +import { useStateValue } from '../hooks/useStateValue'; const TextAreaPropsSpec = Type.Object({ ...BaseTextAreaPropsSpec, diff --git a/packages/arco-lib/src/components/TreeSelect.tsx b/packages/arco-lib/src/components/TreeSelect.tsx index 92520285..dfe6bea2 100644 --- a/packages/arco-lib/src/components/TreeSelect.tsx +++ b/packages/arco-lib/src/components/TreeSelect.tsx @@ -6,7 +6,7 @@ import { FALLBACK_METADATA, getComponentProps } from '../sunmao-helper'; import { TreeSelectPropsSpec as BaseTreeSelectPropsSpec } from '../generated/types/TreeSelect'; import { useEffect, useRef } from 'react'; import { RefTreeSelectType } from '@arco-design/web-react/es/TreeSelect'; -import { useStateValue } from 'src/hooks/useStateValue'; +import { useStateValue } from '../hooks/useStateValue'; const TreeSelectPropsSpec = Type.Object(BaseTreeSelectPropsSpec); const TreeSelectStateSpec = Type.Object({ diff --git a/packages/arco-lib/src/examples/pages/table/customComponent.ts b/packages/arco-lib/src/examples/pages/table/customComponent.ts new file mode 100644 index 00000000..0f8713cb --- /dev/null +++ b/packages/arco-lib/src/examples/pages/table/customComponent.ts @@ -0,0 +1,209 @@ +import { Application } from '@sunmao-ui/core'; + +export const customComponent: Application = { + version: 'sunmao/v1', + kind: 'Application', + metadata: { + name: 'some App', + }, + spec: { + components: [ + { + id: 'table', + type: 'arco/v1/table', + properties: { + columns: [ + { + title: 'Name', + dataIndex: 'name', + sorter: true, + sortDirections: ['ascend', 'descend'], + type: 'text', + filter: true, + displayValue: '', + componentSlotIndex: 0, + }, + { + title: 'Salary', + dataIndex: 'salary', + sorter: true, + sortDirections: ['ascend', 'descend'], + filter: false, + type: 'text', + displayValue: '', + componentSlotIndex: 0, + }, + { + title: 'Link', + dataIndex: 'link', + type: 'link', + filter: true, + sortDirections: ['ascend', 'descend'], + displayValue: '', + componentSlotIndex: 0, + }, + { + title: 'Hello', + type: 'component', + componentSlotIndex: 0, + dataIndex: '', + displayValue: '', + filter: false, + }, + ], + data: [ + { + key: 'key 0', + name: 'Naomi Cook0', + link: 'link-A', + salary: 272, + }, + { + key: 'key 1', + name: 'Kevin Sandra1', + link: 'link-B', + salary: 911, + }, + { + key: 'key 2', + name: 'Kevin Sandra2', + link: 'link-B', + salary: 527, + }, + { + key: 'key 3', + name: 'Kevin Sandra3', + link: 'link-B', + salary: 906, + }, + { + key: 'key 4', + name: 'Naomi Cook4', + link: 'link-A', + salary: 261, + }, + { + key: 'key 5', + name: 'Naomi Cook5', + link: 'link-A', + salary: 134, + }, + { + key: 'key 6', + name: 'Kevin Sandra6', + link: 'link-A', + salary: 877, + }, + { + key: 'key 7', + name: 'Kevin Sandra7', + link: 'link-A', + salary: 287, + }, + { + key: 'key 8', + name: 'Naomi Cook8', + link: 'link-B', + salary: 319, + }, + { + key: 'key 9', + name: 'Kevin Sandra9', + link: 'link-B', + salary: 105, + }, + { + key: 'key 10', + name: 'Naomi Cook10', + link: 'link-B', + salary: 468, + }, + { + key: 'key 11', + name: 'Naomi Cook11', + link: 'link-A', + salary: 53, + }, + { + key: 'key 12', + name: 'Naomi Cook12', + link: 'link-A', + salary: 195, + }, + ], + checkCrossPage: true, + pagination: { + enablePagination: true, + pageSize: 6, + defaultCurrent: 1, + updateWhenDefaultPageChanges: false, + useCustomPagination: false, + }, + rowClick: false, + tableLayoutFixed: false, + borderCell: false, + stripe: false, + size: 'default', + useDefaultFilter: true, + useDefaultSort: true, + pagePosition: 'bottomCenter', + rowSelectionType: 'single', + border: true, + loading: false, + }, + traits: [], + }, + { + id: 'button4', + type: 'arco/v1/button', + properties: { + type: 'primary', + status: 'default', + long: false, + size: 'small', + disabled: false, + loading: false, + shape: 'round', + text: 'Click', + }, + traits: [ + { + type: 'core/v1/slot', + properties: { + container: { + id: 'table', + slot: 'content', + }, + }, + }, + { + type: 'core/v1/event', + properties: { + handlers: [ + { + type: 'onClick', + componentId: '$utils', + method: { + name: 'arco/v1/message', + parameters: { + type: 'info', + content: 'Hello{{$slot.$listItem.name}}', + position: 'top', + closable: false, + duration: 1000, + }, + }, + disabled: false, + wait: { + type: 'delay', + time: 0, + }, + }, + ], + }, + }, + ], + }, + ], + }, +}; diff --git a/packages/arco-lib/src/examples/pages/table/index.tsx b/packages/arco-lib/src/examples/pages/table/index.tsx index 12f0488c..860e893e 100644 --- a/packages/arco-lib/src/examples/pages/table/index.tsx +++ b/packages/arco-lib/src/examples/pages/table/index.tsx @@ -4,6 +4,7 @@ import { basicUsage } from './basicUsage'; import { selection } from './selection'; import { attributes } from './attributes'; import { sortAndFilter } from './sortAndFilter'; +import { customComponent } from './customComponent'; const { Title, Text, Paragraph } = Typography; @@ -24,6 +25,12 @@ export const TableDemoPage: React.FC = () => { You can easily open or close the properties of the table + Custom Column Component + + You can use any Sunmao component as column element instead of plain text. + + + Sort and filter Configure the sortable or filterable of{' '} diff --git a/packages/arco-lib/src/generated/types/Table.ts b/packages/arco-lib/src/generated/types/Table.ts index 5d4bfcc6..f1769108 100644 --- a/packages/arco-lib/src/generated/types/Table.ts +++ b/packages/arco-lib/src/generated/types/Table.ts @@ -182,12 +182,22 @@ export const ColumnSpec = Type.Object({ link: Type.String(), button: Type.String(), module: Type.String(), + component: Type.String(), }), { title: 'Type', category: Category.Basic, } ), + componentSlotIndex: Type.Number({ + title: 'Component Slot Index', + conditions: [ + { + key: 'type', + value: 'component', + }, + ], + }), dataIndex: Type.String({ title: 'Key', category: Category.Basic, @@ -198,6 +208,16 @@ export const ColumnSpec = Type.Object({ title: 'Display Value', category: Category.Basic, description: 'The text you want to display instead of raw text', + conditions: [ + { + key: 'type', + value: 'link', + }, + { + key: 'type', + value: 'text', + }, + ], }), width: Type.Optional( Type.Number({ From 6e22f1f852e5f07ac45827b43cf12a00e036802b Mon Sep 17 00:00:00 2001 From: MrWindlike Date: Tue, 7 Jun 2022 14:15:49 +0800 Subject: [PATCH 7/9] feat: add the iframe component ISSUES CLOSED: #396 --- .../EditorMaskWrapper/EditorMaskWrapper.tsx | 22 ++++- .../runtime/src/components/core/Iframe.tsx | 97 +++++++++++++++++++ packages/runtime/src/services/Registry.tsx | 2 + packages/shared/src/constants/core.ts | 1 + 4 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 packages/runtime/src/components/core/Iframe.tsx diff --git a/packages/editor/src/components/EditorMaskWrapper/EditorMaskWrapper.tsx b/packages/editor/src/components/EditorMaskWrapper/EditorMaskWrapper.tsx index 40489dd9..f6711027 100644 --- a/packages/editor/src/components/EditorMaskWrapper/EditorMaskWrapper.tsx +++ b/packages/editor/src/components/EditorMaskWrapper/EditorMaskWrapper.tsx @@ -1,4 +1,4 @@ -import React, { useMemo, useRef, useState } from 'react'; +import React, { useMemo, useRef, useState, useEffect, useCallback } from 'react'; import { EditorServices } from '../../types'; import { observer } from 'mobx-react-lite'; import { Box } from '@chakra-ui/react'; @@ -66,6 +66,26 @@ export const EditorMaskWrapper: React.FC = observer(props => { ); }; + const onClickIframe = useCallback(() => { + if (document.activeElement?.tagName === 'IFRAME') { + setSelectedComponentId(document.activeElement?.getAttribute('title') || ''); + setTimeout(() => { + window.focus(); + }); + } + }, [setSelectedComponentId]); + + // can't capture the iframe click event + // use window's blur event to detect whether clicking the iframes + useEffect(() => { + window.focus(); + window.addEventListener('blur', onClickIframe); + + return () => { + window.removeEventListener('blur', onClickIframe); + }; + }, [onClickIframe]); + const mousePositionWithOffset: [number, number] = [ mousePosition[0] + scrollOffset[0], mousePosition[1] + scrollOffset[1], diff --git a/packages/runtime/src/components/core/Iframe.tsx b/packages/runtime/src/components/core/Iframe.tsx new file mode 100644 index 00000000..a525caa2 --- /dev/null +++ b/packages/runtime/src/components/core/Iframe.tsx @@ -0,0 +1,97 @@ +import { implementRuntimeComponent } from '../../utils/buildKit'; +import { Type } from '@sinclair/typebox'; +import { CORE_VERSION, CoreComponentName, StringUnion } from '@sunmao-ui/shared'; +import { css } from '@emotion/css'; + +export default implementRuntimeComponent({ + version: CORE_VERSION, + metadata: { + name: CoreComponentName.Iframe, + displayName: 'Iframe', + description: '', + isDraggable: false, + isResizable: false, + exampleProperties: { + src: 'https://www.openstreetmap.org/export/embed.html?bbox=-0.004017949104309083%2C51.47612752641776%2C0.00030577182769775396%2C51.478569861898606&layer=mapnik', + referrerpolicy: 'unset', + sandbox: 'unset', + fetchpriority: 'auto', + allowfullscreen: true, + }, + exampleSize: [1, 1], + annotations: { + category: 'Advance', + }, + }, + spec: { + properties: Type.Object({ + src: Type.String({ title: 'Src' }), + referrerpolicy: StringUnion( + [ + 'unset', + 'no-referrer', + 'no-referrer-when-downgrade', + 'origin', + 'origin-when-cross-origin', + 'same-origin', + 'strict-origin', + 'strict-origin-when-cross-origin', + 'unsafe-url', + ], + { + title: 'Referrer Policy', + description: + "Indicates which referrer to send when fetching the frame's resource.", + } + ), + sandbox: StringUnion( + [ + 'unset', + 'allow-forms', + 'allow-modals', + 'allow-orientation-lock', + 'allow-pointer-lock', + 'allow-popups', + 'allow-popups-to-escape-sandbox', + 'allow-presentation', + 'allow-same-origin', + 'allow-scripts', + 'allow-top-navigation', + 'allow-top-navigation-by-user-activation', + ], + { + title: 'Sandbox', + description: 'Applies extra restrictions to the content in the frame.', + } + ), + fetchpriority: StringUnion(['auto', 'low', 'high'], { + title: 'Fetch Priority', + description: + 'Provides a hint of the relative priority to use when fetching the iframe document', + }), + allowfullscreen: Type.Boolean({ title: 'Allow fullscreen' }), + }), + state: Type.Object({}), + methods: {}, + slots: {}, + styleSlots: ['content'], + events: [], + }, +})(({ component, src, referrerpolicy, sandbox, elementRef, customStyle }) => { + const iframeProps: Record = { + title: component.id, + src, + }; + + if (referrerpolicy !== 'unset') { + iframeProps.referrerpolicy = referrerpolicy; + } + + if (sandbox !== 'unset') { + iframeProps.sandbox = sandbox; + } + + return ( +