diff --git a/packages/editor/src/components/FloatingBox.vue b/packages/editor/src/components/FloatingBox.vue new file mode 100644 index 000000000..66aec2791 --- /dev/null +++ b/packages/editor/src/components/FloatingBox.vue @@ -0,0 +1,117 @@ + + + diff --git a/packages/editor/src/components/TreeNode.vue b/packages/editor/src/components/TreeNode.vue index aeadddd11..ce662f04f 100644 --- a/packages/editor/src/components/TreeNode.vue +++ b/packages/editor/src/components/TreeNode.vue @@ -108,7 +108,7 @@ const selected = computed(() => nodeStatus.value.selected); const visible = computed(() => nodeStatus.value.visible); const draggable = computed(() => nodeStatus.value.draggable); -const hasChilren = computed(() => props.data.items && props.data.items.length > 0); +const hasChilren = computed(() => props.data.items?.some((item) => props.nodeStatusMap.get(item.id)?.visible)); const handleDragStart = (event: DragEvent) => { treeEmit?.('node-dragstart', event, props.data); diff --git a/packages/editor/src/layouts/sidebar/Sidebar.vue b/packages/editor/src/layouts/sidebar/Sidebar.vue index 49864c25f..812c15696 100644 --- a/packages/editor/src/layouts/sidebar/Sidebar.vue +++ b/packages/editor/src/layouts/sidebar/Sidebar.vue @@ -4,13 +4,13 @@
{{ config.text }}
diff --git a/packages/editor/src/layouts/sidebar/layer/LayerPanel.vue b/packages/editor/src/layouts/sidebar/layer/LayerPanel.vue index 6636b45bc..94669d691 100644 --- a/packages/editor/src/layouts/sidebar/layer/LayerPanel.vue +++ b/packages/editor/src/layouts/sidebar/layer/LayerPanel.vue @@ -39,10 +39,11 @@ import { computed, inject, ref } from 'vue'; import { TMagicScrollbar } from '@tmagic/design'; +import type { MNode } from '@tmagic/schema'; import SearchInput from '@editor/components/SearchInput.vue'; import Tree from '@editor/components/Tree.vue'; -import { LayerPanelSlots, MenuButton, MenuComponent, Services } from '@editor/type'; +import type { LayerPanelSlots, MenuButton, MenuComponent, Services } from '@editor/type'; import LayerMenu from './LayerMenu.vue'; import LayerNodeTool from './LayerNodeTool.vue'; @@ -69,10 +70,21 @@ const tree = ref>(); const page = computed(() => editorService?.get('page')); -const { nodeStatusMap } = useNodeStatus(services, page); +const { nodeStatusMap } = useNodeStatus(services); const { isCtrlKeyDown } = useKeybinding(services, tree); -const { filterTextChangeHandler } = useFilter(nodeStatusMap, page); +const filterNodeMethod = (v: string, data: MNode): boolean => { + let name = ''; + if (data.name) { + name = data.name; + } else if (data.items) { + name = 'container'; + } + + return `${data.id}${name}${data.type}`.includes(v); +}; + +const { filterTextChangeHandler } = useFilter(services, nodeStatusMap, filterNodeMethod); const collapseAllHandler = () => { if (!page.value || !nodeStatusMap.value) return; diff --git a/packages/editor/src/layouts/sidebar/layer/use-filter.ts b/packages/editor/src/layouts/sidebar/layer/use-filter.ts index 9e856aef0..60680f81d 100644 --- a/packages/editor/src/layouts/sidebar/layer/use-filter.ts +++ b/packages/editor/src/layouts/sidebar/layer/use-filter.ts @@ -1,58 +1,60 @@ -import { type ComputedRef, ref } from 'vue'; +import { computed, type ComputedRef, ref } from 'vue'; -import { Id, MNode, MPage } from '@tmagic/schema'; +import { Id, MNode } from '@tmagic/schema'; -import { LayerNodeStatus } from '@editor/type'; +import { LayerNodeStatus, Services } from '@editor/type'; import { traverseNode } from '@editor/utils'; import { updateStatus } from '@editor/utils/tree'; export const useFilter = ( + services: Services | undefined, nodeStatusMap: ComputedRef | undefined>, - page: ComputedRef, + filterNodeMethod: (value: string, data: MNode) => boolean, ) => { - // tree方法:对树节点进行筛选时执行的方法 - const filterIsMatch = (value: string, data: MNode): boolean => { - if (!value) { - return true; - } - let name = ''; - if (data.name) { - name = data.name; - } else if (data.items) { - name = 'container'; - } - return `${data.id}${name}${data.type}`.includes(value); - }; + const page = computed(() => services?.editorService.get('page')); - const filterNode = (text: string) => (node: MNode, parents: MNode[]) => { - if (!nodeStatusMap.value) return; + // tree方法:对树节点进行筛选时执行的方法 + const filterIsMatch = (value: string | string[], data: MNode): boolean => { + const string = !Array.isArray(value) ? [value] : value; - const visible = filterIsMatch(text, node); - if (visible && parents.length) { - parents.forEach((parent) => { - updateStatus(nodeStatusMap.value!, parent.id, { - visible, - expand: true, - }); - }); + if (!string.length) { + return true; } - updateStatus(nodeStatusMap.value, node.id, { - visible, - }); + return string.some((v) => filterNodeMethod(v, data)); }; - const filter = (text: string) => { + const filter = (text: string | string[]) => { if (!page.value?.items?.length) return; page.value.items.forEach((node) => { - traverseNode(node, filterNode(text)); + traverseNode(node, (node: MNode, parents: MNode[]) => { + if (!nodeStatusMap.value) return; + + const visible = filterIsMatch(text, node); + if (visible && parents.length) { + console.log( + node.id, + parents.map((a) => a.id), + ); + parents.forEach((parent) => { + updateStatus(nodeStatusMap.value!, parent.id, { + visible, + expand: true, + }); + }); + } + + updateStatus(nodeStatusMap.value, node.id, { + visible, + }); + }); }); }; return { filterText: ref(''), - filterTextChangeHandler(text: string) { + filterTextChangeHandler(text: string | string[]) { filter(text); }, }; diff --git a/packages/editor/src/layouts/sidebar/layer/use-node-status.ts b/packages/editor/src/layouts/sidebar/layer/use-node-status.ts index f918d886b..65dad93e7 100644 --- a/packages/editor/src/layouts/sidebar/layer/use-node-status.ts +++ b/packages/editor/src/layouts/sidebar/layer/use-node-status.ts @@ -1,4 +1,4 @@ -import { computed, type ComputedRef, ref, watch } from 'vue'; +import { computed, ref, watch } from 'vue'; import type { Id, MNode, MPage } from '@tmagic/schema'; import { getNodePath } from '@tmagic/utils'; @@ -7,21 +7,17 @@ import { LayerNodeStatus, Services } from '@editor/type'; import { traverseNode } from '@editor/utils'; import { updateStatus } from '@editor/utils/tree'; -const createPageNodeStatus = ( - services: Services | undefined, - pageId: Id, - initalLayerNodeStatus?: Map, -) => { +const createPageNodeStatus = (page: MPage, initalLayerNodeStatus?: Map) => { const map = new Map(); - map.set(pageId, { + map.set(page.id, { visible: true, expand: true, selected: true, draggable: false, }); - services?.editorService.getNodeById(pageId)?.items.forEach((node: MNode) => + page.items.forEach((node: MNode) => traverseNode(node, (node) => { map.set( node.id, @@ -38,7 +34,8 @@ const createPageNodeStatus = ( return map; }; -export const useNodeStatus = (services: Services | undefined, page: ComputedRef) => { +export const useNodeStatus = (services: Services | undefined) => { + const page = computed(() => services?.editorService.get('page')); const nodes = computed(() => services?.editorService.get('nodes') || []); /** 所有页面的节点状态 */ @@ -57,7 +54,7 @@ export const useNodeStatus = (services: Services | undefined, page: ComputedRef< return; } // 生成节点状态 - nodeStatusMaps.value.set(page.id, createPageNodeStatus(services, page.id, nodeStatusMaps.value.get(page.id))); + nodeStatusMaps.value.set(page.id, createPageNodeStatus(page, nodeStatusMaps.value.get(page.id))); }, { immediate: true, diff --git a/packages/editor/src/layouts/workspace/viewer/NodeListMenu.vue b/packages/editor/src/layouts/workspace/viewer/NodeListMenu.vue index 91df162d0..2105b3ef1 100644 --- a/packages/editor/src/layouts/workspace/viewer/NodeListMenu.vue +++ b/packages/editor/src/layouts/workspace/viewer/NodeListMenu.vue @@ -1,112 +1,63 @@ diff --git a/packages/editor/src/theme/floating-box.scss b/packages/editor/src/theme/floating-box.scss new file mode 100644 index 000000000..014570af8 --- /dev/null +++ b/packages/editor/src/theme/floating-box.scss @@ -0,0 +1,30 @@ +.m-editor-float-box { + position: absolute; + background-color: #fff; + z-index: 100; + border: 1px solid $--border-color; + display: flex; + flex-direction: column; + + .m-editor-float-box-title { + text-align: center; + font-size: 14px; + font-weight: 600; + padding: 5px; + cursor: move; + display: flex; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid $--border-color; + } + + .m-editor-float-box-body { + padding: 5px; + flex: 1; + overflow: auto; + } +} + +.m-editor-floating-box-moveable { + opacity: 0; +} diff --git a/packages/editor/src/theme/stage.scss b/packages/editor/src/theme/stage.scss index 88f9a5c1d..9ae994b13 100644 --- a/packages/editor/src/theme/stage.scss +++ b/packages/editor/src/theme/stage.scss @@ -25,3 +25,27 @@ width: 0 !important; } } + +.m-editor-stage-float-button { + cursor: pointer; + transform: translateY(-50%); + width: 12px; + font-size: 12px; + line-height: 1.2; + position: absolute; + left: 100%; + top: 50%; + padding: 5px; + background-color: #ffffff; + transition: background-color 0.2s; + color: rgba(0, 0, 0, 0.88); + box-shadow: 0 6px 16px 0 rgba(0, 0, 0, 0.08), + 0 3px 6px -4px rgba(0, 0, 0, 0.12), 0 9px 28px 8px rgba(0, 0, 0, 0.05); +} + +.m-editor-node-list-menu { + height: 100%; + width: 100%; + min-width: 300px; + max-height: 500px; +} diff --git a/packages/editor/src/theme/theme.scss b/packages/editor/src/theme/theme.scss index 058c3f435..3efeed546 100644 --- a/packages/editor/src/theme/theme.scss +++ b/packages/editor/src/theme/theme.scss @@ -24,3 +24,4 @@ @import "./key-value.scss"; @import "./floatbox.scss"; @import "./tree.scss"; +@import "./floating-box.scss"; diff --git a/packages/editor/src/utils/editor.ts b/packages/editor/src/utils/editor.ts index 6946abd0a..2e8dc8584 100644 --- a/packages/editor/src/utils/editor.ts +++ b/packages/editor/src/utils/editor.ts @@ -263,7 +263,7 @@ export const traverseNode = (node: MNode, cb: (node: MNode, parents: MNode[]) => if (node.items?.length) { parents.push(node); node.items.forEach((item: MNode) => { - traverseNode(item, cb, parents); + traverseNode(item, cb, [...parents]); }); } }; diff --git a/packages/stage/src/ActionManager.ts b/packages/stage/src/ActionManager.ts index 17f49a003..97c33ff76 100644 --- a/packages/stage/src/ActionManager.ts +++ b/packages/stage/src/ActionManager.ts @@ -561,13 +561,13 @@ export default class ActionManager extends EventEmitter { /** * 在up事件中负责对外通知选中事件,通知画布之外的编辑器更新 */ - private mouseUpHandler = (): void => { + private mouseUpHandler = (event: MouseEvent): void => { getDocument().removeEventListener('mouseup', this.mouseUpHandler); this.container.addEventListener('mousemove', this.mouseMoveHandler); if (this.isMultiSelectStatus) { - this.emit('multi-select', this.selectedElList); + this.emit('multi-select', this.selectedElList, event); } else { - this.emit('select', this.selectedEl); + this.emit('select', this.selectedEl, event); } }; diff --git a/packages/stage/src/StageCore.ts b/packages/stage/src/StageCore.ts index 570a4c634..e4c0c984f 100644 --- a/packages/stage/src/StageCore.ts +++ b/packages/stage/src/StageCore.ts @@ -307,14 +307,14 @@ export default class StageCore extends EventEmitter { .on('before-select', (idOrEl: Id | HTMLElement, event?: MouseEvent) => { this.select(idOrEl, event); }) - .on('select', (selectedEl: HTMLElement) => { - this.emit('select', selectedEl); + .on('select', (selectedEl: HTMLElement, event: MouseEvent) => { + this.emit('select', selectedEl, event); }) .on('before-multi-select', (idOrElList: HTMLElement[] | Id[]) => { this.multiSelect(idOrElList); }) - .on('multi-select', (selectedElList: HTMLElement[]) => { - this.emit('multi-select', selectedElList); + .on('multi-select', (selectedElList: HTMLElement[], event: MouseEvent) => { + this.emit('multi-select', selectedElList, event); }); }