figrecipe 0.5.0__py3-none-any.whl → 0.7.4__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 +220 -819
- figrecipe/_api/__init__.py +48 -0
- figrecipe/_api/_extract.py +108 -0
- figrecipe/_api/_notebook.py +61 -0
- figrecipe/_api/_panel.py +46 -0
- figrecipe/_api/_save.py +191 -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/_dev/__init__.py +29 -0
- figrecipe/_dev/_plotters.py +76 -0
- figrecipe/_dev/_run_demos.py +56 -0
- figrecipe/_dev/demo_plotters/__init__.py +64 -0
- 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/bar_categorical/plot_bar.py +25 -0
- figrecipe/_dev/demo_plotters/bar_categorical/plot_barh.py +25 -0
- figrecipe/_dev/demo_plotters/contour_surface/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_contour.py +30 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_contourf.py +29 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_tricontour.py +28 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_tricontourf.py +28 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_tripcolor.py +29 -0
- figrecipe/_dev/demo_plotters/contour_surface/plot_triplot.py +25 -0
- figrecipe/_dev/demo_plotters/distribution/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/distribution/plot_boxplot.py +24 -0
- figrecipe/_dev/demo_plotters/distribution/plot_ecdf.py +24 -0
- figrecipe/_dev/demo_plotters/distribution/plot_hist.py +24 -0
- figrecipe/_dev/demo_plotters/distribution/plot_hist2d.py +25 -0
- figrecipe/_dev/demo_plotters/distribution/plot_violinplot.py +25 -0
- figrecipe/_dev/demo_plotters/image_matrix/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_hexbin.py +25 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_imshow.py +23 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_matshow.py +23 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_pcolor.py +29 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_pcolormesh.py +29 -0
- figrecipe/_dev/demo_plotters/image_matrix/plot_spy.py +29 -0
- figrecipe/_dev/demo_plotters/line_curve/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_errorbar.py +28 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_fill.py +29 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_fill_between.py +30 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_fill_betweenx.py +28 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_plot.py +28 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_stackplot.py +29 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_stairs.py +27 -0
- figrecipe/_dev/demo_plotters/line_curve/plot_step.py +27 -0
- figrecipe/_dev/demo_plotters/scatter_points/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/scatter_points/plot_scatter.py +24 -0
- figrecipe/_dev/demo_plotters/special/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/special/plot_eventplot.py +25 -0
- figrecipe/_dev/demo_plotters/special/plot_loglog.py +27 -0
- figrecipe/_dev/demo_plotters/special/plot_pie.py +27 -0
- figrecipe/_dev/demo_plotters/special/plot_semilogx.py +27 -0
- figrecipe/_dev/demo_plotters/special/plot_semilogy.py +27 -0
- figrecipe/_dev/demo_plotters/special/plot_stem.py +27 -0
- figrecipe/_dev/demo_plotters/spectral_signal/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_acorr.py +24 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_angle_spectrum.py +28 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_cohere.py +29 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_csd.py +29 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_magnitude_spectrum.py +28 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_phase_spectrum.py +28 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_psd.py +29 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_specgram.py +30 -0
- figrecipe/_dev/demo_plotters/spectral_signal/plot_xcorr.py +25 -0
- figrecipe/_dev/demo_plotters/vector_flow/__init__.py +4 -0
- figrecipe/_dev/demo_plotters/vector_flow/plot_barbs.py +30 -0
- figrecipe/_dev/demo_plotters/vector_flow/plot_quiver.py +30 -0
- figrecipe/_dev/demo_plotters/vector_flow/plot_streamplot.py +30 -0
- figrecipe/_editor/__init__.py +278 -0
- 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 +256 -0
- figrecipe/_editor/_bbox/_extract_axes.py +370 -0
- figrecipe/_editor/_bbox/_extract_text.py +342 -0
- figrecipe/_editor/_bbox/_lines.py +173 -0
- figrecipe/_editor/_bbox/_transforms.py +146 -0
- figrecipe/_editor/_flask_app.py +258 -0
- figrecipe/_editor/_helpers.py +242 -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 +137 -0
- figrecipe/_editor/_hitmap/_restore.py +154 -0
- figrecipe/_editor/_hitmap_main.py +182 -0
- figrecipe/_editor/_overrides.py +318 -0
- figrecipe/_editor/_preferences.py +135 -0
- figrecipe/_editor/_render_overrides.py +480 -0
- figrecipe/_editor/_renderer.py +199 -0
- figrecipe/_editor/_routes_axis.py +453 -0
- figrecipe/_editor/_routes_core.py +284 -0
- figrecipe/_editor/_routes_element.py +317 -0
- figrecipe/_editor/_routes_style.py +223 -0
- figrecipe/_editor/_templates/__init__.py +152 -0
- figrecipe/_editor/_templates/_html.py +502 -0
- figrecipe/_editor/_templates/_scripts/__init__.py +120 -0
- figrecipe/_editor/_templates/_scripts/_api.py +228 -0
- figrecipe/_editor/_templates/_scripts/_colors.py +485 -0
- figrecipe/_editor/_templates/_scripts/_core.py +436 -0
- figrecipe/_editor/_templates/_scripts/_debug_snapshot.py +186 -0
- figrecipe/_editor/_templates/_scripts/_element_editor.py +310 -0
- figrecipe/_editor/_templates/_scripts/_files.py +195 -0
- figrecipe/_editor/_templates/_scripts/_hitmap.py +509 -0
- figrecipe/_editor/_templates/_scripts/_inspector.py +315 -0
- figrecipe/_editor/_templates/_scripts/_labels.py +464 -0
- figrecipe/_editor/_templates/_scripts/_legend_drag.py +265 -0
- figrecipe/_editor/_templates/_scripts/_modals.py +226 -0
- figrecipe/_editor/_templates/_scripts/_overlays.py +292 -0
- figrecipe/_editor/_templates/_scripts/_panel_drag.py +334 -0
- figrecipe/_editor/_templates/_scripts/_panel_position.py +279 -0
- figrecipe/_editor/_templates/_scripts/_selection.py +237 -0
- figrecipe/_editor/_templates/_scripts/_tabs.py +89 -0
- figrecipe/_editor/_templates/_scripts/_view_mode.py +107 -0
- figrecipe/_editor/_templates/_scripts/_zoom.py +179 -0
- figrecipe/_editor/_templates/_styles/__init__.py +69 -0
- figrecipe/_editor/_templates/_styles/_base.py +64 -0
- figrecipe/_editor/_templates/_styles/_buttons.py +206 -0
- figrecipe/_editor/_templates/_styles/_color_input.py +123 -0
- figrecipe/_editor/_templates/_styles/_controls.py +265 -0
- figrecipe/_editor/_templates/_styles/_dynamic_props.py +144 -0
- figrecipe/_editor/_templates/_styles/_forms.py +126 -0
- figrecipe/_editor/_templates/_styles/_hitmap.py +184 -0
- figrecipe/_editor/_templates/_styles/_inspector.py +90 -0
- figrecipe/_editor/_templates/_styles/_labels.py +118 -0
- figrecipe/_editor/_templates/_styles/_modals.py +98 -0
- figrecipe/_editor/_templates/_styles/_overlays.py +130 -0
- figrecipe/_editor/_templates/_styles/_preview.py +225 -0
- figrecipe/_editor/_templates/_styles/_selection.py +73 -0
- figrecipe/_params/_DECORATION_METHODS.py +33 -0
- figrecipe/_params/_PLOTTING_METHODS.py +58 -0
- figrecipe/_params/__init__.py +9 -0
- figrecipe/_recorder.py +92 -110
- figrecipe/_recorder_utils.py +124 -0
- figrecipe/_reproducer/__init__.py +18 -0
- figrecipe/_reproducer/_core.py +498 -0
- figrecipe/_reproducer/_custom_plots.py +279 -0
- figrecipe/_reproducer/_seaborn.py +100 -0
- figrecipe/_reproducer/_violin.py +186 -0
- figrecipe/_seaborn.py +14 -9
- figrecipe/_serializer.py +2 -2
- figrecipe/_signatures/README.md +68 -0
- figrecipe/_signatures/__init__.py +12 -2
- figrecipe/_signatures/_kwargs.py +273 -0
- figrecipe/_signatures/_loader.py +114 -57
- figrecipe/_signatures/_parsing.py +147 -0
- figrecipe/_utils/__init__.py +6 -4
- figrecipe/_utils/_crop.py +10 -4
- figrecipe/_utils/_image_diff.py +37 -33
- figrecipe/_utils/_numpy_io.py +0 -1
- figrecipe/_utils/_units.py +11 -3
- figrecipe/_validator.py +12 -3
- figrecipe/_wrappers/_axes.py +193 -170
- figrecipe/_wrappers/_axes_helpers.py +136 -0
- figrecipe/_wrappers/_axes_plots.py +418 -0
- figrecipe/_wrappers/_axes_seaborn.py +157 -0
- figrecipe/_wrappers/_figure.py +277 -18
- figrecipe/_wrappers/_panel_labels.py +127 -0
- figrecipe/_wrappers/_plot_helpers.py +143 -0
- figrecipe/_wrappers/_violin_helpers.py +180 -0
- figrecipe/plt.py +0 -1
- figrecipe/pyplot.py +2 -1
- figrecipe/styles/__init__.py +12 -11
- 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 +60 -202
- figrecipe/styles/_style_loader.py +73 -121
- figrecipe/styles/_themes.py +151 -0
- figrecipe/styles/presets/MATPLOTLIB.yaml +95 -0
- figrecipe/styles/presets/SCITEX.yaml +181 -0
- figrecipe-0.7.4.dist-info/METADATA +429 -0
- figrecipe-0.7.4.dist-info/RECORD +188 -0
- figrecipe/_reproducer.py +0 -358
- figrecipe-0.5.0.dist-info/METADATA +0 -336
- figrecipe-0.5.0.dist-info/RECORD +0 -26
- {figrecipe-0.5.0.dist-info → figrecipe-0.7.4.dist-info}/WHEEL +0 -0
- {figrecipe-0.5.0.dist-info → figrecipe-0.7.4.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Ruler, grid, and column overlay JavaScript for the figure editor."""
|
|
4
|
+
|
|
5
|
+
SCRIPTS_OVERLAYS = """
|
|
6
|
+
// ============================================
|
|
7
|
+
// MEASUREMENT OVERLAYS (Ruler, Grid, Columns)
|
|
8
|
+
// ============================================
|
|
9
|
+
|
|
10
|
+
// State for overlays
|
|
11
|
+
let rulerVisible = false;
|
|
12
|
+
let gridVisible = false;
|
|
13
|
+
let columnsVisible = false;
|
|
14
|
+
|
|
15
|
+
// Get figure DPI from server (default 300 for preview at 150)
|
|
16
|
+
// Preview DPI is 150, which is half of output DPI
|
|
17
|
+
const PREVIEW_DPI = 150;
|
|
18
|
+
const MM_PER_INCH = 25.4;
|
|
19
|
+
|
|
20
|
+
// Pixels per mm at preview resolution
|
|
21
|
+
function getPxPerMm() {
|
|
22
|
+
return PREVIEW_DPI / MM_PER_INCH;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Unified ruler & grid state
|
|
26
|
+
let rulerGridVisible = false;
|
|
27
|
+
|
|
28
|
+
// Initialize overlay controls (single toggle button)
|
|
29
|
+
function initializeOverlayControls() {
|
|
30
|
+
const btn = document.getElementById('btn-ruler-grid');
|
|
31
|
+
if (btn) {
|
|
32
|
+
btn.addEventListener('click', toggleRulerGrid);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Toggle all ruler/grid overlays together
|
|
37
|
+
function toggleRulerGrid() {
|
|
38
|
+
rulerGridVisible = !rulerGridVisible;
|
|
39
|
+
|
|
40
|
+
// Sync all three visibility states
|
|
41
|
+
rulerVisible = rulerGridVisible;
|
|
42
|
+
gridVisible = rulerGridVisible;
|
|
43
|
+
columnsVisible = rulerGridVisible;
|
|
44
|
+
|
|
45
|
+
// Update overlays
|
|
46
|
+
const rulerOverlay = document.getElementById('ruler-overlay');
|
|
47
|
+
const gridOverlay = document.getElementById('grid-overlay');
|
|
48
|
+
const columnOverlay = document.getElementById('column-overlay');
|
|
49
|
+
|
|
50
|
+
if (rulerOverlay) rulerOverlay.classList.toggle('visible', rulerGridVisible);
|
|
51
|
+
if (gridOverlay) gridOverlay.classList.toggle('visible', rulerGridVisible);
|
|
52
|
+
if (columnOverlay) columnOverlay.classList.toggle('visible', rulerGridVisible);
|
|
53
|
+
|
|
54
|
+
// Draw overlays if visible
|
|
55
|
+
if (rulerGridVisible) {
|
|
56
|
+
drawRuler();
|
|
57
|
+
drawGrid();
|
|
58
|
+
drawColumnGuides();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Update button state
|
|
62
|
+
const btn = document.getElementById('btn-ruler-grid');
|
|
63
|
+
if (btn) {
|
|
64
|
+
btn.classList.toggle('active', rulerGridVisible);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Legacy functions for backward compatibility
|
|
69
|
+
function toggleRuler() { toggleRulerGrid(); }
|
|
70
|
+
function toggleGrid() { toggleRulerGrid(); }
|
|
71
|
+
function toggleColumns() { toggleRulerGrid(); }
|
|
72
|
+
|
|
73
|
+
// Draw ruler overlay (top and left edges)
|
|
74
|
+
function drawRuler() {
|
|
75
|
+
const overlay = document.getElementById('ruler-overlay');
|
|
76
|
+
const img = document.getElementById('preview-image');
|
|
77
|
+
if (!overlay || !img) return;
|
|
78
|
+
|
|
79
|
+
// Clear existing
|
|
80
|
+
overlay.innerHTML = '';
|
|
81
|
+
|
|
82
|
+
const imgWidth = img.naturalWidth || img.width;
|
|
83
|
+
const imgHeight = img.naturalHeight || img.height;
|
|
84
|
+
const pxPerMm = getPxPerMm();
|
|
85
|
+
|
|
86
|
+
// Set SVG size
|
|
87
|
+
overlay.setAttribute('viewBox', `0 0 ${imgWidth} ${imgHeight}`);
|
|
88
|
+
overlay.style.width = imgWidth + 'px';
|
|
89
|
+
overlay.style.height = imgHeight + 'px';
|
|
90
|
+
|
|
91
|
+
const rulerThickness = 15; // pixels
|
|
92
|
+
|
|
93
|
+
// Background for horizontal ruler (top)
|
|
94
|
+
const hBg = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
95
|
+
hBg.setAttribute('x', 0);
|
|
96
|
+
hBg.setAttribute('y', 0);
|
|
97
|
+
hBg.setAttribute('width', imgWidth);
|
|
98
|
+
hBg.setAttribute('height', rulerThickness);
|
|
99
|
+
hBg.setAttribute('class', 'ruler-bg');
|
|
100
|
+
overlay.appendChild(hBg);
|
|
101
|
+
|
|
102
|
+
// Background for vertical ruler (left)
|
|
103
|
+
const vBg = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
104
|
+
vBg.setAttribute('x', 0);
|
|
105
|
+
vBg.setAttribute('y', 0);
|
|
106
|
+
vBg.setAttribute('width', rulerThickness);
|
|
107
|
+
vBg.setAttribute('height', imgHeight);
|
|
108
|
+
vBg.setAttribute('class', 'ruler-bg');
|
|
109
|
+
overlay.appendChild(vBg);
|
|
110
|
+
|
|
111
|
+
const widthMm = imgWidth / pxPerMm;
|
|
112
|
+
const heightMm = imgHeight / pxPerMm;
|
|
113
|
+
|
|
114
|
+
// Draw horizontal ruler ticks (every 1mm, labels every 5mm)
|
|
115
|
+
for (let mm = 0; mm <= widthMm; mm++) {
|
|
116
|
+
const x = mm * pxPerMm;
|
|
117
|
+
const isMajor = mm % 5 === 0;
|
|
118
|
+
const tickLen = isMajor ? 10 : 5;
|
|
119
|
+
|
|
120
|
+
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
121
|
+
line.setAttribute('x1', x);
|
|
122
|
+
line.setAttribute('y1', 0);
|
|
123
|
+
line.setAttribute('x2', x);
|
|
124
|
+
line.setAttribute('y2', tickLen);
|
|
125
|
+
line.setAttribute('class', isMajor ? 'ruler-line-major' : 'ruler-line');
|
|
126
|
+
overlay.appendChild(line);
|
|
127
|
+
|
|
128
|
+
// Label every 10mm
|
|
129
|
+
if (mm % 10 === 0 && mm > 0) {
|
|
130
|
+
const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
131
|
+
text.setAttribute('x', x);
|
|
132
|
+
text.setAttribute('y', 13);
|
|
133
|
+
text.setAttribute('class', 'ruler-text');
|
|
134
|
+
text.setAttribute('text-anchor', 'middle');
|
|
135
|
+
text.textContent = mm.toString();
|
|
136
|
+
overlay.appendChild(text);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Draw vertical ruler ticks (every 1mm, labels every 5mm)
|
|
141
|
+
for (let mm = 0; mm <= heightMm; mm++) {
|
|
142
|
+
const y = mm * pxPerMm;
|
|
143
|
+
const isMajor = mm % 5 === 0;
|
|
144
|
+
const tickLen = isMajor ? 10 : 5;
|
|
145
|
+
|
|
146
|
+
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
147
|
+
line.setAttribute('x1', 0);
|
|
148
|
+
line.setAttribute('y1', y);
|
|
149
|
+
line.setAttribute('x2', tickLen);
|
|
150
|
+
line.setAttribute('y2', y);
|
|
151
|
+
line.setAttribute('class', isMajor ? 'ruler-line-major' : 'ruler-line');
|
|
152
|
+
overlay.appendChild(line);
|
|
153
|
+
|
|
154
|
+
// Label every 10mm
|
|
155
|
+
if (mm % 10 === 0 && mm > 0) {
|
|
156
|
+
const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
157
|
+
text.setAttribute('x', 2);
|
|
158
|
+
text.setAttribute('y', y + 3);
|
|
159
|
+
text.setAttribute('class', 'ruler-text');
|
|
160
|
+
text.setAttribute('text-anchor', 'start');
|
|
161
|
+
text.setAttribute('transform', `rotate(-90, 2, ${y})`);
|
|
162
|
+
text.textContent = mm.toString();
|
|
163
|
+
overlay.appendChild(text);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Draw grid overlay (1mm and 5mm intervals)
|
|
169
|
+
function drawGrid() {
|
|
170
|
+
const overlay = document.getElementById('grid-overlay');
|
|
171
|
+
const img = document.getElementById('preview-image');
|
|
172
|
+
if (!overlay || !img) return;
|
|
173
|
+
|
|
174
|
+
// Clear existing
|
|
175
|
+
overlay.innerHTML = '';
|
|
176
|
+
|
|
177
|
+
const imgWidth = img.naturalWidth || img.width;
|
|
178
|
+
const imgHeight = img.naturalHeight || img.height;
|
|
179
|
+
const pxPerMm = getPxPerMm();
|
|
180
|
+
|
|
181
|
+
// Set SVG size
|
|
182
|
+
overlay.setAttribute('viewBox', `0 0 ${imgWidth} ${imgHeight}`);
|
|
183
|
+
overlay.style.width = imgWidth + 'px';
|
|
184
|
+
overlay.style.height = imgHeight + 'px';
|
|
185
|
+
|
|
186
|
+
const widthMm = imgWidth / pxPerMm;
|
|
187
|
+
const heightMm = imgHeight / pxPerMm;
|
|
188
|
+
|
|
189
|
+
// Draw vertical lines (every 1mm)
|
|
190
|
+
for (let mm = 0; mm <= widthMm; mm++) {
|
|
191
|
+
const x = mm * pxPerMm;
|
|
192
|
+
const is5mm = mm % 5 === 0;
|
|
193
|
+
|
|
194
|
+
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
195
|
+
line.setAttribute('x1', x);
|
|
196
|
+
line.setAttribute('y1', 0);
|
|
197
|
+
line.setAttribute('x2', x);
|
|
198
|
+
line.setAttribute('y2', imgHeight);
|
|
199
|
+
line.setAttribute('class', is5mm ? 'grid-line-5mm' : 'grid-line-1mm');
|
|
200
|
+
overlay.appendChild(line);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Draw horizontal lines (every 1mm)
|
|
204
|
+
for (let mm = 0; mm <= heightMm; mm++) {
|
|
205
|
+
const y = mm * pxPerMm;
|
|
206
|
+
const is5mm = mm % 5 === 0;
|
|
207
|
+
|
|
208
|
+
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
209
|
+
line.setAttribute('x1', 0);
|
|
210
|
+
line.setAttribute('y1', y);
|
|
211
|
+
line.setAttribute('x2', imgWidth);
|
|
212
|
+
line.setAttribute('y2', y);
|
|
213
|
+
line.setAttribute('class', is5mm ? 'grid-line-5mm' : 'grid-line-1mm');
|
|
214
|
+
overlay.appendChild(line);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Draw column guide lines (45mm intervals: 0, 45, 90, 135, 180mm)
|
|
219
|
+
function drawColumnGuides() {
|
|
220
|
+
const overlay = document.getElementById('column-overlay');
|
|
221
|
+
const img = document.getElementById('preview-image');
|
|
222
|
+
if (!overlay || !img) return;
|
|
223
|
+
|
|
224
|
+
// Clear existing
|
|
225
|
+
overlay.innerHTML = '';
|
|
226
|
+
|
|
227
|
+
const imgWidth = img.naturalWidth || img.width;
|
|
228
|
+
const imgHeight = img.naturalHeight || img.height;
|
|
229
|
+
const pxPerMm = getPxPerMm();
|
|
230
|
+
|
|
231
|
+
// Set SVG size
|
|
232
|
+
overlay.setAttribute('viewBox', `0 0 ${imgWidth} ${imgHeight}`);
|
|
233
|
+
overlay.style.width = imgWidth + 'px';
|
|
234
|
+
overlay.style.height = imgHeight + 'px';
|
|
235
|
+
|
|
236
|
+
// Journal column width: 45mm
|
|
237
|
+
// Lines at 0, 45, 90, 135, 180mm for 0, 1, 2, 3, 4 columns
|
|
238
|
+
const columnPositions = [
|
|
239
|
+
{ mm: 0, label: '0' },
|
|
240
|
+
{ mm: 45, label: '1 col' },
|
|
241
|
+
{ mm: 90, label: '2 col' },
|
|
242
|
+
{ mm: 135, label: '3 col' },
|
|
243
|
+
{ mm: 180, label: '4 col' }
|
|
244
|
+
];
|
|
245
|
+
|
|
246
|
+
for (const col of columnPositions) {
|
|
247
|
+
const x = col.mm * pxPerMm;
|
|
248
|
+
|
|
249
|
+
// Skip if beyond image width
|
|
250
|
+
if (x > imgWidth) continue;
|
|
251
|
+
|
|
252
|
+
// Draw vertical line
|
|
253
|
+
const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
254
|
+
line.setAttribute('x1', x);
|
|
255
|
+
line.setAttribute('y1', 0);
|
|
256
|
+
line.setAttribute('x2', x);
|
|
257
|
+
line.setAttribute('y2', imgHeight);
|
|
258
|
+
line.setAttribute('class', 'column-line');
|
|
259
|
+
overlay.appendChild(line);
|
|
260
|
+
|
|
261
|
+
// Label background
|
|
262
|
+
const labelBg = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
|
|
263
|
+
const labelText = `${col.label} (${col.mm}mm)`;
|
|
264
|
+
const labelWidth = labelText.length * 7 + 8;
|
|
265
|
+
labelBg.setAttribute('x', x + 3);
|
|
266
|
+
labelBg.setAttribute('y', 3);
|
|
267
|
+
labelBg.setAttribute('width', labelWidth);
|
|
268
|
+
labelBg.setAttribute('height', 18);
|
|
269
|
+
labelBg.setAttribute('rx', 3);
|
|
270
|
+
labelBg.setAttribute('class', 'column-bg');
|
|
271
|
+
overlay.appendChild(labelBg);
|
|
272
|
+
|
|
273
|
+
// Label
|
|
274
|
+
const text = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
275
|
+
text.setAttribute('x', x + 6);
|
|
276
|
+
text.setAttribute('y', 16);
|
|
277
|
+
text.setAttribute('class', 'column-text');
|
|
278
|
+
text.setAttribute('text-anchor', 'start');
|
|
279
|
+
text.textContent = labelText;
|
|
280
|
+
overlay.appendChild(text);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Update all overlays when image changes
|
|
285
|
+
function updateOverlays() {
|
|
286
|
+
if (rulerVisible) drawRuler();
|
|
287
|
+
if (gridVisible) drawGrid();
|
|
288
|
+
if (columnsVisible) drawColumnGuides();
|
|
289
|
+
}
|
|
290
|
+
"""
|
|
291
|
+
|
|
292
|
+
__all__ = ["SCRIPTS_OVERLAYS"]
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""Panel drag-to-move JavaScript for the figure editor.
|
|
4
|
+
|
|
5
|
+
This module contains the JavaScript code for:
|
|
6
|
+
- Detecting mousedown on axes/panel elements
|
|
7
|
+
- Handling drag movement with visual feedback
|
|
8
|
+
- Updating panel position on drop
|
|
9
|
+
|
|
10
|
+
Coordinates are in mm with upper-left origin.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
SCRIPTS_PANEL_DRAG = """
|
|
14
|
+
// ===== PANEL DRAG-TO-MOVE (mm, upper-left origin) =====
|
|
15
|
+
|
|
16
|
+
let isDraggingPanel = false;
|
|
17
|
+
let draggedPanelIndex = null;
|
|
18
|
+
let dragStartPos = null;
|
|
19
|
+
let dragStartPanelPos = null;
|
|
20
|
+
let panelDragOverlay = null;
|
|
21
|
+
|
|
22
|
+
// Initialize panel drag functionality
|
|
23
|
+
function initPanelDrag() {
|
|
24
|
+
console.log('[PanelDrag] initPanelDrag called');
|
|
25
|
+
const zoomContainer = document.getElementById('zoom-container');
|
|
26
|
+
if (!zoomContainer) {
|
|
27
|
+
console.error('[PanelDrag] zoom-container not found!');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Add mouse event listeners to zoom container
|
|
32
|
+
zoomContainer.addEventListener('mousedown', handlePanelDragStart);
|
|
33
|
+
document.addEventListener('mousemove', handlePanelDragMove);
|
|
34
|
+
document.addEventListener('mouseup', handlePanelDragEnd);
|
|
35
|
+
console.log('[PanelDrag] Event listeners attached');
|
|
36
|
+
|
|
37
|
+
// Create drag overlay element
|
|
38
|
+
panelDragOverlay = document.createElement('div');
|
|
39
|
+
panelDragOverlay.id = 'panel-drag-overlay';
|
|
40
|
+
panelDragOverlay.style.cssText = `
|
|
41
|
+
position: absolute;
|
|
42
|
+
border: 2px dashed #2563eb;
|
|
43
|
+
background: rgba(37, 99, 235, 0.1);
|
|
44
|
+
pointer-events: none;
|
|
45
|
+
display: none;
|
|
46
|
+
z-index: 1000;
|
|
47
|
+
`;
|
|
48
|
+
zoomContainer.appendChild(panelDragOverlay);
|
|
49
|
+
console.log('[PanelDrag] Overlay created:', panelDragOverlay ? 'success' : 'failed');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Handle mouse down - check if on a panel/axes (only drag from empty panel area)
|
|
53
|
+
function handlePanelDragStart(event) {
|
|
54
|
+
console.log('[PanelDrag] handlePanelDragStart called, button:', event.button);
|
|
55
|
+
// Skip if using modifier keys for other actions
|
|
56
|
+
if (event.ctrlKey || event.metaKey || event.altKey) {
|
|
57
|
+
console.log('[PanelDrag] Skipped - modifier key pressed');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Only start drag if clicking on axes element or empty panel area
|
|
62
|
+
// Skip if clicking on specific elements (they should be selected instead)
|
|
63
|
+
const target = event.target;
|
|
64
|
+
const targetKey = target.getAttribute ? target.getAttribute('data-key') : null;
|
|
65
|
+
if (targetKey && typeof currentBboxes !== 'undefined' && currentBboxes[targetKey]) {
|
|
66
|
+
const elemType = currentBboxes[targetKey].type;
|
|
67
|
+
// Only allow drag from axes bbox or if no specific element type
|
|
68
|
+
if (elemType && elemType !== 'axes') {
|
|
69
|
+
console.log('[PanelDrag] Skipped - clicked on element:', elemType);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const img = document.getElementById('preview-image');
|
|
75
|
+
if (!img || !figSize.width_mm || !figSize.height_mm) {
|
|
76
|
+
console.log('[PanelDrag] Skipped - img or figSize not ready');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const rect = img.getBoundingClientRect();
|
|
81
|
+
const x = event.clientX - rect.left;
|
|
82
|
+
const y = event.clientY - rect.top;
|
|
83
|
+
|
|
84
|
+
// Convert to mm coordinates (upper-left origin)
|
|
85
|
+
const mmX = (x / rect.width) * figSize.width_mm;
|
|
86
|
+
const mmY = (y / rect.height) * figSize.height_mm;
|
|
87
|
+
|
|
88
|
+
// Find which panel was clicked (using expanded bounds including labels)
|
|
89
|
+
const panelIndex = findPanelAtPositionMm(mmX, mmY);
|
|
90
|
+
console.log('[PanelDrag] Click at mm:', mmX.toFixed(1), mmY.toFixed(1), '-> panel:', panelIndex);
|
|
91
|
+
|
|
92
|
+
if (panelIndex !== null) {
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
event.stopPropagation();
|
|
95
|
+
|
|
96
|
+
isDraggingPanel = true;
|
|
97
|
+
draggedPanelIndex = panelIndex;
|
|
98
|
+
dragStartPos = { x: event.clientX, y: event.clientY };
|
|
99
|
+
|
|
100
|
+
// Get current panel position (in mm)
|
|
101
|
+
const axKey = Object.keys(panelPositions).sort()[panelIndex];
|
|
102
|
+
const pos = panelPositions[axKey];
|
|
103
|
+
dragStartPanelPos = { ...pos };
|
|
104
|
+
|
|
105
|
+
// Create overlay if it doesn't exist
|
|
106
|
+
if (!panelDragOverlay) {
|
|
107
|
+
console.log('[PanelDrag] Creating overlay on-demand');
|
|
108
|
+
const zoomContainer = document.getElementById('zoom-container');
|
|
109
|
+
if (zoomContainer) {
|
|
110
|
+
panelDragOverlay = document.createElement('div');
|
|
111
|
+
panelDragOverlay.id = 'panel-drag-overlay';
|
|
112
|
+
panelDragOverlay.style.cssText = `
|
|
113
|
+
position: absolute;
|
|
114
|
+
border: 2px dashed #2563eb;
|
|
115
|
+
background: rgba(37, 99, 235, 0.1);
|
|
116
|
+
pointer-events: none;
|
|
117
|
+
display: none;
|
|
118
|
+
z-index: 1000;
|
|
119
|
+
`;
|
|
120
|
+
zoomContainer.appendChild(panelDragOverlay);
|
|
121
|
+
console.log('[PanelDrag] Overlay created on-demand');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Show drag overlay
|
|
126
|
+
if (panelDragOverlay) {
|
|
127
|
+
updateDragOverlayMm(pos, rect);
|
|
128
|
+
panelDragOverlay.style.display = 'block';
|
|
129
|
+
console.log('[PanelDrag] Overlay shown');
|
|
130
|
+
} else {
|
|
131
|
+
console.warn('[PanelDrag] Overlay still null after creation attempt');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Change cursor
|
|
135
|
+
document.body.style.cursor = 'move';
|
|
136
|
+
|
|
137
|
+
console.log('Started dragging panel', panelIndex);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Find which panel contains the given position (in mm, upper-left origin)
|
|
142
|
+
// Uses expanded bounds to include title, labels, and tick areas
|
|
143
|
+
function findPanelAtPositionMm(mmX, mmY) {
|
|
144
|
+
const axKeys = Object.keys(panelPositions).sort();
|
|
145
|
+
|
|
146
|
+
// Margins in mm to expand panel bounds for labels/title/ticks
|
|
147
|
+
const marginLeft = 15; // Space for y-axis label and ticks
|
|
148
|
+
const marginRight = 5; // Small buffer on right
|
|
149
|
+
const marginTop = 8; // Space for title
|
|
150
|
+
const marginBottom = 12; // Space for x-axis label and ticks
|
|
151
|
+
|
|
152
|
+
for (let i = 0; i < axKeys.length; i++) {
|
|
153
|
+
const pos = panelPositions[axKeys[i]];
|
|
154
|
+
|
|
155
|
+
// Expanded bounds including label/title areas
|
|
156
|
+
const expandedLeft = Math.max(0, pos.left - marginLeft);
|
|
157
|
+
const expandedTop = Math.max(0, pos.top - marginTop);
|
|
158
|
+
const expandedRight = Math.min(figSize.width_mm, pos.left + pos.width + marginRight);
|
|
159
|
+
const expandedBottom = Math.min(figSize.height_mm, pos.top + pos.height + marginBottom);
|
|
160
|
+
|
|
161
|
+
// Check if point is within expanded panel bounds
|
|
162
|
+
if (mmX >= expandedLeft && mmX <= expandedRight &&
|
|
163
|
+
mmY >= expandedTop && mmY <= expandedBottom) {
|
|
164
|
+
return i;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Handle mouse move during drag
|
|
171
|
+
function handlePanelDragMove(event) {
|
|
172
|
+
if (!isDraggingPanel) return;
|
|
173
|
+
|
|
174
|
+
event.preventDefault();
|
|
175
|
+
|
|
176
|
+
const img = document.getElementById('preview-image');
|
|
177
|
+
if (!img) return;
|
|
178
|
+
|
|
179
|
+
const rect = img.getBoundingClientRect();
|
|
180
|
+
|
|
181
|
+
// Calculate delta in mm
|
|
182
|
+
const deltaMmX = (event.clientX - dragStartPos.x) / rect.width * figSize.width_mm;
|
|
183
|
+
const deltaMmY = (event.clientY - dragStartPos.y) / rect.height * figSize.height_mm;
|
|
184
|
+
|
|
185
|
+
// Calculate new position (clamped to figure bounds)
|
|
186
|
+
const newLeft = Math.max(0, Math.min(figSize.width_mm - dragStartPanelPos.width, dragStartPanelPos.left + deltaMmX));
|
|
187
|
+
const newTop = Math.max(0, Math.min(figSize.height_mm - dragStartPanelPos.height, dragStartPanelPos.top + deltaMmY));
|
|
188
|
+
|
|
189
|
+
const newPos = {
|
|
190
|
+
left: newLeft,
|
|
191
|
+
top: newTop,
|
|
192
|
+
width: dragStartPanelPos.width,
|
|
193
|
+
height: dragStartPanelPos.height
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// Update visual overlay
|
|
197
|
+
updateDragOverlayMm(newPos, rect);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Update the drag overlay position (pos in mm, upper-left origin)
|
|
201
|
+
function updateDragOverlayMm(pos, imgRect) {
|
|
202
|
+
if (!panelDragOverlay || !figSize.width_mm) return;
|
|
203
|
+
|
|
204
|
+
// Convert mm to screen pixels
|
|
205
|
+
const scaleX = imgRect.width / figSize.width_mm;
|
|
206
|
+
const scaleY = imgRect.height / figSize.height_mm;
|
|
207
|
+
|
|
208
|
+
const left = pos.left * scaleX;
|
|
209
|
+
const top = pos.top * scaleY;
|
|
210
|
+
const width = pos.width * scaleX;
|
|
211
|
+
const height = pos.height * scaleY;
|
|
212
|
+
|
|
213
|
+
panelDragOverlay.style.left = `${left}px`;
|
|
214
|
+
panelDragOverlay.style.top = `${top}px`;
|
|
215
|
+
panelDragOverlay.style.width = `${width}px`;
|
|
216
|
+
panelDragOverlay.style.height = `${height}px`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Handle mouse up - complete the drag
|
|
220
|
+
async function handlePanelDragEnd(event) {
|
|
221
|
+
console.log('[PanelDrag] handlePanelDragEnd called, isDraggingPanel:', isDraggingPanel);
|
|
222
|
+
if (!isDraggingPanel) return;
|
|
223
|
+
|
|
224
|
+
// Hide overlay (with null check)
|
|
225
|
+
if (panelDragOverlay) {
|
|
226
|
+
panelDragOverlay.style.display = 'none';
|
|
227
|
+
console.log('[PanelDrag] Overlay hidden');
|
|
228
|
+
}
|
|
229
|
+
document.body.style.cursor = '';
|
|
230
|
+
|
|
231
|
+
const img = document.getElementById('preview-image');
|
|
232
|
+
if (!img) {
|
|
233
|
+
isDraggingPanel = false;
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const rect = img.getBoundingClientRect();
|
|
238
|
+
|
|
239
|
+
// Calculate final position in mm
|
|
240
|
+
const deltaMmX = (event.clientX - dragStartPos.x) / rect.width * figSize.width_mm;
|
|
241
|
+
const deltaMmY = (event.clientY - dragStartPos.y) / rect.height * figSize.height_mm;
|
|
242
|
+
|
|
243
|
+
const newLeft = Math.max(0, Math.min(figSize.width_mm - dragStartPanelPos.width, dragStartPanelPos.left + deltaMmX));
|
|
244
|
+
const newTop = Math.max(0, Math.min(figSize.height_mm - dragStartPanelPos.height, dragStartPanelPos.top + deltaMmY));
|
|
245
|
+
|
|
246
|
+
// Only update if position actually changed (threshold in mm)
|
|
247
|
+
const threshold = 1.0; // 1mm threshold
|
|
248
|
+
const deltaLeft = Math.abs(newLeft - dragStartPanelPos.left);
|
|
249
|
+
const deltaTop = Math.abs(newTop - dragStartPanelPos.top);
|
|
250
|
+
console.log('[PanelDrag] Delta: left=', deltaLeft.toFixed(2), 'top=', deltaTop.toFixed(2), 'threshold=', threshold);
|
|
251
|
+
|
|
252
|
+
if (deltaLeft > threshold || deltaTop > threshold) {
|
|
253
|
+
console.log('[PanelDrag] Applying new position:', newLeft.toFixed(2), newTop.toFixed(2));
|
|
254
|
+
// Apply the new position (in mm)
|
|
255
|
+
await applyDraggedPanelPosition(
|
|
256
|
+
draggedPanelIndex,
|
|
257
|
+
newLeft,
|
|
258
|
+
newTop,
|
|
259
|
+
dragStartPanelPos.width,
|
|
260
|
+
dragStartPanelPos.height
|
|
261
|
+
);
|
|
262
|
+
} else {
|
|
263
|
+
console.log('[PanelDrag] Movement below threshold, not updating');
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Reset state
|
|
267
|
+
isDraggingPanel = false;
|
|
268
|
+
draggedPanelIndex = null;
|
|
269
|
+
dragStartPos = null;
|
|
270
|
+
dragStartPanelPos = null;
|
|
271
|
+
console.log('[PanelDrag] Drag state reset');
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Apply the dragged panel position to the server (values in mm)
|
|
275
|
+
async function applyDraggedPanelPosition(axIndex, left, top, width, height) {
|
|
276
|
+
document.body.classList.add('loading');
|
|
277
|
+
|
|
278
|
+
try {
|
|
279
|
+
const response = await fetch('/update_axes_position', {
|
|
280
|
+
method: 'POST',
|
|
281
|
+
headers: { 'Content-Type': 'application/json' },
|
|
282
|
+
body: JSON.stringify({
|
|
283
|
+
ax_index: axIndex,
|
|
284
|
+
left: left,
|
|
285
|
+
top: top,
|
|
286
|
+
width: width,
|
|
287
|
+
height: height
|
|
288
|
+
})
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const data = await response.json();
|
|
292
|
+
|
|
293
|
+
if (data.success) {
|
|
294
|
+
// Update preview image and wait for it to load
|
|
295
|
+
const img = document.getElementById('preview-image');
|
|
296
|
+
if (img) {
|
|
297
|
+
await new Promise((resolve) => {
|
|
298
|
+
img.onload = resolve;
|
|
299
|
+
img.src = 'data:image/png;base64,' + data.image;
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Update image size
|
|
304
|
+
if (data.img_size) {
|
|
305
|
+
currentImgWidth = data.img_size.width;
|
|
306
|
+
currentImgHeight = data.img_size.height;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Update bboxes and hitmap
|
|
310
|
+
currentBboxes = data.bboxes;
|
|
311
|
+
loadHitmap();
|
|
312
|
+
updateHitRegions();
|
|
313
|
+
|
|
314
|
+
// Reload positions (now with correct image dimensions)
|
|
315
|
+
await loadPanelPositions();
|
|
316
|
+
|
|
317
|
+
console.log('Panel position updated via drag');
|
|
318
|
+
} else {
|
|
319
|
+
console.error('Failed to update position:', data.error);
|
|
320
|
+
}
|
|
321
|
+
} catch (error) {
|
|
322
|
+
console.error('Failed to update position:', error);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
document.body.classList.remove('loading');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Initialize on DOMContentLoaded
|
|
329
|
+
document.addEventListener('DOMContentLoaded', initPanelDrag);
|
|
330
|
+
"""
|
|
331
|
+
|
|
332
|
+
__all__ = ["SCRIPTS_PANEL_DRAG"]
|
|
333
|
+
|
|
334
|
+
# EOF
|