mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2025-06-25 03:39:17 +08:00
feat: 编辑器支持鼠标悬停高亮组件
This commit is contained in:
parent
5a85e26443
commit
feb9ac9a81
@ -29,11 +29,20 @@
|
|||||||
empty-text="页面空荡荡的"
|
empty-text="页面空荡荡的"
|
||||||
>
|
>
|
||||||
<template #default="{ node, data }">
|
<template #default="{ node, data }">
|
||||||
<slot name="layer-node-content" :node="node" :data="data">
|
<div
|
||||||
<span>
|
:id="data.id"
|
||||||
{{ `${data.name} (${data.id})` }}
|
class="cus-tree-node"
|
||||||
</span>
|
@mousedown="toogleClickFlag"
|
||||||
</slot>
|
@mouseup="toogleClickFlag"
|
||||||
|
@mouseenter="highlightHandler(data)"
|
||||||
|
:class="{ 'cus-tree-node-hover': canHighlight && data.id === highlightNode?.id }"
|
||||||
|
>
|
||||||
|
<slot name="layer-node-content" :node="node" :data="data">
|
||||||
|
<span>
|
||||||
|
{{ `${data.name} (${data.id})` }}
|
||||||
|
</span>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-tree>
|
</el-tree>
|
||||||
|
|
||||||
@ -46,6 +55,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, inject, onMounted, Ref, ref, watchEffect } from 'vue';
|
import { computed, defineComponent, inject, onMounted, Ref, ref, watchEffect } from 'vue';
|
||||||
import type { ElTree } from 'element-plus';
|
import type { ElTree } from 'element-plus';
|
||||||
|
import { throttle } from 'lodash-es';
|
||||||
|
|
||||||
import type { MNode, MPage } from '@tmagic/schema';
|
import type { MNode, MPage } from '@tmagic/schema';
|
||||||
|
|
||||||
@ -54,6 +64,8 @@ import type { Services } from '@editor/type';
|
|||||||
|
|
||||||
import LayerMenu from './LayerMenu.vue';
|
import LayerMenu from './LayerMenu.vue';
|
||||||
|
|
||||||
|
const throttleTime = 150;
|
||||||
|
|
||||||
const select = (data: MNode, editorService?: EditorService) => {
|
const select = (data: MNode, editorService?: EditorService) => {
|
||||||
if (!data.id) {
|
if (!data.id) {
|
||||||
throw new Error('没有id');
|
throw new Error('没有id');
|
||||||
@ -62,6 +74,13 @@ const select = (data: MNode, editorService?: EditorService) => {
|
|||||||
editorService?.select(data);
|
editorService?.select(data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const highlight = (data: MNode, editorService?: EditorService) => {
|
||||||
|
if (!data?.id) {
|
||||||
|
throw new Error('没有id');
|
||||||
|
}
|
||||||
|
editorService?.highlight(data);
|
||||||
|
};
|
||||||
|
|
||||||
const useDrop = (tree: Ref<InstanceType<typeof ElTree> | undefined>, editorService?: EditorService) => ({
|
const useDrop = (tree: Ref<InstanceType<typeof ElTree> | undefined>, editorService?: EditorService) => ({
|
||||||
allowDrop: (draggingNode: any, dropNode: any, type: string): boolean => {
|
allowDrop: (draggingNode: any, dropNode: any, type: string): boolean => {
|
||||||
const { data } = dropNode || {};
|
const { data } = dropNode || {};
|
||||||
@ -87,19 +106,22 @@ const useDrop = (tree: Ref<InstanceType<typeof ElTree> | undefined>, editorServi
|
|||||||
});
|
});
|
||||||
|
|
||||||
const useStatus = (tree: Ref<InstanceType<typeof ElTree> | undefined>, editorService?: EditorService) => {
|
const useStatus = (tree: Ref<InstanceType<typeof ElTree> | undefined>, editorService?: EditorService) => {
|
||||||
|
const highlightNode = ref<MNode>();
|
||||||
|
const node = ref<MNode>();
|
||||||
const page = computed(() => editorService?.get('page'));
|
const page = computed(() => editorService?.get('page'));
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
if (!tree.value) return;
|
if (!tree.value) return;
|
||||||
|
node.value = editorService?.get('node');
|
||||||
const node = editorService?.get('node');
|
node.value && tree.value.setCurrentKey(node.value.id, true);
|
||||||
node && tree.value.setCurrentKey(node.id, true);
|
|
||||||
|
|
||||||
const parent = editorService?.get('parent');
|
const parent = editorService?.get('parent');
|
||||||
if (!parent?.id) return;
|
if (!parent?.id) return;
|
||||||
|
|
||||||
const treeNode = tree.value.getNode(parent.id);
|
const treeNode = tree.value.getNode(parent.id);
|
||||||
treeNode?.updateChildren();
|
treeNode?.updateChildren();
|
||||||
|
|
||||||
|
highlightNode.value = editorService?.get('highlightNode');
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -114,6 +136,9 @@ const useStatus = (tree: Ref<InstanceType<typeof ElTree> | undefined>, editorSer
|
|||||||
}
|
}
|
||||||
resolve([]);
|
resolve([]);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
highlightNode,
|
||||||
|
clickNode: node,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -188,11 +213,25 @@ export default defineComponent({
|
|||||||
setup() {
|
setup() {
|
||||||
const services = inject<Services>('services');
|
const services = inject<Services>('services');
|
||||||
const tree = ref<InstanceType<typeof ElTree>>();
|
const tree = ref<InstanceType<typeof ElTree>>();
|
||||||
|
const clicked = ref(false);
|
||||||
const editorService = services?.editorService;
|
const editorService = services?.editorService;
|
||||||
|
const highlightHandler = throttle((data: MNode) => {
|
||||||
|
highlight(data, editorService);
|
||||||
|
}, throttleTime);
|
||||||
|
|
||||||
|
const toogleClickFlag = () => {
|
||||||
|
clicked.value = !clicked.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const statusData = useStatus(tree, editorService);
|
||||||
|
const canHighlight = computed(
|
||||||
|
() => statusData.highlightNode.value?.id !== statusData.clickNode.value?.id && !clicked.value,
|
||||||
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tree,
|
tree,
|
||||||
|
...statusData,
|
||||||
...useDrop(tree, editorService),
|
...useDrop(tree, editorService),
|
||||||
...useStatus(tree, editorService),
|
|
||||||
...useFilter(tree),
|
...useFilter(tree),
|
||||||
...useContentMenu(editorService),
|
...useContentMenu(editorService),
|
||||||
|
|
||||||
@ -201,8 +240,12 @@ export default defineComponent({
|
|||||||
document.dispatchEvent(new CustomEvent('ui-select', { detail: data }));
|
document.dispatchEvent(new CustomEvent('ui-select', { detail: data }));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
tree.value?.setCurrentKey(data.id);
|
||||||
select(data, editorService);
|
select(data, editorService);
|
||||||
},
|
},
|
||||||
|
highlightHandler,
|
||||||
|
toogleClickFlag,
|
||||||
|
canHighlight,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -110,7 +110,7 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
emits: ['select', 'update', 'sort'],
|
emits: ['select', 'update', 'sort', 'highlight'],
|
||||||
|
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const services = inject<Services>('services');
|
const services = inject<Services>('services');
|
||||||
@ -124,6 +124,7 @@ export default defineComponent({
|
|||||||
const page = computed(() => services?.editorService.get<MPage>('page'));
|
const page = computed(() => services?.editorService.get<MPage>('page'));
|
||||||
const zoom = computed(() => services?.uiService.get<number>('zoom'));
|
const zoom = computed(() => services?.uiService.get<number>('zoom'));
|
||||||
const node = computed(() => services?.editorService.get<MNode>('node'));
|
const node = computed(() => services?.editorService.get<MNode>('node'));
|
||||||
|
const highlightNode = computed(() => services?.editorService.get<MNode>('highlightNode'));
|
||||||
|
|
||||||
let stage: StageCore | null = null;
|
let stage: StageCore | null = null;
|
||||||
let runtime: Runtime | null = null;
|
let runtime: Runtime | null = null;
|
||||||
@ -155,7 +156,13 @@ export default defineComponent({
|
|||||||
|
|
||||||
stage?.mount(stageContainer.value);
|
stage?.mount(stageContainer.value);
|
||||||
|
|
||||||
stage?.on('select', (el: HTMLElement) => emit('select', el));
|
stage?.on('select', (el: HTMLElement) => {
|
||||||
|
emit('select', el);
|
||||||
|
});
|
||||||
|
|
||||||
|
stage?.on('highlight', (el: HTMLElement) => {
|
||||||
|
emit('highlight', el);
|
||||||
|
});
|
||||||
|
|
||||||
stage?.on('update', (ev: UpdateEventData) => {
|
stage?.on('update', (ev: UpdateEventData) => {
|
||||||
emit('update', { id: ev.el.id, style: ev.style });
|
emit('update', { id: ev.el.id, style: ev.style });
|
||||||
@ -202,6 +209,15 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => highlightNode.value?.id,
|
||||||
|
(id) => {
|
||||||
|
nextTick(() => {
|
||||||
|
id && stage?.highlight(id);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
const resizeObserver = new ResizeObserver((entries) => {
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
for (const { contentRect } of entries) {
|
for (const { contentRect } of entries) {
|
||||||
services?.uiService.set('stageContainerRect', {
|
services?.uiService.set('stageContainerRect', {
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
:moveable-options="moveableOptions"
|
:moveable-options="moveableOptions"
|
||||||
:can-select="canSelect"
|
:can-select="canSelect"
|
||||||
@select="selectHandler"
|
@select="selectHandler"
|
||||||
|
@highlight="highlightHandler"
|
||||||
@update="updateNodeHandler"
|
@update="updateNodeHandler"
|
||||||
@sort="sortNodeHandler"
|
@sort="sortNodeHandler"
|
||||||
></magic-stage>
|
></magic-stage>
|
||||||
@ -73,6 +74,10 @@ export default defineComponent({
|
|||||||
sortNodeHandler(ev: SortEventData) {
|
sortNodeHandler(ev: SortEventData) {
|
||||||
services?.editorService.sort(ev.src, ev.dist);
|
services?.editorService.sort(ev.src, ev.dist);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
highlightHandler(el: HTMLElement) {
|
||||||
|
services?.editorService.highlight(el.id);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -51,6 +51,7 @@ class Editor extends BaseService {
|
|||||||
parent: null,
|
parent: null,
|
||||||
node: null,
|
node: null,
|
||||||
stage: null,
|
stage: null,
|
||||||
|
highlightNode: null,
|
||||||
modifiedNodeIds: new Map(),
|
modifiedNodeIds: new Map(),
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -68,12 +69,13 @@ class Editor extends BaseService {
|
|||||||
'moveLayer',
|
'moveLayer',
|
||||||
'undo',
|
'undo',
|
||||||
'redo',
|
'redo',
|
||||||
|
'highlight',
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 设置当前指点节点配置
|
* 设置当前指点节点配置
|
||||||
* @param name 'root' | 'page' | 'parent' | 'node'
|
* @param name 'root' | 'page' | 'parent' | 'node' | 'highlightNode'
|
||||||
* @param value MNode
|
* @param value MNode
|
||||||
* @returns MNode
|
* @returns MNode
|
||||||
*/
|
*/
|
||||||
@ -166,28 +168,12 @@ class Editor extends BaseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 选中指点节点(将指点节点设置成当前选中状态)
|
* 选中指定节点(将指定节点设置成当前选中状态)
|
||||||
* @param config 指点节点配置或者ID
|
* @param config 指定节点配置或者ID
|
||||||
* @returns 当前选中的节点配置
|
* @returns 当前选中的节点配置
|
||||||
*/
|
*/
|
||||||
public async select(config: MNode | Id): Promise<MNode> {
|
public async select(config: MNode | Id): Promise<MNode> | never {
|
||||||
let id: Id;
|
const { node, page, parent } = this.selectedConfigExceptionHandler(config);
|
||||||
if (typeof config === 'string' || typeof config === 'number') {
|
|
||||||
id = config;
|
|
||||||
} else {
|
|
||||||
id = config.id;
|
|
||||||
}
|
|
||||||
if (!id) {
|
|
||||||
throw new Error('没有ID,无法选中');
|
|
||||||
}
|
|
||||||
|
|
||||||
const { node, parent, page } = this.getNodeInfo(id);
|
|
||||||
if (!node) throw new Error('获取不到组件信息');
|
|
||||||
|
|
||||||
if (node.id === this.state.root?.id) {
|
|
||||||
throw new Error('不能选根节点');
|
|
||||||
}
|
|
||||||
|
|
||||||
this.set('node', node);
|
this.set('node', node);
|
||||||
this.set('page', page || null);
|
this.set('page', page || null);
|
||||||
this.set('parent', parent || null);
|
this.set('parent', parent || null);
|
||||||
@ -197,8 +183,19 @@ class Editor extends BaseService {
|
|||||||
} else {
|
} else {
|
||||||
historyService.empty();
|
historyService.empty();
|
||||||
}
|
}
|
||||||
|
return node!;
|
||||||
|
}
|
||||||
|
|
||||||
return node;
|
/**
|
||||||
|
* 高亮指定节点
|
||||||
|
* @param config 指定节点配置或者ID
|
||||||
|
* @returns 当前高亮的节点配置
|
||||||
|
*/
|
||||||
|
public highlight(config: MNode | Id): void {
|
||||||
|
const { node } = this.selectedConfigExceptionHandler(config);
|
||||||
|
const currentHighligtNode = this.get('highlightNode');
|
||||||
|
if (currentHighligtNode === node) return;
|
||||||
|
this.set('highlightNode', node);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -524,6 +521,30 @@ class Editor extends BaseService {
|
|||||||
|
|
||||||
return newConfig;
|
return newConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private selectedConfigExceptionHandler(config: MNode | Id): EditorNodeInfo {
|
||||||
|
let id: Id;
|
||||||
|
if (typeof config === 'string' || typeof config === 'number') {
|
||||||
|
id = config;
|
||||||
|
} else {
|
||||||
|
id = config.id;
|
||||||
|
}
|
||||||
|
if (!id) {
|
||||||
|
throw new Error('没有ID,无法选中');
|
||||||
|
}
|
||||||
|
|
||||||
|
const { node, parent, page } = this.getNodeInfo(id);
|
||||||
|
if (!node) throw new Error('获取不到组件信息');
|
||||||
|
|
||||||
|
if (node.id === this.state.root?.id) {
|
||||||
|
throw new Error('不能选根节点');
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
node,
|
||||||
|
parent,
|
||||||
|
page,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export type EditorService = Editor;
|
export type EditorService = Editor;
|
||||||
|
@ -5,9 +5,9 @@ $--border-color: #d9dbdd;
|
|||||||
|
|
||||||
$--nav-height: 35px;
|
$--nav-height: 35px;
|
||||||
$--nav-color: #070303;
|
$--nav-color: #070303;
|
||||||
$--nav--background-color: #f8fbff;
|
$--nav--background-color: #ffffff;
|
||||||
|
|
||||||
$--sidebar-heder-background-color: $--theme-color;
|
$--sidebar-heder-background-color: $--theme-color;
|
||||||
$--sidebar-content-background-color: #f8fbff;
|
$--sidebar-content-background-color: #ffffff;
|
||||||
|
|
||||||
$--page-bar-height: 32px;
|
$--page-bar-height: 32px;
|
||||||
|
@ -50,6 +50,15 @@
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.magic-editor-layer-panel .cus-tree-node {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.magic-editor-layer-panel .cus-tree-node-hover {
|
||||||
|
background-color: #f3f5f9;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.magic-editor-layer-panel .el-tree-node:focus > .el-tree-node__content {
|
.magic-editor-layer-panel .el-tree-node:focus > .el-tree-node__content {
|
||||||
background-color: $--sidebar-heder-background-color;
|
background-color: $--sidebar-heder-background-color;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
@ -50,6 +50,7 @@ export interface StoreState {
|
|||||||
page: MPage | null;
|
page: MPage | null;
|
||||||
parent: MContainer | null;
|
parent: MContainer | null;
|
||||||
node: MNode | null;
|
node: MNode | null;
|
||||||
|
highlightNode: MNode | null;
|
||||||
stage: StageCore | null;
|
stage: StageCore | null;
|
||||||
modifiedNodeIds: Map<Id, Id>;
|
modifiedNodeIds: Map<Id, Id>;
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ import { Id } from '@tmagic/schema';
|
|||||||
|
|
||||||
import { DEFAULT_ZOOM, GHOST_EL_ID_PREFIX } from './const';
|
import { DEFAULT_ZOOM, GHOST_EL_ID_PREFIX } from './const';
|
||||||
import StageDragResize from './StageDragResize';
|
import StageDragResize from './StageDragResize';
|
||||||
|
import StageHighlight from './StageHighlight';
|
||||||
import StageMask from './StageMask';
|
import StageMask from './StageMask';
|
||||||
import StageRender from './StageRender';
|
import StageRender from './StageRender';
|
||||||
import {
|
import {
|
||||||
@ -37,10 +38,12 @@ import {
|
|||||||
|
|
||||||
export default class StageCore extends EventEmitter {
|
export default class StageCore extends EventEmitter {
|
||||||
public selectedDom: Element | undefined;
|
public selectedDom: Element | undefined;
|
||||||
|
public highlightedDom: Element | undefined;
|
||||||
|
|
||||||
public renderer: StageRender;
|
public renderer: StageRender;
|
||||||
public mask: StageMask;
|
public mask: StageMask;
|
||||||
public dr: StageDragResize;
|
public dr: StageDragResize;
|
||||||
|
public highlightLayer: StageHighlight;
|
||||||
public config: StageCoreConfig;
|
public config: StageCoreConfig;
|
||||||
public zoom = DEFAULT_ZOOM;
|
public zoom = DEFAULT_ZOOM;
|
||||||
private canSelect: CanSelect;
|
private canSelect: CanSelect;
|
||||||
@ -56,6 +59,7 @@ export default class StageCore extends EventEmitter {
|
|||||||
this.renderer = new StageRender({ core: this });
|
this.renderer = new StageRender({ core: this });
|
||||||
this.mask = new StageMask({ core: this });
|
this.mask = new StageMask({ core: this });
|
||||||
this.dr = new StageDragResize({ core: this, container: this.mask.content });
|
this.dr = new StageDragResize({ core: this, container: this.mask.content });
|
||||||
|
this.highlightLayer = new StageHighlight({ core: this, container: this.mask.content });
|
||||||
|
|
||||||
this.renderer.on('runtime-ready', (runtime: Runtime) => this.emit('runtime-ready', runtime));
|
this.renderer.on('runtime-ready', (runtime: Runtime) => this.emit('runtime-ready', runtime));
|
||||||
this.renderer.on('page-el-update', (el: HTMLElement) => this.mask?.observe(el));
|
this.renderer.on('page-el-update', (el: HTMLElement) => this.mask?.observe(el));
|
||||||
@ -70,6 +74,14 @@ export default class StageCore extends EventEmitter {
|
|||||||
.on('changeGuides', (data: GuidesEventData) => {
|
.on('changeGuides', (data: GuidesEventData) => {
|
||||||
this.dr.setGuidelines(data.type, data.guides);
|
this.dr.setGuidelines(data.type, data.guides);
|
||||||
this.emit('changeGuides', data);
|
this.emit('changeGuides', data);
|
||||||
|
})
|
||||||
|
.on('highlight', async (event: MouseEvent) => {
|
||||||
|
await this.setElementFromPoint(event);
|
||||||
|
this.highlightLayer.highlight(this.highlightedDom as HTMLElement);
|
||||||
|
this.emit('highlight', this.highlightedDom);
|
||||||
|
})
|
||||||
|
.on('clearHighlight', async () => {
|
||||||
|
this.highlightLayer.clearHighlight();
|
||||||
});
|
});
|
||||||
|
|
||||||
// 要先触发select,在触发update
|
// 要先触发select,在触发update
|
||||||
@ -104,6 +116,10 @@ export default class StageCore extends EventEmitter {
|
|||||||
for (const el of els) {
|
for (const el of els) {
|
||||||
if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && (await this.canSelect(el, stop))) {
|
if (!el.id.startsWith(GHOST_EL_ID_PREFIX) && (await this.canSelect(el, stop))) {
|
||||||
if (stopped) break;
|
if (stopped) break;
|
||||||
|
if (event.type === 'mousemove') {
|
||||||
|
this.highlight(el);
|
||||||
|
break;
|
||||||
|
}
|
||||||
this.select(el, event);
|
this.select(el, event);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -115,14 +131,7 @@ export default class StageCore extends EventEmitter {
|
|||||||
* @param idOrEl 组件Dom节点的id属性,或者Dom节点
|
* @param idOrEl 组件Dom节点的id属性,或者Dom节点
|
||||||
*/
|
*/
|
||||||
public async select(idOrEl: Id | HTMLElement, event?: MouseEvent): Promise<void> {
|
public async select(idOrEl: Id | HTMLElement, event?: MouseEvent): Promise<void> {
|
||||||
let el;
|
const el = await this.getTargetElement(idOrEl);
|
||||||
if (typeof idOrEl === 'string' || typeof idOrEl === 'number') {
|
|
||||||
const runtime = await this.renderer?.getRuntime();
|
|
||||||
el = await runtime?.select?.(`${idOrEl}`);
|
|
||||||
if (!el) throw new Error(`不存在ID为${idOrEl}的元素`);
|
|
||||||
} else {
|
|
||||||
el = idOrEl;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (el === this.selectedDom) return;
|
if (el === this.selectedDom) return;
|
||||||
|
|
||||||
@ -157,6 +166,17 @@ export default class StageCore extends EventEmitter {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 高亮选中组件
|
||||||
|
* @param el 页面Dom节点
|
||||||
|
*/
|
||||||
|
public async highlight(idOrEl: HTMLElement | Id): Promise<void> {
|
||||||
|
const el = await this.getTargetElement(idOrEl);
|
||||||
|
if (el === this.highlightedDom) return;
|
||||||
|
this.highlightLayer.highlight(el);
|
||||||
|
this.highlightedDom = el;
|
||||||
|
}
|
||||||
|
|
||||||
public sortNode(data: SortEventData): void {
|
public sortNode(data: SortEventData): void {
|
||||||
this.renderer?.getRuntime().then((runtime) => runtime?.sortNode?.(data));
|
this.renderer?.getRuntime().then((runtime) => runtime?.sortNode?.(data));
|
||||||
}
|
}
|
||||||
@ -198,12 +218,25 @@ export default class StageCore extends EventEmitter {
|
|||||||
* 销毁实例
|
* 销毁实例
|
||||||
*/
|
*/
|
||||||
public destroy(): void {
|
public destroy(): void {
|
||||||
const { mask, renderer, dr } = this;
|
const { mask, renderer, dr, highlightLayer } = this;
|
||||||
|
|
||||||
renderer.destroy();
|
renderer.destroy();
|
||||||
mask.destroy();
|
mask.destroy();
|
||||||
dr.destroy();
|
dr.destroy();
|
||||||
|
highlightLayer.destroy();
|
||||||
|
|
||||||
this.removeAllListeners();
|
this.removeAllListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getTargetElement(idOrEl: Id | HTMLElement): Promise<HTMLElement> {
|
||||||
|
let el;
|
||||||
|
if (typeof idOrEl === 'string' || typeof idOrEl === 'number') {
|
||||||
|
const runtime = await this.renderer?.getRuntime();
|
||||||
|
el = await runtime?.select?.(`${idOrEl}`);
|
||||||
|
if (!el) throw new Error(`不存在ID为${idOrEl}的元素`);
|
||||||
|
} else {
|
||||||
|
el = idOrEl;
|
||||||
|
}
|
||||||
|
return el;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
67
packages/stage/src/StageHighlight.ts
Normal file
67
packages/stage/src/StageHighlight.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* Tencent is pleased to support the open source community by making TMagicEditor available.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2021 THL A29 Limited, a Tencent company. All rights reserved.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* eslint-disable no-param-reassign */
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
|
import Moveable from 'moveable';
|
||||||
|
|
||||||
|
import StageCore from './StageCore';
|
||||||
|
import type { StageHighlightConfig } from './types';
|
||||||
|
export default class StageHighlight extends EventEmitter {
|
||||||
|
public core: StageCore;
|
||||||
|
public container: HTMLElement;
|
||||||
|
public target?: HTMLElement;
|
||||||
|
public moveable?: Moveable;
|
||||||
|
|
||||||
|
constructor(config: StageHighlightConfig) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.core = config.core;
|
||||||
|
this.container = config.container;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 高亮鼠标悬停的组件
|
||||||
|
* @param el 选中组件的Dom节点元素
|
||||||
|
*/
|
||||||
|
public highlight(el: HTMLElement): void {
|
||||||
|
if (!el || el === this.target) return;
|
||||||
|
this.target = el;
|
||||||
|
this.moveable?.destroy();
|
||||||
|
this.moveable = new Moveable(this.container, {
|
||||||
|
target: this.target,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 清空高亮
|
||||||
|
*/
|
||||||
|
public clearHighlight(): void {
|
||||||
|
if (!this.moveable) return;
|
||||||
|
this.moveable.target = null;
|
||||||
|
this.moveable.updateTarget();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 销毁实例
|
||||||
|
*/
|
||||||
|
public destroy(): void {
|
||||||
|
this.moveable?.destroy();
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,8 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import { throttle } from 'lodash-es';
|
||||||
|
|
||||||
import { Mode, MouseButton, ZIndex } from './const';
|
import { Mode, MouseButton, ZIndex } from './const';
|
||||||
import Rule from './Rule';
|
import Rule from './Rule';
|
||||||
import type StageCore from './StageCore';
|
import type StageCore from './StageCore';
|
||||||
@ -23,6 +25,7 @@ import type { StageMaskConfig } from './types';
|
|||||||
import { createDiv, getScrollParent, isFixedParent } from './util';
|
import { createDiv, getScrollParent, isFixedParent } from './util';
|
||||||
|
|
||||||
const wrapperClassName = 'editor-mask-wrapper';
|
const wrapperClassName = 'editor-mask-wrapper';
|
||||||
|
const throttleTime = 100;
|
||||||
|
|
||||||
const hideScrollbar = () => {
|
const hideScrollbar = () => {
|
||||||
const style = globalThis.document.createElement('style');
|
const style = globalThis.document.createElement('style');
|
||||||
@ -93,6 +96,7 @@ export default class StageMask extends Rule {
|
|||||||
this.content.addEventListener('mousedown', this.mouseDownHandler);
|
this.content.addEventListener('mousedown', this.mouseDownHandler);
|
||||||
this.wrapper.appendChild(this.content);
|
this.wrapper.appendChild(this.content);
|
||||||
this.content.addEventListener('wheel', this.mouseWheelHandler);
|
this.content.addEventListener('wheel', this.mouseWheelHandler);
|
||||||
|
this.content.addEventListener('mousemove', throttle(this.highlightHandler, throttleTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
public setMode(mode: Mode) {
|
public setMode(mode: Mode) {
|
||||||
@ -203,7 +207,7 @@ export default class StageMask extends Rule {
|
|||||||
* 点击事件处理函数
|
* 点击事件处理函数
|
||||||
* @param event 事件对象
|
* @param event 事件对象
|
||||||
*/
|
*/
|
||||||
private mouseDownHandler = async (event: MouseEvent): Promise<void> => {
|
private mouseDownHandler = (event: MouseEvent): void => {
|
||||||
event.stopImmediatePropagation();
|
event.stopImmediatePropagation();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
@ -218,10 +222,13 @@ export default class StageMask extends Rule {
|
|||||||
|
|
||||||
// 如果是右键点击,这里的mouseup事件监听没有效果
|
// 如果是右键点击,这里的mouseup事件监听没有效果
|
||||||
globalThis.document.addEventListener('mouseup', this.mouseUpHandler);
|
globalThis.document.addEventListener('mouseup', this.mouseUpHandler);
|
||||||
|
this.content.removeEventListener('mousemove', this.highlightHandler);
|
||||||
|
this.emit('clearHighlight');
|
||||||
};
|
};
|
||||||
|
|
||||||
private mouseUpHandler = (): void => {
|
private mouseUpHandler = (): void => {
|
||||||
globalThis.document.removeEventListener('mouseup', this.mouseUpHandler);
|
globalThis.document.removeEventListener('mouseup', this.mouseUpHandler);
|
||||||
|
this.content.addEventListener('mousemove', throttle(this.highlightHandler, throttleTime));
|
||||||
this.emit('select');
|
this.emit('select');
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -264,4 +271,12 @@ export default class StageMask extends Rule {
|
|||||||
|
|
||||||
this.emit('scroll', event);
|
this.emit('scroll', event);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 高亮事件处理函数
|
||||||
|
* @param event 事件对象
|
||||||
|
*/
|
||||||
|
private highlightHandler = (event: MouseEvent): void => {
|
||||||
|
this.emit('highlight', event);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
@ -114,3 +114,8 @@ export interface Magic {
|
|||||||
export interface RuntimeWindow extends Window {
|
export interface RuntimeWindow extends Window {
|
||||||
magic: Magic;
|
magic: Magic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface StageHighlightConfig {
|
||||||
|
core: StageCore;
|
||||||
|
container: HTMLElement;
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user