@@ -340,6 +356,7 @@ if (!window.IAMCCS_ANNOTATE_LOADED) {
const dashedChk = section.querySelector('#iam_dashed');
const hidpiChk = section.querySelector('#iam_hidpi');
const hiddenChk = section.querySelector('#iam_hidden');
+ const penOnlyChk = section.querySelector('#iam_penonly');
const saveChk = section.querySelector('#iam_save_with_wf');
const saveNow = section.querySelector('#iam_save_now');
const loadNow = section.querySelector('#iam_load_now');
@@ -389,6 +406,9 @@ if (!window.IAMCCS_ANNOTATE_LOADED) {
hiddenChk.addEventListener('change', () => {
state.hidden = !!hiddenChk.checked;
app?.canvas?.setDirty(true, true);
+ });
+ penOnlyChk.addEventListener('change', () => {
+ state.penOnly = !!penOnlyChk.checked;
});
saveChk?.addEventListener('change', () => {
state.saveWithWorkflow = !!saveChk.checked;
@@ -445,7 +465,7 @@ if (!window.IAMCCS_ANNOTATE_LOADED) {
-
+
@@ -499,6 +523,7 @@ if (!window.IAMCCS_ANNOTATE_LOADED) {
const dashedChk = panel.querySelector('#iam_dashed');
const hidpiChk = panel.querySelector('#iam_hidpi');
const hiddenChk = panel.querySelector('#iam_hidden');
+ const penOnlyChk = panel.querySelector('#iam_penonly');
const saveChk = panel.querySelector('#iam_save_with_wf');
const saveNow = panel.querySelector('#iam_save_now');
const loadNow = panel.querySelector('#iam_load_now');
@@ -545,6 +570,9 @@ if (!window.IAMCCS_ANNOTATE_LOADED) {
state.hidden = !!hiddenChk.checked;
app?.canvas?.setDirty(true, true);
});
+ penOnlyChk.addEventListener('change', () => {
+ state.penOnly = !!penOnlyChk.checked;
+ });
saveChk?.addEventListener('change', () => {
state.saveWithWorkflow = !!saveChk.checked;
if (state.saveWithWorkflow) persistToGraphExtra(); else removeFromGraphExtra();
@@ -582,9 +610,78 @@ if (!window.IAMCCS_ANNOTATE_LOADED) {
return panel;
}
+ function ensureStateHydratedFromExisting() {
+ try {
+ if (state.hydrated) return;
+ const existing = app?.graph?.extra?.iamccs_annotations;
+ if (!existing) return;
+ // Build merged layers from existing
+ let mergedLayers = [];
+ if (Array.isArray(existing.layers) && existing.layers.length) {
+ mergedLayers = existing.layers.map((layer) => ({
+ name: layer.name || 'Layer',
+ visible: layer.visible !== false,
+ locked: !!layer.locked,
+ paths: Array.isArray(layer.paths) ? layer.paths.map(p => ({
+ color: p.color || '#ff4444',
+ width: p.width || 3,
+ opacity: (typeof p.opacity === 'number') ? p.opacity : 1,
+ mode: p.mode === 'erase' ? 'erase' : 'draw',
+ dashed: !!p.dashed,
+ points: Array.isArray(p.points) ? p.points.map(pt => ({ x: pt.x, y: pt.y })) : [],
+ })) : [],
+ style: (layer.style && typeof layer.style === 'object') ? {
+ color: typeof layer.style.color === 'string' ? layer.style.color : (state.color || '#ff4444'),
+ dashed: !!layer.style.dashed,
+ widthDraw: (typeof layer.style.widthDraw === 'number') ? layer.style.widthDraw : (state.widthDraw || 7),
+ widthErase: (typeof layer.style.widthErase === 'number') ? layer.style.widthErase : (state.widthErase || 48),
+ opacityDraw: (typeof layer.style.opacityDraw === 'number') ? layer.style.opacityDraw : (typeof state.opacityDraw === 'number' ? state.opacityDraw : 1.0),
+ opacityErase: (typeof layer.style.opacityErase === 'number') ? layer.style.opacityErase : (typeof state.opacityErase === 'number' ? state.opacityErase : 1.0),
+ } : {
+ color: state.color || '#ff4444', dashed: !!state.dashed,
+ widthDraw: state.widthDraw || 7, widthErase: state.widthErase || 48,
+ opacityDraw: (typeof state.opacityDraw === 'number' ? state.opacityDraw : 1.0),
+ opacityErase: (typeof state.opacityErase === 'number' ? state.opacityErase : 1.0),
+ },
+ }));
+ } else if (Array.isArray(existing.paths)) {
+ mergedLayers = [{ name: 'Layer 1', visible: true, locked: false, paths: existing.paths.map(p => ({
+ color: p.color || '#ff4444',
+ width: p.width || 3,
+ opacity: (typeof p.opacity === 'number') ? p.opacity : 1,
+ mode: p.mode === 'erase' ? 'erase' : 'draw',
+ dashed: !!p.dashed,
+ points: Array.isArray(p.points) ? p.points.map(pt => ({ x: pt.x, y: pt.y })) : [],
+ })), style: { color: state.color || '#ff4444', dashed: !!state.dashed, widthDraw: state.widthDraw || 7, widthErase: state.widthErase || 48, opacityDraw: (typeof state.opacityDraw === 'number' ? state.opacityDraw : 1.0), opacityErase: (typeof state.opacityErase === 'number' ? state.opacityErase : 1.0) } }];
+ }
+ // Overlay current, appending new paths and style tweaks
+ for (let i = 0; i < state.layers.length; i++) {
+ const cur = state.layers[i];
+ ensureLayerStyle(cur);
+ if (!mergedLayers[i]) {
+ mergedLayers[i] = { name: cur.name || `Layer ${i+1}`, visible: cur.visible !== false, locked: !!cur.locked, paths: [], style: { ...cur.style } };
+ }
+ if (Array.isArray(cur.paths) && cur.paths.length) mergedLayers[i].paths.push(...cur.paths);
+ // Update style with current layer's style as most recent
+ mergedLayers[i].style = { ...mergedLayers[i].style, ...cur.style };
+ }
+ state.layers = mergedLayers;
+ // Flatten paths for backward compatibility
+ state.paths = [];
+ for (const lyr of state.layers) for (const p of lyr.paths) state.paths.push(p);
+ // Clamp current layer index
+ if (state.currentLayerIdx >= state.layers.length) state.currentLayerIdx = Math.max(0, state.layers.length - 1);
+ state.hydrated = true;
+ } catch (e) {
+ console.warn('[IAMCCS] ensureStateHydratedFromExisting failed:', e);
+ }
+ }
+
function persistToGraphExtra(force = false) {
if (!app?.graph) return;
if (!state.saveWithWorkflow && !force) return;
+ // Merge existing annotations if we haven't hydrated yet (prevents overwriting)
+ ensureStateHydratedFromExisting();
app.graph.extra = app.graph.extra || {};
app.graph.extra.iamccs_annotations = {
version: 2,
@@ -624,6 +721,14 @@ if (!window.IAMCCS_ANNOTATE_LOADED) {
dashed: !!p.dashed,
points: Array.isArray(p.points) ? p.points.map(pt => ({ x: pt.x, y: pt.y })) : [],
})) : [],
+ style: (layer.style && typeof layer.style === 'object') ? {
+ color: typeof layer.style.color === 'string' ? layer.style.color : (state.color || '#ff4444'),
+ dashed: !!layer.style.dashed,
+ widthDraw: (typeof layer.style.widthDraw === 'number') ? layer.style.widthDraw : (state.widthDraw || 7),
+ widthErase: (typeof layer.style.widthErase === 'number') ? layer.style.widthErase : (state.widthErase || 48),
+ opacityDraw: (typeof layer.style.opacityDraw === 'number') ? layer.style.opacityDraw : (typeof state.opacityDraw === 'number' ? state.opacityDraw : 1.0),
+ opacityErase: (typeof layer.style.opacityErase === 'number') ? layer.style.opacityErase : (typeof state.opacityErase === 'number' ? state.opacityErase : 1.0),
+ } : { color: state.color || '#ff4444', dashed: !!state.dashed, widthDraw: state.widthDraw || 7, widthErase: state.widthErase || 48, opacityDraw: (typeof state.opacityDraw === 'number' ? state.opacityDraw : 1.0), opacityErase: (typeof state.opacityErase === 'number' ? state.opacityErase : 1.0) },
}));
state.currentLayerIdx = typeof data.currentLayerIdx === 'number' ? data.currentLayerIdx : 0;
} else if (Array.isArray(data.paths)) {
@@ -637,13 +742,23 @@ if (!window.IAMCCS_ANNOTATE_LOADED) {
points: Array.isArray(p.points) ? p.points.map(pt => ({ x: pt.x, y: pt.y })) : [],
}));
// Also populate first layer
- if (state.layers[0]) state.layers[0].paths = [...state.paths];
+ if (state.layers[0]) {
+ ensureLayerStyle(state.layers[0]);
+ state.layers[0].paths = [...state.paths];
+ } else {
+ state.layers = [{ name: 'Layer 1', visible: true, locked: false, paths: [...state.paths], style: { color: state.color || '#ff4444', dashed: !!state.dashed, widthDraw: state.widthDraw || 7, widthErase: state.widthErase || 48, opacityDraw: (typeof state.opacityDraw === 'number' ? state.opacityDraw : 1.0), opacityErase: (typeof state.opacityErase === 'number' ? state.opacityErase : 1.0) } }];
+ }
} else {
return false;
}
-
+ // Refresh flat paths from layers to keep v1 compatibility
+ state.paths = [];
+ for (const lyr of state.layers) for (const p of lyr.paths) state.paths.push(p);
+ state.hydrated = true;
app?.canvas?.setDirty(true, true);
console.log('[IAMCCS] Annotazioni caricate da workflow.extra');
+ // Apply style of selected layer to current brush
+ try { applyLayerStyleToState(); } catch {}
return true;
}
@@ -688,6 +803,11 @@ if (!window.IAMCCS_ANNOTATE_LOADED) {
points: Array.isArray(p.points) ? p.points.map(pt => ({ x: pt.x, y: pt.y })) : [],
}));
app?.canvas?.setDirty(true, true);
+ // Put into first layer for compatibility
+ if (!state.layers[0]) state.layers[0] = { name: 'Layer 1', visible: true, locked: false, paths: [], style: { color: state.color || '#ff4444', dashed: !!state.dashed, widthDraw: state.widthDraw || 7, widthErase: state.widthErase || 48, opacityDraw: (typeof state.opacityDraw === 'number' ? state.opacityDraw : 1.0), opacityErase: (typeof state.opacityErase === 'number' ? state.opacityErase : 1.0) } };
+ ensureLayerStyle(state.layers[0]);
+ state.layers[0].paths = [...state.paths];
+ state.hydrated = true;
if (state.saveWithWorkflow) persistToGraphExtra(true);
console.log('[IAMCCS] Annotations imported:', state.paths.length);
} catch (err) {
@@ -730,6 +850,7 @@ if (!window.IAMCCS_ANNOTATE_LOADED) {
// Put into the first layer for compatibility
if (state.layers[0]) state.layers[0].paths = [...state.paths];
app?.canvas?.setDirty(true, true);
+ state.hydrated = true;
if (state.saveWithWorkflow) persistToGraphExtra(true);
console.log('[IAMCCS] Annotations imported (from object):', state.paths.length);
return true;
@@ -781,20 +902,64 @@ if (!window.IAMCCS_ANNOTATE_LOADED) {
state.paths = [];
state.current = null;
state.currentLayerIdx = 0;
- state.layers = [{ name: 'Layer 1', visible: true, locked: false, paths: [] }];
+ state.layers = [{ name: 'Layer 1', visible: true, locked: false, paths: [], style: { color: state.color || '#ff4444', dashed: !!state.dashed, widthDraw: state.widthDraw || 7, widthErase: state.widthErase || 48, opacityDraw: (typeof state.opacityDraw === 'number' ? state.opacityDraw : 1.0), opacityErase: (typeof state.opacityErase === 'number' ? state.opacityErase : 1.0) } }];
app?.canvas?.setDirty(true, true);
}
}
};
} catch {}
- // Layer management functions
+ // Layer management + per-layer style
+ function ensureLayerStyle(layer) {
+ if (!layer) return;
+ layer.style = layer.style || {};
+ if (typeof layer.style.color !== 'string') layer.style.color = state.color || '#ff4444';
+ if (typeof layer.style.dashed !== 'boolean') layer.style.dashed = !!state.dashed;
+ if (typeof layer.style.widthDraw !== 'number') layer.style.widthDraw = state.widthDraw || 7;
+ if (typeof layer.style.widthErase !== 'number') layer.style.widthErase = state.widthErase || 48;
+ if (typeof layer.style.opacityDraw !== 'number') layer.style.opacityDraw = (typeof state.opacityDraw === 'number' ? state.opacityDraw : 1.0);
+ if (typeof layer.style.opacityErase !== 'number') layer.style.opacityErase = (typeof state.opacityErase === 'number' ? state.opacityErase : 1.0);
+ }
function getCurrentLayer() {
- return state.layers[state.currentLayerIdx];
+ const lyr = state.layers[state.currentLayerIdx];
+ ensureLayerStyle(lyr);
+ return lyr;
+ }
+ function getCurrentLayerStyle() {
+ const lyr = getCurrentLayer();
+ return lyr?.style || {};
+ }
+ function applyLayerStyleToState() {
+ const style = getCurrentLayerStyle();
+ state.color = style.color;
+ state.dashed = !!style.dashed;
+ if (state.eraser) {
+ state.width = style.widthErase;
+ state.opacity = style.opacityErase;
+ } else {
+ state.width = style.widthDraw;
+ state.opacity = style.opacityDraw;
+ }
+ state.widthDraw = style.widthDraw;
+ state.widthErase = style.widthErase;
+ state.opacityDraw = style.opacityDraw;
+ state.opacityErase = style.opacityErase;
+ syncFlagsUI();
+ syncBrushOpacityUI();
+ syncUI();
+ app?.canvas?.setDirty(true, true);
}
function addLayer() {
const layerNum = state.layers.length + 1;
- state.layers.push({ name: `Layer ${layerNum}`, visible: true, locked: false, paths: [] });
+ const base = { ...getCurrentLayerStyle() };
+ state.layers.push({ name: `Layer ${layerNum}`, visible: true, locked: false, paths: [], style: {
+ color: typeof base.color === 'string' ? base.color : (state.color || '#ff4444'),
+ dashed: !!base.dashed,
+ widthDraw: typeof base.widthDraw === 'number' ? base.widthDraw : (state.widthDraw || 7),
+ widthErase: typeof base.widthErase === 'number' ? base.widthErase : (state.widthErase || 48),
+ opacityDraw: typeof base.opacityDraw === 'number' ? base.opacityDraw : (typeof state.opacityDraw === 'number' ? state.opacityDraw : 1.0),
+ opacityErase: typeof base.opacityErase === 'number' ? base.opacityErase : (typeof state.opacityErase === 'number' ? state.opacityErase : 1.0),
+ } });
state.currentLayerIdx = state.layers.length - 1;
app?.canvas?.setDirty(true, true);
return state.layers.length - 1;
@@ -816,7 +981,7 @@ if (!window.IAMCCS_ANNOTATE_LOADED) {
function setCurrentLayer(idx) {
if (idx >= 0 && idx < state.layers.length) {
state.currentLayerIdx = idx;
- app?.canvas?.setDirty(true, true);
+ applyLayerStyleToState();
}
}
@@ -879,15 +1044,17 @@ if (!window.IAMCCS_ANNOTATE_LOADED) {
setEnabled(!state.enabled);
}
}, true);
- // Right-click toggles context menu
+ // Right-click toggles context menu (and always closes if currently open)
btn.addEventListener('contextmenu', (e) => {
e.preventDefault();
- if (ui.contextMenu && ui.contextAnchor && ui.contextAnchor.anchor === btn) {
+ // If any menu is open, close it and stop propagation so global right-click closer doesn't interfere
+ if (ui.contextMenu) {
+ e.stopPropagation();
closeContextMenu('toggle-btn');
- } else {
- showContextMenu(e.clientX, e.clientY, btn);
+ return;
}
- });
+ showContextMenu(e.clientX, e.clientY, btn);
+ }, true);
document.body.appendChild(btn);
ui.floating = btn;
// Make draggable (whole button) and move menu with it if open
@@ -981,6 +1148,10 @@ if (!window.IAMCCS_ANNOTATE_LOADED) {
Hide notes
+