mirror of
https://github.com/sotam0316/drawNET_test.git
synced 2026-04-25 03:58:38 +09:00
static 폴더 및 하위 파일 업로드
This commit is contained in:
@@ -0,0 +1,190 @@
|
||||
import { state } from '../../state.js';
|
||||
import { logger } from '../../utils/logger.js';
|
||||
|
||||
const generateShortId = () => Math.random().toString(36).substring(2, 6);
|
||||
|
||||
export const clipboardHandlers = {
|
||||
copyAttributes: () => {
|
||||
if (!state.graph) return;
|
||||
const selected = state.graph.getSelectedCells();
|
||||
if (selected.length === 0) return;
|
||||
|
||||
const cell = selected[0];
|
||||
const data = cell.getData() || {};
|
||||
|
||||
if (cell.isNode()) {
|
||||
const whitelistedData = {};
|
||||
const dataKeys = ['vendor', 'model', 'status', 'project', 'env', 'tags', 'color', 'asset_path'];
|
||||
dataKeys.forEach(key => {
|
||||
if (data[key] !== undefined) whitelistedData[key] = data[key];
|
||||
});
|
||||
|
||||
const whitelistedAttrs = {
|
||||
label: {
|
||||
fill: cell.attr('label/fill'),
|
||||
fontSize: cell.attr('label/fontSize'),
|
||||
},
|
||||
body: {
|
||||
fill: cell.attr('body/fill'),
|
||||
stroke: cell.attr('body/stroke'),
|
||||
strokeWidth: cell.attr('body/strokeWidth'),
|
||||
}
|
||||
};
|
||||
|
||||
state.clipboardNodeData = whitelistedData;
|
||||
state.clipboardNodeAttrs = whitelistedAttrs;
|
||||
state.clipboardEdgeData = null; // Clear edge clipboard
|
||||
logger.info(`Node attributes copied from ${cell.id}`);
|
||||
} else if (cell.isEdge()) {
|
||||
const edgeKeys = [
|
||||
'color', 'routing', 'style', 'source_anchor', 'target_anchor',
|
||||
'direction', 'is_tunnel', 'routing_offset', 'width', 'flow'
|
||||
];
|
||||
const edgeData = {};
|
||||
edgeKeys.forEach(key => {
|
||||
if (data[key] !== undefined) edgeData[key] = data[key];
|
||||
});
|
||||
|
||||
state.clipboardEdgeData = edgeData;
|
||||
state.clipboardNodeData = null; // Clear node clipboard
|
||||
logger.info(`Edge attributes (Style) copied from ${cell.id}`);
|
||||
}
|
||||
},
|
||||
|
||||
pasteAttributes: async () => {
|
||||
if (!state.graph) return;
|
||||
const selected = state.graph.getSelectedCells();
|
||||
if (selected.length === 0) return;
|
||||
|
||||
const hasNodeData = !!state.clipboardNodeData;
|
||||
const hasEdgeData = !!state.clipboardEdgeData;
|
||||
|
||||
if (!hasNodeData && !hasEdgeData) return;
|
||||
|
||||
const { handleEdgeUpdate } = await import('../../properties_sidebar/handlers/edge.js');
|
||||
|
||||
state.graph.batchUpdate(async () => {
|
||||
for (const cell of selected) {
|
||||
if (cell.isNode() && hasNodeData) {
|
||||
const currentData = cell.getData() || {};
|
||||
cell.setData({ ...currentData, ...state.clipboardNodeData });
|
||||
|
||||
if (state.clipboardNodeAttrs) {
|
||||
const attrs = state.clipboardNodeAttrs;
|
||||
if (attrs.label) {
|
||||
if (attrs.label.fill) cell.attr('label/fill', attrs.label.fill);
|
||||
if (attrs.label.fontSize) cell.attr('label/fontSize', attrs.label.fontSize);
|
||||
}
|
||||
if (attrs.body) {
|
||||
if (attrs.body.fill) cell.attr('body/fill', attrs.body.fill);
|
||||
if (attrs.body.stroke) cell.attr('body/stroke', attrs.body.stroke);
|
||||
if (attrs.body.strokeWidth) cell.attr('body/strokeWidth', attrs.body.strokeWidth);
|
||||
}
|
||||
}
|
||||
} else if (cell.isEdge() && hasEdgeData) {
|
||||
// Apply Edge Formatting
|
||||
// We use handleEdgeUpdate to ensure physical routers/anchors are updated
|
||||
await handleEdgeUpdate(cell, state.clipboardEdgeData);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
logger.high(`Format Painter applied to ${selected.length} elements.`);
|
||||
import('../../persistence.js').then(m => m.markDirty());
|
||||
},
|
||||
|
||||
copyNodes: () => {
|
||||
if (!state.graph) return;
|
||||
const selected = state.graph.getSelectedCells();
|
||||
if (selected.length === 0) return;
|
||||
|
||||
state.graph.copy(selected);
|
||||
logger.high("Nodes copied to clipboard (X6).");
|
||||
},
|
||||
|
||||
pasteNodes: () => {
|
||||
if (!state.graph || state.graph.isClipboardEmpty()) return;
|
||||
|
||||
// Logical Layer Guard: Filter out nodes if target layer is logical
|
||||
const activeLayer = state.layers.find(l => l.id === state.activeLayerId);
|
||||
const isLogical = activeLayer && activeLayer.type === 'logical';
|
||||
|
||||
let pasted = state.graph.paste({ offset: 20 });
|
||||
if (pasted && pasted.length > 0) {
|
||||
if (isLogical) {
|
||||
pasted.forEach(cell => { if (cell.isNode()) cell.remove(); });
|
||||
pasted = pasted.filter(c => !c.isNode());
|
||||
if (pasted.length === 0) {
|
||||
import('/static/js/modules/i18n.js').then(({ t }) => {
|
||||
import('../../ui/utils.js').then(m => m.showToast(t('err_logical_layer_drop'), 'warning'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
state.graph.batchUpdate(() => {
|
||||
pasted.forEach(cell => {
|
||||
if (cell.isNode()) {
|
||||
const currentData = cell.getData() || {};
|
||||
const currentLabel = currentData.label || cell.attr('label/text');
|
||||
if (currentLabel) {
|
||||
const newLabel = `${currentLabel}_${generateShortId()}`;
|
||||
cell.setData({ ...currentData, label: newLabel, id: cell.id });
|
||||
cell.attr('label/text', newLabel);
|
||||
} else {
|
||||
cell.setData({ ...currentData, id: cell.id });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
state.graph.cleanSelection();
|
||||
state.graph.select(pasted);
|
||||
}
|
||||
logger.high("Nodes pasted and randomized (Logical Filter applied).");
|
||||
import('../../persistence.js').then(m => m.markDirty());
|
||||
},
|
||||
|
||||
duplicateNodes: () => {
|
||||
if (!state.graph) return;
|
||||
const selected = state.graph.getSelectedCells();
|
||||
if (selected.length === 0) return;
|
||||
|
||||
state.graph.copy(selected);
|
||||
|
||||
// Logical Layer Guard: Filter out nodes if target layer is logical
|
||||
const activeLayer = state.layers.find(l => l.id === state.activeLayerId);
|
||||
const isLogical = activeLayer && activeLayer.type === 'logical';
|
||||
|
||||
let cloned = state.graph.paste({ offset: 30 });
|
||||
if (cloned && cloned.length > 0) {
|
||||
if (isLogical) {
|
||||
cloned.forEach(cell => { if (cell.isNode()) cell.remove(); });
|
||||
cloned = cloned.filter(c => !c.isNode());
|
||||
if (cloned.length === 0) {
|
||||
import('/static/js/modules/i18n.js').then(({ t }) => {
|
||||
import('../../ui/utils.js').then(m => m.showToast(t('err_logical_layer_drop'), 'warning'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
state.graph.batchUpdate(() => {
|
||||
cloned.forEach(cell => {
|
||||
if (cell.isNode()) {
|
||||
const currentData = cell.getData() || {};
|
||||
const currentLabel = currentData.label || cell.attr('label/text');
|
||||
if (currentLabel) {
|
||||
const newLabel = `${currentLabel}_${generateShortId()}`;
|
||||
cell.setData({ ...currentData, label: newLabel, id: cell.id });
|
||||
cell.attr('label/text', newLabel);
|
||||
} else {
|
||||
cell.setData({ ...currentData, id: cell.id });
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
state.graph.cleanSelection();
|
||||
state.graph.select(cloned);
|
||||
}
|
||||
logger.high("Nodes duplicated and randomized (Logical Filter applied).");
|
||||
import('../../persistence.js').then(m => m.markDirty());
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,111 @@
|
||||
import { state } from '../../state.js';
|
||||
import { logger } from '../../utils/logger.js';
|
||||
import { alignNodes, moveNodes, distributeNodes } from '../../graph/alignment.js';
|
||||
import { handleMenuAction } from '../../ui/context_menu/handlers.js';
|
||||
|
||||
export const graphHandlers = {
|
||||
// ... cleanup ...
|
||||
deleteSelected: () => {
|
||||
if (!state.graph) return;
|
||||
const selected = state.graph.getSelectedCells();
|
||||
if (selected.length === 0) return;
|
||||
|
||||
state.graph.removeCells(selected);
|
||||
import('../../persistence.js').then(m => m.markDirty());
|
||||
},
|
||||
fitScreen: () => {
|
||||
if (state.graph) {
|
||||
state.graph.zoomToFit({ padding: 50 });
|
||||
logger.high("Graph fit to screen (X6).");
|
||||
}
|
||||
},
|
||||
zoomIn: () => state.graph?.zoom(0.2),
|
||||
zoomOut: () => state.graph?.zoom(-0.2),
|
||||
connectNodes: () => handleMenuAction('connect-solid'),
|
||||
connectStraight: () => handleMenuAction('connect-straight'),
|
||||
disconnectNodes: () => handleMenuAction('disconnect'),
|
||||
undo: () => {
|
||||
if (!state.graph) return;
|
||||
try {
|
||||
if (typeof state.graph.undo === 'function') {
|
||||
state.graph.undo();
|
||||
logger.info("Undo performed via graph.undo()");
|
||||
} else {
|
||||
const history = state.graph.getPlugin('history');
|
||||
if (history) history.undo();
|
||||
logger.info("Undo performed via history plugin");
|
||||
}
|
||||
} catch (e) {
|
||||
logger.critical("Undo failed", e);
|
||||
}
|
||||
},
|
||||
redo: () => {
|
||||
if (!state.graph) return;
|
||||
try {
|
||||
if (typeof state.graph.redo === 'function') {
|
||||
state.graph.redo();
|
||||
logger.info("Redo performed via graph.redo()");
|
||||
} else {
|
||||
const history = state.graph.getPlugin('history');
|
||||
if (history) history.redo();
|
||||
logger.info("Redo performed via history plugin");
|
||||
}
|
||||
} catch (e) {
|
||||
logger.critical("Redo failed", e);
|
||||
}
|
||||
},
|
||||
arrangeLayout: () => {
|
||||
logger.high("Auto-layout is currently disabled (Refactoring).");
|
||||
},
|
||||
bringToFront: () => {
|
||||
if (!state.graph) return;
|
||||
const selected = state.graph.getSelectedCells();
|
||||
selected.forEach(cell => cell.toFront({ deep: true }));
|
||||
import('../../persistence.js').then(m => m.markDirty());
|
||||
},
|
||||
sendToBack: () => {
|
||||
if (!state.graph) return;
|
||||
const selected = state.graph.getSelectedCells();
|
||||
selected.forEach(cell => cell.toBack({ deep: true }));
|
||||
import('../../persistence.js').then(m => m.markDirty());
|
||||
},
|
||||
toggleLock: () => {
|
||||
if (!state.graph) return;
|
||||
const selected = state.graph.getSelectedCells();
|
||||
if (selected.length === 0) return;
|
||||
|
||||
selected.forEach(cell => {
|
||||
const data = cell.getData() || {};
|
||||
const isLocked = !data.locked;
|
||||
cell.setData({ ...data, locked: isLocked });
|
||||
cell.setProp('movable', !isLocked);
|
||||
cell.setProp('deletable', !isLocked);
|
||||
cell.setProp('rotatable', !isLocked);
|
||||
|
||||
if (isLocked) {
|
||||
cell.addTools([{ name: 'boundary', args: { attrs: { stroke: '#ef4444', 'stroke-dasharray': '5,5' } } }]);
|
||||
} else {
|
||||
cell.removeTools();
|
||||
}
|
||||
});
|
||||
import('../../persistence.js').then(m => m.markDirty());
|
||||
logger.high(`Toggled lock for ${selected.length} elements.`);
|
||||
},
|
||||
alignTop: () => alignNodes('top'),
|
||||
alignBottom: () => alignNodes('bottom'),
|
||||
alignLeft: () => alignNodes('left'),
|
||||
alignRight: () => alignNodes('right'),
|
||||
alignMiddle: () => alignNodes('middle'),
|
||||
alignCenter: () => alignNodes('center'),
|
||||
distributeHorizontal: () => distributeNodes('horizontal'),
|
||||
distributeVertical: () => distributeNodes('vertical'),
|
||||
groupNodes: () => handleMenuAction('group'),
|
||||
moveUp: () => moveNodes(0, -5),
|
||||
moveDown: () => moveNodes(0, 5),
|
||||
moveLeft: () => moveNodes(-5, 0),
|
||||
moveRight: () => moveNodes(5, 0),
|
||||
moveUpLarge: () => moveNodes(0, -(state.gridSpacing || 20)),
|
||||
moveDownLarge: () => moveNodes(0, (state.gridSpacing || 20)),
|
||||
moveLeftLarge: () => moveNodes(-(state.gridSpacing || 20), 0),
|
||||
moveRightLarge: () => moveNodes((state.gridSpacing || 20), 0)
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
export const ioHandlers = {
|
||||
exportJson: () => {
|
||||
const exportBtn = document.getElementById('export-json');
|
||||
if (exportBtn) exportBtn.click();
|
||||
},
|
||||
importJson: () => {
|
||||
const importBtn = document.getElementById('import-json');
|
||||
if (importBtn) importBtn.click();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,34 @@
|
||||
import { state } from '../../state.js';
|
||||
|
||||
export const uiHandlers = {
|
||||
toggleInventory: () => {
|
||||
import('../../graph/analysis.js').then(m => m.toggleInventory());
|
||||
},
|
||||
toggleProperties: () => {
|
||||
import('../../properties_sidebar/index.js').then(m => m.toggleSidebar());
|
||||
},
|
||||
toggleLayers: () => {
|
||||
import('../../ui/layer_panel.js').then(m => m.toggleLayerPanel());
|
||||
},
|
||||
editLabel: () => {
|
||||
if (!state.graph) return;
|
||||
const selected = state.graph.getSelectedCells();
|
||||
if (selected.length === 0) return;
|
||||
|
||||
import('../../properties_sidebar/index.js').then(m => {
|
||||
m.toggleSidebar(true);
|
||||
setTimeout(() => {
|
||||
const labelInput = document.getElementById('prop-label');
|
||||
if (labelInput) {
|
||||
labelInput.focus();
|
||||
labelInput.select();
|
||||
}
|
||||
}, 50);
|
||||
});
|
||||
},
|
||||
cancel: () => {
|
||||
if (!state.graph) return;
|
||||
state.graph.cleanSelection();
|
||||
import('../../properties_sidebar/index.js').then(m => m.toggleSidebar(false));
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user