Any node can become zoomable: add dimension via right-click
Visual distinction: - Populated dimension: dashed cyan ring + [+] indicator - Empty dimension: subtle gray dashed ring + [○] indicator - No dimension: plain station (no ring) Context menu changes: - "Open dimension" on nodes that have children (zooms in directly) - "Add dimension" on nodes without children (creates empty zoomable space) - Removed old "Add child node" option Add dimension creates an empty children structure on the node with proper parent metadata. The node immediately shows the [○] indicator and becomes zoomable. User enters and builds tracks with FAB + branch handles. Truly recursive metro — any station can contain a world. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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({
|
||||
<button
|
||||
v-if="contextMenu.node?.children"
|
||||
class="context-item"
|
||||
@click="handleContextAddChild"
|
||||
@click="handleContextZoomIn"
|
||||
>
|
||||
+ Add child node
|
||||
> Open dimension
|
||||
</button>
|
||||
<button
|
||||
v-if="!contextMenu.node?.children"
|
||||
class="context-item"
|
||||
@click="handleContextAddDimension"
|
||||
>
|
||||
+ Add dimension
|
||||
</button>
|
||||
<button class="context-item danger" @click="handleContextDelete">Delete</button>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user