figrecipe 0.6.0__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 +161 -1030
- figrecipe/__main__.py +12 -0
- figrecipe/_api/__init__.py +48 -0
- figrecipe/_api/_extract.py +108 -0
- figrecipe/_api/_notebook.py +61 -0
- figrecipe/_api/_panel.py +113 -0
- figrecipe/_api/_save.py +287 -0
- figrecipe/_api/_seaborn_proxy.py +34 -0
- figrecipe/_api/_style_manager.py +153 -0
- figrecipe/_api/_subplots.py +333 -0
- figrecipe/_api/_validate.py +82 -0
- 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 +4 -93
- figrecipe/_dev/_plotters.py +76 -0
- figrecipe/_dev/_run_demos.py +56 -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/_dev/demo_plotters/__init__.py +35 -166
- figrecipe/_dev/demo_plotters/_categories.py +81 -0
- figrecipe/_dev/demo_plotters/_figure_creators.py +119 -0
- figrecipe/_dev/demo_plotters/_helpers.py +31 -0
- figrecipe/_dev/demo_plotters/_registry.py +50 -0
- figrecipe/_dev/demo_plotters/bar_categorical/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/contour_surface/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/distribution/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/image_matrix/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/line_curve/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/{plot_plot.py → line_curve/plot_plot.py} +3 -2
- figrecipe/_dev/demo_plotters/scatter_points/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/special/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/{plot_pie.py → special/plot_pie.py} +5 -1
- figrecipe/_dev/demo_plotters/spectral_signal/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/vector_flow/__init__.py +4 -0
- figrecipe/_editor/__init__.py +61 -13
- figrecipe/_editor/_bbox/__init__.py +43 -0
- figrecipe/_editor/_bbox/_collections.py +177 -0
- figrecipe/_editor/_bbox/_elements.py +159 -0
- figrecipe/_editor/_bbox/_extract.py +402 -0
- figrecipe/_editor/_bbox/_extract_axes.py +370 -0
- figrecipe/_editor/_bbox/_extract_text.py +466 -0
- figrecipe/_editor/_bbox/_lines.py +173 -0
- figrecipe/_editor/_bbox/_transforms.py +146 -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 +200 -1030
- figrecipe/_editor/_helpers.py +251 -0
- figrecipe/_editor/_hitmap/__init__.py +76 -0
- figrecipe/_editor/_hitmap/_artists/__init__.py +21 -0
- figrecipe/_editor/_hitmap/_artists/_collections.py +345 -0
- figrecipe/_editor/_hitmap/_artists/_images.py +68 -0
- figrecipe/_editor/_hitmap/_artists/_lines.py +107 -0
- figrecipe/_editor/_hitmap/_artists/_patches.py +163 -0
- figrecipe/_editor/_hitmap/_artists/_text.py +190 -0
- figrecipe/_editor/_hitmap/_colors.py +181 -0
- figrecipe/_editor/_hitmap/_detect.py +194 -0
- figrecipe/_editor/_hitmap/_restore.py +154 -0
- figrecipe/_editor/_hitmap_main.py +182 -0
- figrecipe/_editor/_overrides.py +4 -1
- figrecipe/_editor/_plot_types_registry.py +190 -0
- figrecipe/_editor/_preferences.py +135 -0
- figrecipe/_editor/_render_overrides.py +507 -0
- figrecipe/_editor/_renderer.py +81 -186
- figrecipe/_editor/_routes_annotation.py +114 -0
- figrecipe/_editor/_routes_axis.py +482 -0
- figrecipe/_editor/_routes_captions.py +130 -0
- figrecipe/_editor/_routes_composition.py +270 -0
- figrecipe/_editor/_routes_core.py +126 -0
- figrecipe/_editor/_routes_datatable.py +364 -0
- figrecipe/_editor/_routes_element.py +335 -0
- 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 +243 -0
- figrecipe/_editor/_templates/__init__.py +116 -1
- figrecipe/_editor/_templates/_html.py +154 -64
- 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 +178 -0
- figrecipe/_editor/_templates/_scripts/_accordion.py +328 -0
- figrecipe/_editor/_templates/_scripts/_annotation_drag.py +504 -0
- figrecipe/_editor/_templates/_scripts/_api.py +228 -0
- figrecipe/_editor/_templates/_scripts/_canvas_context_menu.py +182 -0
- figrecipe/_editor/_templates/_scripts/_captions.py +231 -0
- figrecipe/_editor/_templates/_scripts/_colors.py +485 -0
- figrecipe/_editor/_templates/_scripts/_composition.py +283 -0
- figrecipe/_editor/_templates/_scripts/_core.py +493 -0
- 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/_debug_snapshot.py +186 -0
- figrecipe/_editor/_templates/_scripts/_element_editor.py +325 -0
- figrecipe/_editor/_templates/_scripts/_files.py +429 -0
- figrecipe/_editor/_templates/_scripts/_files_context_menu.py +240 -0
- figrecipe/_editor/_templates/_scripts/_hitmap.py +512 -0
- figrecipe/_editor/_templates/_scripts/_image_drop.py +428 -0
- figrecipe/_editor/_templates/_scripts/_inspector.py +315 -0
- figrecipe/_editor/_templates/_scripts/_labels.py +464 -0
- figrecipe/_editor/_templates/_scripts/_legend_drag.py +270 -0
- figrecipe/_editor/_templates/_scripts/_modals.py +226 -0
- figrecipe/_editor/_templates/_scripts/_multi_select.py +198 -0
- figrecipe/_editor/_templates/_scripts/_overlays.py +292 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag.py +505 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag_snapshot.py +33 -0
- figrecipe/_editor/_templates/_scripts/_panel_position.py +463 -0
- 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 +244 -0
- figrecipe/_editor/_templates/_scripts/_sync.py +242 -0
- figrecipe/_editor/_templates/_scripts/_tabs.py +89 -0
- figrecipe/_editor/_templates/_scripts/_undo_redo.py +348 -0
- figrecipe/_editor/_templates/_scripts/_view_mode.py +107 -0
- figrecipe/_editor/_templates/_scripts/_zoom.py +212 -0
- figrecipe/_editor/_templates/_styles/__init__.py +78 -0
- figrecipe/_editor/_templates/_styles/_base.py +111 -0
- figrecipe/_editor/_templates/_styles/_buttons.py +327 -0
- figrecipe/_editor/_templates/_styles/_color_input.py +123 -0
- figrecipe/_editor/_templates/_styles/_composition.py +87 -0
- figrecipe/_editor/_templates/_styles/_controls.py +430 -0
- 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 +144 -0
- figrecipe/_editor/_templates/_styles/_file_browser.py +466 -0
- figrecipe/_editor/_templates/_styles/_forms.py +224 -0
- figrecipe/_editor/_templates/_styles/_hitmap.py +191 -0
- figrecipe/_editor/_templates/_styles/_inspector.py +90 -0
- figrecipe/_editor/_templates/_styles/_labels.py +118 -0
- figrecipe/_editor/_templates/_styles/_modals.py +127 -0
- figrecipe/_editor/_templates/_styles/_overlays.py +130 -0
- figrecipe/_editor/_templates/_styles/_preview.py +430 -0
- figrecipe/_editor/_templates/_styles/_selection.py +73 -0
- 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 +8 -0
- figrecipe/_recorder.py +63 -109
- figrecipe/_recorder_utils.py +124 -0
- figrecipe/_reproducer/__init__.py +18 -0
- figrecipe/_reproducer/_core.py +509 -0
- figrecipe/_reproducer/_custom_plots.py +279 -0
- figrecipe/_reproducer/_seaborn.py +100 -0
- figrecipe/_reproducer/_violin.py +186 -0
- figrecipe/_signatures/_kwargs.py +273 -0
- figrecipe/_signatures/_loader.py +21 -423
- figrecipe/_signatures/_parsing.py +147 -0
- figrecipe/_utils/__init__.py +3 -0
- figrecipe/_utils/_bundle.py +205 -0
- figrecipe/_wrappers/_axes.py +252 -895
- figrecipe/_wrappers/_axes_helpers.py +136 -0
- figrecipe/_wrappers/_axes_plots.py +418 -0
- figrecipe/_wrappers/_axes_seaborn.py +157 -0
- figrecipe/_wrappers/_caption_generator.py +218 -0
- figrecipe/_wrappers/_figure.py +188 -1
- figrecipe/_wrappers/_panel_labels.py +127 -0
- figrecipe/_wrappers/_plot_helpers.py +143 -0
- figrecipe/_wrappers/_stat_annotation.py +274 -0
- figrecipe/_wrappers/_violin_helpers.py +180 -0
- figrecipe/styles/__init__.py +8 -6
- figrecipe/styles/_dotdict.py +72 -0
- figrecipe/styles/_finalize.py +134 -0
- figrecipe/styles/_fonts.py +77 -0
- figrecipe/styles/_kwargs_converter.py +178 -0
- figrecipe/styles/_plot_styles.py +209 -0
- figrecipe/styles/_style_applier.py +42 -480
- figrecipe/styles/_style_loader.py +16 -192
- figrecipe/styles/_themes.py +151 -0
- figrecipe/styles/presets/MATPLOTLIB.yaml +2 -1
- figrecipe/styles/presets/SCITEX.yaml +40 -28
- figrecipe-0.9.0.dist-info/METADATA +427 -0
- figrecipe-0.9.0.dist-info/RECORD +277 -0
- figrecipe-0.9.0.dist-info/entry_points.txt +2 -0
- figrecipe/_editor/_bbox.py +0 -978
- figrecipe/_editor/_hitmap.py +0 -937
- figrecipe/_editor/_templates/_scripts.py +0 -2778
- figrecipe/_editor/_templates/_styles.py +0 -1326
- figrecipe/_reproducer.py +0 -975
- figrecipe-0.6.0.dist-info/METADATA +0 -394
- figrecipe-0.6.0.dist-info/RECORD +0 -90
- /figrecipe/_dev/demo_plotters/{plot_bar.py → bar_categorical/plot_bar.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_barh.py → bar_categorical/plot_barh.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_contour.py → contour_surface/plot_contour.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_contourf.py → contour_surface/plot_contourf.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_tricontour.py → contour_surface/plot_tricontour.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_tricontourf.py → contour_surface/plot_tricontourf.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_tripcolor.py → contour_surface/plot_tripcolor.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_triplot.py → contour_surface/plot_triplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_boxplot.py → distribution/plot_boxplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_ecdf.py → distribution/plot_ecdf.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_hist.py → distribution/plot_hist.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_hist2d.py → distribution/plot_hist2d.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_violinplot.py → distribution/plot_violinplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_hexbin.py → image_matrix/plot_hexbin.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_imshow.py → image_matrix/plot_imshow.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_matshow.py → image_matrix/plot_matshow.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_pcolor.py → image_matrix/plot_pcolor.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_pcolormesh.py → image_matrix/plot_pcolormesh.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_spy.py → image_matrix/plot_spy.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_errorbar.py → line_curve/plot_errorbar.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_fill.py → line_curve/plot_fill.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_fill_between.py → line_curve/plot_fill_between.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_fill_betweenx.py → line_curve/plot_fill_betweenx.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_stackplot.py → line_curve/plot_stackplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_stairs.py → line_curve/plot_stairs.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_step.py → line_curve/plot_step.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_scatter.py → scatter_points/plot_scatter.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_eventplot.py → special/plot_eventplot.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_loglog.py → special/plot_loglog.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_semilogx.py → special/plot_semilogx.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_semilogy.py → special/plot_semilogy.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_stem.py → special/plot_stem.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_acorr.py → spectral_signal/plot_acorr.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_angle_spectrum.py → spectral_signal/plot_angle_spectrum.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_cohere.py → spectral_signal/plot_cohere.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_csd.py → spectral_signal/plot_csd.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_magnitude_spectrum.py → spectral_signal/plot_magnitude_spectrum.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_phase_spectrum.py → spectral_signal/plot_phase_spectrum.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_psd.py → spectral_signal/plot_psd.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_specgram.py → spectral_signal/plot_specgram.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_xcorr.py → spectral_signal/plot_xcorr.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_barbs.py → vector_flow/plot_barbs.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_quiver.py → vector_flow/plot_quiver.py} +0 -0
- /figrecipe/_dev/demo_plotters/{plot_streamplot.py → vector_flow/plot_streamplot.py} +0 -0
- {figrecipe-0.6.0.dist-info → figrecipe-0.9.0.dist-info}/WHEEL +0 -0
- {figrecipe-0.6.0.dist-info → figrecipe-0.9.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,463 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Panel position JavaScript for the figure editor.
|
|
4
|
+
|
|
5
|
+
This module contains the JavaScript code for:
|
|
6
|
+
- Loading and displaying panel positions
|
|
7
|
+
- Updating panel positions via numerical inputs
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
SCRIPTS_PANEL_POSITION = r"""
|
|
11
|
+
// ===== PANEL POSITION (mm, upper-left origin) =====
|
|
12
|
+
|
|
13
|
+
let panelPositions = {};
|
|
14
|
+
let figSize = { width_mm: 0, height_mm: 0 };
|
|
15
|
+
let currentSelectedPanelIndex = null; // Track currently selected panel
|
|
16
|
+
|
|
17
|
+
// Load panel positions from server
|
|
18
|
+
async function loadPanelPositions() {
|
|
19
|
+
try {
|
|
20
|
+
const response = await fetch('/get_axes_positions');
|
|
21
|
+
const data = await response.json();
|
|
22
|
+
|
|
23
|
+
// Extract figure size
|
|
24
|
+
if (data._figsize) {
|
|
25
|
+
figSize = data._figsize;
|
|
26
|
+
delete data._figsize;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
panelPositions = data;
|
|
30
|
+
// Update inputs if a panel is selected
|
|
31
|
+
if (currentSelectedPanelIndex !== null) {
|
|
32
|
+
updatePanelPositionInputs();
|
|
33
|
+
}
|
|
34
|
+
} catch (error) {
|
|
35
|
+
console.error('Failed to load panel positions:', error);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Update position input fields based on currently selected panel
|
|
40
|
+
function updatePanelPositionInputs(showHighlight = true) {
|
|
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
|
+
}
|
|
63
|
+
|
|
64
|
+
const axKey = Object.keys(panelPositions).sort()[currentSelectedPanelIndex];
|
|
65
|
+
const pos = panelPositions[axKey];
|
|
66
|
+
|
|
67
|
+
if (!pos) return;
|
|
68
|
+
|
|
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;
|
|
81
|
+
|
|
82
|
+
// Values are already in mm from server
|
|
83
|
+
if (leftInput) leftInput.value = pos.left;
|
|
84
|
+
if (topInput) topInput.value = pos.top;
|
|
85
|
+
if (widthInput) widthInput.value = pos.width;
|
|
86
|
+
if (heightInput) heightInput.value = pos.height;
|
|
87
|
+
|
|
88
|
+
// Draw highlight
|
|
89
|
+
if (showHighlight) {
|
|
90
|
+
drawPanelSelectionHighlight(pos);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Draw visual highlight around selected panel (axis bbox only)
|
|
95
|
+
function drawPanelSelectionHighlight(pos) {
|
|
96
|
+
const overlay = document.getElementById('selection-overlay');
|
|
97
|
+
if (!overlay) return;
|
|
98
|
+
|
|
99
|
+
// Clear previous panel highlight (but keep element selections)
|
|
100
|
+
const existingHighlight = document.getElementById('panel-selection-highlight');
|
|
101
|
+
if (existingHighlight) existingHighlight.remove();
|
|
102
|
+
|
|
103
|
+
const img = document.getElementById('preview-image');
|
|
104
|
+
if (!img || !img.naturalWidth || !img.naturalHeight) return;
|
|
105
|
+
|
|
106
|
+
// Ensure viewBox is set
|
|
107
|
+
overlay.setAttribute('viewBox', `0 0 ${img.naturalWidth} ${img.naturalHeight}`);
|
|
108
|
+
overlay.style.width = `${img.naturalWidth}px`;
|
|
109
|
+
overlay.style.height = `${img.naturalHeight}px`;
|
|
110
|
+
|
|
111
|
+
// Convert mm coords (upper-left origin) to image pixel coords
|
|
112
|
+
// pos.left, pos.top, pos.width, pos.height are in mm
|
|
113
|
+
const scaleX = img.naturalWidth / figSize.width_mm;
|
|
114
|
+
const scaleY = img.naturalHeight / figSize.height_mm;
|
|
115
|
+
|
|
116
|
+
const x = pos.left * scaleX;
|
|
117
|
+
const y = pos.top * scaleY; // Already in upper-left origin
|
|
118
|
+
const width = pos.width * scaleX;
|
|
119
|
+
const height = pos.height * scaleY;
|
|
120
|
+
|
|
121
|
+
// Create highlight rectangle
|
|
122
|
+
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
123
|
+
rect.id = 'panel-selection-highlight';
|
|
124
|
+
rect.setAttribute('x', x);
|
|
125
|
+
rect.setAttribute('y', y);
|
|
126
|
+
rect.setAttribute('width', width);
|
|
127
|
+
rect.setAttribute('height', height);
|
|
128
|
+
rect.setAttribute('fill', 'none');
|
|
129
|
+
rect.setAttribute('stroke', '#f59e0b');
|
|
130
|
+
rect.setAttribute('stroke-width', '3');
|
|
131
|
+
rect.setAttribute('stroke-dasharray', '8,4');
|
|
132
|
+
rect.style.pointerEvents = 'none';
|
|
133
|
+
|
|
134
|
+
overlay.appendChild(rect);
|
|
135
|
+
}
|
|
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
|
+
|
|
191
|
+
// Clear panel selection highlight
|
|
192
|
+
function clearPanelSelectionHighlight() {
|
|
193
|
+
const existingHighlight = document.getElementById('panel-selection-highlight');
|
|
194
|
+
if (existingHighlight) existingHighlight.remove();
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Select panel by index (called when clicking on axes in canvas)
|
|
198
|
+
function selectPanelByIndex(axIndex, switchToAxisTab = true) {
|
|
199
|
+
// Update the current selected panel index
|
|
200
|
+
currentSelectedPanelIndex = axIndex;
|
|
201
|
+
|
|
202
|
+
// Update inputs and highlight
|
|
203
|
+
updatePanelPositionInputs();
|
|
204
|
+
|
|
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
|
+
}
|
|
217
|
+
|
|
218
|
+
console.log('Selected panel', axIndex);
|
|
219
|
+
}
|
|
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
|
+
|
|
229
|
+
// Find panel index from axes key (e.g., "ax_0" -> 0)
|
|
230
|
+
function getPanelIndexFromKey(key) {
|
|
231
|
+
if (!key) return null;
|
|
232
|
+
|
|
233
|
+
// Handle "ax_N" format
|
|
234
|
+
const match = key.match(/ax_(\d+)/);
|
|
235
|
+
if (match) {
|
|
236
|
+
return parseInt(match[1], 10);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Handle axes type elements - find by checking bboxes
|
|
240
|
+
const axKeys = Object.keys(panelPositions).sort();
|
|
241
|
+
for (let i = 0; i < axKeys.length; i++) {
|
|
242
|
+
if (axKeys[i] === key) {
|
|
243
|
+
return i;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Apply panel position changes
|
|
251
|
+
async function applyPanelPosition() {
|
|
252
|
+
const leftInput = document.getElementById('panel_left');
|
|
253
|
+
const topInput = document.getElementById('panel_top');
|
|
254
|
+
const widthInput = document.getElementById('panel_width');
|
|
255
|
+
const heightInput = document.getElementById('panel_height');
|
|
256
|
+
|
|
257
|
+
if (currentSelectedPanelIndex === null) {
|
|
258
|
+
console.error('No panel selected');
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (!leftInput || !topInput || !widthInput || !heightInput) {
|
|
263
|
+
console.error('Panel position inputs not found');
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const axIndex = currentSelectedPanelIndex;
|
|
268
|
+
const left = parseFloat(leftInput.value);
|
|
269
|
+
const top = parseFloat(topInput.value);
|
|
270
|
+
const width = parseFloat(widthInput.value);
|
|
271
|
+
const height = parseFloat(heightInput.value);
|
|
272
|
+
|
|
273
|
+
// Validate values (mm, must be positive and within figure bounds)
|
|
274
|
+
if ([left, top, width, height].some(v => isNaN(v) || v < 0)) {
|
|
275
|
+
alert('Position values must be positive numbers in mm');
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (left + width > figSize.width_mm || top + height > figSize.height_mm) {
|
|
280
|
+
alert(`Panel extends beyond figure bounds (${figSize.width_mm.toFixed(1)} x ${figSize.height_mm.toFixed(1)} mm)`);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
document.body.classList.add('loading');
|
|
285
|
+
|
|
286
|
+
try {
|
|
287
|
+
const response = await fetch('/update_axes_position', {
|
|
288
|
+
method: 'POST',
|
|
289
|
+
headers: { 'Content-Type': 'application/json' },
|
|
290
|
+
body: JSON.stringify({
|
|
291
|
+
ax_index: axIndex,
|
|
292
|
+
left: left,
|
|
293
|
+
top: top,
|
|
294
|
+
width: width,
|
|
295
|
+
height: height
|
|
296
|
+
})
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const data = await response.json();
|
|
300
|
+
|
|
301
|
+
if (data.success) {
|
|
302
|
+
// Update preview image and wait for it to load
|
|
303
|
+
const img = document.getElementById('preview-image');
|
|
304
|
+
if (img) {
|
|
305
|
+
await new Promise((resolve) => {
|
|
306
|
+
img.onload = resolve;
|
|
307
|
+
img.src = 'data:image/png;base64,' + data.image;
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Update image size
|
|
312
|
+
if (data.img_size) {
|
|
313
|
+
currentImgWidth = data.img_size.width;
|
|
314
|
+
currentImgHeight = data.img_size.height;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Update bboxes
|
|
318
|
+
currentBboxes = data.bboxes;
|
|
319
|
+
loadHitmap();
|
|
320
|
+
updateHitRegions();
|
|
321
|
+
|
|
322
|
+
// Reload positions to reflect any adjustments (now with correct image dimensions)
|
|
323
|
+
await loadPanelPositions();
|
|
324
|
+
|
|
325
|
+
console.log('Panel position updated successfully');
|
|
326
|
+
} else {
|
|
327
|
+
alert('Failed to update position: ' + data.error);
|
|
328
|
+
}
|
|
329
|
+
} catch (error) {
|
|
330
|
+
alert('Failed to update position: ' + error.message);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
document.body.classList.remove('loading');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Initialize panel position controls
|
|
337
|
+
function initPanelPositionControls() {
|
|
338
|
+
const applyBtn = document.getElementById('apply_panel_position');
|
|
339
|
+
if (applyBtn) {
|
|
340
|
+
applyBtn.addEventListener('click', applyPanelPosition);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
// Load initial positions
|
|
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');
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Call initialization on DOMContentLoaded
|
|
458
|
+
document.addEventListener('DOMContentLoaded', initPanelPositionControls);
|
|
459
|
+
"""
|
|
460
|
+
|
|
461
|
+
__all__ = ["SCRIPTS_PANEL_POSITION"]
|
|
462
|
+
|
|
463
|
+
# EOF
|
|
@@ -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
|