figrecipe 0.7.4__py3-none-any.whl → 0.9.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- figrecipe/__init__.py +74 -76
- figrecipe/__main__.py +12 -0
- figrecipe/_api/_panel.py +67 -0
- figrecipe/_api/_save.py +100 -4
- figrecipe/_cli/__init__.py +7 -0
- figrecipe/_cli/_compose.py +87 -0
- figrecipe/_cli/_convert.py +117 -0
- figrecipe/_cli/_crop.py +82 -0
- figrecipe/_cli/_edit.py +70 -0
- figrecipe/_cli/_extract.py +128 -0
- figrecipe/_cli/_fonts.py +47 -0
- figrecipe/_cli/_info.py +67 -0
- figrecipe/_cli/_main.py +58 -0
- figrecipe/_cli/_reproduce.py +79 -0
- figrecipe/_cli/_style.py +77 -0
- figrecipe/_cli/_validate.py +66 -0
- figrecipe/_cli/_version.py +50 -0
- figrecipe/_composition/__init__.py +32 -0
- figrecipe/_composition/_alignment.py +452 -0
- figrecipe/_composition/_compose.py +179 -0
- figrecipe/_composition/_import_axes.py +127 -0
- figrecipe/_composition/_visibility.py +125 -0
- figrecipe/_dev/__init__.py +2 -0
- figrecipe/_dev/browser/__init__.py +69 -0
- figrecipe/_dev/browser/_audio.py +240 -0
- figrecipe/_dev/browser/_caption.py +356 -0
- figrecipe/_dev/browser/_click_effect.py +146 -0
- figrecipe/_dev/browser/_cursor.py +196 -0
- figrecipe/_dev/browser/_highlight.py +105 -0
- figrecipe/_dev/browser/_narration.py +237 -0
- figrecipe/_dev/browser/_recorder.py +446 -0
- figrecipe/_dev/browser/_utils.py +178 -0
- figrecipe/_dev/browser/_video_trim/__init__.py +152 -0
- figrecipe/_dev/browser/_video_trim/_detection.py +223 -0
- figrecipe/_dev/browser/_video_trim/_markers.py +140 -0
- figrecipe/_editor/__init__.py +36 -36
- figrecipe/_editor/_bbox/_extract.py +155 -9
- figrecipe/_editor/_bbox/_extract_text.py +124 -0
- figrecipe/_editor/_call_overrides.py +183 -0
- figrecipe/_editor/_datatable_plot_handlers.py +249 -0
- figrecipe/_editor/_figure_layout.py +211 -0
- figrecipe/_editor/_flask_app.py +157 -16
- figrecipe/_editor/_helpers.py +17 -8
- figrecipe/_editor/_hitmap/_detect.py +89 -32
- figrecipe/_editor/_hitmap_main.py +4 -4
- figrecipe/_editor/_overrides.py +4 -1
- figrecipe/_editor/_plot_types_registry.py +190 -0
- figrecipe/_editor/_render_overrides.py +38 -11
- figrecipe/_editor/_renderer.py +46 -1
- figrecipe/_editor/_routes_annotation.py +114 -0
- figrecipe/_editor/_routes_axis.py +35 -6
- figrecipe/_editor/_routes_captions.py +130 -0
- figrecipe/_editor/_routes_composition.py +270 -0
- figrecipe/_editor/_routes_core.py +15 -173
- figrecipe/_editor/_routes_datatable.py +364 -0
- figrecipe/_editor/_routes_element.py +37 -19
- figrecipe/_editor/_routes_files.py +443 -0
- figrecipe/_editor/_routes_image.py +200 -0
- figrecipe/_editor/_routes_snapshot.py +94 -0
- figrecipe/_editor/_routes_style.py +28 -8
- figrecipe/_editor/_templates/__init__.py +40 -2
- figrecipe/_editor/_templates/_html.py +97 -103
- figrecipe/_editor/_templates/_html_components/__init__.py +13 -0
- figrecipe/_editor/_templates/_html_components/_composition_toolbar.py +79 -0
- figrecipe/_editor/_templates/_html_components/_file_browser.py +41 -0
- figrecipe/_editor/_templates/_html_datatable.py +92 -0
- figrecipe/_editor/_templates/_scripts/__init__.py +58 -0
- figrecipe/_editor/_templates/_scripts/_accordion.py +328 -0
- figrecipe/_editor/_templates/_scripts/_annotation_drag.py +504 -0
- figrecipe/_editor/_templates/_scripts/_api.py +1 -1
- figrecipe/_editor/_templates/_scripts/_canvas_context_menu.py +182 -0
- figrecipe/_editor/_templates/_scripts/_captions.py +231 -0
- figrecipe/_editor/_templates/_scripts/_composition.py +283 -0
- figrecipe/_editor/_templates/_scripts/_core.py +94 -37
- figrecipe/_editor/_templates/_scripts/_datatable/__init__.py +59 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_cell_edit.py +97 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_clipboard.py +164 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_context_menu.py +221 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_core.py +150 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_editable.py +511 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_import.py +161 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_plot.py +261 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_selection.py +438 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_table.py +256 -0
- figrecipe/_editor/_templates/_scripts/_datatable/_tabs.py +354 -0
- figrecipe/_editor/_templates/_scripts/_element_editor.py +17 -2
- figrecipe/_editor/_templates/_scripts/_files.py +274 -40
- figrecipe/_editor/_templates/_scripts/_files_context_menu.py +240 -0
- figrecipe/_editor/_templates/_scripts/_hitmap.py +87 -84
- figrecipe/_editor/_templates/_scripts/_image_drop.py +428 -0
- figrecipe/_editor/_templates/_scripts/_legend_drag.py +5 -0
- figrecipe/_editor/_templates/_scripts/_multi_select.py +198 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag.py +219 -48
- figrecipe/_editor/_templates/_scripts/_panel_drag_snapshot.py +33 -0
- figrecipe/_editor/_templates/_scripts/_panel_position.py +238 -54
- figrecipe/_editor/_templates/_scripts/_panel_resize.py +230 -0
- figrecipe/_editor/_templates/_scripts/_panel_snap.py +307 -0
- figrecipe/_editor/_templates/_scripts/_region_select.py +255 -0
- figrecipe/_editor/_templates/_scripts/_selection.py +8 -1
- figrecipe/_editor/_templates/_scripts/_sync.py +242 -0
- figrecipe/_editor/_templates/_scripts/_undo_redo.py +348 -0
- figrecipe/_editor/_templates/_scripts/_zoom.py +52 -19
- figrecipe/_editor/_templates/_styles/__init__.py +9 -0
- figrecipe/_editor/_templates/_styles/_base.py +47 -0
- figrecipe/_editor/_templates/_styles/_buttons.py +127 -6
- figrecipe/_editor/_templates/_styles/_composition.py +87 -0
- figrecipe/_editor/_templates/_styles/_controls.py +168 -3
- figrecipe/_editor/_templates/_styles/_datatable/__init__.py +40 -0
- figrecipe/_editor/_templates/_styles/_datatable/_editable.py +203 -0
- figrecipe/_editor/_templates/_styles/_datatable/_panel.py +268 -0
- figrecipe/_editor/_templates/_styles/_datatable/_table.py +479 -0
- figrecipe/_editor/_templates/_styles/_datatable/_toolbar.py +384 -0
- figrecipe/_editor/_templates/_styles/_datatable/_vars.py +123 -0
- figrecipe/_editor/_templates/_styles/_dynamic_props.py +5 -5
- figrecipe/_editor/_templates/_styles/_file_browser.py +466 -0
- figrecipe/_editor/_templates/_styles/_forms.py +98 -0
- figrecipe/_editor/_templates/_styles/_hitmap.py +7 -0
- figrecipe/_editor/_templates/_styles/_modals.py +29 -0
- figrecipe/_editor/_templates/_styles/_overlays.py +5 -5
- figrecipe/_editor/_templates/_styles/_preview.py +213 -8
- figrecipe/_editor/_templates/_styles/_spinner.py +117 -0
- figrecipe/_editor/static/audio/click.mp3 +0 -0
- figrecipe/_editor/static/click.mp3 +0 -0
- figrecipe/_editor/static/icons/favicon.ico +0 -0
- figrecipe/_integrations/__init__.py +17 -0
- figrecipe/_integrations/_scitex_stats.py +298 -0
- figrecipe/_params/_DECORATION_METHODS.py +2 -0
- figrecipe/_recorder.py +28 -3
- figrecipe/_reproducer/_core.py +60 -49
- figrecipe/_utils/__init__.py +3 -0
- figrecipe/_utils/_bundle.py +205 -0
- figrecipe/_wrappers/_axes.py +150 -2
- figrecipe/_wrappers/_caption_generator.py +218 -0
- figrecipe/_wrappers/_figure.py +26 -1
- figrecipe/_wrappers/_stat_annotation.py +274 -0
- figrecipe/styles/_style_applier.py +10 -2
- figrecipe/styles/presets/SCITEX.yaml +11 -4
- {figrecipe-0.7.4.dist-info → figrecipe-0.9.0.dist-info}/METADATA +144 -146
- figrecipe-0.9.0.dist-info/RECORD +277 -0
- figrecipe-0.9.0.dist-info/entry_points.txt +2 -0
- figrecipe-0.7.4.dist-info/RECORD +0 -188
- {figrecipe-0.7.4.dist-info → figrecipe-0.9.0.dist-info}/WHEEL +0 -0
- {figrecipe-0.7.4.dist-info → figrecipe-0.9.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -18,6 +18,11 @@ let draggedPanelIndex = null;
|
|
|
18
18
|
let dragStartPos = null;
|
|
19
19
|
let dragStartPanelPos = null;
|
|
20
20
|
let panelDragOverlay = null;
|
|
21
|
+
let panelBboxDragOverlay = null; // Outer panel bbox overlay
|
|
22
|
+
let panelHoverOverlay = null;
|
|
23
|
+
let hoveredPanelIndex = null;
|
|
24
|
+
let dragStartPanelBbox = null; // Initial panel bbox in pixels
|
|
25
|
+
let panelBboxOffset = null; // Offset from axis to panel bbox (in mm)
|
|
21
26
|
|
|
22
27
|
// Initialize panel drag functionality
|
|
23
28
|
function initPanelDrag() {
|
|
@@ -34,48 +39,68 @@ function initPanelDrag() {
|
|
|
34
39
|
document.addEventListener('mouseup', handlePanelDragEnd);
|
|
35
40
|
console.log('[PanelDrag] Event listeners attached');
|
|
36
41
|
|
|
37
|
-
// Create drag overlay
|
|
42
|
+
// Create drag overlay for axis bbox (inner, subtle orange)
|
|
38
43
|
panelDragOverlay = document.createElement('div');
|
|
39
44
|
panelDragOverlay.id = 'panel-drag-overlay';
|
|
40
45
|
panelDragOverlay.style.cssText = `
|
|
46
|
+
position: absolute;
|
|
47
|
+
border: 2px dashed #f59e0b;
|
|
48
|
+
background: rgba(245, 158, 11, 0.08);
|
|
49
|
+
pointer-events: none;
|
|
50
|
+
display: none;
|
|
51
|
+
z-index: 999;
|
|
52
|
+
`;
|
|
53
|
+
zoomContainer.appendChild(panelDragOverlay);
|
|
54
|
+
|
|
55
|
+
// Create outer panel bbox overlay (prominent blue)
|
|
56
|
+
panelBboxDragOverlay = document.createElement('div');
|
|
57
|
+
panelBboxDragOverlay.id = 'panel-bbox-drag-overlay';
|
|
58
|
+
panelBboxDragOverlay.style.cssText = `
|
|
41
59
|
position: absolute;
|
|
42
60
|
border: 2px dashed #2563eb;
|
|
43
|
-
background: rgba(37, 99, 235, 0.
|
|
61
|
+
background: rgba(37, 99, 235, 0.05);
|
|
44
62
|
pointer-events: none;
|
|
45
63
|
display: none;
|
|
46
64
|
z-index: 1000;
|
|
47
65
|
`;
|
|
48
|
-
zoomContainer.appendChild(
|
|
49
|
-
|
|
66
|
+
zoomContainer.appendChild(panelBboxDragOverlay);
|
|
67
|
+
|
|
68
|
+
// Create hover overlay element for visual feedback
|
|
69
|
+
panelHoverOverlay = document.createElement('div');
|
|
70
|
+
panelHoverOverlay.id = 'panel-hover-overlay';
|
|
71
|
+
panelHoverOverlay.style.cssText = `
|
|
72
|
+
position: absolute;
|
|
73
|
+
border: 2px solid rgba(37, 99, 235, 0.5);
|
|
74
|
+
background: rgba(37, 99, 235, 0.05);
|
|
75
|
+
pointer-events: none;
|
|
76
|
+
display: none;
|
|
77
|
+
z-index: 999;
|
|
78
|
+
transition: opacity 0.15s ease-in-out;
|
|
79
|
+
`;
|
|
80
|
+
zoomContainer.appendChild(panelHoverOverlay);
|
|
81
|
+
|
|
82
|
+
// Add hover detection on zoom container
|
|
83
|
+
zoomContainer.addEventListener('mousemove', handlePanelHover);
|
|
84
|
+
zoomContainer.addEventListener('mouseleave', hidePanelHover);
|
|
85
|
+
|
|
86
|
+
console.log('[PanelDrag] Overlays created');
|
|
50
87
|
}
|
|
51
88
|
|
|
52
89
|
// Handle mouse down - check if on a panel/axes (only drag from empty panel area)
|
|
53
90
|
function handlePanelDragStart(event) {
|
|
54
|
-
|
|
55
|
-
// Skip if using modifier keys for other actions
|
|
56
|
-
if (event.ctrlKey || event.metaKey || event.altKey) {
|
|
57
|
-
console.log('[PanelDrag] Skipped - modifier key pressed');
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
91
|
+
if (event.ctrlKey || event.metaKey || event.altKey) return; // Skip modifier keys
|
|
60
92
|
|
|
61
|
-
// Only
|
|
62
|
-
// Skip if clicking on specific elements (they should be selected instead)
|
|
93
|
+
// Only allow drag from axes/imshow/contour/quadmesh/quiver (fills panel area)
|
|
63
94
|
const target = event.target;
|
|
64
95
|
const targetKey = target.getAttribute ? target.getAttribute('data-key') : null;
|
|
65
96
|
if (targetKey && typeof currentBboxes !== 'undefined' && currentBboxes[targetKey]) {
|
|
66
97
|
const elemType = currentBboxes[targetKey].type;
|
|
67
|
-
|
|
68
|
-
if (elemType && elemType
|
|
69
|
-
console.log('[PanelDrag] Skipped - clicked on element:', elemType);
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
98
|
+
const dragAllowedTypes = ['axes', 'image', 'contour', 'quadmesh', 'quiver'];
|
|
99
|
+
if (elemType && !dragAllowedTypes.includes(elemType)) return;
|
|
72
100
|
}
|
|
73
101
|
|
|
74
102
|
const img = document.getElementById('preview-image');
|
|
75
|
-
if (!img || !figSize.width_mm || !figSize.height_mm)
|
|
76
|
-
console.log('[PanelDrag] Skipped - img or figSize not ready');
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
103
|
+
if (!img || !figSize.width_mm || !figSize.height_mm) return;
|
|
79
104
|
|
|
80
105
|
const rect = img.getBoundingClientRect();
|
|
81
106
|
const x = event.clientX - rect.left;
|
|
@@ -93,15 +118,41 @@ function handlePanelDragStart(event) {
|
|
|
93
118
|
event.preventDefault();
|
|
94
119
|
event.stopPropagation();
|
|
95
120
|
|
|
121
|
+
// Capture state before drag for undo
|
|
122
|
+
if (typeof pushToHistory === 'function') {
|
|
123
|
+
pushToHistory();
|
|
124
|
+
}
|
|
125
|
+
|
|
96
126
|
isDraggingPanel = true;
|
|
97
127
|
draggedPanelIndex = panelIndex;
|
|
98
128
|
dragStartPos = { x: event.clientX, y: event.clientY };
|
|
99
129
|
|
|
130
|
+
// Hide hover overlay when starting drag
|
|
131
|
+
hidePanelHover();
|
|
132
|
+
|
|
100
133
|
// Get current panel position (in mm)
|
|
101
134
|
const axKey = Object.keys(panelPositions).sort()[panelIndex];
|
|
102
135
|
const pos = panelPositions[axKey];
|
|
103
136
|
dragStartPanelPos = { ...pos };
|
|
104
137
|
|
|
138
|
+
// Get panel bbox (outer bounds including labels) and calculate offset from axis
|
|
139
|
+
const panelBboxes = currentBboxes?._meta?.panel_bboxes;
|
|
140
|
+
if (panelBboxes && panelBboxes[panelIndex] && img.naturalWidth) {
|
|
141
|
+
dragStartPanelBbox = { ...panelBboxes[panelIndex] };
|
|
142
|
+
// Convert panel bbox to mm and calculate offset from axis position
|
|
143
|
+
const pxToMmX = figSize.width_mm / img.naturalWidth;
|
|
144
|
+
const pxToMmY = figSize.height_mm / img.naturalHeight;
|
|
145
|
+
panelBboxOffset = {
|
|
146
|
+
left: dragStartPanelBbox.x * pxToMmX - pos.left,
|
|
147
|
+
top: dragStartPanelBbox.y * pxToMmY - pos.top,
|
|
148
|
+
width: dragStartPanelBbox.width * pxToMmX,
|
|
149
|
+
height: dragStartPanelBbox.height * pxToMmY
|
|
150
|
+
};
|
|
151
|
+
} else {
|
|
152
|
+
dragStartPanelBbox = null;
|
|
153
|
+
panelBboxOffset = null;
|
|
154
|
+
}
|
|
155
|
+
|
|
105
156
|
// Create overlay if it doesn't exist
|
|
106
157
|
if (!panelDragOverlay) {
|
|
107
158
|
console.log('[PanelDrag] Creating overlay on-demand');
|
|
@@ -122,15 +173,27 @@ function handlePanelDragStart(event) {
|
|
|
122
173
|
}
|
|
123
174
|
}
|
|
124
175
|
|
|
125
|
-
// Show drag overlay
|
|
176
|
+
// Show drag overlay (axis bbox)
|
|
126
177
|
if (panelDragOverlay) {
|
|
127
178
|
updateDragOverlayMm(pos, rect);
|
|
128
179
|
panelDragOverlay.style.display = 'block';
|
|
129
|
-
console.log('[PanelDrag]
|
|
180
|
+
console.log('[PanelDrag] Axis overlay shown');
|
|
130
181
|
} else {
|
|
131
182
|
console.warn('[PanelDrag] Overlay still null after creation attempt');
|
|
132
183
|
}
|
|
133
184
|
|
|
185
|
+
// Show panel bbox overlay (outer bounds) - follows snapped axis position
|
|
186
|
+
if (panelBboxDragOverlay && panelBboxOffset) {
|
|
187
|
+
updatePanelBboxDragOverlayMm(pos, rect);
|
|
188
|
+
panelBboxDragOverlay.style.display = 'block';
|
|
189
|
+
console.log('[PanelDrag] Panel bbox overlay shown');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Create and show panel snapshot for visual feedback
|
|
193
|
+
if (typeof startSnapshotDrag === 'function') {
|
|
194
|
+
startSnapshotDrag(panelIndex, rect, pos);
|
|
195
|
+
}
|
|
196
|
+
|
|
134
197
|
// Change cursor
|
|
135
198
|
document.body.style.cursor = 'move';
|
|
136
199
|
|
|
@@ -167,6 +230,84 @@ function findPanelAtPositionMm(mmX, mmY) {
|
|
|
167
230
|
return null;
|
|
168
231
|
}
|
|
169
232
|
|
|
233
|
+
// Handle mouse hover over panels - show visual feedback
|
|
234
|
+
function handlePanelHover(event) {
|
|
235
|
+
// Skip if dragging
|
|
236
|
+
if (isDraggingPanel) {
|
|
237
|
+
hidePanelHover();
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const img = document.getElementById('preview-image');
|
|
242
|
+
if (!img || !figSize.width_mm || !figSize.height_mm) return;
|
|
243
|
+
|
|
244
|
+
const rect = img.getBoundingClientRect();
|
|
245
|
+
const x = event.clientX - rect.left;
|
|
246
|
+
const y = event.clientY - rect.top;
|
|
247
|
+
|
|
248
|
+
// Check if mouse is within image bounds
|
|
249
|
+
if (x < 0 || x > rect.width || y < 0 || y > rect.height) {
|
|
250
|
+
hidePanelHover();
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Convert to mm coordinates
|
|
255
|
+
const mmX = (x / rect.width) * figSize.width_mm;
|
|
256
|
+
const mmY = (y / rect.height) * figSize.height_mm;
|
|
257
|
+
|
|
258
|
+
// Find panel at position
|
|
259
|
+
const panelIndex = findPanelAtPositionMm(mmX, mmY);
|
|
260
|
+
|
|
261
|
+
if (panelIndex !== null && panelIndex !== hoveredPanelIndex) {
|
|
262
|
+
showPanelHover(panelIndex, rect);
|
|
263
|
+
} else if (panelIndex === null) {
|
|
264
|
+
hidePanelHover();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// Show hover feedback for a panel
|
|
269
|
+
function showPanelHover(panelIndex, imgRect) {
|
|
270
|
+
if (!panelHoverOverlay) return;
|
|
271
|
+
|
|
272
|
+
hoveredPanelIndex = panelIndex;
|
|
273
|
+
|
|
274
|
+
// Get panel position
|
|
275
|
+
const axKey = Object.keys(panelPositions).sort()[panelIndex];
|
|
276
|
+
const pos = panelPositions[axKey];
|
|
277
|
+
if (!pos) return;
|
|
278
|
+
|
|
279
|
+
// Convert mm to screen pixels
|
|
280
|
+
const scaleX = imgRect.width / figSize.width_mm;
|
|
281
|
+
const scaleY = imgRect.height / figSize.height_mm;
|
|
282
|
+
|
|
283
|
+
const left = pos.left * scaleX;
|
|
284
|
+
const top = pos.top * scaleY;
|
|
285
|
+
const width = pos.width * scaleX;
|
|
286
|
+
const height = pos.height * scaleY;
|
|
287
|
+
|
|
288
|
+
panelHoverOverlay.style.left = `${left}px`;
|
|
289
|
+
panelHoverOverlay.style.top = `${top}px`;
|
|
290
|
+
panelHoverOverlay.style.width = `${width}px`;
|
|
291
|
+
panelHoverOverlay.style.height = `${height}px`;
|
|
292
|
+
panelHoverOverlay.style.display = 'block';
|
|
293
|
+
|
|
294
|
+
// Change cursor to indicate draggable
|
|
295
|
+
document.body.style.cursor = 'move';
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Hide hover feedback
|
|
299
|
+
function hidePanelHover() {
|
|
300
|
+
if (panelHoverOverlay) {
|
|
301
|
+
panelHoverOverlay.style.display = 'none';
|
|
302
|
+
}
|
|
303
|
+
hoveredPanelIndex = null;
|
|
304
|
+
|
|
305
|
+
// Reset cursor if not dragging
|
|
306
|
+
if (!isDraggingPanel) {
|
|
307
|
+
document.body.style.cursor = '';
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
170
311
|
// Handle mouse move during drag
|
|
171
312
|
function handlePanelDragMove(event) {
|
|
172
313
|
if (!isDraggingPanel) return;
|
|
@@ -182,9 +323,17 @@ function handlePanelDragMove(event) {
|
|
|
182
323
|
const deltaMmX = (event.clientX - dragStartPos.x) / rect.width * figSize.width_mm;
|
|
183
324
|
const deltaMmY = (event.clientY - dragStartPos.y) / rect.height * figSize.height_mm;
|
|
184
325
|
|
|
185
|
-
// Calculate new position (clamped to figure bounds)
|
|
186
|
-
|
|
187
|
-
|
|
326
|
+
// Calculate raw new position (clamped to figure bounds)
|
|
327
|
+
let newLeft = Math.max(0, Math.min(figSize.width_mm - dragStartPanelPos.width, dragStartPanelPos.left + deltaMmX));
|
|
328
|
+
let newTop = Math.max(0, Math.min(figSize.height_mm - dragStartPanelPos.height, dragStartPanelPos.top + deltaMmY));
|
|
329
|
+
|
|
330
|
+
// Apply snapping (Alt key disables snapping for fine control)
|
|
331
|
+
let snapResult = { pos: { left: newLeft, top: newTop }, guides: [] };
|
|
332
|
+
if (typeof applySnapping === 'function' && !event.altKey) {
|
|
333
|
+
snapResult = applySnapping(newLeft, newTop, dragStartPanelPos.width, dragStartPanelPos.height, draggedPanelIndex);
|
|
334
|
+
newLeft = snapResult.pos.left;
|
|
335
|
+
newTop = snapResult.pos.top;
|
|
336
|
+
}
|
|
188
337
|
|
|
189
338
|
const newPos = {
|
|
190
339
|
left: newLeft,
|
|
@@ -193,27 +342,40 @@ function handlePanelDragMove(event) {
|
|
|
193
342
|
height: dragStartPanelPos.height
|
|
194
343
|
};
|
|
195
344
|
|
|
196
|
-
// Update visual
|
|
345
|
+
// Update visual overlays - both use snapped position in mm
|
|
197
346
|
updateDragOverlayMm(newPos, rect);
|
|
347
|
+
updatePanelBboxDragOverlayMm(newPos, rect);
|
|
348
|
+
|
|
349
|
+
// Update snapshot position
|
|
350
|
+
if (typeof updateSnapshotPosition === 'function') {
|
|
351
|
+
updateSnapshotPosition(newPos, rect);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Show/hide alignment guides
|
|
355
|
+
if (typeof showSnapGuides === 'function') {
|
|
356
|
+
showSnapGuides(snapResult.guides, rect);
|
|
357
|
+
}
|
|
198
358
|
}
|
|
199
359
|
|
|
200
|
-
// Update
|
|
360
|
+
// Update axis drag overlay (mm to screen pixels)
|
|
201
361
|
function updateDragOverlayMm(pos, imgRect) {
|
|
202
362
|
if (!panelDragOverlay || !figSize.width_mm) return;
|
|
363
|
+
const scaleX = imgRect.width / figSize.width_mm, scaleY = imgRect.height / figSize.height_mm;
|
|
364
|
+
panelDragOverlay.style.left = `${pos.left * scaleX}px`;
|
|
365
|
+
panelDragOverlay.style.top = `${pos.top * scaleY}px`;
|
|
366
|
+
panelDragOverlay.style.width = `${pos.width * scaleX}px`;
|
|
367
|
+
panelDragOverlay.style.height = `${pos.height * scaleY}px`;
|
|
368
|
+
}
|
|
203
369
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
panelDragOverlay.style.left = `${left}px`;
|
|
214
|
-
panelDragOverlay.style.top = `${top}px`;
|
|
215
|
-
panelDragOverlay.style.width = `${width}px`;
|
|
216
|
-
panelDragOverlay.style.height = `${height}px`;
|
|
370
|
+
// Update panel bbox overlay based on snapped axis position (in mm)
|
|
371
|
+
function updatePanelBboxDragOverlayMm(axisPos, imgRect) {
|
|
372
|
+
if (!panelBboxDragOverlay || !panelBboxOffset || !figSize.width_mm) return;
|
|
373
|
+
const scaleX = imgRect.width / figSize.width_mm, scaleY = imgRect.height / figSize.height_mm;
|
|
374
|
+
// Panel bbox position = axis position + offset (both in mm, converted to screen pixels)
|
|
375
|
+
panelBboxDragOverlay.style.left = `${(axisPos.left + panelBboxOffset.left) * scaleX}px`;
|
|
376
|
+
panelBboxDragOverlay.style.top = `${(axisPos.top + panelBboxOffset.top) * scaleY}px`;
|
|
377
|
+
panelBboxDragOverlay.style.width = `${panelBboxOffset.width * scaleX}px`;
|
|
378
|
+
panelBboxDragOverlay.style.height = `${panelBboxOffset.height * scaleY}px`;
|
|
217
379
|
}
|
|
218
380
|
|
|
219
381
|
// Handle mouse up - complete the drag
|
|
@@ -221,12 +383,14 @@ async function handlePanelDragEnd(event) {
|
|
|
221
383
|
console.log('[PanelDrag] handlePanelDragEnd called, isDraggingPanel:', isDraggingPanel);
|
|
222
384
|
if (!isDraggingPanel) return;
|
|
223
385
|
|
|
224
|
-
// Hide
|
|
225
|
-
if (panelDragOverlay)
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
386
|
+
// Hide overlays, snapshot, and snap guides
|
|
387
|
+
if (panelDragOverlay) panelDragOverlay.style.display = 'none';
|
|
388
|
+
if (panelBboxDragOverlay) panelBboxDragOverlay.style.display = 'none';
|
|
389
|
+
if (typeof endSnapshotDrag === 'function') endSnapshotDrag();
|
|
390
|
+
if (typeof hideSnapGuides === 'function') hideSnapGuides();
|
|
229
391
|
document.body.style.cursor = '';
|
|
392
|
+
dragStartPanelBbox = null;
|
|
393
|
+
panelBboxOffset = null;
|
|
230
394
|
|
|
231
395
|
const img = document.getElementById('preview-image');
|
|
232
396
|
if (!img) {
|
|
@@ -240,8 +404,15 @@ async function handlePanelDragEnd(event) {
|
|
|
240
404
|
const deltaMmX = (event.clientX - dragStartPos.x) / rect.width * figSize.width_mm;
|
|
241
405
|
const deltaMmY = (event.clientY - dragStartPos.y) / rect.height * figSize.height_mm;
|
|
242
406
|
|
|
243
|
-
|
|
244
|
-
|
|
407
|
+
let newLeft = Math.max(0, Math.min(figSize.width_mm - dragStartPanelPos.width, dragStartPanelPos.left + deltaMmX));
|
|
408
|
+
let newTop = Math.max(0, Math.min(figSize.height_mm - dragStartPanelPos.height, dragStartPanelPos.top + deltaMmY));
|
|
409
|
+
|
|
410
|
+
// Apply snapping to final position (unless Alt was held)
|
|
411
|
+
if (typeof applySnapping === 'function' && !event.altKey) {
|
|
412
|
+
const snapResult = applySnapping(newLeft, newTop, dragStartPanelPos.width, dragStartPanelPos.height, draggedPanelIndex);
|
|
413
|
+
newLeft = snapResult.pos.left;
|
|
414
|
+
newTop = snapResult.pos.top;
|
|
415
|
+
}
|
|
245
416
|
|
|
246
417
|
// Only update if position actually changed (threshold in mm)
|
|
247
418
|
const threshold = 1.0; // 1mm threshold
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Panel drag snapshot functionality with server-side isolated rendering.
|
|
4
|
+
|
|
5
|
+
This module provides clean panel snapshots rendered in isolation (no overlap)
|
|
6
|
+
by fetching from the server, with async caching for smooth UX.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
SCRIPTS_PANEL_DRAG_SNAPSHOT = """
|
|
10
|
+
// ===== PANEL DRAG SNAPSHOT (DISABLED - corrupts figure state) =====
|
|
11
|
+
// Server-side snapshot rendering was disabled because matplotlib figures
|
|
12
|
+
// are not thread-safe. Modifying visibility to render isolated panels
|
|
13
|
+
// corrupts the shared figure state in Flask's threaded mode.
|
|
14
|
+
|
|
15
|
+
// No-op stubs to prevent errors from panel_drag.py calls
|
|
16
|
+
function startSnapshotDrag(panelIndex, imgRect, initialPos) {
|
|
17
|
+
// Disabled - no snapshot during drag
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function updateSnapshotPosition(pos, imgRect) {
|
|
21
|
+
// Disabled - no snapshot during drag
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function endSnapshotDrag() {
|
|
25
|
+
// Disabled - no snapshot during drag
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// No initialization needed
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
__all__ = ["SCRIPTS_PANEL_DRAG_SNAPSHOT"]
|
|
32
|
+
|
|
33
|
+
# EOF
|