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.
Files changed (269) hide show
  1. figrecipe/__init__.py +161 -1030
  2. figrecipe/__main__.py +12 -0
  3. figrecipe/_api/__init__.py +48 -0
  4. figrecipe/_api/_extract.py +108 -0
  5. figrecipe/_api/_notebook.py +61 -0
  6. figrecipe/_api/_panel.py +113 -0
  7. figrecipe/_api/_save.py +287 -0
  8. figrecipe/_api/_seaborn_proxy.py +34 -0
  9. figrecipe/_api/_style_manager.py +153 -0
  10. figrecipe/_api/_subplots.py +333 -0
  11. figrecipe/_api/_validate.py +82 -0
  12. figrecipe/_cli/__init__.py +7 -0
  13. figrecipe/_cli/_compose.py +87 -0
  14. figrecipe/_cli/_convert.py +117 -0
  15. figrecipe/_cli/_crop.py +82 -0
  16. figrecipe/_cli/_edit.py +70 -0
  17. figrecipe/_cli/_extract.py +128 -0
  18. figrecipe/_cli/_fonts.py +47 -0
  19. figrecipe/_cli/_info.py +67 -0
  20. figrecipe/_cli/_main.py +58 -0
  21. figrecipe/_cli/_reproduce.py +79 -0
  22. figrecipe/_cli/_style.py +77 -0
  23. figrecipe/_cli/_validate.py +66 -0
  24. figrecipe/_cli/_version.py +50 -0
  25. figrecipe/_composition/__init__.py +32 -0
  26. figrecipe/_composition/_alignment.py +452 -0
  27. figrecipe/_composition/_compose.py +179 -0
  28. figrecipe/_composition/_import_axes.py +127 -0
  29. figrecipe/_composition/_visibility.py +125 -0
  30. figrecipe/_dev/__init__.py +4 -93
  31. figrecipe/_dev/_plotters.py +76 -0
  32. figrecipe/_dev/_run_demos.py +56 -0
  33. figrecipe/_dev/browser/__init__.py +69 -0
  34. figrecipe/_dev/browser/_audio.py +240 -0
  35. figrecipe/_dev/browser/_caption.py +356 -0
  36. figrecipe/_dev/browser/_click_effect.py +146 -0
  37. figrecipe/_dev/browser/_cursor.py +196 -0
  38. figrecipe/_dev/browser/_highlight.py +105 -0
  39. figrecipe/_dev/browser/_narration.py +237 -0
  40. figrecipe/_dev/browser/_recorder.py +446 -0
  41. figrecipe/_dev/browser/_utils.py +178 -0
  42. figrecipe/_dev/browser/_video_trim/__init__.py +152 -0
  43. figrecipe/_dev/browser/_video_trim/_detection.py +223 -0
  44. figrecipe/_dev/browser/_video_trim/_markers.py +140 -0
  45. figrecipe/_dev/demo_plotters/__init__.py +35 -166
  46. figrecipe/_dev/demo_plotters/_categories.py +81 -0
  47. figrecipe/_dev/demo_plotters/_figure_creators.py +119 -0
  48. figrecipe/_dev/demo_plotters/_helpers.py +31 -0
  49. figrecipe/_dev/demo_plotters/_registry.py +50 -0
  50. figrecipe/_dev/demo_plotters/bar_categorical/__init__.py +4 -0
  51. figrecipe/_dev/demo_plotters/contour_surface/__init__.py +4 -0
  52. figrecipe/_dev/demo_plotters/distribution/__init__.py +4 -0
  53. figrecipe/_dev/demo_plotters/image_matrix/__init__.py +4 -0
  54. figrecipe/_dev/demo_plotters/line_curve/__init__.py +4 -0
  55. figrecipe/_dev/demo_plotters/{plot_plot.py → line_curve/plot_plot.py} +3 -2
  56. figrecipe/_dev/demo_plotters/scatter_points/__init__.py +4 -0
  57. figrecipe/_dev/demo_plotters/special/__init__.py +4 -0
  58. figrecipe/_dev/demo_plotters/{plot_pie.py → special/plot_pie.py} +5 -1
  59. figrecipe/_dev/demo_plotters/spectral_signal/__init__.py +4 -0
  60. figrecipe/_dev/demo_plotters/vector_flow/__init__.py +4 -0
  61. figrecipe/_editor/__init__.py +61 -13
  62. figrecipe/_editor/_bbox/__init__.py +43 -0
  63. figrecipe/_editor/_bbox/_collections.py +177 -0
  64. figrecipe/_editor/_bbox/_elements.py +159 -0
  65. figrecipe/_editor/_bbox/_extract.py +402 -0
  66. figrecipe/_editor/_bbox/_extract_axes.py +370 -0
  67. figrecipe/_editor/_bbox/_extract_text.py +466 -0
  68. figrecipe/_editor/_bbox/_lines.py +173 -0
  69. figrecipe/_editor/_bbox/_transforms.py +146 -0
  70. figrecipe/_editor/_call_overrides.py +183 -0
  71. figrecipe/_editor/_datatable_plot_handlers.py +249 -0
  72. figrecipe/_editor/_figure_layout.py +211 -0
  73. figrecipe/_editor/_flask_app.py +200 -1030
  74. figrecipe/_editor/_helpers.py +251 -0
  75. figrecipe/_editor/_hitmap/__init__.py +76 -0
  76. figrecipe/_editor/_hitmap/_artists/__init__.py +21 -0
  77. figrecipe/_editor/_hitmap/_artists/_collections.py +345 -0
  78. figrecipe/_editor/_hitmap/_artists/_images.py +68 -0
  79. figrecipe/_editor/_hitmap/_artists/_lines.py +107 -0
  80. figrecipe/_editor/_hitmap/_artists/_patches.py +163 -0
  81. figrecipe/_editor/_hitmap/_artists/_text.py +190 -0
  82. figrecipe/_editor/_hitmap/_colors.py +181 -0
  83. figrecipe/_editor/_hitmap/_detect.py +194 -0
  84. figrecipe/_editor/_hitmap/_restore.py +154 -0
  85. figrecipe/_editor/_hitmap_main.py +182 -0
  86. figrecipe/_editor/_overrides.py +4 -1
  87. figrecipe/_editor/_plot_types_registry.py +190 -0
  88. figrecipe/_editor/_preferences.py +135 -0
  89. figrecipe/_editor/_render_overrides.py +507 -0
  90. figrecipe/_editor/_renderer.py +81 -186
  91. figrecipe/_editor/_routes_annotation.py +114 -0
  92. figrecipe/_editor/_routes_axis.py +482 -0
  93. figrecipe/_editor/_routes_captions.py +130 -0
  94. figrecipe/_editor/_routes_composition.py +270 -0
  95. figrecipe/_editor/_routes_core.py +126 -0
  96. figrecipe/_editor/_routes_datatable.py +364 -0
  97. figrecipe/_editor/_routes_element.py +335 -0
  98. figrecipe/_editor/_routes_files.py +443 -0
  99. figrecipe/_editor/_routes_image.py +200 -0
  100. figrecipe/_editor/_routes_snapshot.py +94 -0
  101. figrecipe/_editor/_routes_style.py +243 -0
  102. figrecipe/_editor/_templates/__init__.py +116 -1
  103. figrecipe/_editor/_templates/_html.py +154 -64
  104. figrecipe/_editor/_templates/_html_components/__init__.py +13 -0
  105. figrecipe/_editor/_templates/_html_components/_composition_toolbar.py +79 -0
  106. figrecipe/_editor/_templates/_html_components/_file_browser.py +41 -0
  107. figrecipe/_editor/_templates/_html_datatable.py +92 -0
  108. figrecipe/_editor/_templates/_scripts/__init__.py +178 -0
  109. figrecipe/_editor/_templates/_scripts/_accordion.py +328 -0
  110. figrecipe/_editor/_templates/_scripts/_annotation_drag.py +504 -0
  111. figrecipe/_editor/_templates/_scripts/_api.py +228 -0
  112. figrecipe/_editor/_templates/_scripts/_canvas_context_menu.py +182 -0
  113. figrecipe/_editor/_templates/_scripts/_captions.py +231 -0
  114. figrecipe/_editor/_templates/_scripts/_colors.py +485 -0
  115. figrecipe/_editor/_templates/_scripts/_composition.py +283 -0
  116. figrecipe/_editor/_templates/_scripts/_core.py +493 -0
  117. figrecipe/_editor/_templates/_scripts/_datatable/__init__.py +59 -0
  118. figrecipe/_editor/_templates/_scripts/_datatable/_cell_edit.py +97 -0
  119. figrecipe/_editor/_templates/_scripts/_datatable/_clipboard.py +164 -0
  120. figrecipe/_editor/_templates/_scripts/_datatable/_context_menu.py +221 -0
  121. figrecipe/_editor/_templates/_scripts/_datatable/_core.py +150 -0
  122. figrecipe/_editor/_templates/_scripts/_datatable/_editable.py +511 -0
  123. figrecipe/_editor/_templates/_scripts/_datatable/_import.py +161 -0
  124. figrecipe/_editor/_templates/_scripts/_datatable/_plot.py +261 -0
  125. figrecipe/_editor/_templates/_scripts/_datatable/_selection.py +438 -0
  126. figrecipe/_editor/_templates/_scripts/_datatable/_table.py +256 -0
  127. figrecipe/_editor/_templates/_scripts/_datatable/_tabs.py +354 -0
  128. figrecipe/_editor/_templates/_scripts/_debug_snapshot.py +186 -0
  129. figrecipe/_editor/_templates/_scripts/_element_editor.py +325 -0
  130. figrecipe/_editor/_templates/_scripts/_files.py +429 -0
  131. figrecipe/_editor/_templates/_scripts/_files_context_menu.py +240 -0
  132. figrecipe/_editor/_templates/_scripts/_hitmap.py +512 -0
  133. figrecipe/_editor/_templates/_scripts/_image_drop.py +428 -0
  134. figrecipe/_editor/_templates/_scripts/_inspector.py +315 -0
  135. figrecipe/_editor/_templates/_scripts/_labels.py +464 -0
  136. figrecipe/_editor/_templates/_scripts/_legend_drag.py +270 -0
  137. figrecipe/_editor/_templates/_scripts/_modals.py +226 -0
  138. figrecipe/_editor/_templates/_scripts/_multi_select.py +198 -0
  139. figrecipe/_editor/_templates/_scripts/_overlays.py +292 -0
  140. figrecipe/_editor/_templates/_scripts/_panel_drag.py +505 -0
  141. figrecipe/_editor/_templates/_scripts/_panel_drag_snapshot.py +33 -0
  142. figrecipe/_editor/_templates/_scripts/_panel_position.py +463 -0
  143. figrecipe/_editor/_templates/_scripts/_panel_resize.py +230 -0
  144. figrecipe/_editor/_templates/_scripts/_panel_snap.py +307 -0
  145. figrecipe/_editor/_templates/_scripts/_region_select.py +255 -0
  146. figrecipe/_editor/_templates/_scripts/_selection.py +244 -0
  147. figrecipe/_editor/_templates/_scripts/_sync.py +242 -0
  148. figrecipe/_editor/_templates/_scripts/_tabs.py +89 -0
  149. figrecipe/_editor/_templates/_scripts/_undo_redo.py +348 -0
  150. figrecipe/_editor/_templates/_scripts/_view_mode.py +107 -0
  151. figrecipe/_editor/_templates/_scripts/_zoom.py +212 -0
  152. figrecipe/_editor/_templates/_styles/__init__.py +78 -0
  153. figrecipe/_editor/_templates/_styles/_base.py +111 -0
  154. figrecipe/_editor/_templates/_styles/_buttons.py +327 -0
  155. figrecipe/_editor/_templates/_styles/_color_input.py +123 -0
  156. figrecipe/_editor/_templates/_styles/_composition.py +87 -0
  157. figrecipe/_editor/_templates/_styles/_controls.py +430 -0
  158. figrecipe/_editor/_templates/_styles/_datatable/__init__.py +40 -0
  159. figrecipe/_editor/_templates/_styles/_datatable/_editable.py +203 -0
  160. figrecipe/_editor/_templates/_styles/_datatable/_panel.py +268 -0
  161. figrecipe/_editor/_templates/_styles/_datatable/_table.py +479 -0
  162. figrecipe/_editor/_templates/_styles/_datatable/_toolbar.py +384 -0
  163. figrecipe/_editor/_templates/_styles/_datatable/_vars.py +123 -0
  164. figrecipe/_editor/_templates/_styles/_dynamic_props.py +144 -0
  165. figrecipe/_editor/_templates/_styles/_file_browser.py +466 -0
  166. figrecipe/_editor/_templates/_styles/_forms.py +224 -0
  167. figrecipe/_editor/_templates/_styles/_hitmap.py +191 -0
  168. figrecipe/_editor/_templates/_styles/_inspector.py +90 -0
  169. figrecipe/_editor/_templates/_styles/_labels.py +118 -0
  170. figrecipe/_editor/_templates/_styles/_modals.py +127 -0
  171. figrecipe/_editor/_templates/_styles/_overlays.py +130 -0
  172. figrecipe/_editor/_templates/_styles/_preview.py +430 -0
  173. figrecipe/_editor/_templates/_styles/_selection.py +73 -0
  174. figrecipe/_editor/_templates/_styles/_spinner.py +117 -0
  175. figrecipe/_editor/static/audio/click.mp3 +0 -0
  176. figrecipe/_editor/static/click.mp3 +0 -0
  177. figrecipe/_editor/static/icons/favicon.ico +0 -0
  178. figrecipe/_integrations/__init__.py +17 -0
  179. figrecipe/_integrations/_scitex_stats.py +298 -0
  180. figrecipe/_params/_DECORATION_METHODS.py +8 -0
  181. figrecipe/_recorder.py +63 -109
  182. figrecipe/_recorder_utils.py +124 -0
  183. figrecipe/_reproducer/__init__.py +18 -0
  184. figrecipe/_reproducer/_core.py +509 -0
  185. figrecipe/_reproducer/_custom_plots.py +279 -0
  186. figrecipe/_reproducer/_seaborn.py +100 -0
  187. figrecipe/_reproducer/_violin.py +186 -0
  188. figrecipe/_signatures/_kwargs.py +273 -0
  189. figrecipe/_signatures/_loader.py +21 -423
  190. figrecipe/_signatures/_parsing.py +147 -0
  191. figrecipe/_utils/__init__.py +3 -0
  192. figrecipe/_utils/_bundle.py +205 -0
  193. figrecipe/_wrappers/_axes.py +252 -895
  194. figrecipe/_wrappers/_axes_helpers.py +136 -0
  195. figrecipe/_wrappers/_axes_plots.py +418 -0
  196. figrecipe/_wrappers/_axes_seaborn.py +157 -0
  197. figrecipe/_wrappers/_caption_generator.py +218 -0
  198. figrecipe/_wrappers/_figure.py +188 -1
  199. figrecipe/_wrappers/_panel_labels.py +127 -0
  200. figrecipe/_wrappers/_plot_helpers.py +143 -0
  201. figrecipe/_wrappers/_stat_annotation.py +274 -0
  202. figrecipe/_wrappers/_violin_helpers.py +180 -0
  203. figrecipe/styles/__init__.py +8 -6
  204. figrecipe/styles/_dotdict.py +72 -0
  205. figrecipe/styles/_finalize.py +134 -0
  206. figrecipe/styles/_fonts.py +77 -0
  207. figrecipe/styles/_kwargs_converter.py +178 -0
  208. figrecipe/styles/_plot_styles.py +209 -0
  209. figrecipe/styles/_style_applier.py +42 -480
  210. figrecipe/styles/_style_loader.py +16 -192
  211. figrecipe/styles/_themes.py +151 -0
  212. figrecipe/styles/presets/MATPLOTLIB.yaml +2 -1
  213. figrecipe/styles/presets/SCITEX.yaml +40 -28
  214. figrecipe-0.9.0.dist-info/METADATA +427 -0
  215. figrecipe-0.9.0.dist-info/RECORD +277 -0
  216. figrecipe-0.9.0.dist-info/entry_points.txt +2 -0
  217. figrecipe/_editor/_bbox.py +0 -978
  218. figrecipe/_editor/_hitmap.py +0 -937
  219. figrecipe/_editor/_templates/_scripts.py +0 -2778
  220. figrecipe/_editor/_templates/_styles.py +0 -1326
  221. figrecipe/_reproducer.py +0 -975
  222. figrecipe-0.6.0.dist-info/METADATA +0 -394
  223. figrecipe-0.6.0.dist-info/RECORD +0 -90
  224. /figrecipe/_dev/demo_plotters/{plot_bar.py → bar_categorical/plot_bar.py} +0 -0
  225. /figrecipe/_dev/demo_plotters/{plot_barh.py → bar_categorical/plot_barh.py} +0 -0
  226. /figrecipe/_dev/demo_plotters/{plot_contour.py → contour_surface/plot_contour.py} +0 -0
  227. /figrecipe/_dev/demo_plotters/{plot_contourf.py → contour_surface/plot_contourf.py} +0 -0
  228. /figrecipe/_dev/demo_plotters/{plot_tricontour.py → contour_surface/plot_tricontour.py} +0 -0
  229. /figrecipe/_dev/demo_plotters/{plot_tricontourf.py → contour_surface/plot_tricontourf.py} +0 -0
  230. /figrecipe/_dev/demo_plotters/{plot_tripcolor.py → contour_surface/plot_tripcolor.py} +0 -0
  231. /figrecipe/_dev/demo_plotters/{plot_triplot.py → contour_surface/plot_triplot.py} +0 -0
  232. /figrecipe/_dev/demo_plotters/{plot_boxplot.py → distribution/plot_boxplot.py} +0 -0
  233. /figrecipe/_dev/demo_plotters/{plot_ecdf.py → distribution/plot_ecdf.py} +0 -0
  234. /figrecipe/_dev/demo_plotters/{plot_hist.py → distribution/plot_hist.py} +0 -0
  235. /figrecipe/_dev/demo_plotters/{plot_hist2d.py → distribution/plot_hist2d.py} +0 -0
  236. /figrecipe/_dev/demo_plotters/{plot_violinplot.py → distribution/plot_violinplot.py} +0 -0
  237. /figrecipe/_dev/demo_plotters/{plot_hexbin.py → image_matrix/plot_hexbin.py} +0 -0
  238. /figrecipe/_dev/demo_plotters/{plot_imshow.py → image_matrix/plot_imshow.py} +0 -0
  239. /figrecipe/_dev/demo_plotters/{plot_matshow.py → image_matrix/plot_matshow.py} +0 -0
  240. /figrecipe/_dev/demo_plotters/{plot_pcolor.py → image_matrix/plot_pcolor.py} +0 -0
  241. /figrecipe/_dev/demo_plotters/{plot_pcolormesh.py → image_matrix/plot_pcolormesh.py} +0 -0
  242. /figrecipe/_dev/demo_plotters/{plot_spy.py → image_matrix/plot_spy.py} +0 -0
  243. /figrecipe/_dev/demo_plotters/{plot_errorbar.py → line_curve/plot_errorbar.py} +0 -0
  244. /figrecipe/_dev/demo_plotters/{plot_fill.py → line_curve/plot_fill.py} +0 -0
  245. /figrecipe/_dev/demo_plotters/{plot_fill_between.py → line_curve/plot_fill_between.py} +0 -0
  246. /figrecipe/_dev/demo_plotters/{plot_fill_betweenx.py → line_curve/plot_fill_betweenx.py} +0 -0
  247. /figrecipe/_dev/demo_plotters/{plot_stackplot.py → line_curve/plot_stackplot.py} +0 -0
  248. /figrecipe/_dev/demo_plotters/{plot_stairs.py → line_curve/plot_stairs.py} +0 -0
  249. /figrecipe/_dev/demo_plotters/{plot_step.py → line_curve/plot_step.py} +0 -0
  250. /figrecipe/_dev/demo_plotters/{plot_scatter.py → scatter_points/plot_scatter.py} +0 -0
  251. /figrecipe/_dev/demo_plotters/{plot_eventplot.py → special/plot_eventplot.py} +0 -0
  252. /figrecipe/_dev/demo_plotters/{plot_loglog.py → special/plot_loglog.py} +0 -0
  253. /figrecipe/_dev/demo_plotters/{plot_semilogx.py → special/plot_semilogx.py} +0 -0
  254. /figrecipe/_dev/demo_plotters/{plot_semilogy.py → special/plot_semilogy.py} +0 -0
  255. /figrecipe/_dev/demo_plotters/{plot_stem.py → special/plot_stem.py} +0 -0
  256. /figrecipe/_dev/demo_plotters/{plot_acorr.py → spectral_signal/plot_acorr.py} +0 -0
  257. /figrecipe/_dev/demo_plotters/{plot_angle_spectrum.py → spectral_signal/plot_angle_spectrum.py} +0 -0
  258. /figrecipe/_dev/demo_plotters/{plot_cohere.py → spectral_signal/plot_cohere.py} +0 -0
  259. /figrecipe/_dev/demo_plotters/{plot_csd.py → spectral_signal/plot_csd.py} +0 -0
  260. /figrecipe/_dev/demo_plotters/{plot_magnitude_spectrum.py → spectral_signal/plot_magnitude_spectrum.py} +0 -0
  261. /figrecipe/_dev/demo_plotters/{plot_phase_spectrum.py → spectral_signal/plot_phase_spectrum.py} +0 -0
  262. /figrecipe/_dev/demo_plotters/{plot_psd.py → spectral_signal/plot_psd.py} +0 -0
  263. /figrecipe/_dev/demo_plotters/{plot_specgram.py → spectral_signal/plot_specgram.py} +0 -0
  264. /figrecipe/_dev/demo_plotters/{plot_xcorr.py → spectral_signal/plot_xcorr.py} +0 -0
  265. /figrecipe/_dev/demo_plotters/{plot_barbs.py → vector_flow/plot_barbs.py} +0 -0
  266. /figrecipe/_dev/demo_plotters/{plot_quiver.py → vector_flow/plot_quiver.py} +0 -0
  267. /figrecipe/_dev/demo_plotters/{plot_streamplot.py → vector_flow/plot_streamplot.py} +0 -0
  268. {figrecipe-0.6.0.dist-info → figrecipe-0.9.0.dist-info}/WHEEL +0 -0
  269. {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