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,428 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
"""External image drop JavaScript for the figure editor.
|
|
4
|
+
|
|
5
|
+
This module contains the JavaScript code for:
|
|
6
|
+
- Handling drag & drop of external images onto the editor
|
|
7
|
+
- Creating imshow panels from dropped images
|
|
8
|
+
- Handling recipe file drops
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
SCRIPTS_IMAGE_DROP = """
|
|
12
|
+
// ===== EXTERNAL IMAGE/FILE DROP =====
|
|
13
|
+
|
|
14
|
+
let dropOverlay = null;
|
|
15
|
+
|
|
16
|
+
// Initialize drop zone functionality
|
|
17
|
+
function initImageDrop() {
|
|
18
|
+
console.log('[ImageDrop] initImageDrop called');
|
|
19
|
+
const zoomContainer = document.getElementById('zoom-container');
|
|
20
|
+
const previewContainer = document.getElementById('preview-wrapper');
|
|
21
|
+
|
|
22
|
+
if (!previewContainer) {
|
|
23
|
+
console.error('[ImageDrop] preview-wrapper not found!');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Create drop overlay
|
|
28
|
+
dropOverlay = document.createElement('div');
|
|
29
|
+
dropOverlay.id = 'drop-overlay';
|
|
30
|
+
dropOverlay.innerHTML = `
|
|
31
|
+
<div class="drop-message">
|
|
32
|
+
<div class="drop-icon">📷</div>
|
|
33
|
+
<div class="drop-text">Drop image to add as panel</div>
|
|
34
|
+
<div class="drop-subtext">Supports PNG, JPG, GIF, YAML recipe files</div>
|
|
35
|
+
</div>
|
|
36
|
+
`;
|
|
37
|
+
dropOverlay.style.cssText = `
|
|
38
|
+
position: absolute;
|
|
39
|
+
top: 0;
|
|
40
|
+
left: 0;
|
|
41
|
+
right: 0;
|
|
42
|
+
bottom: 0;
|
|
43
|
+
background: rgba(37, 99, 235, 0.9);
|
|
44
|
+
display: none;
|
|
45
|
+
align-items: center;
|
|
46
|
+
justify-content: center;
|
|
47
|
+
z-index: 2000;
|
|
48
|
+
pointer-events: none;
|
|
49
|
+
`;
|
|
50
|
+
previewContainer.style.position = 'relative';
|
|
51
|
+
previewContainer.appendChild(dropOverlay);
|
|
52
|
+
|
|
53
|
+
// Style the drop message
|
|
54
|
+
const style = document.createElement('style');
|
|
55
|
+
style.textContent = `
|
|
56
|
+
.drop-message {
|
|
57
|
+
text-align: center;
|
|
58
|
+
color: white;
|
|
59
|
+
}
|
|
60
|
+
.drop-icon {
|
|
61
|
+
font-size: 64px;
|
|
62
|
+
margin-bottom: 16px;
|
|
63
|
+
}
|
|
64
|
+
.drop-text {
|
|
65
|
+
font-size: 24px;
|
|
66
|
+
font-weight: bold;
|
|
67
|
+
margin-bottom: 8px;
|
|
68
|
+
}
|
|
69
|
+
.drop-subtext {
|
|
70
|
+
font-size: 14px;
|
|
71
|
+
opacity: 0.8;
|
|
72
|
+
}
|
|
73
|
+
`;
|
|
74
|
+
document.head.appendChild(style);
|
|
75
|
+
|
|
76
|
+
// Add drag & drop event listeners
|
|
77
|
+
previewContainer.addEventListener('dragenter', handleDragEnter);
|
|
78
|
+
previewContainer.addEventListener('dragover', handleDragOver);
|
|
79
|
+
previewContainer.addEventListener('dragleave', handleDragLeave);
|
|
80
|
+
previewContainer.addEventListener('drop', handleDrop);
|
|
81
|
+
|
|
82
|
+
console.log('[ImageDrop] Drop zone initialized');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Handle drag enter
|
|
86
|
+
function handleDragEnter(event) {
|
|
87
|
+
event.preventDefault();
|
|
88
|
+
event.stopPropagation();
|
|
89
|
+
|
|
90
|
+
// Always show overlay if dragging files - browser restricts type info until drop
|
|
91
|
+
if (hasAnyFiles(event)) {
|
|
92
|
+
showDropOverlay();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Handle drag over
|
|
97
|
+
function handleDragOver(event) {
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
event.stopPropagation();
|
|
100
|
+
|
|
101
|
+
// Must call preventDefault to allow drop
|
|
102
|
+
if (hasAnyFiles(event)) {
|
|
103
|
+
event.dataTransfer.dropEffect = 'copy';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Check if event has any files (permissive check for dragenter/dragover)
|
|
108
|
+
function hasAnyFiles(event) {
|
|
109
|
+
// Check dataTransfer.types for 'Files' - most reliable cross-browser
|
|
110
|
+
if (event.dataTransfer.types) {
|
|
111
|
+
for (const type of event.dataTransfer.types) {
|
|
112
|
+
if (type === 'Files' || type === 'application/x-moz-file') {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Fallback: check items
|
|
118
|
+
const items = event.dataTransfer.items;
|
|
119
|
+
if (items && items.length > 0) {
|
|
120
|
+
for (const item of items) {
|
|
121
|
+
if (item.kind === 'file') {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Handle drag leave
|
|
130
|
+
function handleDragLeave(event) {
|
|
131
|
+
event.preventDefault();
|
|
132
|
+
event.stopPropagation();
|
|
133
|
+
|
|
134
|
+
// Only hide if leaving the container entirely
|
|
135
|
+
const rect = event.currentTarget.getBoundingClientRect();
|
|
136
|
+
if (event.clientX < rect.left || event.clientX > rect.right ||
|
|
137
|
+
event.clientY < rect.top || event.clientY > rect.bottom) {
|
|
138
|
+
hideDropOverlay();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// Handle drop
|
|
143
|
+
async function handleDrop(event) {
|
|
144
|
+
event.preventDefault();
|
|
145
|
+
event.stopPropagation();
|
|
146
|
+
hideDropOverlay();
|
|
147
|
+
|
|
148
|
+
const files = event.dataTransfer.files;
|
|
149
|
+
if (files.length === 0) {
|
|
150
|
+
// Try to get image from URL (dragged from browser)
|
|
151
|
+
const imageUrl = event.dataTransfer.getData('text/uri-list') ||
|
|
152
|
+
event.dataTransfer.getData('text/plain');
|
|
153
|
+
if (imageUrl && isImageUrl(imageUrl)) {
|
|
154
|
+
await handleImageUrl(imageUrl, event);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
console.log('[ImageDrop] No files dropped');
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
for (const file of files) {
|
|
162
|
+
if (isImageFile(file)) {
|
|
163
|
+
await handleImageFile(file, event);
|
|
164
|
+
} else if (isRecipeFile(file)) {
|
|
165
|
+
await handleRecipeFile(file);
|
|
166
|
+
} else {
|
|
167
|
+
console.log('[ImageDrop] Unsupported file type:', file.type);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Check if event has valid files
|
|
173
|
+
function hasValidFiles(event) {
|
|
174
|
+
const items = event.dataTransfer.items;
|
|
175
|
+
if (!items) return false;
|
|
176
|
+
|
|
177
|
+
for (const item of items) {
|
|
178
|
+
if (item.kind === 'file') {
|
|
179
|
+
const type = item.type;
|
|
180
|
+
// Accept known image/yaml types
|
|
181
|
+
if (type.startsWith('image/') ||
|
|
182
|
+
type === 'application/x-yaml' ||
|
|
183
|
+
type === 'text/yaml') {
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
// When dragging from file system, type may be empty
|
|
187
|
+
// Accept any file and filter by extension in handleDrop
|
|
188
|
+
if (type === '' || type === 'application/octet-stream') {
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// Also accept URLs (images dragged from browser)
|
|
193
|
+
if (item.kind === 'string' && item.type === 'text/uri-list') {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Check if file is an image
|
|
201
|
+
function isImageFile(file) {
|
|
202
|
+
if (file.type.startsWith('image/')) {
|
|
203
|
+
return true;
|
|
204
|
+
}
|
|
205
|
+
// Fallback to extension check when type is empty (Windows file drag)
|
|
206
|
+
const ext = file.name.toLowerCase().split('.').pop();
|
|
207
|
+
return ['png', 'jpg', 'jpeg', 'gif', 'webp', 'bmp', 'svg'].includes(ext);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Check if file is a recipe file
|
|
211
|
+
function isRecipeFile(file) {
|
|
212
|
+
return file.name.endsWith('.yaml') ||
|
|
213
|
+
file.name.endsWith('.yml') ||
|
|
214
|
+
file.type === 'application/x-yaml' ||
|
|
215
|
+
file.type === 'text/yaml';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Check if URL points to an image
|
|
219
|
+
function isImageUrl(url) {
|
|
220
|
+
const imageExtensions = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.bmp'];
|
|
221
|
+
const lowerUrl = url.toLowerCase();
|
|
222
|
+
return imageExtensions.some(ext => lowerUrl.includes(ext));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Handle dropped image file
|
|
226
|
+
async function handleImageFile(file, event) {
|
|
227
|
+
console.log('[ImageDrop] Processing image file:', file.name);
|
|
228
|
+
document.body.classList.add('loading');
|
|
229
|
+
|
|
230
|
+
try {
|
|
231
|
+
// Get drop position relative to image
|
|
232
|
+
const img = document.getElementById('preview-image');
|
|
233
|
+
let dropX = 0.5, dropY = 0.5; // Default to center
|
|
234
|
+
|
|
235
|
+
if (img && figSize.width_mm && figSize.height_mm) {
|
|
236
|
+
const rect = img.getBoundingClientRect();
|
|
237
|
+
const x = event.clientX - rect.left;
|
|
238
|
+
const y = event.clientY - rect.top;
|
|
239
|
+
dropX = Math.max(0, Math.min(1, x / rect.width));
|
|
240
|
+
dropY = Math.max(0, Math.min(1, y / rect.height));
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Read file as base64
|
|
244
|
+
const base64 = await fileToBase64(file);
|
|
245
|
+
|
|
246
|
+
// Send to server
|
|
247
|
+
const response = await fetch('/add_image_panel', {
|
|
248
|
+
method: 'POST',
|
|
249
|
+
headers: { 'Content-Type': 'application/json' },
|
|
250
|
+
body: JSON.stringify({
|
|
251
|
+
image_data: base64,
|
|
252
|
+
filename: file.name,
|
|
253
|
+
drop_x: dropX,
|
|
254
|
+
drop_y: dropY
|
|
255
|
+
})
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
const data = await response.json();
|
|
259
|
+
|
|
260
|
+
if (data.success) {
|
|
261
|
+
// Update preview
|
|
262
|
+
const previewImg = document.getElementById('preview-image');
|
|
263
|
+
if (previewImg) {
|
|
264
|
+
await new Promise((resolve) => {
|
|
265
|
+
previewImg.onload = resolve;
|
|
266
|
+
previewImg.src = 'data:image/png;base64,' + data.image;
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Update state
|
|
271
|
+
if (data.img_size) {
|
|
272
|
+
currentImgWidth = data.img_size.width;
|
|
273
|
+
currentImgHeight = data.img_size.height;
|
|
274
|
+
}
|
|
275
|
+
if (data.bboxes) {
|
|
276
|
+
currentBboxes = data.bboxes;
|
|
277
|
+
loadHitmap();
|
|
278
|
+
updateHitRegions();
|
|
279
|
+
}
|
|
280
|
+
await loadPanelPositions();
|
|
281
|
+
|
|
282
|
+
console.log('[ImageDrop] Image panel added successfully');
|
|
283
|
+
} else {
|
|
284
|
+
console.error('[ImageDrop] Failed to add image:', data.error);
|
|
285
|
+
alert('Failed to add image: ' + data.error);
|
|
286
|
+
}
|
|
287
|
+
} catch (error) {
|
|
288
|
+
console.error('[ImageDrop] Error processing image:', error);
|
|
289
|
+
alert('Error processing image: ' + error.message);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
document.body.classList.remove('loading');
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Handle image URL (dragged from browser)
|
|
296
|
+
async function handleImageUrl(url, event) {
|
|
297
|
+
console.log('[ImageDrop] Processing image URL:', url);
|
|
298
|
+
document.body.classList.add('loading');
|
|
299
|
+
|
|
300
|
+
try {
|
|
301
|
+
// Get drop position
|
|
302
|
+
const img = document.getElementById('preview-image');
|
|
303
|
+
let dropX = 0.5, dropY = 0.5;
|
|
304
|
+
|
|
305
|
+
if (img && figSize.width_mm && figSize.height_mm) {
|
|
306
|
+
const rect = img.getBoundingClientRect();
|
|
307
|
+
const x = event.clientX - rect.left;
|
|
308
|
+
const y = event.clientY - rect.top;
|
|
309
|
+
dropX = Math.max(0, Math.min(1, x / rect.width));
|
|
310
|
+
dropY = Math.max(0, Math.min(1, y / rect.height));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Send URL to server
|
|
314
|
+
const response = await fetch('/add_image_from_url', {
|
|
315
|
+
method: 'POST',
|
|
316
|
+
headers: { 'Content-Type': 'application/json' },
|
|
317
|
+
body: JSON.stringify({
|
|
318
|
+
url: url,
|
|
319
|
+
drop_x: dropX,
|
|
320
|
+
drop_y: dropY
|
|
321
|
+
})
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
const data = await response.json();
|
|
325
|
+
|
|
326
|
+
if (data.success) {
|
|
327
|
+
// Update preview
|
|
328
|
+
const previewImg = document.getElementById('preview-image');
|
|
329
|
+
if (previewImg) {
|
|
330
|
+
await new Promise((resolve) => {
|
|
331
|
+
previewImg.onload = resolve;
|
|
332
|
+
previewImg.src = 'data:image/png;base64,' + data.image;
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (data.img_size) {
|
|
337
|
+
currentImgWidth = data.img_size.width;
|
|
338
|
+
currentImgHeight = data.img_size.height;
|
|
339
|
+
}
|
|
340
|
+
if (data.bboxes) {
|
|
341
|
+
currentBboxes = data.bboxes;
|
|
342
|
+
loadHitmap();
|
|
343
|
+
updateHitRegions();
|
|
344
|
+
}
|
|
345
|
+
await loadPanelPositions();
|
|
346
|
+
|
|
347
|
+
console.log('[ImageDrop] Image from URL added successfully');
|
|
348
|
+
} else {
|
|
349
|
+
console.error('[ImageDrop] Failed to add image from URL:', data.error);
|
|
350
|
+
alert('Failed to add image: ' + data.error);
|
|
351
|
+
}
|
|
352
|
+
} catch (error) {
|
|
353
|
+
console.error('[ImageDrop] Error processing URL:', error);
|
|
354
|
+
alert('Error processing image URL: ' + error.message);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
document.body.classList.remove('loading');
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Handle dropped recipe file
|
|
361
|
+
async function handleRecipeFile(file) {
|
|
362
|
+
console.log('[ImageDrop] Processing recipe file:', file.name);
|
|
363
|
+
document.body.classList.add('loading');
|
|
364
|
+
|
|
365
|
+
try {
|
|
366
|
+
const content = await file.text();
|
|
367
|
+
|
|
368
|
+
const response = await fetch('/load_recipe', {
|
|
369
|
+
method: 'POST',
|
|
370
|
+
headers: { 'Content-Type': 'application/json' },
|
|
371
|
+
body: JSON.stringify({
|
|
372
|
+
recipe_content: content,
|
|
373
|
+
filename: file.name
|
|
374
|
+
})
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
const data = await response.json();
|
|
378
|
+
|
|
379
|
+
if (data.success) {
|
|
380
|
+
// Reload the editor with new figure
|
|
381
|
+
window.location.reload();
|
|
382
|
+
} else {
|
|
383
|
+
console.error('[ImageDrop] Failed to load recipe:', data.error);
|
|
384
|
+
alert('Failed to load recipe: ' + data.error);
|
|
385
|
+
}
|
|
386
|
+
} catch (error) {
|
|
387
|
+
console.error('[ImageDrop] Error processing recipe:', error);
|
|
388
|
+
alert('Error processing recipe: ' + error.message);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
document.body.classList.remove('loading');
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Convert file to base64
|
|
395
|
+
function fileToBase64(file) {
|
|
396
|
+
return new Promise((resolve, reject) => {
|
|
397
|
+
const reader = new FileReader();
|
|
398
|
+
reader.onload = () => {
|
|
399
|
+
// Remove data URL prefix (e.g., "data:image/png;base64,")
|
|
400
|
+
const base64 = reader.result.split(',')[1];
|
|
401
|
+
resolve(base64);
|
|
402
|
+
};
|
|
403
|
+
reader.onerror = reject;
|
|
404
|
+
reader.readAsDataURL(file);
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
// Show drop overlay
|
|
409
|
+
function showDropOverlay() {
|
|
410
|
+
if (dropOverlay) {
|
|
411
|
+
dropOverlay.style.display = 'flex';
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Hide drop overlay
|
|
416
|
+
function hideDropOverlay() {
|
|
417
|
+
if (dropOverlay) {
|
|
418
|
+
dropOverlay.style.display = 'none';
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Initialize on DOMContentLoaded
|
|
423
|
+
document.addEventListener('DOMContentLoaded', initImageDrop);
|
|
424
|
+
"""
|
|
425
|
+
|
|
426
|
+
__all__ = ["SCRIPTS_IMAGE_DROP"]
|
|
427
|
+
|
|
428
|
+
# EOF
|