scitex 2.8.1__py3-none-any.whl → 2.10.2__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 (415) hide show
  1. scitex/__init__.py +15 -7
  2. scitex/__version__.py +1 -2
  3. scitex/_install_guide.py +250 -0
  4. scitex/_optional_deps.py +206 -39
  5. scitex/ai/_gen_ai/_Groq.py +2 -4
  6. scitex/ai/_gen_ai/_OpenAI.py +5 -2
  7. scitex/ai/_gen_ai/_Perplexity.py +20 -6
  8. scitex/audio/__init__.py +24 -15
  9. scitex/audio/_cross_process_lock.py +139 -0
  10. scitex/audio/_mcp_handlers.py +256 -0
  11. scitex/audio/_mcp_tool_schemas.py +203 -0
  12. scitex/audio/engines/elevenlabs_engine.py +5 -2
  13. scitex/audio/mcp_server.py +98 -457
  14. scitex/bridge/__init__.py +30 -19
  15. scitex/bridge/_figrecipe.py +245 -0
  16. scitex/bridge/_helpers.py +2 -1
  17. scitex/bridge/_plt_vis.py +23 -10
  18. scitex/bridge/_stats_plt.py +18 -5
  19. scitex/bridge/_stats_vis.py +16 -2
  20. scitex/browser/__init__.py +84 -44
  21. scitex/browser/automation/__init__.py +5 -1
  22. scitex/browser/core/BrowserMixin.py +17 -4
  23. scitex/browser/core/__init__.py +11 -2
  24. scitex/browser/remote/CaptchaHandler.py +1 -1
  25. scitex/browser/remote/ZenRowsAPIClient.py +1 -1
  26. scitex/capture/grid.py +487 -0
  27. scitex/capture/mcp_handlers.py +401 -0
  28. scitex/capture/mcp_tool_defs.py +192 -0
  29. scitex/capture/mcp_tools.py +241 -0
  30. scitex/capture/mcp_utils.py +30 -0
  31. scitex/cli/convert.py +421 -0
  32. scitex/cli/main.py +6 -4
  33. scitex/datetime/__init__.py +46 -0
  34. scitex/datetime/_linspace.py +100 -0
  35. scitex/datetime/_normalize_timestamp.py +306 -0
  36. scitex/db/_delete_duplicates.py +4 -4
  37. scitex/db/_sqlite3/_delete_duplicates.py +11 -2
  38. scitex/dev/plt/__init__.py +61 -62
  39. scitex/dev/plt/demo_plotters/__init__.py +0 -0
  40. scitex/dev/plt/demo_plotters/plot_mpl_axhline.py +28 -0
  41. scitex/dev/plt/demo_plotters/plot_mpl_axhspan.py +28 -0
  42. scitex/dev/plt/demo_plotters/plot_mpl_axvline.py +28 -0
  43. scitex/dev/plt/demo_plotters/plot_mpl_axvspan.py +28 -0
  44. scitex/dev/plt/demo_plotters/plot_mpl_bar.py +29 -0
  45. scitex/dev/plt/demo_plotters/plot_mpl_barh.py +29 -0
  46. scitex/dev/plt/demo_plotters/plot_mpl_boxplot.py +28 -0
  47. scitex/dev/plt/demo_plotters/plot_mpl_contour.py +31 -0
  48. scitex/dev/plt/demo_plotters/plot_mpl_contourf.py +31 -0
  49. scitex/dev/plt/demo_plotters/plot_mpl_errorbar.py +30 -0
  50. scitex/dev/plt/demo_plotters/plot_mpl_eventplot.py +28 -0
  51. scitex/dev/plt/demo_plotters/plot_mpl_fill.py +30 -0
  52. scitex/dev/plt/demo_plotters/plot_mpl_fill_between.py +31 -0
  53. scitex/dev/plt/demo_plotters/plot_mpl_hexbin.py +28 -0
  54. scitex/dev/plt/demo_plotters/plot_mpl_hist.py +28 -0
  55. scitex/dev/plt/demo_plotters/plot_mpl_hist2d.py +28 -0
  56. scitex/dev/plt/demo_plotters/plot_mpl_imshow.py +29 -0
  57. scitex/dev/plt/demo_plotters/plot_mpl_pcolormesh.py +31 -0
  58. scitex/dev/plt/demo_plotters/plot_mpl_pie.py +29 -0
  59. scitex/dev/plt/demo_plotters/plot_mpl_plot.py +29 -0
  60. scitex/dev/plt/demo_plotters/plot_mpl_quiver.py +31 -0
  61. scitex/dev/plt/demo_plotters/plot_mpl_scatter.py +28 -0
  62. scitex/dev/plt/demo_plotters/plot_mpl_stackplot.py +31 -0
  63. scitex/dev/plt/demo_plotters/plot_mpl_stem.py +29 -0
  64. scitex/dev/plt/demo_plotters/plot_mpl_step.py +29 -0
  65. scitex/dev/plt/demo_plotters/plot_mpl_violinplot.py +28 -0
  66. scitex/dev/plt/demo_plotters/plot_sns_barplot.py +29 -0
  67. scitex/dev/plt/demo_plotters/plot_sns_boxplot.py +29 -0
  68. scitex/dev/plt/demo_plotters/plot_sns_heatmap.py +28 -0
  69. scitex/dev/plt/demo_plotters/plot_sns_histplot.py +29 -0
  70. scitex/dev/plt/demo_plotters/plot_sns_kdeplot.py +29 -0
  71. scitex/dev/plt/demo_plotters/plot_sns_lineplot.py +31 -0
  72. scitex/dev/plt/demo_plotters/plot_sns_scatterplot.py +29 -0
  73. scitex/dev/plt/demo_plotters/plot_sns_stripplot.py +29 -0
  74. scitex/dev/plt/demo_plotters/plot_sns_swarmplot.py +29 -0
  75. scitex/dev/plt/demo_plotters/plot_sns_violinplot.py +29 -0
  76. scitex/dev/plt/demo_plotters/plot_stx_bar.py +29 -0
  77. scitex/dev/plt/demo_plotters/plot_stx_barh.py +29 -0
  78. scitex/dev/plt/demo_plotters/plot_stx_box.py +28 -0
  79. scitex/dev/plt/demo_plotters/plot_stx_boxplot.py +28 -0
  80. scitex/dev/plt/demo_plotters/plot_stx_conf_mat.py +28 -0
  81. scitex/dev/plt/demo_plotters/plot_stx_contour.py +31 -0
  82. scitex/dev/plt/demo_plotters/plot_stx_ecdf.py +28 -0
  83. scitex/dev/plt/demo_plotters/plot_stx_errorbar.py +30 -0
  84. scitex/dev/plt/demo_plotters/plot_stx_fill_between.py +31 -0
  85. scitex/dev/plt/demo_plotters/plot_stx_fillv.py +28 -0
  86. scitex/dev/plt/demo_plotters/plot_stx_heatmap.py +28 -0
  87. scitex/dev/plt/demo_plotters/plot_stx_image.py +28 -0
  88. scitex/dev/plt/demo_plotters/plot_stx_imshow.py +28 -0
  89. scitex/dev/plt/demo_plotters/plot_stx_joyplot.py +28 -0
  90. scitex/dev/plt/demo_plotters/plot_stx_kde.py +28 -0
  91. scitex/dev/plt/demo_plotters/plot_stx_line.py +28 -0
  92. scitex/dev/plt/demo_plotters/plot_stx_mean_ci.py +28 -0
  93. scitex/dev/plt/demo_plotters/plot_stx_mean_std.py +28 -0
  94. scitex/dev/plt/demo_plotters/plot_stx_median_iqr.py +28 -0
  95. scitex/dev/plt/demo_plotters/plot_stx_raster.py +28 -0
  96. scitex/dev/plt/demo_plotters/plot_stx_rectangle.py +28 -0
  97. scitex/dev/plt/demo_plotters/plot_stx_scatter.py +29 -0
  98. scitex/dev/plt/demo_plotters/plot_stx_shaded_line.py +29 -0
  99. scitex/dev/plt/demo_plotters/plot_stx_violin.py +28 -0
  100. scitex/dev/plt/demo_plotters/plot_stx_violinplot.py +28 -0
  101. scitex/dev/plt/mpl/get_dir_ax.py +46 -0
  102. scitex/dev/plt/mpl/get_signatures.py +176 -0
  103. scitex/dev/plt/mpl/get_signatures_details.py +522 -0
  104. scitex/dict/_pop_keys.py +1 -7
  105. scitex/dsp/__init__.py +15 -10
  106. scitex/dsp/add_noise.py +5 -2
  107. scitex/dsp/example.py +35 -22
  108. scitex/dsp/filt.py +8 -3
  109. scitex/dsp/reference.py +3 -2
  110. scitex/dsp/utils/__init__.py +2 -1
  111. scitex/dsp/utils/_differential_bandpass_filters.py +14 -4
  112. scitex/dt/__init__.py +39 -2
  113. scitex/errors.py +82 -521
  114. scitex/fig/__init__.py +4 -4
  115. scitex/fig/editor/edit/panel_loader.py +1 -1
  116. scitex/fig/io/_bundle.py +7 -7
  117. scitex/fts/README.md +262 -0
  118. scitex/fts/TODO.md +66 -0
  119. scitex/fts/__init__.py +90 -0
  120. scitex/fts/_bundle/README_IN_BUNDLE.md +102 -0
  121. scitex/fts/_bundle/_FTS.py +657 -0
  122. scitex/fts/_bundle/__init__.py +38 -0
  123. scitex/fts/_bundle/_children.py +216 -0
  124. scitex/fts/_bundle/_conversion/__init__.py +15 -0
  125. scitex/fts/_bundle/_conversion/_bundle2dict.py +44 -0
  126. scitex/fts/_bundle/_conversion/_dict2bundle.py +50 -0
  127. scitex/fts/_bundle/_dataclasses/_Axes.py +57 -0
  128. scitex/fts/_bundle/_dataclasses/_BBox.py +54 -0
  129. scitex/fts/_bundle/_dataclasses/_ColumnDef.py +72 -0
  130. scitex/fts/_bundle/_dataclasses/_DataFormat.py +40 -0
  131. scitex/fts/_bundle/_dataclasses/_DataInfo.py +135 -0
  132. scitex/fts/_bundle/_dataclasses/_DataSource.py +44 -0
  133. scitex/fts/_bundle/_dataclasses/_Node.py +319 -0
  134. scitex/fts/_bundle/_dataclasses/_NodeRefs.py +45 -0
  135. scitex/fts/_bundle/_dataclasses/_SizeMM.py +38 -0
  136. scitex/fts/_bundle/_dataclasses/__init__.py +35 -0
  137. scitex/fts/_bundle/_extractors/__init__.py +32 -0
  138. scitex/fts/_bundle/_extractors/_extract_bar.py +131 -0
  139. scitex/fts/_bundle/_extractors/_extract_line.py +71 -0
  140. scitex/fts/_bundle/_extractors/_extract_scatter.py +79 -0
  141. scitex/fts/_bundle/_loader.py +134 -0
  142. scitex/fts/_bundle/_mpl_helpers.py +389 -0
  143. scitex/fts/_bundle/_saver.py +269 -0
  144. scitex/fts/_bundle/_storage.py +200 -0
  145. scitex/fts/_bundle/_utils/__init__.py +55 -0
  146. scitex/fts/_bundle/_utils/_const.py +26 -0
  147. scitex/fts/_bundle/_utils/_errors.py +73 -0
  148. scitex/fts/_bundle/_utils/_generate.py +21 -0
  149. scitex/fts/_bundle/_utils/_types.py +76 -0
  150. scitex/fts/_bundle/_validation.py +434 -0
  151. scitex/fts/_bundle/_zipbundle.py +165 -0
  152. scitex/fts/_fig/__init__.py +22 -0
  153. scitex/fts/_fig/_backend/__init__.py +53 -0
  154. scitex/fts/_fig/_backend/_export.py +165 -0
  155. scitex/fts/_fig/_backend/_parser.py +188 -0
  156. scitex/fts/_fig/_backend/_render.py +538 -0
  157. scitex/fts/_fig/_composite.py +345 -0
  158. scitex/fts/_fig/_dataclasses/_ChannelEncoding.py +46 -0
  159. scitex/fts/_fig/_dataclasses/_Encoding.py +82 -0
  160. scitex/fts/_fig/_dataclasses/_Theme.py +441 -0
  161. scitex/fts/_fig/_dataclasses/_TraceEncoding.py +52 -0
  162. scitex/fts/_fig/_dataclasses/__init__.py +47 -0
  163. scitex/fts/_fig/_editor/__init__.py +14 -0
  164. scitex/fts/_fig/_editor/_cui/__init__.py +33 -0
  165. scitex/fts/_fig/_editor/_cui/_backend_detector.py +39 -0
  166. scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +366 -0
  167. scitex/fts/_fig/_editor/_cui/_editor_launcher.py +175 -0
  168. scitex/fts/_fig/_editor/_cui/_manual_handler.py +52 -0
  169. scitex/fts/_fig/_editor/_cui/_panel_loader.py +246 -0
  170. scitex/fts/_fig/_editor/_cui/_path_resolver.py +66 -0
  171. scitex/fts/_fig/_editor/_defaults.py +300 -0
  172. scitex/fts/_fig/_editor/_gui/__init__.py +11 -0
  173. scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +20 -0
  174. scitex/fts/_fig/_editor/_gui/_flask_editor/_bbox.py +1339 -0
  175. scitex/fts/_fig/_editor/_gui/_flask_editor/_core.py +1688 -0
  176. scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +664 -0
  177. scitex/fts/_fig/_editor/_gui/_flask_editor/_renderer.py +853 -0
  178. scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +79 -0
  179. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +41 -0
  180. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +16 -0
  181. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +85 -0
  182. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +217 -0
  183. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +93 -0
  184. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +57 -0
  185. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +112 -0
  186. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +59 -0
  187. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +212 -0
  188. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/canvas.css +176 -0
  189. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +190 -0
  190. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +59 -0
  191. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +45 -0
  192. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +95 -0
  193. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +101 -0
  194. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +138 -0
  195. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +31 -0
  196. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +7 -0
  197. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +56 -0
  198. scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +78 -0
  199. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +314 -0
  200. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +107 -0
  201. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +54 -0
  202. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +172 -0
  203. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +258 -0
  204. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +48 -0
  205. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +71 -0
  206. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +288 -0
  207. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +143 -0
  208. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +245 -0
  209. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +992 -0
  210. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +339 -0
  211. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +286 -0
  212. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +371 -0
  213. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +293 -0
  214. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +426 -0
  215. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +152 -0
  216. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +265 -0
  217. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +184 -0
  218. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +57 -0
  219. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +100 -0
  220. scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +34 -0
  221. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +124 -0
  222. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +851 -0
  223. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +4932 -0
  224. scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +1657 -0
  225. scitex/fts/_fig/_editor/_gui/_flask_editor.py +36 -0
  226. scitex/fts/_fig/_models/_Annotations.py +115 -0
  227. scitex/fts/_fig/_models/_Axes.py +152 -0
  228. scitex/fts/_fig/_models/_Figure.py +138 -0
  229. scitex/fts/_fig/_models/_Guides.py +104 -0
  230. scitex/fts/_fig/_models/_Plot.py +123 -0
  231. scitex/fts/_fig/_models/_Styles.py +245 -0
  232. scitex/fts/_fig/_models/__init__.py +80 -0
  233. scitex/fts/_fig/_models/_plot_types/__init__.py +156 -0
  234. scitex/fts/_fig/_models/_plot_types/_bar.py +43 -0
  235. scitex/fts/_fig/_models/_plot_types/_box.py +38 -0
  236. scitex/fts/_fig/_models/_plot_types/_distribution.py +36 -0
  237. scitex/fts/_fig/_models/_plot_types/_errorbar.py +60 -0
  238. scitex/fts/_fig/_models/_plot_types/_histogram.py +30 -0
  239. scitex/fts/_fig/_models/_plot_types/_image.py +61 -0
  240. scitex/fts/_fig/_models/_plot_types/_line.py +57 -0
  241. scitex/fts/_fig/_models/_plot_types/_scatter.py +30 -0
  242. scitex/fts/_fig/_models/_plot_types/_seaborn.py +121 -0
  243. scitex/fts/_fig/_models/_plot_types/_violin.py +36 -0
  244. scitex/fts/_fig/_utils/__init__.py +129 -0
  245. scitex/fts/_fig/_utils/_auto_layout.py +127 -0
  246. scitex/fts/_fig/_utils/_calc_bounds.py +111 -0
  247. scitex/fts/_fig/_utils/_const_sizes.py +48 -0
  248. scitex/fts/_fig/_utils/_convert_coords.py +77 -0
  249. scitex/fts/_fig/_utils/_get_template.py +178 -0
  250. scitex/fts/_fig/_utils/_normalize.py +73 -0
  251. scitex/fts/_fig/_utils/_plot_layout.py +397 -0
  252. scitex/fts/_fig/_utils/_validate.py +197 -0
  253. scitex/fts/_kinds/__init__.py +45 -0
  254. scitex/fts/_kinds/_figure/__init__.py +19 -0
  255. scitex/fts/_kinds/_figure/_composite.py +345 -0
  256. scitex/fts/_kinds/_plot/__init__.py +25 -0
  257. scitex/fts/_kinds/_plot/_backend/__init__.py +53 -0
  258. scitex/fts/_kinds/_plot/_backend/_export.py +165 -0
  259. scitex/fts/_kinds/_plot/_backend/_parser.py +188 -0
  260. scitex/fts/_kinds/_plot/_backend/_render.py +538 -0
  261. scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +46 -0
  262. scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +82 -0
  263. scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +441 -0
  264. scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +52 -0
  265. scitex/fts/_kinds/_plot/_dataclasses/__init__.py +47 -0
  266. scitex/fts/_kinds/_plot/_models/_Annotations.py +115 -0
  267. scitex/fts/_kinds/_plot/_models/_Axes.py +152 -0
  268. scitex/fts/_kinds/_plot/_models/_Figure.py +138 -0
  269. scitex/fts/_kinds/_plot/_models/_Guides.py +104 -0
  270. scitex/fts/_kinds/_plot/_models/_Plot.py +123 -0
  271. scitex/fts/_kinds/_plot/_models/_Styles.py +245 -0
  272. scitex/fts/_kinds/_plot/_models/__init__.py +80 -0
  273. scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +156 -0
  274. scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +43 -0
  275. scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +38 -0
  276. scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +36 -0
  277. scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +60 -0
  278. scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +30 -0
  279. scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +61 -0
  280. scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +57 -0
  281. scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +30 -0
  282. scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +121 -0
  283. scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +36 -0
  284. scitex/fts/_kinds/_plot/_utils/__init__.py +129 -0
  285. scitex/fts/_kinds/_plot/_utils/_auto_layout.py +127 -0
  286. scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +111 -0
  287. scitex/fts/_kinds/_plot/_utils/_const_sizes.py +48 -0
  288. scitex/fts/_kinds/_plot/_utils/_convert_coords.py +77 -0
  289. scitex/fts/_kinds/_plot/_utils/_get_template.py +178 -0
  290. scitex/fts/_kinds/_plot/_utils/_normalize.py +73 -0
  291. scitex/fts/_kinds/_plot/_utils/_plot_layout.py +397 -0
  292. scitex/fts/_kinds/_plot/_utils/_validate.py +197 -0
  293. scitex/fts/_kinds/_shape/__init__.py +141 -0
  294. scitex/fts/_kinds/_stats/__init__.py +56 -0
  295. scitex/fts/_kinds/_stats/_dataclasses/_Stats.py +423 -0
  296. scitex/fts/_kinds/_stats/_dataclasses/__init__.py +48 -0
  297. scitex/fts/_kinds/_table/__init__.py +72 -0
  298. scitex/fts/_kinds/_table/_latex/__init__.py +93 -0
  299. scitex/fts/_kinds/_table/_latex/_editor/__init__.py +11 -0
  300. scitex/fts/_kinds/_table/_latex/_editor/_app.py +725 -0
  301. scitex/fts/_kinds/_table/_latex/_export.py +279 -0
  302. scitex/fts/_kinds/_table/_latex/_figure_exporter.py +153 -0
  303. scitex/fts/_kinds/_table/_latex/_stats_formatter.py +274 -0
  304. scitex/fts/_kinds/_table/_latex/_table_exporter.py +362 -0
  305. scitex/fts/_kinds/_table/_latex/_utils.py +369 -0
  306. scitex/fts/_kinds/_table/_latex/_validator.py +445 -0
  307. scitex/fts/_kinds/_text/__init__.py +77 -0
  308. scitex/fts/_schemas/data_info.schema.json +75 -0
  309. scitex/fts/_schemas/encoding.schema.json +90 -0
  310. scitex/fts/_schemas/node.schema.json +145 -0
  311. scitex/fts/_schemas/render_manifest.schema.json +62 -0
  312. scitex/fts/_schemas/stats.schema.json +132 -0
  313. scitex/fts/_schemas/theme.schema.json +141 -0
  314. scitex/fts/_stats/__init__.py +48 -0
  315. scitex/fts/_stats/_dataclasses/_Stats.py +423 -0
  316. scitex/fts/_stats/_dataclasses/__init__.py +48 -0
  317. scitex/fts/_tables/__init__.py +65 -0
  318. scitex/fts/_tables/_latex/__init__.py +93 -0
  319. scitex/fts/_tables/_latex/_editor/__init__.py +11 -0
  320. scitex/fts/_tables/_latex/_editor/_app.py +725 -0
  321. scitex/fts/_tables/_latex/_export.py +279 -0
  322. scitex/fts/_tables/_latex/_figure_exporter.py +153 -0
  323. scitex/fts/_tables/_latex/_stats_formatter.py +274 -0
  324. scitex/fts/_tables/_latex/_table_exporter.py +362 -0
  325. scitex/fts/_tables/_latex/_utils.py +369 -0
  326. scitex/fts/_tables/_latex/_validator.py +445 -0
  327. scitex/gen/__init__.py +66 -25
  328. scitex/gen/misc.py +28 -0
  329. scitex/io/__init__.py +47 -32
  330. scitex/io/_load.py +87 -36
  331. scitex/io/_load_modules/__init__.py +10 -7
  332. scitex/io/_load_modules/_pandas.py +6 -1
  333. scitex/io/_save.py +299 -1556
  334. scitex/io/_save_modules/__init__.py +76 -19
  335. scitex/io/_save_modules/_figure_utils.py +90 -0
  336. scitex/io/_save_modules/_image_csv.py +497 -0
  337. scitex/io/_save_modules/_legends.py +91 -0
  338. scitex/io/_save_modules/_pltz_bundle.py +356 -0
  339. scitex/io/_save_modules/_pltz_stx.py +536 -0
  340. scitex/io/_save_modules/_stx_bundle.py +104 -0
  341. scitex/io/_save_modules/_symlink.py +96 -0
  342. scitex/io/_save_modules/_yaml.py +1 -1
  343. scitex/io/_save_modules/_zarr.py +64 -18
  344. scitex/io/bundle/README.md +212 -0
  345. scitex/io/bundle/__init__.py +110 -0
  346. scitex/io/{_bundle.py → bundle/_core.py} +168 -97
  347. scitex/io/bundle/_nested.py +713 -0
  348. scitex/io/bundle/_types.py +74 -0
  349. scitex/io/{_zip_bundle.py → bundle/_zip.py} +93 -45
  350. scitex/io/utils/h5_to_zarr.py +1 -1
  351. scitex/logging/__init__.py +108 -13
  352. scitex/logging/_errors.py +508 -0
  353. scitex/logging/_formatters.py +30 -6
  354. scitex/logging/_warnings.py +261 -0
  355. scitex/plt/__init__.py +4 -1
  356. scitex/plt/_figrecipe.py +236 -0
  357. scitex/plt/_subplots/_AxisWrapper.py +6 -0
  358. scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +112 -1
  359. scitex/plt/_subplots/_FigWrapper.py +15 -0
  360. scitex/plt/_subplots/_SubplotsWrapper.py +125 -489
  361. scitex/plt/_subplots/_export_as_csv.py +11 -0
  362. scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +2 -0
  363. scitex/plt/_subplots/_export_as_csv_formatters/_format_pcolormesh.py +66 -0
  364. scitex/plt/_subplots/_export_as_csv_formatters/_format_stackplot.py +62 -0
  365. scitex/plt/_subplots/_export_as_csv_formatters/test_formatters.py +208 -0
  366. scitex/plt/_subplots/_fonts.py +71 -0
  367. scitex/plt/_subplots/_mm_layout.py +282 -0
  368. scitex/plt/gallery/__init__.py +99 -2
  369. scitex/plt/styles/_plot_postprocess.py +3 -1
  370. scitex/plt/utils/_configure_mpl.py +16 -19
  371. scitex/repro/_RandomStateManager.py +13 -8
  372. scitex/resource/__init__.py +19 -1
  373. scitex/resource/_utils/_get_env_info.py +13 -25
  374. scitex/schema/__init__.py +149 -160
  375. scitex/schema/_encoding.py +273 -0
  376. scitex/schema/_figure_elements.py +406 -0
  377. scitex/schema/_theme.py +360 -0
  378. scitex/schema/_validation.py +0 -98
  379. scitex/scholar/__init__.py +56 -14
  380. scitex/scholar/auth/ScholarAuthManager.py +1 -1
  381. scitex/scholar/auth/__init__.py +11 -2
  382. scitex/scholar/auth/providers/BaseAuthenticator.py +1 -1
  383. scitex/scholar/auth/providers/EZProxyAuthenticator.py +1 -1
  384. scitex/scholar/auth/providers/OpenAthensAuthenticator.py +1 -1
  385. scitex/scholar/auth/providers/ShibbolethAuthenticator.py +1 -1
  386. scitex/scholar/config/ScholarConfig.py +1 -1
  387. scitex/scholar/core/Scholar.py +1 -1
  388. scitex/session/_decorator.py +18 -16
  389. scitex/session/_lifecycle.py +9 -11
  390. scitex/session/template.py +9 -8
  391. scitex/sh/test_sh.py +72 -0
  392. scitex/sh/test_sh_simple.py +61 -0
  393. scitex/stats/__init__.py +221 -97
  394. scitex/stats/_schema.py +21 -22
  395. scitex/stats/descriptive/_circular.py +212 -351
  396. scitex/stats/descriptive/_describe.py +81 -132
  397. scitex/stats/descriptive/_nan.py +205 -433
  398. scitex/stats/descriptive/_real.py +127 -141
  399. scitex/str/_format_plot_text.py +5 -5
  400. scitex/str/_latex.py +26 -84
  401. scitex/str/_latex_fallback.py +53 -47
  402. scitex/web/_search_pubmed.py +5 -4
  403. scitex/writer/tests/test_diff_between.py +451 -0
  404. scitex/writer/tests/test_document_section.py +311 -0
  405. scitex/writer/tests/test_document_workflow.py +393 -0
  406. scitex/writer/tests/test_writer.py +361 -0
  407. scitex/writer/tests/test_writer_integration.py +303 -0
  408. {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/METADATA +368 -183
  409. {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/RECORD +412 -97
  410. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/ARCHITECTURE_EXAMPLE.md +0 -905
  411. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/BULLETIN_BOARD_EXAMPLE.md +0 -99
  412. scitex/scholar/docs/to_claude/guidelines/examples/mgmt/PROJECT_DESCRIPTION_EXAMPLE.md +0 -96
  413. {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/WHEEL +0 -0
  414. {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/entry_points.txt +0 -0
  415. {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,426 @@
1
+ /**
2
+ * Main Entry Point
3
+ * Initializes all modules and sets up the Flask figure editor
4
+ *
5
+ * Module Structure:
6
+ * - core/: State management, utilities, API communication
7
+ * - canvas/: Canvas view, panel dragging, resizing, selection
8
+ * - editor/: Preview management, element detection, overlay rendering
9
+ * - alignment/: Panel alignment (basic and axis-based)
10
+ * - shortcuts/: Keyboard shortcuts and context menus
11
+ * - ui/: Form controls, downloads, help, theme
12
+ */
13
+
14
+ // ============================================================================
15
+ // Initialization
16
+ // ============================================================================
17
+ document.addEventListener('DOMContentLoaded', function() {
18
+ console.log('Flask Figure Editor initializing...');
19
+
20
+ // Initialize theme
21
+ initializeTheme();
22
+
23
+ // Initialize form controls
24
+ initializeControls();
25
+
26
+ // Load initial preview
27
+ loadInitialPreview();
28
+
29
+ // Setup resize observer for overlay
30
+ setupResizeObserver();
31
+
32
+ // Setup field-to-element sync
33
+ setupFieldToElementSync();
34
+
35
+ console.log('Flask Figure Editor initialized');
36
+ });
37
+
38
+ // ============================================================================
39
+ // Form Controls Initialization
40
+ // ============================================================================
41
+ function initializeControls() {
42
+ // Labels - Title
43
+ const titleInput = document.getElementById('title');
44
+ if (titleInput) titleInput.addEventListener('input', scheduleUpdate);
45
+
46
+ // Labels - Caption
47
+ const captionInput = document.getElementById('caption');
48
+ if (captionInput) captionInput.addEventListener('input', scheduleUpdate);
49
+
50
+ // Labels - Axis
51
+ const xlabelInput = document.getElementById('xlabel');
52
+ const ylabelInput = document.getElementById('ylabel');
53
+ if (xlabelInput) xlabelInput.addEventListener('input', scheduleUpdate);
54
+ if (ylabelInput) ylabelInput.addEventListener('input', scheduleUpdate);
55
+
56
+ // Axis limits
57
+ ['xmin', 'xmax', 'ymin', 'ymax'].forEach(id => {
58
+ const input = document.getElementById(id);
59
+ if (input) input.addEventListener('input', scheduleUpdate);
60
+ });
61
+
62
+ // Traces
63
+ updateTracesList();
64
+
65
+ // Legend
66
+ const legendVisibleInput = document.getElementById('legend_visible');
67
+ if (legendVisibleInput) legendVisibleInput.addEventListener('change', scheduleUpdate);
68
+
69
+ const legendLocInput = document.getElementById('legend_loc');
70
+ if (legendLocInput) {
71
+ legendLocInput.addEventListener('change', function() {
72
+ toggleCustomLegendPosition();
73
+ scheduleUpdate();
74
+ });
75
+ }
76
+
77
+ // Axis and Ticks - X Axis (Bottom)
78
+ ['x_n_ticks', 'hide_x_ticks', 'x_tick_fontsize', 'x_tick_direction', 'x_tick_length', 'x_tick_width'].forEach(id => {
79
+ const input = document.getElementById(id);
80
+ if (input) input.addEventListener('change', scheduleUpdate);
81
+ });
82
+
83
+ // X Axis (Top)
84
+ ['show_x_top', 'x_top_mirror'].forEach(id => {
85
+ const input = document.getElementById(id);
86
+ if (input) input.addEventListener('change', scheduleUpdate);
87
+ });
88
+
89
+ // Y Axis (Left)
90
+ ['y_n_ticks', 'hide_y_ticks', 'y_tick_fontsize', 'y_tick_direction', 'y_tick_length', 'y_tick_width'].forEach(id => {
91
+ const input = document.getElementById(id);
92
+ if (input) input.addEventListener('change', scheduleUpdate);
93
+ });
94
+
95
+ // Y Axis (Right)
96
+ ['show_y_right', 'y_right_mirror'].forEach(id => {
97
+ const input = document.getElementById(id);
98
+ if (input) input.addEventListener('change', scheduleUpdate);
99
+ });
100
+
101
+ // Spines
102
+ ['hide_bottom_spine', 'hide_left_spine'].forEach(id => {
103
+ const input = document.getElementById(id);
104
+ if (input) input.addEventListener('change', scheduleUpdate);
105
+ });
106
+
107
+ // Z Axis (3D)
108
+ ['hide_z_ticks', 'z_n_ticks', 'z_tick_fontsize', 'z_tick_direction'].forEach(id => {
109
+ const input = document.getElementById(id);
110
+ if (input) input.addEventListener('change', scheduleUpdate);
111
+ });
112
+
113
+ // Style
114
+ ['grid', 'hide_top_spine', 'hide_right_spine', 'axis_width', 'axis_fontsize'].forEach(id => {
115
+ const input = document.getElementById(id);
116
+ if (input) input.addEventListener('change', scheduleUpdate);
117
+ });
118
+
119
+ // Initialize background type from overrides
120
+ if (overrides.transparent) {
121
+ setBackgroundType('transparent');
122
+ } else if (overrides.facecolor === 'black') {
123
+ setBackgroundType('black');
124
+ } else {
125
+ setBackgroundType('white');
126
+ }
127
+
128
+ // Dimensions (convert from inches in metadata to mm by default)
129
+ if (overrides.fig_size) {
130
+ // fig_size is in inches in the JSON - convert to mm for default display
131
+ const widthInput = document.getElementById('fig_width');
132
+ const heightInput = document.getElementById('fig_height');
133
+ if (widthInput && heightInput) {
134
+ widthInput.value = (overrides.fig_size[0] * INCH_TO_MM).toFixed(1);
135
+ heightInput.value = (overrides.fig_size[1] * INCH_TO_MM).toFixed(1);
136
+ }
137
+ }
138
+
139
+ // Default unit is mm, which is already set in HTML and JS state
140
+
141
+ // Setup color sync for selected element property inputs
142
+ setupColorSync('trace-color-picker', 'trace-color-text');
143
+ setupColorSync('scatter-color-picker', 'scatter-color-text');
144
+ setupColorSync('fill-color-picker', 'fill-color-text');
145
+
146
+ // Mark initialization complete - now background changes will trigger updates
147
+ initializingBackground = false;
148
+
149
+ console.log('Controls initialized');
150
+ }
151
+
152
+ // ============================================================================
153
+ // Resize Observer Setup
154
+ // ============================================================================
155
+ function setupResizeObserver() {
156
+ // Add resize handler to update overlay when window/image size changes
157
+ window.addEventListener('resize', updateOverlay);
158
+
159
+ // Use ResizeObserver to detect when the preview container changes size
160
+ const previewContainer = document.getElementById('preview-container');
161
+ if (previewContainer && typeof ResizeObserver !== 'undefined') {
162
+ const resizeObserver = new ResizeObserver(() => {
163
+ updateOverlay();
164
+ });
165
+ resizeObserver.observe(previewContainer);
166
+ }
167
+ }
168
+
169
+ // ============================================================================
170
+ // Field-to-Element Synchronization
171
+ // ============================================================================
172
+ function setupFieldToElementSync() {
173
+ // Map field IDs to element names
174
+ const fieldToElement = {
175
+ // Title, Labels & Caption section
176
+ 'title': 'title',
177
+ 'caption': 'caption',
178
+ 'xlabel': 'xlabel',
179
+ 'ylabel': 'ylabel',
180
+
181
+ // Axis & Ticks section
182
+ 'xmin': 'xaxis',
183
+ 'xmax': 'xaxis',
184
+ 'ymin': 'yaxis',
185
+ 'ymax': 'yaxis',
186
+ 'x_n_ticks': 'xaxis',
187
+ 'y_n_ticks': 'yaxis',
188
+ 'hide_x_ticks': 'xaxis',
189
+ 'hide_y_ticks': 'yaxis',
190
+
191
+ // Legend section
192
+ 'legend_visible': 'legend',
193
+ 'legend_loc': 'legend',
194
+ 'legend_frameon': 'legend',
195
+ 'legend_fontsize': 'legend',
196
+ 'legend_ncols': 'legend',
197
+ 'legend_x': 'legend',
198
+ 'legend_y': 'legend',
199
+ };
200
+
201
+ // Add focus listeners to all mapped fields
202
+ for (const [fieldId, elementName] of Object.entries(fieldToElement)) {
203
+ const field = document.getElementById(fieldId);
204
+ if (field) {
205
+ field.addEventListener('focus', function() {
206
+ // Find the element in bboxes - for multi-panel, check ax_00 first
207
+ let foundElement = elementName;
208
+ if (elementBboxes[`ax_00_${elementName}`]) {
209
+ foundElement = `ax_00_${elementName}`;
210
+ } else if (elementBboxes[elementName]) {
211
+ foundElement = elementName;
212
+ } else {
213
+ // Try to find with axis prefix (e.g., ax_00_title)
214
+ for (const key of Object.keys(elementBboxes)) {
215
+ if (key.endsWith(`_${elementName}`)) {
216
+ foundElement = key;
217
+ break;
218
+ }
219
+ }
220
+ }
221
+
222
+ if (elementBboxes[foundElement]) {
223
+ hoveredElement = foundElement;
224
+ updateOverlay();
225
+ }
226
+ });
227
+
228
+ // Also handle mouseenter for hover feedback
229
+ field.addEventListener('mouseenter', function() {
230
+ const helpText = this.getAttribute('title') || this.getAttribute('placeholder');
231
+ if (helpText) {
232
+ setStatus(helpText, false);
233
+ }
234
+ });
235
+
236
+ field.addEventListener('mouseleave', function() {
237
+ setStatus('Ready', false);
238
+ });
239
+ }
240
+ }
241
+ }
242
+
243
+ // ============================================================================
244
+ // Traces List Update
245
+ // ============================================================================
246
+ function updateTracesList() {
247
+ const container = document.getElementById('traces-list');
248
+ if (!container) return;
249
+
250
+ container.innerHTML = '';
251
+ if (!traces || traces.length === 0) return;
252
+
253
+ traces.forEach((trace, idx) => {
254
+ const item = document.createElement('div');
255
+ item.className = 'trace-item';
256
+ item.innerHTML = `
257
+ <label>${trace.label || `Trace ${idx}`}</label>
258
+ <input type="color" value="${trace.color || '#000000'}" onchange="updateTraceColor(${idx}, this.value)">
259
+ <select onchange="updateTraceStyle(${idx}, this.value)">
260
+ <option value="-" ${trace.linestyle === '-' ? 'selected' : ''}>Solid</option>
261
+ <option value="--" ${trace.linestyle === '--' ? 'selected' : ''}>Dashed</option>
262
+ <option value="-." ${trace.linestyle === '-.' ? 'selected' : ''}>Dash-dot</option>
263
+ <option value=":" ${trace.linestyle === ':' ? 'selected' : ''}>Dotted</option>
264
+ </select>
265
+ `;
266
+ container.appendChild(item);
267
+ });
268
+ }
269
+
270
+ function updateTraceColor(idx, color) {
271
+ if (traces[idx]) {
272
+ traces[idx].color = color;
273
+ scheduleUpdate();
274
+ }
275
+ }
276
+
277
+ function updateTraceStyle(idx, style) {
278
+ if (traces[idx]) {
279
+ traces[idx].linestyle = style;
280
+ scheduleUpdate();
281
+ }
282
+ }
283
+
284
+ // ============================================================================
285
+ // Hover System Initialization (for SVG mode)
286
+ // ============================================================================
287
+ function initHoverSystemForElement(el) {
288
+ // Initialize hover detection for inline SVG elements
289
+ if (!el) return;
290
+
291
+ el.addEventListener('mousemove', (e) => {
292
+ // Find element at cursor position
293
+ // This would require SVG-specific hit detection
294
+ // For now, just update overlay
295
+ updateOverlay();
296
+ });
297
+
298
+ el.addEventListener('click', (e) => {
299
+ // Select element on click
300
+ // This would require SVG-specific hit detection
301
+ });
302
+ }
303
+
304
+ // ============================================================================
305
+ // Canvas Item Interaction (Multi-Panel)
306
+ // ============================================================================
307
+ function initCanvasItemInteraction(item, panelIdx, panelName) {
308
+ const container = item.querySelector('.panel-card-container');
309
+ if (!container) return;
310
+
311
+ const img = container.querySelector('img');
312
+ const overlay = container.querySelector('svg');
313
+ if (!img || !overlay) return;
314
+
315
+ // Wait for image to load to get dimensions
316
+ img.addEventListener('load', () => {
317
+ overlay.setAttribute('width', img.offsetWidth);
318
+ overlay.setAttribute('height', img.offsetHeight);
319
+ overlay.style.width = img.offsetWidth + 'px';
320
+ overlay.style.height = img.offsetHeight + 'px';
321
+ });
322
+
323
+ // Mousemove for hover detection (accounting for object-fit:contain letterboxing)
324
+ container.addEventListener('mousemove', (e) => {
325
+ const panelCache = panelBboxesCache[panelName];
326
+ if (!panelCache) return;
327
+
328
+ const rect = img.getBoundingClientRect();
329
+ const dims = getObjectFitContainDimensions(img);
330
+
331
+ // Mouse position relative to container
332
+ const x = e.clientX - rect.left;
333
+ const y = e.clientY - rect.top;
334
+
335
+ // Adjust for letterbox offset to get position relative to actual rendered image
336
+ const imgRelX = x - dims.offsetX;
337
+ const imgRelY = y - dims.offsetY;
338
+
339
+ // Check if click is within rendered image bounds
340
+ if (imgRelX < 0 || imgRelY < 0 || imgRelX > dims.displayWidth || imgRelY > dims.displayHeight) {
341
+ // Outside rendered image area (in letterbox region)
342
+ if (panelHoveredElement !== null) {
343
+ panelHoveredElement = null;
344
+ updatePanelOverlay(overlay, panelCache.bboxes, panelCache.imgSize, rect.width, rect.height, null, null, img);
345
+ }
346
+ return;
347
+ }
348
+
349
+ // Scale to original image coordinates
350
+ const scaleX = panelCache.imgSize.width / dims.displayWidth;
351
+ const scaleY = panelCache.imgSize.height / dims.displayHeight;
352
+ const imgX = imgRelX * scaleX;
353
+ const imgY = imgRelY * scaleY;
354
+
355
+ const element = findElementInPanelAt(imgX, imgY, panelCache.bboxes);
356
+ if (element !== panelHoveredElement || activePanelCard !== item) {
357
+ panelHoveredElement = element;
358
+ activePanelCard = item;
359
+ updatePanelOverlay(overlay, panelCache.bboxes, panelCache.imgSize, rect.width, rect.height, element, selectedElement, img);
360
+ }
361
+ });
362
+
363
+ // Mouseleave to clear hover
364
+ container.addEventListener('mouseleave', () => {
365
+ panelHoveredElement = null;
366
+ activePanelCard = null;
367
+ const panelCache = panelBboxesCache[panelName];
368
+ if (panelCache) {
369
+ updatePanelOverlay(overlay, panelCache.bboxes, panelCache.imgSize, img.offsetWidth, img.offsetHeight, null, selectedElement, img);
370
+ }
371
+ });
372
+
373
+ // Mousedown to start element drag (ONLY for legends and panel letters)
374
+ container.addEventListener('mousedown', (e) => {
375
+ if (panelHoveredElement && isDraggableElement(panelHoveredElement, panelBboxesCache[panelName]?.bboxes)) {
376
+ // Only allow dragging of legends and panel letters (scientific rigor)
377
+ startElementDrag(e, panelHoveredElement, panelName, img, panelBboxesCache[panelName].bboxes);
378
+ }
379
+ });
380
+
381
+ // Click to select element (accounting for object-fit:contain letterboxing)
382
+ container.addEventListener('click', (e) => {
383
+ // Recalculate element at click position (in case hover didn't detect it)
384
+ const panelCache = panelBboxesCache[panelName];
385
+ if (!panelCache) return;
386
+
387
+ const rect = img.getBoundingClientRect();
388
+ const dims = getObjectFitContainDimensions(img);
389
+
390
+ // Mouse position relative to container
391
+ const x = e.clientX - rect.left;
392
+ const y = e.clientY - rect.top;
393
+
394
+ // Adjust for letterbox offset
395
+ const imgRelX = x - dims.offsetX;
396
+ const imgRelY = y - dims.offsetY;
397
+
398
+ // Check if click is within rendered image bounds
399
+ if (imgRelX >= 0 && imgRelY >= 0 && imgRelX <= dims.displayWidth && imgRelY <= dims.displayHeight) {
400
+ // Scale to original image coordinates
401
+ const scaleX = panelCache.imgSize.width / dims.displayWidth;
402
+ const scaleY = panelCache.imgSize.height / dims.displayHeight;
403
+ const imgX = imgRelX * scaleX;
404
+ const imgY = imgRelY * scaleY;
405
+
406
+ const element = findElementInPanelAt(imgX, imgY, panelCache.bboxes);
407
+ if (element) {
408
+ loadPanelForEditing(panelName);
409
+ }
410
+ }
411
+ });
412
+
413
+ // Drag support for repositioning
414
+ initPanelDrag(item, panelName);
415
+ }
416
+
417
+ // ============================================================================
418
+ // Panel Card Interaction (for panel grid view - if used)
419
+ // ============================================================================
420
+ function initPanelCardInteraction(card, panelIdx, panelName) {
421
+ // Similar to initCanvasItemInteraction but for panel grid cards
422
+ // Simplified version - delegates to initCanvasItemInteraction
423
+ initCanvasItemInteraction(card, panelIdx, panelName);
424
+ }
425
+
426
+ console.log('main.js loaded');
@@ -0,0 +1,152 @@
1
+ /**
2
+ * Context Menu
3
+ * Right-click context menu for panel operations
4
+ */
5
+
6
+ // ============================================================================
7
+ // Show Context Menu
8
+ // ============================================================================
9
+ function showContextMenu(e, panelName) {
10
+ e.preventDefault();
11
+ hideContextMenu();
12
+
13
+ const selectedCount = document.querySelectorAll('.panel-canvas-item.selected').length;
14
+ const hasSelection = selectedCount > 0;
15
+
16
+ const menu = document.createElement('div');
17
+ menu.id = 'canvas-context-menu';
18
+ menu.className = 'context-menu';
19
+ menu.innerHTML = `
20
+ <div class="context-menu-item" onclick="selectAllPanels(); hideContextMenu();">
21
+ <span class="context-menu-icon">⬚</span> Select All <span class="context-menu-shortcut">Ctrl+A</span>
22
+ </div>
23
+ <div class="context-menu-item ${!hasSelection ? 'disabled' : ''}" onclick="${hasSelection ? 'deselectAllPanels(); hideContextMenu();' : ''}">
24
+ <span class="context-menu-icon">○</span> Deselect All <span class="context-menu-shortcut">Esc</span>
25
+ </div>
26
+ <div class="context-menu-divider"></div>
27
+ <div class="context-menu-item ${!hasSelection ? 'disabled' : ''}" onclick="${hasSelection ? 'bringPanelToFront(); hideContextMenu();' : ''}">
28
+ <span class="context-menu-icon">↑</span> Bring to Front <span class="context-menu-shortcut">Alt+F</span>
29
+ </div>
30
+ <div class="context-menu-item ${!hasSelection ? 'disabled' : ''}" onclick="${hasSelection ? 'sendPanelToBack(); hideContextMenu();' : ''}">
31
+ <span class="context-menu-icon">↓</span> Send to Back <span class="context-menu-shortcut">Alt+B</span>
32
+ </div>
33
+ <div class="context-menu-divider"></div>
34
+ <div class="context-menu-submenu">
35
+ <div class="context-menu-item">
36
+ <span class="context-menu-icon">≡</span> Align <span class="context-menu-arrow">▶</span>
37
+ </div>
38
+ <div class="context-submenu">
39
+ <div class="context-menu-item ${selectedCount < 2 ? 'disabled' : ''}" onclick="${selectedCount >= 2 ? "alignPanels('left'); hideContextMenu();" : ''}">Left</div>
40
+ <div class="context-menu-item ${selectedCount < 2 ? 'disabled' : ''}" onclick="${selectedCount >= 2 ? "alignPanels('right'); hideContextMenu();" : ''}">Right</div>
41
+ <div class="context-menu-item ${selectedCount < 2 ? 'disabled' : ''}" onclick="${selectedCount >= 2 ? "alignPanels('top'); hideContextMenu();" : ''}">Top</div>
42
+ <div class="context-menu-item ${selectedCount < 2 ? 'disabled' : ''}" onclick="${selectedCount >= 2 ? "alignPanels('bottom'); hideContextMenu();" : ''}">Bottom</div>
43
+ <div class="context-menu-divider"></div>
44
+ <div class="context-menu-item ${selectedCount < 2 ? 'disabled' : ''}" onclick="${selectedCount >= 2 ? "alignPanels('center-h'); hideContextMenu();" : ''}">Center H</div>
45
+ <div class="context-menu-item ${selectedCount < 2 ? 'disabled' : ''}" onclick="${selectedCount >= 2 ? "alignPanels('center-v'); hideContextMenu();" : ''}">Center V</div>
46
+ </div>
47
+ </div>
48
+ <div class="context-menu-submenu">
49
+ <div class="context-menu-item">
50
+ <span class="context-menu-icon">⊞</span> Align by Axis <span class="context-menu-arrow">▶</span>
51
+ </div>
52
+ <div class="context-submenu">
53
+ <div class="context-menu-item ${selectedCount < 2 ? 'disabled' : ''}" onclick="${selectedCount >= 2 ? "alignPanelsByAxis('left'); hideContextMenu();" : ''}">Y-Axis (Left)</div>
54
+ <div class="context-menu-item ${selectedCount < 2 ? 'disabled' : ''}" onclick="${selectedCount >= 2 ? "alignPanelsByAxis('bottom'); hideContextMenu();" : ''}">X-Axis (Bottom)</div>
55
+ <div class="context-menu-divider"></div>
56
+ <div class="context-menu-item ${selectedCount < 2 ? 'disabled' : ''}" onclick="${selectedCount >= 2 ? "stackPanelsVertically(); hideContextMenu();" : ''}">Stack Vertically</div>
57
+ </div>
58
+ </div>
59
+ <div class="context-menu-submenu">
60
+ <div class="context-menu-item">
61
+ <span class="context-menu-icon">⇔</span> Distribute <span class="context-menu-arrow">▶</span>
62
+ </div>
63
+ <div class="context-submenu">
64
+ <div class="context-menu-item ${selectedCount < 3 ? 'disabled' : ''}" onclick="${selectedCount >= 3 ? "distributePanels('horizontal'); hideContextMenu();" : ''}">Horizontal</div>
65
+ <div class="context-menu-item ${selectedCount < 3 ? 'disabled' : ''}" onclick="${selectedCount >= 3 ? "distributePanels('vertical'); hideContextMenu();" : ''}">Vertical</div>
66
+ </div>
67
+ </div>
68
+ <div class="context-menu-divider"></div>
69
+ <div class="context-menu-item" onclick="toggleGridVisibility(); hideContextMenu();">
70
+ <span class="context-menu-icon">⊞</span> Toggle Grid <span class="context-menu-shortcut">G</span>
71
+ </div>
72
+ <div class="context-menu-divider"></div>
73
+ <div class="context-menu-item" onclick="showShortcutHelp(); hideContextMenu();">
74
+ <span class="context-menu-icon">⌨</span> Keyboard Shortcuts <span class="context-menu-shortcut">?</span>
75
+ </div>
76
+ `;
77
+
78
+ // Position menu at cursor
79
+ menu.style.left = e.clientX + 'px';
80
+ menu.style.top = e.clientY + 'px';
81
+
82
+ document.body.appendChild(menu);
83
+ contextMenu = menu;
84
+
85
+ // Adjust position if menu goes off screen
86
+ const rect = menu.getBoundingClientRect();
87
+ if (rect.right > window.innerWidth) {
88
+ menu.style.left = (window.innerWidth - rect.width - 5) + 'px';
89
+ }
90
+ if (rect.bottom > window.innerHeight) {
91
+ menu.style.top = (window.innerHeight - rect.height - 5) + 'px';
92
+ }
93
+ }
94
+
95
+ // ============================================================================
96
+ // Hide Context Menu
97
+ // ============================================================================
98
+ function hideContextMenu() {
99
+ if (contextMenu) {
100
+ contextMenu.remove();
101
+ contextMenu = null;
102
+ }
103
+ }
104
+
105
+ // ============================================================================
106
+ // Event Listeners for Context Menu
107
+ // ============================================================================
108
+
109
+ // Close context menu on click outside
110
+ document.addEventListener('click', (e) => {
111
+ if (contextMenu && !contextMenu.contains(e.target)) {
112
+ hideContextMenu();
113
+ }
114
+ });
115
+
116
+ // Close context menu on Escape
117
+ document.addEventListener('keydown', (e) => {
118
+ if (e.key === 'Escape' && contextMenu) {
119
+ hideContextMenu();
120
+ }
121
+ });
122
+
123
+ // Attach context menu to canvas
124
+ document.addEventListener('DOMContentLoaded', () => {
125
+ const canvas = document.getElementById('panel-canvas');
126
+ if (canvas) {
127
+ canvas.addEventListener('contextmenu', (e) => {
128
+ // Check if right-click is on a panel
129
+ const panel = e.target.closest('.panel-canvas-item');
130
+ const panelName = panel ? panel.dataset.panelName : null;
131
+
132
+ // If clicking on a panel that's not selected, select it
133
+ if (panel && !panel.classList.contains('selected')) {
134
+ if (!e.ctrlKey && !e.metaKey) {
135
+ deselectAllPanels();
136
+ }
137
+ panel.classList.add('selected');
138
+ }
139
+
140
+ showContextMenu(e, panelName);
141
+ });
142
+
143
+ // Click on empty canvas space deselects all panels
144
+ canvas.addEventListener('click', (e) => {
145
+ // Only deselect if clicking directly on canvas background, not on a panel
146
+ const panel = e.target.closest('.panel-canvas-item');
147
+ if (!panel && !e.ctrlKey && !e.metaKey) {
148
+ deselectAllPanels();
149
+ }
150
+ });
151
+ }
152
+ });