feat: add node collapse/expand on double-click and toolbar buttons
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Double-click any node to toggle collapse — hides ports, edges, and children. Toolbar buttons allow collapsing/expanding all nodes at once. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@ -7,6 +7,7 @@ import { FormsModule } from '@angular/forms';
|
||||
import { MessageService } from 'primeng/api';
|
||||
import { SchemaStore } from '../../store/schema.store';
|
||||
import { applyDagreLayout } from '../../features/schema/layout/dagre-layout';
|
||||
import { collapseAll, expandAll } from '../../features/schema/collapse/collapse-utils';
|
||||
|
||||
@Component({
|
||||
selector: 'app-toolbar',
|
||||
@ -74,6 +75,9 @@ import { applyDagreLayout } from '../../features/schema/layout/dagre-layout';
|
||||
(onClick)="store.toggleLasso()"
|
||||
/>
|
||||
<p-button icon="pi pi-image" size="small" [outlined]="true" pTooltip="Экспорт PNG" tooltipPosition="bottom" (onClick)="wip()" />
|
||||
<span style="width: 1px; height: 24px; background: #e0e0e0; margin: 0 4px"></span>
|
||||
<p-button icon="pi pi-minus-circle" size="small" [outlined]="true" pTooltip="Свернуть всё" tooltipPosition="bottom" (onClick)="handleCollapseAll()" />
|
||||
<p-button icon="pi pi-plus-circle" size="small" [outlined]="true" pTooltip="Развернуть всё" tooltipPosition="bottom" (onClick)="handleExpandAll()" />
|
||||
</div>
|
||||
|
||||
<!-- Right: auto-layout + zoom + mode -->
|
||||
@ -158,6 +162,16 @@ export class ToolbarComponent {
|
||||
}
|
||||
}
|
||||
|
||||
handleCollapseAll() {
|
||||
const g = this.store.graph();
|
||||
if (g) collapseAll(g);
|
||||
}
|
||||
|
||||
handleExpandAll() {
|
||||
const g = this.store.graph();
|
||||
if (g) expandAll(g);
|
||||
}
|
||||
|
||||
wip() {
|
||||
this.messageService.add({ severity: 'info', summary: 'В разработке' });
|
||||
}
|
||||
|
||||
@ -20,6 +20,9 @@ export const CARD_HEIGHT = 40;
|
||||
|
||||
export const PORT_RADIUS = 6;
|
||||
|
||||
export const CROSS_COLLAPSED_HEIGHT = 40;
|
||||
export const SPLICE_COLLAPSED_SIZE = 40;
|
||||
|
||||
export const LAYER_GAP = 60;
|
||||
export const DEVICE_GAP = 100;
|
||||
export const LAYER_PADDING_X = 60;
|
||||
|
||||
346
frontend/src/app/features/schema/collapse/collapse-utils.ts
Normal file
346
frontend/src/app/features/schema/collapse/collapse-utils.ts
Normal file
@ -0,0 +1,346 @@
|
||||
import type { Graph, Node, Edge } from '@antv/x6';
|
||||
import {
|
||||
DEVICE_HEADER_HEIGHT,
|
||||
SITE_HEADER_HEIGHT,
|
||||
CROSS_COLLAPSED_HEIGHT,
|
||||
SPLICE_COLLAPSED_SIZE,
|
||||
CROSS_WIDTH,
|
||||
} from '../../../constants/sizes';
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
function setNodeEdgesVisibility(graph: Graph, node: Node, visible: boolean): void {
|
||||
const edges = graph.getConnectedEdges(node);
|
||||
for (const edge of edges) {
|
||||
if (visible) {
|
||||
edge.show();
|
||||
} else {
|
||||
edge.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setPortsVisibility(node: Node, visible: boolean): void {
|
||||
const ports = node.getPorts();
|
||||
for (const port of ports) {
|
||||
node.setPortProp(port.id!, 'attrs/circle/r', visible ? 6 : 0);
|
||||
}
|
||||
}
|
||||
|
||||
function hideChildEdges(graph: Graph, child: Node): void {
|
||||
const edges = graph.getConnectedEdges(child);
|
||||
for (const edge of edges) {
|
||||
edge.hide();
|
||||
}
|
||||
// Also handle card children
|
||||
const grandchildren = child.getChildren() as Node[] | undefined;
|
||||
if (grandchildren) {
|
||||
for (const gc of grandchildren) {
|
||||
const gcEdges = graph.getConnectedEdges(gc);
|
||||
for (const e of gcEdges) {
|
||||
e.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function showChildEdges(graph: Graph, child: Node): void {
|
||||
const childData = child.getData() as Record<string, unknown>;
|
||||
const childCollapsed = childData?.['collapsed'] === true;
|
||||
|
||||
// Show edges connected directly to the child (device-level edges)
|
||||
const edges = graph.getConnectedEdges(child, { deep: false });
|
||||
for (const edge of edges) {
|
||||
edge.show();
|
||||
}
|
||||
|
||||
// Show edges for grandchildren (cards/ports) only if child is not collapsed
|
||||
if (!childCollapsed) {
|
||||
const grandchildren = child.getChildren() as Node[] | undefined;
|
||||
if (grandchildren) {
|
||||
for (const gc of grandchildren) {
|
||||
const gcEdges = graph.getConnectedEdges(gc);
|
||||
for (const e of gcEdges) {
|
||||
e.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Device Node ---
|
||||
|
||||
function collapseDeviceNode(graph: Graph, node: Node): void {
|
||||
const size = node.getSize();
|
||||
const data = node.getData() as Record<string, unknown>;
|
||||
|
||||
// Hide card children
|
||||
const children = node.getChildren() as Node[] | undefined;
|
||||
if (children) {
|
||||
for (const child of children) {
|
||||
child.hide();
|
||||
hideChildEdges(graph, child);
|
||||
}
|
||||
}
|
||||
|
||||
setNodeEdgesVisibility(graph, node, false);
|
||||
setPortsVisibility(node, false);
|
||||
|
||||
node.resize(size.width, DEVICE_HEADER_HEIGHT);
|
||||
node.setData({
|
||||
...data,
|
||||
collapsed: true,
|
||||
_expandedWidth: size.width,
|
||||
_expandedHeight: size.height,
|
||||
});
|
||||
}
|
||||
|
||||
function expandDeviceNode(graph: Graph, node: Node): void {
|
||||
const data = node.getData() as Record<string, unknown>;
|
||||
const expandedW = (data['_expandedWidth'] as number) || node.getSize().width;
|
||||
const expandedH = (data['_expandedHeight'] as number) || node.getSize().height;
|
||||
|
||||
node.resize(expandedW, expandedH);
|
||||
|
||||
// Show card children
|
||||
const children = node.getChildren() as Node[] | undefined;
|
||||
if (children) {
|
||||
for (const child of children) {
|
||||
child.show();
|
||||
showChildEdges(graph, child);
|
||||
}
|
||||
}
|
||||
|
||||
setNodeEdgesVisibility(graph, node, true);
|
||||
setPortsVisibility(node, true);
|
||||
|
||||
node.setData({
|
||||
...data,
|
||||
collapsed: false,
|
||||
_expandedWidth: undefined,
|
||||
_expandedHeight: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
// --- Site Node ---
|
||||
|
||||
function collapseSiteNode(graph: Graph, node: Node): void {
|
||||
const size = node.getSize();
|
||||
const data = node.getData() as Record<string, unknown>;
|
||||
|
||||
const children = node.getChildren() as Node[] | undefined;
|
||||
if (children) {
|
||||
for (const child of children) {
|
||||
child.hide();
|
||||
hideChildEdges(graph, child);
|
||||
// Also hide grandchildren (cards inside devices)
|
||||
const grandchildren = child.getChildren() as Node[] | undefined;
|
||||
if (grandchildren) {
|
||||
for (const gc of grandchildren) {
|
||||
gc.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.resize(size.width, SITE_HEADER_HEIGHT);
|
||||
node.setData({
|
||||
...data,
|
||||
collapsed: true,
|
||||
_expandedWidth: size.width,
|
||||
_expandedHeight: size.height,
|
||||
});
|
||||
}
|
||||
|
||||
function expandSiteNode(graph: Graph, node: Node): void {
|
||||
const data = node.getData() as Record<string, unknown>;
|
||||
const expandedW = (data['_expandedWidth'] as number) || node.getSize().width;
|
||||
const expandedH = (data['_expandedHeight'] as number) || node.getSize().height;
|
||||
|
||||
node.resize(expandedW, expandedH);
|
||||
|
||||
const children = node.getChildren() as Node[] | undefined;
|
||||
if (children) {
|
||||
for (const child of children) {
|
||||
child.show();
|
||||
const childData = child.getData() as Record<string, unknown>;
|
||||
const childCollapsed = childData?.['collapsed'] === true;
|
||||
|
||||
if (!childCollapsed) {
|
||||
// Device is expanded — show its cards and all edges
|
||||
const grandchildren = child.getChildren() as Node[] | undefined;
|
||||
if (grandchildren) {
|
||||
for (const gc of grandchildren) {
|
||||
gc.show();
|
||||
}
|
||||
}
|
||||
showChildEdges(graph, child);
|
||||
} else {
|
||||
// Device remains collapsed — show device-level edges only
|
||||
const directEdges = graph.getConnectedEdges(child, { deep: false });
|
||||
for (const edge of directEdges) {
|
||||
edge.show();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
node.setData({
|
||||
...data,
|
||||
collapsed: false,
|
||||
_expandedWidth: undefined,
|
||||
_expandedHeight: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
// --- Cross Device Node ---
|
||||
|
||||
function collapseCrossNode(graph: Graph, node: Node): void {
|
||||
const size = node.getSize();
|
||||
const data = node.getData() as Record<string, unknown>;
|
||||
|
||||
setNodeEdgesVisibility(graph, node, false);
|
||||
setPortsVisibility(node, false);
|
||||
|
||||
node.resize(CROSS_WIDTH, CROSS_COLLAPSED_HEIGHT);
|
||||
node.setData({
|
||||
...data,
|
||||
collapsed: true,
|
||||
_expandedWidth: size.width,
|
||||
_expandedHeight: size.height,
|
||||
});
|
||||
}
|
||||
|
||||
function expandCrossNode(graph: Graph, node: Node): void {
|
||||
const data = node.getData() as Record<string, unknown>;
|
||||
const expandedW = (data['_expandedWidth'] as number) || node.getSize().width;
|
||||
const expandedH = (data['_expandedHeight'] as number) || node.getSize().height;
|
||||
|
||||
node.resize(expandedW, expandedH);
|
||||
|
||||
setNodeEdgesVisibility(graph, node, true);
|
||||
setPortsVisibility(node, true);
|
||||
|
||||
node.setData({
|
||||
...data,
|
||||
collapsed: false,
|
||||
_expandedWidth: undefined,
|
||||
_expandedHeight: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
// --- Splice Node ---
|
||||
|
||||
function collapseSpliceNode(graph: Graph, node: Node): void {
|
||||
const size = node.getSize();
|
||||
const data = node.getData() as Record<string, unknown>;
|
||||
|
||||
setNodeEdgesVisibility(graph, node, false);
|
||||
setPortsVisibility(node, false);
|
||||
|
||||
node.resize(SPLICE_COLLAPSED_SIZE, SPLICE_COLLAPSED_SIZE);
|
||||
node.setData({
|
||||
...data,
|
||||
collapsed: true,
|
||||
_expandedWidth: size.width,
|
||||
_expandedHeight: size.height,
|
||||
});
|
||||
}
|
||||
|
||||
function expandSpliceNode(graph: Graph, node: Node): void {
|
||||
const data = node.getData() as Record<string, unknown>;
|
||||
const expandedW = (data['_expandedWidth'] as number) || node.getSize().width;
|
||||
const expandedH = (data['_expandedHeight'] as number) || node.getSize().height;
|
||||
|
||||
node.resize(expandedW, expandedH);
|
||||
|
||||
setNodeEdgesVisibility(graph, node, true);
|
||||
setPortsVisibility(node, true);
|
||||
|
||||
node.setData({
|
||||
...data,
|
||||
collapsed: false,
|
||||
_expandedWidth: undefined,
|
||||
_expandedHeight: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
// --- Public API ---
|
||||
|
||||
export function toggleNodeCollapse(graph: Graph, node: Node): void {
|
||||
const data = node.getData() as Record<string, unknown>;
|
||||
const collapsed = data?.['collapsed'] === true;
|
||||
const shape = node.shape;
|
||||
|
||||
if (collapsed) {
|
||||
switch (shape) {
|
||||
case 'device-node':
|
||||
expandDeviceNode(graph, node);
|
||||
break;
|
||||
case 'site-node':
|
||||
expandSiteNode(graph, node);
|
||||
break;
|
||||
case 'cross-device-node':
|
||||
expandCrossNode(graph, node);
|
||||
break;
|
||||
case 'splice-node':
|
||||
expandSpliceNode(graph, node);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (shape) {
|
||||
case 'device-node':
|
||||
collapseDeviceNode(graph, node);
|
||||
break;
|
||||
case 'site-node':
|
||||
collapseSiteNode(graph, node);
|
||||
break;
|
||||
case 'cross-device-node':
|
||||
collapseCrossNode(graph, node);
|
||||
break;
|
||||
case 'splice-node':
|
||||
collapseSpliceNode(graph, node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function collapseAll(graph: Graph): void {
|
||||
const nodes = graph.getNodes();
|
||||
|
||||
// Collapse devices first (inside sites), then sites
|
||||
for (const node of nodes) {
|
||||
const data = node.getData() as Record<string, unknown>;
|
||||
if (data?.['collapsed'] === true) continue;
|
||||
const shape = node.shape;
|
||||
if (shape === 'device-node') collapseDeviceNode(graph, node);
|
||||
else if (shape === 'cross-device-node') collapseCrossNode(graph, node);
|
||||
else if (shape === 'splice-node') collapseSpliceNode(graph, node);
|
||||
}
|
||||
|
||||
for (const node of nodes) {
|
||||
const data = node.getData() as Record<string, unknown>;
|
||||
if (data?.['collapsed'] === true) continue;
|
||||
if (node.shape === 'site-node') collapseSiteNode(graph, node);
|
||||
}
|
||||
}
|
||||
|
||||
export function expandAll(graph: Graph): void {
|
||||
const nodes = graph.getNodes();
|
||||
|
||||
// Expand sites first, then devices
|
||||
for (const node of nodes) {
|
||||
const data = node.getData() as Record<string, unknown>;
|
||||
if (data?.['collapsed'] !== true) continue;
|
||||
if (node.shape === 'site-node') expandSiteNode(graph, node);
|
||||
}
|
||||
|
||||
for (const node of nodes) {
|
||||
const data = node.getData() as Record<string, unknown>;
|
||||
if (data?.['collapsed'] !== true) continue;
|
||||
const shape = node.shape;
|
||||
if (shape === 'device-node') expandDeviceNode(graph, node);
|
||||
else if (shape === 'cross-device-node') expandCrossNode(graph, node);
|
||||
else if (shape === 'splice-node') expandSpliceNode(graph, node);
|
||||
}
|
||||
}
|
||||
@ -10,6 +10,7 @@ interface CrossDeviceData {
|
||||
id1: string;
|
||||
id2: string;
|
||||
status: EntityStatus;
|
||||
collapsed?: boolean;
|
||||
}
|
||||
|
||||
export function renderCrossDeviceNode(cell: Cell): HTMLElement {
|
||||
@ -73,16 +74,32 @@ export function renderCrossDeviceNode(cell: Cell): HTMLElement {
|
||||
pointerEvents: 'none',
|
||||
});
|
||||
|
||||
const nameRow = document.createElement('div');
|
||||
Object.assign(nameRow.style, {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '4px',
|
||||
});
|
||||
|
||||
const indicator = document.createElement('span');
|
||||
Object.assign(indicator.style, { fontSize: '9px', lineHeight: '1' });
|
||||
indicator.textContent = data.collapsed ? '\u25B6' : '\u25BC';
|
||||
nameRow.appendChild(indicator);
|
||||
|
||||
const nameEl = document.createElement('div');
|
||||
Object.assign(nameEl.style, {
|
||||
fontWeight: '700',
|
||||
fontSize: '11px',
|
||||
marginBottom: '4px',
|
||||
marginBottom: data.collapsed ? '0' : '4px',
|
||||
wordBreak: 'break-word',
|
||||
});
|
||||
nameEl.textContent = data.name;
|
||||
textDiv.appendChild(nameEl);
|
||||
nameRow.appendChild(nameEl);
|
||||
|
||||
textDiv.appendChild(nameRow);
|
||||
|
||||
if (!data.collapsed) {
|
||||
if (data.networkName) {
|
||||
const el = document.createElement('div');
|
||||
Object.assign(el.style, { color: '#595959', fontSize: '9px' });
|
||||
@ -103,6 +120,7 @@ export function renderCrossDeviceNode(cell: Cell): HTMLElement {
|
||||
el.textContent = data.id1;
|
||||
textDiv.appendChild(el);
|
||||
}
|
||||
}
|
||||
|
||||
container.appendChild(textDiv);
|
||||
|
||||
|
||||
@ -12,6 +12,7 @@ interface DeviceNodeData {
|
||||
id2: string;
|
||||
group: DeviceGroup;
|
||||
status: EntityStatus;
|
||||
collapsed?: boolean;
|
||||
}
|
||||
|
||||
export function renderDeviceNode(cell: Cell): HTMLElement {
|
||||
@ -51,6 +52,19 @@ export function renderDeviceNode(cell: Cell): HTMLElement {
|
||||
textAlign: 'center',
|
||||
});
|
||||
|
||||
const headerTop = document.createElement('div');
|
||||
Object.assign(headerTop.style, {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '4px',
|
||||
});
|
||||
|
||||
const indicator = document.createElement('span');
|
||||
Object.assign(indicator.style, { fontSize: '9px', lineHeight: '1' });
|
||||
indicator.textContent = data.collapsed ? '\u25B6' : '\u25BC';
|
||||
headerTop.appendChild(indicator);
|
||||
|
||||
const nameEl = document.createElement('div');
|
||||
Object.assign(nameEl.style, {
|
||||
fontWeight: '700',
|
||||
@ -59,7 +73,9 @@ export function renderDeviceNode(cell: Cell): HTMLElement {
|
||||
lineHeight: '13px',
|
||||
});
|
||||
nameEl.textContent = data.name;
|
||||
header.appendChild(nameEl);
|
||||
headerTop.appendChild(nameEl);
|
||||
|
||||
header.appendChild(headerTop);
|
||||
|
||||
if (isActive) {
|
||||
if (data.networkName) {
|
||||
@ -79,6 +95,7 @@ export function renderDeviceNode(cell: Cell): HTMLElement {
|
||||
|
||||
container.appendChild(header);
|
||||
|
||||
if (!data.collapsed) {
|
||||
// Body
|
||||
const body = document.createElement('div');
|
||||
Object.assign(body.style, {
|
||||
@ -114,6 +131,7 @@ export function renderDeviceNode(cell: Cell): HTMLElement {
|
||||
}
|
||||
|
||||
container.appendChild(body);
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
@ -9,6 +9,7 @@ interface SiteNodeData {
|
||||
erpCode: string;
|
||||
code1C: string;
|
||||
status: EntityStatus;
|
||||
collapsed?: boolean;
|
||||
}
|
||||
|
||||
export function renderSiteNode(cell: Cell): HTMLElement {
|
||||
@ -44,6 +45,18 @@ export function renderSiteNode(cell: Cell): HTMLElement {
|
||||
pointerEvents: 'auto',
|
||||
});
|
||||
|
||||
const nameRow = document.createElement('div');
|
||||
Object.assign(nameRow.style, {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: '4px',
|
||||
});
|
||||
|
||||
const indicator = document.createElement('span');
|
||||
Object.assign(indicator.style, { fontSize: '9px', lineHeight: '1' });
|
||||
indicator.textContent = data.collapsed ? '\u25B6' : '\u25BC';
|
||||
nameRow.appendChild(indicator);
|
||||
|
||||
const nameEl = document.createElement('div');
|
||||
Object.assign(nameEl.style, {
|
||||
fontWeight: '700',
|
||||
@ -51,6 +64,7 @@ export function renderSiteNode(cell: Cell): HTMLElement {
|
||||
wordBreak: 'break-word',
|
||||
});
|
||||
nameEl.textContent = data.name;
|
||||
nameRow.appendChild(nameEl);
|
||||
|
||||
const infoEl = document.createElement('div');
|
||||
Object.assign(infoEl.style, {
|
||||
@ -60,7 +74,7 @@ export function renderSiteNode(cell: Cell): HTMLElement {
|
||||
});
|
||||
infoEl.textContent = `${data.address} | ERP: ${data.erpCode} | 1С: ${data.code1C}`;
|
||||
|
||||
header.appendChild(nameEl);
|
||||
header.appendChild(nameRow);
|
||||
header.appendChild(infoEl);
|
||||
container.appendChild(header);
|
||||
|
||||
|
||||
@ -9,6 +9,7 @@ interface SpliceNodeData {
|
||||
id1: string;
|
||||
id2: string;
|
||||
status: EntityStatus;
|
||||
collapsed?: boolean;
|
||||
}
|
||||
|
||||
export function renderSpliceNode(cell: Cell): HTMLElement {
|
||||
@ -34,17 +35,32 @@ export function renderSpliceNode(cell: Cell): HTMLElement {
|
||||
lineHeight: '14px',
|
||||
});
|
||||
|
||||
const nameRow = document.createElement('div');
|
||||
Object.assign(nameRow.style, {
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '4px',
|
||||
});
|
||||
|
||||
const indicator = document.createElement('span');
|
||||
Object.assign(indicator.style, { fontSize: '9px', lineHeight: '1' });
|
||||
indicator.textContent = data.collapsed ? '\u25B6' : '\u25BC';
|
||||
nameRow.appendChild(indicator);
|
||||
|
||||
const nameEl = document.createElement('div');
|
||||
Object.assign(nameEl.style, {
|
||||
fontWeight: '700',
|
||||
fontSize: '11px',
|
||||
marginBottom: '2px',
|
||||
marginBottom: data.collapsed ? '0' : '2px',
|
||||
wordBreak: 'break-word',
|
||||
});
|
||||
nameEl.textContent = data.name;
|
||||
container.appendChild(nameEl);
|
||||
nameRow.appendChild(nameEl);
|
||||
|
||||
if (data.marking) {
|
||||
container.appendChild(nameRow);
|
||||
|
||||
if (!data.collapsed && data.marking) {
|
||||
const markingEl = document.createElement('div');
|
||||
Object.assign(markingEl.style, { color: '#595959', fontSize: '9px' });
|
||||
markingEl.textContent = data.marking;
|
||||
|
||||
@ -13,6 +13,7 @@ import { registerAllNodes } from '../graph/register-nodes';
|
||||
import { buildGraphData } from '../helpers/data-mapper';
|
||||
import { mockData } from '../../../mock/schema-data';
|
||||
import { DeviceGroup } from '../../../types/index';
|
||||
import { toggleNodeCollapse } from '../collapse/collapse-utils';
|
||||
import type { Graph } from '@antv/x6';
|
||||
|
||||
let nodesRegistered = false;
|
||||
@ -233,6 +234,10 @@ export class SchemaCanvasComponent implements AfterViewInit, OnDestroy {
|
||||
this.store.setRightPanelData(null);
|
||||
});
|
||||
|
||||
graph.on('node:dblclick', ({ node }) => {
|
||||
toggleNodeCollapse(graph, node);
|
||||
});
|
||||
|
||||
graph.on('edge:dblclick', ({ edge }) => {
|
||||
const line = mockData.lines.find((l) => l.id === edge.id);
|
||||
if (line) {
|
||||
|
||||
Reference in New Issue
Block a user