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,164 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Datatable clipboard operations JavaScript - copy, paste, cut."""
|
|
4
|
+
|
|
5
|
+
JS_DATATABLE_CLIPBOARD = """
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Clipboard Operations (Copy, Paste, Cut)
|
|
8
|
+
// ============================================================================
|
|
9
|
+
let datatableCopiedRange = null; // Track copied cell range for marching ants
|
|
10
|
+
let datatableIsCutOperation = false; // Track if it was cut (not copy)
|
|
11
|
+
|
|
12
|
+
function handleCopy(e) {
|
|
13
|
+
if (!datatableSelectedCells || datatableEditMode) return;
|
|
14
|
+
|
|
15
|
+
e.preventDefault();
|
|
16
|
+
const text = getSelectedCellsAsTSV();
|
|
17
|
+
e.clipboardData.setData('text/plain', text);
|
|
18
|
+
|
|
19
|
+
// Store copied range and show marching ants
|
|
20
|
+
datatableCopiedRange = { ...datatableSelectedCells };
|
|
21
|
+
datatableIsCutOperation = false;
|
|
22
|
+
showMarchingAnts();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function handleCut(e) {
|
|
26
|
+
if (!datatableSelectedCells || datatableEditMode) return;
|
|
27
|
+
|
|
28
|
+
e.preventDefault();
|
|
29
|
+
const text = getSelectedCellsAsTSV();
|
|
30
|
+
e.clipboardData.setData('text/plain', text);
|
|
31
|
+
|
|
32
|
+
// Store cut range and show marching ants with faded cells
|
|
33
|
+
datatableCopiedRange = { ...datatableSelectedCells };
|
|
34
|
+
datatableIsCutOperation = true;
|
|
35
|
+
showMarchingAnts();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Show Excel-style marching ants border around copied/cut cells
|
|
39
|
+
function showMarchingAnts() {
|
|
40
|
+
clearMarchingAnts(); // Clear any existing marching ants
|
|
41
|
+
if (!datatableCopiedRange) return;
|
|
42
|
+
|
|
43
|
+
const { startRow, startCol, endRow, endCol } = datatableCopiedRange;
|
|
44
|
+
const minRow = Math.min(startRow, endRow);
|
|
45
|
+
const maxRow = Math.max(startRow, endRow);
|
|
46
|
+
const minCol = Math.min(startCol, endCol);
|
|
47
|
+
const maxCol = Math.max(startCol, endCol);
|
|
48
|
+
|
|
49
|
+
for (let r = minRow; r <= maxRow; r++) {
|
|
50
|
+
for (let c = minCol; c <= maxCol; c++) {
|
|
51
|
+
const cell = document.querySelector(`td[data-row="${r}"][data-col="${c}"]`);
|
|
52
|
+
if (!cell) continue;
|
|
53
|
+
|
|
54
|
+
// Add border classes based on position in selection
|
|
55
|
+
if (r === minRow) cell.classList.add('copy-border-top');
|
|
56
|
+
if (r === maxRow) cell.classList.add('copy-border-bottom');
|
|
57
|
+
if (c === minCol) cell.classList.add('copy-border-left');
|
|
58
|
+
if (c === maxCol) cell.classList.add('copy-border-right');
|
|
59
|
+
|
|
60
|
+
// Add faded effect for cut operation
|
|
61
|
+
if (datatableIsCutOperation) {
|
|
62
|
+
cell.classList.add('cut-pending');
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Clear marching ants border
|
|
69
|
+
function clearMarchingAnts() {
|
|
70
|
+
document.querySelectorAll('.copy-border-top, .copy-border-bottom, .copy-border-left, .copy-border-right, .cut-pending').forEach(cell => {
|
|
71
|
+
cell.classList.remove('copy-border-top', 'copy-border-bottom', 'copy-border-left', 'copy-border-right', 'cut-pending');
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function handlePaste(e) {
|
|
76
|
+
if (!datatableCurrentCell || datatableEditMode || !datatableData) return;
|
|
77
|
+
|
|
78
|
+
e.preventDefault();
|
|
79
|
+
const text = e.clipboardData.getData('text/plain');
|
|
80
|
+
if (!text) return;
|
|
81
|
+
|
|
82
|
+
// If this was a cut operation, clear the source cells
|
|
83
|
+
if (datatableIsCutOperation && datatableCopiedRange) {
|
|
84
|
+
const { startRow, startCol, endRow, endCol } = datatableCopiedRange;
|
|
85
|
+
const minRow = Math.min(startRow, endRow);
|
|
86
|
+
const maxRow = Math.max(startRow, endRow);
|
|
87
|
+
const minCol = Math.min(startCol, endCol);
|
|
88
|
+
const maxCol = Math.max(startCol, endCol);
|
|
89
|
+
|
|
90
|
+
for (let r = minRow; r <= maxRow; r++) {
|
|
91
|
+
for (let c = minCol; c <= maxCol; c++) {
|
|
92
|
+
if (datatableData.rows[r]) {
|
|
93
|
+
datatableData.rows[r][c] = '';
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const rows = text.split('\\n').map(line => line.split('\\t'));
|
|
100
|
+
const startRow = datatableCurrentCell.row;
|
|
101
|
+
const startCol = datatableCurrentCell.col;
|
|
102
|
+
|
|
103
|
+
rows.forEach((rowData, rOffset) => {
|
|
104
|
+
const targetRow = startRow + rOffset;
|
|
105
|
+
if (targetRow >= datatableData.rows.length) return;
|
|
106
|
+
|
|
107
|
+
rowData.forEach((value, cOffset) => {
|
|
108
|
+
const targetCol = startCol + cOffset;
|
|
109
|
+
if (targetCol >= datatableData.columns.length) return;
|
|
110
|
+
|
|
111
|
+
// Preserve empty strings as empty (not convert to 0)
|
|
112
|
+
if (value === '' || value === null || value === undefined) {
|
|
113
|
+
datatableData.rows[targetRow][targetCol] = '';
|
|
114
|
+
} else {
|
|
115
|
+
const colType = datatableData.columns[targetCol]?.type;
|
|
116
|
+
if (colType === 'numeric') {
|
|
117
|
+
const num = parseFloat(value);
|
|
118
|
+
datatableData.rows[targetRow][targetCol] = isNaN(num) ? value : num;
|
|
119
|
+
} else {
|
|
120
|
+
datatableData.rows[targetRow][targetCol] = value;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Clear marching ants and reset cut state
|
|
127
|
+
clearMarchingAnts();
|
|
128
|
+
datatableCopiedRange = null;
|
|
129
|
+
datatableIsCutOperation = false;
|
|
130
|
+
|
|
131
|
+
renderDatatable();
|
|
132
|
+
updateCellSelectionDisplay();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function getSelectedCellsAsTSV() {
|
|
136
|
+
if (!datatableSelectedCells || !datatableData) return '';
|
|
137
|
+
|
|
138
|
+
const { startRow, startCol, endRow, endCol } = datatableSelectedCells;
|
|
139
|
+
const minRow = Math.min(startRow, endRow);
|
|
140
|
+
const maxRow = Math.max(startRow, endRow);
|
|
141
|
+
const minCol = Math.min(startCol, endCol);
|
|
142
|
+
const maxCol = Math.max(startCol, endCol);
|
|
143
|
+
|
|
144
|
+
const lines = [];
|
|
145
|
+
for (let r = minRow; r <= maxRow; r++) {
|
|
146
|
+
const cells = [];
|
|
147
|
+
for (let c = minCol; c <= maxCol; c++) {
|
|
148
|
+
const value = datatableData.rows[r]?.[c];
|
|
149
|
+
// Preserve None as "None" string for copy
|
|
150
|
+
if (value === null || value === undefined) {
|
|
151
|
+
cells.push('');
|
|
152
|
+
} else {
|
|
153
|
+
cells.push(String(value));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
lines.push(cells.join('\\t'));
|
|
157
|
+
}
|
|
158
|
+
return lines.join('\\n');
|
|
159
|
+
}
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
__all__ = ["JS_DATATABLE_CLIPBOARD"]
|
|
163
|
+
|
|
164
|
+
# EOF
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Datatable right-click context menu JavaScript."""
|
|
4
|
+
|
|
5
|
+
JS_DATATABLE_CONTEXT_MENU = """
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Context Menu (Right-Click Menu)
|
|
8
|
+
// ============================================================================
|
|
9
|
+
let datatableContextMenu = null;
|
|
10
|
+
|
|
11
|
+
function createContextMenu() {
|
|
12
|
+
if (datatableContextMenu) return;
|
|
13
|
+
|
|
14
|
+
datatableContextMenu = document.createElement('div');
|
|
15
|
+
datatableContextMenu.className = 'datatable-context-menu';
|
|
16
|
+
datatableContextMenu.style.display = 'none';
|
|
17
|
+
datatableContextMenu.innerHTML = `
|
|
18
|
+
<div class="context-menu-item" data-action="cut">
|
|
19
|
+
Cut<span class="shortcut">Ctrl+X</span>
|
|
20
|
+
</div>
|
|
21
|
+
<div class="context-menu-item" data-action="copy">
|
|
22
|
+
Copy<span class="shortcut">Ctrl+C</span>
|
|
23
|
+
</div>
|
|
24
|
+
<div class="context-menu-item" data-action="paste">
|
|
25
|
+
Paste<span class="shortcut">Ctrl+V</span>
|
|
26
|
+
</div>
|
|
27
|
+
<div class="context-menu-divider"></div>
|
|
28
|
+
<div class="context-menu-item" data-action="clear">
|
|
29
|
+
Clear cells<span class="shortcut">Del</span>
|
|
30
|
+
</div>
|
|
31
|
+
<div class="context-menu-divider"></div>
|
|
32
|
+
<div class="context-menu-item" data-action="insert-row">
|
|
33
|
+
Insert row below
|
|
34
|
+
</div>
|
|
35
|
+
<div class="context-menu-item" data-action="insert-col">
|
|
36
|
+
Insert column right
|
|
37
|
+
</div>
|
|
38
|
+
<div class="context-menu-divider"></div>
|
|
39
|
+
<div class="context-menu-item" data-action="delete-row">
|
|
40
|
+
Delete row
|
|
41
|
+
</div>
|
|
42
|
+
<div class="context-menu-item" data-action="delete-col">
|
|
43
|
+
Delete column
|
|
44
|
+
</div>
|
|
45
|
+
`;
|
|
46
|
+
document.body.appendChild(datatableContextMenu);
|
|
47
|
+
setupContextMenuListeners();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function setupContextMenuListeners() {
|
|
51
|
+
if (!datatableContextMenu) return;
|
|
52
|
+
|
|
53
|
+
// Click on menu items
|
|
54
|
+
datatableContextMenu.querySelectorAll('.context-menu-item').forEach(item => {
|
|
55
|
+
item.addEventListener('click', (e) => {
|
|
56
|
+
e.stopPropagation();
|
|
57
|
+
const action = item.dataset.action;
|
|
58
|
+
handleContextMenuAction(action);
|
|
59
|
+
hideContextMenu();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Hide on click outside
|
|
64
|
+
document.addEventListener('click', hideContextMenu);
|
|
65
|
+
document.addEventListener('scroll', hideContextMenu, true);
|
|
66
|
+
document.addEventListener('keydown', (e) => {
|
|
67
|
+
if (e.key === 'Escape') hideContextMenu();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function handleContextMenuAction(action) {
|
|
72
|
+
if (!datatableData) return;
|
|
73
|
+
|
|
74
|
+
switch (action) {
|
|
75
|
+
case 'cut':
|
|
76
|
+
// Simulate Ctrl+X
|
|
77
|
+
document.execCommand('cut');
|
|
78
|
+
break;
|
|
79
|
+
case 'copy':
|
|
80
|
+
// Copy selected cells as TSV
|
|
81
|
+
const copyText = getSelectedCellsAsTSV();
|
|
82
|
+
navigator.clipboard.writeText(copyText).catch(console.error);
|
|
83
|
+
break;
|
|
84
|
+
case 'paste':
|
|
85
|
+
// Paste from clipboard
|
|
86
|
+
navigator.clipboard.readText().then(text => {
|
|
87
|
+
if (!text || !datatableCurrentCell) return;
|
|
88
|
+
const rows = text.split('\\n').map(line => line.split('\\t'));
|
|
89
|
+
const startRow = datatableCurrentCell.row;
|
|
90
|
+
const startCol = datatableCurrentCell.col;
|
|
91
|
+
rows.forEach((rowData, rOffset) => {
|
|
92
|
+
const targetRow = startRow + rOffset;
|
|
93
|
+
if (targetRow >= datatableData.rows.length) return;
|
|
94
|
+
rowData.forEach((value, cOffset) => {
|
|
95
|
+
const targetCol = startCol + cOffset;
|
|
96
|
+
if (targetCol >= datatableData.columns.length) return;
|
|
97
|
+
datatableData.rows[targetRow][targetCol] = value;
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
renderDatatable();
|
|
101
|
+
}).catch(console.error);
|
|
102
|
+
break;
|
|
103
|
+
case 'clear':
|
|
104
|
+
clearSelectedCells();
|
|
105
|
+
break;
|
|
106
|
+
case 'insert-row':
|
|
107
|
+
insertRowBelow();
|
|
108
|
+
break;
|
|
109
|
+
case 'insert-col':
|
|
110
|
+
insertColumnRight();
|
|
111
|
+
break;
|
|
112
|
+
case 'delete-row':
|
|
113
|
+
deleteCurrentRow();
|
|
114
|
+
break;
|
|
115
|
+
case 'delete-col':
|
|
116
|
+
deleteCurrentColumn();
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function insertRowBelow() {
|
|
122
|
+
if (!datatableData || !datatableCurrentCell) return;
|
|
123
|
+
const row = datatableCurrentCell.row;
|
|
124
|
+
const newRow = datatableData.columns.map(() => '');
|
|
125
|
+
datatableData.rows.splice(row + 1, 0, newRow);
|
|
126
|
+
renderDatatable();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function insertColumnRight() {
|
|
130
|
+
if (!datatableData || !datatableCurrentCell) return;
|
|
131
|
+
const col = datatableCurrentCell.col;
|
|
132
|
+
const newColIdx = datatableData.columns.length;
|
|
133
|
+
datatableData.columns.splice(col + 1, 0, {
|
|
134
|
+
name: `col${newColIdx + 1}`,
|
|
135
|
+
type: 'numeric',
|
|
136
|
+
index: col + 1
|
|
137
|
+
});
|
|
138
|
+
// Reindex columns
|
|
139
|
+
datatableData.columns.forEach((c, i) => c.index = i);
|
|
140
|
+
// Add cell to all rows
|
|
141
|
+
datatableData.rows.forEach(row => row.splice(col + 1, 0, ''));
|
|
142
|
+
renderDatatable();
|
|
143
|
+
updateVarAssignSlots();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function deleteCurrentRow() {
|
|
147
|
+
if (!datatableData || !datatableCurrentCell) return;
|
|
148
|
+
if (datatableData.rows.length <= 1) return; // Keep at least 1 row
|
|
149
|
+
const row = datatableCurrentCell.row;
|
|
150
|
+
datatableData.rows.splice(row, 1);
|
|
151
|
+
if (datatableCurrentCell.row >= datatableData.rows.length) {
|
|
152
|
+
datatableCurrentCell.row = datatableData.rows.length - 1;
|
|
153
|
+
}
|
|
154
|
+
renderDatatable();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function deleteCurrentColumn() {
|
|
158
|
+
if (!datatableData || !datatableCurrentCell) return;
|
|
159
|
+
if (datatableData.columns.length <= 1) return; // Keep at least 1 col
|
|
160
|
+
const col = datatableCurrentCell.col;
|
|
161
|
+
datatableData.columns.splice(col, 1);
|
|
162
|
+
// Reindex columns
|
|
163
|
+
datatableData.columns.forEach((c, i) => c.index = i);
|
|
164
|
+
// Remove cell from all rows
|
|
165
|
+
datatableData.rows.forEach(row => row.splice(col, 1));
|
|
166
|
+
if (datatableCurrentCell.col >= datatableData.columns.length) {
|
|
167
|
+
datatableCurrentCell.col = datatableData.columns.length - 1;
|
|
168
|
+
}
|
|
169
|
+
renderDatatable();
|
|
170
|
+
updateVarAssignSlots();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function showContextMenu(e) {
|
|
174
|
+
if (!datatableContextMenu) createContextMenu();
|
|
175
|
+
|
|
176
|
+
e.preventDefault();
|
|
177
|
+
e.stopPropagation();
|
|
178
|
+
|
|
179
|
+
const x = e.clientX;
|
|
180
|
+
const y = e.clientY;
|
|
181
|
+
|
|
182
|
+
// Position off-screen to measure
|
|
183
|
+
datatableContextMenu.style.left = '-9999px';
|
|
184
|
+
datatableContextMenu.style.top = '-9999px';
|
|
185
|
+
datatableContextMenu.style.display = 'block';
|
|
186
|
+
|
|
187
|
+
const menuWidth = datatableContextMenu.offsetWidth;
|
|
188
|
+
const menuHeight = datatableContextMenu.offsetHeight;
|
|
189
|
+
|
|
190
|
+
// Adjust position to fit in viewport
|
|
191
|
+
let left = x;
|
|
192
|
+
let top = y;
|
|
193
|
+
if (x + menuWidth > window.innerWidth - 10) {
|
|
194
|
+
left = x - menuWidth;
|
|
195
|
+
}
|
|
196
|
+
if (y + menuHeight > window.innerHeight - 10) {
|
|
197
|
+
top = y - menuHeight;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
datatableContextMenu.style.left = `${Math.max(10, left)}px`;
|
|
201
|
+
datatableContextMenu.style.top = `${Math.max(10, top)}px`;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function hideContextMenu() {
|
|
205
|
+
if (datatableContextMenu) {
|
|
206
|
+
datatableContextMenu.style.display = 'none';
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Attach context menu to table
|
|
211
|
+
function attachContextMenuListener() {
|
|
212
|
+
const table = document.querySelector('.datatable-table');
|
|
213
|
+
if (table) {
|
|
214
|
+
table.addEventListener('contextmenu', showContextMenu);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
"""
|
|
218
|
+
|
|
219
|
+
__all__ = ["JS_DATATABLE_CONTEXT_MENU"]
|
|
220
|
+
|
|
221
|
+
# EOF
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Core datatable JavaScript: state, panel toggle, initialization."""
|
|
4
|
+
|
|
5
|
+
JS_DATATABLE_CORE = """
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Datatable Panel State
|
|
8
|
+
// ============================================================================
|
|
9
|
+
let datatableData = null; // Parsed data: {columns: [...], rows: [...]}
|
|
10
|
+
let datatableSelectedColumns = new Set(); // Selected column indices
|
|
11
|
+
let datatablePlotType = 'plot'; // Default plot type
|
|
12
|
+
let datatableTargetAxis = null; // null = new figure, 0+ = existing axis index
|
|
13
|
+
let datatableVarAssignments = {}; // Variable name -> column index mapping
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Panel Initialization
|
|
17
|
+
// ============================================================================
|
|
18
|
+
function initDatatablePanel() {
|
|
19
|
+
// Initialize sub-modules
|
|
20
|
+
initDatatableDropzone();
|
|
21
|
+
initPlotTypeButtons();
|
|
22
|
+
|
|
23
|
+
// Initialize plot button (New = create new panel)
|
|
24
|
+
const plotBtn = document.getElementById('btn-datatable-plot');
|
|
25
|
+
if (plotBtn) {
|
|
26
|
+
plotBtn.addEventListener('click', () => {
|
|
27
|
+
datatableTargetAxis = null; // New panel
|
|
28
|
+
plotFromVarAssignments();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Initialize split button dropdown
|
|
33
|
+
initPlotDropdown();
|
|
34
|
+
|
|
35
|
+
// Load existing data if available from figure
|
|
36
|
+
loadExistingData();
|
|
37
|
+
|
|
38
|
+
// Hook into canvas selection to sync with datatable
|
|
39
|
+
hookCanvasSelection();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function hookCanvasSelection() {
|
|
43
|
+
// Wrap the selectElement function to add datatable sync
|
|
44
|
+
if (typeof window.selectElement === 'function') {
|
|
45
|
+
const originalSelectElement = window.selectElement;
|
|
46
|
+
window.selectElement = function(element) {
|
|
47
|
+
originalSelectElement(element);
|
|
48
|
+
if (typeof syncDatatableToElement === 'function') {
|
|
49
|
+
syncDatatableToElement(element);
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Also hook clearSelection
|
|
55
|
+
if (typeof window.clearSelection === 'function') {
|
|
56
|
+
const originalClearSelection = window.clearSelection;
|
|
57
|
+
window.clearSelection = function() {
|
|
58
|
+
originalClearSelection();
|
|
59
|
+
if (typeof clearDatatableHighlight === 'function') {
|
|
60
|
+
clearDatatableHighlight();
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function initPlotDropdown() {
|
|
67
|
+
const dropdownBtn = document.getElementById('btn-plot-dropdown');
|
|
68
|
+
const dropdownMenu = document.getElementById('plot-dropdown-menu');
|
|
69
|
+
if (!dropdownBtn || !dropdownMenu) return;
|
|
70
|
+
|
|
71
|
+
// Toggle dropdown
|
|
72
|
+
dropdownBtn.addEventListener('click', (e) => {
|
|
73
|
+
e.stopPropagation();
|
|
74
|
+
dropdownMenu.classList.toggle('show');
|
|
75
|
+
if (dropdownMenu.classList.contains('show')) {
|
|
76
|
+
populatePlotDropdown();
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Close on outside click
|
|
81
|
+
document.addEventListener('click', () => {
|
|
82
|
+
dropdownMenu.classList.remove('show');
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function populatePlotDropdown() {
|
|
87
|
+
const menu = document.getElementById('plot-dropdown-menu');
|
|
88
|
+
if (!menu) return;
|
|
89
|
+
|
|
90
|
+
fetch('/get_axes_positions').then(r => r.json()).then(data => {
|
|
91
|
+
const axes = Object.keys(data)
|
|
92
|
+
.filter(k => k.startsWith('ax_'))
|
|
93
|
+
.sort((a, b) => {
|
|
94
|
+
const matchA = a.match(/ax_(\\d+)_(\\d+)/);
|
|
95
|
+
const matchB = b.match(/ax_(\\d+)_(\\d+)/);
|
|
96
|
+
if (matchA && matchB) {
|
|
97
|
+
return parseInt(matchA[2]) - parseInt(matchB[2]) || parseInt(matchA[1]) - parseInt(matchB[1]);
|
|
98
|
+
}
|
|
99
|
+
return 0;
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
let html = '';
|
|
103
|
+
axes.forEach((key, i) => {
|
|
104
|
+
html += `<button class="dropdown-item" onclick="addToPanel(${i})">Add to P${i + 1}</button>`;
|
|
105
|
+
});
|
|
106
|
+
menu.innerHTML = html || '<div class="dropdown-item" style="color:var(--text-secondary)">No panels</div>';
|
|
107
|
+
}).catch(() => {
|
|
108
|
+
menu.innerHTML = '<div class="dropdown-item" style="color:var(--text-secondary)">No panels</div>';
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function addToPanel(axisIndex) {
|
|
113
|
+
datatableTargetAxis = axisIndex;
|
|
114
|
+
document.getElementById('plot-dropdown-menu').classList.remove('show');
|
|
115
|
+
plotFromVarAssignments();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// ============================================================================
|
|
119
|
+
// Clear Data
|
|
120
|
+
// ============================================================================
|
|
121
|
+
function clearDatatableData() {
|
|
122
|
+
datatableData = null;
|
|
123
|
+
datatableSelectedColumns.clear();
|
|
124
|
+
datatableVarAssignments = {};
|
|
125
|
+
|
|
126
|
+
const content = document.getElementById('datatable-content');
|
|
127
|
+
if (content) {
|
|
128
|
+
content.innerHTML = '';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Show dropzone again
|
|
132
|
+
const dropzone = document.getElementById('datatable-dropzone');
|
|
133
|
+
if (dropzone) dropzone.style.display = 'block';
|
|
134
|
+
|
|
135
|
+
const toolbar = document.querySelector('.datatable-toolbar');
|
|
136
|
+
if (toolbar) toolbar.style.display = 'none';
|
|
137
|
+
|
|
138
|
+
const varAssign = document.getElementById('datatable-var-assign');
|
|
139
|
+
if (varAssign) varAssign.style.display = 'none';
|
|
140
|
+
|
|
141
|
+
updateSelectionInfo();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Initialize on page load
|
|
145
|
+
document.addEventListener('DOMContentLoaded', initDatatablePanel);
|
|
146
|
+
"""
|
|
147
|
+
|
|
148
|
+
__all__ = ["JS_DATATABLE_CORE"]
|
|
149
|
+
|
|
150
|
+
# EOF
|