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,228 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""API calls JavaScript for the figure editor.
|
|
4
|
+
|
|
5
|
+
This module contains the JavaScript code for:
|
|
6
|
+
- Preview update and refresh
|
|
7
|
+
- Form data collection
|
|
8
|
+
- Save/reset/restore operations
|
|
9
|
+
- Override status management
|
|
10
|
+
- Download functionality
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
SCRIPTS_API = """
|
|
14
|
+
// ===== API CALLS AND SERVER COMMUNICATION =====
|
|
15
|
+
|
|
16
|
+
// Schedule update with debounce
|
|
17
|
+
function scheduleUpdate() {
|
|
18
|
+
if (updateTimeout) {
|
|
19
|
+
clearTimeout(updateTimeout);
|
|
20
|
+
}
|
|
21
|
+
updateTimeout = setTimeout(updatePreview, UPDATE_DEBOUNCE);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Collect current overrides from form
|
|
25
|
+
function collectOverrides() {
|
|
26
|
+
const overrides = {};
|
|
27
|
+
|
|
28
|
+
const inputs = document.querySelectorAll('input, select');
|
|
29
|
+
inputs.forEach(input => {
|
|
30
|
+
if (input.id === 'dark-mode-toggle') return;
|
|
31
|
+
if (!input.id) return;
|
|
32
|
+
|
|
33
|
+
let value;
|
|
34
|
+
if (input.type === 'checkbox') {
|
|
35
|
+
value = input.checked;
|
|
36
|
+
} else if (input.type === 'number' || input.type === 'range') {
|
|
37
|
+
value = parseFloat(input.value);
|
|
38
|
+
if (isNaN(value)) return;
|
|
39
|
+
} else {
|
|
40
|
+
value = input.value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
overrides[input.id] = value;
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
return overrides;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Update preview
|
|
50
|
+
async function updatePreview() {
|
|
51
|
+
const overrides = collectOverrides();
|
|
52
|
+
const darkMode = document.documentElement.getAttribute('data-theme') === 'dark';
|
|
53
|
+
|
|
54
|
+
document.body.classList.add('loading');
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
const response = await fetch('/update', {
|
|
58
|
+
method: 'POST',
|
|
59
|
+
headers: { 'Content-Type': 'application/json' },
|
|
60
|
+
body: JSON.stringify({ overrides, dark_mode: darkMode })
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const data = await response.json();
|
|
64
|
+
|
|
65
|
+
// Update preview image
|
|
66
|
+
const img = document.getElementById('preview-image');
|
|
67
|
+
img.src = 'data:image/png;base64,' + data.image;
|
|
68
|
+
|
|
69
|
+
// Update dimensions for hitmap scaling
|
|
70
|
+
if (data.img_size) {
|
|
71
|
+
currentImgWidth = data.img_size.width;
|
|
72
|
+
currentImgHeight = data.img_size.height;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Update bboxes
|
|
76
|
+
currentBboxes = data.bboxes;
|
|
77
|
+
|
|
78
|
+
// Redraw selection if element still exists
|
|
79
|
+
if (selectedElement && currentBboxes[selectedElement.key]) {
|
|
80
|
+
drawSelection(selectedElement.key);
|
|
81
|
+
} else {
|
|
82
|
+
clearSelection();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Reload hitmap and redraw hit regions
|
|
86
|
+
loadHitmap();
|
|
87
|
+
updateHitRegions();
|
|
88
|
+
|
|
89
|
+
} catch (error) {
|
|
90
|
+
console.error('Update failed:', error);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
document.body.classList.remove('loading');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Reset values to initial
|
|
97
|
+
function resetValues() {
|
|
98
|
+
initializeValues();
|
|
99
|
+
updatePreview();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Save overrides
|
|
103
|
+
async function saveOverrides() {
|
|
104
|
+
const overrides = collectOverrides();
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const response = await fetch('/save', {
|
|
108
|
+
method: 'POST',
|
|
109
|
+
headers: { 'Content-Type': 'application/json' },
|
|
110
|
+
body: JSON.stringify({ overrides })
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
const data = await response.json();
|
|
114
|
+
if (data.success) {
|
|
115
|
+
if (data.has_overrides) {
|
|
116
|
+
showOverrideStatus(data.timestamp);
|
|
117
|
+
}
|
|
118
|
+
alert('Saved successfully!' + (data.path ? '\\nPath: ' + data.path : ''));
|
|
119
|
+
}
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error('Save failed:', error);
|
|
122
|
+
alert('Save failed: ' + error.message);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Download figure
|
|
127
|
+
function downloadFigure(format) {
|
|
128
|
+
window.location.href = '/download/' + format;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Restore to original programmatic style (clear manual overrides)
|
|
132
|
+
async function restoreOriginal() {
|
|
133
|
+
if (!confirm('Restore to original programmatic style? This will clear all manual overrides.')) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
document.body.classList.add('loading');
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const response = await fetch('/restore', {
|
|
141
|
+
method: 'POST',
|
|
142
|
+
headers: { 'Content-Type': 'application/json' }
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
const data = await response.json();
|
|
146
|
+
|
|
147
|
+
if (data.success) {
|
|
148
|
+
const img = document.getElementById('preview-image');
|
|
149
|
+
img.src = 'data:image/png;base64,' + data.image;
|
|
150
|
+
|
|
151
|
+
currentBboxes = data.bboxes;
|
|
152
|
+
|
|
153
|
+
// Reset form values to original style
|
|
154
|
+
if (data.original_style) {
|
|
155
|
+
for (const [key, value] of Object.entries(data.original_style)) {
|
|
156
|
+
const element = document.getElementById(key);
|
|
157
|
+
if (element) {
|
|
158
|
+
if (element.type === 'checkbox') {
|
|
159
|
+
element.checked = Boolean(value);
|
|
160
|
+
} else if (element.type === 'range') {
|
|
161
|
+
element.value = value;
|
|
162
|
+
const valueSpan = document.getElementById(key + '_value');
|
|
163
|
+
if (valueSpan) valueSpan.textContent = value;
|
|
164
|
+
} else {
|
|
165
|
+
element.value = value;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
clearSelection();
|
|
172
|
+
hideOverrideStatus();
|
|
173
|
+
loadHitmap();
|
|
174
|
+
}
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error('Restore failed:', error);
|
|
177
|
+
alert('Restore failed: ' + error.message);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
document.body.classList.remove('loading');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Check and display override status
|
|
184
|
+
async function checkOverrideStatus() {
|
|
185
|
+
try {
|
|
186
|
+
const response = await fetch('/style');
|
|
187
|
+
const data = await response.json();
|
|
188
|
+
|
|
189
|
+
if (data.has_overrides) {
|
|
190
|
+
showOverrideStatus(data.manual_timestamp);
|
|
191
|
+
} else {
|
|
192
|
+
hideOverrideStatus();
|
|
193
|
+
}
|
|
194
|
+
} catch (error) {
|
|
195
|
+
console.error('Failed to check override status:', error);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Show override status indicator
|
|
200
|
+
function showOverrideStatus(timestamp) {
|
|
201
|
+
const statusEl = document.getElementById('override-status');
|
|
202
|
+
const timestampEl = document.getElementById('override-timestamp');
|
|
203
|
+
|
|
204
|
+
if (statusEl) statusEl.style.display = 'flex';
|
|
205
|
+
|
|
206
|
+
if (timestampEl && timestamp) {
|
|
207
|
+
const date = new Date(timestamp);
|
|
208
|
+
timestampEl.textContent = 'Last modified: ' + date.toLocaleString();
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Hide override status indicator
|
|
213
|
+
function hideOverrideStatus() {
|
|
214
|
+
const statusEl = document.getElementById('override-status');
|
|
215
|
+
if (statusEl) statusEl.style.display = 'none';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Update override status after save
|
|
219
|
+
async function updateOverrideStatusAfterSave(data) {
|
|
220
|
+
if (data.has_overrides) {
|
|
221
|
+
showOverrideStatus(data.timestamp);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
"""
|
|
225
|
+
|
|
226
|
+
__all__ = ["SCRIPTS_API"]
|
|
227
|
+
|
|
228
|
+
# EOF
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Canvas right-click context menu JavaScript."""
|
|
4
|
+
|
|
5
|
+
JS_CANVAS_CONTEXT_MENU = """
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Canvas Context Menu (Right-Click Menu)
|
|
8
|
+
// ============================================================================
|
|
9
|
+
let canvasContextMenu = null;
|
|
10
|
+
|
|
11
|
+
function createCanvasContextMenu() {
|
|
12
|
+
if (canvasContextMenu) return;
|
|
13
|
+
|
|
14
|
+
canvasContextMenu = document.createElement('div');
|
|
15
|
+
canvasContextMenu.className = 'canvas-context-menu';
|
|
16
|
+
canvasContextMenu.style.display = 'none';
|
|
17
|
+
canvasContextMenu.innerHTML = `
|
|
18
|
+
<div class="context-menu-item" data-action="copy-image">
|
|
19
|
+
Copy image<span class="shortcut">Ctrl+C</span>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="context-menu-item" data-action="download-png">
|
|
22
|
+
Download PNG
|
|
23
|
+
</div>
|
|
24
|
+
<div class="context-menu-item" data-action="download-svg">
|
|
25
|
+
Download SVG
|
|
26
|
+
</div>
|
|
27
|
+
<div class="context-menu-item" data-action="download-pdf">
|
|
28
|
+
Download PDF
|
|
29
|
+
</div>
|
|
30
|
+
<div class="context-menu-divider"></div>
|
|
31
|
+
<div class="context-menu-item" data-action="zoom-fit">
|
|
32
|
+
Fit to view<span class="shortcut">F</span>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="context-menu-item" data-action="zoom-100">
|
|
35
|
+
Zoom 100%<span class="shortcut">0</span>
|
|
36
|
+
</div>
|
|
37
|
+
<div class="context-menu-divider"></div>
|
|
38
|
+
<div class="context-menu-item" data-action="render">
|
|
39
|
+
Re-render<span class="shortcut">R</span>
|
|
40
|
+
</div>
|
|
41
|
+
<div class="context-menu-item" data-action="toggle-grid">
|
|
42
|
+
Toggle grid<span class="shortcut">G</span>
|
|
43
|
+
</div>
|
|
44
|
+
`;
|
|
45
|
+
document.body.appendChild(canvasContextMenu);
|
|
46
|
+
setupCanvasContextMenuListeners();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function setupCanvasContextMenuListeners() {
|
|
50
|
+
if (!canvasContextMenu) return;
|
|
51
|
+
|
|
52
|
+
// Click on menu items
|
|
53
|
+
canvasContextMenu.querySelectorAll('.context-menu-item').forEach(item => {
|
|
54
|
+
item.addEventListener('click', (e) => {
|
|
55
|
+
e.stopPropagation();
|
|
56
|
+
const action = item.dataset.action;
|
|
57
|
+
handleCanvasContextMenuAction(action);
|
|
58
|
+
hideCanvasContextMenu();
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Hide on click outside
|
|
63
|
+
document.addEventListener('click', hideCanvasContextMenu);
|
|
64
|
+
document.addEventListener('scroll', hideCanvasContextMenu, true);
|
|
65
|
+
document.addEventListener('keydown', (e) => {
|
|
66
|
+
if (e.key === 'Escape') hideCanvasContextMenu();
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function handleCanvasContextMenuAction(action) {
|
|
71
|
+
switch (action) {
|
|
72
|
+
case 'copy-image':
|
|
73
|
+
copyCanvasImage();
|
|
74
|
+
break;
|
|
75
|
+
case 'download-png':
|
|
76
|
+
document.getElementById('btn-download-png-menu')?.click();
|
|
77
|
+
break;
|
|
78
|
+
case 'download-svg':
|
|
79
|
+
document.getElementById('btn-download-svg-menu')?.click();
|
|
80
|
+
break;
|
|
81
|
+
case 'download-pdf':
|
|
82
|
+
document.getElementById('btn-download-pdf-menu')?.click();
|
|
83
|
+
break;
|
|
84
|
+
case 'zoom-fit':
|
|
85
|
+
zoomToFit();
|
|
86
|
+
break;
|
|
87
|
+
case 'zoom-100':
|
|
88
|
+
setZoom(1.0);
|
|
89
|
+
break;
|
|
90
|
+
case 'render':
|
|
91
|
+
document.getElementById('btn-refresh')?.click();
|
|
92
|
+
break;
|
|
93
|
+
case 'toggle-grid':
|
|
94
|
+
document.getElementById('btn-ruler-grid')?.click();
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function copyCanvasImage() {
|
|
100
|
+
const img = document.getElementById('preview-image');
|
|
101
|
+
if (!img) return;
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
// Fetch the image and convert to blob
|
|
105
|
+
const response = await fetch(img.src);
|
|
106
|
+
const blob = await response.blob();
|
|
107
|
+
await navigator.clipboard.write([
|
|
108
|
+
new ClipboardItem({ 'image/png': blob })
|
|
109
|
+
]);
|
|
110
|
+
showToast('Image copied to clipboard');
|
|
111
|
+
} catch (err) {
|
|
112
|
+
console.error('Failed to copy image:', err);
|
|
113
|
+
showToast('Failed to copy image', 'error');
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function showCanvasContextMenu(e) {
|
|
118
|
+
if (!canvasContextMenu) createCanvasContextMenu();
|
|
119
|
+
|
|
120
|
+
e.preventDefault();
|
|
121
|
+
e.stopPropagation();
|
|
122
|
+
|
|
123
|
+
const x = e.clientX;
|
|
124
|
+
const y = e.clientY;
|
|
125
|
+
|
|
126
|
+
// Position off-screen to measure
|
|
127
|
+
canvasContextMenu.style.left = '-9999px';
|
|
128
|
+
canvasContextMenu.style.top = '-9999px';
|
|
129
|
+
canvasContextMenu.style.display = 'block';
|
|
130
|
+
|
|
131
|
+
const menuWidth = canvasContextMenu.offsetWidth;
|
|
132
|
+
const menuHeight = canvasContextMenu.offsetHeight;
|
|
133
|
+
|
|
134
|
+
// Adjust position to fit in viewport
|
|
135
|
+
let left = x;
|
|
136
|
+
let top = y;
|
|
137
|
+
if (x + menuWidth > window.innerWidth - 10) {
|
|
138
|
+
left = x - menuWidth;
|
|
139
|
+
}
|
|
140
|
+
if (y + menuHeight > window.innerHeight - 10) {
|
|
141
|
+
top = y - menuHeight;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
canvasContextMenu.style.left = `${Math.max(10, left)}px`;
|
|
145
|
+
canvasContextMenu.style.top = `${Math.max(10, top)}px`;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function hideCanvasContextMenu() {
|
|
149
|
+
if (canvasContextMenu) {
|
|
150
|
+
canvasContextMenu.style.display = 'none';
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Simple toast notification
|
|
155
|
+
function showToast(message, type = 'success') {
|
|
156
|
+
let toast = document.getElementById('toast-notification');
|
|
157
|
+
if (!toast) {
|
|
158
|
+
toast = document.createElement('div');
|
|
159
|
+
toast.id = 'toast-notification';
|
|
160
|
+
toast.className = 'toast-notification';
|
|
161
|
+
document.body.appendChild(toast);
|
|
162
|
+
}
|
|
163
|
+
toast.textContent = message;
|
|
164
|
+
toast.className = `toast-notification ${type}`;
|
|
165
|
+
toast.style.display = 'block';
|
|
166
|
+
setTimeout(() => {
|
|
167
|
+
toast.style.display = 'none';
|
|
168
|
+
}, 2000);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Initialize canvas context menu
|
|
172
|
+
function initializeCanvasContextMenu() {
|
|
173
|
+
const wrapper = document.getElementById('preview-wrapper');
|
|
174
|
+
if (wrapper) {
|
|
175
|
+
wrapper.addEventListener('contextmenu', showCanvasContextMenu);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
"""
|
|
179
|
+
|
|
180
|
+
__all__ = ["JS_CANVAS_CONTEXT_MENU"]
|
|
181
|
+
|
|
182
|
+
# EOF
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Caption controls JavaScript for the figure editor.
|
|
4
|
+
|
|
5
|
+
This module contains the JavaScript code for:
|
|
6
|
+
- Scientific figure captions (Fig. 1. Description...)
|
|
7
|
+
- Panel captions for multi-panel figures
|
|
8
|
+
- Caption preview and backend synchronization
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
SCRIPTS_CAPTIONS = """
|
|
12
|
+
// ===== CAPTION CONTROLS (Scientific Figure Captions) =====
|
|
13
|
+
|
|
14
|
+
// Initialize caption input event handlers
|
|
15
|
+
function initializeCaptionInputs() {
|
|
16
|
+
const figNumInput = document.getElementById('caption_figure_number');
|
|
17
|
+
const figTextInput = document.getElementById('caption_figure_text');
|
|
18
|
+
const panelTextInput = document.getElementById('caption_panel_text');
|
|
19
|
+
const previewEl = document.getElementById('caption-preview-text');
|
|
20
|
+
|
|
21
|
+
// Update caption preview when figure number changes
|
|
22
|
+
if (figNumInput) {
|
|
23
|
+
figNumInput.addEventListener('input', function() {
|
|
24
|
+
updateCaptionPreview();
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Update caption preview and send to backend when caption text changes
|
|
29
|
+
if (figTextInput) {
|
|
30
|
+
let timeout;
|
|
31
|
+
figTextInput.addEventListener('input', function() {
|
|
32
|
+
updateCaptionPreview();
|
|
33
|
+
clearTimeout(timeout);
|
|
34
|
+
timeout = setTimeout(() => {
|
|
35
|
+
updateFigureCaption();
|
|
36
|
+
}, UPDATE_DEBOUNCE);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Update panel caption on input
|
|
41
|
+
if (panelTextInput) {
|
|
42
|
+
let timeout;
|
|
43
|
+
panelTextInput.addEventListener('input', function() {
|
|
44
|
+
clearTimeout(timeout);
|
|
45
|
+
timeout = setTimeout(() => {
|
|
46
|
+
updatePanelCaption();
|
|
47
|
+
}, UPDATE_DEBOUNCE);
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Initial preview update
|
|
52
|
+
updateCaptionPreview();
|
|
53
|
+
loadCaptions();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Update the caption preview text (composed caption in both properties panel and canvas pane)
|
|
57
|
+
function updateCaptionPreview() {
|
|
58
|
+
const figNumInput = document.getElementById('caption_figure_number');
|
|
59
|
+
const figTextInput = document.getElementById('caption_figure_text');
|
|
60
|
+
const composedEl = document.getElementById('composed-caption-text');
|
|
61
|
+
const canvasCaptionEl = document.getElementById('canvas-caption-text');
|
|
62
|
+
|
|
63
|
+
const figNum = figNumInput?.value || '1';
|
|
64
|
+
const figText = figTextInput?.value || '';
|
|
65
|
+
|
|
66
|
+
// Build composed caption HTML
|
|
67
|
+
let html = `<b>Fig. ${figNum}.</b>`;
|
|
68
|
+
if (figText) {
|
|
69
|
+
html += ` ${figText}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Add panel captions if available
|
|
73
|
+
const panelCaptions = getPanelCaptions();
|
|
74
|
+
if (panelCaptions.length > 0) {
|
|
75
|
+
const panelHtml = panelCaptions
|
|
76
|
+
.map((pc, i) => pc ? `<span class="panel-caption">(${String.fromCharCode(65 + i)}) ${pc}</span>` : '')
|
|
77
|
+
.filter(s => s) // Filter empty strings
|
|
78
|
+
.join(' ');
|
|
79
|
+
if (panelHtml) {
|
|
80
|
+
html += ' ' + panelHtml;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Update both preview locations
|
|
85
|
+
if (composedEl) composedEl.innerHTML = html;
|
|
86
|
+
if (canvasCaptionEl) canvasCaptionEl.innerHTML = html;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Get all panel captions from stored state
|
|
90
|
+
function getPanelCaptions() {
|
|
91
|
+
// Start with loaded panel captions from server
|
|
92
|
+
const captions = [...loadedPanelCaptions];
|
|
93
|
+
|
|
94
|
+
// Check for UI overrides
|
|
95
|
+
for (let i = 0; i < 9; i++) { // Support up to 9 panels (A-I)
|
|
96
|
+
const input = document.querySelector(`[data-panel-caption="${i}"]`);
|
|
97
|
+
if (input && input.value) {
|
|
98
|
+
captions[i] = input.value;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Also check current panel caption input
|
|
102
|
+
const currentPanel = document.getElementById('caption_panel_text');
|
|
103
|
+
if (currentPanel && currentPanel.value && selectedElement?.ax_index !== undefined) {
|
|
104
|
+
captions[selectedElement.ax_index] = currentPanel.value;
|
|
105
|
+
}
|
|
106
|
+
return captions; // Keep all entries (including empty) for proper indexing
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Store loaded panel captions globally
|
|
110
|
+
let loadedPanelCaptions = [];
|
|
111
|
+
|
|
112
|
+
// Update panel caption input when panel is selected
|
|
113
|
+
function updatePanelCaptionInput(axIndex) {
|
|
114
|
+
const panelTextInput = document.getElementById('caption_panel_text');
|
|
115
|
+
if (!panelTextInput) return;
|
|
116
|
+
|
|
117
|
+
// Get caption for this panel index
|
|
118
|
+
const caption = loadedPanelCaptions[axIndex] || '';
|
|
119
|
+
panelTextInput.value = caption;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Load existing captions from server
|
|
123
|
+
async function loadCaptions() {
|
|
124
|
+
try {
|
|
125
|
+
const response = await fetch('/get_captions');
|
|
126
|
+
const data = await response.json();
|
|
127
|
+
|
|
128
|
+
if (data.figure_number) {
|
|
129
|
+
const figNumInput = document.getElementById('caption_figure_number');
|
|
130
|
+
if (figNumInput) figNumInput.value = data.figure_number;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (data.figure_caption) {
|
|
134
|
+
const figTextInput = document.getElementById('caption_figure_text');
|
|
135
|
+
if (figTextInput) figTextInput.value = data.figure_caption;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Store panel captions for composed caption
|
|
139
|
+
if (data.panel_captions && Array.isArray(data.panel_captions)) {
|
|
140
|
+
loadedPanelCaptions = data.panel_captions;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
updateCaptionPreview();
|
|
144
|
+
console.log('Loaded captions:', data);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.log('Captions not loaded (endpoint may not exist yet):', error.message);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Update figure caption on server
|
|
151
|
+
async function updateFigureCaption() {
|
|
152
|
+
const figNumInput = document.getElementById('caption_figure_number');
|
|
153
|
+
const figTextInput = document.getElementById('caption_figure_text');
|
|
154
|
+
|
|
155
|
+
const figNum = parseInt(figNumInput?.value) || 1;
|
|
156
|
+
const figText = figTextInput?.value || '';
|
|
157
|
+
|
|
158
|
+
console.log(`Updating figure caption: Fig. ${figNum}. ${figText}`);
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
const response = await fetch('/update_caption', {
|
|
162
|
+
method: 'POST',
|
|
163
|
+
headers: { 'Content-Type': 'application/json' },
|
|
164
|
+
body: JSON.stringify({
|
|
165
|
+
type: 'figure',
|
|
166
|
+
figure_number: figNum,
|
|
167
|
+
text: figText
|
|
168
|
+
})
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
const data = await response.json();
|
|
172
|
+
|
|
173
|
+
if (data.success) {
|
|
174
|
+
console.log('Figure caption updated');
|
|
175
|
+
} else {
|
|
176
|
+
console.error('Figure caption update failed:', data.error);
|
|
177
|
+
}
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error('Figure caption update failed:', error);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Update panel caption on server
|
|
184
|
+
async function updatePanelCaption() {
|
|
185
|
+
const panelTextInput = document.getElementById('caption_panel_text');
|
|
186
|
+
const panelText = panelTextInput?.value || '';
|
|
187
|
+
|
|
188
|
+
// Get current panel index - prefer currentSelectedPanelIndex, fallback to selectedElement
|
|
189
|
+
let panelIndex = 0;
|
|
190
|
+
if (typeof currentSelectedPanelIndex !== 'undefined' && currentSelectedPanelIndex !== null) {
|
|
191
|
+
panelIndex = currentSelectedPanelIndex;
|
|
192
|
+
} else if (selectedElement && selectedElement.ax_index !== undefined) {
|
|
193
|
+
panelIndex = selectedElement.ax_index;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Update local cache
|
|
197
|
+
while (loadedPanelCaptions.length <= panelIndex) {
|
|
198
|
+
loadedPanelCaptions.push('');
|
|
199
|
+
}
|
|
200
|
+
loadedPanelCaptions[panelIndex] = panelText;
|
|
201
|
+
|
|
202
|
+
console.log(`Updating panel ${panelIndex} caption: ${panelText}`);
|
|
203
|
+
|
|
204
|
+
try {
|
|
205
|
+
const response = await fetch('/update_caption', {
|
|
206
|
+
method: 'POST',
|
|
207
|
+
headers: { 'Content-Type': 'application/json' },
|
|
208
|
+
body: JSON.stringify({
|
|
209
|
+
type: 'panel',
|
|
210
|
+
panel_index: panelIndex,
|
|
211
|
+
text: panelText
|
|
212
|
+
})
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
const data = await response.json();
|
|
216
|
+
|
|
217
|
+
if (data.success) {
|
|
218
|
+
console.log('Panel caption updated');
|
|
219
|
+
updateCaptionPreview(); // Update composed caption preview
|
|
220
|
+
} else {
|
|
221
|
+
console.error('Panel caption update failed:', data.error);
|
|
222
|
+
}
|
|
223
|
+
} catch (error) {
|
|
224
|
+
console.error('Panel caption update failed:', error);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
"""
|
|
228
|
+
|
|
229
|
+
__all__ = ["SCRIPTS_CAPTIONS"]
|
|
230
|
+
|
|
231
|
+
# EOF
|