diff --git a/svelte-app/src/lib/canvas/interactions.ts b/svelte-app/src/lib/canvas/interactions.ts index a926ba5..d61fd61 100644 --- a/svelte-app/src/lib/canvas/interactions.ts +++ b/svelte-app/src/lib/canvas/interactions.ts @@ -99,6 +99,14 @@ function hitTest(cx: number, cy: number): number | null { return hitTestSymbols(cx, cy, layout.symbols); } +/** Check if a device type (symbolDef) is compatible with a placed symbol for label assignment. + * Matches by SVG file so base and _v variants are treated as the same type. */ +function isTypeCompatible(symbolDef: (typeof SYMBOLS)[number], placedSymbolId: string): boolean { + const placedDef = SYMBOLS.find(s => s.id === placedSymbolId); + if (!placedDef) return false; + return symbolDef.file === placedDef.file; +} + function onCanvasMousedown(e: MouseEvent) { if (e.button !== 0) return; e.preventDefault(); @@ -471,12 +479,14 @@ function onMousemove(e: MouseEvent) { const sym = dragState.symbolDef; dragState.ghost.style.left = (e.clientX - sym.w / 2) + 'px'; dragState.ghost.style.top = (e.clientY - sym.h / 2) + 'px'; - // Show drop-target highlight when dragging a device over an existing symbol + // Show drop-target highlight when dragging a device over a compatible symbol if (dragState.deviceLabel) { const pos = screenToCanvas(e.clientX, e.clientY); const hoverId = hitTest(pos.x, pos.y); - if (layout.labelDropTarget !== hoverId) { - layout.labelDropTarget = hoverId; + const hoverSym = hoverId !== null ? layout.symbols.find(s => s.id === hoverId) : null; + const compatible = hoverSym && isTypeCompatible(sym, hoverSym.symbolId) ? hoverId : null; + if (layout.labelDropTarget !== compatible) { + layout.labelDropTarget = compatible; layout.markDirty(); } } @@ -646,16 +656,14 @@ function onMouseup(e: MouseEvent) { const rot = sym.defaultRotation || 0; const pos = screenToCanvas(e.clientX, e.clientY); - // If dropped onto an existing symbol and we have a device label, assign the label + // If dropped onto a compatible existing symbol and we have a device label, assign the label const hitId = dragState.deviceLabel ? hitTest(pos.x, pos.y) : null; - if (hitId !== null && dragState.deviceLabel) { - const target = layout.symbols.find(s => s.id === hitId); - if (target) { - layout.pushUndo(); - target.label = dragState.deviceLabel; - layout.markDirty(); - layout.saveMcmState(); - } + const target = hitId !== null ? layout.symbols.find(s => s.id === hitId) : null; + if (target && dragState.deviceLabel && isTypeCompatible(sym, target.symbolId)) { + layout.pushUndo(); + target.label = dragState.deviceLabel; + layout.markDirty(); + layout.saveMcmState(); } else { // Drop onto empty space: place a new symbol let dropX = pos.x - sym.w / 2; diff --git a/svelte-app/static/projectes/devices-manifest.json b/svelte-app/static/projectes/devices-manifest.json index 5bc98df..a111513 100644 --- a/svelte-app/static/projectes/devices-manifest.json +++ b/svelte-app/static/projectes/devices-manifest.json @@ -1,5 +1,10 @@ { "MCM01": [ + { + "id": "MCM01", + "svg": "mcm", + "zone": "MCM01" + }, { "id": "PDP01", "svg": "pdp", @@ -807,6 +812,11 @@ } ], "MCM02": [ + { + "id": "MCM02", + "svg": "mcm", + "zone": "MCM02" + }, { "id": "PDP02", "svg": "pdp", @@ -2404,6 +2414,11 @@ } ], "MCM03": [ + { + "id": "MCM03", + "svg": "mcm", + "zone": "MCM03" + }, { "id": "PDP03", "svg": "pdp", @@ -4001,6 +4016,11 @@ } ], "MCM04": [ + { + "id": "MCM04", + "svg": "mcm", + "zone": "MCM04" + }, { "id": "PDP06", "svg": "pdp", @@ -5648,6 +5668,11 @@ } ], "MCM05": [ + { + "id": "MCM05", + "svg": "mcm", + "zone": "MCM05" + }, { "id": "PDP09", "svg": "pdp", @@ -7230,6 +7255,11 @@ } ], "MCM06": [ + { + "id": "MCM06", + "svg": "mcm", + "zone": "MCM06" + }, { "id": "PDP10", "svg": "pdp", @@ -8607,6 +8637,11 @@ } ], "MCM07": [ + { + "id": "MCM07", + "svg": "mcm", + "zone": "MCM07" + }, { "id": "PDP11", "svg": "pdp", @@ -9404,6 +9439,11 @@ } ], "MCM08": [ + { + "id": "MCM08", + "svg": "mcm", + "zone": "MCM08" + }, { "id": "PDP15", "svg": "pdp", @@ -10786,6 +10826,11 @@ } ], "MCM09": [ + { + "id": "MCM09", + "svg": "mcm", + "zone": "MCM09" + }, { "id": "NCP1_1_BCN1", "svg": "beacon", @@ -11473,6 +11518,11 @@ } ], "MCM10": [ + { + "id": "MCM10", + "svg": "mcm", + "zone": "MCM10" + }, { "id": "NCP2_1_BCN1", "svg": "beacon", @@ -12195,6 +12245,11 @@ } ], "MCM11": [ + { + "id": "MCM11", + "svg": "mcm", + "zone": "MCM11" + }, { "id": "NCP1_10_BCN1", "svg": "beacon", @@ -16052,6 +16107,11 @@ } ], "MCM12": [ + { + "id": "MCM12", + "svg": "mcm", + "zone": "MCM12" + }, { "id": "NCP2_11_TPE1", "svg": "photoeye", @@ -19994,6 +20054,11 @@ } ], "MCM13": [ + { + "id": "MCM13", + "svg": "mcm", + "zone": "MCM13" + }, { "id": "PDP01", "svg": "pdp", @@ -24496,6 +24561,11 @@ "svg": "conveyor", "zone": "BYDC_9" }, + { + "id": "MCM14", + "svg": "mcm", + "zone": "MCM14" + }, { "id": "PDP17", "svg": "pdp", @@ -26303,6 +26373,11 @@ "svg": "conveyor", "zone": "BYDC_20" }, + { + "id": "MCM15", + "svg": "mcm", + "zone": "MCM15" + }, { "id": "PDP19", "svg": "pdp", @@ -26655,6 +26730,11 @@ "svg": "jam_reset", "zone": "JP1102_1C" }, + { + "id": "MCM16", + "svg": "mcm", + "zone": "MCM16" + }, { "id": "S011011_BCN1", "svg": "beacon", @@ -28382,6 +28462,11 @@ "svg": "jam_reset", "zone": "JP3103_1C" }, + { + "id": "MCM17", + "svg": "mcm", + "zone": "MCM17" + }, { "id": "S013041_BCN1", "svg": "beacon", @@ -30334,6 +30419,11 @@ "svg": "jam_reset", "zone": "JP2098_1C" }, + { + "id": "MCM18", + "svg": "mcm", + "zone": "MCM18" + }, { "id": "S012011_BCN1", "svg": "beacon", @@ -32396,6 +32486,11 @@ "svg": "jam_reset", "zone": "JP4108_1C" }, + { + "id": "MCM19", + "svg": "mcm", + "zone": "MCM19" + }, { "id": "S014015_BCN1", "svg": "beacon", diff --git a/svelte-app/vite.config.ts b/svelte-app/vite.config.ts index defa8ab..1cc3543 100644 --- a/svelte-app/vite.config.ts +++ b/svelte-app/vite.config.ts @@ -216,6 +216,11 @@ function generateDeviceManifest() { } } + // Always include the MCM symbol itself + if (!devices.some(d => d.id === mcm)) { + devices.push({ id: mcm, svg: 'mcm', zone: mcm }); + } + // Sort by zone then id devices.sort((a, b) => a.zone.localeCompare(b.zone) || a.id.localeCompare(b.id)); manifest[mcm] = devices;