mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-05-24 17:33:37 +00:00
新增编辑器配置项 alwaysMultiSelect(默认 false),开启后无需按住 Ctrl/Meta 键,组件树与画布点击即多选;当 disabledMultiSelect=true 时本配置失效。同步 在 stage 层 ActionManager 暴露 setAlwaysMultiSelect 方法用于运行时切换,并 补充组件树/服务/画布的状态联动、文档与单元测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
256 lines
8.5 KiB
Vue
256 lines
8.5 KiB
Vue
<template>
|
||
<Framework
|
||
:disabled-page-fragment="disabledPageFragment"
|
||
:page-bar-sort-options="pageBarSortOptions"
|
||
:page-filter-function="pageFilterFunction"
|
||
>
|
||
<template #header>
|
||
<slot name="header"></slot>
|
||
</template>
|
||
|
||
<template #nav>
|
||
<slot name="nav" :editorService="editorService"><TMagicNavMenu :data="menu"></TMagicNavMenu></slot>
|
||
</template>
|
||
|
||
<template #content-before>
|
||
<slot name="content-before"></slot>
|
||
</template>
|
||
|
||
<template #src-code><slot name="src-code" :editorService="editorService"></slot></template>
|
||
|
||
<template #sidebar>
|
||
<slot name="sidebar" :editorService="editorService">
|
||
<Sidebar
|
||
:data="sidebar"
|
||
:layer-content-menu="layerContentMenu"
|
||
:custom-content-menu="customContentMenu"
|
||
:indent="treeIndent"
|
||
:next-level-indent-increment="treeNextLevelIndentIncrement"
|
||
:layer-node-is-expandable="layerNodeIsExpandable"
|
||
:can-drop-in="canDropIn"
|
||
:before-layer-node-dblclick="beforeLayerNodeDblclick"
|
||
@layer-node-dblclick="layerNodeDblclickHandler"
|
||
>
|
||
<template #layer-panel-header>
|
||
<slot name="layer-panel-header"></slot>
|
||
</template>
|
||
|
||
<template #layer-node-content="{ data }">
|
||
<slot name="layer-node-content" :data="data"></slot>
|
||
</template>
|
||
|
||
<template #layer-node-label="{ data }">
|
||
<slot name="layer-node-label" :data="data"></slot>
|
||
</template>
|
||
|
||
<template #layer-node-tool="{ data }">
|
||
<slot name="layer-node-tool" :data="data"></slot>
|
||
</template>
|
||
|
||
<template #component-list="{ componentGroupList }">
|
||
<slot name="component-list" :component-group-list="componentGroupList"></slot>
|
||
</template>
|
||
|
||
<template #component-list-panel-header>
|
||
<slot name="component-list-panel-header"></slot>
|
||
</template>
|
||
|
||
<template #component-list-item="{ component }">
|
||
<slot name="component-list-item" :component="component"></slot>
|
||
</template>
|
||
|
||
<template #code-block-panel-header>
|
||
<slot name="code-block-panel-header"></slot>
|
||
</template>
|
||
|
||
<template #code-block-panel-tool="{ id, data }">
|
||
<slot name="code-block-panel-tool" :id="id" :data="data"></slot>
|
||
</template>
|
||
|
||
<template #code-block-panel-search>
|
||
<slot name="code-block-panel-search"></slot>
|
||
</template>
|
||
|
||
<template #data-source-panel-tool="{ data }">
|
||
<slot name="data-source-panel-tool" :data="data"></slot>
|
||
</template>
|
||
|
||
<template #data-source-panel-search>
|
||
<slot name="data-source-panel-search"></slot>
|
||
</template>
|
||
</Sidebar>
|
||
</slot>
|
||
</template>
|
||
|
||
<template #workspace>
|
||
<slot name="workspace" :editorService="editorService">
|
||
<Workspace
|
||
:disabled-stage-overlay="disabledStageOverlay"
|
||
:stage-content-menu="stageContentMenu"
|
||
:custom-content-menu="customContentMenu"
|
||
>
|
||
<template #stage-top><slot name="stage-top"></slot></template>
|
||
<template #stage><slot name="stage"></slot></template>
|
||
<template #workspace-content><slot name="workspace-content" :editorService="editorService"></slot></template>
|
||
</Workspace>
|
||
</slot>
|
||
</template>
|
||
|
||
<template #props-panel>
|
||
<slot name="props-panel">
|
||
<PropsPanel
|
||
:extend-state="extendFormState"
|
||
:disabled-show-src="disabledShowSrc"
|
||
@mounted="propsPanelMountedHandler"
|
||
@unmounted="propsPanelUnmountedHandler"
|
||
@form-error="propsPanelFormErrorHandler"
|
||
@submit-error="propsPanelSubmitErrorHandler"
|
||
>
|
||
<template #props-panel-header>
|
||
<slot name="props-panel-header"></slot>
|
||
</template>
|
||
</PropsPanel>
|
||
</slot>
|
||
</template>
|
||
|
||
<template #empty><slot name="empty" :editorService="editorService"></slot></template>
|
||
|
||
<template #content-after>
|
||
<slot name="content-after"></slot>
|
||
</template>
|
||
|
||
<template #footer>
|
||
<slot name="footer"></slot>
|
||
</template>
|
||
|
||
<template #page-bar><slot name="page-bar"></slot></template>
|
||
<template #page-bar-add-button><slot name="page-bar-add-button"></slot></template>
|
||
<template #page-bar-title="{ page }"><slot name="page-bar-title" :page="page"></slot></template>
|
||
<template #page-bar-popover="{ page }"><slot name="page-bar-popover" :page="page"></slot></template>
|
||
<template #page-list-popover="{ list }"><slot name="page-list-popover" :list="list"></slot></template>
|
||
</Framework>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import { EventEmitter } from 'events';
|
||
|
||
import { provide } from 'vue';
|
||
|
||
import type { MApp } from '@tmagic/core';
|
||
|
||
import Framework from './layouts/Framework.vue';
|
||
import TMagicNavMenu from './layouts/NavMenu.vue';
|
||
import FormPanel from './layouts/props-panel/FormPanel.vue';
|
||
import PropsPanel from './layouts/props-panel/PropsPanel.vue';
|
||
import Sidebar from './layouts/sidebar/Sidebar.vue';
|
||
import Workspace from './layouts/workspace/Workspace.vue';
|
||
import codeBlockService from './services/codeBlock';
|
||
import componentListService from './services/componentList';
|
||
import dataSourceService from './services/dataSource';
|
||
import depService from './services/dep';
|
||
import editorService from './services/editor';
|
||
import eventsService from './services/events';
|
||
import historyService from './services/history';
|
||
import keybindingService from './services/keybinding';
|
||
import propsService from './services/props';
|
||
import stageOverlayService from './services/stageOverlay';
|
||
import storageService from './services/storage';
|
||
import uiService from './services/ui';
|
||
import keybindingConfig from './utils/keybinding-config';
|
||
import { defaultEditorProps, EditorProps } from './editorProps';
|
||
import { initServiceEvents, initServiceState } from './initService';
|
||
import type { TreeNodeData } from './type';
|
||
import type { EditorSlots, EventBus, Services, StageOptions } from './type';
|
||
|
||
defineSlots<EditorSlots>();
|
||
|
||
defineOptions({
|
||
name: 'MEditor',
|
||
});
|
||
|
||
const emit = defineEmits<{
|
||
'props-panel-mounted': [instance: InstanceType<typeof FormPanel>];
|
||
'props-panel-unmounted': [];
|
||
'update:modelValue': [value: MApp | null];
|
||
'props-form-error': [e: any];
|
||
'props-submit-error': [e: any];
|
||
'layer-node-dblclick': [event: MouseEvent, data: TreeNodeData];
|
||
}>();
|
||
|
||
const props = withDefaults(defineProps<EditorProps>(), defaultEditorProps);
|
||
|
||
const services: Services = {
|
||
componentListService,
|
||
eventsService,
|
||
historyService,
|
||
propsService,
|
||
editorService,
|
||
uiService,
|
||
storageService,
|
||
codeBlockService,
|
||
depService,
|
||
dataSourceService,
|
||
keybindingService,
|
||
stageOverlayService,
|
||
};
|
||
|
||
initServiceEvents(props, emit, services);
|
||
initServiceState(props, services);
|
||
keybindingService.register(keybindingConfig);
|
||
keybindingService.registerEl('global');
|
||
|
||
const stageOptions: StageOptions = {
|
||
runtimeUrl: props.runtimeUrl,
|
||
autoScrollIntoView: props.autoScrollIntoView,
|
||
render: props.render,
|
||
moveableOptions: props.moveableOptions,
|
||
canSelect: props.canSelect,
|
||
updateDragEl: props.updateDragEl,
|
||
isContainer: props.isContainer,
|
||
// sourceIds 为空表示从组件列表新增(尚无 id),否则是画布上拖动已有组件
|
||
canDropIn: props.canDropIn
|
||
? (sourceIds, targetId) =>
|
||
props.canDropIn!(sourceIds, targetId, sourceIds.length === 0 ? 'stage-add' : 'stage-drag')
|
||
: undefined,
|
||
containerHighlightClassName: props.containerHighlightClassName,
|
||
containerHighlightDuration: props.containerHighlightDuration,
|
||
containerHighlightType: props.containerHighlightType,
|
||
disabledDragStart: props.disabledDragStart,
|
||
renderType: props.renderType,
|
||
guidesOptions: props.guidesOptions,
|
||
disabledMultiSelect: props.disabledMultiSelect,
|
||
alwaysMultiSelect: props.alwaysMultiSelect,
|
||
beforeDblclick: props.beforeDblclick,
|
||
};
|
||
|
||
stageOverlayService.set('stageOptions', stageOptions);
|
||
|
||
provide('services', services);
|
||
|
||
provide('codeOptions', props.codeOptions);
|
||
provide('stageOptions', stageOptions);
|
||
|
||
provide<EventBus>('eventBus', new EventEmitter());
|
||
|
||
const propsPanelMountedHandler = (e: InstanceType<typeof FormPanel>) => {
|
||
emit('props-panel-mounted', e);
|
||
};
|
||
const propsPanelUnmountedHandler = () => {
|
||
emit('props-panel-unmounted');
|
||
};
|
||
|
||
const propsPanelSubmitErrorHandler = (e: any) => {
|
||
emit('props-submit-error', e);
|
||
};
|
||
|
||
const propsPanelFormErrorHandler = (e: any) => {
|
||
emit('props-form-error', e);
|
||
};
|
||
|
||
const layerNodeDblclickHandler = (event: MouseEvent, data: TreeNodeData) => {
|
||
emit('layer-node-dblclick', event, data);
|
||
};
|
||
|
||
defineExpose(services);
|
||
</script>
|