From 40abbc217495cc226e313adef34312ceebc74963 Mon Sep 17 00:00:00 2001 From: coder-byte <308929264@qq.com> Date: Wed, 19 Jan 2022 14:44:26 +0800 Subject: [PATCH] fix(core/react): some bug fix about viewport (#198) --- examples/multi-workspace/.npmignore | 4 + examples/multi-workspace/LICENSE.md | 20 + examples/multi-workspace/README.md | 1 + examples/multi-workspace/config/template.ejs | 15 + .../multi-workspace/config/webpack.base.ts | 102 +++++ .../multi-workspace/config/webpack.dev.ts | 52 +++ .../multi-workspace/config/webpack.prod.ts | 36 ++ examples/multi-workspace/package.json | 41 ++ examples/multi-workspace/src/content.tsx | 47 ++ examples/multi-workspace/src/main.tsx | 407 +++++++++++++++++ examples/multi-workspace/tsconfig.build.json | 10 + examples/multi-workspace/tsconfig.json | 5 + examples/sandbox-multi-workspace/.npmignore | 4 + examples/sandbox-multi-workspace/LICENSE.md | 20 + examples/sandbox-multi-workspace/README.md | 1 + .../config/template.ejs | 15 + .../config/webpack.base.ts | 103 +++++ .../config/webpack.dev.ts | 52 +++ .../config/webpack.prod.ts | 36 ++ examples/sandbox-multi-workspace/package.json | 41 ++ .../sandbox-multi-workspace/src/content.tsx | 47 ++ examples/sandbox-multi-workspace/src/main.tsx | 430 ++++++++++++++++++ .../sandbox-multi-workspace/src/sandbox.tsx | 7 + .../tsconfig.build.json | 10 + .../sandbox-multi-workspace/tsconfig.json | 5 + package.json | 2 + .../core/src/effects/useSelectionEffect.ts | 2 +- packages/core/src/models/Viewport.ts | 6 +- packages/react/src/containers/Viewport.tsx | 4 +- .../react/src/hooks/useValidNodeOffsetRect.ts | 2 +- packages/shared/src/event.ts | 2 +- 31 files changed, 1522 insertions(+), 7 deletions(-) create mode 100644 examples/multi-workspace/.npmignore create mode 100644 examples/multi-workspace/LICENSE.md create mode 100644 examples/multi-workspace/README.md create mode 100644 examples/multi-workspace/config/template.ejs create mode 100644 examples/multi-workspace/config/webpack.base.ts create mode 100644 examples/multi-workspace/config/webpack.dev.ts create mode 100644 examples/multi-workspace/config/webpack.prod.ts create mode 100644 examples/multi-workspace/package.json create mode 100644 examples/multi-workspace/src/content.tsx create mode 100644 examples/multi-workspace/src/main.tsx create mode 100644 examples/multi-workspace/tsconfig.build.json create mode 100644 examples/multi-workspace/tsconfig.json create mode 100644 examples/sandbox-multi-workspace/.npmignore create mode 100644 examples/sandbox-multi-workspace/LICENSE.md create mode 100644 examples/sandbox-multi-workspace/README.md create mode 100644 examples/sandbox-multi-workspace/config/template.ejs create mode 100644 examples/sandbox-multi-workspace/config/webpack.base.ts create mode 100644 examples/sandbox-multi-workspace/config/webpack.dev.ts create mode 100644 examples/sandbox-multi-workspace/config/webpack.prod.ts create mode 100644 examples/sandbox-multi-workspace/package.json create mode 100644 examples/sandbox-multi-workspace/src/content.tsx create mode 100644 examples/sandbox-multi-workspace/src/main.tsx create mode 100644 examples/sandbox-multi-workspace/src/sandbox.tsx create mode 100644 examples/sandbox-multi-workspace/tsconfig.build.json create mode 100644 examples/sandbox-multi-workspace/tsconfig.json diff --git a/examples/multi-workspace/.npmignore b/examples/multi-workspace/.npmignore new file mode 100644 index 000000000..cc5cbf1a4 --- /dev/null +++ b/examples/multi-workspace/.npmignore @@ -0,0 +1,4 @@ +node_modules +*.log +build +__tests__ \ No newline at end of file diff --git a/examples/multi-workspace/LICENSE.md b/examples/multi-workspace/LICENSE.md new file mode 100644 index 000000000..509632e8e --- /dev/null +++ b/examples/multi-workspace/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2015-present, Alibaba Group Holding Limited. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/examples/multi-workspace/README.md b/examples/multi-workspace/README.md new file mode 100644 index 000000000..7490a9317 --- /dev/null +++ b/examples/multi-workspace/README.md @@ -0,0 +1 @@ +# @designable/playground diff --git a/examples/multi-workspace/config/template.ejs b/examples/multi-workspace/config/template.ejs new file mode 100644 index 000000000..02784b41e --- /dev/null +++ b/examples/multi-workspace/config/template.ejs @@ -0,0 +1,15 @@ + + + + Designable Playground + + + +
+
+ + + + + + \ No newline at end of file diff --git a/examples/multi-workspace/config/webpack.base.ts b/examples/multi-workspace/config/webpack.base.ts new file mode 100644 index 000000000..dbe282aee --- /dev/null +++ b/examples/multi-workspace/config/webpack.base.ts @@ -0,0 +1,102 @@ +import path from 'path' +import fs from 'fs-extra' +import MiniCssExtractPlugin from 'mini-css-extract-plugin' +//import { getThemeVariables } from 'antd/dist/theme' + +const getAlias = () => { + const packagesDir = path.resolve(__dirname, '../../../packages') + const packages = fs.readdirSync(packagesDir) + const pkg = fs.readJSONSync(path.resolve(__dirname, '../package.json')) + const deps = Object.entries(pkg.dependencies).reduce((deps, [key]) => { + if (key.includes('@designable/')) { + return deps + } else if (key.includes('react')) { + deps[key] = require.resolve(key) + return deps + } + deps[key] = key + return deps + }, {}) + const alias = packages + .map((v) => path.join(packagesDir, v)) + .filter((v) => { + return !fs.statSync(v).isFile() + }) + .reduce((buf, _path) => { + const name = path.basename(_path) + return { + ...buf, + [`@designable/${name}$`]: `${_path}/src`, + } + }, deps) + return alias +} +export default { + mode: 'development', + devtool: 'inline-source-map', // 嵌入到源文件中 + stats: { + entrypoints: false, + children: false, + }, + entry: { + playground: path.resolve(__dirname, '../src/main'), + }, + output: { + path: path.resolve(__dirname, '../dist'), + filename: '[name].[hash].bundle.js', + }, + resolve: { + modules: ['node_modules'], + extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'], + alias: getAlias(), + }, + externals: { + // '@formily/reactive': 'Formily.Reactive', + react: 'React', + 'react-dom': 'ReactDOM', + moment: 'moment', + antd: 'antd', + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: [ + { + loader: require.resolve('ts-loader'), + options: { + transpileOnly: true, + }, + }, + ], + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, require.resolve('css-loader')], + }, + { + test: /\.less$/, + use: [ + MiniCssExtractPlugin.loader, + { loader: 'css-loader' }, + { + loader: 'less-loader', + options: { + // modifyVars: getThemeVariables({ + // dark: true // 开启暗黑模式 + // }), + javascriptEnabled: true, + }, + }, + ], + }, + { + test: /\.html?$/, + loader: require.resolve('file-loader'), + options: { + name: '[name].[ext]', + }, + }, + ], + }, +} diff --git a/examples/multi-workspace/config/webpack.dev.ts b/examples/multi-workspace/config/webpack.dev.ts new file mode 100644 index 000000000..e39bec5bf --- /dev/null +++ b/examples/multi-workspace/config/webpack.dev.ts @@ -0,0 +1,52 @@ +import baseConfig from './webpack.base' +import HtmlWebpackPlugin from 'html-webpack-plugin' +import MiniCssExtractPlugin from 'mini-css-extract-plugin' +//import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer' +import webpack from 'webpack' +import path from 'path' + +const PORT = 3000 + +const createPages = (pages) => { + return pages.map(({ filename, template, chunk }) => { + return new HtmlWebpackPlugin({ + filename, + template, + inject: 'body', + chunks: chunk, + }) + }) +} + +for (let key in baseConfig.entry) { + if (Array.isArray(baseConfig.entry[key])) { + baseConfig.entry[key].push( + require.resolve('webpack/hot/dev-server'), + `${require.resolve('webpack-dev-server/client')}?http://localhost:${PORT}` + ) + } +} + +export default { + ...baseConfig, + plugins: [ + new MiniCssExtractPlugin({ + filename: '[name].[hash].css', + chunkFilename: '[id].[hash].css', + }), + ...createPages([ + { + filename: 'index.html', + template: path.resolve(__dirname, './template.ejs'), + chunk: ['playground'], + }, + ]), + new webpack.HotModuleReplacementPlugin(), + // new BundleAnalyzerPlugin() + ], + devServer: { + host: '127.0.0.1', + open: true, + port: PORT, + }, +} diff --git a/examples/multi-workspace/config/webpack.prod.ts b/examples/multi-workspace/config/webpack.prod.ts new file mode 100644 index 000000000..f031583d7 --- /dev/null +++ b/examples/multi-workspace/config/webpack.prod.ts @@ -0,0 +1,36 @@ +import baseConfig from './webpack.base' +import HtmlWebpackPlugin from 'html-webpack-plugin' +import MiniCssExtractPlugin from 'mini-css-extract-plugin' +import path from 'path' + +const createPages = (pages) => { + return pages.map(({ filename, template, chunk }) => { + return new HtmlWebpackPlugin({ + filename, + template, + inject: 'body', + chunks: chunk, + }) + }) +} + +export default { + ...baseConfig, + mode: 'production', + plugins: [ + new MiniCssExtractPlugin({ + filename: '[name].[hash].css', + chunkFilename: '[id].[hash].css', + }), + ...createPages([ + { + filename: 'index.html', + template: path.resolve(__dirname, './template.ejs'), + chunk: ['playground'], + }, + ]), + ], + optimization: { + minimize: true, + }, +} diff --git a/examples/multi-workspace/package.json b/examples/multi-workspace/package.json new file mode 100644 index 000000000..c05ae70ce --- /dev/null +++ b/examples/multi-workspace/package.json @@ -0,0 +1,41 @@ +{ + "name": "@designable/multi-workspace-example", + "version": "1.0.0-beta.43", + "license": "MIT", + "private": true, + "engines": { + "npm": ">=3.0.0" + }, + "scripts": { + "build": "rimraf dist && webpack-cli --config config/webpack.prod.ts", + "start": "webpack-dev-server --config config/webpack.dev.ts" + }, + "devDependencies": { + "file-loader": "^5.0.2", + "fs-extra": "^8.1.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^1.6.0", + "raw-loader": "^4.0.0", + "style-loader": "^1.1.3", + "ts-loader": "^7.0.4", + "typescript": "4.1.5", + "webpack": "^4.41.5", + "webpack-bundle-analyzer": "^3.9.0", + "webpack-cli": "^3.3.10", + "webpack-dev-server": "^3.10.1" + }, + "dependencies": { + "@designable/core": "1.0.0-beta.43", + "@designable/react": "1.0.0-beta.43", + "@designable/react-sandbox": "1.0.0-beta.43", + "@designable/react-settings-form": "1.0.0-beta.43", + "@designable/shared": "1.0.0-beta.43", + "@formily/reactive": "^2.0.2", + "@formily/reactive-react": "^2.0.2", + "antd": "^4.15.2", + "react": "^16.8.x", + "react-dom": "^16.8.x", + "react-jss": "^10.4.0" + }, + "gitHead": "820790a9ae32c2348bb36b3de7ca5f1051ed392c" +} diff --git a/examples/multi-workspace/src/content.tsx b/examples/multi-workspace/src/content.tsx new file mode 100644 index 000000000..d432dcbe5 --- /dev/null +++ b/examples/multi-workspace/src/content.tsx @@ -0,0 +1,47 @@ +import React from 'react' +import { ComponentTreeWidget, useTreeNode } from '@designable/react' +import { observer } from '@formily/reactive-react' +import 'antd/dist/antd.css' + +export const Content = () => ( + { + const node = useTreeNode() + return ( + + {node.props.title} + {props.children} + + ) + }), + Card: (props) => { + return ( +
+ {props.children ? props.children : 拖拽字段进入该区域} +
+ ) + }, + }} + /> +) diff --git a/examples/multi-workspace/src/main.tsx b/examples/multi-workspace/src/main.tsx new file mode 100644 index 000000000..fd53baa36 --- /dev/null +++ b/examples/multi-workspace/src/main.tsx @@ -0,0 +1,407 @@ +import React from 'react' +import ReactDOM from 'react-dom' +import { + Designer, + IconWidget, + Workbench, + Workspace, + ViewPanel, + DesignerToolsWidget, + ViewToolsWidget, + OutlineTreeWidget, + ResourceWidget, + StudioPanel, + CompositePanel, + WorkspacePanel, + ToolbarPanel, + ViewportPanel, + SettingsPanel, + HistoryWidget, +} from '@designable/react' +import { SettingsForm, MonacoInput } from '@designable/react-settings-form' +import { observer } from '@formily/react' +import { + createDesigner, + createResource, + createBehavior, + GlobalRegistry, +} from '@designable/core' +import { Content } from './content' +import { Space, Button, Radio } from 'antd' +import { GithubOutlined } from '@ant-design/icons' +import 'antd/dist/antd.less' + +const RootBehavior = createBehavior({ + name: 'Root', + selector: 'Root', + designerProps: { + droppable: true, + }, + designerLocales: { + 'zh-CN': { + title: '根组件', + }, + 'en-US': { + title: 'Root', + }, + 'ko-KR': { + title: '루트', + }, + }, +}) + +const InputBehavior = createBehavior({ + name: 'Input', + selector: (node) => + node.componentName === 'Field' && node.props['x-component'] === 'Input', + designerProps: { + propsSchema: { + type: 'object', + $namespace: 'Field', + properties: { + 'field-properties': { + type: 'void', + 'x-component': 'CollapseItem', + title: '字段属性', + properties: { + title: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + + hidden: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'Switch', + }, + default: { + 'x-decorator': 'FormItem', + 'x-component': 'ValueInput', + }, + test: { + type: 'void', + title: '测试', + 'x-decorator': 'FormItem', + 'x-component': 'DrawerSetter', + 'x-component-props': { + text: '打开抽屉', + }, + properties: { + test: { + type: 'string', + title: '测试输入', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + }, + }, + }, + }, + + 'component-styles': { + type: 'void', + title: '样式', + 'x-component': 'CollapseItem', + properties: { + 'style.width': { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'SizeInput', + }, + 'style.height': { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'SizeInput', + }, + 'style.display': { + 'x-component': 'DisplayStyleSetter', + }, + 'style.background': { + 'x-component': 'BackgroundStyleSetter', + }, + 'style.boxShadow': { + 'x-component': 'BoxShadowStyleSetter', + }, + 'style.font': { + 'x-component': 'FontStyleSetter', + }, + 'style.margin': { + 'x-component': 'BoxStyleSetter', + }, + 'style.padding': { + 'x-component': 'BoxStyleSetter', + }, + 'style.borderRadius': { + 'x-component': 'BorderRadiusStyleSetter', + }, + 'style.border': { + 'x-component': 'BorderStyleSetter', + }, + }, + }, + }, + }, + }, + designerLocales: { + 'zh-CN': { + title: '输入框', + settings: { + title: '标题', + hidden: '是否隐藏', + default: '默认值', + style: { + width: '宽度', + height: '高度', + display: '展示', + background: '背景', + boxShadow: '阴影', + font: '字体', + margin: '外边距', + padding: '内边距', + borderRadius: '圆角', + border: '边框', + }, + }, + }, + 'en-US': { + title: 'Input', + settings: { + title: 'Title', + hidden: 'Hidden', + default: 'Default Value', + style: { + width: 'Width', + height: 'Height', + display: 'Display', + background: 'Background', + boxShadow: 'Box Shadow', + font: 'Font', + margin: 'Margin', + padding: 'Padding', + borderRadius: 'Border Radius', + border: 'Border', + }, + }, + }, + 'ko-KR': { + title: '입력', + settings: { + title: '텍스트', + hidden: '숨김 여부', + default: '기본 설정 값', + style: { + width: '너비', + height: '높이', + display: '디스플레이', + background: '배경', + boxShadow: '그림자 박스', + font: '폰트', + margin: '마진', + padding: '패딩', + borderRadius: '테두리 굴곡', + border: '테두리', + }, + }, + }, + }, +}) + +const CardBehavior = createBehavior({ + name: 'Card', + selector: 'Card', + designerProps: { + droppable: true, + }, + designerLocales: { + 'zh-CN': { + title: '卡片', + }, + 'en-US': { + title: 'Card', + }, + 'ko-KR': { + title: '카드', + }, + }, +}) + +GlobalRegistry.setDesignerBehaviors([RootBehavior, InputBehavior, CardBehavior]) + +const Input = createResource({ + title: { + 'zh-CN': '输入框', + 'en-US': 'Input', + 'ko-KR': '입력 상자', + }, + icon: 'InputSource', + elements: [ + { + componentName: 'Field', + props: { + title: '输入框', + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + }, + ], +}) + +const Card = createResource({ + title: { + 'zh-CN': '卡片', + 'en-US': 'Card', + 'ko-KR': '카드 상자', + }, + icon: 'CardSource', + elements: [ + { + componentName: 'Card', + props: { + title: '卡片', + }, + }, + ], +}) + +GlobalRegistry.registerDesignerLocales({ + 'zh-CN': { + sources: { + Inputs: '输入控件', + Displays: '展示控件', + Feedbacks: '反馈控件', + }, + }, + 'en-US': { + sources: { + Inputs: 'Inputs', + Displays: 'Displays', + Feedbacks: 'Feedbacks', + }, + }, + 'ko-KR': { + sources: { + Inputs: '입력', + Displays: '디스플레이', + Feedbacks: '피드백', + }, + }, +}) + +const Logo: React.FC = () => ( +
+ +
+) + +const Actions = observer(() => ( + + { + GlobalRegistry.setDesignerLanguage(e.target.value) + }} + /> + + + + +)) + +const engine = createDesigner() +window.engine = engine +const App = () => { + return ( + + + } actions={}> + + + + + + + + + + + + + + + + + + {' '} + + + {() => } + + {() => { + return ( +
+
123123
123123
123123
123123
`} + /> + + ) + }} +
+
+
+
+ + + + + {' '} + + + {() => } + + {() => { + return ( +
+
123123
123123
123123
123123
`} + /> + + ) + }} +
+
+
+
+ + + +
+
+
+ ) +} + +ReactDOM.render(, document.getElementById('root')) diff --git a/examples/multi-workspace/tsconfig.build.json b/examples/multi-workspace/tsconfig.build.json new file mode 100644 index 000000000..8f2e5f5a9 --- /dev/null +++ b/examples/multi-workspace/tsconfig.build.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./lib", + "paths": { + "@designable/*": ["../*"] + }, + "declaration": true + } +} diff --git a/examples/multi-workspace/tsconfig.json b/examples/multi-workspace/tsconfig.json new file mode 100644 index 000000000..c6865c25a --- /dev/null +++ b/examples/multi-workspace/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.json", + "include": ["./src/**/*.ts", "./src/**/*.tsx"], + "exclude": ["./src/__tests__/*", "./esm/*", "./lib/*"] +} diff --git a/examples/sandbox-multi-workspace/.npmignore b/examples/sandbox-multi-workspace/.npmignore new file mode 100644 index 000000000..cc5cbf1a4 --- /dev/null +++ b/examples/sandbox-multi-workspace/.npmignore @@ -0,0 +1,4 @@ +node_modules +*.log +build +__tests__ \ No newline at end of file diff --git a/examples/sandbox-multi-workspace/LICENSE.md b/examples/sandbox-multi-workspace/LICENSE.md new file mode 100644 index 000000000..509632e8e --- /dev/null +++ b/examples/sandbox-multi-workspace/LICENSE.md @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2015-present, Alibaba Group Holding Limited. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/examples/sandbox-multi-workspace/README.md b/examples/sandbox-multi-workspace/README.md new file mode 100644 index 000000000..7490a9317 --- /dev/null +++ b/examples/sandbox-multi-workspace/README.md @@ -0,0 +1 @@ +# @designable/playground diff --git a/examples/sandbox-multi-workspace/config/template.ejs b/examples/sandbox-multi-workspace/config/template.ejs new file mode 100644 index 000000000..39cd66619 --- /dev/null +++ b/examples/sandbox-multi-workspace/config/template.ejs @@ -0,0 +1,15 @@ + + + + Designable Playground + + + +
+
+ + + + + + \ No newline at end of file diff --git a/examples/sandbox-multi-workspace/config/webpack.base.ts b/examples/sandbox-multi-workspace/config/webpack.base.ts new file mode 100644 index 000000000..5c193d880 --- /dev/null +++ b/examples/sandbox-multi-workspace/config/webpack.base.ts @@ -0,0 +1,103 @@ +import path from 'path' +import fs from 'fs-extra' +import MiniCssExtractPlugin from 'mini-css-extract-plugin' +//import { getThemeVariables } from 'antd/dist/theme' + +const getAlias = () => { + const packagesDir = path.resolve(__dirname, '../../../packages') + const packages = fs.readdirSync(packagesDir) + const pkg = fs.readJSONSync(path.resolve(__dirname, '../package.json')) + const deps = Object.entries(pkg.dependencies).reduce((deps, [key]) => { + if (key.includes('@designable/')) { + return deps + } else if (key.includes('react')) { + deps[key] = require.resolve(key) + return deps + } + deps[key] = key + return deps + }, {}) + const alias = packages + .map((v) => path.join(packagesDir, v)) + .filter((v) => { + return !fs.statSync(v).isFile() + }) + .reduce((buf, _path) => { + const name = path.basename(_path) + return { + ...buf, + [`@designable/${name}$`]: `${_path}/src`, + } + }, deps) + return alias +} +export default { + mode: 'development', + devtool: 'inline-source-map', // 嵌入到源文件中 + stats: { + entrypoints: false, + children: false, + }, + entry: { + playground: path.resolve(__dirname, '../src/main'), + sandbox: path.resolve(__dirname, '../src/sandbox'), + }, + output: { + path: path.resolve(__dirname, '../dist'), + filename: '[name].bundle.js', + }, + resolve: { + modules: ['node_modules'], + extensions: ['.js', '.jsx', '.ts', '.tsx', '.json'], + alias: getAlias(), + }, + externals: { + '@formily/reactive': 'Formily.Reactive', + react: 'React', + 'react-dom': 'ReactDOM', + moment: 'moment', + antd: 'antd', + }, + module: { + rules: [ + { + test: /\.tsx?$/, + use: [ + { + loader: require.resolve('ts-loader'), + options: { + transpileOnly: true, + }, + }, + ], + }, + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, require.resolve('css-loader')], + }, + { + test: /\.less$/, + use: [ + MiniCssExtractPlugin.loader, + { loader: 'css-loader' }, + { + loader: 'less-loader', + options: { + // modifyVars: getThemeVariables({ + // dark: true // 开启暗黑模式 + // }), + javascriptEnabled: true, + }, + }, + ], + }, + { + test: /\.html?$/, + loader: require.resolve('file-loader'), + options: { + name: '[name].[ext]', + }, + }, + ], + }, +} diff --git a/examples/sandbox-multi-workspace/config/webpack.dev.ts b/examples/sandbox-multi-workspace/config/webpack.dev.ts new file mode 100644 index 000000000..e39bec5bf --- /dev/null +++ b/examples/sandbox-multi-workspace/config/webpack.dev.ts @@ -0,0 +1,52 @@ +import baseConfig from './webpack.base' +import HtmlWebpackPlugin from 'html-webpack-plugin' +import MiniCssExtractPlugin from 'mini-css-extract-plugin' +//import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer' +import webpack from 'webpack' +import path from 'path' + +const PORT = 3000 + +const createPages = (pages) => { + return pages.map(({ filename, template, chunk }) => { + return new HtmlWebpackPlugin({ + filename, + template, + inject: 'body', + chunks: chunk, + }) + }) +} + +for (let key in baseConfig.entry) { + if (Array.isArray(baseConfig.entry[key])) { + baseConfig.entry[key].push( + require.resolve('webpack/hot/dev-server'), + `${require.resolve('webpack-dev-server/client')}?http://localhost:${PORT}` + ) + } +} + +export default { + ...baseConfig, + plugins: [ + new MiniCssExtractPlugin({ + filename: '[name].[hash].css', + chunkFilename: '[id].[hash].css', + }), + ...createPages([ + { + filename: 'index.html', + template: path.resolve(__dirname, './template.ejs'), + chunk: ['playground'], + }, + ]), + new webpack.HotModuleReplacementPlugin(), + // new BundleAnalyzerPlugin() + ], + devServer: { + host: '127.0.0.1', + open: true, + port: PORT, + }, +} diff --git a/examples/sandbox-multi-workspace/config/webpack.prod.ts b/examples/sandbox-multi-workspace/config/webpack.prod.ts new file mode 100644 index 000000000..f031583d7 --- /dev/null +++ b/examples/sandbox-multi-workspace/config/webpack.prod.ts @@ -0,0 +1,36 @@ +import baseConfig from './webpack.base' +import HtmlWebpackPlugin from 'html-webpack-plugin' +import MiniCssExtractPlugin from 'mini-css-extract-plugin' +import path from 'path' + +const createPages = (pages) => { + return pages.map(({ filename, template, chunk }) => { + return new HtmlWebpackPlugin({ + filename, + template, + inject: 'body', + chunks: chunk, + }) + }) +} + +export default { + ...baseConfig, + mode: 'production', + plugins: [ + new MiniCssExtractPlugin({ + filename: '[name].[hash].css', + chunkFilename: '[id].[hash].css', + }), + ...createPages([ + { + filename: 'index.html', + template: path.resolve(__dirname, './template.ejs'), + chunk: ['playground'], + }, + ]), + ], + optimization: { + minimize: true, + }, +} diff --git a/examples/sandbox-multi-workspace/package.json b/examples/sandbox-multi-workspace/package.json new file mode 100644 index 000000000..ec02629de --- /dev/null +++ b/examples/sandbox-multi-workspace/package.json @@ -0,0 +1,41 @@ +{ + "name": "@designable/sandbox-multi-workspace-example", + "version": "1.0.0-beta.43", + "license": "MIT", + "private": true, + "engines": { + "npm": ">=3.0.0" + }, + "scripts": { + "build": "rimraf dist && webpack-cli --config config/webpack.prod.ts", + "start": "webpack-dev-server --config config/webpack.dev.ts" + }, + "devDependencies": { + "file-loader": "^5.0.2", + "fs-extra": "^8.1.0", + "html-webpack-plugin": "^3.2.0", + "mini-css-extract-plugin": "^1.6.0", + "raw-loader": "^4.0.0", + "style-loader": "^1.1.3", + "ts-loader": "^7.0.4", + "typescript": "4.1.5", + "webpack": "^4.41.5", + "webpack-bundle-analyzer": "^3.9.0", + "webpack-cli": "^3.3.10", + "webpack-dev-server": "^3.10.1" + }, + "dependencies": { + "@designable/core": "1.0.0-beta.43", + "@designable/react": "1.0.0-beta.43", + "@designable/react-sandbox": "1.0.0-beta.43", + "@designable/react-settings-form": "1.0.0-beta.43", + "@designable/shared": "1.0.0-beta.43", + "@formily/reactive": "^2.0.2", + "@formily/reactive-react": "^2.0.2", + "antd": "^4.15.2", + "react": "^16.8.x", + "react-dom": "^16.8.x", + "react-jss": "^10.4.0" + }, + "gitHead": "820790a9ae32c2348bb36b3de7ca5f1051ed392c" +} diff --git a/examples/sandbox-multi-workspace/src/content.tsx b/examples/sandbox-multi-workspace/src/content.tsx new file mode 100644 index 000000000..d432dcbe5 --- /dev/null +++ b/examples/sandbox-multi-workspace/src/content.tsx @@ -0,0 +1,47 @@ +import React from 'react' +import { ComponentTreeWidget, useTreeNode } from '@designable/react' +import { observer } from '@formily/reactive-react' +import 'antd/dist/antd.css' + +export const Content = () => ( + { + const node = useTreeNode() + return ( + + {node.props.title} + {props.children} + + ) + }), + Card: (props) => { + return ( +
+ {props.children ? props.children : 拖拽字段进入该区域} +
+ ) + }, + }} + /> +) diff --git a/examples/sandbox-multi-workspace/src/main.tsx b/examples/sandbox-multi-workspace/src/main.tsx new file mode 100644 index 000000000..d9b092663 --- /dev/null +++ b/examples/sandbox-multi-workspace/src/main.tsx @@ -0,0 +1,430 @@ +import React from 'react' +import ReactDOM from 'react-dom' +import { + Designer, + IconWidget, + Workbench, + Workspace, + ViewPanel, + DesignerToolsWidget, + ViewToolsWidget, + OutlineTreeWidget, + ResourceWidget, + StudioPanel, + CompositePanel, + WorkspacePanel, + ToolbarPanel, + ViewportPanel, + SettingsPanel, + HistoryWidget, +} from '@designable/react' +import { SettingsForm, MonacoInput } from '@designable/react-settings-form' +import { observer } from '@formily/react' +import { + createDesigner, + createResource, + createBehavior, + GlobalRegistry, +} from '@designable/core' +import { Space, Button, Radio } from 'antd' +import { GithubOutlined } from '@ant-design/icons' +import { Sandbox } from '@designable/react-sandbox' +import 'antd/dist/antd.less' + +const RootBehavior = createBehavior({ + name: 'Root', + selector: 'Root', + designerProps: { + droppable: true, + }, + designerLocales: { + 'zh-CN': { + title: '根组件', + }, + 'en-US': { + title: 'Root', + }, + 'ko-KR': { + title: '루트', + }, + }, +}) + +const InputBehavior = createBehavior({ + name: 'Input', + selector: (node) => + node.componentName === 'Field' && node.props['x-component'] === 'Input', + designerProps: { + propsSchema: { + type: 'object', + $namespace: 'Field', + properties: { + 'field-properties': { + type: 'void', + 'x-component': 'CollapseItem', + title: '字段属性', + properties: { + title: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + + hidden: { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'Switch', + }, + default: { + 'x-decorator': 'FormItem', + 'x-component': 'ValueInput', + }, + test: { + type: 'void', + title: '测试', + 'x-decorator': 'FormItem', + 'x-component': 'DrawerSetter', + 'x-component-props': { + text: '打开抽屉', + }, + properties: { + test: { + type: 'string', + title: '测试输入', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + }, + }, + }, + }, + + 'component-styles': { + type: 'void', + title: '样式', + 'x-component': 'CollapseItem', + properties: { + 'style.width': { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'SizeInput', + }, + 'style.height': { + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'SizeInput', + }, + 'style.display': { + 'x-component': 'DisplayStyleSetter', + }, + 'style.background': { + 'x-component': 'BackgroundStyleSetter', + }, + 'style.boxShadow': { + 'x-component': 'BoxShadowStyleSetter', + }, + 'style.font': { + 'x-component': 'FontStyleSetter', + }, + 'style.margin': { + 'x-component': 'BoxStyleSetter', + }, + 'style.padding': { + 'x-component': 'BoxStyleSetter', + }, + 'style.borderRadius': { + 'x-component': 'BorderRadiusStyleSetter', + }, + 'style.border': { + 'x-component': 'BorderStyleSetter', + }, + }, + }, + }, + }, + }, + designerLocales: { + 'zh-CN': { + title: '输入框', + settings: { + title: '标题', + hidden: '是否隐藏', + default: '默认值', + style: { + width: '宽度', + height: '高度', + display: '展示', + background: '背景', + boxShadow: '阴影', + font: '字体', + margin: '外边距', + padding: '内边距', + borderRadius: '圆角', + border: '边框', + }, + }, + }, + 'en-US': { + title: 'Input', + settings: { + title: 'Title', + hidden: 'Hidden', + default: 'Default Value', + style: { + width: 'Width', + height: 'Height', + display: 'Display', + background: 'Background', + boxShadow: 'Box Shadow', + font: 'Font', + margin: 'Margin', + padding: 'Padding', + borderRadius: 'Border Radius', + border: 'Border', + }, + }, + }, + 'ko-KR': { + title: '입력', + settings: { + title: '텍스트', + hidden: '숨김 여부', + default: '기본 설정 값', + style: { + width: '너비', + height: '높이', + display: '디스플레이', + background: '배경', + boxShadow: '그림자 박스', + font: '폰트', + margin: '마진', + padding: '패딩', + borderRadius: '테두리 굴곡', + border: '테두리', + }, + }, + }, + }, +}) + +const CardBehavior = createBehavior({ + name: 'Card', + selector: 'Card', + designerProps: { + droppable: true, + }, + designerLocales: { + 'zh-CN': { + title: '卡片', + }, + 'en-US': { + title: 'Card', + }, + 'ko-KR': { + title: '카드', + }, + }, +}) + +GlobalRegistry.setDesignerBehaviors([RootBehavior, InputBehavior, CardBehavior]) + +const Input = createResource({ + title: { + 'zh-CN': '输入框', + 'en-US': 'Input', + 'ko-KR': '입력 상자', + }, + icon: 'InputSource', + elements: [ + { + componentName: 'Field', + props: { + title: '输入框', + type: 'string', + 'x-decorator': 'FormItem', + 'x-component': 'Input', + }, + }, + ], +}) + +const Card = createResource({ + title: { + 'zh-CN': '卡片', + 'en-US': 'Card', + 'ko-KR': '카드 상자', + }, + icon: 'CardSource', + elements: [ + { + componentName: 'Card', + props: { + title: '卡片', + }, + }, + ], +}) + +GlobalRegistry.registerDesignerLocales({ + 'zh-CN': { + sources: { + Inputs: '输入控件', + Displays: '展示控件', + Feedbacks: '反馈控件', + }, + }, + 'en-US': { + sources: { + Inputs: 'Inputs', + Displays: 'Displays', + Feedbacks: 'Feedbacks', + }, + }, + 'ko-KR': { + sources: { + Inputs: '입력', + Displays: '디스플레이', + Feedbacks: '피드백', + }, + }, +}) + +const Logo: React.FC = () => ( +
+ +
+) + +const Actions = observer(() => ( + + { + GlobalRegistry.setDesignerLanguage(e.target.value) + }} + /> + + + + +)) + +const engine = createDesigner() +const App = () => { + return ( + + + } actions={}> + + + + + + + + + + + + + + + + + + {' '} + + + + {() => ( + + )} + + + {() => { + return ( +
+
123123
123123
123123
123123
`} + /> + + ) + }} +
+
+
+
+ + + + + {' '} + + + + {() => ( + + )} + + + {() => { + return ( +
+
123123
123123
123123
123123
`} + /> + + ) + }} +
+
+
+
+ + + +
+
+
+ ) +} + +ReactDOM.render(, document.getElementById('root')) diff --git a/examples/sandbox-multi-workspace/src/sandbox.tsx b/examples/sandbox-multi-workspace/src/sandbox.tsx new file mode 100644 index 000000000..2959d652c --- /dev/null +++ b/examples/sandbox-multi-workspace/src/sandbox.tsx @@ -0,0 +1,7 @@ +import React from 'react' +import { Content } from './content' +import { renderSandboxContent } from '@designable/react-sandbox' + +renderSandboxContent(() => { + return +}) diff --git a/examples/sandbox-multi-workspace/tsconfig.build.json b/examples/sandbox-multi-workspace/tsconfig.build.json new file mode 100644 index 000000000..8f2e5f5a9 --- /dev/null +++ b/examples/sandbox-multi-workspace/tsconfig.build.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./lib", + "paths": { + "@designable/*": ["../*"] + }, + "declaration": true + } +} diff --git a/examples/sandbox-multi-workspace/tsconfig.json b/examples/sandbox-multi-workspace/tsconfig.json new file mode 100644 index 000000000..c6865c25a --- /dev/null +++ b/examples/sandbox-multi-workspace/tsconfig.json @@ -0,0 +1,5 @@ +{ + "extends": "../../tsconfig.json", + "include": ["./src/**/*.ts", "./src/**/*.tsx"], + "exclude": ["./src/__tests__/*", "./esm/*", "./lib/*"] +} diff --git a/package.json b/package.json index fec7ab5aa..1c2069b59 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ "start:playground": "npm run start:basic", "start:basic": "yarn workspace @designable/basic-example start", "start:sandbox": "yarn workspace @designable/sandbox-example start", + "start:multi-workspace": "yarn workspace @designable/multi-workspace-example start", + "start:sandbox-multi-workspace": "yarn workspace @designable/sandbox-multi-workspace-example start", "test": "jest --coverage", "test:watch": "jest --watch", "test:prod": "jest --coverage --silent", diff --git a/packages/core/src/effects/useSelectionEffect.ts b/packages/core/src/effects/useSelectionEffect.ts index 1fdf01849..a29e79eff 100644 --- a/packages/core/src/effects/useSelectionEffect.ts +++ b/packages/core/src/effects/useSelectionEffect.ts @@ -14,7 +14,7 @@ export const useSelectionEffect = (engine: Engine) => { `*[${engine.props.nodeSelectionIdAttrName}]` ) const currentWorkspace = - event.context.workspace ?? engine.workbench.activeWorkspace + event.context?.workspace ?? engine.workbench.activeWorkspace if (!currentWorkspace) return if (!el?.getAttribute) { const point = new Point(event.data.topClientX, event.data.topClientY) diff --git a/packages/core/src/models/Viewport.ts b/packages/core/src/models/Viewport.ts index b589fe6c4..b9903fb05 100644 --- a/packages/core/src/models/Viewport.ts +++ b/packages/core/src/models/Viewport.ts @@ -191,6 +191,8 @@ export class Viewport { if (this.isIframe) { this.workspace.detachEvents(this.contentWindow) this.workspace.detachEvents(this.viewportElement) + } else if (this.viewportElement) { + this.workspace.detachEvents(this.viewportElement) } } @@ -403,8 +405,8 @@ export class Viewport { if (!node) return const rect = this.getElementRectById(node.id) if (node && node === node.root) { - if (!rect) return this.innerRect - return calcBoundingRect([this.innerRect, rect]) + if (!rect) return this.rect + return calcBoundingRect([this.rect, rect]) } if (rect) { diff --git a/packages/react/src/containers/Viewport.tsx b/packages/react/src/containers/Viewport.tsx index ddcc52492..ea7b2458a 100644 --- a/packages/react/src/containers/Viewport.tsx +++ b/packages/react/src/containers/Viewport.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from 'react' +import React, { useLayoutEffect, useRef, useState } from 'react' import { usePrefix, useViewport } from '../hooks' import { AuxToolWidget, EmptyWidget } from '../widgets' import { Viewport as ViewportType } from '@designable/core' @@ -21,7 +21,7 @@ export const Viewport: React.FC = ({ const ref = useRef() const viewportRef = useRef() const isFrameRef = useRef(false) - useEffect(() => { + useLayoutEffect(() => { const frameElement = ref.current.querySelector('iframe') if (!viewport) return if (viewportRef.current && viewportRef.current !== viewport) { diff --git a/packages/react/src/hooks/useValidNodeOffsetRect.ts b/packages/react/src/hooks/useValidNodeOffsetRect.ts index d04324fc3..f8f7d1483 100644 --- a/packages/react/src/hooks/useValidNodeOffsetRect.ts +++ b/packages/react/src/hooks/useValidNodeOffsetRect.ts @@ -32,7 +32,7 @@ export const useValidNodeOffsetRect = (node: TreeNode) => { }, [viewport, node]) useEffect(() => { - if (!element) return + if (!element || !element.isConnected) return if (observerRef.current) { observerRef.current.disconnect() } diff --git a/packages/shared/src/event.ts b/packages/shared/src/event.ts index 55ba76098..140a54a7d 100644 --- a/packages/shared/src/event.ts +++ b/packages/shared/src/event.ts @@ -351,7 +351,7 @@ export class Event extends Subscribable> { }) return } - if (container instanceof Window) { + if (isWindow(container)) { return this.detachEvents(container.document) } if (!container[ATTACHED_SYMBOL]) return