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
|
@@ -12,6 +12,7 @@ SCRIPTS_PANEL_POSITION = r"""
|
|
|
12
12
|
|
|
13
13
|
let panelPositions = {};
|
|
14
14
|
let figSize = { width_mm: 0, height_mm: 0 };
|
|
15
|
+
let currentSelectedPanelIndex = null; // Track currently selected panel
|
|
15
16
|
|
|
16
17
|
// Load panel positions from server
|
|
17
18
|
async function loadPanelPositions() {
|
|
@@ -26,54 +27,57 @@ async function loadPanelPositions() {
|
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
panelPositions = data;
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
// Update inputs if a panel is selected
|
|
31
|
+
if (currentSelectedPanelIndex !== null) {
|
|
32
|
+
updatePanelPositionInputs();
|
|
33
|
+
}
|
|
31
34
|
} catch (error) {
|
|
32
35
|
console.error('Failed to load panel positions:', error);
|
|
33
36
|
}
|
|
34
37
|
}
|
|
35
38
|
|
|
36
|
-
// Update
|
|
37
|
-
function updatePanelSelector() {
|
|
38
|
-
const selector = document.getElementById('panel_selector');
|
|
39
|
-
if (!selector) return;
|
|
40
|
-
|
|
41
|
-
selector.innerHTML = '';
|
|
42
|
-
const axKeys = Object.keys(panelPositions).sort();
|
|
43
|
-
|
|
44
|
-
axKeys.forEach((key, index) => {
|
|
45
|
-
const opt = document.createElement('option');
|
|
46
|
-
opt.value = index;
|
|
47
|
-
// Show panel label (A, B, C...) if available
|
|
48
|
-
const label = String.fromCharCode(65 + index); // A, B, C...
|
|
49
|
-
opt.textContent = `Panel ${label} (${key})`;
|
|
50
|
-
selector.appendChild(opt);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
// Add change event listener - mark as explicitly selected on dropdown change
|
|
54
|
-
selector.addEventListener('change', () => {
|
|
55
|
-
panelExplicitlySelected = true;
|
|
56
|
-
updatePanelPositionInputs();
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
let panelExplicitlySelected = false;
|
|
61
|
-
|
|
62
|
-
// Update position input fields based on selected panel
|
|
39
|
+
// Update position input fields based on currently selected panel
|
|
63
40
|
function updatePanelPositionInputs(showHighlight = true) {
|
|
64
|
-
const
|
|
65
|
-
|
|
41
|
+
const indicator = document.getElementById('current_panel_indicator');
|
|
42
|
+
const leftInput = document.getElementById('panel_left');
|
|
43
|
+
const topInput = document.getElementById('panel_top');
|
|
44
|
+
const widthInput = document.getElementById('panel_width');
|
|
45
|
+
const heightInput = document.getElementById('panel_height');
|
|
46
|
+
const applyBtn = document.getElementById('apply_panel_position');
|
|
47
|
+
|
|
48
|
+
// If no panel selected, show placeholder and disable inputs
|
|
49
|
+
if (currentSelectedPanelIndex === null) {
|
|
50
|
+
if (indicator) {
|
|
51
|
+
indicator.textContent = 'Select an element';
|
|
52
|
+
indicator.classList.remove('panel-selected');
|
|
53
|
+
}
|
|
54
|
+
[leftInput, topInput, widthInput, heightInput].forEach(input => {
|
|
55
|
+
if (input) {
|
|
56
|
+
input.value = '';
|
|
57
|
+
input.disabled = true;
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
if (applyBtn) applyBtn.disabled = true;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
66
63
|
|
|
67
|
-
const
|
|
68
|
-
const axKey = Object.keys(panelPositions).sort()[axIndex];
|
|
64
|
+
const axKey = Object.keys(panelPositions).sort()[currentSelectedPanelIndex];
|
|
69
65
|
const pos = panelPositions[axKey];
|
|
70
66
|
|
|
71
67
|
if (!pos) return;
|
|
72
68
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
69
|
+
// Update indicator
|
|
70
|
+
if (indicator) {
|
|
71
|
+
const label = String.fromCharCode(65 + currentSelectedPanelIndex); // A, B, C...
|
|
72
|
+
indicator.textContent = `Panel ${label}`;
|
|
73
|
+
indicator.classList.add('panel-selected');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Enable inputs and update values
|
|
77
|
+
[leftInput, topInput, widthInput, heightInput].forEach(input => {
|
|
78
|
+
if (input) input.disabled = false;
|
|
79
|
+
});
|
|
80
|
+
if (applyBtn) applyBtn.disabled = false;
|
|
77
81
|
|
|
78
82
|
// Values are already in mm from server
|
|
79
83
|
if (leftInput) leftInput.value = pos.left;
|
|
@@ -81,13 +85,13 @@ function updatePanelPositionInputs(showHighlight = true) {
|
|
|
81
85
|
if (widthInput) widthInput.value = pos.width;
|
|
82
86
|
if (heightInput) heightInput.value = pos.height;
|
|
83
87
|
|
|
84
|
-
//
|
|
85
|
-
if (showHighlight
|
|
88
|
+
// Draw highlight
|
|
89
|
+
if (showHighlight) {
|
|
86
90
|
drawPanelSelectionHighlight(pos);
|
|
87
91
|
}
|
|
88
92
|
}
|
|
89
93
|
|
|
90
|
-
// Draw visual highlight around selected panel
|
|
94
|
+
// Draw visual highlight around selected panel (axis bbox only)
|
|
91
95
|
function drawPanelSelectionHighlight(pos) {
|
|
92
96
|
const overlay = document.getElementById('selection-overlay');
|
|
93
97
|
if (!overlay) return;
|
|
@@ -130,6 +134,60 @@ function drawPanelSelectionHighlight(pos) {
|
|
|
130
134
|
overlay.appendChild(rect);
|
|
131
135
|
}
|
|
132
136
|
|
|
137
|
+
// Draw full panel bbox (union of all elements belonging to the panel)
|
|
138
|
+
function drawPanelBbox(axIndex) {
|
|
139
|
+
const overlay = document.getElementById('selection-overlay');
|
|
140
|
+
if (!overlay) return;
|
|
141
|
+
|
|
142
|
+
// Clear previous panel bbox
|
|
143
|
+
const existingBbox = document.getElementById('panel-bbox-highlight');
|
|
144
|
+
if (existingBbox) existingBbox.remove();
|
|
145
|
+
|
|
146
|
+
// Get panel_bboxes from currentBboxes metadata
|
|
147
|
+
const panelBboxes = currentBboxes?._meta?.panel_bboxes;
|
|
148
|
+
if (!panelBboxes || !panelBboxes[axIndex]) {
|
|
149
|
+
console.log('[PanelBbox] No panel bbox for ax', axIndex);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const panelBbox = panelBboxes[axIndex];
|
|
154
|
+
const img = document.getElementById('preview-image');
|
|
155
|
+
if (!img || !img.naturalWidth || !img.naturalHeight) return;
|
|
156
|
+
|
|
157
|
+
// Ensure viewBox is set
|
|
158
|
+
overlay.setAttribute('viewBox', `0 0 ${img.naturalWidth} ${img.naturalHeight}`);
|
|
159
|
+
overlay.style.width = `${img.naturalWidth}px`;
|
|
160
|
+
overlay.style.height = `${img.naturalHeight}px`;
|
|
161
|
+
|
|
162
|
+
// panel bbox is already in pixel coordinates
|
|
163
|
+
const x = panelBbox.x;
|
|
164
|
+
const y = panelBbox.y;
|
|
165
|
+
const width = panelBbox.width;
|
|
166
|
+
const height = panelBbox.height;
|
|
167
|
+
|
|
168
|
+
// Create panel bbox rectangle (different style from axis highlight)
|
|
169
|
+
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
170
|
+
rect.id = 'panel-bbox-highlight';
|
|
171
|
+
rect.setAttribute('x', x - 2); // Small padding
|
|
172
|
+
rect.setAttribute('y', y - 2);
|
|
173
|
+
rect.setAttribute('width', width + 4);
|
|
174
|
+
rect.setAttribute('height', height + 4);
|
|
175
|
+
rect.setAttribute('fill', 'rgba(59, 130, 246, 0.05)'); // Very light blue fill
|
|
176
|
+
rect.setAttribute('stroke', '#3b82f6'); // Blue stroke
|
|
177
|
+
rect.setAttribute('stroke-width', '2');
|
|
178
|
+
rect.setAttribute('stroke-dasharray', '6,3');
|
|
179
|
+
rect.style.pointerEvents = 'none';
|
|
180
|
+
|
|
181
|
+
overlay.appendChild(rect);
|
|
182
|
+
console.log('[PanelBbox] Drew bbox for panel', axIndex, panelBbox);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Clear panel bbox highlight
|
|
186
|
+
function clearPanelBbox() {
|
|
187
|
+
const existingBbox = document.getElementById('panel-bbox-highlight');
|
|
188
|
+
if (existingBbox) existingBbox.remove();
|
|
189
|
+
}
|
|
190
|
+
|
|
133
191
|
// Clear panel selection highlight
|
|
134
192
|
function clearPanelSelectionHighlight() {
|
|
135
193
|
const existingHighlight = document.getElementById('panel-selection-highlight');
|
|
@@ -137,25 +195,37 @@ function clearPanelSelectionHighlight() {
|
|
|
137
195
|
}
|
|
138
196
|
|
|
139
197
|
// Select panel by index (called when clicking on axes in canvas)
|
|
140
|
-
function selectPanelByIndex(axIndex) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
// Mark as explicitly selected
|
|
145
|
-
panelExplicitlySelected = true;
|
|
146
|
-
|
|
147
|
-
// Update dropdown selection
|
|
148
|
-
selector.value = axIndex;
|
|
198
|
+
function selectPanelByIndex(axIndex, switchToAxisTab = true) {
|
|
199
|
+
// Update the current selected panel index
|
|
200
|
+
currentSelectedPanelIndex = axIndex;
|
|
149
201
|
|
|
150
202
|
// Update inputs and highlight
|
|
151
203
|
updatePanelPositionInputs();
|
|
152
204
|
|
|
153
|
-
//
|
|
154
|
-
|
|
205
|
+
// Draw panel bbox (union of all elements)
|
|
206
|
+
drawPanelBbox(axIndex);
|
|
207
|
+
|
|
208
|
+
// Update panel caption input
|
|
209
|
+
if (typeof updatePanelCaptionInput === 'function') {
|
|
210
|
+
updatePanelCaptionInput(axIndex);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Switch to Axis tab only if requested (default true for backwards compat)
|
|
214
|
+
if (switchToAxisTab) {
|
|
215
|
+
switchTab('axis');
|
|
216
|
+
}
|
|
155
217
|
|
|
156
218
|
console.log('Selected panel', axIndex);
|
|
157
219
|
}
|
|
158
220
|
|
|
221
|
+
// Clear panel selection (called when selection is cleared)
|
|
222
|
+
function clearPanelSelection() {
|
|
223
|
+
currentSelectedPanelIndex = null;
|
|
224
|
+
updatePanelPositionInputs(false);
|
|
225
|
+
clearPanelSelectionHighlight();
|
|
226
|
+
clearPanelBbox();
|
|
227
|
+
}
|
|
228
|
+
|
|
159
229
|
// Find panel index from axes key (e.g., "ax_0" -> 0)
|
|
160
230
|
function getPanelIndexFromKey(key) {
|
|
161
231
|
if (!key) return null;
|
|
@@ -179,18 +249,22 @@ function getPanelIndexFromKey(key) {
|
|
|
179
249
|
|
|
180
250
|
// Apply panel position changes
|
|
181
251
|
async function applyPanelPosition() {
|
|
182
|
-
const selector = document.getElementById('panel_selector');
|
|
183
252
|
const leftInput = document.getElementById('panel_left');
|
|
184
253
|
const topInput = document.getElementById('panel_top');
|
|
185
254
|
const widthInput = document.getElementById('panel_width');
|
|
186
255
|
const heightInput = document.getElementById('panel_height');
|
|
187
256
|
|
|
188
|
-
if (
|
|
257
|
+
if (currentSelectedPanelIndex === null) {
|
|
258
|
+
console.error('No panel selected');
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (!leftInput || !topInput || !widthInput || !heightInput) {
|
|
189
263
|
console.error('Panel position inputs not found');
|
|
190
264
|
return;
|
|
191
265
|
}
|
|
192
266
|
|
|
193
|
-
const axIndex =
|
|
267
|
+
const axIndex = currentSelectedPanelIndex;
|
|
194
268
|
const left = parseFloat(leftInput.value);
|
|
195
269
|
const top = parseFloat(topInput.value);
|
|
196
270
|
const width = parseFloat(widthInput.value);
|
|
@@ -268,6 +342,116 @@ function initPanelPositionControls() {
|
|
|
268
342
|
|
|
269
343
|
// Load initial positions
|
|
270
344
|
loadPanelPositions();
|
|
345
|
+
|
|
346
|
+
// Initialize debug toolbar if in debug mode
|
|
347
|
+
if (typeof DEBUG_MODE !== 'undefined' && DEBUG_MODE) {
|
|
348
|
+
initDebugToolbar();
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// ===== DEBUG MODE: SHOW ALL BBOXES =====
|
|
353
|
+
|
|
354
|
+
let allBboxesVisible = false;
|
|
355
|
+
|
|
356
|
+
// Toggle showing all panel bboxes at once
|
|
357
|
+
function toggleAllBboxes() {
|
|
358
|
+
allBboxesVisible = !allBboxesVisible;
|
|
359
|
+
|
|
360
|
+
const overlay = document.getElementById('selection-overlay');
|
|
361
|
+
if (!overlay) return;
|
|
362
|
+
|
|
363
|
+
// Remove existing debug bboxes
|
|
364
|
+
document.querySelectorAll('.debug-panel-bbox').forEach(el => el.remove());
|
|
365
|
+
|
|
366
|
+
if (!allBboxesVisible) {
|
|
367
|
+
console.log('[Debug] All bboxes hidden');
|
|
368
|
+
updateDebugButton(false);
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const panelBboxes = currentBboxes?._meta?.panel_bboxes;
|
|
373
|
+
if (!panelBboxes) {
|
|
374
|
+
console.warn('[Debug] No panel bboxes available');
|
|
375
|
+
showToast('No panel bboxes available', 'warning');
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const img = document.getElementById('preview-image');
|
|
380
|
+
if (!img || !img.naturalWidth || !img.naturalHeight) return;
|
|
381
|
+
|
|
382
|
+
// Ensure viewBox is set
|
|
383
|
+
overlay.setAttribute('viewBox', `0 0 ${img.naturalWidth} ${img.naturalHeight}`);
|
|
384
|
+
overlay.style.width = `${img.naturalWidth}px`;
|
|
385
|
+
overlay.style.height = `${img.naturalHeight}px`;
|
|
386
|
+
|
|
387
|
+
// Colors for different panels
|
|
388
|
+
const colors = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6', '#8b5cf6', '#ec4899', '#06b6d4', '#84cc16', '#f97316'];
|
|
389
|
+
|
|
390
|
+
// Draw bbox for each panel
|
|
391
|
+
Object.entries(panelBboxes).forEach(([axIdx, bbox], idx) => {
|
|
392
|
+
const color = colors[idx % colors.length];
|
|
393
|
+
const label = String.fromCharCode(65 + parseInt(axIdx)); // A, B, C...
|
|
394
|
+
|
|
395
|
+
// Create bbox rectangle
|
|
396
|
+
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
397
|
+
rect.classList.add('debug-panel-bbox');
|
|
398
|
+
rect.setAttribute('x', bbox.x);
|
|
399
|
+
rect.setAttribute('y', bbox.y);
|
|
400
|
+
rect.setAttribute('width', bbox.width);
|
|
401
|
+
rect.setAttribute('height', bbox.height);
|
|
402
|
+
rect.setAttribute('fill', 'none');
|
|
403
|
+
rect.setAttribute('stroke', color);
|
|
404
|
+
rect.setAttribute('stroke-width', '2');
|
|
405
|
+
rect.setAttribute('stroke-dasharray', '5,3');
|
|
406
|
+
rect.style.pointerEvents = 'none';
|
|
407
|
+
overlay.appendChild(rect);
|
|
408
|
+
|
|
409
|
+
// Create label
|
|
410
|
+
const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
411
|
+
text.classList.add('debug-panel-bbox');
|
|
412
|
+
text.setAttribute('x', bbox.x + 5);
|
|
413
|
+
text.setAttribute('y', bbox.y + 15);
|
|
414
|
+
text.setAttribute('fill', color);
|
|
415
|
+
text.setAttribute('font-size', '14');
|
|
416
|
+
text.setAttribute('font-weight', 'bold');
|
|
417
|
+
text.style.pointerEvents = 'none';
|
|
418
|
+
text.textContent = `Panel ${label}`;
|
|
419
|
+
overlay.appendChild(text);
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
console.log('[Debug] Showing all panel bboxes:', Object.keys(panelBboxes).length);
|
|
423
|
+
updateDebugButton(true);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Update debug button state
|
|
427
|
+
function updateDebugButton(active) {
|
|
428
|
+
const btn = document.getElementById('btn-debug-bboxes');
|
|
429
|
+
if (btn) {
|
|
430
|
+
btn.classList.toggle('active', active);
|
|
431
|
+
btn.title = active ? 'Hide All Bboxes (Alt+B)' : 'Show All Bboxes (Alt+B)';
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// Initialize debug toolbar
|
|
436
|
+
function initDebugToolbar() {
|
|
437
|
+
// Find the preview controls area
|
|
438
|
+
const previewControls = document.querySelector('.preview-controls');
|
|
439
|
+
if (!previewControls) return;
|
|
440
|
+
|
|
441
|
+
// Create debug button
|
|
442
|
+
const debugBtn = document.createElement('button');
|
|
443
|
+
debugBtn.id = 'btn-debug-bboxes';
|
|
444
|
+
debugBtn.className = 'btn-icon btn-debug';
|
|
445
|
+
debugBtn.title = 'Show All Bboxes (Alt+B)';
|
|
446
|
+
debugBtn.innerHTML = '🔲';
|
|
447
|
+
debugBtn.style.cssText = 'margin-left: 8px; background: #374151; border: 1px dashed #f59e0b; color: #f59e0b;';
|
|
448
|
+
debugBtn.onclick = toggleAllBboxes;
|
|
449
|
+
|
|
450
|
+
// Add to controls
|
|
451
|
+
previewControls.appendChild(debugBtn);
|
|
452
|
+
|
|
453
|
+
console.log('[Debug] Debug toolbar initialized (FIGRECIPE_DEBUG_MODE=1)');
|
|
454
|
+
console.log('[Debug] Shortcuts: Alt+I = Element Inspector, Alt+B = Show All Bboxes');
|
|
271
455
|
}
|
|
272
456
|
|
|
273
457
|
// Call initialization on DOMContentLoaded
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Panel resize JavaScript for the figure editor.
|
|
4
|
+
|
|
5
|
+
This module handles resizing of panels by dragging dividers:
|
|
6
|
+
- File browser panel (left)
|
|
7
|
+
- Data panel (left)
|
|
8
|
+
- Properties panel (right)
|
|
9
|
+
|
|
10
|
+
Features smart accordion:
|
|
11
|
+
- Auto-collapse when dragged below threshold
|
|
12
|
+
- Auto-expand when dragging from collapsed state
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
SCRIPTS_PANEL_RESIZE = """
|
|
16
|
+
// ==================== PANEL RESIZE ====================
|
|
17
|
+
// Enables dragging panel dividers to resize panels with smart accordion
|
|
18
|
+
|
|
19
|
+
// Store default widths for each panel
|
|
20
|
+
const panelDefaultWidths = {
|
|
21
|
+
'file-browser-panel': 200,
|
|
22
|
+
'datatable-panel': 280,
|
|
23
|
+
'controls-panel': 350
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Collapse threshold - panels collapse when dragged below this width
|
|
27
|
+
const COLLAPSE_THRESHOLD = 60;
|
|
28
|
+
|
|
29
|
+
// Expand threshold - collapsed panels expand when dragged beyond this
|
|
30
|
+
const EXPAND_THRESHOLD = 50;
|
|
31
|
+
|
|
32
|
+
function initPanelResize() {
|
|
33
|
+
// File browser resize handle
|
|
34
|
+
const fileBrowserResize = document.getElementById('file-browser-resize');
|
|
35
|
+
const fileBrowserPanel = document.getElementById('file-browser-panel');
|
|
36
|
+
|
|
37
|
+
// Datatable resize handle
|
|
38
|
+
const datatableResize = document.getElementById('datatable-resize');
|
|
39
|
+
const datatablePanel = document.getElementById('datatable-panel');
|
|
40
|
+
|
|
41
|
+
// Properties panel resize handle (will be added to controls-panel)
|
|
42
|
+
const controlsPanel = document.querySelector('.controls-panel');
|
|
43
|
+
|
|
44
|
+
if (fileBrowserResize && fileBrowserPanel) {
|
|
45
|
+
initSmartResizer(fileBrowserResize, fileBrowserPanel, 'left', 'file-browser-panel');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (datatableResize && datatablePanel) {
|
|
49
|
+
initSmartResizer(datatableResize, datatablePanel, 'left', 'datatable-panel');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Add resize handle for properties panel
|
|
53
|
+
if (controlsPanel) {
|
|
54
|
+
let propertiesResize = document.getElementById('properties-resize');
|
|
55
|
+
if (!propertiesResize) {
|
|
56
|
+
propertiesResize = document.createElement('div');
|
|
57
|
+
propertiesResize.id = 'properties-resize';
|
|
58
|
+
propertiesResize.className = 'properties-resize';
|
|
59
|
+
controlsPanel.insertBefore(propertiesResize, controlsPanel.firstChild);
|
|
60
|
+
}
|
|
61
|
+
initSmartResizer(propertiesResize, controlsPanel, 'right', 'controls-panel');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log('[PanelResize] Initialized with smart accordion');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function initSmartResizer(resizeHandle, panel, side, panelId) {
|
|
68
|
+
let isResizing = false;
|
|
69
|
+
let startX = 0;
|
|
70
|
+
let startWidth = 0;
|
|
71
|
+
let wasCollapsed = false;
|
|
72
|
+
|
|
73
|
+
// Handle click on resize handle when panel is collapsed - expand it
|
|
74
|
+
resizeHandle.addEventListener('click', (e) => {
|
|
75
|
+
if (panel.classList.contains('collapsed')) {
|
|
76
|
+
e.stopPropagation();
|
|
77
|
+
expandPanel(panel, panelId);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
resizeHandle.addEventListener('mousedown', (e) => {
|
|
82
|
+
isResizing = true;
|
|
83
|
+
startX = e.clientX;
|
|
84
|
+
wasCollapsed = panel.classList.contains('collapsed');
|
|
85
|
+
|
|
86
|
+
// If collapsed, start with minimal width
|
|
87
|
+
if (wasCollapsed) {
|
|
88
|
+
startWidth = panel.offsetWidth;
|
|
89
|
+
} else {
|
|
90
|
+
startWidth = panel.offsetWidth;
|
|
91
|
+
// Save current width as default if it's reasonable
|
|
92
|
+
if (startWidth > COLLAPSE_THRESHOLD) {
|
|
93
|
+
panelDefaultWidths[panelId] = startWidth;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
resizeHandle.classList.add('resizing');
|
|
98
|
+
document.body.style.cursor = 'col-resize';
|
|
99
|
+
document.body.style.userSelect = 'none';
|
|
100
|
+
e.preventDefault();
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
document.addEventListener('mousemove', (e) => {
|
|
104
|
+
if (!isResizing) return;
|
|
105
|
+
|
|
106
|
+
const deltaX = e.clientX - startX;
|
|
107
|
+
let newWidth;
|
|
108
|
+
|
|
109
|
+
if (side === 'left') {
|
|
110
|
+
// For left panel, positive delta increases width
|
|
111
|
+
newWidth = startWidth + deltaX;
|
|
112
|
+
} else {
|
|
113
|
+
// For right panel, negative delta increases width
|
|
114
|
+
newWidth = startWidth - deltaX;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Smart accordion behavior
|
|
118
|
+
if (wasCollapsed) {
|
|
119
|
+
// Expanding from collapsed state
|
|
120
|
+
if (Math.abs(deltaX) > EXPAND_THRESHOLD) {
|
|
121
|
+
// Expand to default width
|
|
122
|
+
expandPanel(panel, panelId);
|
|
123
|
+
// Continue resizing from expanded state
|
|
124
|
+
startX = e.clientX;
|
|
125
|
+
startWidth = panelDefaultWidths[panelId];
|
|
126
|
+
wasCollapsed = false;
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
// Normal resize - check for collapse threshold
|
|
130
|
+
if (newWidth < COLLAPSE_THRESHOLD) {
|
|
131
|
+
// Collapse the panel
|
|
132
|
+
collapsePanel(panel, panelId);
|
|
133
|
+
isResizing = false;
|
|
134
|
+
resizeHandle.classList.remove('resizing');
|
|
135
|
+
document.body.style.cursor = '';
|
|
136
|
+
document.body.style.userSelect = '';
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Clamp to min/max (but above collapse threshold)
|
|
141
|
+
const minWidth = Math.max(COLLAPSE_THRESHOLD, parseInt(getComputedStyle(panel).minWidth) || 160);
|
|
142
|
+
const maxWidth = parseInt(getComputedStyle(panel).maxWidth) || 500;
|
|
143
|
+
newWidth = Math.max(minWidth, Math.min(maxWidth, newWidth));
|
|
144
|
+
|
|
145
|
+
panel.style.width = newWidth + 'px';
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
document.addEventListener('mouseup', () => {
|
|
150
|
+
if (isResizing) {
|
|
151
|
+
isResizing = false;
|
|
152
|
+
resizeHandle.classList.remove('resizing');
|
|
153
|
+
document.body.style.cursor = '';
|
|
154
|
+
document.body.style.userSelect = '';
|
|
155
|
+
|
|
156
|
+
// Save width if not collapsed
|
|
157
|
+
if (!panel.classList.contains('collapsed')) {
|
|
158
|
+
const currentWidth = panel.offsetWidth;
|
|
159
|
+
if (currentWidth > COLLAPSE_THRESHOLD) {
|
|
160
|
+
panelDefaultWidths[panelId] = currentWidth;
|
|
161
|
+
localStorage.setItem(`figrecipe_${panelId}_width`, currentWidth);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
// Also handle mouse leaving window
|
|
168
|
+
document.addEventListener('mouseleave', () => {
|
|
169
|
+
if (isResizing) {
|
|
170
|
+
isResizing = false;
|
|
171
|
+
resizeHandle.classList.remove('resizing');
|
|
172
|
+
document.body.style.cursor = '';
|
|
173
|
+
document.body.style.userSelect = '';
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function collapsePanel(panel, panelId) {
|
|
179
|
+
panel.classList.add('collapsed');
|
|
180
|
+
|
|
181
|
+
// Update localStorage
|
|
182
|
+
const storageKey = getStorageKey(panelId);
|
|
183
|
+
if (storageKey) {
|
|
184
|
+
localStorage.setItem(storageKey, 'true');
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Dispatch custom event for accordion sync
|
|
188
|
+
panel.dispatchEvent(new CustomEvent('panelCollapsed', { bubbles: true }));
|
|
189
|
+
console.log(`[PanelResize] ${panelId} collapsed via drag`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function expandPanel(panel, panelId) {
|
|
193
|
+
panel.classList.remove('collapsed');
|
|
194
|
+
|
|
195
|
+
// Restore saved width or default
|
|
196
|
+
const savedWidth = localStorage.getItem(`figrecipe_${panelId}_width`);
|
|
197
|
+
const width = savedWidth ? parseInt(savedWidth) : panelDefaultWidths[panelId];
|
|
198
|
+
panel.style.width = width + 'px';
|
|
199
|
+
|
|
200
|
+
// Update localStorage
|
|
201
|
+
const storageKey = getStorageKey(panelId);
|
|
202
|
+
if (storageKey) {
|
|
203
|
+
localStorage.setItem(storageKey, 'false');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Dispatch custom event for accordion sync
|
|
207
|
+
panel.dispatchEvent(new CustomEvent('panelExpanded', { bubbles: true }));
|
|
208
|
+
console.log(`[PanelResize] ${panelId} expanded via drag to ${width}px`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function getStorageKey(panelId) {
|
|
212
|
+
const keyMap = {
|
|
213
|
+
'file-browser-panel': 'figrecipe_filebrowser_collapsed',
|
|
214
|
+
'datatable-panel': 'figrecipe_data_collapsed',
|
|
215
|
+
'controls-panel': 'figrecipe_properties_collapsed'
|
|
216
|
+
};
|
|
217
|
+
return keyMap[panelId];
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Initialize on DOM ready
|
|
221
|
+
if (document.readyState === 'loading') {
|
|
222
|
+
document.addEventListener('DOMContentLoaded', initPanelResize);
|
|
223
|
+
} else {
|
|
224
|
+
initPanelResize();
|
|
225
|
+
}
|
|
226
|
+
"""
|
|
227
|
+
|
|
228
|
+
__all__ = ["SCRIPTS_PANEL_RESIZE"]
|
|
229
|
+
|
|
230
|
+
# EOF
|