diff --git a/resources/js/Components/MetroMap/MetroCanvas.vue b/resources/js/Components/MetroMap/MetroCanvas.vue
index 2d4774d..913d9b0 100644
--- a/resources/js/Components/MetroMap/MetroCanvas.vue
+++ b/resources/js/Components/MetroMap/MetroCanvas.vue
@@ -772,7 +772,7 @@ const renderDimension = (dimData, opacity, parentGroup) => {
})
.attr('stroke-width', 2)
- // Child-dimension indicator
+ // Child-dimension indicator: populated dimension (has nodes inside)
nodeGroups.filter(d => d.children && (d.children.nodes?.length ?? 0) > 0)
.append('circle')
.attr('r', 16)
@@ -783,7 +783,6 @@ const renderDimension = (dimData, opacity, parentGroup) => {
.attr('opacity', 0.5)
.attr('filter', 'url(#childGlow)')
- // "[+]" label for nodes with children
nodeGroups.filter(d => d.children && (d.children.nodes?.length ?? 0) > 0)
.append('text')
.attr('x', 13)
@@ -794,6 +793,26 @@ const renderDimension = (dimData, opacity, parentGroup) => {
.attr('opacity', 0.8)
.text('[+]')
+ // Empty dimension indicator (dimension exists but no nodes yet)
+ nodeGroups.filter(d => d.children && (d.children.nodes?.length ?? 0) === 0)
+ .append('circle')
+ .attr('r', 14)
+ .attr('fill', 'none')
+ .attr('stroke', '#8892b0')
+ .attr('stroke-width', 0.8)
+ .attr('stroke-dasharray', '2,4')
+ .attr('opacity', 0.4)
+
+ nodeGroups.filter(d => d.children && (d.children.nodes?.length ?? 0) === 0)
+ .append('text')
+ .attr('x', 13)
+ .attr('y', -13)
+ .attr('fill', '#8892b0')
+ .attr('font-family', "'VT323', monospace")
+ .attr('font-size', '10px')
+ .attr('opacity', 0.5)
+ .text('[○]')
+
// Station label
nodeGroups.append('text')
.attr('x', 0)
@@ -880,22 +899,35 @@ const handleContextEdit = () => {
closeContextMenu()
}
-const handleContextAddChild = () => {
- if (contextMenu.value.node) {
- emit('create-node', {
- x: contextMenu.value.canvasX,
- y: contextMenu.value.canvasY,
- parentNodeId: contextMenu.value.node.id,
- parentEntityType: contextMenu.value.node.entityType ?? null,
- parentEntityId: contextMenu.value.node.entityId ?? null,
- dimensionId: contextMenu.value.node.id,
- depth: currentDepth.value + 1,
- addToNode: contextMenu.value.node,
- })
+/** "Add dimension" — attach an empty children object to a node, making it zoomable */
+const handleContextAddDimension = () => {
+ const node = contextMenu.value.node
+ if (!node) { closeContextMenu(); return }
+
+ // Create empty dimension structure on the node
+ node.children = {
+ lines: [],
+ nodes: [],
+ connections: [],
+ parentEntityType: node.entityType ?? null,
+ parentEntityId: node.entityId ?? null,
+ parentName: node.name ?? null,
}
+
+ // Re-render to show the new [○] indicator
+ renderMap()
closeContextMenu()
}
+/** "Open dimension" — zoom into a node that already has children */
+const handleContextZoomIn = () => {
+ const node = contextMenu.value.node
+ if (!node?.children) { closeContextMenu(); return }
+
+ closeContextMenu()
+ commitDimensionChange('in', node)
+}
+
const handleContextDelete = () => {
if (contextMenu.value.node) emit('delete-node', contextMenu.value.node)
closeContextMenu()
@@ -1055,9 +1087,16 @@ defineExpose({
+