fix(canvas): keep container host height when editing an unrelated field
Editing any field on a container-mode vm/lxc/docker_host node (e.g. just renaming it or adding a property) reset its manually-set height to undefined, snapping the container back to auto-fit content size and scrambling/overflowing its nested children. Root cause: updateNode clears height on every 'properties' change and the node modal always includes properties in its payload. Only proxmox was excluded from the reset; vm/lxc/docker_host in container mode were not. - export CONTAINER_MODE_TYPES from virtualEdgeParent (reuse, no re-declare) - updateNode: skip the height reset for any container-mode host type; proxmox stays always-excluded (legacy), plain leaf nodes still auto-fit - regression tests: manual height preserved for container-mode vm/lxc/docker_host and proxmox; still reset for a non-container vm Fixes #278 ha-relevant: yes
This commit is contained in:
@@ -118,4 +118,53 @@ describe('canvasStore — sizing & z-order', () => {
|
||||
useCanvasStore.getState().updateNode('r1', { properties: [] })
|
||||
expect(useCanvasStore.getState().nodes.find((n) => n.id === 'r1')?.height).toBe(240)
|
||||
})
|
||||
|
||||
// Regression (#278): editing any field on a container-mode vm/lxc/docker_host host used to
|
||||
// reset its manually-set height to undefined, snapping it back to auto-fit size and
|
||||
// scrambling nested children. Only proxmox was excluded. Keep the manual height for every
|
||||
// container-mode host type.
|
||||
it.each(['vm', 'lxc', 'docker_host'] as const)(
|
||||
'updateNode preserves manual height for a container-mode %s when properties change',
|
||||
(type) => {
|
||||
const host: Node<NodeData> = {
|
||||
id: 'h1',
|
||||
type,
|
||||
position: { x: 0, y: 0 },
|
||||
width: 400,
|
||||
height: 300,
|
||||
data: { label: type, type, status: 'unknown', services: [], container_mode: true },
|
||||
}
|
||||
useCanvasStore.setState({ nodes: [host], edges: [] })
|
||||
useCanvasStore.getState().updateNode('h1', { properties: [], label: 'renamed' })
|
||||
expect(useCanvasStore.getState().nodes.find((n) => n.id === 'h1')?.height).toBe(300)
|
||||
}
|
||||
)
|
||||
|
||||
it('updateNode still resets height for a vm NOT in container mode when properties change', () => {
|
||||
const leaf: Node<NodeData> = {
|
||||
id: 'v1',
|
||||
type: 'vm',
|
||||
position: { x: 0, y: 0 },
|
||||
width: 200,
|
||||
height: 120,
|
||||
data: { label: 'vm', type: 'vm', status: 'unknown', services: [], container_mode: false },
|
||||
}
|
||||
useCanvasStore.setState({ nodes: [leaf], edges: [] })
|
||||
useCanvasStore.getState().updateNode('v1', { properties: [] })
|
||||
expect(useCanvasStore.getState().nodes.find((n) => n.id === 'v1')?.height).toBeUndefined()
|
||||
})
|
||||
|
||||
it('updateNode preserves height for a proxmox host when properties change', () => {
|
||||
const px: Node<NodeData> = {
|
||||
id: 'px1',
|
||||
type: 'proxmox',
|
||||
position: { x: 0, y: 0 },
|
||||
width: 500,
|
||||
height: 350,
|
||||
data: { label: 'px', type: 'proxmox', status: 'unknown', services: [], container_mode: true },
|
||||
}
|
||||
useCanvasStore.setState({ nodes: [px], edges: [] })
|
||||
useCanvasStore.getState().updateNode('px1', { properties: [] })
|
||||
expect(useCanvasStore.getState().nodes.find((n) => n.id === 'px1')?.height).toBe(350)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -13,6 +13,7 @@ import { generateUUID } from '@/utils/uuid'
|
||||
import { normalizeHandle, removedHandleIds, handleCountField, sideDefault, handleId, SIDES } from '@/utils/handleUtils'
|
||||
import { applyOpacity } from '@/utils/colorUtils'
|
||||
import { readHideIp, writeHideIp } from '@/utils/ipDisplay'
|
||||
import { CONTAINER_MODE_TYPES } from '@/utils/virtualEdgeParent'
|
||||
|
||||
type HistoryEntry = { nodes: Node<NodeData>[]; edges: Edge<EdgeData>[] }
|
||||
type Clipboard = { nodes: Node<NodeData>[]; edges: Edge<EdgeData>[] }
|
||||
@@ -325,8 +326,13 @@ export const useCanvasStore = create<CanvasState>((set) => ({
|
||||
let nodes = state.nodes.map((n) => {
|
||||
if (n.id !== id) return n
|
||||
const updated: Node<NodeData> = { ...n, data: { ...n.data, ...data } }
|
||||
// When properties change, clear stored height so the node auto-sizes to fit new content
|
||||
if ('properties' in data && n.data.type !== 'proxmox' && n.data.type !== 'groupRect' && n.data.type !== 'group') {
|
||||
// When properties change, clear stored height so the node auto-sizes to fit new content.
|
||||
// A container-mode host (vm/lxc/docker_host) keeps a manually-set height: resetting it
|
||||
// snaps the container back to auto-fit size and scrambles its nested children (#278).
|
||||
// proxmox is always excluded (legacy behavior); the other container types are excluded
|
||||
// only while actually in container mode.
|
||||
const isContainerHost = CONTAINER_MODE_TYPES.has(n.data.type) && !!n.data.container_mode
|
||||
if ('properties' in data && n.data.type !== 'proxmox' && n.data.type !== 'groupRect' && n.data.type !== 'group' && !isContainerHost) {
|
||||
updated.height = undefined
|
||||
}
|
||||
if ('parent_id' in data) {
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { NodeData } from '@/types'
|
||||
|
||||
export type NodeType = NodeData['type']
|
||||
|
||||
const CONTAINER_MODE_TYPES = new Set<NodeType>(['proxmox', 'vm', 'lxc', 'docker_host'])
|
||||
export const CONTAINER_MODE_TYPES = new Set<NodeType>(['proxmox', 'vm', 'lxc', 'docker_host'])
|
||||
const DOCKER_CONTAINER_PARENT_TYPES = new Set<NodeType>(['docker_host', 'lxc', 'vm', 'proxmox'])
|
||||
|
||||
export interface VirtualEdgeEndpoint {
|
||||
|
||||
Reference in New Issue
Block a user