From 625cba310c1a66b7f1e7baacbd11ffbe3e7edb8f Mon Sep 17 00:00:00 2001 From: kangwei Date: Fri, 14 Feb 2020 02:32:12 +0800 Subject: [PATCH] initial --- packages/designer/.editorconfig | 16 + packages/designer/.eslintignore | 6 + packages/designer/.eslintrc | 3 + packages/designer/.gitignore | 40 + packages/designer/.prettierrc | 6 + packages/designer/package.json | 40 + .../src/builtins/drag-ghost/ghost.less | 29 + .../src/builtins/drag-ghost/ghost.tsx | 105 +++ .../designer/src/builtins/embed-editor.ts | 59 ++ .../src/builtins/simulator/auxilary/README.md | 22 + .../simulator/auxilary/auxiliary.less | 20 + .../builtins/simulator/auxilary/auxiliary.tsx | 31 + .../builtins/simulator/auxilary/droping.ts | 13 + .../builtins/simulator/auxilary/edging.less | 39 + .../builtins/simulator/auxilary/edging.tsx | 62 ++ .../auxilary/embed-editor-toolbar.tsx | 12 + .../src/builtins/simulator/auxilary/index.ts | 1 + .../simulator/auxilary/insertion.less | 23 + .../builtins/simulator/auxilary/insertion.tsx | 139 ++++ .../simulator/auxilary/offset-observer.ts | 60 ++ .../simulator/auxilary/selecting.less | 39 + .../builtins/simulator/auxilary/selecting.tsx | 85 ++ .../builtins/simulator/create-simulator.ts | 80 ++ .../designer/src/builtins/simulator/index.tsx | 0 .../designer/src/builtins/simulator/screen | 0 packages/designer/src/designer/canvas.less | 47 ++ packages/designer/src/designer/canvas.tsx | 76 ++ packages/designer/src/designer/designer.ts | 18 + .../src/designer/document/document-context.ts | 173 +++++ .../src/designer/document/document.tsx | 0 .../designer/src/designer/document/history.ts | 0 .../src/designer/document/location.ts | 123 +++ .../src/designer/document/master-board.ts | 733 ++++++++++++++++++ .../designer/src/designer/document/node.ts | 478 ++++++++++++ .../designer/src/designer/document/props.ts | 558 +++++++++++++ .../src/designer/document/root-node.ts | 133 ++++ .../src/designer/document/scroller.ts | 134 ++++ .../src/designer/document/selection.ts | 151 ++++ .../src/designer/document/stash-space.ts | 65 ++ .../src/designer/document/viewport.ts | 96 +++ packages/designer/src/designer/dragon.ts | 349 +++++++++ packages/designer/src/designer/hotkey.ts | 114 +++ packages/designer/src/designer/index.ts | 0 packages/designer/src/designer/project.ts | 76 ++ packages/designer/src/designer/schema.ts | 159 ++++ .../src/designer/simulator-interface.ts | 0 packages/designer/src/designer/workspace.tsx | 0 packages/designer/src/index.ts | 0 packages/designer/src/utils/clipboard.ts | 95 +++ packages/designer/src/utils/clone-deep.ts | 23 + packages/designer/src/utils/create-content.ts | 17 + packages/designer/src/utils/create-defer.ts | 17 + packages/designer/src/utils/cursor.less | 15 + packages/designer/src/utils/cursor.ts | 60 ++ packages/designer/src/utils/dom.ts | 19 + .../designer/src/utils/get-prototype-of.ts | 7 + .../designer/src/utils/has-own-property.ts | 4 + packages/designer/src/utils/hotkey.ts | 618 +++++++++++++++ packages/designer/src/utils/index.ts | 9 + packages/designer/src/utils/is-css-url.ts | 3 + packages/designer/src/utils/is-es-module.ts | 3 + packages/designer/src/utils/is-function.ts | 3 + packages/designer/src/utils/is-object.ts | 3 + .../designer/src/utils/is-plain-object.ts | 9 + packages/designer/src/utils/is-react.ts | 9 + packages/designer/src/utils/parse-code.ts | 7 + packages/designer/src/utils/path.ts | 173 +++++ packages/designer/src/utils/react.ts | 32 + packages/designer/src/utils/script.ts | 38 + .../designer/src/utils/set-prototype-of.ts | 8 + packages/designer/src/utils/shallow-equal.ts | 27 + packages/designer/src/utils/style-point.ts | 55 ++ packages/designer/src/utils/throttle.ts | 100 +++ packages/designer/src/utils/type-check.ts | 11 + packages/designer/src/utils/unique-id.ts | 4 + .../designer/src/utils/value-to-source.ts | 232 ++++++ packages/designer/tsconfig.json | 9 + 77 files changed, 6023 insertions(+) create mode 100644 packages/designer/.editorconfig create mode 100644 packages/designer/.eslintignore create mode 100644 packages/designer/.eslintrc create mode 100644 packages/designer/.gitignore create mode 100644 packages/designer/.prettierrc create mode 100644 packages/designer/package.json create mode 100644 packages/designer/src/builtins/drag-ghost/ghost.less create mode 100644 packages/designer/src/builtins/drag-ghost/ghost.tsx create mode 100644 packages/designer/src/builtins/embed-editor.ts create mode 100644 packages/designer/src/builtins/simulator/auxilary/README.md create mode 100644 packages/designer/src/builtins/simulator/auxilary/auxiliary.less create mode 100644 packages/designer/src/builtins/simulator/auxilary/auxiliary.tsx create mode 100644 packages/designer/src/builtins/simulator/auxilary/droping.ts create mode 100644 packages/designer/src/builtins/simulator/auxilary/edging.less create mode 100644 packages/designer/src/builtins/simulator/auxilary/edging.tsx create mode 100644 packages/designer/src/builtins/simulator/auxilary/embed-editor-toolbar.tsx create mode 100644 packages/designer/src/builtins/simulator/auxilary/index.ts create mode 100644 packages/designer/src/builtins/simulator/auxilary/insertion.less create mode 100644 packages/designer/src/builtins/simulator/auxilary/insertion.tsx create mode 100644 packages/designer/src/builtins/simulator/auxilary/offset-observer.ts create mode 100644 packages/designer/src/builtins/simulator/auxilary/selecting.less create mode 100644 packages/designer/src/builtins/simulator/auxilary/selecting.tsx create mode 100644 packages/designer/src/builtins/simulator/create-simulator.ts create mode 100644 packages/designer/src/builtins/simulator/index.tsx create mode 100644 packages/designer/src/builtins/simulator/screen create mode 100644 packages/designer/src/designer/canvas.less create mode 100644 packages/designer/src/designer/canvas.tsx create mode 100644 packages/designer/src/designer/designer.ts create mode 100644 packages/designer/src/designer/document/document-context.ts create mode 100644 packages/designer/src/designer/document/document.tsx create mode 100644 packages/designer/src/designer/document/history.ts create mode 100644 packages/designer/src/designer/document/location.ts create mode 100644 packages/designer/src/designer/document/master-board.ts create mode 100644 packages/designer/src/designer/document/node.ts create mode 100644 packages/designer/src/designer/document/props.ts create mode 100644 packages/designer/src/designer/document/root-node.ts create mode 100644 packages/designer/src/designer/document/scroller.ts create mode 100644 packages/designer/src/designer/document/selection.ts create mode 100644 packages/designer/src/designer/document/stash-space.ts create mode 100644 packages/designer/src/designer/document/viewport.ts create mode 100644 packages/designer/src/designer/dragon.ts create mode 100644 packages/designer/src/designer/hotkey.ts create mode 100644 packages/designer/src/designer/index.ts create mode 100644 packages/designer/src/designer/project.ts create mode 100644 packages/designer/src/designer/schema.ts create mode 100644 packages/designer/src/designer/simulator-interface.ts create mode 100644 packages/designer/src/designer/workspace.tsx create mode 100644 packages/designer/src/index.ts create mode 100644 packages/designer/src/utils/clipboard.ts create mode 100644 packages/designer/src/utils/clone-deep.ts create mode 100644 packages/designer/src/utils/create-content.ts create mode 100644 packages/designer/src/utils/create-defer.ts create mode 100644 packages/designer/src/utils/cursor.less create mode 100644 packages/designer/src/utils/cursor.ts create mode 100644 packages/designer/src/utils/dom.ts create mode 100644 packages/designer/src/utils/get-prototype-of.ts create mode 100644 packages/designer/src/utils/has-own-property.ts create mode 100644 packages/designer/src/utils/hotkey.ts create mode 100644 packages/designer/src/utils/index.ts create mode 100644 packages/designer/src/utils/is-css-url.ts create mode 100644 packages/designer/src/utils/is-es-module.ts create mode 100644 packages/designer/src/utils/is-function.ts create mode 100644 packages/designer/src/utils/is-object.ts create mode 100644 packages/designer/src/utils/is-plain-object.ts create mode 100644 packages/designer/src/utils/is-react.ts create mode 100644 packages/designer/src/utils/parse-code.ts create mode 100644 packages/designer/src/utils/path.ts create mode 100644 packages/designer/src/utils/react.ts create mode 100644 packages/designer/src/utils/script.ts create mode 100644 packages/designer/src/utils/set-prototype-of.ts create mode 100644 packages/designer/src/utils/shallow-equal.ts create mode 100644 packages/designer/src/utils/style-point.ts create mode 100644 packages/designer/src/utils/throttle.ts create mode 100644 packages/designer/src/utils/type-check.ts create mode 100644 packages/designer/src/utils/unique-id.ts create mode 100644 packages/designer/src/utils/value-to-source.ts create mode 100644 packages/designer/tsconfig.json diff --git a/packages/designer/.editorconfig b/packages/designer/.editorconfig new file mode 100644 index 000000000..16a029ac9 --- /dev/null +++ b/packages/designer/.editorconfig @@ -0,0 +1,16 @@ +# EditorConfig is awesome: http://EditorConfig.org + +# top-most EditorConfig file +root = true + +# Tab indentation +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[*.md] +trim_trailing_whitespace = false diff --git a/packages/designer/.eslintignore b/packages/designer/.eslintignore new file mode 100644 index 000000000..1fb2edf7c --- /dev/null +++ b/packages/designer/.eslintignore @@ -0,0 +1,6 @@ +.idea/ +.vscode/ +build/ +.* +~* +node_modules diff --git a/packages/designer/.eslintrc b/packages/designer/.eslintrc new file mode 100644 index 000000000..db78d35d1 --- /dev/null +++ b/packages/designer/.eslintrc @@ -0,0 +1,3 @@ +{ + "extends": "./node_modules/@recore/config/.eslintrc" +} diff --git a/packages/designer/.gitignore b/packages/designer/.gitignore new file mode 100644 index 000000000..5261403b4 --- /dev/null +++ b/packages/designer/.gitignore @@ -0,0 +1,40 @@ +node_modules/ +coverage/ +build/ +dist/ +.idea/ +.vscode/ +.theia/ +.recore/ +~* +package-lock.json + +# 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 +.Trash* +*.swp +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db diff --git a/packages/designer/.prettierrc b/packages/designer/.prettierrc new file mode 100644 index 000000000..8748c5ed3 --- /dev/null +++ b/packages/designer/.prettierrc @@ -0,0 +1,6 @@ +{ + "semi": true, + "singleQuote": true, + "printWidth": 120, + "trailingComma": "all" +} diff --git a/packages/designer/package.json b/packages/designer/package.json new file mode 100644 index 000000000..e302646e8 --- /dev/null +++ b/packages/designer/package.json @@ -0,0 +1,40 @@ +{ + "name": "lowcode-designer", + "version": "0.9.0", + "description": "alibaba lowcode designer", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "MIT", + "dependencies": { + "@recore/obx": "^1.0.5", + "@types/medium-editor": "^5.0.3", + "classnames": "^2.2.6", + "react": "^16", + "react-dom": "^16.7.0" + }, + "devDependencies": { + "@types/classnames": "^2.2.7", + "@types/jest": "^24.0.16", + "@types/react": "^16", + "@types/react-dom": "^16", + "@recore/config": "^2.0.0", + "ts-jest": "^24.0.2", + "ts-node": "^8.0.1", + "eslint": "^6.5.1", + "husky": "^1.1.2", + "jest": "^23.4.1", + "lint-staged": "^7.1.2", + "tslib": "^1.9.3", + "typescript": "^3.1.3", + "prettier": "^1.18.2" + }, + "lint-staged": { + "./src/**/*.{ts,tsx}": [ + "eslint --fix", + "git add" + ] + } +} diff --git a/packages/designer/src/builtins/drag-ghost/ghost.less b/packages/designer/src/builtins/drag-ghost/ghost.less new file mode 100644 index 000000000..c470c4ebc --- /dev/null +++ b/packages/designer/src/builtins/drag-ghost/ghost.less @@ -0,0 +1,29 @@ +.my-ghost-group { + box-sizing: border-box; + position: fixed; + z-index: 99999; + width: 100px; + display: flex; + flex-direction: column; + align-items: center; + pointer-events: none; + background-color: rgba(0, 0, 0, 0.4); + opacity: 0.5; + .my-ghost { + .my-ghost-title { + text-align: center; + font-size: var(--font-size-text); + text-overflow: ellipsis; + color: var(--color-text-light); + white-space: nowrap; + overflow: hidden; + } + } +} + +.dragging { + position: fixed; + z-index: 99999; + width: 100%; + box-shadow: 0 0 6px grey; +} diff --git a/packages/designer/src/builtins/drag-ghost/ghost.tsx b/packages/designer/src/builtins/drag-ghost/ghost.tsx new file mode 100644 index 000000000..972516abe --- /dev/null +++ b/packages/designer/src/builtins/drag-ghost/ghost.tsx @@ -0,0 +1,105 @@ +import { Component } from 'react'; +import { observer, obx } from '@ali/recore'; +import { dragon } from '../../globals/dragon'; + +import './ghost.less'; +import { OutlineBoardID } from '../builtin-panes/outline-pane/outline-board'; +// import { INode } from '../../document/node'; + +type offBinding = () => any; + +@observer +export default class Ghost extends Component { + private dispose: offBinding[] = []; + @obx.ref private dragment: any = null; + @obx.ref private x = 0; + @obx.ref private y = 0; + + componentWillMount() { + this.dispose = [ + dragon.onDragstart(e => { + this.dragment = e.dragTarget; + this.x = e.clientX; + this.y = e.clientY; + }), + dragon.onDrag(e => { + this.x = e.clientX; + this.y = e.clientY; + }), + dragon.onDragend(() => { + this.dragment = null; + this.x = 0; + this.y = 0; + }), + ]; + } + + shouldComponentUpdate() { + return false; + } + + componentWillUnmount() { + if (this.dispose) { + this.dispose.forEach(off => off()); + } + } + + renderGhostGroup() { + const dragment = this.dragment; + if (Array.isArray(dragment)) { + return dragment.map((node: any, index: number) => { + const ghost = ( +
+
{node.tagName}
+
+ ); + return ghost; + }); + } else { + return ( +
+
{dragment.tagName}
+
+ ); + } + } + + render() { + if (!this.dragment) { + return null; + } + + // let x = this.x; + // let y = this.y; + + // todo: 考虑多个图标、title、不同 sensor 区域的形态 + if (dragon.activeSensor && dragon.activeSensor.id === OutlineBoardID) { + // const nodeId = (this.dragment as INode).id; + // const elt = document.querySelector(`[data-id="${nodeId}"`) as HTMLDivElement; + // + // if (elt) { + // // do something + // // const target = elt.cloneNode(true) as HTMLDivElement; + // console.log('>>> target', elt); + // elt.classList.remove('hidden'); + // elt.classList.add('dragging'); + // elt.style.transform = `translate(${this.x}px, ${this.y}px)`; + // } + // + // return null; + // x -= 30; + // y += 30; + } + + return ( +
+ {this.renderGhostGroup()} +
+ ); + } +} diff --git a/packages/designer/src/builtins/embed-editor.ts b/packages/designer/src/builtins/embed-editor.ts new file mode 100644 index 000000000..89368a8e8 --- /dev/null +++ b/packages/designer/src/builtins/embed-editor.ts @@ -0,0 +1,59 @@ +import MediumEditor from 'medium-editor'; +import { computed, obx } from '@ali/recore'; +import { current } from './current'; +import ElementNode from '../document/node/element-node'; + +class EmbedEditor { + @obx container?: HTMLDivElement | null; + private _editor?: any; + @computed getEditor(): any | null { + if (this._editor) { + this._editor.destroy(); + this._editor = null; + } + const win = current.document!.contentWindow; + const doc = current.document!.ownerDocument; + if (!win || !doc || !this.container) { + return null; + } + + const rect = this.container.getBoundingClientRect(); + + this._editor = new MediumEditor([], { + contentWindow: win, + ownerDocument: doc, + toolbar: { + diffLeft: rect.left, + diffTop: rect.top - 10, + }, + elementsContainer: this.container, + }); + return this._editor; + } + + @obx.ref editing?: [ElementNode, string, HTMLElement]; + + edit(node: ElementNode, prop: string, el: HTMLElement) { + const ed = this.getEditor(); + if (!ed) { + return; + } + this.exitAndSave(); + console.info(el); + this.editing = [node, prop, el]; + ed.origElements = el; + ed.setup(); + } + + exitAndSave() { + this.editing = undefined; + // removeElements + // get content save to + } + + mount(container?: HTMLDivElement | null) { + this.container = container; + } +} + +export default new EmbedEditor(); diff --git a/packages/designer/src/builtins/simulator/auxilary/README.md b/packages/designer/src/builtins/simulator/auxilary/README.md new file mode 100644 index 000000000..bd959599f --- /dev/null +++ b/packages/designer/src/builtins/simulator/auxilary/README.md @@ -0,0 +1,22 @@ +辅助类 + 对齐线 + 插入指示 insertion 竖线 横线 插入块 禁止插入块 + 幽灵替身 ghost + 聚焦编辑指示 + + +插入指示 insertion 竖线 横线 插入块 禁止插入块 + +竖线:红色,绿色 +横线:红色,绿色 +插入块:透明绿色,透明红色 + +投放指示线 + +cover + +轮廓服务 + 悬停指示线 xray mode? + 选中指示线 + 投放指示线 + 透视线 x-ray diff --git a/packages/designer/src/builtins/simulator/auxilary/auxiliary.less b/packages/designer/src/builtins/simulator/auxilary/auxiliary.less new file mode 100644 index 000000000..0cd365d85 --- /dev/null +++ b/packages/designer/src/builtins/simulator/auxilary/auxiliary.less @@ -0,0 +1,20 @@ +.my-auxiliary { + pointer-events: none; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + overflow: visible; + z-index: 800; + .embed-editor-toolbar { + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + > * { + pointer-events: all; + } + } +} diff --git a/packages/designer/src/builtins/simulator/auxilary/auxiliary.tsx b/packages/designer/src/builtins/simulator/auxilary/auxiliary.tsx new file mode 100644 index 000000000..1e5e308fc --- /dev/null +++ b/packages/designer/src/builtins/simulator/auxilary/auxiliary.tsx @@ -0,0 +1,31 @@ +import { observer } from '@ali/recore'; +import { Component } from 'react'; +import { getCurrentDocument } from '../../globals'; +import './auxiliary.less'; +import { EdgingView } from './edging'; +import { InsertionView } from './insertion'; +import { SelectingView } from './selecting'; +import EmbedEditorToolbar from './embed-editor-toolbar'; + +@observer +export class AuxiliaryView extends Component { + shouldComponentUpdate() { + return false; + } + + render() { + const doc = getCurrentDocument(); + if (!doc || !doc.ready) { + return null; + } + const { scrollX, scrollY, scale } = doc.viewport; + return ( +
+ + + + +
+ ); + } +} diff --git a/packages/designer/src/builtins/simulator/auxilary/droping.ts b/packages/designer/src/builtins/simulator/auxilary/droping.ts new file mode 100644 index 000000000..4b1ce2884 --- /dev/null +++ b/packages/designer/src/builtins/simulator/auxilary/droping.ts @@ -0,0 +1,13 @@ +// outline +// insertion +/* +// 插入指示 insertion 竖线 横线 插入块 禁止插入块 + +竖线:红色,绿色 +横线:红色,绿色 +插入块:透明绿色,透明红色 + +投放指示线 + +cover +*/ diff --git a/packages/designer/src/builtins/simulator/auxilary/edging.less b/packages/designer/src/builtins/simulator/auxilary/edging.less new file mode 100644 index 000000000..733ae746d --- /dev/null +++ b/packages/designer/src/builtins/simulator/auxilary/edging.less @@ -0,0 +1,39 @@ +.my-edging { + box-sizing: border-box; + pointer-events: none; + position: absolute; + top: 0; + left: 0; + border: 1px dashed var(--color-brand-light); + z-index: 1; + background: rgba(95, 240, 114, 0.04); + will-change: transform, width, height; + transition-property: transform, width, height; + transition-duration: 60ms; + transition-timing-function: linear; + overflow: visible; + >.title { + position: absolute; + color: var(--color-brand-light); + top: -20px; + left: 0; + font-weight: lighter; + } + + &.x-shadow { + border-color: rgba(138, 93, 226, 0.8); + background: rgba(138, 93, 226, 0.04); + + >.title { + color: rgba(138, 93, 226, 1.0); + } + } + + &.x-flow { + border-color: rgba(255, 99, 8, 0.8); + background: rgba(255, 99, 8, 0.04); + >.title { + color: rgb(255, 99, 8); + } + } +} diff --git a/packages/designer/src/builtins/simulator/auxilary/edging.tsx b/packages/designer/src/builtins/simulator/auxilary/edging.tsx new file mode 100644 index 000000000..2cde1a9aa --- /dev/null +++ b/packages/designer/src/builtins/simulator/auxilary/edging.tsx @@ -0,0 +1,62 @@ +import { observer } from '@ali/recore'; +import { Component } from 'react'; +import { edging } from '../../globals/edging'; +import './edging.less'; +import { hasConditionFlow } from '../../document/node'; +import { isShadowNode } from '../../document/node/shadow-node'; +import { isConditionFlow } from '../../document/node/condition-flow'; +import { current } from '../../globals'; + +@observer +export class EdgingView extends Component { + shouldComponentUpdate() { + return false; + } + + render() { + const node = edging.watching; + if (!node || !edging.enable || (current.selection && current.selection.has(node.id))) { + return null; + } + + // TODO: think of multi rects + // TODO: findDOMNode cause a render bug + const rect = node.document.computeRect(node); + if (!rect) { + return null; + } + + const { scale, scrollTarget } = node.document.viewport; + + const sx = scrollTarget!.left; + const sy = scrollTarget!.top; + + const style = { + width: rect.width * scale, + height: rect.height * scale, + transform: `translate(${(sx + rect.left) * scale}px, ${(sy + rect.top) * scale}px)`, + } as any; + + let className = 'my-edging'; + + // handle x-for node + if (isShadowNode(node)) { + className += ' x-shadow'; + } + // handle x-if/else-if/else node + if (isConditionFlow(node) || hasConditionFlow(node)) { + className += ' x-flow'; + } + + // TODO: + // 1. thinkof icon + // 2. thinkof top|bottom|inner space + + return ( +
+ {/* eslint-disable-next-line jsx-a11y/anchor-is-valid */} + {(node as any).title || node.tagName} +
+ ); + } +} diff --git a/packages/designer/src/builtins/simulator/auxilary/embed-editor-toolbar.tsx b/packages/designer/src/builtins/simulator/auxilary/embed-editor-toolbar.tsx new file mode 100644 index 000000000..e8d6d8fac --- /dev/null +++ b/packages/designer/src/builtins/simulator/auxilary/embed-editor-toolbar.tsx @@ -0,0 +1,12 @@ +import { Component } from 'react'; +import embedEditor from '../../globals/embed-editor'; + +export default class EmbedEditorToolbar extends Component { + shouldComponentUpdate() { + return false; + } + + render() { + return
embedEditor.mount(shell)} />; + } +} diff --git a/packages/designer/src/builtins/simulator/auxilary/index.ts b/packages/designer/src/builtins/simulator/auxilary/index.ts new file mode 100644 index 000000000..61552f4e3 --- /dev/null +++ b/packages/designer/src/builtins/simulator/auxilary/index.ts @@ -0,0 +1 @@ +export * from './auxiliary'; diff --git a/packages/designer/src/builtins/simulator/auxilary/insertion.less b/packages/designer/src/builtins/simulator/auxilary/insertion.less new file mode 100644 index 000000000..871210b7b --- /dev/null +++ b/packages/designer/src/builtins/simulator/auxilary/insertion.less @@ -0,0 +1,23 @@ +.my-insertion { + position: absolute; + top: -1.5px; + left: 0; + z-index: 12; + pointer-events: none !important; + background-color: var(--color-brand-light); + height: 3px; + + &.cover { + top: 0; + height: auto; + width: auto; + opacity: 0.3; + } + + &.vertical { + top: 0; + left: -1.5px; + width: 3px; + height: auto; + } +} diff --git a/packages/designer/src/builtins/simulator/auxilary/insertion.tsx b/packages/designer/src/builtins/simulator/auxilary/insertion.tsx new file mode 100644 index 000000000..cc09d6ec4 --- /dev/null +++ b/packages/designer/src/builtins/simulator/auxilary/insertion.tsx @@ -0,0 +1,139 @@ +import { Component } from 'react'; +import { observer } from '@ali/recore'; +import { getCurrentDocument } from '../../globals'; +import './insertion.less'; +import Location, { isLocationChildrenDetail, isVertical, LocationChildrenDetail, Rect } from '../../document/location'; +import { isConditionFlow } from '../../document/node/condition-flow'; +import { getChildAt, INodeParent } from '../../document/node'; +import DocumentContext from '../../document/document-context'; + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function processPropDetail() { + // return { insertType: 'cover', coverEdge: ? }; +} + +interface InsertionData { + edge?: Rect; + insertType?: string; + vertical?: boolean; + nearRect?: Rect; + coverRect?: Rect; +} + +/** + * 处理拖拽子节点(INode)情况 + */ +function processChildrenDetail( + doc: DocumentContext, + target: INodeParent, + detail: LocationChildrenDetail, +): InsertionData { + const edge = doc.computeRect(target); + if (!edge) { + return {}; + } + + const ret: any = { + edge, + insertType: 'before', + }; + + if (isConditionFlow(target)) { + ret.insertType = 'cover'; + ret.coverRect = edge; + return ret; + } + + if (detail.near) { + const { node, pos, rect, align } = detail.near; + ret.nearRect = rect || doc.computeRect(node); + ret.vertical = align ? align === 'V' : isVertical(ret.nearRect); + ret.insertType = pos; + return ret; + } + + // from outline-tree: has index, but no near + // TODO: think of shadowNode & ConditionFlow + const { index } = detail; + let nearNode = getChildAt(target, index); + if (!nearNode) { + // index = 0, eg. nochild, + nearNode = getChildAt(target, index > 0 ? index - 1 : 0); + if (!nearNode) { + ret.insertType = 'cover'; + ret.coverRect = edge; + return ret; + } + ret.insertType = 'after'; + } + if (nearNode) { + ret.nearRect = doc.computeRect(nearNode); + ret.vertical = isVertical(ret.nearRect); + } + return ret; +} + +/** + * 将 detail 信息转换为页面"坐标"信息 + */ +function processDetail({ target, detail, document }: Location): InsertionData { + if (isLocationChildrenDetail(detail)) { + return processChildrenDetail(document, target, detail); + } else { + // TODO: others... + const edge = document.computeRect(target); + return edge ? { edge, insertType: 'cover', coverRect: edge } : {}; + } +} + +@observer +export class InsertionView extends Component { + shouldComponentUpdate() { + return false; + } + + render() { + const doc = getCurrentDocument(); + if (!doc || !doc.dropLocation) { + return null; + } + + const { scale, scrollTarget } = doc.viewport; + const sx = scrollTarget!.left; + const sy = scrollTarget!.top; + + const { edge, insertType, coverRect, nearRect, vertical } = processDetail(doc.dropLocation); + if (!edge) { + return null; + } + + let className = 'my-insertion'; + const style: any = {}; + let x: number; + let y: number; + if (insertType === 'cover') { + className += ' cover'; + x = (coverRect!.left + sx) * scale; + y = (coverRect!.top + sy) * scale; + style.width = coverRect!.width * scale; + style.height = coverRect!.height * scale; + } else { + if (!nearRect) { + return null; + } + if (vertical) { + className += ' vertical'; + x = ((insertType === 'before' ? nearRect.left : nearRect.right) + sx) * scale; + y = (nearRect.top + sy) * scale; + style.height = nearRect!.height * scale; + } else { + x = (nearRect.left + sx) * scale; + y = ((insertType === 'before' ? nearRect.top : nearRect.bottom) + sy) * scale; + style.width = nearRect.width * scale; + } + } + style.transform = `translate3d(${x}px, ${y}px, 0)`; + + return
; + } +} diff --git a/packages/designer/src/builtins/simulator/auxilary/offset-observer.ts b/packages/designer/src/builtins/simulator/auxilary/offset-observer.ts new file mode 100644 index 000000000..960674b47 --- /dev/null +++ b/packages/designer/src/builtins/simulator/auxilary/offset-observer.ts @@ -0,0 +1,60 @@ +import { obx } from '@ali/recore'; +import { INode } from '../../document/node'; + +export default class OffsetObserver { + @obx.ref offsetTop = 0; + @obx.ref offsetLeft = 0; + @obx.ref offsetRight = 0; + @obx.ref offsetBottom = 0; + @obx.ref height = 0; + @obx.ref width = 0; + @obx.ref hasOffset = false; + @obx.ref left = 0; + @obx.ref top = 0; + @obx.ref right = 0; + @obx.ref bottom = 0; + + private pid: number | undefined; + + constructor(node: INode) { + const document = node.document; + const scrollTarget = document.viewport.scrollTarget!; + + let pid: number; + const compute = () => { + if (pid !== this.pid) { + return; + } + + const rect = document.computeRect(node); + if (!rect) { + this.hasOffset = false; + return; + } + this.hasOffset = true; + this.offsetLeft = rect.left + scrollTarget.left; + this.offsetRight = rect.right + scrollTarget.left; + this.offsetTop = rect.top + scrollTarget.top; + this.offsetBottom = rect.bottom + scrollTarget.top; + this.height = rect.height; + this.width = rect.width; + this.left = rect.left; + this.top = rect.top; + this.right = rect.right; + this.bottom = rect.bottom; + this.pid = pid = (window as any).requestIdleCallback(compute); + }; + + // try first + compute(); + // try second, ensure the dom mounted + this.pid = pid = (window as any).requestIdleCallback(compute); + } + + destroy() { + if (this.pid) { + (window as any).cancelIdleCallback(this.pid); + } + this.pid = undefined; + } +} diff --git a/packages/designer/src/builtins/simulator/auxilary/selecting.less b/packages/designer/src/builtins/simulator/auxilary/selecting.less new file mode 100644 index 000000000..924c52d8c --- /dev/null +++ b/packages/designer/src/builtins/simulator/auxilary/selecting.less @@ -0,0 +1,39 @@ +.my-selecting { + pointer-events: none; + position: absolute; + top: 0; + left: 0; + border: 1px solid var(--color-brand-light); + z-index: 2; + overflow: visible; + >.title { + position: absolute; + color: var(--color-brand-light); + top: -20px; + left: 0; + font-weight: lighter; + } + &.dragging { + background: rgba(182, 178, 178, 0.8); + border: none; + pointer-events: all; + } + + &.x-shadow { + border-color: rgba(147, 112, 219, 1.0); + background: rgba(147, 112, 219, 0.04); + >.title { + color: rgba(147, 112, 219, 1.0); + } + &.highlight { + background: transparent; + } + } + + &.x-flow { + border-color: rgb(255, 99, 8); + >.title { + color: rgb(255, 99, 8); + } + } +} diff --git a/packages/designer/src/builtins/simulator/auxilary/selecting.tsx b/packages/designer/src/builtins/simulator/auxilary/selecting.tsx new file mode 100644 index 000000000..b89c35c99 --- /dev/null +++ b/packages/designer/src/builtins/simulator/auxilary/selecting.tsx @@ -0,0 +1,85 @@ +import { observer } from '@ali/recore'; +import { Component, Fragment } from 'react'; +import classNames from 'classnames'; +import { INode, isElementNode, isConfettiNode, hasConditionFlow } from '../../document/node'; +import OffsetObserver from './offset-observer'; +import './selecting.less'; +import { isShadowNode, isShadowsContainer } from '../../document/node/shadow-node'; +import { isConditionFlow } from '../../document/node/condition-flow'; +import { current, dragon } from '../../globals'; + +@observer +export class SingleSelectingView extends Component<{ node: INode; highlight?: boolean }> { + private offsetObserver: OffsetObserver; + + constructor(props: { node: INode; highlight?: boolean }) { + super(props); + this.offsetObserver = new OffsetObserver(props.node); + } + + render() { + if (!this.offsetObserver.hasOffset) { + return null; + } + + const scale = this.props.node.document.viewport.scale; + const { width, height, offsetTop, offsetLeft } = this.offsetObserver; + + const style = { + width: width * scale, + height: height * scale, + transform: `translate3d(${offsetLeft * scale}px, ${offsetTop * scale}px, 0)`, + } as any; + + const { node, highlight } = this.props; + + const className = classNames('my-selecting', { + 'x-shadow': isShadowNode(node), + 'x-flow': hasConditionFlow(node) || isConditionFlow(node), + highlight, + }); + + return
; + } +} + +@observer +export class SelectingView extends Component { + get selecting(): INode[] { + const sel = current.selection; + if (!sel) { + return []; + } + if (dragon.dragging) { + return sel.getTopNodes(); + } + + return sel.getNodes(); + } + render() { + return this.selecting.map(node => { + // select all nodes when doing x-for + if (isShadowsContainer(node)) { + // FIXME: thinkof nesting for + const views = []; + for (const shadowNode of (node as any).getShadows()!.values()) { + views.push(); + } + return {views}; + } else if (isShadowNode(node)) { + const shadows = node.origin.getShadows()!.values(); + const views = []; + for (const shadowNode of shadows) { + views.push(); + } + return {views}; + } + // select the visible node when doing x-if + else if (isConditionFlow(node)) { + return ; + } + + return ; + }); + } +} diff --git a/packages/designer/src/builtins/simulator/create-simulator.ts b/packages/designer/src/builtins/simulator/create-simulator.ts new file mode 100644 index 000000000..9c673e59b --- /dev/null +++ b/packages/designer/src/builtins/simulator/create-simulator.ts @@ -0,0 +1,80 @@ +import { getCurrentAdaptor } from '../globals'; +import Simulator from '../adaptors/simulator'; +import { isCSSUrl } from '../utils/is-css-url'; + +export interface AssetMap { + jsUrl?: string; + cssUrl?: string; + jsText?: string; + cssText?: string; +} +export type AssetList = string[]; +export type Assets = AssetMap[] | AssetList; + +function isAssetMap(obj: any): obj is AssetMap { + return obj && typeof obj === 'object'; +} + +export function createSimulator(iframe: HTMLIFrameElement, vendors: Assets = []): Promise> { + const currentAdaptor = getCurrentAdaptor(); + const win: any = iframe.contentWindow; + const doc = iframe.contentDocument!; + + const styles: string[] = []; + let scripts: string[] = []; + const afterScripts: string[] = []; + + vendors.forEach((asset: any) => { + if (!isAssetMap(asset)) { + if (isCSSUrl(asset)) { + asset = { cssUrl: asset }; + } else { + if (asset.startsWith('!')) { + afterScripts.push(``); + return; + } + asset = { jsUrl: asset }; + } + } + if (asset.jsText) { + scripts.push(``); + } + if (asset.jsUrl) { + scripts.push(``); + } + if (asset.cssUrl) { + styles.push(``); + } + if (asset.cssText) { + styles.push(``); + } + }); + + currentAdaptor.simulatorUrls.forEach(url => { + if (isCSSUrl(url)) { + styles.push(``); + } else { + scripts.push(``); + } + }); + scripts = scripts.concat(afterScripts); + + doc.open(); + doc.write(` +${styles.join('\n')} + +${scripts.join('\n')} +`); + doc.close(); + + return new Promise(resolve => { + if (win.VisionSimulator) { + return resolve(win.VisionSimulator); + } + const loaded = () => { + resolve(win.VisionSimulator); + win.removeEventListener('load', loaded); + }; + win.addEventListener('load', loaded); + }); +} diff --git a/packages/designer/src/builtins/simulator/index.tsx b/packages/designer/src/builtins/simulator/index.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/packages/designer/src/builtins/simulator/screen b/packages/designer/src/builtins/simulator/screen new file mode 100644 index 000000000..e69de29bb diff --git a/packages/designer/src/designer/canvas.less b/packages/designer/src/designer/canvas.less new file mode 100644 index 000000000..cfc57d730 --- /dev/null +++ b/packages/designer/src/designer/canvas.less @@ -0,0 +1,47 @@ +.my-canvas { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + margin: 10px; + box-shadow: 0 2px 10px 0 rgba(31,56,88,.15); +} + +html.my-show-topbar .my-canvas { + top: var(--topbar-height); +} +html.my-show-toolbar .my-canvas { + top: var(--toolbar-height); +} +html.my-show-topbar.my-show-toolbar .my-canvas { + top: calc(var(--topbar-height) + var(--topbar-height)); +} + +.my-screen { + top: 0; + bottom: 0; + width: 100%; + left: 0; + position: absolute; + overflow: hidden; +} + +.my-doc-shell { + position: absolute; + top: 0; + bottom: 0; + left: 0; + width: 100%; + overflow: hidden; + .my-doc-frame { + border: none; + transform-origin: 0 0; + height: 100%; + width: 100%; + } +} + +.my-drag-pane-mode .my-doc-shell { + pointer-events: none; +} diff --git a/packages/designer/src/designer/canvas.tsx b/packages/designer/src/designer/canvas.tsx new file mode 100644 index 000000000..3a6daadf3 --- /dev/null +++ b/packages/designer/src/designer/canvas.tsx @@ -0,0 +1,76 @@ +import { Component } from 'react'; +import { observer } from '@ali/recore'; +import { getCurrentDocument, screen, progressing } from '../../globals'; +import { AutoFit } from '../../document/viewport'; +import { AuxiliaryView } from '../auxiliary'; +import { PreLoaderView } from '../widgets/pre-loader'; +import DocumentContext from '../../document/document-context'; +import FocusingArea from '../widgets/focusing-area'; +import './canvas.less'; + +const Canvas = () => ( + { + const doc = getCurrentDocument(); + if (doc) { + doc.selection.clear(); + } + return false; + }} + > + + +); + +export default Canvas; + +@observer +class Screen extends Component { + render() { + const doc = getCurrentDocument(); + // TODO: thinkof multi documents + return ( +
screen.mount(elmt)} className="my-screen"> + {progressing.visible ? : null} + + {doc ? : null} +
+ ); + } +} + +@observer +class DocumentView extends Component<{ doc: DocumentContext }> { + componentWillUnmount() { + this.props.doc.sleep(); + } + + render() { + const { doc } = this.props; + const viewport = doc.viewport; + let shellStyle = {}; + let frameStyle = {}; + if (viewport.width !== AutoFit && viewport.height !== AutoFit) { + const shellWidth = viewport.width * viewport.scale; + const screenWidth = screen.width; + const shellLeft = shellWidth < screenWidth ? `calc((100% - ${shellWidth}px) / 2)` : 0; + shellStyle = { + width: shellWidth, + left: shellLeft, + }; + frameStyle = { + transform: `scale(${viewport.scale})`, + height: viewport.height, + width: viewport.width, + }; + } + + return ( +
+