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,493 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Core state, initialization, and utility JavaScript for the figure editor."""
|
|
4
|
+
|
|
5
|
+
SCRIPTS_CORE = """
|
|
6
|
+
// ==================== CORE STATE & INITIALIZATION ====================
|
|
7
|
+
|
|
8
|
+
// Debug mode - enabled via FIGRECIPE_DEBUG_MODE=1 env var
|
|
9
|
+
const DEBUG_MODE = DEBUG_MODE_PLACEHOLDER;
|
|
10
|
+
|
|
11
|
+
// State
|
|
12
|
+
let currentBboxes = initialBboxes;
|
|
13
|
+
let colorMap = initialColorMap;
|
|
14
|
+
let callsData = {}; // Recorded calls with signatures
|
|
15
|
+
let selectedElement = null;
|
|
16
|
+
let hitmapLoaded = false;
|
|
17
|
+
let hitmapCtx = null;
|
|
18
|
+
let hitmapImg = null;
|
|
19
|
+
let updateTimeout = null;
|
|
20
|
+
let currentImgWidth = imgWidth; // Track current preview dimensions
|
|
21
|
+
let currentImgHeight = imgHeight;
|
|
22
|
+
let hitmapVisible = false; // Hitmap overlay visibility (hover-only by default)
|
|
23
|
+
const UPDATE_DEBOUNCE = 500; // ms
|
|
24
|
+
|
|
25
|
+
// Overlapping element cycling state
|
|
26
|
+
let lastClickPosition = null;
|
|
27
|
+
let overlappingElements = [];
|
|
28
|
+
let cycleIndex = 0;
|
|
29
|
+
let hoveredElement = null; // Track currently hovered element for click priority
|
|
30
|
+
|
|
31
|
+
// View mode: 'all' shows all properties, 'selected' shows only element-specific
|
|
32
|
+
let viewMode = 'all';
|
|
33
|
+
|
|
34
|
+
// Zoom/Pan state
|
|
35
|
+
let zoomLevel = 1.0;
|
|
36
|
+
const ZOOM_MIN = 0.1;
|
|
37
|
+
const ZOOM_MAX = 5.0;
|
|
38
|
+
const ZOOM_STEP = 0.25;
|
|
39
|
+
let isPanning = false;
|
|
40
|
+
let panTarget = null; // Current scrollable element being panned
|
|
41
|
+
let panStartX = 0;
|
|
42
|
+
let panStartY = 0;
|
|
43
|
+
let scrollStartX = 0;
|
|
44
|
+
let scrollStartY = 0;
|
|
45
|
+
|
|
46
|
+
// Initialize
|
|
47
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
48
|
+
initializeValues();
|
|
49
|
+
initializeEventListeners();
|
|
50
|
+
loadHitmap();
|
|
51
|
+
loadLabels(); // Load current axis labels
|
|
52
|
+
|
|
53
|
+
// Update hit regions on window resize
|
|
54
|
+
window.addEventListener('resize', updateHitRegions);
|
|
55
|
+
|
|
56
|
+
// Update hit regions and overlays when preview image loads
|
|
57
|
+
const previewImg = document.getElementById('preview-image');
|
|
58
|
+
previewImg.addEventListener('load', updateHitRegions);
|
|
59
|
+
previewImg.addEventListener('load', updateOverlays);
|
|
60
|
+
|
|
61
|
+
// Initialize hit regions visibility state
|
|
62
|
+
const overlay = document.getElementById('hitregion-overlay');
|
|
63
|
+
const btn = document.getElementById('btn-show-hitmap');
|
|
64
|
+
|
|
65
|
+
if (hitmapVisible) {
|
|
66
|
+
if (overlay) overlay.classList.add('visible');
|
|
67
|
+
if (btn) {
|
|
68
|
+
btn.classList.add('active');
|
|
69
|
+
btn.textContent = 'Hide Hit Regions';
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
// Hover-only mode when hidden
|
|
73
|
+
if (overlay) overlay.classList.add('hover-mode');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Draw hit regions - handle both already-loaded and loading images
|
|
77
|
+
function initHitRegions() {
|
|
78
|
+
if (previewImg.complete && previewImg.naturalWidth > 0) {
|
|
79
|
+
console.log('Image already loaded, drawing hit regions');
|
|
80
|
+
drawHitRegions();
|
|
81
|
+
} else {
|
|
82
|
+
console.log('Image not loaded yet, waiting...');
|
|
83
|
+
setTimeout(initHitRegions, 100);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
setTimeout(initHitRegions, 50);
|
|
87
|
+
|
|
88
|
+
// Initialize zoom/pan
|
|
89
|
+
initializeZoomPan();
|
|
90
|
+
|
|
91
|
+
// Initialize measurement overlay controls
|
|
92
|
+
initializeOverlayControls();
|
|
93
|
+
|
|
94
|
+
// Initialize context menus
|
|
95
|
+
if (typeof initializeCanvasContextMenu === 'function') initializeCanvasContextMenu();
|
|
96
|
+
if (typeof initializeFilesContextMenu === 'function') initializeFilesContextMenu();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Theme values are passed from server via initialValues
|
|
100
|
+
// These come from the applied theme (SCITEX, MATPLOTLIB, etc.)
|
|
101
|
+
// initialValues is populated by the server from the loaded style preset
|
|
102
|
+
|
|
103
|
+
// Store original theme defaults for comparison
|
|
104
|
+
const themeDefaults = {...initialValues};
|
|
105
|
+
|
|
106
|
+
// Initialize form values and placeholders from applied theme
|
|
107
|
+
function initializeValues() {
|
|
108
|
+
// initialValues contains the theme's default values from the server
|
|
109
|
+
// These are the actual values from the applied style preset (not hardcoded)
|
|
110
|
+
|
|
111
|
+
for (const [key, value] of Object.entries(initialValues)) {
|
|
112
|
+
const element = document.getElementById(key);
|
|
113
|
+
if (element) {
|
|
114
|
+
if (element.type === 'checkbox') {
|
|
115
|
+
element.checked = Boolean(value);
|
|
116
|
+
} else if (element.type === 'range') {
|
|
117
|
+
element.value = value;
|
|
118
|
+
const valueSpan = document.getElementById(key + '_value');
|
|
119
|
+
if (valueSpan) valueSpan.textContent = value;
|
|
120
|
+
} else {
|
|
121
|
+
// Set the value
|
|
122
|
+
element.value = value;
|
|
123
|
+
// Set placeholder to show theme default (visible when field is cleared)
|
|
124
|
+
if (element.type === 'number' || element.type === 'text') {
|
|
125
|
+
element.placeholder = value;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Log applied theme info
|
|
132
|
+
const styleNameEl = document.getElementById('style-name');
|
|
133
|
+
if (styleNameEl) {
|
|
134
|
+
console.log('Applied theme:', styleNameEl.textContent);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Check if a field value differs from the theme default
|
|
139
|
+
function updateModifiedState(element) {
|
|
140
|
+
const key = element.id;
|
|
141
|
+
const defaultValue = themeDefaults[key];
|
|
142
|
+
const formRow = element.closest('.form-row');
|
|
143
|
+
if (!formRow || defaultValue === undefined) return;
|
|
144
|
+
|
|
145
|
+
let currentValue;
|
|
146
|
+
if (element.type === 'checkbox') {
|
|
147
|
+
currentValue = element.checked;
|
|
148
|
+
} else if (element.type === 'number') {
|
|
149
|
+
currentValue = parseFloat(element.value);
|
|
150
|
+
} else {
|
|
151
|
+
currentValue = element.value;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Compare values (handle type conversion)
|
|
155
|
+
const isModified = String(currentValue) !== String(defaultValue);
|
|
156
|
+
formRow.classList.toggle('value-modified', isModified);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Update all modified states
|
|
160
|
+
function updateAllModifiedStates() {
|
|
161
|
+
const inputs = document.querySelectorAll('input, select');
|
|
162
|
+
inputs.forEach(input => {
|
|
163
|
+
if (input.id && input.id !== 'dark-mode-toggle') {
|
|
164
|
+
updateModifiedState(input);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ==================== EVENT LISTENERS ====================
|
|
170
|
+
|
|
171
|
+
// Initialize event listeners
|
|
172
|
+
function initializeEventListeners() {
|
|
173
|
+
// Preview image click for element selection
|
|
174
|
+
const previewImg = document.getElementById('preview-image');
|
|
175
|
+
if (previewImg) previewImg.addEventListener('click', handlePreviewClick);
|
|
176
|
+
|
|
177
|
+
// SVG overlay click - deselect when clicking on empty area (not on a shape)
|
|
178
|
+
const hitregionOverlay = document.getElementById('hitregion-overlay');
|
|
179
|
+
if (hitregionOverlay) {
|
|
180
|
+
hitregionOverlay.addEventListener('click', function(event) {
|
|
181
|
+
if (event.target === hitregionOverlay) clearSelection();
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Selection overlay click - same behavior
|
|
186
|
+
const selectionOverlay = document.getElementById('selection-overlay');
|
|
187
|
+
if (selectionOverlay) {
|
|
188
|
+
selectionOverlay.addEventListener('click', function(event) {
|
|
189
|
+
if (event.target === selectionOverlay) clearSelection();
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Dark mode toggle button
|
|
194
|
+
const darkModeToggle = document.getElementById('dark-mode-toggle');
|
|
195
|
+
if (darkModeToggle) {
|
|
196
|
+
const updateThemeIcon = (theme) => { darkModeToggle.textContent = theme === 'dark' ? '🌙' : '☀️'; };
|
|
197
|
+
darkModeToggle.addEventListener('click', function() {
|
|
198
|
+
const currentTheme = document.documentElement.getAttribute('data-theme');
|
|
199
|
+
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
|
|
200
|
+
document.documentElement.setAttribute('data-theme', newTheme);
|
|
201
|
+
updateThemeIcon(newTheme);
|
|
202
|
+
scheduleUpdate();
|
|
203
|
+
});
|
|
204
|
+
updateThemeIcon(document.documentElement.getAttribute('data-theme'));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Form inputs - auto update on change
|
|
208
|
+
// Exclude panel position inputs - they have their own Apply button
|
|
209
|
+
const panelPositionInputIds = ['panel_left', 'panel_top', 'panel_width', 'panel_height'];
|
|
210
|
+
const inputs = document.querySelectorAll('input, select');
|
|
211
|
+
inputs.forEach(input => {
|
|
212
|
+
if (panelPositionInputIds.includes(input.id)) return; // Skip panel position inputs
|
|
213
|
+
|
|
214
|
+
// Update modified state and trigger preview update
|
|
215
|
+
input.addEventListener('change', function() {
|
|
216
|
+
updateModifiedState(this);
|
|
217
|
+
scheduleUpdate();
|
|
218
|
+
});
|
|
219
|
+
if (input.type === 'number' || input.type === 'text') {
|
|
220
|
+
input.addEventListener('input', function() {
|
|
221
|
+
updateModifiedState(this);
|
|
222
|
+
scheduleUpdate();
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Range slider value display
|
|
227
|
+
if (input.type === 'range') {
|
|
228
|
+
input.addEventListener('input', function() {
|
|
229
|
+
const valueSpan = document.getElementById(this.id + '_value');
|
|
230
|
+
if (valueSpan) valueSpan.textContent = this.value;
|
|
231
|
+
updateModifiedState(this);
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// Buttons - with null checks
|
|
237
|
+
const btnRefresh = document.getElementById('btn-refresh');
|
|
238
|
+
const btnReset = document.getElementById('btn-reset');
|
|
239
|
+
const btnSave = document.getElementById('btn-save');
|
|
240
|
+
const btnRestore = document.getElementById('btn-restore');
|
|
241
|
+
if (btnRefresh) btnRefresh.addEventListener('click', updatePreview);
|
|
242
|
+
if (btnReset) btnReset.addEventListener('click', resetValues);
|
|
243
|
+
if (btnSave) btnSave.addEventListener('click', saveOverrides);
|
|
244
|
+
if (btnRestore) btnRestore.addEventListener('click', restoreOriginal);
|
|
245
|
+
const hitmapBtn = document.getElementById('btn-show-hitmap');
|
|
246
|
+
if (hitmapBtn) hitmapBtn.addEventListener('click', toggleHitmapOverlay);
|
|
247
|
+
|
|
248
|
+
// Download dropdown, label inputs, and captions
|
|
249
|
+
initializeDownloadDropdown();
|
|
250
|
+
initializeLabelInputs();
|
|
251
|
+
if (typeof initializeCaptionInputs === 'function') initializeCaptionInputs();
|
|
252
|
+
|
|
253
|
+
// View mode toggle buttons (legacy)
|
|
254
|
+
const btnAll = document.getElementById('btn-show-all');
|
|
255
|
+
const btnSelected = document.getElementById('btn-show-selected');
|
|
256
|
+
if (btnAll) btnAll.addEventListener('click', () => setViewMode('all'));
|
|
257
|
+
if (btnSelected) btnSelected.addEventListener('click', () => setViewMode('selected'));
|
|
258
|
+
|
|
259
|
+
// Tab navigation
|
|
260
|
+
const tabFigure = document.getElementById('tab-figure');
|
|
261
|
+
const tabAxis = document.getElementById('tab-axis');
|
|
262
|
+
const tabElement = document.getElementById('tab-element');
|
|
263
|
+
if (tabFigure) tabFigure.addEventListener('click', () => switchTab('figure'));
|
|
264
|
+
if (tabAxis) tabAxis.addEventListener('click', () => switchTab('axis'));
|
|
265
|
+
if (tabElement) tabElement.addEventListener('click', () => switchTab('element'));
|
|
266
|
+
|
|
267
|
+
// Theme modal handlers
|
|
268
|
+
initializeThemeModal();
|
|
269
|
+
initializeShortcutsModal();
|
|
270
|
+
|
|
271
|
+
// Check initial override status
|
|
272
|
+
checkOverrideStatus();
|
|
273
|
+
|
|
274
|
+
// Check modified states after initial values are set
|
|
275
|
+
setTimeout(updateAllModifiedStates, 100);
|
|
276
|
+
|
|
277
|
+
// Keyboard shortcuts
|
|
278
|
+
document.addEventListener('keydown', handleKeyboardShortcuts);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// Handle keyboard shortcuts
|
|
282
|
+
function handleKeyboardShortcuts(event) {
|
|
283
|
+
// Ignore shortcuts when typing in input fields
|
|
284
|
+
const activeElement = document.activeElement;
|
|
285
|
+
const isInputField = activeElement.tagName === 'INPUT' ||
|
|
286
|
+
activeElement.tagName === 'TEXTAREA' ||
|
|
287
|
+
activeElement.tagName === 'SELECT';
|
|
288
|
+
|
|
289
|
+
// Ctrl+Alt+I: Debug snapshot (screenshot + console logs)
|
|
290
|
+
if (event.ctrlKey && event.altKey && (event.key === 'i' || event.key === 'I')) {
|
|
291
|
+
event.preventDefault();
|
|
292
|
+
event.stopPropagation();
|
|
293
|
+
console.log('[DEBUG] Ctrl+Alt+I pressed, calling captureDebugSnapshot');
|
|
294
|
+
if (typeof captureDebugSnapshot === 'function') {
|
|
295
|
+
captureDebugSnapshot();
|
|
296
|
+
} else {
|
|
297
|
+
console.error('[DEBUG] captureDebugSnapshot is not defined!');
|
|
298
|
+
showToast('Debug snapshot not available', 'error');
|
|
299
|
+
}
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Alt+I (without Ctrl): Element Inspector toggle (DEBUG MODE ONLY)
|
|
304
|
+
if (DEBUG_MODE && event.altKey && !event.ctrlKey && !event.shiftKey && (event.key === 'i' || event.key === 'I')) {
|
|
305
|
+
event.preventDefault();
|
|
306
|
+
event.stopPropagation();
|
|
307
|
+
if (typeof toggleElementInspector === 'function') {
|
|
308
|
+
toggleElementInspector();
|
|
309
|
+
}
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Alt+B: Show All Bboxes toggle (DEBUG MODE ONLY)
|
|
314
|
+
if (DEBUG_MODE && event.altKey && !event.ctrlKey && !event.shiftKey && (event.key === 'b' || event.key === 'B')) {
|
|
315
|
+
event.preventDefault();
|
|
316
|
+
event.stopPropagation();
|
|
317
|
+
if (typeof toggleAllBboxes === 'function') {
|
|
318
|
+
toggleAllBboxes();
|
|
319
|
+
}
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Ctrl+S: Save overrides
|
|
324
|
+
if (event.ctrlKey && event.key === 's') {
|
|
325
|
+
event.preventDefault();
|
|
326
|
+
saveOverrides();
|
|
327
|
+
showToast('Saved!', 'success');
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Ctrl+N: New blank figure
|
|
332
|
+
if (event.ctrlKey && event.key === 'n') {
|
|
333
|
+
event.preventDefault();
|
|
334
|
+
if (typeof createNewFigure === 'function') {
|
|
335
|
+
createNewFigure();
|
|
336
|
+
}
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Ctrl+Z: Undo
|
|
341
|
+
if (event.ctrlKey && !event.shiftKey && event.key === 'z') {
|
|
342
|
+
event.preventDefault();
|
|
343
|
+
if (typeof undo === 'function') {
|
|
344
|
+
undo();
|
|
345
|
+
}
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Ctrl+Shift+Z or Ctrl+Y: Redo
|
|
350
|
+
if ((event.ctrlKey && event.shiftKey && event.key === 'Z') ||
|
|
351
|
+
(event.ctrlKey && event.key === 'y')) {
|
|
352
|
+
event.preventDefault();
|
|
353
|
+
if (typeof redo === 'function') {
|
|
354
|
+
redo();
|
|
355
|
+
}
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Ctrl+Shift+S: Download PNG
|
|
360
|
+
if (event.ctrlKey && event.shiftKey && event.key === 'S') {
|
|
361
|
+
event.preventDefault();
|
|
362
|
+
downloadFigure('png');
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
// F5 or Ctrl+R: Render preview
|
|
367
|
+
if (event.key === 'F5' || (event.ctrlKey && event.key === 'r')) {
|
|
368
|
+
event.preventDefault();
|
|
369
|
+
updatePreview();
|
|
370
|
+
showToast('Rendered', 'info');
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// Only handle the following shortcuts if not in an input field
|
|
375
|
+
if (isInputField) return;
|
|
376
|
+
|
|
377
|
+
// Escape: Close modals or clear selection
|
|
378
|
+
if (event.key === 'Escape') {
|
|
379
|
+
const shortcutsModal = document.getElementById('shortcuts-modal');
|
|
380
|
+
if (shortcutsModal && shortcutsModal.style.display === 'flex') {
|
|
381
|
+
hideShortcutsModal();
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
384
|
+
clearSelection();
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Tab navigation: 1, 2, 3 keys
|
|
389
|
+
if (event.key === '1') {
|
|
390
|
+
switchTab('figure');
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
if (event.key === '2') {
|
|
394
|
+
switchTab('axis');
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
if (event.key === '3') {
|
|
398
|
+
switchTab('element');
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// R: Render (re-render figure)
|
|
403
|
+
if (event.key === 'r' || event.key === 'R') {
|
|
404
|
+
updatePreview();
|
|
405
|
+
showToast('Rendered', 'info');
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// G: Toggle rulers and grid
|
|
410
|
+
if (event.key === 'g' || event.key === 'G') {
|
|
411
|
+
toggleRulerGrid();
|
|
412
|
+
const state = rulerGridVisible ? 'ON' : 'OFF';
|
|
413
|
+
showToast(`Ruler & Grid: ${state}`, 'info');
|
|
414
|
+
return;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
// ?: Show keyboard shortcuts
|
|
418
|
+
if (event.key === '?') {
|
|
419
|
+
showShortcutsModal();
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ==================== UTILITY FUNCTIONS ====================
|
|
425
|
+
|
|
426
|
+
// Show toast notification
|
|
427
|
+
function showToast(message, type = 'info') {
|
|
428
|
+
// Remove existing toast if any
|
|
429
|
+
const existingToast = document.querySelector('.toast-notification');
|
|
430
|
+
if (existingToast) {
|
|
431
|
+
existingToast.remove();
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Create toast element
|
|
435
|
+
const toast = document.createElement('div');
|
|
436
|
+
toast.className = 'toast-notification toast-' + type;
|
|
437
|
+
toast.textContent = message;
|
|
438
|
+
|
|
439
|
+
// Style the toast
|
|
440
|
+
Object.assign(toast.style, {
|
|
441
|
+
position: 'fixed',
|
|
442
|
+
bottom: '20px',
|
|
443
|
+
left: '50%',
|
|
444
|
+
transform: 'translateX(-50%)',
|
|
445
|
+
padding: '10px 20px',
|
|
446
|
+
borderRadius: '4px',
|
|
447
|
+
color: 'white',
|
|
448
|
+
fontWeight: '500',
|
|
449
|
+
zIndex: '10000',
|
|
450
|
+
opacity: '0',
|
|
451
|
+
transition: 'opacity 0.3s ease',
|
|
452
|
+
boxShadow: '0 2px 8px rgba(0,0,0,0.3)'
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
// Set background color based on type
|
|
456
|
+
const colors = {
|
|
457
|
+
success: '#4CAF50',
|
|
458
|
+
info: '#2196F3',
|
|
459
|
+
warning: '#ff9800',
|
|
460
|
+
error: '#f44336'
|
|
461
|
+
};
|
|
462
|
+
toast.style.backgroundColor = colors[type] || colors.info;
|
|
463
|
+
|
|
464
|
+
document.body.appendChild(toast);
|
|
465
|
+
|
|
466
|
+
// Fade in
|
|
467
|
+
requestAnimationFrame(() => {
|
|
468
|
+
toast.style.opacity = '1';
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
// Remove after delay
|
|
472
|
+
setTimeout(() => {
|
|
473
|
+
toast.style.opacity = '0';
|
|
474
|
+
setTimeout(() => toast.remove(), 300);
|
|
475
|
+
}, 2000);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// Debounce utility
|
|
479
|
+
function debounce(func, wait) {
|
|
480
|
+
let timeout;
|
|
481
|
+
return function(...args) {
|
|
482
|
+
clearTimeout(timeout);
|
|
483
|
+
timeout = setTimeout(() => func.apply(this, args), wait);
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Note: scheduleUpdate() is defined in _api.py to avoid duplication
|
|
488
|
+
// It calls updatePreview() with debounce, which properly includes dark_mode
|
|
489
|
+
|
|
490
|
+
// ==================== END CORE ====================
|
|
491
|
+
"""
|
|
492
|
+
|
|
493
|
+
__all__ = ["SCRIPTS_CORE"]
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Datatable JavaScript modules - orchestrator.
|
|
4
|
+
|
|
5
|
+
Combines all datatable JavaScript modules:
|
|
6
|
+
- _core.py: State, panel toggle, initialization
|
|
7
|
+
- _tabs.py: Multi-tab management for multiple datasets
|
|
8
|
+
- _import.py: Drag-drop, file parsing
|
|
9
|
+
- _table.py: Table rendering with smart truncation
|
|
10
|
+
- _selection.py: Multi-cell selection and highlights
|
|
11
|
+
- _cell_edit.py: Inline cell editing
|
|
12
|
+
- _clipboard.py: Copy, paste, cut operations
|
|
13
|
+
- _plot.py: Plot type hints, variable assignment, plotting
|
|
14
|
+
- _editable.py: Create and edit tables manually
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from ._cell_edit import JS_DATATABLE_CELL_EDIT
|
|
18
|
+
from ._clipboard import JS_DATATABLE_CLIPBOARD
|
|
19
|
+
from ._context_menu import JS_DATATABLE_CONTEXT_MENU
|
|
20
|
+
from ._core import JS_DATATABLE_CORE
|
|
21
|
+
from ._editable import JS_DATATABLE_EDITABLE
|
|
22
|
+
from ._import import JS_DATATABLE_IMPORT
|
|
23
|
+
from ._plot import get_js_datatable_plot
|
|
24
|
+
from ._selection import JS_DATATABLE_SELECTION
|
|
25
|
+
from ._table import JS_DATATABLE_TABLE
|
|
26
|
+
from ._tabs import JS_DATATABLE_TABS
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def get_scripts_datatable() -> str:
|
|
30
|
+
"""Generate combined datatable JavaScript."""
|
|
31
|
+
return (
|
|
32
|
+
JS_DATATABLE_CORE
|
|
33
|
+
+ "\n"
|
|
34
|
+
+ JS_DATATABLE_TABS
|
|
35
|
+
+ "\n"
|
|
36
|
+
+ JS_DATATABLE_IMPORT
|
|
37
|
+
+ "\n"
|
|
38
|
+
+ JS_DATATABLE_TABLE
|
|
39
|
+
+ "\n"
|
|
40
|
+
+ JS_DATATABLE_SELECTION
|
|
41
|
+
+ "\n"
|
|
42
|
+
+ JS_DATATABLE_CELL_EDIT
|
|
43
|
+
+ "\n"
|
|
44
|
+
+ JS_DATATABLE_CLIPBOARD
|
|
45
|
+
+ "\n"
|
|
46
|
+
+ JS_DATATABLE_CONTEXT_MENU
|
|
47
|
+
+ "\n"
|
|
48
|
+
+ JS_DATATABLE_EDITABLE
|
|
49
|
+
+ "\n"
|
|
50
|
+
+ get_js_datatable_plot()
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# For backward compatibility
|
|
55
|
+
SCRIPTS_DATATABLE = get_scripts_datatable()
|
|
56
|
+
|
|
57
|
+
__all__ = ["SCRIPTS_DATATABLE", "get_scripts_datatable"]
|
|
58
|
+
|
|
59
|
+
# EOF
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Datatable inline cell editing JavaScript."""
|
|
4
|
+
|
|
5
|
+
JS_DATATABLE_CELL_EDIT = """
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Cell Editing (Inline Edit Mode)
|
|
8
|
+
// ============================================================================
|
|
9
|
+
let datatableEditingCell = null; // Track which cell is being edited
|
|
10
|
+
let datatableSkipBlur = false; // Flag to skip blur during Tab/Enter navigation
|
|
11
|
+
|
|
12
|
+
function enterCellEditMode(cell) {
|
|
13
|
+
if (datatableEditMode) return;
|
|
14
|
+
|
|
15
|
+
datatableEditMode = true;
|
|
16
|
+
datatableEditingCell = cell;
|
|
17
|
+
cell.classList.add('cell-editing');
|
|
18
|
+
|
|
19
|
+
const span = cell.querySelector('.cell-text');
|
|
20
|
+
const originalValue = span ? span.textContent : '';
|
|
21
|
+
|
|
22
|
+
// Replace span with input
|
|
23
|
+
cell.innerHTML = `<input type="text" class="cell-edit-input" value="${originalValue}">`;
|
|
24
|
+
const input = cell.querySelector('input');
|
|
25
|
+
input.focus();
|
|
26
|
+
input.select();
|
|
27
|
+
|
|
28
|
+
// Handle input events
|
|
29
|
+
input.addEventListener('blur', (e) => {
|
|
30
|
+
// Skip if Tab/Enter is handling navigation, or if this isn't the editing cell
|
|
31
|
+
if (datatableSkipBlur || datatableEditingCell !== cell) {
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (datatableEditMode && datatableEditingCell === cell) {
|
|
35
|
+
exitCellEditMode(cell, input.value, false);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
input.addEventListener('keydown', (e) => {
|
|
39
|
+
if (e.key === 'Enter') {
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
datatableSkipBlur = true; // Prevent blur from interfering
|
|
42
|
+
exitCellEditMode(cell, input.value, true);
|
|
43
|
+
navigateWithTabEnterAndEdit('enter', e.shiftKey);
|
|
44
|
+
datatableSkipBlur = false;
|
|
45
|
+
} else if (e.key === 'Escape') {
|
|
46
|
+
e.preventDefault();
|
|
47
|
+
exitCellEditMode(cell, originalValue, false);
|
|
48
|
+
} else if (e.key === 'Tab') {
|
|
49
|
+
e.preventDefault();
|
|
50
|
+
datatableSkipBlur = true; // Prevent blur from interfering
|
|
51
|
+
exitCellEditMode(cell, input.value, true);
|
|
52
|
+
navigateWithTabEnterAndEdit('tab', e.shiftKey);
|
|
53
|
+
datatableSkipBlur = false;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function exitCellEditMode(cell, value, continueEditing = false) {
|
|
59
|
+
if (!datatableEditMode) return;
|
|
60
|
+
|
|
61
|
+
const row = parseInt(cell.dataset.row);
|
|
62
|
+
const col = parseInt(cell.dataset.col);
|
|
63
|
+
|
|
64
|
+
// Update data - preserve empty values as empty (not 0)
|
|
65
|
+
if (datatableData && datatableData.rows[row]) {
|
|
66
|
+
if (value === '' || value === null || value === undefined) {
|
|
67
|
+
datatableData.rows[row][col] = '';
|
|
68
|
+
} else {
|
|
69
|
+
const colType = datatableData.columns[col]?.type;
|
|
70
|
+
if (colType === 'numeric') {
|
|
71
|
+
const num = parseFloat(value);
|
|
72
|
+
datatableData.rows[row][col] = isNaN(num) ? value : num;
|
|
73
|
+
} else {
|
|
74
|
+
datatableData.rows[row][col] = value;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Restore cell display with span wrapper
|
|
80
|
+
const displayValue = value === null || value === undefined ? '' : value;
|
|
81
|
+
cell.innerHTML = `<span class="cell-text">${displayValue}</span>`;
|
|
82
|
+
cell.classList.remove('cell-editing');
|
|
83
|
+
cell.setAttribute('title', displayValue);
|
|
84
|
+
|
|
85
|
+
datatableEditMode = false;
|
|
86
|
+
datatableEditingCell = null; // Clear editing cell reference
|
|
87
|
+
|
|
88
|
+
// Only focus this cell if we're NOT continuing to next cell
|
|
89
|
+
if (!continueEditing) {
|
|
90
|
+
cell.focus();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
__all__ = ["JS_DATATABLE_CELL_EDIT"]
|
|
96
|
+
|
|
97
|
+
# EOF
|