mirror of
https://github.com/Powerful-517/yys-editor.git
synced 2026-01-23 22:43:28 +00:00
temp
This commit is contained in:
@@ -56,12 +56,13 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, watch, onMounted, onBeforeUnmount } from 'vue';
|
||||
import LogicFlow, { EventType } from '@logicflow/core';
|
||||
import type { Position, NodeData, EdgeData, BaseNodeModel, GraphModel } from '@logicflow/core';
|
||||
import type { Position, NodeData, EdgeData, BaseNodeModel, GraphModel, GraphData } from '@logicflow/core';
|
||||
import '@logicflow/core/lib/style/index.css';
|
||||
import { Menu, Label, Snapshot, SelectionSelect } from '@logicflow/extension';
|
||||
import '@logicflow/extension/lib/style/index.css';
|
||||
import '@logicflow/core/es/index.css';
|
||||
import '@logicflow/extension/es/index.css';
|
||||
import { translateEdgeData, translateNodeData } from '@logicflow/core/es/keyboard/shortcut';
|
||||
|
||||
import { register } from '@logicflow/vue-node-registry';
|
||||
import ShikigamiSelectNode from './nodes/yys/ShikigamiSelectNode.vue';
|
||||
@@ -76,6 +77,10 @@ import { setLogicFlowInstance, destroyLogicFlowInstance } from '@/ts/useLogicFlo
|
||||
type AlignType = 'left' | 'right' | 'top' | 'bottom' | 'hcenter' | 'vcenter';
|
||||
type DistributeType = 'horizontal' | 'vertical';
|
||||
|
||||
const MOVE_STEP = 2;
|
||||
const MOVE_STEP_LARGE = 10;
|
||||
const COPY_TRANSLATION = 40;
|
||||
|
||||
const props = defineProps<{
|
||||
height?: string;
|
||||
}>();
|
||||
@@ -101,6 +106,280 @@ const { showMessage } = useGlobalMessage();
|
||||
|
||||
// 当前选中节点
|
||||
const selectedNode = ref<any>(null);
|
||||
const copyBuffer = ref<GraphData | null>(null);
|
||||
let nextPasteDistance = COPY_TRANSLATION;
|
||||
|
||||
function isInputLike(event?: KeyboardEvent) {
|
||||
const target = event?.target as HTMLElement | null;
|
||||
if (!target) return false;
|
||||
const tag = target.tagName?.toLowerCase();
|
||||
return ['input', 'textarea', 'select', 'option'].includes(tag) || target.isContentEditable;
|
||||
}
|
||||
|
||||
function shouldSkipShortcut(event?: KeyboardEvent) {
|
||||
const lfInstance = lf.value as any;
|
||||
if (!lfInstance) return true;
|
||||
if (lfInstance.keyboard?.disabled) return true;
|
||||
if (lfInstance.graphModel?.textEditElement) return true;
|
||||
if (isInputLike(event)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function ensureMeta(meta?: Record<string, any>) {
|
||||
const next: Record<string, any> = meta ? { ...meta } : {};
|
||||
if (next.visible == null) next.visible = true;
|
||||
if (next.locked == null) next.locked = false;
|
||||
return next;
|
||||
}
|
||||
|
||||
function applyMetaToModel(model: BaseNodeModel, metaInput?: Record<string, any>) {
|
||||
const lfInstance = lf.value;
|
||||
const meta = ensureMeta(metaInput ?? (model.getProperties?.() as any)?.meta ?? (model as any)?.properties?.meta);
|
||||
model.visible = meta.visible !== false;
|
||||
model.draggable = !meta.locked;
|
||||
model.setHittable?.(!meta.locked);
|
||||
model.setHitable?.(!meta.locked);
|
||||
model.setIsShowAnchor?.(!meta.locked);
|
||||
model.setRotatable?.(!meta.locked);
|
||||
model.setResizable?.(!meta.locked);
|
||||
|
||||
if (lfInstance) {
|
||||
const connectedEdges = lfInstance.getNodeEdges(model.id);
|
||||
connectedEdges.forEach((edgeModel) => {
|
||||
edgeModel.visible = meta.visible !== false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeNodeModel(model: BaseNodeModel) {
|
||||
const lfInstance = lf.value;
|
||||
if (!lfInstance) return;
|
||||
|
||||
const props = (model.getProperties?.() as any) ?? (model as any)?.properties ?? {};
|
||||
const incomingMeta = ensureMeta(props.meta);
|
||||
const currentMeta = ensureMeta((model as any)?.properties?.meta);
|
||||
const needPersist =
|
||||
currentMeta.visible !== incomingMeta.visible ||
|
||||
currentMeta.locked !== incomingMeta.locked ||
|
||||
currentMeta.groupId !== incomingMeta.groupId;
|
||||
|
||||
if (needPersist) {
|
||||
lfInstance.setProperties(model.id, { ...props, meta: incomingMeta });
|
||||
}
|
||||
applyMetaToModel(model, incomingMeta);
|
||||
}
|
||||
|
||||
function normalizeAllNodes() {
|
||||
const lfInstance = lf.value;
|
||||
if (!lfInstance) return;
|
||||
lfInstance.graphModel?.nodes.forEach((model: BaseNodeModel) => normalizeNodeModel(model));
|
||||
}
|
||||
|
||||
function updateNodeMeta(model: BaseNodeModel, updater: (meta: Record<string, any>) => Record<string, any>) {
|
||||
const lfInstance = lf.value;
|
||||
if (!lfInstance) return;
|
||||
const props = (model.getProperties?.() as any) ?? (model as any)?.properties ?? {};
|
||||
const nextMeta = updater(ensureMeta(props.meta));
|
||||
lfInstance.setProperties(model.id, { ...props, meta: nextMeta });
|
||||
applyMetaToModel(model, nextMeta);
|
||||
}
|
||||
|
||||
function getSelectedNodeModels() {
|
||||
const graphModel = lf.value?.graphModel;
|
||||
if (!graphModel) return [];
|
||||
return [...graphModel.selectNodes];
|
||||
}
|
||||
|
||||
function collectGroupNodeIds(models: BaseNodeModel[]) {
|
||||
const graphModel = lf.value?.graphModel;
|
||||
if (!graphModel) return [];
|
||||
const ids = new Set<string>();
|
||||
models.forEach((model) => {
|
||||
const meta = ensureMeta((model.getProperties?.() as any)?.meta ?? (model as any)?.properties?.meta);
|
||||
if (meta.locked) return;
|
||||
if (meta.groupId) {
|
||||
graphModel.nodes.forEach((node) => {
|
||||
const peerMeta = ensureMeta((node.getProperties?.() as any)?.meta ?? (node as any)?.properties?.meta);
|
||||
if (peerMeta.groupId === meta.groupId && !peerMeta.locked) {
|
||||
ids.add(node.id);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ids.add(model.id);
|
||||
}
|
||||
});
|
||||
return Array.from(ids);
|
||||
}
|
||||
|
||||
function moveSelectedNodes(deltaX: number, deltaY: number) {
|
||||
const graphModel = lf.value?.graphModel;
|
||||
if (!graphModel) return;
|
||||
const targets = collectGroupNodeIds(getSelectedNodeModels());
|
||||
if (!targets.length) return;
|
||||
graphModel.moveNodes(targets, deltaX, deltaY);
|
||||
}
|
||||
|
||||
function deleteSelectedElements(event?: KeyboardEvent) {
|
||||
if (shouldSkipShortcut(event)) return true;
|
||||
const lfInstance = lf.value;
|
||||
if (!lfInstance) return true;
|
||||
|
||||
const { nodes, edges } = lfInstance.getSelectElements(true);
|
||||
const lockedNodes = nodes.filter((node) => ensureMeta((node as any).properties?.meta).locked);
|
||||
edges.forEach((edge) => edge.id && lfInstance.deleteEdge(edge.id));
|
||||
nodes
|
||||
.filter((node) => {
|
||||
const meta = ensureMeta((node as any).properties?.meta);
|
||||
return !meta.locked && meta.visible !== false;
|
||||
})
|
||||
.forEach((node) => node.id && lfInstance.deleteNode(node.id));
|
||||
|
||||
if (lockedNodes.length) {
|
||||
showMessage('warning', '部分节点已锁定,未删除');
|
||||
}
|
||||
updateSelectedCount();
|
||||
selectedNode.value = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
function toggleLockSelected(event?: KeyboardEvent) {
|
||||
if (shouldSkipShortcut(event)) return true;
|
||||
const models = getSelectedNodeModels();
|
||||
if (!models.length) {
|
||||
showMessage('info', '请选择节点后再执行锁定/解锁');
|
||||
return true;
|
||||
}
|
||||
const hasUnlocked = models.some((model) => !ensureMeta((model.getProperties?.() as any)?.meta).locked);
|
||||
models.forEach((model) => {
|
||||
updateNodeMeta(model, (meta) => ({ ...meta, locked: hasUnlocked ? true : false }));
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function toggleVisibilitySelected(event?: KeyboardEvent) {
|
||||
if (shouldSkipShortcut(event)) return true;
|
||||
const models = getSelectedNodeModels();
|
||||
if (!models.length) {
|
||||
showMessage('info', '请选择节点后再执行显示/隐藏');
|
||||
return true;
|
||||
}
|
||||
const hasVisible = models.some((model) => ensureMeta((model.getProperties?.() as any)?.meta).visible !== false);
|
||||
models.forEach((model) => {
|
||||
updateNodeMeta(model, (meta) => ({ ...meta, visible: !hasVisible ? true : false }));
|
||||
});
|
||||
if (hasVisible) {
|
||||
selectedNode.value = null;
|
||||
}
|
||||
updateSelectedCount();
|
||||
return false;
|
||||
}
|
||||
|
||||
function groupSelectedNodes(event?: KeyboardEvent) {
|
||||
if (shouldSkipShortcut(event)) return true;
|
||||
const models = getSelectedNodeModels().filter((model) => !ensureMeta((model.getProperties?.() as any)?.meta).locked);
|
||||
if (models.length < 2) {
|
||||
showMessage('warning', '请选择至少两个未锁定的节点进行分组');
|
||||
return true;
|
||||
}
|
||||
const groupId = `group_${Date.now().toString(36)}`;
|
||||
models.forEach((model) => {
|
||||
updateNodeMeta(model, (meta) => ({ ...meta, groupId }));
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function ungroupSelectedNodes(event?: KeyboardEvent) {
|
||||
if (shouldSkipShortcut(event)) return true;
|
||||
const models = getSelectedNodeModels();
|
||||
if (!models.length) {
|
||||
showMessage('info', '请选择节点后再执行解组');
|
||||
return true;
|
||||
}
|
||||
models.forEach((model) => {
|
||||
updateNodeMeta(model, (meta) => {
|
||||
const nextMeta = { ...meta };
|
||||
delete nextMeta.groupId;
|
||||
return nextMeta;
|
||||
});
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleArrowMove(direction: 'left' | 'right' | 'up' | 'down', event?: KeyboardEvent) {
|
||||
if (shouldSkipShortcut(event)) return true;
|
||||
const step = (event?.shiftKey ? MOVE_STEP_LARGE : MOVE_STEP) * (direction === 'left' || direction === 'up' ? -1 : 1);
|
||||
if (direction === 'left' || direction === 'right') {
|
||||
moveSelectedNodes(step, 0);
|
||||
} else {
|
||||
moveSelectedNodes(0, step);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function remapGroupIds(nodes: GraphData['nodes']) {
|
||||
const map = new Map<string, string>();
|
||||
const seed = Date.now().toString(36);
|
||||
nodes.forEach((node, index) => {
|
||||
const meta = ensureMeta((node as any).properties?.meta);
|
||||
if (meta.groupId) {
|
||||
if (!map.has(meta.groupId)) {
|
||||
map.set(meta.groupId, `group_${seed}_${index}`);
|
||||
}
|
||||
meta.groupId = map.get(meta.groupId);
|
||||
}
|
||||
(node as any).properties = { ...(node as any).properties, meta };
|
||||
});
|
||||
}
|
||||
|
||||
function handleCopy(event?: KeyboardEvent) {
|
||||
if (shouldSkipShortcut(event)) return true;
|
||||
const lfInstance = lf.value;
|
||||
if (!lfInstance) return true;
|
||||
const elements = lfInstance.getSelectElements(false);
|
||||
if (!elements.nodes.length && !elements.edges.length) {
|
||||
copyBuffer.value = null;
|
||||
return true;
|
||||
}
|
||||
const nodes = elements.nodes.map((node) => translateNodeData(JSON.parse(JSON.stringify(node)), COPY_TRANSLATION));
|
||||
const edges = elements.edges.map((edge) => translateEdgeData(JSON.parse(JSON.stringify(edge)), COPY_TRANSLATION));
|
||||
remapGroupIds(nodes);
|
||||
copyBuffer.value = { nodes, edges };
|
||||
nextPasteDistance = COPY_TRANSLATION;
|
||||
return false;
|
||||
}
|
||||
|
||||
function handlePaste(event?: KeyboardEvent) {
|
||||
if (shouldSkipShortcut(event)) return true;
|
||||
const lfInstance = lf.value;
|
||||
if (!lfInstance || !copyBuffer.value) return true;
|
||||
|
||||
lfInstance.clearSelectElements();
|
||||
const added = lfInstance.addElements(copyBuffer.value, nextPasteDistance);
|
||||
if (added) {
|
||||
added.nodes.forEach((model) => {
|
||||
normalizeNodeModel(model);
|
||||
lfInstance.selectElementById(model.id, true);
|
||||
});
|
||||
added.edges.forEach((edge) => lfInstance.selectElementById(edge.id, true));
|
||||
copyBuffer.value.nodes.forEach((node) => translateNodeData(node, COPY_TRANSLATION));
|
||||
copyBuffer.value.edges.forEach((edge) => translateEdgeData(edge, COPY_TRANSLATION));
|
||||
nextPasteDistance += COPY_TRANSLATION;
|
||||
updateSelectedCount(lfInstance.graphModel);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function handleNodeDrag(args: { data: NodeData; deltaX: number; deltaY: number }) {
|
||||
const { data, deltaX, deltaY } = args;
|
||||
if (!deltaX && !deltaY) return;
|
||||
const graphModel = lf.value?.graphModel;
|
||||
if (!graphModel) return;
|
||||
const model = graphModel.getNodeModelById(data.id);
|
||||
if (!model) return;
|
||||
const targets = collectGroupNodeIds([model]).filter((id) => id !== model.id);
|
||||
if (!targets.length) return;
|
||||
graphModel.moveNodes(targets, deltaX, deltaY);
|
||||
}
|
||||
|
||||
function updateSelectedCount(model?: GraphModel) {
|
||||
const lfInstance = lf.value;
|
||||
@@ -127,7 +406,11 @@ function applySnapGrid(enabled: boolean) {
|
||||
function getSelectedRects() {
|
||||
const lfInstance = lf.value;
|
||||
if (!lfInstance) return [];
|
||||
return lfInstance.graphModel.selectNodes.map((model: BaseNodeModel) => {
|
||||
const unlocked = lfInstance.graphModel.selectNodes.filter((model: BaseNodeModel) => {
|
||||
const meta = ensureMeta((model.getProperties?.() as any)?.meta ?? (model as any)?.properties?.meta);
|
||||
return !meta.locked && meta.visible !== false;
|
||||
});
|
||||
return unlocked.map((model: BaseNodeModel) => {
|
||||
const bounds = model.getBounds();
|
||||
const width = bounds.maxX - bounds.minX;
|
||||
const height = bounds.maxY - bounds.minY;
|
||||
@@ -242,6 +525,9 @@ onMounted(() => {
|
||||
allowRotate: true,
|
||||
overlapMode: -1,
|
||||
snapline: true,
|
||||
keyboard: {
|
||||
enabled: true
|
||||
},
|
||||
plugins: [Menu, Label, Snapshot, SelectionSelect],
|
||||
pluginsOptions: {
|
||||
label: {
|
||||
@@ -257,6 +543,26 @@ onMounted(() => {
|
||||
const lfInstance = lf.value;
|
||||
if (!lfInstance) return;
|
||||
|
||||
lfInstance.keyboard.off(['cmd + c', 'ctrl + c']);
|
||||
lfInstance.keyboard.off(['cmd + v', 'ctrl + v']);
|
||||
lfInstance.keyboard.off(['backspace']);
|
||||
|
||||
const bindShortcut = (keys: string | string[], handler: (event?: KeyboardEvent) => boolean | void) => {
|
||||
lfInstance.keyboard.on(keys, (event: KeyboardEvent) => handler(event));
|
||||
};
|
||||
|
||||
bindShortcut(['del', 'backspace'], deleteSelectedElements);
|
||||
bindShortcut(['left'], (event) => handleArrowMove('left', event));
|
||||
bindShortcut(['right'], (event) => handleArrowMove('right', event));
|
||||
bindShortcut(['up'], (event) => handleArrowMove('up', event));
|
||||
bindShortcut(['down'], (event) => handleArrowMove('down', event));
|
||||
bindShortcut(['cmd + c', 'ctrl + c'], handleCopy);
|
||||
bindShortcut(['cmd + v', 'ctrl + v'], handlePaste);
|
||||
bindShortcut(['cmd + g', 'ctrl + g'], groupSelectedNodes);
|
||||
bindShortcut(['cmd + u', 'ctrl + u'], ungroupSelectedNodes);
|
||||
bindShortcut(['cmd + l', 'ctrl + l'], toggleLockSelected);
|
||||
bindShortcut(['cmd + shift + h', 'ctrl + shift + h'], toggleVisibilitySelected);
|
||||
|
||||
lfInstance.extension.menu.addMenuConfig({
|
||||
nodeMenu: [
|
||||
{
|
||||
@@ -307,6 +613,7 @@ onMounted(() => {
|
||||
lfInstance.render({
|
||||
// 渲染的数据
|
||||
});
|
||||
normalizeAllNodes();
|
||||
lfInstance.updateEditConfig({
|
||||
multipleSelectKey: 'shift',
|
||||
snapGrid: snapGridEnabled.value
|
||||
@@ -320,6 +627,15 @@ onMounted(() => {
|
||||
updateSelectedCount();
|
||||
});
|
||||
|
||||
lfInstance.on(EventType.NODE_DRAG, (args) => handleNodeDrag(args as any));
|
||||
|
||||
lfInstance.on(EventType.NODE_ADD, ({ data }) => {
|
||||
const model = lfInstance.getNodeModelById(data.id);
|
||||
if (model) normalizeNodeModel(model);
|
||||
});
|
||||
|
||||
lfInstance.on(EventType.GRAPH_RENDERED, () => normalizeAllNodes());
|
||||
|
||||
// 监听空白点击事件,取消选中
|
||||
lfInstance.on(EventType.BLANK_CLICK, () => {
|
||||
selectedNode.value = null;
|
||||
@@ -337,6 +653,10 @@ onMounted(() => {
|
||||
};
|
||||
}
|
||||
}
|
||||
const model = lfInstance.getNodeModelById(nodeId);
|
||||
if (model && data.properties?.meta) {
|
||||
applyMetaToModel(model, data.properties.meta);
|
||||
}
|
||||
});
|
||||
|
||||
lfInstance.on('selection:selected', () => updateSelectedCount());
|
||||
|
||||
Reference in New Issue
Block a user