diff --git a/.eslintrc.js b/.eslintrc.js index f1b6c7b..b22d43b 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -31,6 +31,16 @@ module.exports = { '@typescript-eslint/indent': 0, 'import/no-cycle': 0, '@typescript-eslint/no-shadow': 0, - 'max-len': ["error", {"code": 180}] - } + 'max-len': ['error', { code: 180 }], + 'comma-dangle': [ + 'warn', + { + objects: 'always', + arrays: 'always', + imports: 'never', + functions: 'never', + }, + ], + semi: ['error', 'always'], + }, }; diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..281c877 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,20 @@ +# ref: https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners + +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence +* @liujuping @JackLian @alvarto + +/packages/plugin-manual/ @alvarto +/packages/base-monaco-editor/ @alvarto @wangshihao111 @SuSunSam +/packages/plugin-code-editor/ @alvarto @SuSunSam +/packages/plugin-schema/ @alvarto +/packages/plugin-components-pane/ @mark-ck @love999262 +/packages/plugin-datasource-pane/ @YSMJ1994 +/packages/plugin-zh-en/ @JackLian @liujuping +/packages/plugin-undo-redo/ @JackLian @liujuping +/packages/plugin-resource-tabs/ @JackLian @liujuping +/packages/plugin-set-ref-prop/ @JackLian @liujuping +/packages/plugin-view-manager-pane/ @JackLian @liujuping +/packages/base-monaco-editor/ @hzd822 +/packages/plugin-multiple-editor/ @hzd822 +/packages/action-block @liujuping \ No newline at end of file diff --git a/.github/workflows/deprecate npm.yml b/.github/workflows/deprecate npm.yml new file mode 100644 index 0000000..ea096ec --- /dev/null +++ b/.github/workflows/deprecate npm.yml @@ -0,0 +1,32 @@ +name: deprecate Package Version + +on: + workflow_dispatch: + inputs: + version: + description: 'Version to be deleted package@version' + required: true + +jobs: + delete-package-version: + runs-on: ubuntu-latest + if: >- + github.ref == 'refs/heads/main' && + (github.actor == 'JackLian' || github.actor == 'liujuping') + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: '14' + + - name: deprecate Package Version + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + + echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc + npm deprecate ${{ github.event.inputs.version }} "This version is deprecated. Please consider upgrading to a newer version." diff --git a/.github/workflows/publish beta npm.yml b/.github/workflows/publish beta npm.yml new file mode 100644 index 0000000..01ab685 --- /dev/null +++ b/.github/workflows/publish beta npm.yml @@ -0,0 +1,38 @@ +name: Publish Beta NPM Packages +on: + workflow_dispatch: + inputs: + packagePath: + description: 'Path to the package (e.g., action-block)' + required: true + betaVersion: + description: 'Beta version number (e.g., 1.0.1-beta.0)' + required: true + +jobs: + publish-package: + runs-on: ubuntu-latest + if: >- + github.actor == 'JackLian' || github.actor == 'liujuping' + + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: '16' # 或者您希望的任何版本 + + - name: Change to Package Directory + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + npm install --legacy-peer-deps + echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc + cd packages/${{ github.event.inputs.packagePath }} + npm install --legacy-peer-deps + npm run build + npm version ${{ github.event.inputs.betaVersion }} + echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc + npm publish --tag beta \ No newline at end of file diff --git a/.github/workflows/publish npm.yml b/.github/workflows/publish npm.yml new file mode 100644 index 0000000..5ada959 --- /dev/null +++ b/.github/workflows/publish npm.yml @@ -0,0 +1,51 @@ +name: Publish NPM Packages +on: + workflow_dispatch: + inputs: + packagePath: + description: 'Path to the package (e.g., action-block)' + required: true + versionType: + description: 'Version update type (major, minor, patch)' + required: true + default: 'patch' + +jobs: + publish-package: + runs-on: ubuntu-latest + if: >- + github.ref == 'refs/heads/main' && + (github.actor == 'JackLian' || github.actor == 'liujuping') + + steps: + - name: Checkout Repository + uses: actions/checkout@v2 + + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: '16' # 或者您希望的任何版本 + + - name: Publish Package + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + + echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc + npm install --legacy-peer-deps + cd packages/${{ github.event.inputs.packagePath }} + npm install --legacy-peer-deps + npm version ${{ github.event.inputs.versionType }} + npm run build + + echo "//registry.npmjs.org/:_authToken=${{ secrets.NPM_TOKEN }}" > .npmrc + npm publish + + echo "PACKAGE_NAME=$(node -p "require('./package.json').name")" >> $GITHUB_ENV + echo "PACKAGE_VERSION=$(node -p "require('./package.json').version")" >> $GITHUB_ENV + + git add package.json + git commit -m "Bump version to ${PACKAGE_VERSION}" + + git tag -a "${PACKAGE_NAME}@${PACKAGE_VERSION}" -m "Release ${PACKAGE_NAME} version ${PACKAGE_VERSION}" + git push origin "${PACKAGE_NAME}@${PACKAGE_VERSION}" \ No newline at end of file diff --git a/README.md b/README.md index d23206e..c71cea3 100644 --- a/README.md +++ b/README.md @@ -25,3 +25,5 @@ - plugin-schema - plugin-undo-redo - plugin-zh-cn +- plugin-block +- action-block diff --git a/package.json b/package.json index c4dfabc..761e9e0 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ ] }, "scripts": { + "bootstrap": "lerna bootstrap", "dev": "npm run bootstrap && npm run build && cooking watch -c build/cooking.demo.js -p", "build": "./scripts/build.sh", "clean": "rm -rf ./packages/*/lib ./packages/*/es ./packages/*/dist ./packages/*/build", @@ -39,9 +40,9 @@ } }, "devDependencies": { - "lerna": "^4.x", "f2elint": "^2.0.1", "husky": "^7.0.4", + "lerna": "^4.x", "typescript": "^3.2.2" }, "engines": { @@ -53,5 +54,6 @@ }, "resolutions": { "@builder/babel-preset-ice": "1.0.1" - } -} + }, + "repository": "https://github.com/alibaba/lowcode-plugins.git" +} \ No newline at end of file diff --git a/packages/action-block/README.md b/packages/action-block/README.md new file mode 100644 index 0000000..9bda2b0 --- /dev/null +++ b/packages/action-block/README.md @@ -0,0 +1,46 @@ +# 区块管理 - 保存为区块 + +## 区块实体 + +``` + +interface Block { + name: string; + title: string; + schema: string; + screenshot: string; + created_at?: string; + updated_at?: string; +} + +``` + +## 注意 + +使用区块管理需要提前将对应的 API 注册到 engine config 里: + +``` + +interface BlockAPI { + listBlocks: () => Block[]; + createBlock: (Block) => any; +} + +function setupConfig() { + config.set('apiList', { + block: { + listBlocks, + createBlock + }, + }) +} +``` + +# 使用方式 + +``` +import { material } from '@alilc/lowcode-engine'; +import { default as saveAsBlock } from '@alilc/action-block'; + +material.addBuiltinComponentAction(saveAsBlock); +``` \ No newline at end of file diff --git a/packages/action-block/build.json b/packages/action-block/build.json new file mode 100644 index 0000000..d2836c1 --- /dev/null +++ b/packages/action-block/build.json @@ -0,0 +1,17 @@ +{ + "externals": { + "react": "var window.React", + "@alifd/next": "var window.Next", + "@alilc/lowcode-engine": "var window.AliLowCodeEngine" + }, + "plugins": [ + "build-plugin-component", + "build-plugin-fusion", + [ + "build-plugin-moment-locales", + { + "locales": ["zh-cn"] + } + ] + ] +} diff --git a/packages/action-block/package.json b/packages/action-block/package.json new file mode 100644 index 0000000..e9c0c00 --- /dev/null +++ b/packages/action-block/package.json @@ -0,0 +1,31 @@ +{ + "name": "@alilc/action-block", + "version": "1.0.1", + "description": "", + "main": "lib/index.js", + "repository": { + "type": "git", + "url": "https://github.com/alibaba/lowcode-plugins.git", + "directory": "packages/action-block" + }, + "scripts": { + "start": "build-scripts start", + "build": "build-scripts build", + "prepublishOnly": "npm run build" + }, + "author": "mark.ck", + "license": "MIT", + "dependencies": { + "@alifd/next": "^1.25.31", + "@alilc/lowcode-engine": "^1.0.5", + "html2canvas": "^1.4.1", + "react": "^16.8.1" + }, + "devDependencies": { + "@alib/build-scripts": "^0.1.32", + "build-plugin-component": "^1.10.0", + "build-plugin-fusion": "^0.1.22", + "build-plugin-moment-locales": "^0.1.3" + }, + "homepage": "https://unpkg.com/@alilc/action-block@1.0.1/build/index.html" +} diff --git a/packages/action-block/src/index.scss b/packages/action-block/src/index.scss new file mode 100644 index 0000000..1f019fb --- /dev/null +++ b/packages/action-block/src/index.scss @@ -0,0 +1,11 @@ +.block-screenshot { + background: gainsboro; + display: flex; + justify-content: center; + align-items: center; + padding: 10px 0; + + img { + max-width: 80%; + } +} \ No newline at end of file diff --git a/packages/action-block/src/index.tsx b/packages/action-block/src/index.tsx new file mode 100644 index 0000000..22eaa46 --- /dev/null +++ b/packages/action-block/src/index.tsx @@ -0,0 +1,119 @@ +import * as React from 'react'; +import { default as html2canvas } from 'html2canvas'; +import { Node, config, event } from '@alilc/lowcode-engine'; +import { Dialog, Form, Input } from '@alifd/next'; +import './index.scss'; + +const FormItem = Form.Item; + +interface SaveAsBlockProps { + node: Node; +} + +function checkBlockAPI () { + const apiList = config.get('apiList') || {}; + const { block: blockAPI } = apiList; + + if (!blockAPI?.createBlock) { + throw new Error('[BlockPane] block api required in engine config.'); + } + + return blockAPI; +} + +let dialog: any; + +const SaveAsBlock = (props: SaveAsBlockProps) => { + const { createBlock } = checkBlockAPI(); + const { node } = props; + const [ src, setSrc ] = React.useState(); + React.useEffect(() => { + const generateImage = async () => { + let dom2 = node.getDOMNode(); + // console.log('html2canvas: ', html2canvas); + const canvas = await html2canvas?.(dom2, { scale: 0.5 }); + const dataUrl = canvas.toDataURL(); + setSrc(dataUrl); + }; + + generateImage(); + }, []); + + const save = async (values) => { + const { name, title } = values; + const { schema } = node; + + await createBlock({ + name, + title, + schema: JSON.stringify(schema), + screenshot: src, + }); + dialog?.hide(); + event.emit('BlockChanged'); + } + + return
+
+ + + + + + + +
+ + +
+ +
+ + + 保存 + + 重置 + +
+
+} + + +export default { + name: 'add', + content: { + icon: { + type: 'add', + size: 'xs' + }, + title: '新增', + action(node: Node) { + // console.log('node: ', node); + dialog = Dialog.show({ + v2: true, + title: "保存为区块", + content: , + footer: false + }); + }, + }, + important: true, +}; diff --git a/packages/base-monaco-editor/CHANGELOG.md b/packages/base-monaco-editor/CHANGELOG.md index 58196de..1ca5153 100644 --- a/packages/base-monaco-editor/CHANGELOG.md +++ b/packages/base-monaco-editor/CHANGELOG.md @@ -1,3 +1,27 @@ +## 1.1.1 + +- 避免 UIPaaS 中的样式冲突,将 `.ve-code-control` 替换为 `.lc-code-control` + +## 1.1.0 + +- Publish following changes + +## 1.1.0-beta.3 + +- 去除 `overflow: visible` 样式,避免动作面板遮住按钮 + +## 1.1.0-beta.1 + +- 添加 controller 实例,可用作中间交换介质,解决与代码编辑相关的插件间的协作问题 [@wangshihao111](https://github.com/wangshihao111) +- 优化 Monaco 单例,元信息保存在 controller 实例内部 [@wangshihao111](https://github.com/wangshihao111) + +## 1.1.0-beta.0 + +- 重构 type / path / value 联动,彻底修复 monaco 在多文件模式下覆盖多个 path 值的 bug +- 修复 ts 类型问题 [@wangshihao111](https://github.com/wangshihao111) +- 添加 configure 方法,支持配置是否开启 monaco 单例模式 [@wangshihao111](https://github.com/wangshihao111) +- 新增插件参数:`enhancers`,用于强化编辑器功能 [@wangshihao111](https://github.com/wangshihao111) + ## 1.0.0 - Publish following changes diff --git a/packages/base-monaco-editor/README.md b/packages/base-monaco-editor/README.md index 0740b37..49473bb 100644 --- a/packages/base-monaco-editor/README.md +++ b/packages/base-monaco-editor/README.md @@ -125,6 +125,24 @@ function App() { ReactDOM.render(, mountNode); ``` +### Using controller + +```ts +import { controller } from '@alilc/lowcode-plugin-base-monaco-editor'; + +// configure Monaco to be singleton +controller.updateMeta({ singleton: true }); + +// Get all metadata +controller.getMeta(); + +// register a custom method +controller.registerMethod('methodName', (a, b, c) => { }); + +// call custom methods +const ret = controller.call('methodName', a, b, c); +``` + ## Citation This is forked from [monaco-react](https://github.com/suren-atoyan/monaco-react). Thanks for [suren-atoyan](https://github.com/suren-atoyan)'s effort for making monaco editor appoachable. diff --git a/packages/base-monaco-editor/package.json b/packages/base-monaco-editor/package.json index 520741f..6dfa41a 100644 --- a/packages/base-monaco-editor/package.json +++ b/packages/base-monaco-editor/package.json @@ -1,8 +1,9 @@ { "name": "@alilc/lowcode-plugin-base-monaco-editor", - "version": "1.0.0", + "version": "1.1.2", "description": "代码编辑组件,monaco-editor 的低代码适配封装", "publishConfig": { + "registry": "https://registry.npmjs.org/", "access": "public" }, "files": [ @@ -31,7 +32,7 @@ "component" ], "dependencies": { - "@monaco-editor/loader": "^1.2.0", + "@monaco-editor/loader": "1.3.0", "classnames": "^2.3.1" }, "devDependencies": { @@ -62,5 +63,10 @@ "commit-msg": "f2elint commit-msg-scan" } }, - "homepage": "https://unpkg.com/@alilc/lowcode-plugin-base-monaco-editor@1.0.0/build/index.html" + "homepage": "https://unpkg.com/@alilc/lowcode-plugin-base-monaco-editor@1.1.2/build/index.html", + "repository": { + "type": "git", + "url": "https://github.com/alibaba/lowcode-plugins.git", + "directory": "packages/base-monaco-editor" + } } diff --git a/packages/base-monaco-editor/src/controller.ts b/packages/base-monaco-editor/src/controller.ts new file mode 100644 index 0000000..2ebe419 --- /dev/null +++ b/packages/base-monaco-editor/src/controller.ts @@ -0,0 +1,39 @@ +export interface EditorMeta { + singleton: boolean; + [key: string]: any; +} + +export class Controller { + private methodMap: Record; + private meta: EditorMeta; + + constructor() { + this.methodMap = {}; + this.meta = { singleton: false }; + } + + registerMethod(name: string, fn: Function) { + this.methodMap[name] = fn; + } + + call(name: string, ...args: any[]) { + return this.methodMap[name]?.(...args); + } + + updateMeta(obj: Partial) { + Object.assign(this.meta, obj); + } + + getMeta() { + return Object.freeze({ ...this.meta }); + } +} + +const CONFIGURE_KEY = '__base_monaco_editor_controller__'; +const fakeWindow: any = window; + +if (!fakeWindow[CONFIGURE_KEY]) { + fakeWindow[CONFIGURE_KEY] = new Controller(); +} + +export const controller: Controller = fakeWindow[CONFIGURE_KEY]; diff --git a/packages/base-monaco-editor/src/helper.ts b/packages/base-monaco-editor/src/helper.ts index 475f80a..5180c52 100644 --- a/packages/base-monaco-editor/src/helper.ts +++ b/packages/base-monaco-editor/src/helper.ts @@ -1,70 +1,25 @@ /* eslint-disable no-empty */ -import { useEffect, useState, useRef, CSSProperties } from 'react'; -import loader from '@monaco-editor/loader'; - -loader.config({ - paths: { - vs: 'https://g.alicdn.com/code/lib/monaco-editor/0.31.1/min/vs', - }, -}); - -type IAmbigousFn = (...args: any[]) => any; +import React, { useEffect, useState, useRef, CSSProperties } from 'react'; +import { Monaco } from '@monaco-editor/loader'; +import type { editor as oEditor } from 'monaco-editor'; +import { getMonaco } from './monaco'; // @todo fill type def for monaco editor without refering monaco editor /** * @see https://microsoft.github.io/monaco-editor/api/index.html */ -export interface IEditorInstance { - getModel: IAmbigousFn; - dispose: IAmbigousFn; - getValue: () => string; - onDidChangeModelContent: (input: any) => void; - setTheme: (input: string) => void; - setModelLanguage: (model: any, language: string) => void; - layout: () => void; - setValue: (value: string) => void; - executeEdits: IAmbigousFn; - pushUndoStop: IAmbigousFn; - EditorOption?: Record; - getOption?: (input: string) => any; - onDidFocusEditorText: (...args: any[]) => void; - onDidBlurEditorText: (...args: any[]) => void; - getModifiedEditor?: () => IEditorInstance; - setModel: IAmbigousFn; - revealLineInCenter: IAmbigousFn; - focus: IAmbigousFn; - Range: new(...args: any[]) => any; - getPosition: IAmbigousFn; - setPosition: IAmbigousFn; - deltaDecorations: IAmbigousFn; - addAction: IAmbigousFn; - saveViewState: () => ICodeEditorViewState; - createModel: IAmbigousFn; - [key: string]: any; -} -export interface IMonacoInstance { - editor?: { - create: IAmbigousFn; - [key: string]: any; - }; - KeyCode?: Record; - KeyMod?: Record; - [otherKeys: string]: any; -} +export type IEditorInstance = oEditor.IStandaloneCodeEditor | oEditor.IStandaloneDiffEditor; -type ICodeEditorViewState = { - contributionsState: any; - cursorState: any; - viewState: any; -} +export type EditorEnhancer = + (monaco: Monaco, editorIns: IEditorInstance) => any; export interface IGeneralManacoEditorProps { /** [Monaco editor options](https://microsoft.github.io/monaco-editor/) */ options?: Record; /** callback after monaco's loaded and after editor's loaded */ - editorDidMount?: (monaco: IMonacoInstance, editor: IEditorInstance) => void; + editorDidMount?: (monaco: Monaco, editor: IEditorInstance) => void; /** callback after monaco's loaded and before editor's loaded */ - editorWillMount?: (monaco: IMonacoInstance) => void; + editorWillMount?: (monaco: Monaco) => void; /** path of the current model, useful when creating a multi-model editor */ path?: string; /** whether to save the models' view states between model changes or not */ @@ -89,6 +44,8 @@ export interface IGeneralManacoEditorProps { enableOutline?: boolean; /** style of wrapper */ style?: CSSProperties; + overrideServices?: oEditor.IEditorOverrideServices; + enhancers?: EditorEnhancer[]; } export interface ISingleMonacoEditorProps extends IGeneralManacoEditorProps { @@ -103,16 +60,15 @@ export interface IDiffMonacoEditorProps extends IGeneralManacoEditorProps { const CURRENT_LANGUAGE = ((window as any).locale || window.localStorage.getItem('vdev-locale') || '').replace(/_/, '-') || 'zh-CN'; export const WORD_EDITOR_INITIALIZING = CURRENT_LANGUAGE === 'en-US' ? 'Initializing Editor' : '编辑器初始化中'; -export const INITIAL_OPTIONS = { +export const INITIAL_OPTIONS: oEditor.IStandaloneEditorConstructionOptions = { fontSize: 12, tabSize: 2, fontFamily: 'Menlo, Monaco, Courier New, monospace', - renderIndentGuides: true, folding: true, minimap: { enabled: false, }, - autoIndent: true, + autoIndent: 'advanced', contextmenu: true, useTabStops: true, wordBasedSuggestions: true, @@ -131,9 +87,34 @@ export const INITIAL_OPTIONS = { }, }; -export const useEditor = (type: 'single' | 'diff', props: IGeneralManacoEditorProps) => { +const DIFF_EDITOR_INITIAL_OPTIONS: oEditor.IStandaloneDiffEditorConstructionOptions = { + fontSize: 12, + fontFamily: 'Menlo, Monaco, Courier New, monospace', + folding: true, + minimap: { + enabled: false, + }, + autoIndent: 'advanced', + contextmenu: true, + useTabStops: true, + formatOnPaste: true, + automaticLayout: true, + lineNumbers: 'on', + wordWrap: 'off', + scrollBeyondLastLine: false, + fixedOverflowWidgets: false, + snippetSuggestions: 'top', + scrollbar: { + vertical: 'auto', + horizontal: 'auto', + verticalScrollbarSize: 10, + horizontalScrollbarSize: 10, + }, +}; + +export const useEditor = (type: 'single' | 'diff', props: IGeneralManacoEditorProps) => { const { - editorDidMount, editorWillMount, theme, value, path, language, saveViewState, defaultValue, + editorDidMount, editorWillMount, theme, value, path, language, saveViewState, defaultValue, enhancers, overrideServices } = props; const [isEditorReady, setIsEditorReady] = useState(false); @@ -146,7 +127,7 @@ export const useEditor = (type: 'single' | 'diff', props: IGeneralManacoEditorPr const previousPath = usePrevious(path); const requireConfigRef = useRef(props.requireConfig); const optionRef = useRef(props.options); - const monacoRef = useRef(); + const monacoRef = useRef(); const editorRef = useRef(); const containerRef = useRef(); const typeRef = useRef(type); @@ -154,7 +135,13 @@ export const useEditor = (type: 'single' | 'diff', props: IGeneralManacoEditorPr const editorWillMountRef = useRef(); const decomposeRef = useRef(false); - const viewStatusRef = useRef>(new Map()) + const viewStatusRef = useRef>(new Map()); + + const enhancersRef = useRef({}); + + useEffect(() => { + enhancersRef.current.enhancers = enhancers; + }, [enhancers]); useEffect(() => { editorDidMountRef.current = editorDidMount; @@ -180,13 +167,8 @@ export const useEditor = (type: 'single' | 'diff', props: IGeneralManacoEditorPr // make sure loader / editor only init once useEffect(() => { setLoading(true); - - if (requireConfigRef.current) { - loader.config(requireConfigRef.current); - } - - loader.init() - .then((monaco: any) => { + getMonaco(requireConfigRef.current) + .then((monaco: Monaco) => { // 兼容旧版本 monaco-editor 写死 MonacoEnvironment 的问题 (window as any).MonacoEnvironment = undefined; if (typeof (window as any).define === 'function' && (window as any).define.amd) { @@ -203,19 +185,19 @@ export const useEditor = (type: 'single' | 'diff', props: IGeneralManacoEditorPr if (!containerRef.current) { return; } - let editor: IEditorInstance; + let editor: oEditor.IStandaloneCodeEditor | oEditor.IStandaloneDiffEditor; if (typeRef.current === 'single') { const model = getOrCreateModel( monaco, valueRef.current ?? defaultValueRef.current ?? '', languageRef.current, - pathRef.current + pathRef.current, ); editor = monaco.editor.create(containerRef.current, { automaticLayout: true, ...INITIAL_OPTIONS, ...optionRef.current, - }); + }, overrideServices); editor.setModel(model); } else { const originalModel = monaco @@ -228,14 +210,14 @@ export const useEditor = (type: 'single' | 'diff', props: IGeneralManacoEditorPr editor = monaco.editor.createDiffEditor(containerRef.current, { automaticLayout: true, - ...INITIAL_OPTIONS, + ...DIFF_EDITOR_INITIAL_OPTIONS, ...optionRef.current, - }); + }, overrideServices); editor.setModel({ original: originalModel, modified: modifiedModel }); } - editorRef.current = editor; + enhancersRef.current.enhancers?.forEach((en: any) => en(monaco, editor as any)); try { editorDidMountRef.current?.(monaco, editor); } catch (err) { } @@ -258,30 +240,6 @@ export const useEditor = (type: 'single' | 'diff', props: IGeneralManacoEditorPr monacoRef.current.editor.setTheme(theme); }, [isEditorReady, theme]); - // controlled value - useEffect(() => { - if (!isEditorReady) { - return; - } - - const editor = type === 'diff' - ? editorRef.current.getModifiedEditor() - : editorRef.current; - - const nextValue = value ?? defaultValueRef.current ?? '' - if (editor?.getOption?.(monacoRef.current?.editor.EditorOption.readOnly)) { - editor?.setValue(nextValue); - } else if (value !== editor?.getValue()) { - editor?.executeEdits('', [{ - range: editor?.getModel().getFullModelRange(), - text: nextValue, - forceMoveMarkers: true, - }]); - - editor?.pushUndoStop(); - } - }, [isEditorReady, type, value]); - // focus status useEffect(() => { if (!isEditorReady) { @@ -289,8 +247,8 @@ export const useEditor = (type: 'single' | 'diff', props: IGeneralManacoEditorPr } const editor = type === 'diff' - ? editorRef.current.getModifiedEditor() - : editorRef.current; + ? (editorRef.current as oEditor.IStandaloneDiffEditor).getModifiedEditor() + : editorRef.current as oEditor.IStandaloneCodeEditor; editor?.onDidFocusEditorText(() => { !decomposeRef.current && setFocused(true); }); @@ -306,7 +264,35 @@ export const useEditor = (type: 'single' | 'diff', props: IGeneralManacoEditorPr }; }, []); - // multi-model implementation + // controlled value -- diff mode / without path only + useEffect(() => { + if (!isEditorReady) { + return; + } + + if (type !== 'diff' && !!path) { + return; + } + + const editor = type === 'diff' + ? (editorRef.current as oEditor.IStandaloneDiffEditor).getModifiedEditor() + : editorRef.current as oEditor.IStandaloneCodeEditor; + + const nextValue = value ?? defaultValueRef.current ?? ''; + if (editor?.getOption?.(monacoRef.current?.editor.EditorOption.readOnly)) { + editor?.setValue(nextValue); + } else if (value !== editor?.getValue()) { + editor?.executeEdits('', [{ + range: editor?.getModel().getFullModelRange(), + text: nextValue, + forceMoveMarkers: true, + }]); + + editor?.pushUndoStop(); + } + }, [isEditorReady, path, type, value]); + + // multi-model && controlled value (shouldn't be diff mode) useEffect(() => { if (!isEditorReady) { return; @@ -327,29 +313,30 @@ export const useEditor = (type: 'single' | 'diff', props: IGeneralManacoEditorPr path, ); + const editor = editorRef.current as oEditor.IStandaloneCodeEditor; if (valueRef.current !== null && valueRef.current !== undefined && model.getValue() !== valueRef.current) { model.setValue(valueRef.current); } - if (model !== editorRef.current.getModel()) { - saveViewState && viewStatusRef.current.set(previousPath, editorRef.current.saveViewState()); - editorRef.current.setModel(model); - saveViewState && editorRef.current.restoreViewState(viewStatusRef.current.get(path)); + saveViewState && viewStatusRef.current.set(previousPath, editor.saveViewState()); + editor.setModel(model); + saveViewState && editor.restoreViewState(viewStatusRef.current.get(path)); } - }, [isEditorReady, path, previousPath, type]); + }, [isEditorReady, value, path, previousPath, type]); + let retEditorRef: React.MutableRefObject = editorRef as any; return { isEditorReady, focused, loading, containerRef, monacoRef, - editorRef, + editorRef: retEditorRef, valueRef, } as const; }; -function getOrCreateModel(monaco: IMonacoInstance, value?: string, language?: string, path?: string) { +function getOrCreateModel(monaco: Monaco, value?: string, language?: string, path?: string) { if (path) { const prevModel = monaco .editor @@ -365,9 +352,9 @@ function getOrCreateModel(monaco: IMonacoInstance, value?: string, language?: st } function usePrevious(value: T) { - const ref = useRef() + const ref = useRef(); useEffect(() => { - ref.current = value - }, [value]) - return ref.current + ref.current = value; + }, [value]); + return ref.current; } diff --git a/packages/base-monaco-editor/src/index.scss b/packages/base-monaco-editor/src/index.scss index a263d61..dfe6986 100644 --- a/packages/base-monaco-editor/src/index.scss +++ b/packages/base-monaco-editor/src/index.scss @@ -1,34 +1,31 @@ -.ve-code-control { +.lc-code-control { position: relative; box-sizing: content-box; border-radius: 3px; min-height: 100px; border: 1px solid transparent; - &.ve-code-control { - overflow: visible; - } } -.ve-code-control:hover { +.lc-code-control:hover { border-color: var(--color-field-border-hover, rgba(31, 56, 88, 0.1)); } -.ve-code-control.ve-focused { +.lc-code-control.ve-focused { border-color: var(--color-field-border-active, rgba(31, 56, 88, 0.15)); } -.ve-code-control.ve-outline { +.lc-code-control.ve-outline { border: 1px solid var(--color-field-border, rgba(31, 56, 88, 0.05)) !important; } -.ve-code-control .react-monaco-editor-container { +.lc-code-control .react-monaco-editor-container { min-height: 100px; height: 100%; width: 100%; background: transparent; } -.ve-code-control .base-monaco-full-screen-icon, +.lc-code-control .base-monaco-full-screen-icon, .base-monaco-full-screen-icon-cancel { width: 20px; height: 20px; @@ -50,7 +47,7 @@ right: 138px; } -.ve-code-control .loading { +.lc-code-control .loading { position: absolute; display: flex; align-items: center; @@ -70,7 +67,7 @@ } } -.ve-code-control .syntaxTips { +.lc-code-control .syntaxTips { position: absolute; bottom: 0; left: 0; @@ -84,12 +81,12 @@ box-sizing: border-box; } -.ve-code-control .syntaxTips:hover { +.lc-code-control .syntaxTips:hover { max-height: 50%; overflow: auto; } -.ve-code-control .syntaxTips .infoIcon { +.lc-code-control .syntaxTips .infoIcon { position: absolute; width: 20px; height: 16px; diff --git a/packages/base-monaco-editor/src/index.tsx b/packages/base-monaco-editor/src/index.tsx index d9a7dff..dc5bbe0 100644 --- a/packages/base-monaco-editor/src/index.tsx +++ b/packages/base-monaco-editor/src/index.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { useRef, useEffect, useMemo, useState } from 'react'; import classNames from 'classnames'; +import type { editor } from 'monaco-editor'; import { ISingleMonacoEditorProps, WORD_EDITOR_INITIALIZING, @@ -9,6 +10,9 @@ import { INITIAL_OPTIONS, } from './helper'; +export * from './monaco'; +export * from './controller'; + const SingleMonacoEditor = (props: ISingleMonacoEditorProps) => { const { onChange, enableOutline, width, height, language, supportFullScreen } = props; const onChangeRef = useRef(onChange); @@ -23,10 +27,10 @@ const SingleMonacoEditor = (props: ISingleMonacoEditorProps) => { monacoRef, editorRef, valueRef, - } = useEditor('single', props); + } = useEditor('single', props); const subscriptionRef = useRef(null); - const className = classNames('ve-code-control', props.className, { + const className = classNames('lc-code-control', props.className, { 've-focused': focused, 've-outline': enableOutline, }); @@ -49,9 +53,10 @@ const SingleMonacoEditor = (props: ISingleMonacoEditorProps) => { useEffect(() => { if (isEditorReady) { + const editorInstance = editorRef.current; subscriptionRef.current?.dispose(); - subscriptionRef.current = editorRef.current?.onDidChangeModelContent((event: any) => { - const editorValue = editorRef.current?.getModel().getValue(); + subscriptionRef.current = editorInstance?.onDidChangeModelContent((event: any) => { + const editorValue = editorInstance?.getModel().getValue(); if (valueRef.current !== editorValue) { onChangeRef.current?.(editorValue, event); @@ -62,8 +67,9 @@ const SingleMonacoEditor = (props: ISingleMonacoEditorProps) => { useEffect(() => { return () => { + const editorInstance = editorRef.current; subscriptionRef.current?.dispose(); - editorRef.current?.getModel()?.dispose(); + editorInstance?.getModel()?.dispose(); // eslint-disable-next-line react-hooks/exhaustive-deps editorRef.current?.dispose(); }; @@ -74,10 +80,11 @@ const SingleMonacoEditor = (props: ISingleMonacoEditorProps) => { return; } - monacoRef.current?.editor.setModelLanguage(editorRef.current?.getModel(), language); + monacoRef.current?.editor.setModelLanguage((editorRef.current)?.getModel(), language); }, [editorRef, isEditorReady, language, monacoRef]); const fullScreen = () => { + const editorInstance = editorRef.current; if (!isFullScreen) { setIsFullScreen(true); setFullScreenStyle({ @@ -91,36 +98,36 @@ const SingleMonacoEditor = (props: ISingleMonacoEditorProps) => { zIndex: 9998, }); // 更新小地图配置 - editorRef.current?.updateOptions({ - ...editorRef.current?.getOptions(), + editorInstance?.updateOptions({ + ...editorInstance?.getOptions(), minimap: { enabled: true, }, }); - editorRef.current?.layout(); + editorInstance?.layout(); } else { setIsFullScreen(false); - editorRef.current?.updateOptions({ - ...editorRef.current?.getOptions(), + editorInstance?.updateOptions({ + ...editorInstance?.getOptions(), minimap: { enabled: false, }, }); - editorRef.current?.layout(); + editorInstance?.layout(); } }; return (
{loading && {WORD_EDITOR_INITIALIZING}} - -
- {supportFullScreen &&
} -
+ +
+ {supportFullScreen &&
} +
); }; @@ -128,12 +135,12 @@ const SingleMonacoEditor = (props: ISingleMonacoEditorProps) => { const DiffMonacoEditor = (props: IDiffMonacoEditorProps) => { const { enableOutline, width, height, language, original } = props; - const { isEditorReady, focused, loading, containerRef, monacoRef, editorRef } = useEditor( + const { isEditorReady, focused, loading, containerRef, monacoRef, editorRef } = useEditor( 'diff', props, ); - const className = classNames('ve-code-control', props.className, { + const className = classNames('lc-code-control', props.className, { 've-focused': focused, 've-outline': enableOutline, }); @@ -146,7 +153,7 @@ const DiffMonacoEditor = (props: IDiffMonacoEditorProps) => { if (!isEditorReady) { return; } - editorRef.current.getModel().original.setValue(original ?? ''); + (editorRef.current).getModel().original.setValue(original ?? ''); }, [editorRef, isEditorReady, original]); useEffect(() => { diff --git a/packages/base-monaco-editor/src/monaco.ts b/packages/base-monaco-editor/src/monaco.ts new file mode 100644 index 0000000..c2f4a24 --- /dev/null +++ b/packages/base-monaco-editor/src/monaco.ts @@ -0,0 +1,53 @@ +import loader, { Monaco } from '@monaco-editor/loader'; +//@ts-ignore +import isEqual from 'lodash/isEqual'; +import { controller, EditorMeta } from './controller'; + +export const getSingletonMonaco = (() => { + let monaco: Monaco; + let prevOptions: any; + return async (options?: any) => { + if (!monaco || !isEqual(prevOptions, options)) { + const hasConfig = Object.keys(options || {}).length > 0; + loader.config( + hasConfig + ? options + : { + paths: { + vs: 'https://g.alicdn.com/code/lib/monaco-editor/0.33.0/min/vs', + }, + }, + ); + // eslint-disable-next-line require-atomic-updates + monaco = await loader.init(); + // eslint-disable-next-line require-atomic-updates + prevOptions = options; + } + return monaco; + }; +})(); + +export const getCommonMonaco = (config: any): Promise => { + if (config) { + loader.config(config); + } else { + loader.config({ + paths: { + vs: 'https://g.alicdn.com/code/lib/monaco-editor/0.31.1/min/vs', + }, + }); + } + return loader.init(); +}; + +export function getMonaco(config?: any) { + const hasConfig = Object.keys(config || {}).length > 0; + const monacoConfig = hasConfig ? config : undefined; + return controller.getMeta().singleton + ? getSingletonMonaco(monacoConfig) + : getCommonMonaco(monacoConfig); +} + +export function configure(config: EditorMeta) { + controller.updateMeta(config); +} diff --git a/packages/plugin-block/README.md b/packages/plugin-block/README.md new file mode 100644 index 0000000..a339eb8 --- /dev/null +++ b/packages/plugin-block/README.md @@ -0,0 +1,46 @@ +# 区块管理 - 区块面板 + +## 区块实体 + +``` + +interface Block { + name: string; + title: string; + schema: string; + screenshot: string; + created_at?: string; + updated_at?: string; +} + +``` + +## 注意 + +使用区块管理需要提前将对应的 API 注册到 engine config 里: + +``` + +interface BlockAPI { + listBlocks: () => Block[]; + createBlock: (Block) => any; +} + +function setupConfig() { + config.set('apiList', { + block: { + listBlocks, + createBlock + }, + }) +} +``` + +# 使用方式 + +``` +import { plugins } from '@alilc/lowcode-engine'; +import BlockPane from '@alilc/lowcode-plugin-block'; + +await plugins.register(BlockPane); +``` \ No newline at end of file diff --git a/packages/plugin-block/build.json b/packages/plugin-block/build.json new file mode 100644 index 0000000..d2836c1 --- /dev/null +++ b/packages/plugin-block/build.json @@ -0,0 +1,17 @@ +{ + "externals": { + "react": "var window.React", + "@alifd/next": "var window.Next", + "@alilc/lowcode-engine": "var window.AliLowCodeEngine" + }, + "plugins": [ + "build-plugin-component", + "build-plugin-fusion", + [ + "build-plugin-moment-locales", + { + "locales": ["zh-cn"] + } + ] + ] +} diff --git a/packages/plugin-block/package.json b/packages/plugin-block/package.json new file mode 100644 index 0000000..6c27ed4 --- /dev/null +++ b/packages/plugin-block/package.json @@ -0,0 +1,31 @@ +{ + "name": "@alilc/lowcode-plugin-block", + "version": "1.0.0-beta.0", + "description": "", + "main": "lib/index.js", + "scripts": { + "start": "build-scripts start", + "build": "build-scripts build", + "prepublishOnly": "npm run build" + }, + "author": "mark.ck", + "license": "MIT", + "dependencies": { + "@alifd/next": "^1.25.31", + "@alilc/lowcode-engine": "^1.0.5", + "html2canvas": "^1.4.1", + "react": "^16.8.1" + }, + "devDependencies": { + "@alib/build-scripts": "^0.1.32", + "build-plugin-component": "^1.10.0", + "build-plugin-fusion": "^0.1.22", + "build-plugin-moment-locales": "^0.1.3" + }, + "homepage": "https://unpkg.com/@alilc/lowcode-plugin-block@1.0.0-beta.0/build/index.html", + "repository": { + "type": "git", + "url": "https://github.com/alibaba/lowcode-plugins.git", + "directory": "packages/plugin-block" + } +} diff --git a/packages/plugin-block/src/card/index.scss b/packages/plugin-block/src/card/index.scss new file mode 100644 index 0000000..63b8052 --- /dev/null +++ b/packages/plugin-block/src/card/index.scss @@ -0,0 +1,32 @@ +.block-card { + display: flex; + flex-direction: column; + padding: 14px; + align-items: center; + justify-content: space-between; + width: 50%; + height: 150px; + flex-grow: 0; + flex-shrink: 0; + border-right: 1px solid #eaeaea; + border-top: 1px solid #eaeaea; + border-bottom: 1px solid #eaeaea; + box-shadow: 0 0 0 0 rgb(0 0 0 / 15%); + transition: box-shadow 0.2s ease; + + .next-divider-hoz { + margin-bottom: 0 !important; + } + + .block-card-screenshot { + display: flex; + padding: 10px; + background: gainsboro; + img { + width: 56px; + height: 56px; + object-fit: contain; + } + } + +} \ No newline at end of file diff --git a/packages/plugin-block/src/card/index.tsx b/packages/plugin-block/src/card/index.tsx new file mode 100644 index 0000000..263d962 --- /dev/null +++ b/packages/plugin-block/src/card/index.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; + +import './index.scss'; + +const { useState, useEffect } = React; + +interface BlockCardProps { + id: string; + title: string; + screenshot: string; +} + +const BlockCard = (props: BlockCardProps) => { + const { id, title, screenshot='https://tianshu.alicdn.com/19307bb5-2881-44ad-82d3-f92e2f44aabb.png' } = props; + + return
+
+ +
+ + {title} + +
; +}; + +export default BlockCard; \ No newline at end of file diff --git a/packages/plugin-block/src/index.tsx b/packages/plugin-block/src/index.tsx new file mode 100644 index 0000000..dfbbb71 --- /dev/null +++ b/packages/plugin-block/src/index.tsx @@ -0,0 +1,47 @@ +import * as React from 'react'; +import { IPublicModelPluginContext } from '@alilc/lowcode-types'; +import { default as BlockPane } from './pane'; + +const LowcodePluginCusPlugin = (ctx: IPublicModelPluginContext) => { + return { + // 插件名,注册环境下唯一 + name: 'LowcodePluginCusPlugin', + // 依赖的插件(插件名数组) + dep: [], + // 插件对外暴露的数据和方法 + exports() { + return { + data: '你可以把插件的数据这样对外暴露', + func: () => { + console.log('方法也是一样'); + }, + } + }, + // 插件的初始化函数,在引擎初始化之后会立刻调用 + init() { + // 你可以拿到其他插件暴露的方法和属性 + // const { data, func } = ctx.plugins.pluginA; + // func(); + + // console.log(options.name); + + // 往引擎增加面板 + ctx.skeleton.add({ + area: 'leftArea', + name: 'blockPane', + type: 'PanelDock', + props: { + icon: , + description: '区块面板', + }, + content: BlockPane, + }); + + ctx.logger.log('打个日志'); + }, + }; +}; + +LowcodePluginCusPlugin.pluginName = 'BlockPlugin'; + +export default LowcodePluginCusPlugin; diff --git a/packages/plugin-block/src/pane/index.scss b/packages/plugin-block/src/pane/index.scss new file mode 100644 index 0000000..e576c4d --- /dev/null +++ b/packages/plugin-block/src/pane/index.scss @@ -0,0 +1,13 @@ +.block-pane { + overflow-y: scroll; + height: 100%; +} + +.block-pane-loading { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; +} + diff --git a/packages/plugin-block/src/pane/index.tsx b/packages/plugin-block/src/pane/index.tsx new file mode 100644 index 0000000..3f2f6cd --- /dev/null +++ b/packages/plugin-block/src/pane/index.tsx @@ -0,0 +1,124 @@ +import * as React from 'react'; + +import { common, project, config, event } from '@alilc/lowcode-engine'; +import { Loading, Box } from '@alifd/next'; + +import { default as BlockCard } from '../card'; +import { default as store } from '../store'; + +import './index.scss'; + +const { useState, useEffect } = React; + +const DEFAULT_SCREENSHOT = 'https://tianshu.alicdn.com/19307bb5-2881-44ad-82d3-f92e2f44aabb.png'; + +function checkBlockAPI() { + const apiList = config.get('apiList') || {}; + const { block: blockAPI } = apiList; + + if (!blockAPI?.listBlocks) { + throw new Error('[BlockPane] block api required in engine config.'); + } + + return blockAPI; +} + +export interface Block { + +} + +export interface BlockResponse { + code: number; + data: Block[]; +} + +export interface BlockPaneAPI { + listBlocks: () => BlockResponse; +} + +export interface BlockPaneProps { + api: BlockPaneAPI +} + +export const BlockPane = (props: BlockPaneProps) => { + const { listBlocks } = checkBlockAPI(); + const [ blocks, setBlocks ] = useState(); + useEffect(() => { + const fetchBlocks = async () => { + const res = await listBlocks(); + store.init(res); + setBlocks(res); + }; + event.on('common:BlockChanged', () => { + fetchBlocks(); + }) + fetchBlocks(); + + }, []); + + const registerAdditive = (shell: HTMLDivElement | null) => { + console.log('shell: ', shell); + if (!shell || shell.dataset.registered) { + return; + } + + function getSnippetId(elem: any) { + if (!elem) { + return null; + } + while (shell !== elem) { + console.log('elem.classList; ', elem.classList); + if (elem.classList.contains('snippet')) { + return elem.dataset.id; + } + elem = elem.parentNode; + } + return null; + } + + const _dragon = common.designerCabin.dragon + console.log('_dragon: ', _dragon); + if (!_dragon) { + return; + } + + // eslint-disable-next-line + const click = (e: Event) => {}; + + shell.addEventListener('click', click); + + _dragon.from(shell, (e: Event) => { + const doc = project.getCurrentDocument(); + const id = getSnippetId(e.target); + console.log('doc: ', doc); + console.log('id: ', id); + if (!doc || !id) { + return false; + } + + console.log('store.get(id): ', store.get(id)); + + const dragTarget = { + type: 'nodedata', + data: store.get(id), + }; + + return dragTarget; + }); + + shell.dataset.registered = 'true'; + }; + + if (!blocks?.length) { + return
+ } + + return
+ { + blocks.map(item => ) + } + +
; +} + +export default BlockPane; \ No newline at end of file diff --git a/packages/plugin-block/src/store/index.ts b/packages/plugin-block/src/store/index.ts new file mode 100644 index 0000000..140bc53 --- /dev/null +++ b/packages/plugin-block/src/store/index.ts @@ -0,0 +1,30 @@ + + +class BlockStore { + + store; + + constructor() { + this.store = new Map(); + } + + init(blocks) { + blocks.forEach(block => { + const { id, schema } = block; + this.store.set(`${id}`, JSON.parse(schema)); + }); + } + + set(id, snippets) { + this.store.set(id, snippets); + } + + get(id) { + return this.store.get(id); + } + +} + +const singleton = new BlockStore(); + +export default singleton; \ No newline at end of file diff --git a/packages/plugin-code-editor/.editorconfig b/packages/plugin-code-editor/.editorconfig new file mode 100644 index 0000000..3192996 --- /dev/null +++ b/packages/plugin-code-editor/.editorconfig @@ -0,0 +1,13 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true +quote_type = single + +[*.md] +trim_trailing_whitespace = false diff --git a/packages/plugin-code-editor/.eslintignore b/packages/plugin-code-editor/.eslintignore new file mode 100644 index 0000000..36c1703 --- /dev/null +++ b/packages/plugin-code-editor/.eslintignore @@ -0,0 +1,7 @@ +node_modules/ +build/ +dist/ +**/*.min.js +**/*-min.js +**/*.bundle.js +*.js diff --git a/packages/plugin-code-editor/.eslintrc.js b/packages/plugin-code-editor/.eslintrc.js new file mode 100644 index 0000000..0245b1d --- /dev/null +++ b/packages/plugin-code-editor/.eslintrc.js @@ -0,0 +1,11 @@ +module.exports = { + extends: [ + 'eslint-config-ali/typescript/react', + 'prettier', + 'prettier/@typescript-eslint', + 'prettier/react', + ], + rules: { + 'no-param-reassign': 0, + }, +}; diff --git a/packages/plugin-code-editor/.gitignore b/packages/plugin-code-editor/.gitignore deleted file mode 100644 index 77dfbd1..0000000 --- a/packages/plugin-code-editor/.gitignore +++ /dev/null @@ -1,104 +0,0 @@ -# project custom -build -dist -lib -es -package-lock.json -yarn.lock -deploy-space/packages -deploy-space/.env - - -# IDE -.vscode -.idea - -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release -lib - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env -.env.test - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# next.js build output -.next - -# nuxt.js build output -.nuxt - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# mac config files -.DS_Store - -# codealike -codealike.json diff --git a/packages/plugin-code-editor/CHANGELOG.md b/packages/plugin-code-editor/CHANGELOG.md index 6784156..a8f5cba 100644 --- a/packages/plugin-code-editor/CHANGELOG.md +++ b/packages/plugin-code-editor/CHANGELOG.md @@ -1,3 +1,20 @@ +## 1.0.5 / 2022-11-28 + +- Fix 修正 UIPaaS 中针对 source 的异常提示问题 + +## 1.0.4 / 2022-11-18 + +- Fix 修正输入 / 粘贴时光标位置错误的 bug +- Fix 修复当上下文没有 originCode 时,通过 schema 中的局部 source 字段无法恢复 state 的问题 + +## 1.0.3 / 2022-07-11 + +- Fix 源码面板高度塌陷的 [bug](https://github.com/alibaba/lowcode-engine/issues/803) + +## 1.0.2 / 2022-05-05 + +* Fix [schema export issue](https://github.com/alibaba/lowcode-engine/issues/416) + ## 1.0.1 - 修正和 ext 的联动 diff --git a/packages/plugin-code-editor/build.js b/packages/plugin-code-editor/build.js new file mode 100644 index 0000000..9bcddea --- /dev/null +++ b/packages/plugin-code-editor/build.js @@ -0,0 +1,22 @@ +module.exports = { + plugins: [ + [ + 'build-plugin-fusion', + { + themePackage: '@alifd/theme-lowcode-light', + }, + ], + [ + '@alilc/build-plugin-alt', + { + type: 'plugin', + // 开启注入调试模式,see:https://www.yuque.com/lce/doc/ulvlkz + inject: true, + // 配置要打开的页面,在注入调试模式下,不配置此项的话不会打开浏览器 + // 支持直接使用官方 demo 项目:https://lowcode-engine.cn/demo/index.html + // openUrl: 'https://lowcode-engine.cn/demo/index.html?debug', + }, + ], + './build.plugin.js', + ], +}; diff --git a/packages/plugin-code-editor/build.json b/packages/plugin-code-editor/build.json deleted file mode 100644 index f9c8757..0000000 --- a/packages/plugin-code-editor/build.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "plugins": [ - [ - "build-plugin-fusion", - { - "themePackage": "@alifd/theme-lowcode-light" - } - ], - ["@alilc/build-plugin-alt", { - "type": "plugin" - }], - "./build.plugin.js" - ] -} diff --git a/packages/plugin-code-editor/package.json b/packages/plugin-code-editor/package.json index 9b6de0b..340bc01 100644 --- a/packages/plugin-code-editor/package.json +++ b/packages/plugin-code-editor/package.json @@ -1,7 +1,7 @@ { "name": "@alilc/lowcode-plugin-code-editor", "author": "59174526@qq.com & humphry.huang9@gmail.com", - "version": "1.0.1", + "version": "1.0.9", "description": "CodeEditor", "files": [ "es", @@ -12,19 +12,20 @@ "types": "lib/index.d.ts", "README": "README.md", "scripts": { - "start": "build-scripts start", + "start": "build-scripts start --config build.js", "prepublishOnly": "npm run build", - "build": "build-scripts build" + "build": "build-scripts build --config build.js" }, "publishConfig": { + "registry": "https://registry.npmjs.org/", "access": "public" }, "dependencies": { "@alifd/next": "^1.25.13", - "@alilc/lowcode-plugin-base-monaco-editor": "^1.0.0", + "@alilc/lowcode-plugin-base-monaco-editor": "^1.1.1", "@babel/core": "^7.15.8", "@babel/parser": "^7.15.8", - "@babel/preset-env": "^7.15.8", + "@babel/preset-env": "7.22.9", "@babel/preset-react": "^7.14.5", "@babel/standalone": "^7.15.8", "@babel/traverse": "^7.15.4", @@ -49,5 +50,10 @@ "@types/react-dom": "^16.9.4", "build-plugin-fusion": "^0.1.22" }, - "license": "MIT" + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/alibaba/lowcode-plugins.git", + "directory": "packages/plugin-code-editor" + } } diff --git a/packages/plugin-code-editor/src/components/ErrorBoundary/ErrorBoundary.less b/packages/plugin-code-editor/src/components/ErrorBoundary/ErrorBoundary.less index ab8f7b3..5a9324d 100644 --- a/packages/plugin-code-editor/src/components/ErrorBoundary/ErrorBoundary.less +++ b/packages/plugin-code-editor/src/components/ErrorBoundary/ErrorBoundary.less @@ -1,7 +1,4 @@ -.container { - min-width: 300px; -} -.actions { +.plugin-code-editor-errorBoundary-actions { margin: 12px auto; width: fit-content; text-align: center; diff --git a/packages/plugin-code-editor/src/components/ErrorBoundary/ErrorBoundary.tsx b/packages/plugin-code-editor/src/components/ErrorBoundary/ErrorBoundary.tsx index fabd328..72159fd 100644 --- a/packages/plugin-code-editor/src/components/ErrorBoundary/ErrorBoundary.tsx +++ b/packages/plugin-code-editor/src/components/ErrorBoundary/ErrorBoundary.tsx @@ -1,7 +1,7 @@ import React, { Component, ErrorInfo, Suspense } from 'react'; import { Loading, Message, Button } from '@alifd/next'; -import styles from './ErrorBoundary.less'; +import './ErrorBoundary.less'; interface ErrorBoundaryProps { onCatch?: (error: Error, info: ErrorInfo) => void; @@ -41,7 +41,7 @@ export class ErrorBoundary extends Component

详细错误: {errorInfo || '未知原因'}

-
+
diff --git a/packages/plugin-code-editor/src/components/JSEditor/JsEditor.tsx b/packages/plugin-code-editor/src/components/JSEditor/JsEditor.tsx index c84a816..4b0acfd 100644 --- a/packages/plugin-code-editor/src/components/JSEditor/JsEditor.tsx +++ b/packages/plugin-code-editor/src/components/JSEditor/JsEditor.tsx @@ -178,8 +178,14 @@ export class JsEditor extends PureComponent { if (!monacoEditor || !monaco) { return; } - const count = monacoEditor.getModel()?.getLineCount() ?? 0; - const range = new monaco.Range(count, 1, count, 1); + + // 找到最后一个 },在他前面插入新的 function 字符串 + const matches = monacoEditor.getModel()?.findMatches('}'); + let range = {} + if(matches && matches.length > 0) { + const { startLineNumber, startColumn } = matches[matches.length - 1]?.range || {} + range = new monaco.Range(startLineNumber, startColumn, startLineNumber, startColumn); + } const functionCode = params.template ? params.template : @@ -248,10 +254,9 @@ export class JsEditor extends PureComponent { return; } - const pos = monacoEditor.getPosition(); + // const pos = monacoEditor.getPosition(); this.setState({ errorInfo, hasError, code: newCode, errorLocation }, () => { - monacoEditor.setPosition(pos); - + // monacoEditor.setPosition(pos); // update error decorations if (this.lastErrorDecoration) { monacoEditor.deltaDecorations( diff --git a/packages/plugin-code-editor/src/config/default-code.ts b/packages/plugin-code-editor/src/config/default-code.ts index 3765874..bf7ea44 100644 --- a/packages/plugin-code-editor/src/config/default-code.ts +++ b/packages/plugin-code-editor/src/config/default-code.ts @@ -1,5 +1,5 @@ export const defaultCode = ` - export default class LowcodeComponent extends Component { + export default class Page extends Component { // 可以在 state 中定义搭建所需要的 State state = { test: 1, diff --git a/packages/plugin-code-editor/src/index.tsx b/packages/plugin-code-editor/src/index.tsx index 766a74c..c40e74e 100644 --- a/packages/plugin-code-editor/src/index.tsx +++ b/packages/plugin-code-editor/src/index.tsx @@ -1,8 +1,9 @@ import { CodeEditorPane } from './pane'; -import { project, ILowCodePluginContext } from '@alilc/lowcode-engine'; +import { project } from '@alilc/lowcode-engine'; import icon from './icon'; +import { IPublicModelPluginContext } from '@alilc/lowcode-types'; -const plugin = (ctx: ILowCodePluginContext) => { +const plugin = (ctx: IPublicModelPluginContext) => { return { name: 'codeEditor', width: 600, @@ -14,7 +15,7 @@ const plugin = (ctx: ILowCodePluginContext) => { }, // 插件的初始化函数,在引擎初始化之后会立刻调用 init() { - const schemaDock = ctx.skeleton.add({ + const codeEditorDock = ctx.skeleton.add({ area: 'leftArea', name: 'codeEditor', type: 'PanelDock', @@ -35,9 +36,9 @@ const plugin = (ctx: ILowCodePluginContext) => { ), }); - schemaDock && schemaDock.disable(); + codeEditorDock && codeEditorDock.disable(); project.onSimulatorRendererReady(() => { - schemaDock.enable(); + codeEditorDock.enable(); }); }, }; diff --git a/packages/plugin-code-editor/src/pane/index.less b/packages/plugin-code-editor/src/pane/index.less index 16f5e99..3956047 100644 --- a/packages/plugin-code-editor/src/pane/index.less +++ b/packages/plugin-code-editor/src/pane/index.less @@ -15,7 +15,7 @@ overflow: hidden; } - .ve-code-control { + .lc-code-control { position: relative; height: calc(100% - 24px); } diff --git a/packages/plugin-code-editor/src/pane/index.tsx b/packages/plugin-code-editor/src/pane/index.tsx index 917832a..dae3b0c 100644 --- a/packages/plugin-code-editor/src/pane/index.tsx +++ b/packages/plugin-code-editor/src/pane/index.tsx @@ -4,6 +4,7 @@ import { Dialog, Message } from '@alifd/next'; import { JsEditor, CssEditor } from '../components'; import { schema2JsCode, schema2CssCode } from '../utils'; import { WORDS, TAB_KEY } from '../config'; +import { common } from '@alilc/lowcode-engine'; import { FunctionEventParams } from '../types'; import { Project, Event, Skeleton } from '@alilc/lowcode-shell'; @@ -27,7 +28,7 @@ export const CodeEditorPane = memo(({ project, event, skeleton }: CodeEditorPane const cssEditorRef = useRef(null); const saveSchemaRef = useRef<() => void>(); // save code to schema - const [schema, setSchema] = useState(() => project.exportSchema()); + const [schema, setSchema] = useState(() => project.exportSchema(common.designerCabin.TransformStage.Save)); const jsCode = useMemo(() => { return schema2JsCode(schema); @@ -40,7 +41,7 @@ export const CodeEditorPane = memo(({ project, event, skeleton }: CodeEditorPane useEffect(() => { saveSchemaRef.current = () => { try { - const currentSchema = lowcodeProjectRef.current?.exportSchema(); + const currentSchema = lowcodeProjectRef.current?.exportSchema(common.designerCabin.TransformStage.Save); const pageNode = currentSchema.componentsTree[0]; const { state, methods, lifeCycles, originCode = '' } = jsEditorRef.current?.getSchemaFromCode() ?? {}; const css = cssEditorRef.current?.getBeautifiedCSS() ?? cssCode; @@ -48,7 +49,7 @@ export const CodeEditorPane = memo(({ project, event, skeleton }: CodeEditorPane pageNode.methods = methods; pageNode.lifeCycles = lifeCycles; pageNode.originCode = originCode; - + pageNode.css = css; lowcodeProjectRef.current?.importSchema(currentSchema); @@ -69,7 +70,7 @@ export const CodeEditorPane = memo(({ project, event, skeleton }: CodeEditorPane ), onOk: () => { - skeletonRef.current?.showPanel("codeEditor") + skeletonRef.current?.showPanel('codeEditor'); }, }); } @@ -93,9 +94,9 @@ export const CodeEditorPane = memo(({ project, event, skeleton }: CodeEditorPane useEffect(() => { // load schema on open - eventRef.current?.on('skeleton.panel-dock.active', (pluginName) => { - if (pluginName == 'codeEditor') { - const schema = lowcodeProjectRef.current?.exportSchema() + skeletonRef.current?.onShowPanel((pluginName: string) => { + if (pluginName === 'codeEditor') { + const schema = lowcodeProjectRef.current?.exportSchema(common.designerCabin.TransformStage.Save); if (!schema) { return; } @@ -106,26 +107,26 @@ export const CodeEditorPane = memo(({ project, event, skeleton }: CodeEditorPane cssEditorRef.current?._updateCode(cssCode); } }); - + // save schema when panel closed - eventRef.current?.on('skeleton.panel-dock.unactive', (pluginName) => { + skeletonRef.current?.onHidePanel((pluginName: string) => { if (pluginName === 'codeEditor') { saveSchemaRef.current?.(); } }); // focus function by functionName - eventRef.current?.on('common:codeEditor.focusByFunction', (params: FunctionEventParams) => { + eventRef.current?.on('common:codeEditor.focusByFunction', (params) => { setActiveKey(TAB_KEY.JS); setTimeout(() => { - jsEditorRef.current?.focusByFunctionName(params); + jsEditorRef.current?.focusByFunctionName(params as FunctionEventParams); }, 100); }); - eventRef.current?.on('common:codeEditor.addFunction', (params: FunctionEventParams) => { + eventRef.current?.on('common:codeEditor.addFunction', (params) => { setActiveKey(TAB_KEY.JS); setTimeout(() => { - jsEditorRef.current?.addFunction(params); + jsEditorRef.current?.addFunction(params as FunctionEventParams); }, 100); }); }, []); diff --git a/packages/plugin-code-editor/src/types/methods.ts b/packages/plugin-code-editor/src/types/methods.ts index 2e62f08..093941f 100644 --- a/packages/plugin-code-editor/src/types/methods.ts +++ b/packages/plugin-code-editor/src/types/methods.ts @@ -1,7 +1,8 @@ +import { JSFunction, JSExpression } from '@alilc/lowcode-types'; +export type Method = JSExpression | JSFunction & { + source: string; +} + export interface Methods { - [key: string]: { - type: 'JSFunction'; - value: string; - originalCode: string; - }; + [key: string]: Method; } diff --git a/packages/plugin-code-editor/src/types/state.ts b/packages/plugin-code-editor/src/types/state.ts index d994852..ff85180 100644 --- a/packages/plugin-code-editor/src/types/state.ts +++ b/packages/plugin-code-editor/src/types/state.ts @@ -1,6 +1,6 @@ import { JSExpression } from '@alilc/lowcode-types'; -export interface IState extends JSExpression{ - // 原始代码 - originCode: string; +export interface IState extends JSExpression { + // 原始代码 + source: string; } \ No newline at end of file diff --git a/packages/plugin-code-editor/src/typeings.d.ts b/packages/plugin-code-editor/src/typings.d.ts similarity index 100% rename from packages/plugin-code-editor/src/typeings.d.ts rename to packages/plugin-code-editor/src/typings.d.ts diff --git a/packages/plugin-code-editor/src/utils/evaluate.ts b/packages/plugin-code-editor/src/utils/evaluate.ts new file mode 100644 index 0000000..d4b7db7 --- /dev/null +++ b/packages/plugin-code-editor/src/utils/evaluate.ts @@ -0,0 +1,32 @@ +interface Expr { + type: string; + value: string | number; + extType?: string; +} + +type ExprType = Expr | string; + +function getExprStr(expr: ExprType) { + if (typeof expr === 'string') { + return expr; + } + return expr && (expr as Expr).value; +} + +function getEvalExpressionStr(expr: ExprType): string | undefined { + const exprStr = getExprStr(expr); + if (exprStr == undefined) { + return exprStr; + } else if (exprStr === '') { + return undefined; + } + return `(function(){return (${exprStr})}).call($scope)`; +} + +export function evaluate(expr: ExprType) { + const evalExprStr = getEvalExpressionStr(expr); + const code = `with($scope || {}) { return ${evalExprStr} }`; + const fn = new Function('$scope', code); + // 暂时不传递 $scope + return fn(); +} diff --git a/packages/plugin-code-editor/src/utils/get-methods.ts b/packages/plugin-code-editor/src/utils/get-methods.ts index 72579f7..cd890b2 100644 --- a/packages/plugin-code-editor/src/utils/get-methods.ts +++ b/packages/plugin-code-editor/src/utils/get-methods.ts @@ -54,8 +54,7 @@ export const getMethods = (ast: Node) => { methods[name] = { type: 'JSFunction', value: compiledCode, - // 这里的 originalCode 直接放在全局,不挂在局部 - // originalCode: codeStr, + source: codeStr, }; }, }); diff --git a/packages/plugin-code-editor/src/utils/schema-to-code.ts b/packages/plugin-code-editor/src/utils/schema-to-code.ts index 166ae6b..986ed9a 100644 --- a/packages/plugin-code-editor/src/utils/schema-to-code.ts +++ b/packages/plugin-code-editor/src/utils/schema-to-code.ts @@ -1,14 +1,14 @@ - import { js_beautify, css_beautify } from 'js-beautify'; -import { isJSExpression, ProjectSchema, RootSchema, JSFunction, JSExpression } from '@alilc/lowcode-types'; +import { isJSExpression, ProjectSchema, RootSchema } from '@alilc/lowcode-types'; import { Dialog } from '@alifd/next'; import { IState } from '../types'; -import { defaultStateCode, WORDS } from '../config'; +import { WORDS } from '../config'; +import type { Method } from '../types/methods'; const js_beautify_config = { indent_size: 2, indent_empty_lines: true, e4x: true }; const initCode = (componentSchema: RootSchema | undefined) => { - const code = `class LowcodeComponent extends Component { + const code = `class Page extends Component { ${initStateCode(componentSchema)} ${initLifeCycleCode(componentSchema)} ${initMethodsCode(componentSchema)} @@ -30,34 +30,24 @@ export const schema2CssCode = (schema: ProjectSchema) => { }; export const beautifyCSS = (input?: string): string => { - return input ? css_beautify(input, { indent_size: 2 }) : '' + return input ? css_beautify(input, { indent_size: 2 }) : ''; } function initStateCode(componentSchema: RootSchema | undefined) { if (componentSchema?.state) { - let states: Record = {}; - let needNotice = false; - Object.keys(componentSchema.state).forEach((item) => { - const state = componentSchema.state?.[item]; + let statesStr = 'state = {\n'; + Object.keys(componentSchema.state).forEach((key) => { + const state = componentSchema.state?.[key]; if (typeof state === 'object' && isJSExpression(state)) { - states[item] = (state as IState).originCode || state.value; // 兼容历史数据 - if (!(state as IState).originCode) { - needNotice = true; - } + statesStr += `"${key}": ${(state as IState).source || state.value},\n`; } else { - states[item] = state; // 兼容历史数据 + statesStr += `"${key}": ${typeof state === 'string' ? '"' + state + '"' : state},,\n`; } }); - if (needNotice) { - Dialog.alert({ - title: WORDS.title, - content: WORDS.irreparableState, - }); - } - return `state = ${JSON.stringify(states)}`; + statesStr += '}'; + return statesStr; } - return defaultStateCode; } function initLifeCycleCode(componentSchema: RootSchema | undefined) { @@ -100,10 +90,10 @@ function initMethodsCode(componentSchema: RootSchema | undefined) { } } -function createFunctionCode(functionName: string, functionNode: JSFunction | JSExpression) { +function createFunctionCode(functionName: string, functionNode: Method) { if (functionNode?.type === 'JSExpression' || functionNode?.type === 'JSFunction') { // 读取原始代码 - let functionCode = functionNode.originalCode; + let functionCode = functionNode.source; if (functionCode) { functionCode = functionCode.replace(/function/, ''); } else { diff --git a/packages/plugin-code-editor/src/utils/state-parser.ts b/packages/plugin-code-editor/src/utils/state-parser.ts index 439e964..872eb2b 100644 --- a/packages/plugin-code-editor/src/utils/state-parser.ts +++ b/packages/plugin-code-editor/src/utils/state-parser.ts @@ -18,7 +18,6 @@ export const stateParser = (ast: Node) => { // get state identifier or literal if (path.isIdentifier({ name: 'state' }) || path.isLiteral({ value: 'state' })) { const properties = path.container?.value?.properties; - // console.log('properties', properties); if (properties) { properties.forEach((property) => { // creat empty AST @@ -26,7 +25,7 @@ export const stateParser = (ast: Node) => { code.program.body.push(t.variableDeclaration('var', [ t.variableDeclarator(t.identifier('name'), property.value), ])); - + const codeStr = babelTransformFromAst(code).code; const compiledCode = transformJS(codeStr, defaultBabelConfig).code; if (compiledCode) { diff --git a/packages/plugin-code-editor/src/utils/transform.ts b/packages/plugin-code-editor/src/utils/transform.ts index 7f18e94..faed277 100644 --- a/packages/plugin-code-editor/src/utils/transform.ts +++ b/packages/plugin-code-editor/src/utils/transform.ts @@ -1,4 +1,3 @@ - import { transform } from './babel'; import { TransformResult } from '../types'; diff --git a/packages/plugin-components-pane/.gitignore b/packages/plugin-components-pane/.gitignore deleted file mode 100644 index 8523c97..0000000 --- a/packages/plugin-components-pane/.gitignore +++ /dev/null @@ -1,26 +0,0 @@ -# See https://help.github.com/ignore-files/ for more about ignoring files. - -# dependencies -node_modules/ - -# production -build/ -dist/ -tmp/ -lib/ -es/ - -# misc -.idea/ -.happypack -.DS_Store -*.swp -*.dia~ -.ice - -npm-debug.log* -yarn-debug.log* -yarn-error.log* -index.module.scss.d.ts - -node_modules diff --git a/packages/plugin-components-pane/build.json b/packages/plugin-components-pane/build.json index 90ae90f..36f5196 100644 --- a/packages/plugin-components-pane/build.json +++ b/packages/plugin-components-pane/build.json @@ -1,6 +1,14 @@ { "plugins": [ "build-plugin-component", + [ + "@alilc/build-plugin-alt", + { + "type": "plugin", + "inject": true, + "openUrl": "https://lowcode-engine.cn/demo/index.html?debug" + } + ], "build-plugin-fusion", [ "build-plugin-moment-locales", diff --git a/packages/plugin-components-pane/demo/demo.hbs b/packages/plugin-components-pane/demo/demo.hbs index bcca41a..f5147dd 100644 --- a/packages/plugin-components-pane/demo/demo.hbs +++ b/packages/plugin-components-pane/demo/demo.hbs @@ -11,9 +11,9 @@ - - - + + + @@ -21,4 +21,4 @@
- \ No newline at end of file + diff --git a/packages/plugin-components-pane/package.json b/packages/plugin-components-pane/package.json index 33b0615..7f12a94 100644 --- a/packages/plugin-components-pane/package.json +++ b/packages/plugin-components-pane/package.json @@ -1,6 +1,6 @@ { "name": "@alilc/lowcode-plugin-components-pane", - "version": "1.0.3", + "version": "2.0.4", "description": "低代码组件面板", "files": [ "es/", @@ -31,19 +31,20 @@ ], "license": "ISC", "devDependencies": { - "@alib/build-scripts": "^0.1.3", + "@alib/build-scripts": "^0.1.32", "@alifd/next": "^1.23.12", "@svgr/webpack": "^5.5.0", "@types/react": "^16.9.13", "@types/react-dom": "^16.9.4", "@typescript-eslint/parser": "^4.28.3", - "build-plugin-component": "^0.2.7-1", + "build-plugin-component": "^1.12.0", "build-plugin-fusion": "^0.1.0", "build-plugin-moment-locales": "^0.1.0", "eventemitter3": "^4.0.7", "f2elint": "^1.2.0", "html-webpack-plugin": "^5.3.2", "moment": "^2.29.1", + "@alilc/build-plugin-alt": "^1.0.0", "react-dom": "^16.0.0" }, "husky": { @@ -57,5 +58,10 @@ "classnames": "^2.3.1", "lodash.debounce": "^4.0.8", "react": "^16.0.0" + }, + "repository": { + "type": "git", + "url": "https://github.com/alibaba/lowcode-plugins.git", + "directory": "packages/plugin-components-pane" } } diff --git a/packages/plugin-components-pane/src/components/Component/index.tsx b/packages/plugin-components-pane/src/components/Component/index.tsx index 5fae255..681a414 100644 --- a/packages/plugin-components-pane/src/components/Component/index.tsx +++ b/packages/plugin-components-pane/src/components/Component/index.tsx @@ -2,12 +2,13 @@ import React from 'react'; import cls from 'classnames/bind'; import Svg from '../../Icon/Component'; import style from './index.module.scss'; -import { getTextReader, StandardComponentMeta } from '../../utils/transform'; +import { Text, StandardComponentMeta } from '../../utils/transform'; const cx = cls.bind(style); interface Props { data: StandardComponentMeta; + t: (input: Text) => string; } interface State { @@ -37,7 +38,7 @@ export default class Component extends React.Component { constructor(props) { super(props); - this.t = getTextReader('zh_CN'); + this.t = props.t; } renderIcon() { diff --git a/packages/plugin-components-pane/src/components/Tab/index.module.scss b/packages/plugin-components-pane/src/components/Tab/index.module.scss index b443fca..ffa7ab5 100644 --- a/packages/plugin-components-pane/src/components/Tab/index.module.scss +++ b/packages/plugin-components-pane/src/components/Tab/index.module.scss @@ -6,6 +6,7 @@ position: relative; flex-grow: 0; flex-shrink: 0; + overflow-x: scroll; .items { display: flex; diff --git a/packages/plugin-components-pane/src/index.tsx b/packages/plugin-components-pane/src/index.tsx index a6130a7..6b26af8 100644 --- a/packages/plugin-components-pane/src/index.tsx +++ b/packages/plugin-components-pane/src/index.tsx @@ -1,308 +1,29 @@ -import React from 'react'; -import { Search } from '@alifd/next'; -import { PluginProps } from '@alilc/lowcode-types'; -import cls from 'classnames/bind'; -import debounce from 'lodash.debounce'; -import style from './index.module.scss'; -import IconOfPane from './Icon'; -import Category from './components/Category'; -import List from './components/List'; -import Component from './components/Component'; -import Tab from './components/Tab'; -import ComponentManager from './store'; -import transform, { getTextReader, SortedGroups, Text } from './utils/transform'; - -const { material, common, project, event } = window.AliLowCodeEngine || {}; - -const isNewEngineVersion = !!material; - -const store = new ComponentManager(); - -const cx = cls.bind(style); - -interface ComponentPaneProps extends PluginProps { - [key: string]: any; -} - -interface ComponentPaneState { - groups: SortedGroups[]; - filter: SortedGroups[]; - keyword: string; -} - -export default class ComponentPane extends React.Component { - static displayName = 'LowcodeComponentPane'; - - static defaultProps = { - lang: 'zh_CN', - }; - - state: ComponentPaneState = { - groups: [], - filter: [], - keyword: '', - }; - - store = store; - - t: (input: Text) => string; - - getStrKeywords: (keywords: Text[]) => string; - - getFilteredComponents = debounce(() => { - const { groups = [], keyword } = this.state; - if (!keyword) { - this.setState({ - filter: groups, +import { IPublicModelPluginContext } from '@alilc/lowcode-types'; +import ComponentsPane from './pane'; +const ComponentPanelPlugin = (ctx: IPublicModelPluginContext) => { + return { + async init() { + const { skeleton, project } = ctx; + // 注册组件面板 + const componentsPane = skeleton.add({ + area: 'leftArea', + type: 'PanelDock', + name: 'componentsPane', + content: ComponentsPane, + contentProps: {}, + props: { + align: 'top', + icon: 'zujianku', + description: '组件库', + }, }); - return; - } - - const filter = groups.map((group) => ({ - ...group, - categories: group.categories - .map((category) => ({ - ...category, - components: category.components.filter((c) => { - const strTitle = this.t(c.title); - const strComponentName = this.t(c.componentName); - const strDescription = this.t(c.description); - const strKeywords = this.getStrKeywords(c.keywords); - const keyToSearch = `${strTitle}#${strComponentName}#${strDescription}#${strKeywords}`.toLowerCase(); - return keyToSearch.includes(keyword); - }), - })) - .filter((c) => c?.components?.length), - })); - - this.setState({ - filter, - }); - }, 200); - - constructor(props) { - super(props); - this.t = getTextReader(props.lang); - this.getStrKeywords = (keywords: Text[]): string => { - if (typeof keywords === 'string') { - return keywords; - } - if (keywords && Array.isArray(keywords) && keywords.length) { - return keywords.map(keyword => this.t(keyword)).join('-'); - } - return ''; - }; - } - - componentDidMount() { - const { editor } = this.props; - if (!editor) { - this.initComponentList(); - return; - } - const assets = isNewEngineVersion ? material.getAssets() : editor.get('assets'); - if (assets) { - this.initComponentList(); - } else { - console.warn('[ComponentsPane]: assets not ready, wait for assets ready event.') - } - - if (isNewEngineVersion) { - event.on('trunk.change', this.initComponentList.bind(this)); - material.onChangeAssets(this.initComponentList.bind(this)); - } else { - editor.on('trunk.change', this.initComponentList.bind(this)); - editor.once('editor.ready', this.initComponentList.bind(this)); - editor.on('designer.incrementalAssetsReady', this.initComponentList.bind(this)); - } - } - - /** - * 初始化组件列表 - * TODO: 无副作用,可多次执行 - */ - initComponentList() { - const { editor } = this.props; - const rawData = isNewEngineVersion ? material.getAssets() : editor.get('assets'); - - const meta = transform(rawData); - - const { groups, snippets } = meta; - - this.store.setSnippets(snippets); - - this.setState({ - groups, - filter: groups, - }); - } - - registerAdditive = (shell: HTMLDivElement | null) => { - if (!shell || shell.dataset.registered) { - return; - } - - function getSnippetId(elem: any) { - if (!elem) { - return null; - } - while (shell !== elem) { - if (elem.classList.contains('snippet')) { - return elem.dataset.id; - } - elem = elem.parentNode; - } - return null; - } - - const { editor } = this.props; - const designer = !isNewEngineVersion ? editor?.get('designer') : null; - const _dragon = isNewEngineVersion ? common.designerCabin.dragon : designer?.dragon; - if (!_dragon || (!isNewEngineVersion && !designer)) { - return; - } - - // eslint-disable-next-line - const click = (e: Event) => {}; - - shell.addEventListener('click', click); - - _dragon.from(shell, (e: Event) => { - const doc = isNewEngineVersion ? project.getCurrentDocument() : designer?.currentDocument; - const id = getSnippetId(e.target); - if (!doc || !id) { - return false; - } - - const dragTarget = { - type: 'nodedata', - data: this.store.getSnippetById(id), - }; - - return dragTarget; - }); - - shell.dataset.registered = 'true'; + componentsPane?.disable?.(); + project.onSimulatorRendererReady(() => { + componentsPane?.enable?.(); + }) + }, }; - - handleSearch = (keyword = '') => { - this.setState({ - keyword: keyword.toLowerCase(), - }); - this.getFilteredComponents(); - }; - - renderEmptyContent() { - return ( -
- -
暂无组件,请在物料站点添加
-
- ) - } - - renderContent() { - const { filter = [], keyword } = this.state; - const hasContent = filter.filter(item => { - return item?.categories?.filter(category => { - return category?.components?.length; - }).length; - }).length; - if (!hasContent) { - return this.renderEmptyContent(); - } - if (keyword) { - return ( -
- {filter.map((group) => { - const { categories } = group; - {return categories.map((category) => { - const { components } = category; - const cname = this.t(category.name); - return ( - - - {components.map((component) => { - const { componentName, snippets = [] } = component; - return snippets.filter(snippet => snippet.id).map(snippet => { - return ( - - ); - }); - })} - - - ); - })} - })} -
- ) - } - return ( - - {filter.map((group) => { - const { categories } = group; - return ( - -
- {categories.map((category) => { - const { components } = category; - const cname = this.t(category.name); - return ( - - - {components.map((component) => { - const { componentName, snippets = [] } = component; - return snippets.filter(snippet => snippet.id).map(snippet => { - return ( - - ); - }); - })} - - - ); - })} -
-
- ); - })} -
- ); - } - - render() { - return ( -
-
- -
- {this.renderContent()} -
- ); - } } +ComponentPanelPlugin.pluginName = 'ComponentPanelPlugin'; +export default ComponentPanelPlugin; -export const PaneIcon = IconOfPane; diff --git a/packages/plugin-components-pane/src/index.module.scss b/packages/plugin-components-pane/src/pane/index.module.scss similarity index 87% rename from packages/plugin-components-pane/src/index.module.scss rename to packages/plugin-components-pane/src/pane/index.module.scss index 2d8c140..d63baaf 100644 --- a/packages/plugin-components-pane/src/index.module.scss +++ b/packages/plugin-components-pane/src/pane/index.module.scss @@ -33,4 +33,9 @@ line-height: 2; } } + + > .filtered-content { + overflow-y: overlay; + overflow-x: hidden; + } } diff --git a/packages/plugin-components-pane/src/pane/index.tsx b/packages/plugin-components-pane/src/pane/index.tsx new file mode 100644 index 0000000..eacb5db --- /dev/null +++ b/packages/plugin-components-pane/src/pane/index.tsx @@ -0,0 +1,321 @@ +import React from 'react'; +import { Search } from '@alifd/next'; +import { PluginProps } from '@alilc/lowcode-types'; +import cls from 'classnames/bind'; +import debounce from 'lodash.debounce'; +import style from './index.module.scss'; +import IconOfPane from '../Icon'; +import Category from '../components/Category'; +import List from '../components/List'; +import Component from '../components/Component'; +import Tab from '../components/Tab'; +import ComponentManager from '../store'; +import transform, { getTextReader, SortedGroups, Text, StandardComponentMeta, SnippetMeta, createI18n } from '../utils/transform'; + +const { material, common, project, event } = window.AliLowCodeEngine || {}; + +const isNewEngineVersion = !!material; + +const store = new ComponentManager(); + +const cx = cls.bind(style); + +interface ComponentPaneProps extends PluginProps { + [key: string]: any; +} + +interface ComponentPaneState { + groups: SortedGroups[]; + filter: SortedGroups[]; + keyword: string; +} + +export default class ComponentPane extends React.Component { + static displayName = 'LowcodeComponentPane'; + + static defaultProps = { + lang: 'zh_CN', + }; + + state: ComponentPaneState = { + groups: [], + filter: [], + keyword: '', + }; + + store = store; + + t: (input: Text) => string; + + getStrKeywords: (keywords: Text[]) => string; + + getKeyToSearch (c:StandardComponentMeta|SnippetMeta){ + const strTitle = this.t(c.title); + const strComponentName = this.t((c as SnippetMeta).schema?.componentName); + const strDescription = "description" in c ? this.t(c.description):''; + const strKeywords = "keywords" in c ? this.getStrKeywords(c.keywords||[]):''; + return `${strTitle}#${strComponentName}#${strDescription}#${strKeywords}`.toLowerCase(); + } + + getFilteredComponents = debounce(() => { + const { groups = [], keyword } = this.state; + if (!keyword) { + this.setState({ + filter: groups, + }); + return; + } + + + + const filter = groups.map((group) => ({ + ...group, + categories: group.categories + .map((category) => ({ + ...category, + components: category.components.filter((c) => { + let keyToSearch = this.getKeyToSearch(c); + if(c.snippets){ + c.snippets.map((item)=>{ + keyToSearch += `_${this.getKeyToSearch(item)}` + }) + } + return keyToSearch.includes(keyword); + }), + })) + .filter((c) => c?.components?.length), + })); + + this.setState({ + filter, + }); + }, 200); + + constructor(props) { + super(props); + this.t = getTextReader(props.lang); + this.getStrKeywords = (keywords: Text[]): string => { + if (typeof keywords === 'string') { + return keywords; + } + if (keywords && Array.isArray(keywords) && keywords.length) { + return keywords.map(keyword => this.t(keyword)).join('-'); + } + return ''; + }; + } + + componentDidMount() { + const { editor } = this.props; + if (!editor) { + this.initComponentList(); + return; + } + const assets = isNewEngineVersion ? material.getAssets() : editor.get('assets'); + if (assets) { + this.initComponentList(); + } else { + console.warn('[ComponentsPane]: assets not ready, wait for assets ready event.') + } + + if (isNewEngineVersion) { + event.on('trunk.change', this.initComponentList.bind(this)); + material.onChangeAssets(this.initComponentList.bind(this)); + } else { + editor.on('trunk.change', this.initComponentList.bind(this)); + editor.once('editor.ready', this.initComponentList.bind(this)); + editor.on('designer.incrementalAssetsReady', this.initComponentList.bind(this)); + } + } + + /** + * 初始化组件列表 + * TODO: 无副作用,可多次执行 + */ + initComponentList() { + const { editor } = this.props; + const rawData = isNewEngineVersion ? material.getAssets() : editor.get('assets'); + + const meta = transform(rawData, this.t); + + const { groups, snippets } = meta; + + this.store.setSnippets(snippets); + + this.setState({ + groups, + filter: groups, + }); + } + + registerAdditive = (shell: HTMLDivElement | null) => { + if (!shell || shell.dataset.registered) { + return; + } + + function getSnippetId(elem: any) { + if (!elem) { + return null; + } + while (shell !== elem) { + if (elem.classList.contains('snippet')) { + return elem.dataset.id; + } + elem = elem.parentNode; + } + return null; + } + + const { editor } = this.props; + const designer = !isNewEngineVersion ? editor?.get('designer') : null; + const _dragon = isNewEngineVersion ? common.designerCabin.dragon : designer?.dragon; + if (!_dragon || (!isNewEngineVersion && !designer)) { + return; + } + + // eslint-disable-next-line + const click = (e: Event) => {}; + + shell.addEventListener('click', click); + + _dragon.from(shell, (e: Event) => { + const doc = isNewEngineVersion ? project.getCurrentDocument() : designer?.currentDocument; + const id = getSnippetId(e.target); + if (!doc || !id) { + return false; + } + + const dragTarget = { + type: 'nodedata', + data: this.store.getSnippetById(id), + }; + + return dragTarget; + }); + + shell.dataset.registered = 'true'; + }; + + handleSearch = (keyword = '') => { + this.setState({ + keyword: keyword.toLowerCase(), + }); + this.getFilteredComponents(); + }; + + renderEmptyContent() { + return ( +
+ +
{this.t(createI18n('暂无组件,请在物料站点添加', 'No components, please add materials'))}
+
+ ) + } + + renderContent() { + const { filter = [], keyword } = this.state; + const hasContent = filter.filter(item => { + return item?.categories?.filter(category => { + return category?.components?.length; + }).length; + }).length; + if (!hasContent) { + return this.renderEmptyContent(); + } + if (keyword) { + return ( +
+ {filter.map((group) => { + const { categories } = group; + {return categories.map((category) => { + const { components } = category; + const cname = this.t(category.name); + return ( + + + {components.map((component) => { + const { componentName, snippets = [] } = component; + return snippets.filter(snippet => snippet.id && this.getKeyToSearch(snippet).toLowerCase().includes(keyword)).map(snippet => { + return ( + + ); + }); + })} + + + ); + })} + })} +
+ ) + } + return ( + + {filter.map((group) => { + const { categories } = group; + return ( + +
+ {categories.map((category) => { + const { components } = category; + const cname = this.t(category.name); + return ( + + + {components.map((component) => { + const { componentName, snippets = [] } = component; + return snippets.filter(snippet => snippet.id).map(snippet => { + return ( + + ); + }); + })} + + + ); + })} +
+
+ ); + })} +
+ ); + } + + render() { + return ( +
+
+ +
+ {this.renderContent()} +
+ ); + } +} + +export const PaneIcon = IconOfPane; diff --git a/packages/plugin-components-pane/src/utils/transform.ts b/packages/plugin-components-pane/src/utils/transform.ts index 7406842..69f3f59 100644 --- a/packages/plugin-components-pane/src/utils/transform.ts +++ b/packages/plugin-components-pane/src/utils/transform.ts @@ -51,8 +51,7 @@ export interface IgnoreComponents { [key: string]: string[]; } -export default function transform(raw: any) { - const t = getTextReader('zh_CN'); +export default function transform(raw: any, t: (input: Text) => string) { let groupList: Text[] = []; let categoryList: Text[] = []; let ignoreComponents: IgnoreComponents = {}; @@ -136,7 +135,7 @@ export default function transform(raw: any) { const { sort } = stdComponent; const { group, category, priority = 0 } = sort; - const hasGroup = textExistIn(group, groupList); + const hasGroup = textExistIn(group, groupList, t); if (hasGroup) { if (!map[t(group)]) { @@ -319,7 +318,6 @@ export function pipe(arr: any[]) { }; } -export function textExistIn(text: Text, arr: Text[]) { - const t = getTextReader('zh_CN'); +export function textExistIn(text: Text, arr: Text[], t: (input: Text) => string) { return !!arr.find((item) => t(item) === t(text)); } diff --git a/packages/plugin-datasource-pane/.gitignore b/packages/plugin-datasource-pane/.gitignore deleted file mode 100644 index 77dfbd1..0000000 --- a/packages/plugin-datasource-pane/.gitignore +++ /dev/null @@ -1,104 +0,0 @@ -# project custom -build -dist -lib -es -package-lock.json -yarn.lock -deploy-space/packages -deploy-space/.env - - -# IDE -.vscode -.idea - -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -lerna-debug.log* - -# Runtime data -pids -*.pid -*.seed -*.pid.lock - - -# Directory for instrumented libs generated by jscoverage/JSCover -lib-cov - -# Coverage directory used by tools like istanbul -coverage - -# nyc test coverage -.nyc_output - -# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) -.grunt - -# Bower dependency directory (https://bower.io/) -bower_components - -# node-waf configuration -.lock-wscript - -# Compiled binary addons (https://nodejs.org/api/addons.html) -build/Release -lib - -# Dependency directories -node_modules/ -jspm_packages/ - -# TypeScript v1 declaration files -typings/ - -# Optional npm cache directory -.npm - -# Optional eslint cache -.eslintcache - -# Optional REPL history -.node_repl_history - -# Output of 'npm pack' -*.tgz - -# Yarn Integrity file -.yarn-integrity - -# dotenv environment variables file -.env -.env.test - -# parcel-bundler cache (https://parceljs.org/) -.cache - -# next.js build output -.next - -# nuxt.js build output -.nuxt - -# vuepress build output -.vuepress/dist - -# Serverless directories -.serverless/ - -# FuseBox cache -.fusebox/ - -# DynamoDB Local files -.dynamodb/ - -# mac config files -.DS_Store - -# codealike -codealike.json diff --git a/packages/plugin-datasource-pane/CHANGELOG.md b/packages/plugin-datasource-pane/CHANGELOG.md new file mode 100644 index 0000000..e9adbe0 --- /dev/null +++ b/packages/plugin-datasource-pane/CHANGELOG.md @@ -0,0 +1,3 @@ +# 1.0.5 + +- fix https://github.com/alibaba/lowcode-engine/issues/466 diff --git a/packages/plugin-datasource-pane/README.md b/packages/plugin-datasource-pane/README.md index 6fd408d..42a853b 100644 --- a/packages/plugin-datasource-pane/README.md +++ b/packages/plugin-datasource-pane/README.md @@ -89,7 +89,104 @@ type DataSourceType = { ## 定制导入插件 -WIP +### 导入组件示例 + +导入组件引入方式参考 pluginProps +``` +import { DataSourceImportPluginTest } from './DataSourceImportPluginTest'; + +{ + ... + importPlugins: [ + { + name: '这里可以导入', + title: '这里可以导入', + component: DataSourceImportPluginTest, + componentProps: { + onImport: (res) => { + console.log('ceshi ') + + }, + onCancel: () => { + console.log('ceshi2 ') + + } + } + } + ], + exportPlugins: [], + formComponents: { }, + ... +} + +// DataSourceImportPluginTest.jsx + +/** + * 源码导入插件 + * @todo editor 关联 types,并提供详细的出错信息 + */ +import React, { PureComponent } from 'react'; +import { Button } from '@alifd/next'; +import _noop from 'lodash/noop'; +import _isArray from 'lodash/isArray'; +import _last from 'lodash/last'; +import _isPlainObject from 'lodash/isPlainObject'; +import { RuntimeDataSourceConfig as DataSourceConfig } from '@alilc/lowcode-datasource-types'; +import { JSONSchema6 } from 'json-schema'; +import type { ComponentType } from 'react'; +export interface DataSourceType { + type: string; + schema: JSONSchema6; + plugin?: ComponentType; +} +export interface DataSourcePaneImportPluginComponentProps { + dataSourceTypes: DataSourceType[]; + onImport?: (dataSourceList: DataSourceConfig[]) => void; + onCancel?: () => void; +} +export interface DataSourceImportPluginCodeProps + extends DataSourcePaneImportPluginComponentProps { + defaultValue?: DataSourceConfig[]; +} +export interface DataSourceImportPluginCodeState { + code: string; + isCodeValid: boolean; +} +export class DataSourceImportPluginCode extends PureComponent< + DataSourceImportPluginCodeProps, + DataSourceImportPluginCodeState +> { + handleComplete = () => { + console.log('确认') + }; + onCancel = () => { + console.log('取消') + }; + render() { + return ( +
+ 此处代码可以自定义 +

+ + +

+
+ ); + } +} + +``` + +具体的 component 可参考 DataSourceImportPluginCode + [查看](https://github.com/alibaba/lowcode-plugins/blob/main/packages/plugin-datasource-pane/src/components/DataSourceImportPluginCode/DataSourceImportPluginCode.tsx) + + demo 截图 + ![Alt](https://user-images.githubusercontent.com/14235113/186659341-dff511e8-f032-423c-8be7-e0cc281f3964.png) + + + ## 定制导出插件 @@ -126,4 +223,4 @@ WIP # 参考 -* [搭建协议规范](https://yuque.antfin-inc.com/mo/spec/spec-low-code-building-schema) +* [搭建协议规范](https://lowcode-engine.cn/lowcode) \ No newline at end of file diff --git a/packages/plugin-datasource-pane/build.js b/packages/plugin-datasource-pane/build.js new file mode 100644 index 0000000..fc2fa16 --- /dev/null +++ b/packages/plugin-datasource-pane/build.js @@ -0,0 +1,23 @@ +module.exports = { + plugins: [ + 'build-plugin-component', + 'build-plugin-fusion', + [ + 'build-plugin-moment-locales', + { + locales: ['zh-cn'], + }, + ], + [ + '@alilc/build-plugin-alt', + { + type: 'plugin', + // 开启注入调试模式,see:https://www.yuque.com/lce/doc/ulvlkz + inject: true, + // 配置要打开的页面,在注入调试模式下,不配置此项的话不会打开浏览器 + // 支持直接使用官方 demo 项目:https://lowcode-engine.cn/demo/index.html + openUrl: 'https://lowcode-engine.cn/demo/index.html?debug', + }, + ], + ], +}; diff --git a/packages/plugin-datasource-pane/build.plugin.js b/packages/plugin-datasource-pane/build.plugin.js deleted file mode 100644 index df1825d..0000000 --- a/packages/plugin-datasource-pane/build.plugin.js +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = ({ onGetWebpackConfig }) => { - // see: https://github.com/ice-lab/build-scripts#%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91 - onGetWebpackConfig((config) => { - console.log(config); - config.merge({ - node: { - fs: 'empty', - }, - }); - return config; - }); -}; diff --git a/packages/plugin-datasource-pane/package.json b/packages/plugin-datasource-pane/package.json index 1629058..898bd5f 100644 --- a/packages/plugin-datasource-pane/package.json +++ b/packages/plugin-datasource-pane/package.json @@ -1,14 +1,14 @@ { "name": "@alilc/lowcode-plugin-datasource-pane", - "version": "1.0.2", + "version": "1.0.11", "description": "低代码引擎数据源面板", "main": "lib/index.js", "files": [ "lib" ], "scripts": { - "start": "build-scripts start", - "build": "build-scripts build", + "start": "build-scripts start --config build.js", + "build": "build-scripts build --config build.js", "test": "ava", "test:snapshot": "ava --update-snapshots" }, @@ -26,8 +26,9 @@ "devDependencies": { "@alib/build-scripts": "^0.1.32", "@alifd/theme-lowcode-light": "^0.2.1", - "@alilc/build-plugin-alt": "beta", + "@alilc/build-plugin-alt": "^1.2.2", "@alilc/lowcode-engine": "beta", + "@alilc/lowcode-shell": "^1.0.0", "@alilc/lowcode-types": "beta", "@types/babel-types": "^7.0.9", "@types/babel__preset-env": "^7.9.2", @@ -35,6 +36,7 @@ "@types/react-dom": "^16.9.4", "@types/traverse": "^0.6.32", "build-plugin-fusion": "^0.1.9", + "build-plugin-moment-locales": "^0.1.3", "monaco-editor": "^0.21.0" }, "peerDependencies": { @@ -42,11 +44,24 @@ "moment": "^2.29.1" }, "dependencies": { + "@alifd/next": "^1.25.13", + "@babel/core": "^7.15.8", + "@babel/parser": "^7.15.8", + "@babel/preset-env": "^7.15.8", + "@babel/preset-react": "^7.14.5", + "@babel/standalone": "^7.15.8", + "@babel/traverse": "^7.15.4", + "@babel/types": "^7.15.6", + "babel-plugin-transform-remove-strict-mode": "^0.0.2", + "classnames": "^2.3.1", + "js-beautify": "^1.14.0", + "react": "^16.8.1", + "react-dom": "^16.8.1", "@alilc/lowcode-plugin-base-monaco-editor": "^1.0.0", "@alilc/lowcode-types": "^1.0.0-beta.3", - "@formily/core": "^2.0.0-rc.3", - "@formily/next": "^2.0.0-rc.3", - "@formily/react": "^2.0.0-rc.3", + "@formily/core": "^2.1.12", + "@formily/next": "^2.1.12", + "@formily/react": "^2.1.12", "@types/lodash": "^4.14.178", "@xstate/react": "^1.6.1", "ajv": "^6.12.4", @@ -59,5 +74,10 @@ "traverse": "^0.6.6", "xstate": "^4.26.0" }, - "homepage": "https://unpkg.com/@alilc/lowcode-plugin-datasource-pane@1.0.2/build/index.html" + "homepage": "https://unpkg.com/@alilc/lowcode-plugin-datasource-pane@1.0.11/build/index.html", + "repository": { + "type": "git", + "url": "https://github.com/alibaba/lowcode-plugins.git", + "directory": "packages/plugin-datasource-pane" + } } diff --git a/packages/plugin-datasource-pane/src/classname-setter/index.tsx b/packages/plugin-datasource-pane/src/classname-setter/index.tsx index f515751..c86e79f 100644 --- a/packages/plugin-datasource-pane/src/classname-setter/index.tsx +++ b/packages/plugin-datasource-pane/src/classname-setter/index.tsx @@ -1,6 +1,7 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { Select } from '@alifd/next'; +import { common } from '@alilc/lowcode-engine'; interface Color { rgb: any; @@ -27,7 +28,7 @@ export default class ClassNameView extends PureComponent { getClassNameList = () => { const { project } = this.context; - const schema = project.exportSchema(); + const schema = project.exportSchema(common.designerCabin.TransformStage.Save); const { css } = schema.componentsTree[0]; const classNameList = []; const re = /\.?\w+[^{]+\{[^}]*\}/g; diff --git a/packages/plugin-datasource-pane/src/components/ClassNameView/ClassNameView.tsx b/packages/plugin-datasource-pane/src/components/ClassNameView/ClassNameView.tsx index ece734e..2e5f1da 100644 --- a/packages/plugin-datasource-pane/src/components/ClassNameView/ClassNameView.tsx +++ b/packages/plugin-datasource-pane/src/components/ClassNameView/ClassNameView.tsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react'; import PropTypes from 'prop-types'; import { Select } from '@alifd/next'; - +import { common } from '@alilc/lowcode-engine'; export interface PluginProps { value: string; @@ -30,7 +30,7 @@ export default class ClassNameView extends PureComponent { const { project } = this.context; - const schema = project.exportSchema(); + const schema = project.exportSchema(common.designerCabin.TransformStage.Save); const { css } = schema.componentsTree[0]; const classNameList: string[] = []; const re = /\.?\w+[^{]+\{[^}]*\}/g; diff --git a/packages/plugin-datasource-pane/src/components/DataSourceExport/DataSourceExport.tsx b/packages/plugin-datasource-pane/src/components/DataSourceExport/DataSourceExport.tsx index 40daaa8..bff557a 100644 --- a/packages/plugin-datasource-pane/src/components/DataSourceExport/DataSourceExport.tsx +++ b/packages/plugin-datasource-pane/src/components/DataSourceExport/DataSourceExport.tsx @@ -18,47 +18,44 @@ import { generateClassName } from '../../utils/misc'; // import './import-plugins/code.scss'; export interface DataSourceExportProps { -dataSourceList: DataSourceConfig[]; -dataSourceTypes: DataSourceType[]; + dataSourceList: DataSourceConfig[]; + dataSourceTypes: DataSourceType[]; } export interface DataSourceExportState { -code: string; -isCodeValid: boolean; + code: string; + isCodeValid: boolean; } -export class DataSourceExport extends PureComponent< -DataSourceExportProps, -DataSourceExportState -> { -static defaultProps = { +export class DataSourceExport extends PureComponent { + static defaultProps = { dataSourceList: [], -}; + }; -state = { + state = { code: '', isCodeValid: true, -}; + }; -submit = () => { + submit = () => { return new Promise((resolve, reject) => { const { isCodeValid, code } = this.state; if (isCodeValid) reject(new Error('格式有误')); resolve({ schema: code }); }); -}; + }; -private monacoRef: any; + private monacoRef: any; -constructor(props: DataSourceExportProps) { + constructor(props: DataSourceExportProps) { super(props); - this.state.code = JSON.stringify(this.deriveValue(this.props.dataSourceList)); + this.state.code = JSON.stringify(this.deriveValue(this.props.dataSourceList), null, 2); this.handleEditorDidMount = this.handleEditorDidMount.bind(this); this.handleEditorChange = this.handleEditorChange.bind(this); -} + } -deriveValue = (value: any) => { + deriveValue = (value: any) => { const { dataSourceTypes } = this.props; if (!_isArray(dataSourceTypes) || dataSourceTypes.length === 0) return []; @@ -74,36 +71,50 @@ deriveValue = (value: any) => { const ajv = new Ajv(); return (result as DataSourceConfig[]).filter((dataSource) => { - if (!dataSource.type) return false; - const dataSourceType = dataSourceTypes.find((type) => type.type === dataSource.type); - if (!dataSourceType) return false; - return ajv.validate(dataSourceType.schema, dataSource); + if (!dataSource.type) return false; + + const dataSourceType = dataSourceTypes.find((type) => type.type === dataSource.type); + + if (!dataSourceType) return false; + + // 向下兼容 + if (dataSourceType.schema) { + // 校验失败的数据源,给予用户提示 + const validate = ajv.compile(dataSourceType.schema) + const valid = validate(dataSource) + if (!valid) console.warn(validate.errors) + return valid + } else { + // 用户不传入 schema 校验规则,默认返回 true + return true + } }); -}; + }; -handleCopy = () => { + handleCopy = () => { Message.success('粘贴成功!'); -}; + }; -handleEditorChange = (newValue) => { + handleEditorChange = (newValue) => { if (this.monacoRef) { - if (!this.monacoRef.getModelMarkers().find((marker: editor.IMarker) => marker.owner === 'json')) { + if (!this.monacoRef.getModelMarkers().find((marker: editor.IMarker) => marker.owner === 'json')) { this.setState({ isCodeValid: true, code: newValue }); + } } - } -}; + }; -handleEditorDidMount = (isFullscreen, editor, monaco) => { - this.monacoRef = monaco?.editor; -}; + handleEditorDidMount = (editor: MonacoEditor, monaco: MonacoEditor) => { + this.monacoRef = editor?.editor; + }; -handleReset = () => { -if (this.monacoRef) { - this.monacoRef.getModels()?.[0]?.setValue?.(code); -} -}; + handleReset = () => { + const code = JSON.stringify(this.deriveValue(this.props.dataSourceList), null, 2) + if (this.monacoRef) { + this.monacoRef.getModels()?.[0]?.setValue?.(code); + } + }; -render() { + render() { const { code, isCodeValid } = this.state; // @todo @@ -134,5 +145,5 @@ render() {

); -} + } } diff --git a/packages/plugin-datasource-pane/src/components/DataSourceForm/DataSourceForm.tsx b/packages/plugin-datasource-pane/src/components/DataSourceForm/DataSourceForm.tsx index 82376ad..4597b79 100644 --- a/packages/plugin-datasource-pane/src/components/DataSourceForm/DataSourceForm.tsx +++ b/packages/plugin-datasource-pane/src/components/DataSourceForm/DataSourceForm.tsx @@ -1,6 +1,6 @@ // @todo schema default import React, { PureComponent } from 'react'; -import { createForm, registerValidateRules } from '@formily/core'; +import { createForm, registerValidateRules, Form as FormilyForm } from '@formily/core'; import { createSchemaField } from '@formily/react'; import { Space, @@ -34,6 +34,7 @@ import { generateClassName } from '../../utils/misc'; import { filterXDisplay } from '../../utils/filter-x-display'; import { DataSourceFormProps, DataSourceFormMode } from '../../types'; +import { isJSExpression } from '@alilc/lowcode-types'; const SCHEMA = { type: 'object', @@ -41,11 +42,7 @@ const SCHEMA = { type: { title: '类型', type: 'string', - editable: false, readOnly: true, - hidden: true, - display: 'hidden', - visible: false, 'x-decorator': 'FormItem', 'x-component-props': { // labelWidth: 300, @@ -79,8 +76,8 @@ const SCHEMA = { }, params: { title: '请求参数', - type: 'object', - default: {}, + type: 'array', + default: [], 'x-decorator-props': { addonAfter: , }, @@ -89,7 +86,7 @@ const SCHEMA = { type: 'string', title: '请求方法', required: true, - enum: ['GET', 'POST', 'OPTIONS', 'PUT', 'DELETE'].map((i) => ({ + enum: ['GET', 'POST', 'OPTIONS', 'PUT', 'PATCH', 'DELETE'].map((i) => ({ label: i, value: i, })), @@ -117,9 +114,9 @@ const SCHEMA = { }, }, headers: { - type: 'object', + type: 'array', title: '请求头信息', - default: {}, + default: [], 'x-decorator-props': { addonAfter: , }, @@ -182,9 +179,34 @@ const SCHEMA = { /** * 通过是否存在 ID 来决定读写状态 */ -export class DataSourceForm extends PureComponent { +export class DataSourceForm extends PureComponent { + constructor (props) { + super(props) + + this.state = { + form: this.createForm() + } + } + + createForm(): FormilyForm { + return createForm({ + initialValues: this.deriveInitialData(this.props.dataSource), + }) + } + + componentDidUpdate(prevProps: DataSourceFormProps) { + const type = this.props.dataSourceType?.type; + const ptype = prevProps.dataSourceType?.type; + // dataSource 或 dataSourceType.type 变了,需要更新 form,界面刷新 + if (this.props.dataSource !== prevProps.dataSource || type !== ptype) { + this.setState({ + form: this.createForm() + }) + } + } + submit = () => { - return this.form + return this.state.form .submit() .then((formData: any) => { if (_isArray(_get(formData, 'options.params'))) { @@ -218,9 +240,8 @@ export class DataSourceForm extends PureComponent { deriveInitialData = (dataSource: object = {}) => { const { dataSourceType } = this.props; const result: any = _cloneDeep(dataSource); - // TODO - if (_isPlainObject(_get(result, 'options.params'))) { + if (_isPlainObject(_get(result, 'options.params')) && !isJSExpression(_get(result, 'options.params'))) { result.options.params = Object.keys(result.options.params).reduce( (acc: any, cur: any) => { acc.push({ @@ -232,7 +253,7 @@ export class DataSourceForm extends PureComponent { [], ); } - if (_isPlainObject(_get(result, 'options.headers'))) { + if (_isPlainObject(_get(result, 'options.headers')) && !isJSExpression(_get(result, 'options.headers'))) { result.options.headers = Object.keys(result.options.headers).reduce( (acc: any, cur: any) => { acc.push({ @@ -296,25 +317,18 @@ export class DataSourceForm extends PureComponent { }, 'x-decorator': 'FormItem', items: { - type: 'void', - 'x-component': 'Space', + type: 'object', + // 'x-component': 'Space', properties: { - sort: { - type: 'void', - 'x-decorator': 'FormItem', - 'x-component': 'ArrayItems.SortHandle', - }, - params: { + space: { type: 'void', 'x-component': 'Space', - 'x-component-props': { - /* direction: "vertical", - align: "start", - style: { - alignItems: "flex-start", - }, */ - }, properties: { + sort: { + type: 'void', + 'x-decorator': 'FormItem', + 'x-component': 'ArrayItems.SortHandle', + }, name: { title: '', type: 'string', @@ -334,19 +348,19 @@ export class DataSourceForm extends PureComponent { // type: "string", 'x-component': 'ParamValue', 'x-component-props': { - // types: ["string", "boolean", "expression", "number"], - // placeholder: "value", + types: ['string', 'boolean', 'expression'], + placeholder: 'value', }, }, + remove: { + type: 'void', + 'x-decorator': 'FormItem', + 'x-component': 'ArrayItems.Remove', + }, + }, }, - }, - remove: { - type: 'void', - 'x-decorator': 'FormItem', - 'x-component': 'ArrayItems.Remove', }, }, - }, properties: { addition: { type: 'void', @@ -397,7 +411,7 @@ export class DataSourceForm extends PureComponent { // type: "string", 'x-component': 'ParamValue', 'x-component-props': { - // types: ["string", "boolean", "expression", "number"], + types: ['string', 'boolean', 'expression'], // placeholder: "value", }, }, @@ -466,10 +480,6 @@ export class DataSourceForm extends PureComponent { }; }; - form = createForm({ - initialValues: this.deriveInitialData(this.props.dataSource), - }); - render() { const SchemaField = createSchemaField({ components: { @@ -492,7 +502,7 @@ export class DataSourceForm extends PureComponent { return (
-
+ { return arg; diff --git a/packages/plugin-datasource-pane/src/components/DataSourceImport/DataSourceImport.tsx b/packages/plugin-datasource-pane/src/components/DataSourceImport/DataSourceImport.tsx index a92125a..cc69379 100644 --- a/packages/plugin-datasource-pane/src/components/DataSourceImport/DataSourceImport.tsx +++ b/packages/plugin-datasource-pane/src/components/DataSourceImport/DataSourceImport.tsx @@ -32,9 +32,18 @@ export class DataSourceImport extends PureComponent< static defaultProps = { defaultValue: [ { - type: 'http', - id: 'test', - }, + type: 'fetch', + isInit: false, + options: { + method: 'GET', + isCors: true, + timeout: 5000, + uri: '/info', + params: {}, + headers: {} + }, + id: 'info' + } ], }; @@ -47,8 +56,10 @@ export class DataSourceImport extends PureComponent< return new Promise((resolve, reject) => { const { isCodeValid, code } = this.state; - if (isCodeValid) reject(new Error('格式有误')); - resolve({ schema: code }); + if (!isCodeValid) reject(new Error('导入格式有误')); + + // 只 resolve 通过 schema 校验的数据 + resolve(this.deriveValue(JSON.parse(code))); }); }; @@ -56,7 +67,7 @@ export class DataSourceImport extends PureComponent< constructor(props: DataSourceImportProps) { super(props); - this.state.code = JSON.stringify(this.deriveValue(this.props.defaultValue)); + this.state.code = JSON.stringify(this.deriveValue(this.props.defaultValue), null, 2); this.handleEditorDidMount = this.handleEditorDidMount.bind(this); this.handleEditorChange = this.handleEditorChange.bind(this); this.handleComplete = this.handleComplete.bind(this); @@ -79,14 +90,31 @@ export class DataSourceImport extends PureComponent< return (result as DataSourceConfig[]).filter((dataSource) => { if (!dataSource.type) return false; + const dataSourceType = dataSourceTypes.find( (type) => type.type === dataSource.type, ); + if (!dataSourceType) return false; - return ajv.validate(dataSourceType.schema, dataSource); + + // 向下兼容 + if (dataSourceType.schema) { + // 校验失败的数据源,给予用户提示 + const validate = ajv.compile(dataSourceType.schema) + const valid = validate(dataSource) + if (!valid) console.warn(validate.errors) + return valid + } else { + // 用户不传入 schema 校验规则,默认返回 true + return true + } }); }; + /** + * 看代码是未使用到 + * @deprecated + */ handleComplete = () => { if (this.monacoRef) { if ( @@ -116,8 +144,8 @@ export class DataSourceImport extends PureComponent< } }; - handleEditorDidMount = (isFullscreen: boolean, editor: MonacoEditor, monaco: MonacoEditor) => { - this.monacoRef = monaco?.editor; + handleEditorDidMount = (editor: MonacoEditor, monaco: MonacoEditor) => { + this.monacoRef = editor?.editor; }; render() { diff --git a/packages/plugin-datasource-pane/src/components/DataSourceImportPluginCode/DataSourceImportPluginCode.tsx b/packages/plugin-datasource-pane/src/components/DataSourceImportPluginCode/DataSourceImportPluginCode.tsx index 77cc726..52dfa8f 100644 --- a/packages/plugin-datasource-pane/src/components/DataSourceImportPluginCode/DataSourceImportPluginCode.tsx +++ b/packages/plugin-datasource-pane/src/components/DataSourceImportPluginCode/DataSourceImportPluginCode.tsx @@ -3,7 +3,7 @@ * @todo editor 关联 types,并提供详细的出错信息 */ import React, { PureComponent } from 'react'; -import { Button } from '@alifd/next'; +import { Button, Message } from '@alifd/next'; import _noop from 'lodash/noop'; import _isArray from 'lodash/isArray'; import _last from 'lodash/last'; @@ -44,6 +44,20 @@ export class DataSourceImportPluginCode extends PureComponent< isCodeValid: true, }; + /* @author daifuyang + ** @description:修复默认panel ref没有submit方法 + */ + submit = () => { + return new Promise((resolve, reject) => { + const { isCodeValid, code } = this.state; + + if (!isCodeValid) reject(new Error('导入格式有误')); + + // 只 resolve 通过 schema 校验的数据 + resolve(this.deriveValue(JSON.parse(code))); + }); + }; + private monacoRef: any; constructor(props: DataSourceImportPluginCodeProps) { @@ -75,7 +89,8 @@ export class DataSourceImportPluginCode extends PureComponent< (type) => type.type === dataSource.type, ); if (!dataSourceType) return false; - return ajv.validate(dataSourceType.schema, dataSource); + // 处理下默认为空的情况,向下兼容 + return ajv.validate(dataSourceType.schema || {}, dataSource); }); }; @@ -86,10 +101,11 @@ export class DataSourceImportPluginCode extends PureComponent< .getModelMarkers() .find((marker: editor.IMarker) => marker.owner === 'json') ) { + Message.success("检验成功,点击右上方确定完成导入!") this.setState({ isCodeValid: true }); - const model: any = _last(this.monacoRef.getModels()); - if (!model) return; - this.props.onImport?.(this.deriveValue(JSON.parse(model.getValue()))); + // const model: any = _last(this.monacoRef.getModels()); + // if (!model) return; + // this.props.onImport?.(this.deriveValue(JSON.parse(model.getValue()))); return; } } @@ -108,10 +124,14 @@ export class DataSourceImportPluginCode extends PureComponent< } }; - handleEditorDidMount = (isFullscreen, editor, monaco) => { - this.monacoRef = monaco?.editor; + /* @author daifuyang + ** @description:修复编辑器挂载事件 + */ + handleEditorDidMount = (editor: MonacoEditor, monaco: MonacoEditor) => { + this.monacoRef = editor?.editor; }; + render() { const { onCancel = _noop } = this.props; const { code, isCodeValid } = this.state; @@ -133,7 +153,7 @@ export class DataSourceImportPluginCode extends PureComponent<

diff --git a/packages/plugin-datasource-pane/src/components/DataSourceListItem/DataSourceListItem.tsx b/packages/plugin-datasource-pane/src/components/DataSourceListItem/DataSourceListItem.tsx index ebc4ac8..52e7e45 100644 --- a/packages/plugin-datasource-pane/src/components/DataSourceListItem/DataSourceListItem.tsx +++ b/packages/plugin-datasource-pane/src/components/DataSourceListItem/DataSourceListItem.tsx @@ -110,6 +110,10 @@ export class DataSourceListItem extends Component { selected, renderInfoTags = defaultRenderInfoTags, } = this.props; + + // 拖拽中,需要向右偏移 8p,避免覆盖 + const offsetStyle = mode === DataSourcePanelMode.SORTING ? { marginLeft: '8px' } : null; + return this.props?.connectDropTarget?.( this.props?.connectDragPreview?.(
{ onChange={this.handleExportCheckChange} /> )} -
+
{
}
-
+
{renderInfoTags(dataSource)?.map?.((tag: DataSourceInfoTag) => { if (tag.tooltip === true) { return ( @@ -189,6 +193,7 @@ export class DataSourceListItem extends Component { } return ( void; + field: Field } export interface ComponentSwitchBtnCompState { @@ -34,17 +37,62 @@ class ComponentSwitchBtnCompComp extends PureComponent< componentDidMount() { this.originalComponent = this.props.originalComponent; + + // 表单回调的时候,如果初始值是 expression,那需要切换组件 + if (isJSExpression(this.props.field.value)) { + this.props.setComponent('LowcodeExpression') + this.setState({ currentComponent: 'LowcodeExpression' }); + } } handleSwitch = () => { + const { field, setComponent } = this.props + let nextComponent = null; if (this.state.currentComponent === this.originalComponent) { nextComponent = this.props.component; } else { nextComponent = this.originalComponent; } - this.setState({ currentComponent: nextComponent }); - this.props.setComponent?.(nextComponent); + + let nextValue: number | boolean | string | JSExpression | Array = field.value + + switch(nextComponent) { + case 'Switch': + // expression 转 boolean + if (isJSExpression(nextValue)) { + nextValue = (nextValue && nextValue.value === 'true') || false; + } + break; + case 'NumberPicker': + // expression 转 number + if (isJSExpression(nextValue)) { + const val = +(nextValue && nextValue.value) + nextValue = isNaN(val) ? 0 : val + } + break; + case 'ArrayItems': + // expression 转 array + if (isJSExpression(nextValue)) { + nextValue = [] + } + break; + case 'LowcodeExpression': + // 普通组件转 array + nextValue = { + type: 'JSExpression', + value: nextValue + '', + } + break; + default: // 默认 expression 转 string (Input、Select 组件走这) + if (isJSExpression(nextValue)) { + nextValue = (nextValue && nextValue.value) || '' + } + } + + this.setState({ currentComponent: nextComponent! }); + field.setValue(nextValue) + setComponent?.(nextComponent!); }; render() { @@ -66,6 +114,7 @@ export const ComponentSwitchBtn = connect( ComponentSwitchBtnCompComp, mapProps((props, field) => { return { + field, setComponent: field.setComponent, originalComponent: _get(field, 'component[0]'), }; diff --git a/packages/plugin-datasource-pane/src/components/Forms/form-lazy-obj.tsx b/packages/plugin-datasource-pane/src/components/Forms/form-lazy-obj.tsx index 0c5da9c..0d65f38 100644 --- a/packages/plugin-datasource-pane/src/components/Forms/form-lazy-obj.tsx +++ b/packages/plugin-datasource-pane/src/components/Forms/form-lazy-obj.tsx @@ -12,6 +12,7 @@ import _pick from 'lodash/pick'; import { JSFunction } from './jsFunction'; import { RemoveBtn } from './form-lazy-obj-remove-btn'; import { generateClassName } from '../../utils/misc'; +import { isJSFunction } from '@alilc/lowcode-types'; const { Item: MenuButtonItem } = MenuButton; @@ -22,11 +23,18 @@ export interface FormLazyObjProps { export const FormLazyObj = observer((props: FormLazyObjProps) => { const { addText = '添加' } = props; + const field = useField(); - const [selectedProperties, setSelectedProperties] = useState([]); + const schema = useFieldSchema(); - useEffect(() => {}, [field]); + const [selectedProperties, setSelectedProperties] = useState(() => { + // 自动回填数据处理函数 + return Object.keys(schema.properties || {}).filter(property => { + return isJSFunction(field.form.values[property]) + }) + }); + const properties = useMemo(() => { return Object.keys(schema.properties || {}) .filter((i) => selectedProperties.indexOf(i) === -1) @@ -40,9 +48,15 @@ export const FormLazyObj = observer((props: FormLazyObjProps) => { setSelectedProperties((selectedProperties) => selectedProperties.concat(propertyKey)); }, []); + /* 改成formily内部支持 */ const handleRemove = useCallback((propertyKey) => { - setSelectedProperties((selectedProperties) => selectedProperties.filter((i) => i !== propertyKey)); - }, []); + field?.form?.query(propertyKey)?.take()?.setState((state) => { + state.visible = !state.visible; + }); + // setSelectedProperties((selectedProperties) => + // selectedProperties.filter((i) => i !== propertyKey) + // ); + }, [field]); const addition = useMemo(() => { if (properties.length === 0) return null; @@ -69,6 +83,7 @@ export const FormLazyObj = observer((props: FormLazyObjProps) => { }); const schemaJSON = schema.toJSON(); const schemaProperties = _pick(schemaJSON.properties, selectedProperties); + return ( void; } @@ -25,37 +23,29 @@ export class JSFunctionComp extends PureComponent< onChange: _noop, }; - private monacoRef: any = null; + // private monacoRef: any = null; - handleEditorChange = (newValue) => { - if (this.monacoRef) { - if ( - !(this.monacoRef as any) - .getModelMarkers() - .find((marker: editor.IMarker) => marker.owner === 'json') - ) { - this.props.onChange?.({ - type: 'JSFunction', - value: newValue, - }); - } - } + handleEditorChange = (newValue: string) => { + this.props.onChange?.({ + type: 'JSFunction', + value: newValue, + }); }; - handleEditorDidMount = (isFullscreen, editor, monaco) => { - this.monacoRef = monaco?.editor; - }; + // handleEditorDidMount = (isFullscreen: boolean, editor, monaco) => { + // this.monacoRef = monaco?.editor; + // }; render() { - const { value } = this.props; + const { value, className } = this.props; return ( -
+
); diff --git a/packages/plugin-datasource-pane/src/components/Forms/json.tsx b/packages/plugin-datasource-pane/src/components/Forms/json.tsx index 04c6cc3..38d68ac 100644 --- a/packages/plugin-datasource-pane/src/components/Forms/json.tsx +++ b/packages/plugin-datasource-pane/src/components/Forms/json.tsx @@ -2,10 +2,10 @@ import React, { PureComponent } from 'react'; import { connect } from '@formily/react'; import MonacoEditor from '@alilc/lowcode-plugin-base-monaco-editor'; import _noop from 'lodash/noop'; -import { editor } from 'monaco-editor'; +// import { editor } from 'monaco-editor'; export interface JSONProps { - className: string; + className?: string; value: Record; onChange?: (val: any) => void; } @@ -19,37 +19,26 @@ export class JSONComp extends PureComponent { onChange: _noop, }; - private monacoRef: any = null; - - handleEditorChange = (newValue) => { - if (this.monacoRef) { - if ( - !(this.monacoRef as any) - .getModelMarkers() - .find((marker: editor.IMarker) => marker.owner === 'json') - ) { - this.props.onChange?.({ - type: 'JSON', - value: newValue, - }); - } - } - }; + // private monacoRef: any = null; - handleEditorDidMount = (isFullscreen, editor, monaco) => { - this.monacoRef = monaco?.editor; + handleEditorChange = (newValue: string) => { + this.props.onChange?.(newValue); }; + // handleEditorDidMount = (editor) => { + // this.monacoRef = editor; + // }; + render() { - const { value } = this.props; + const { value, className } = this.props; return ( -
+
); diff --git a/packages/plugin-datasource-pane/src/components/Forms/lowcode-expression.tsx b/packages/plugin-datasource-pane/src/components/Forms/lowcode-expression.tsx index fcc64c8..7a5cf9d 100644 --- a/packages/plugin-datasource-pane/src/components/Forms/lowcode-expression.tsx +++ b/packages/plugin-datasource-pane/src/components/Forms/lowcode-expression.tsx @@ -26,9 +26,9 @@ export class LowcodeExpressionComp extends PureComponent }; render() { - const { value } = this.props; + const { value, className } = this.props; return ( -
+
{({ setters }) => { const ExpressionSetter = setters?.getSetter('ExpressionSetter').component; diff --git a/packages/plugin-datasource-pane/src/components/Forms/param-value.scss b/packages/plugin-datasource-pane/src/components/Forms/param-value.scss index d587e5d..734278a 100644 --- a/packages/plugin-datasource-pane/src/components/Forms/param-value.scss +++ b/packages/plugin-datasource-pane/src/components/Forms/param-value.scss @@ -10,6 +10,12 @@ $cls-prefix: "lowcode-plugin-datasource-pane"; } } +// .#{$cls-prefix}-component-switchbtn{ +// position: absolute; +// bottom: 0; +// right: 0; +// } + .#{$cls-prefix}-universal-value-typeswitch.next-btn-group { .next-btn { padding: 0 2px; @@ -30,4 +36,4 @@ $cls-prefix: "lowcode-plugin-datasource-pane"; .#{$cls-prefix}-universal-value-jsonstring, .#{$cls-prefix}-universal-value-json { width: 200px !important; -} \ No newline at end of file +} diff --git a/packages/plugin-datasource-pane/src/components/Forms/param-value.tsx b/packages/plugin-datasource-pane/src/components/Forms/param-value.tsx index 4bbdc7e..4c572dc 100644 --- a/packages/plugin-datasource-pane/src/components/Forms/param-value.tsx +++ b/packages/plugin-datasource-pane/src/components/Forms/param-value.tsx @@ -10,6 +10,7 @@ import { Switch, } from '@alifd/next'; import { connect } from '@formily/react'; +import { JSExpression, isJSExpression } from '@alilc/lowcode-types'; import _isPlainObject from 'lodash/isPlainObject'; import _isArray from 'lodash/isArray'; import _isNumber from 'lodash/isNumber'; @@ -22,6 +23,15 @@ import './param-value.scss'; const { Group: RadioGroup } = Radio; +enum ParamValueEnum { + STRING ='string', + NUMBER ='number', + BOOLEAN ='boolean', + EXPRSSION ='expression', + JSON ='json', + JSONSTRING = 'jsonstring', +} + type ParamValueType = | 'string' | 'number' @@ -40,7 +50,11 @@ export interface ParamValueProps { export interface ParamValueState { type: ParamValueType; - value: string | number | { type: 'JSFunction'; value: string } | boolean; + value: string | number | JSExpression | boolean; +} + +function isBoolean(val: boolean | string) { + return _isBoolean(val) || val === 'true' || val === 'false'; } const TYPE_LABEL_MAP = { @@ -86,6 +100,14 @@ class ParamValueComp extends PureComponent { // @todo 需要再 bind 一次? handleChange = (value: any) => { + const { type } = this.state; + if (type === 'json') { + this.props.onChange?.({ + type: 'JSExpression', + value, + }); + return; + } this.setState({ value, }); @@ -101,27 +123,50 @@ class ParamValueComp extends PureComponent { const currentTypeIndex = this.props.types.indexOf(type); nextRealType = types[(currentTypeIndex + 1) % types.length]; } - if (nextRealType === 'string') { - nextValue = nextValue.toString(); - } else if (nextRealType === 'number') { - nextValue *= 1; - } else if (nextRealType === 'boolean') { - nextValue = - nextValue === 'true' || - (nextValue && nextValue.value === 'true') || - false; - } else if (nextRealType === 'expression') { - // ExpressionSetter 的 value 接受 string - nextValue = String(nextValue); - } else if (nextRealType === 'jsonstring') { - nextValue = ''; - } else if (nextRealType === 'object') { - nextValue = safeParse(nextValue); + switch (nextRealType) { + case ParamValueEnum.STRING: + if (isJSExpression(nextValue)) { + nextValue = nextValue.value; + } + // nextValue = nextValue.toString(); + break; + case ParamValueEnum.NUMBER: + nextValue *= 1; + break; + case ParamValueEnum.BOOLEAN: + if (isBoolean(nextValue)) { + nextValue = nextValue === 'true' || (nextValue && nextValue.value === 'true') || false; + } + break; + case ParamValueEnum.JSONSTRING: + nextValue = ''; + break; + case ParamValueEnum.JSON: + if (isJSExpression(nextValue)) { + nextValue = nextValue.value; + } + break; + case ParamValueEnum.EXPRSSION: + if (!isJSExpression(nextValue)) { + nextValue = { + type: 'JSExpression', + value: nextValue, + }; + } + break; + default: } this.setState({ type: nextRealType as ParamValueType, value: nextValue, }); + if (nextRealType === 'json') { + this.props.onChange?.({ + type: 'JSExpression', + value: nextValue, + }); + return; + } this.props.onChange?.(nextValue); }; @@ -129,12 +174,9 @@ class ParamValueComp extends PureComponent { const handleSwitch = () => { this.handleTypeChange(); }; - const handleSwitchTo = (type) => { + const handleSwitchTo = (type: string) => { this.handleTypeChange(type); }; - /* return ( - - ); */ return ( } -
- - +
+ 数据源 + {helpLink && } +
+ + +
+ { + return ( + i.id.indexOf(current.context.dataSourceListFilter.keyword) !== + -1 && + (!current.context.dataSourceListFilter.dataSourceType || + current.context.dataSourceListFilter.dataSourceType === + i.type) + ); + })} + onOperationClick={this.handleOperationClick} /> -
- { - return ( - i.id.indexOf(current.context.dataSourceListFilter.keyword) !== - -1 && - (!current.context.dataSourceListFilter.dataSourceType || - current.context.dataSourceListFilter.dataSourceType === - i.type) - ); - })} - onOperationClick={this.handleOperationClick} - /> -
- {this.renderDetail()}
- + {this.renderDetail()} +
); } } diff --git a/packages/plugin-datasource-pane/src/pane/index.scss b/packages/plugin-datasource-pane/src/pane/index.scss index d209c11..a8dd4c6 100644 --- a/packages/plugin-datasource-pane/src/pane/index.scss +++ b/packages/plugin-datasource-pane/src/pane/index.scss @@ -4,7 +4,7 @@ $cls-prefix: "lowcode-plugin-datasource-pane"; .#{$cls-prefix}-container { overflow-y: auto; - margin: 8px; + padding: 8px; height: 100%; >.next-tabs { display: flex; @@ -161,6 +161,10 @@ $cls-prefix: "lowcode-plugin-datasource-pane"; flex: 1 1 auto; overflow-y: auto; padding: 8px; + + .next-formily-item-addon-after { + position: relative; + } } } @@ -177,9 +181,12 @@ $cls-prefix: "lowcode-plugin-datasource-pane"; width: 42px; } .#{$cls-prefix}-component-switchbtn { - margin-left: -40px; - left: 40px; - position: relative; + position: absolute; + right: -20px; + bottom: 5px; + // margin-left: -40px; + // left: 40px; + // position: relative; } } diff --git a/packages/plugin-datasource-pane/src/pane/index.tsx b/packages/plugin-datasource-pane/src/pane/index.tsx index 3bea392..1e5f4be 100644 --- a/packages/plugin-datasource-pane/src/pane/index.tsx +++ b/packages/plugin-datasource-pane/src/pane/index.tsx @@ -7,6 +7,8 @@ import _get from 'lodash/get'; import _set from 'lodash/set'; import _isEmpty from 'lodash/isEmpty'; import _isFunction from 'lodash/isFunction'; +import { HTML5Backend } from 'react-dnd-html5-backend'; +import { DndProvider } from 'react-dnd'; import { EditorContext } from '../utils/editor-context'; import { DataSourcePane } from './DataSourcePane'; import { DataSourceFilter } from '../components/DataSourceFilter'; @@ -24,6 +26,7 @@ import { isSchemaValid, correctSchema } from '../utils/schema'; import { createStateService } from '../utils/stateMachine'; import { DataSourcePaneContext } from '../utils/panel-context'; import { mergeTwoObjectListByKey } from '../utils/misc'; +import { common } from '@alilc/lowcode-engine'; import './index.scss'; @@ -31,8 +34,6 @@ export interface DataSource { list: InterpretDataSourceConfig[]; } -const stateService = createStateService(); - export { DataSourceForm } from '../components/DataSourceForm'; const PLUGIN_NAME = 'dataSourcePane'; @@ -82,6 +83,8 @@ export default class DataSourcePanePlugin extends PureComponent< exportPlugins: [], }; + stateService = createStateService(); + state = { active: false, panelKey: 1, @@ -109,17 +112,17 @@ export default class DataSourcePanePlugin extends PureComponent< } componentDidMount() { - stateService.start(); + this.stateService.start(); } componentWillUnmount() { - stateService.stop(); + this.stateService.stop(); } handleSchemaChange = (schema: DataSource) => { const { project, onSchemaChange } = this.props; if (project) { - const docSchema = project.exportSchema(); + const docSchema = project.exportSchema(common.designerCabin.TransformStage.Save); if (!_isEmpty(docSchema)) { _set(docSchema, 'componentsTree[0].dataSource', schema); project.importSchema(docSchema); @@ -148,7 +151,7 @@ export default class DataSourcePanePlugin extends PureComponent< if (!active) return null; - const projectSchema = project.exportSchema() ?? {}; + const projectSchema = project.exportSchema(common.designerCabin.TransformStage.Save) ?? {}; let schema = defaultSchema; if (_isFunction(defaultSchema)) { schema = defaultSchema(); @@ -165,32 +168,34 @@ export default class DataSourcePanePlugin extends PureComponent< return ( - - { /* @ts-ignore */ } - >, - importPlugins as unknown as Array>, - 'name', - )} - exportPlugins={mergeTwoObjectListByKey( - BUILTIN_IMPORT_PLUGINS as unknown as Array>, - exportPlugins as unknown as Array>, - 'name', - )} - dataSourceTypes={dataSourceTypes} - initialSchema={schema} - onSchemaChange={this.handleSchemaChange} - /> - + + + { /* @ts-ignore */ } + >, + importPlugins as unknown as Array>, + 'name', + )} + exportPlugins={mergeTwoObjectListByKey( + BUILTIN_IMPORT_PLUGINS as unknown as Array>, + exportPlugins as unknown as Array>, + 'name', + )} + dataSourceTypes={dataSourceTypes} + initialSchema={schema} + onSchemaChange={this.handleSchemaChange} + /> + + ); diff --git a/packages/plugin-datasource-pane/src/utils/stateMachine.ts b/packages/plugin-datasource-pane/src/utils/stateMachine.ts index 4307e23..15edb12 100644 --- a/packages/plugin-datasource-pane/src/utils/stateMachine.ts +++ b/packages/plugin-datasource-pane/src/utils/stateMachine.ts @@ -161,7 +161,14 @@ export const createStateMachine = (dataSourceList: DataSourceConfig[] = []) => c target: 'idle', actions: assign({ dataSourceList: (context, event) => { - return context.dataSourceList.concat(event.payload); + // 直接 concat 会出现重复 + const filterDataSourceList = context.dataSourceList.filter((item) => { + return !event.payload.find( + (dataSource: DataSourceConfig) => dataSource.id === item.id, + ) + }) + + return filterDataSourceList.concat(event.payload); }, detail: { visible: false, diff --git a/packages/plugin-manual/.gitignore b/packages/plugin-manual/.gitignore deleted file mode 100644 index 7d916c2..0000000 --- a/packages/plugin-manual/.gitignore +++ /dev/null @@ -1,43 +0,0 @@ -node_modules/ -build/ -es/ -lib/ -.idea/ -.vscode/ -jsconfig.json -typings.json -typings/ -~* - -# Packages # -############ -# it's better to unpack these files and commit the raw source -# git has its own built in compression methods -*.7z -*.dmg -*.gz -*.iso -*.jar -*.rar -*.tar -*.zip - -# Logs and databases # -###################### -*.log -*.sql -*.sqlite - -# OS generated files # -###################### -.DS_Store -*.swp -.DS_Store? -._* -.Spotlight-V100 -.Trashes -ehthumbs.db -Thumbs.db -.lock -package-lock.json -.vdev diff --git a/packages/plugin-manual/build.json b/packages/plugin-manual/build.json index 1edfc2f..1767fef 100644 --- a/packages/plugin-manual/build.json +++ b/packages/plugin-manual/build.json @@ -3,7 +3,8 @@ [ "@alilc/build-plugin-alt", { - "type": "plugin" + "type": "plugin", + "inject": true } ], [ diff --git a/packages/plugin-manual/package.json b/packages/plugin-manual/package.json index 687eaf2..700637d 100644 --- a/packages/plugin-manual/package.json +++ b/packages/plugin-manual/package.json @@ -1,7 +1,7 @@ { "name": "@alilc/lowcode-plugin-manual", "author": "humphry.huang9@gmail.com", - "version": "1.0.2", + "version": "1.0.4", "description": "低代码产品使用手册", "main": "lib/index.js", "module": "es/index.js", @@ -15,6 +15,7 @@ "pub": "npm publish" }, "publishConfig": { + "registry": "https://registry.npmjs.org/", "access": "public" }, "dependencies": {}, @@ -30,5 +31,10 @@ "build-plugin-fusion": "^0.1.19", "@alilc/build-plugin-alt": "^1.0.0" }, - "license": "MIT" + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/alibaba/lowcode-plugins.git", + "directory": "packages/plugin-manual" + } } diff --git a/packages/plugin-manual/src/index.tsx b/packages/plugin-manual/src/index.tsx index b0b28d6..cf85189 100644 --- a/packages/plugin-manual/src/index.tsx +++ b/packages/plugin-manual/src/index.tsx @@ -1,42 +1,20 @@ -import * as React from 'react'; -import { ILowCodePluginContext } from '@alilc/lowcode-engine'; +import { IPublicModelPluginContext } from '@alilc/lowcode-types'; import { IconQuestion } from './icon'; -import { Dialog } from '@alifd/next'; -import { Documents } from './popup'; -const PluginManual = (ctx: ILowCodePluginContext) => { +const PluginManual = (ctx: IPublicModelPluginContext) => { return { - // 插件名,注册环境下唯一 - name: 'PluginManual', - // 依赖的插件(插件名数组) - dep: [], - // 插件对外暴露的数据和方法 - exports() { - return { } - }, - // 插件的初始化函数,在引擎初始化之后会立刻调用 init() { // 往引擎增加面板 ctx.skeleton.add({ area: 'leftArea', - name: 'demoPane', + name: 'manualPane', type: 'PanelDock', props: { align: 'bottom', icon: IconQuestion, description: '如何使用', onClick() { - Dialog.show({ - title: '低代码产品使用文档', - content: ( - - ), - height: window.innerHeight - 100 + 'px', - style: { - width: window.innerWidth - 300, - }, - footer: false, - }) + window.open('https://lowcode-engine.cn/site/docs/demoUsage/intro', '_blank').focus(); }, }, }); diff --git a/packages/plugin-manual/src/popup.tsx b/packages/plugin-manual/src/popup.tsx deleted file mode 100644 index 3710ddc..0000000 --- a/packages/plugin-manual/src/popup.tsx +++ /dev/null @@ -1,158 +0,0 @@ -import * as React from 'react'; -import { useEffect, useState } from 'react'; -import { material } from '@alilc/lowcode-engine'; -import { Tree, Tag, Loading } from '@alifd/next'; - -import { IconBug, IconLink } from './icon'; - -export function Documents() { - const [menu, setMenu] = useState([] as Array<{ title: string, url: string }>) - const [loading, setLoading] = useState(false) - const [selection, setSelection] = useState([] as string[]) - - useEffect(() => { - setLoading(true) - fetch('https://lowcode-engine.cn/api/get_usage') - .then((res) => res.json()) - .then((val) => { - const menu = val.data.filter((a: any) => !isNaN(parseInt(a.title))) - menu.sort((a: any, b: any) => parseInt(a.title) > parseInt(b.title) ? 1 : -1) - setMenu(menu) - setSelection(menu.length ? ['0'] : []) - }) - .catch(err => console.error(err)) - .finally(() => { - setLoading(false) - }) - }, []) - - return ( - -
-
- {!loading && ( - { - if (Array.isArray(newSelection) && newSelection.length > 0) { - setSelection(newSelection) - } - }} - isNodeBlock={{ - defaultPadingLeft: 1, - indent: 1, - }} - style={{ lineHeight: '26px' }} - > - {menu.map((item, index) => ( - - ))} - - - {IconLink} - 低代码引擎技术文档 - - )} - onClick={() => { - window.open('https://lowcode-engine.cn/doc') - }} - /> - - {IconLink} - engine - - {getVerionOf('ali-lowcode/ali-lowcode-engine') ?? (window as any).AliLowCodeEngine.version ?? '-'} - - - )} - onClick={() => { - window.open('https://lowcode-engine.cn/doc?url=engine-changelog') - }} - /> - - {IconLink} - ext - - {getVerionOf('ali-lowcode/lowcode-engine-ext') ?? (window as any).AliLowCodeEngineExt.version ?? '-'} - - - )} - onClick={() => { - window.open('https://lowcode-engine.cn/doc?url=engine-ext-changelog') - }} - /> - - {IconBug} - 提交 bug - - )} - onClick={() => { - const assets = material.getAssets() - const message = `## 复现截图 - -## 复现流程与链接 - -## 期望结果 - -## 环境信息 - -- 引擎版本 ${getVerionOf('ali-lowcode/ali-lowcode-engine') ?? (window as any).AliLowCodeEngine.version ?? '-'} -- ext 版本 ${getVerionOf('ali-lowcode/lowcode-engine-ext') ?? (window as any).AliLowCodeEngineExt.version ?? '-'} -- 物料 -${assets.packages - .filter((item: any) => !!item.package) - .map((item: any) => (` - ${item.package}${item.version ? '@' + item.version : ''}`.replace(/@/g, '﹫'))) - .join('\n') -}` - window.open(`https://github.com/alibaba/lowcode-engine/issues/new?body=${ - encodeURIComponent(message) - }`) - }} - /> - - - )} -
- {menu[+selection[0]]?.url && ( -