From f2338814053dc5c57f7ce5a4c8d06f66ecdf69b5 Mon Sep 17 00:00:00 2001 From: xzdry Date: Sat, 29 Jan 2022 14:41:21 +0800 Subject: [PATCH 01/12] feat: change selected componet will automatically jump to the inspect interface --- .../src/components/ComponentWrapper.tsx | 4 ++++ packages/editor/src/components/Editor.tsx | 23 +++++++++++++++---- packages/editor/src/services/EditorStore.ts | 20 ++++++++++++++++ 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/packages/editor/src/components/ComponentWrapper.tsx b/packages/editor/src/components/ComponentWrapper.tsx index f134828a..8d4300a2 100644 --- a/packages/editor/src/components/ComponentWrapper.tsx +++ b/packages/editor/src/components/ComponentWrapper.tsx @@ -125,6 +125,8 @@ export function useComponentWrapper(services: EditorServices): ComponentWrapperT setHoverComponentId, dragOverComponentId, setDragOverComponentId, + setLeftTabIdx, + setRightTabIdx } = editorStore; const [slots, isDroppable] = useMemo(() => { @@ -156,6 +158,7 @@ export function useComponentWrapper(services: EditorServices): ComponentWrapperT const onClickWrapper = (e: React.MouseEvent) => { e.stopPropagation(); + setRightTabIdx(0); setSelectedComponentId(component.id); }; const onMouseEnterWrapper = (e: React.MouseEvent) => { @@ -222,6 +225,7 @@ export function useComponentWrapper(services: EditorServices): ComponentWrapperT e.preventDefault(); setDragOverComponentId(''); setCurrentSlot(undefined); + setLeftTabIdx(1); const creatingComponent = e.dataTransfer?.getData('component') || ''; eventBus.send( 'operation', diff --git a/packages/editor/src/components/Editor.tsx b/packages/editor/src/components/Editor.tsx index f2313171..d671861f 100644 --- a/packages/editor/src/components/Editor.tsx +++ b/packages/editor/src/components/Editor.tsx @@ -29,7 +29,15 @@ type Props = { export const Editor: React.FC = observer( ({ App, registry, stateStore, services }) => { const { eventBus, editorStore } = services; - const { components, selectedComponentId, modules } = editorStore; + const { + components, + selectedComponentId, + modules, + setLeftTabIdx, + setRightTabIdx, + leftTabIdx, + rightTabIdx, + } = editorStore; const [scale, setScale] = useState(100); const [preview, setPreview] = useState(false); @@ -89,10 +97,7 @@ export const Editor: React.FC = observer( const appComponent = useMemo(() => { return ( - + = observer( flexDirection="column" textAlign="left" isLazy + index={leftTabIdx} + onChange={idx => { + setLeftTabIdx(idx); + }} > Explorer @@ -195,6 +204,10 @@ export const Editor: React.FC = observer( height="100%" display="flex" flexDirection="column" + index={rightTabIdx} + onChange={idx => { + setRightTabIdx(idx); + }} > Inspect diff --git a/packages/editor/src/services/EditorStore.ts b/packages/editor/src/services/EditorStore.ts index 6e01beda..b8c49812 100644 --- a/packages/editor/src/services/EditorStore.ts +++ b/packages/editor/src/services/EditorStore.ts @@ -19,6 +19,10 @@ export class EditorStore { _selectedComponentId = ''; _hoverComponentId = ''; _dragOverComponentId = ''; + // default left tab: Explorer + _leftTabIdx = 0; + // default right tab: Insert + _rightTabIdx = 1; // current editor editing target(app or module) currentEditingTarget: EditingTarget = { kind: 'app', @@ -107,6 +111,14 @@ export class EditorStore { return this.currentComponentsVersion === this.lastSavedComponentsVersion; } + get leftTabIdx() { + return this._leftTabIdx; + } + + get rightTabIdx() { + return this._rightTabIdx; + } + // origin components of app of module // when switch app or module, components should refresh get originComponents(): ComponentSchema[] { @@ -180,4 +192,12 @@ export class EditorStore { setLastSavedComponentsVersion = (val: number) => { this.lastSavedComponentsVersion = val; }; + + setLeftTabIdx = (val: number) => { + this._leftTabIdx = val; + } + + setRightTabIdx = (val: number) => { + this._rightTabIdx = val; + } } From c7142b53e8dd84d3f35dea54e85dff41c8f7c998 Mon Sep 17 00:00:00 2001 From: xzdry Date: Sun, 30 Jan 2022 12:27:31 +0800 Subject: [PATCH 02/12] refactor: optimized naming --- .../src/components/ComponentWrapper.tsx | 7 ++--- packages/editor/src/components/Editor.tsx | 20 ++++++------- packages/editor/src/services/EditorStore.ts | 29 +++++++++---------- packages/editor/src/services/enum.ts | 13 +++++++++ 4 files changed, 39 insertions(+), 30 deletions(-) create mode 100644 packages/editor/src/services/enum.ts diff --git a/packages/editor/src/components/ComponentWrapper.tsx b/packages/editor/src/components/ComponentWrapper.tsx index 8d4300a2..3b1e85d8 100644 --- a/packages/editor/src/components/ComponentWrapper.tsx +++ b/packages/editor/src/components/ComponentWrapper.tsx @@ -7,6 +7,7 @@ import { ComponentWrapperType } from '@sunmao-ui/runtime'; import { genOperation } from '../operations'; import { EditorServices } from '../types'; +import { ExploreMenuTabs } from '../services/enum'; type ComponentEditorState = 'drag' | 'select' | 'hover' | 'idle'; @@ -125,8 +126,7 @@ export function useComponentWrapper(services: EditorServices): ComponentWrapperT setHoverComponentId, dragOverComponentId, setDragOverComponentId, - setLeftTabIdx, - setRightTabIdx + setExploreMenuTab, } = editorStore; const [slots, isDroppable] = useMemo(() => { @@ -158,7 +158,6 @@ export function useComponentWrapper(services: EditorServices): ComponentWrapperT const onClickWrapper = (e: React.MouseEvent) => { e.stopPropagation(); - setRightTabIdx(0); setSelectedComponentId(component.id); }; const onMouseEnterWrapper = (e: React.MouseEvent) => { @@ -225,7 +224,7 @@ export function useComponentWrapper(services: EditorServices): ComponentWrapperT e.preventDefault(); setDragOverComponentId(''); setCurrentSlot(undefined); - setLeftTabIdx(1); + setExploreMenuTab(ExploreMenuTabs.UI_TREE); const creatingComponent = e.dataTransfer?.getData('component') || ''; eventBus.send( 'operation', diff --git a/packages/editor/src/components/Editor.tsx b/packages/editor/src/components/Editor.tsx index d671861f..3183de18 100644 --- a/packages/editor/src/components/Editor.tsx +++ b/packages/editor/src/components/Editor.tsx @@ -33,10 +33,10 @@ export const Editor: React.FC = observer( components, selectedComponentId, modules, - setLeftTabIdx, - setRightTabIdx, - leftTabIdx, - rightTabIdx, + toolMenuTab, + exploreMenuTab, + setToolMenuTab, + setExploreMenuTab } = editorStore; const [scale, setScale] = useState(100); @@ -162,9 +162,9 @@ export const Editor: React.FC = observer( flexDirection="column" textAlign="left" isLazy - index={leftTabIdx} - onChange={idx => { - setLeftTabIdx(idx); + index={exploreMenuTab} + onChange={activatedTab => { + setExploreMenuTab(activatedTab); }} > @@ -204,9 +204,9 @@ export const Editor: React.FC = observer( height="100%" display="flex" flexDirection="column" - index={rightTabIdx} - onChange={idx => { - setRightTabIdx(idx); + index={toolMenuTab} + onChange={activatedTab => { + setToolMenuTab(activatedTab); }} > diff --git a/packages/editor/src/services/EditorStore.ts b/packages/editor/src/services/EditorStore.ts index b8c49812..f7b4ef5a 100644 --- a/packages/editor/src/services/EditorStore.ts +++ b/packages/editor/src/services/EditorStore.ts @@ -6,6 +6,7 @@ import { EventBusType } from './eventBus'; import { AppStorage } from './AppStorage'; import { SchemaValidator } from '../validator'; import { removeModuleId } from '../utils/addModuleId'; +import { ExploreMenuTabs, ToolMenuTabs } from './enum'; type EditingTarget = { kind: 'app' | 'module'; @@ -19,10 +20,8 @@ export class EditorStore { _selectedComponentId = ''; _hoverComponentId = ''; _dragOverComponentId = ''; - // default left tab: Explorer - _leftTabIdx = 0; - // default right tab: Insert - _rightTabIdx = 1; + exploreMenuTab = ExploreMenuTabs.EXPLORE; + toolMenuTab = ToolMenuTabs.INSERT; // current editor editing target(app or module) currentEditingTarget: EditingTarget = { kind: 'app', @@ -75,6 +74,12 @@ export class EditorStore { } ); + reaction( + () => this.selectedComponentId, + () => { + this.setToolMenuTab(ToolMenuTabs.INSPECT); + }); + this.updateCurrentEditingTarget('app', this.app.version, this.app.metadata.name); } @@ -111,14 +116,6 @@ export class EditorStore { return this.currentComponentsVersion === this.lastSavedComponentsVersion; } - get leftTabIdx() { - return this._leftTabIdx; - } - - get rightTabIdx() { - return this._rightTabIdx; - } - // origin components of app of module // when switch app or module, components should refresh get originComponents(): ComponentSchema[] { @@ -193,11 +190,11 @@ export class EditorStore { this.lastSavedComponentsVersion = val; }; - setLeftTabIdx = (val: number) => { - this._leftTabIdx = val; + setExploreMenuTab = (val: ExploreMenuTabs) => { + this.exploreMenuTab = val; } - setRightTabIdx = (val: number) => { - this._rightTabIdx = val; + setToolMenuTab = (val: ToolMenuTabs) => { + this.toolMenuTab = val; } } diff --git a/packages/editor/src/services/enum.ts b/packages/editor/src/services/enum.ts new file mode 100644 index 00000000..7bed509d --- /dev/null +++ b/packages/editor/src/services/enum.ts @@ -0,0 +1,13 @@ +/* eslint-disable no-unused-vars */ +enum ExploreMenuTabs { + EXPLORE = 0, + UI_TREE = 1, + STATE = 2 +} + +enum ToolMenuTabs { + INSPECT = 0, + INSERT = 1 +} + +export { ExploreMenuTabs, ToolMenuTabs }; From aba1265b0081b480054176686f9796062c28c9a3 Mon Sep 17 00:00:00 2001 From: xzdry Date: Mon, 7 Feb 2022 15:54:30 +0800 Subject: [PATCH 03/12] fix: correct some typos --- packages/editor/src/components/ComponentWrapper.tsx | 6 +++--- packages/editor/src/services/EditorStore.ts | 8 ++++---- packages/editor/src/services/enum.ts | 7 +++---- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/editor/src/components/ComponentWrapper.tsx b/packages/editor/src/components/ComponentWrapper.tsx index 3b1e85d8..75163667 100644 --- a/packages/editor/src/components/ComponentWrapper.tsx +++ b/packages/editor/src/components/ComponentWrapper.tsx @@ -7,7 +7,7 @@ import { ComponentWrapperType } from '@sunmao-ui/runtime'; import { genOperation } from '../operations'; import { EditorServices } from '../types'; -import { ExploreMenuTabs } from '../services/enum'; +import { ExplorerMenuTabs } from '../services/enum'; type ComponentEditorState = 'drag' | 'select' | 'hover' | 'idle'; @@ -126,7 +126,7 @@ export function useComponentWrapper(services: EditorServices): ComponentWrapperT setHoverComponentId, dragOverComponentId, setDragOverComponentId, - setExploreMenuTab, + setExplorerMenuTab, } = editorStore; const [slots, isDroppable] = useMemo(() => { @@ -224,7 +224,7 @@ export function useComponentWrapper(services: EditorServices): ComponentWrapperT e.preventDefault(); setDragOverComponentId(''); setCurrentSlot(undefined); - setExploreMenuTab(ExploreMenuTabs.UI_TREE); + setExplorerMenuTab(ExplorerMenuTabs.UI_TREE); const creatingComponent = e.dataTransfer?.getData('component') || ''; eventBus.send( 'operation', diff --git a/packages/editor/src/services/EditorStore.ts b/packages/editor/src/services/EditorStore.ts index f7b4ef5a..1e38acc9 100644 --- a/packages/editor/src/services/EditorStore.ts +++ b/packages/editor/src/services/EditorStore.ts @@ -6,7 +6,7 @@ import { EventBusType } from './eventBus'; import { AppStorage } from './AppStorage'; import { SchemaValidator } from '../validator'; import { removeModuleId } from '../utils/addModuleId'; -import { ExploreMenuTabs, ToolMenuTabs } from './enum'; +import { ExplorerMenuTabs, ToolMenuTabs } from './enum'; type EditingTarget = { kind: 'app' | 'module'; @@ -20,7 +20,7 @@ export class EditorStore { _selectedComponentId = ''; _hoverComponentId = ''; _dragOverComponentId = ''; - exploreMenuTab = ExploreMenuTabs.EXPLORE; + explorerMenuTab = ExplorerMenuTabs.EXPLORER; toolMenuTab = ToolMenuTabs.INSERT; // current editor editing target(app or module) currentEditingTarget: EditingTarget = { @@ -190,8 +190,8 @@ export class EditorStore { this.lastSavedComponentsVersion = val; }; - setExploreMenuTab = (val: ExploreMenuTabs) => { - this.exploreMenuTab = val; + setExplorerMenuTab = (val: ExplorerMenuTabs) => { + this.explorerMenuTab = val; } setToolMenuTab = (val: ToolMenuTabs) => { diff --git a/packages/editor/src/services/enum.ts b/packages/editor/src/services/enum.ts index 7bed509d..55c668b0 100644 --- a/packages/editor/src/services/enum.ts +++ b/packages/editor/src/services/enum.ts @@ -1,6 +1,5 @@ -/* eslint-disable no-unused-vars */ -enum ExploreMenuTabs { - EXPLORE = 0, +enum ExplorerMenuTabs { + EXPLORER = 0, UI_TREE = 1, STATE = 2 } @@ -10,4 +9,4 @@ enum ToolMenuTabs { INSERT = 1 } -export { ExploreMenuTabs, ToolMenuTabs }; +export { ExplorerMenuTabs, ToolMenuTabs }; From 727718010b00a14f1911dedaf3c6e151ccb49d67 Mon Sep 17 00:00:00 2001 From: xzdry Date: Mon, 7 Feb 2022 15:54:42 +0800 Subject: [PATCH 04/12] fix: use @typescript-eslint/no-unused-vars instead of no-unused-vars to support typescript --- .eslintrc.json | 2 ++ packages/editor/src/components/Editor.tsx | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 545121e0..4749f621 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -25,6 +25,8 @@ "linebreak-style": ["error", "unix"], "no-case-declarations": "off", "no-use-before-define": "off", + "no-unused-vars":"off", + "@typescript-eslint/no-unused-vars": ["error"], "comma-dangle": "off", "space-before-function-paren": "off", "multiline-ternary": "off", diff --git a/packages/editor/src/components/Editor.tsx b/packages/editor/src/components/Editor.tsx index 3183de18..d18c6f8c 100644 --- a/packages/editor/src/components/Editor.tsx +++ b/packages/editor/src/components/Editor.tsx @@ -34,9 +34,9 @@ export const Editor: React.FC = observer( selectedComponentId, modules, toolMenuTab, - exploreMenuTab, + explorerMenuTab, setToolMenuTab, - setExploreMenuTab + setExplorerMenuTab } = editorStore; const [scale, setScale] = useState(100); @@ -162,9 +162,9 @@ export const Editor: React.FC = observer( flexDirection="column" textAlign="left" isLazy - index={exploreMenuTab} + index={explorerMenuTab} onChange={activatedTab => { - setExploreMenuTab(activatedTab); + setExplorerMenuTab(activatedTab); }} > From ac1c09e98f18e430665ac4a97eef7d625adcaaad Mon Sep 17 00:00:00 2001 From: MrWindlike Date: Mon, 7 Feb 2022 17:24:44 +0800 Subject: [PATCH 05/12] feat(editor): auto-complete the parameters of the method --- .../src/components/Types/Toast.ts | 10 ++-- .../EventTraitForm/EventHandlerForm.tsx | 54 ++++++++++++++++--- .../editor/src/components/KeyValueEditor.tsx | 27 ++++++---- packages/runtime/src/services/Registry.tsx | 19 +++++-- 4 files changed, 85 insertions(+), 25 deletions(-) diff --git a/packages/chakra-ui-lib/src/components/Types/Toast.ts b/packages/chakra-ui-lib/src/components/Types/Toast.ts index f53c697f..226faa31 100644 --- a/packages/chakra-ui-lib/src/components/Types/Toast.ts +++ b/packages/chakra-ui-lib/src/components/Types/Toast.ts @@ -10,7 +10,7 @@ const ToastPosition = Type.Union([ Type.Literal('bottom-right'), Type.Literal('bottom-left'), ]); -export const ToastOpenParamterSchema = Type.Object({ +export const ToastOpenParameterSchema = Type.Object({ position: Type.Optional(ToastPosition), duration: Type.Optional(Type.Union([Type.Number(), Type.Null()])), title: Type.Optional(Type.String()), @@ -40,7 +40,7 @@ export const ToastCloseParameterSchema = Type.Object({ positions: Type.Optional(Type.Array(ToastPosition)), }); -export type ToastOpenParameter = Static; +export type ToastOpenParameter = Static; export type ToastCloseParameter = Static; const pickProperty = >( @@ -58,14 +58,15 @@ export default function ToastUtilMethodFactory() { let toast: ReturnType | undefined; const toastOpen: UtilMethod = { name: 'toast.open', - method(parameters: Static) { + method(parameters: Static) { if (!toast) { toast = createStandaloneToast(); } if (parameters) { - toast(pickProperty(ToastOpenParamterSchema, parameters)); + toast(pickProperty(ToastOpenParameterSchema, parameters)); } }, + parameters: ToastOpenParameterSchema, }; const toastClose: UtilMethod = { @@ -85,6 +86,7 @@ export default function ToastUtilMethodFactory() { } } }, + parameters: ToastCloseParameterSchema, }; return [toastOpen, toastClose]; } diff --git a/packages/editor/src/components/ComponentForm/EventTraitForm/EventHandlerForm.tsx b/packages/editor/src/components/ComponentForm/EventTraitForm/EventHandlerForm.tsx index b7830760..d1b1d45c 100644 --- a/packages/editor/src/components/ComponentForm/EventTraitForm/EventHandlerForm.tsx +++ b/packages/editor/src/components/ComponentForm/EventTraitForm/EventHandlerForm.tsx @@ -1,4 +1,4 @@ -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useState, useMemo } from 'react'; import { Box, FormControl, @@ -19,6 +19,7 @@ import { KeyValueEditor } from '../../KeyValueEditor'; import { EditorServices } from '../../../types'; import { ComponentModel } from '../../../AppModel/ComponentModel'; import { AppModel } from '../../../AppModel/AppModel'; +import { ComponentId } from '../../../AppModel/IAppModel'; type Props = { eventTypes: readonly string[]; @@ -31,7 +32,7 @@ type Props = { export const EventHandlerForm: React.FC = observer(props => { const { handler, eventTypes, onChange, onRemove, hideEventType, services } = props; - const { registry, editorStore } = services; + const { registry, editorStore, appModelManager } = services; const { utilMethods } = registry; const { components } = editorStore; const [methods, setMethods] = useState([]); @@ -43,6 +44,41 @@ export const EventHandlerForm: React.FC = observer(props => { }, }); + const hasParams = useMemo( + () => Object.keys(formik.values.method.parameters ?? {}).length, + [formik.values.method.parameters] + ); + const params = useMemo(() => { + const params: Record = {}; + const { values } = formik; + const methodName = values.method.name; + + if (values.method.name) { + let parameters = {}; + + if (handler.componentId === GLOBAL_UTILS_ID) { + const targetMethod = utilMethods.get(methodName); + + parameters = targetMethod?.parameters?.properties ?? {}; + } else { + const targetComponent = appModelManager.appModel.getComponentById( + handler.componentId as ComponentId + ); + const targetMethod = (targetComponent?.methods ?? []).find( + ({ name }) => name === formik.values.method.name + ); + + parameters = targetMethod?.parameters?.properties ?? {}; + } + + for (const key in parameters) { + params[key] = values.method.parameters?.[key] ?? ''; + } + } + + return params; + }, [formik.values.method.name]); + const updateMethods = useCallback( (componentId: string) => { if (componentId === GLOBAL_UTILS_ID) { @@ -68,6 +104,10 @@ export const EventHandlerForm: React.FC = observer(props => { formik.setValues(handler); }, [handler]); + useEffect(() => { + formik.setFieldValue('method.parameters', params); + }, [params]); + useEffect(() => { if (handler.componentId) { updateMethods(handler.componentId); @@ -76,6 +116,8 @@ export const EventHandlerForm: React.FC = observer(props => { const onTargetComponentChange = (e: React.ChangeEvent) => { updateMethods(e.target.value); + formik.handleChange(e); + formik.setFieldValue('method', { name: '', parameters: {} }); }; const typeField = ( @@ -102,10 +144,7 @@ export const EventHandlerForm: React.FC = observer(props => { = props => { onChange={onInputChange} onBlur={onBlur} /> - } - size="xs" - onClick={() => onRemoveRow(i)} - variant="ghost" - /> + {onlySetValue ? null : ( + } + size="xs" + onClick={() => onRemoveRow(i)} + variant="ghost" + /> + )} ); }); @@ -77,9 +82,11 @@ export const KeyValueEditor: React.FC = props => { return ( {rowItems} - + {onlySetValue ? null : ( + + )} ); }; diff --git a/packages/runtime/src/services/Registry.tsx b/packages/runtime/src/services/Registry.tsx index 0ebd412d..6b9cf2a7 100644 --- a/packages/runtime/src/services/Registry.tsx +++ b/packages/runtime/src/services/Registry.tsx @@ -28,6 +28,7 @@ import { ApiService } from './apiService'; export type UtilMethod = { name: string; method: (parameters?: any) => void; + parameters?: any; }; export type UtilMethodFactory = () => UtilMethod[]; @@ -50,7 +51,7 @@ export class Registry { components = new Map>(); traits = new Map>(); modules = new Map>(); - utilMethods = new Map(); + utilMethods = new Map(); private apiService: ApiService; constructor(apiService: ApiService) { @@ -166,7 +167,7 @@ export class Registry { if (this.utilMethods.get(m.name)) { throw new Error(`Already has utilMethod ${m.name} in this registry.`); } - this.utilMethods.set(m.name, m.method); + this.utilMethods.set(m.name, m); } installLib(lib: SunmaoLib) { @@ -185,9 +186,19 @@ export class Registry { private mountUtilMethods() { this.apiService.on('uiMethod', ({ componentId, name, parameters }) => { if (componentId === GLOBAL_UTILS_ID) { - const utilMethod = this.utilMethods.get(name); + const utilMethod = this.utilMethods.get(name)?.method; if (utilMethod) { - utilMethod(parameters); + const params: Record = {}; + + for (const key in parameters) { + const value = parameters[key]; + + if (value !== undefined && value !== '') { + params[key] = value; + } + } + + utilMethod(params); } } }); From 8b84fb4863945edd90e9957badd4e30af4bec3f0 Mon Sep 17 00:00:00 2001 From: Yanzhen Yu Date: Tue, 8 Feb 2022 13:50:10 +0800 Subject: [PATCH 06/12] put prettier eslint config to the last --- .eslintrc.json | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 545121e0..691cd699 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -8,8 +8,8 @@ "plugin:react/recommended", "plugin:react-hooks/recommended", "plugin:@typescript-eslint/recommended", - "prettier", - "standard" + "standard", + "prettier" ], "parser": "@typescript-eslint/parser", "parserOptions": { @@ -48,5 +48,13 @@ "react/jsx-wrap-multilines": ["error"], "react/jsx-tag-spacing": ["error"] }, - "ignorePatterns": ["node_modules", "*.d.ts", "*.js", "package.json", "*.html", "*.spec.ts", "*.spec.tsx"] + "ignorePatterns": [ + "node_modules", + "*.d.ts", + "*.js", + "package.json", + "*.html", + "*.spec.ts", + "*.spec.tsx" + ] } From c683619a864420214a6838372c749b2fb2e17357 Mon Sep 17 00:00:00 2001 From: Yanzhen Yu Date: Tue, 8 Feb 2022 18:09:01 +0800 Subject: [PATCH 07/12] impl #225 trigger auto-complete when typing --- .../CodeEditor/ExpressionEditor.tsx | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/packages/editor/src/components/CodeEditor/ExpressionEditor.tsx b/packages/editor/src/components/CodeEditor/ExpressionEditor.tsx index 7f0a6a90..16542a15 100644 --- a/packages/editor/src/components/CodeEditor/ExpressionEditor.tsx +++ b/packages/editor/src/components/CodeEditor/ExpressionEditor.tsx @@ -22,7 +22,12 @@ function installTern(cm: CodeMirror.Editor) { const t = new CodeMirror.TernServer({ defs: [ecma as unknown as Def] }); cm.on('cursorActivity', cm => t.updateArgHints(cm)); cm.on('change', (_instance, change) => { - if (change.text.length === 1 && change.text[0] === '.') { + if ( + // change happened + change.text.length + (change.removed?.length || 0) > 0 && + // not changed by auto-complete + change.origin !== 'complete' + ) { t.complete(cm); } }); @@ -69,6 +74,9 @@ export const ExpressionEditor: React.FC<{ }, theme: 'ayu-mirage', viewportMargin: Infinity, + hintOptions: { + completeSingle: false, + }, }); const t = installTern(cm.current); tServer.current = t.server; @@ -93,5 +101,14 @@ export const ExpressionEditor: React.FC<{ } }, [defs]); - return ; + return ( + + ); }; From a722dcc34aa54014546b76c14bc845759ab3e971 Mon Sep 17 00:00:00 2001 From: Yanzhen Yu Date: Wed, 9 Feb 2022 12:50:51 +0800 Subject: [PATCH 08/12] extend metadata and set category annotation to core components --- .eslintrc.json | 3 +- .../chakra-ui-lib/src/components/Tabs.tsx | 6 +- packages/core/src/component.ts | 5 +- packages/core/src/metadata.ts | 14 ++- .../JsonSchemaForm/SchemaField.tsx | 1 + .../runtime/src/components/core/Dummy.tsx | 3 + .../src/components/core/GridLayout.tsx | 3 + .../src/components/core/ModuleContainer.tsx | 86 +++++++++---------- packages/runtime/src/components/core/Text.tsx | 3 + 9 files changed, 70 insertions(+), 54 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 71019273..4e8cd4a9 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -25,7 +25,7 @@ "linebreak-style": ["error", "unix"], "no-case-declarations": "off", "no-use-before-define": "off", - "no-unused-vars":"off", + "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": ["error"], "comma-dangle": "off", "space-before-function-paren": "off", @@ -33,6 +33,7 @@ "@typescript-eslint/explicit-module-boundary-types": "off", "@typescript-eslint/no-explicit-any": "off", "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/ban-types": "off", "react/prop-types": "off", "react/display-name": "off", "react/jsx-uses-react": "off", diff --git a/packages/chakra-ui-lib/src/components/Tabs.tsx b/packages/chakra-ui-lib/src/components/Tabs.tsx index cfe26ea2..0a2bb6a0 100644 --- a/packages/chakra-ui-lib/src/components/Tabs.tsx +++ b/packages/chakra-ui-lib/src/components/Tabs.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { css } from '@emotion/css'; import { Tabs as BaseTabs, @@ -74,7 +74,9 @@ export default implementRuntimeComponent({ {tabNames.map((_, idx) => { - const ele = slotsElements.content ? ([] as React.ReactElement[]).concat(slotsElements.content)[idx] : placeholder; + const ele = slotsElements.content + ? ([] as React.ReactElement[]).concat(slotsElements.content)[idx] + : placeholder; return ( > = { name: string; description?: string; + annotations?: Record & TAnnotations; }; -export type ComponentMetadata = Metadata & { +type ComponentCategory = + | (string & {}) + | 'Layout' + | 'Input' + | 'Display' + | 'Advance' + | undefined; + +export type ComponentMetadata = Metadata<{ category?: ComponentCategory }> & { + // TODO:(yanzhen): move to annotations isDraggable: boolean; isResizable: boolean; displayName: string; diff --git a/packages/editor/src/components/ComponentForm/JsonSchemaForm/SchemaField.tsx b/packages/editor/src/components/ComponentForm/JsonSchemaForm/SchemaField.tsx index ade3789e..ee9d0d63 100644 --- a/packages/editor/src/components/ComponentForm/JsonSchemaForm/SchemaField.tsx +++ b/packages/editor/src/components/ComponentForm/JsonSchemaForm/SchemaField.tsx @@ -134,6 +134,7 @@ const SchemaField: React.FC = props => { return ( { - if (!type) { - return Please choose a module to render. - } - if (!id) { - return Please set a id for module. - } - - return ( - - ); - }) \ No newline at end of file + }, + spec: { + properties: ModuleSchema, + state: {}, + methods: {}, + slots: [], + styleSlots: [], + events: [], + }, +})(({ id, type, properties, handlers, services, app }) => { + if (!type) { + return Please choose a module to render.; + } + if (!id) { + return Please set a id for module.; + } + + return ( + + ); +}); diff --git a/packages/runtime/src/components/core/Text.tsx b/packages/runtime/src/components/core/Text.tsx index 31739bea..2ef6897e 100644 --- a/packages/runtime/src/components/core/Text.tsx +++ b/packages/runtime/src/components/core/Text.tsx @@ -25,6 +25,9 @@ export default implementRuntimeComponent({ }, }, exampleSize: [4, 1], + annotations: { + category: 'Display', + }, }, spec: { properties: PropsSchema, From 31bfa1fc321a0426141757277d9f4beadba14f8b Mon Sep 17 00:00:00 2001 From: Yanzhen Yu Date: Wed, 9 Feb 2022 13:41:04 +0800 Subject: [PATCH 09/12] refactor component list with category annotation --- .../ComponentsList/ComponentList.tsx | 176 +++++++++++------- 1 file changed, 104 insertions(+), 72 deletions(-) diff --git a/packages/editor/src/components/ComponentsList/ComponentList.tsx b/packages/editor/src/components/ComponentsList/ComponentList.tsx index 8a0ad1cb..13e73c6b 100644 --- a/packages/editor/src/components/ComponentsList/ComponentList.tsx +++ b/packages/editor/src/components/ComponentsList/ComponentList.tsx @@ -1,101 +1,133 @@ -import React from 'react'; +import React, { useMemo, useState } from 'react'; import { - Tabs, - TabList, - Tab, - TabPanels, - TabPanel, - SimpleGrid, Flex, Box, + Accordion, + AccordionItem, + AccordionPanel, + AccordionButton, + AccordionIcon, + Input, + Tag, } from '@chakra-ui/react'; -import { encodeDragDataTransfer, DROP_EXAMPLE_SIZE_PREFIX } from '@sunmao-ui/runtime'; -import { Registry } from '@sunmao-ui/runtime'; +import { + encodeDragDataTransfer, + DROP_EXAMPLE_SIZE_PREFIX, + Registry, +} from '@sunmao-ui/runtime'; +import { groupBy, sortBy } from 'lodash-es'; type Props = { registry: Registry; }; +type Category = { + name: string; + components: ReturnType; +}; + +const PRESET_CATEGORY_ORDER = { + Layout: 5, + Input: 4, + Display: 3, + Advance: -Infinity, +}; + +function getCategoryOrder(name: string): number { + return name in PRESET_CATEGORY_ORDER + ? PRESET_CATEGORY_ORDER[name as keyof typeof PRESET_CATEGORY_ORDER] + : 0; +} + export const ComponentList: React.FC = ({ registry }) => { + const [filterText, setFilterText] = useState(''); + const categories = useMemo(() => { + const grouped = groupBy( + registry.getAllComponents().filter(c => { + if (!filterText) { + return true; + } + return new RegExp(filterText, 'i').test(c.metadata.displayName); + }), + c => c.metadata.annotations?.category || 'Advance' + ); + return sortBy( + Object.keys(grouped).map(name => ({ + name, + components: sortBy(grouped[name], 'metadata.name'), + })), + c => -getCategoryOrder(c.name) + ); + }, [filterText, registry]); + return ( - - - {Array.from(registry.components.keys()).map(version => ( - {version} - ))} - - - {Array.from(registry.components.keys()).map(version => ( - - - {Array.from(registry.components.get(version)!.values()).map(c => { - const onDragStart = (e: any) => { - e.dataTransfer.setData('component', `${c.version}/${c.metadata.name}`); - // pass the exampleSize to gridlayout to render placeholder - e.dataTransfer.setData( - encodeDragDataTransfer( - `${DROP_EXAMPLE_SIZE_PREFIX}${JSON.stringify( - c.metadata.exampleSize - )}` - ), - '' - ); - }; - const cEle = ( - + <> + setFilterText(evt.currentTarget.value)} + /> + idx)}> + {categories.map(category => { + return ( + + + + {category.name} + + + + + {category.components.map(c => { + const onDragStart = (e: any) => { + e.dataTransfer.setData( + 'component', + `${c.version}/${c.metadata.name}` + ); + // pass the exampleSize to gridlayout to render placeholder + e.dataTransfer.setData( + encodeDragDataTransfer( + `${DROP_EXAMPLE_SIZE_PREFIX}${JSON.stringify( + c.metadata.exampleSize + )}` + ), + '' + ); + }; + const cEle = ( - - - - - {c.metadata.displayName} - - - ); - return cEle; - })} - - - ))} - - + + {c.version} + + + ); + return cEle; + })} + + + ); + })} + + ); }; From f46df01acc0d0cc92b670e8aa09fc6c21d592cc7 Mon Sep 17 00:00:00 2001 From: Yanzhen Yu Date: Wed, 9 Feb 2022 14:50:33 +0800 Subject: [PATCH 10/12] add category annotation to chakra ui lib --- packages/chakra-ui-lib/src/components/Box.tsx | 3 + .../chakra-ui-lib/src/components/Button.tsx | 5 +- .../chakra-ui-lib/src/components/Checkbox.tsx | 3 + .../src/components/CheckboxGroup.tsx | 3 + .../chakra-ui-lib/src/components/Dialog.tsx | 16 +++-- .../chakra-ui-lib/src/components/Divider.tsx | 3 + .../src/components/Form/Form.tsx | 7 ++- .../src/components/Form/FormControl.tsx | 8 ++- .../chakra-ui-lib/src/components/HStack.tsx | 3 + .../chakra-ui-lib/src/components/Image.tsx | 5 +- .../chakra-ui-lib/src/components/Input.tsx | 5 +- packages/chakra-ui-lib/src/components/Kbd.tsx | 3 + .../chakra-ui-lib/src/components/Link.tsx | 3 + .../chakra-ui-lib/src/components/List.tsx | 3 + .../src/components/MultiSelect.tsx | 47 ++++++++------- .../src/components/NumberInput.tsx | 5 +- .../chakra-ui-lib/src/components/Radio.tsx | 59 ++++++++++--------- .../src/components/RadioGroup.tsx | 3 + .../chakra-ui-lib/src/components/Root.tsx | 3 + .../chakra-ui-lib/src/components/Select.tsx | 3 + .../chakra-ui-lib/src/components/Stack.tsx | 3 + .../src/components/Table/index.ts | 2 +- .../src/components/Table/spec.ts | 5 +- .../chakra-ui-lib/src/components/Tabs.tsx | 3 + .../chakra-ui-lib/src/components/Tooltip.tsx | 3 + .../chakra-ui-lib/src/components/VStack.tsx | 3 + .../ComponentsList/ComponentList.tsx | 12 +++- 27 files changed, 154 insertions(+), 67 deletions(-) diff --git a/packages/chakra-ui-lib/src/components/Box.tsx b/packages/chakra-ui-lib/src/components/Box.tsx index 0abadce7..fd0918b6 100644 --- a/packages/chakra-ui-lib/src/components/Box.tsx +++ b/packages/chakra-ui-lib/src/components/Box.tsx @@ -283,6 +283,9 @@ export default implementRuntimeComponent({ border: '1px solid black', }, exampleSize: [6, 6], + annotations: { + category: 'Layout', + }, }, spec: { properties: StyleSchema, diff --git a/packages/chakra-ui-lib/src/components/Button.tsx b/packages/chakra-ui-lib/src/components/Button.tsx index 2500172c..7e3ebb27 100644 --- a/packages/chakra-ui-lib/src/components/Button.tsx +++ b/packages/chakra-ui-lib/src/components/Button.tsx @@ -32,12 +32,15 @@ export default implementRuntimeComponent({ isLoading: false, }, exampleSize: [2, 1], + annotations: { + category: 'Input', + }, }, spec: { properties: PropsSchema, state: StateSchema, methods: { - click: void 0, + click: undefined, }, slots: [], styleSlots: ['content'], diff --git a/packages/chakra-ui-lib/src/components/Checkbox.tsx b/packages/chakra-ui-lib/src/components/Checkbox.tsx index 5986cfde..722daef2 100644 --- a/packages/chakra-ui-lib/src/components/Checkbox.tsx +++ b/packages/chakra-ui-lib/src/components/Checkbox.tsx @@ -53,6 +53,9 @@ export default implementRuntimeComponent({ size: 'md', }, exampleSize: [3, 1], + annotations: { + category: 'Input', + }, }, spec: { properties: PropsSchema, diff --git a/packages/chakra-ui-lib/src/components/CheckboxGroup.tsx b/packages/chakra-ui-lib/src/components/CheckboxGroup.tsx index b4b849f1..911b8212 100644 --- a/packages/chakra-ui-lib/src/components/CheckboxGroup.tsx +++ b/packages/chakra-ui-lib/src/components/CheckboxGroup.tsx @@ -28,6 +28,9 @@ export default implementRuntimeComponent({ defaultValue: [], }, exampleSize: [3, 3], + annotations: { + category: 'Input', + }, }, spec: { properties: PropsSchema, diff --git a/packages/chakra-ui-lib/src/components/Dialog.tsx b/packages/chakra-ui-lib/src/components/Dialog.tsx index 752d33f6..3d0d921b 100644 --- a/packages/chakra-ui-lib/src/components/Dialog.tsx +++ b/packages/chakra-ui-lib/src/components/Dialog.tsx @@ -1,8 +1,5 @@ import { useEffect, useState, useRef } from 'react'; -import { - implementRuntimeComponent, - DIALOG_CONTAINER_ID, -} from '@sunmao-ui/runtime'; +import { implementRuntimeComponent, DIALOG_CONTAINER_ID } from '@sunmao-ui/runtime'; import { AlertDialog, AlertDialogBody, @@ -44,6 +41,9 @@ export default implementRuntimeComponent({ disableConfirm: false, }, exampleSize: [6, 6], + annotations: { + category: 'Display', + }, }, spec: { properties: PropsSchema, @@ -52,8 +52,8 @@ export default implementRuntimeComponent({ openDialog: Type.Object({ title: Type.String(), }), - confirmDialog: void 0, - cancelDialog: void 0, + confirmDialog: undefined, + cancelDialog: undefined, }, slots: ['content'], styleSlots: ['content'], @@ -131,9 +131,7 @@ export default implementRuntimeComponent({ {...(containerRef.current ? dialogContentProps : {})} > {title} - - {slotsElements.content} - + {slotsElements.content}