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 { MessageService } from 'primeng/api';
|
||||||
import { SchemaStore } from '../../store/schema.store';
|
import { SchemaStore } from '../../store/schema.store';
|
||||||
import { applyDagreLayout } from '../../features/schema/layout/dagre-layout';
|
import { applyDagreLayout } from '../../features/schema/layout/dagre-layout';
|
||||||
|
import { collapseAll, expandAll } from '../../features/schema/collapse/collapse-utils';
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-toolbar',
|
selector: 'app-toolbar',
|
||||||
@ -74,6 +75,9 @@ import { applyDagreLayout } from '../../features/schema/layout/dagre-layout';
|
|||||||
(onClick)="store.toggleLasso()"
|
(onClick)="store.toggleLasso()"
|
||||||
/>
|
/>
|
||||||
<p-button icon="pi pi-image" size="small" [outlined]="true" pTooltip="Экспорт PNG" tooltipPosition="bottom" (onClick)="wip()" />
|
<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>
|
</div>
|
||||||
|
|
||||||
<!-- Right: auto-layout + zoom + mode -->
|
<!-- 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() {
|
wip() {
|
||||||
this.messageService.add({ severity: 'info', summary: 'В разработке' });
|
this.messageService.add({ severity: 'info', summary: 'В разработке' });
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,9 @@ export const CARD_HEIGHT = 40;
|
|||||||
|
|
||||||
export const PORT_RADIUS = 6;
|
export const PORT_RADIUS = 6;
|
||||||
|
|
||||||
|
export const CROSS_COLLAPSED_HEIGHT = 40;
|
||||||
|
export const SPLICE_COLLAPSED_SIZE = 40;
|
||||||
|
|
||||||
export const LAYER_GAP = 60;
|
export const LAYER_GAP = 60;
|
||||||
export const DEVICE_GAP = 100;
|
export const DEVICE_GAP = 100;
|
||||||
export const LAYER_PADDING_X = 60;
|
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;
|
id1: string;
|
||||||
id2: string;
|
id2: string;
|
||||||
status: EntityStatus;
|
status: EntityStatus;
|
||||||
|
collapsed?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderCrossDeviceNode(cell: Cell): HTMLElement {
|
export function renderCrossDeviceNode(cell: Cell): HTMLElement {
|
||||||
@ -73,16 +74,32 @@ export function renderCrossDeviceNode(cell: Cell): HTMLElement {
|
|||||||
pointerEvents: 'none',
|
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');
|
const nameEl = document.createElement('div');
|
||||||
Object.assign(nameEl.style, {
|
Object.assign(nameEl.style, {
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
fontSize: '11px',
|
fontSize: '11px',
|
||||||
marginBottom: '4px',
|
marginBottom: data.collapsed ? '0' : '4px',
|
||||||
wordBreak: 'break-word',
|
wordBreak: 'break-word',
|
||||||
});
|
});
|
||||||
nameEl.textContent = data.name;
|
nameEl.textContent = data.name;
|
||||||
textDiv.appendChild(nameEl);
|
nameRow.appendChild(nameEl);
|
||||||
|
|
||||||
|
textDiv.appendChild(nameRow);
|
||||||
|
|
||||||
|
if (!data.collapsed) {
|
||||||
if (data.networkName) {
|
if (data.networkName) {
|
||||||
const el = document.createElement('div');
|
const el = document.createElement('div');
|
||||||
Object.assign(el.style, { color: '#595959', fontSize: '9px' });
|
Object.assign(el.style, { color: '#595959', fontSize: '9px' });
|
||||||
@ -103,6 +120,7 @@ export function renderCrossDeviceNode(cell: Cell): HTMLElement {
|
|||||||
el.textContent = data.id1;
|
el.textContent = data.id1;
|
||||||
textDiv.appendChild(el);
|
textDiv.appendChild(el);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
container.appendChild(textDiv);
|
container.appendChild(textDiv);
|
||||||
|
|
||||||
|
|||||||
@ -12,6 +12,7 @@ interface DeviceNodeData {
|
|||||||
id2: string;
|
id2: string;
|
||||||
group: DeviceGroup;
|
group: DeviceGroup;
|
||||||
status: EntityStatus;
|
status: EntityStatus;
|
||||||
|
collapsed?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderDeviceNode(cell: Cell): HTMLElement {
|
export function renderDeviceNode(cell: Cell): HTMLElement {
|
||||||
@ -51,6 +52,19 @@ export function renderDeviceNode(cell: Cell): HTMLElement {
|
|||||||
textAlign: 'center',
|
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');
|
const nameEl = document.createElement('div');
|
||||||
Object.assign(nameEl.style, {
|
Object.assign(nameEl.style, {
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
@ -59,7 +73,9 @@ export function renderDeviceNode(cell: Cell): HTMLElement {
|
|||||||
lineHeight: '13px',
|
lineHeight: '13px',
|
||||||
});
|
});
|
||||||
nameEl.textContent = data.name;
|
nameEl.textContent = data.name;
|
||||||
header.appendChild(nameEl);
|
headerTop.appendChild(nameEl);
|
||||||
|
|
||||||
|
header.appendChild(headerTop);
|
||||||
|
|
||||||
if (isActive) {
|
if (isActive) {
|
||||||
if (data.networkName) {
|
if (data.networkName) {
|
||||||
@ -79,6 +95,7 @@ export function renderDeviceNode(cell: Cell): HTMLElement {
|
|||||||
|
|
||||||
container.appendChild(header);
|
container.appendChild(header);
|
||||||
|
|
||||||
|
if (!data.collapsed) {
|
||||||
// Body
|
// Body
|
||||||
const body = document.createElement('div');
|
const body = document.createElement('div');
|
||||||
Object.assign(body.style, {
|
Object.assign(body.style, {
|
||||||
@ -114,6 +131,7 @@ export function renderDeviceNode(cell: Cell): HTMLElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
container.appendChild(body);
|
container.appendChild(body);
|
||||||
|
}
|
||||||
|
|
||||||
return container;
|
return container;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ interface SiteNodeData {
|
|||||||
erpCode: string;
|
erpCode: string;
|
||||||
code1C: string;
|
code1C: string;
|
||||||
status: EntityStatus;
|
status: EntityStatus;
|
||||||
|
collapsed?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderSiteNode(cell: Cell): HTMLElement {
|
export function renderSiteNode(cell: Cell): HTMLElement {
|
||||||
@ -44,6 +45,18 @@ export function renderSiteNode(cell: Cell): HTMLElement {
|
|||||||
pointerEvents: 'auto',
|
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');
|
const nameEl = document.createElement('div');
|
||||||
Object.assign(nameEl.style, {
|
Object.assign(nameEl.style, {
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
@ -51,6 +64,7 @@ export function renderSiteNode(cell: Cell): HTMLElement {
|
|||||||
wordBreak: 'break-word',
|
wordBreak: 'break-word',
|
||||||
});
|
});
|
||||||
nameEl.textContent = data.name;
|
nameEl.textContent = data.name;
|
||||||
|
nameRow.appendChild(nameEl);
|
||||||
|
|
||||||
const infoEl = document.createElement('div');
|
const infoEl = document.createElement('div');
|
||||||
Object.assign(infoEl.style, {
|
Object.assign(infoEl.style, {
|
||||||
@ -60,7 +74,7 @@ export function renderSiteNode(cell: Cell): HTMLElement {
|
|||||||
});
|
});
|
||||||
infoEl.textContent = `${data.address} | ERP: ${data.erpCode} | 1С: ${data.code1C}`;
|
infoEl.textContent = `${data.address} | ERP: ${data.erpCode} | 1С: ${data.code1C}`;
|
||||||
|
|
||||||
header.appendChild(nameEl);
|
header.appendChild(nameRow);
|
||||||
header.appendChild(infoEl);
|
header.appendChild(infoEl);
|
||||||
container.appendChild(header);
|
container.appendChild(header);
|
||||||
|
|
||||||
|
|||||||
@ -9,6 +9,7 @@ interface SpliceNodeData {
|
|||||||
id1: string;
|
id1: string;
|
||||||
id2: string;
|
id2: string;
|
||||||
status: EntityStatus;
|
status: EntityStatus;
|
||||||
|
collapsed?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderSpliceNode(cell: Cell): HTMLElement {
|
export function renderSpliceNode(cell: Cell): HTMLElement {
|
||||||
@ -34,17 +35,32 @@ export function renderSpliceNode(cell: Cell): HTMLElement {
|
|||||||
lineHeight: '14px',
|
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');
|
const nameEl = document.createElement('div');
|
||||||
Object.assign(nameEl.style, {
|
Object.assign(nameEl.style, {
|
||||||
fontWeight: '700',
|
fontWeight: '700',
|
||||||
fontSize: '11px',
|
fontSize: '11px',
|
||||||
marginBottom: '2px',
|
marginBottom: data.collapsed ? '0' : '2px',
|
||||||
wordBreak: 'break-word',
|
wordBreak: 'break-word',
|
||||||
});
|
});
|
||||||
nameEl.textContent = data.name;
|
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');
|
const markingEl = document.createElement('div');
|
||||||
Object.assign(markingEl.style, { color: '#595959', fontSize: '9px' });
|
Object.assign(markingEl.style, { color: '#595959', fontSize: '9px' });
|
||||||
markingEl.textContent = data.marking;
|
markingEl.textContent = data.marking;
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { registerAllNodes } from '../graph/register-nodes';
|
|||||||
import { buildGraphData } from '../helpers/data-mapper';
|
import { buildGraphData } from '../helpers/data-mapper';
|
||||||
import { mockData } from '../../../mock/schema-data';
|
import { mockData } from '../../../mock/schema-data';
|
||||||
import { DeviceGroup } from '../../../types/index';
|
import { DeviceGroup } from '../../../types/index';
|
||||||
|
import { toggleNodeCollapse } from '../collapse/collapse-utils';
|
||||||
import type { Graph } from '@antv/x6';
|
import type { Graph } from '@antv/x6';
|
||||||
|
|
||||||
let nodesRegistered = false;
|
let nodesRegistered = false;
|
||||||
@ -233,6 +234,10 @@ export class SchemaCanvasComponent implements AfterViewInit, OnDestroy {
|
|||||||
this.store.setRightPanelData(null);
|
this.store.setRightPanelData(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
graph.on('node:dblclick', ({ node }) => {
|
||||||
|
toggleNodeCollapse(graph, node);
|
||||||
|
});
|
||||||
|
|
||||||
graph.on('edge:dblclick', ({ edge }) => {
|
graph.on('edge:dblclick', ({ edge }) => {
|
||||||
const line = mockData.lines.find((l) => l.id === edge.id);
|
const line = mockData.lines.find((l) => l.id === edge.id);
|
||||||
if (line) {
|
if (line) {
|
||||||
|
|||||||
Reference in New Issue
Block a user