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.
- scitex/__init__.py +15 -7
- scitex/__version__.py +1 -2
- scitex/_install_guide.py +250 -0
- scitex/_optional_deps.py +206 -39
- scitex/ai/_gen_ai/_Groq.py +2 -4
- scitex/ai/_gen_ai/_OpenAI.py +5 -2
- scitex/ai/_gen_ai/_Perplexity.py +20 -6
- scitex/audio/__init__.py +24 -15
- scitex/audio/_cross_process_lock.py +139 -0
- scitex/audio/_mcp_handlers.py +256 -0
- scitex/audio/_mcp_tool_schemas.py +203 -0
- scitex/audio/engines/elevenlabs_engine.py +5 -2
- scitex/audio/mcp_server.py +98 -457
- scitex/bridge/__init__.py +30 -19
- scitex/bridge/_figrecipe.py +245 -0
- scitex/bridge/_helpers.py +2 -1
- scitex/bridge/_plt_vis.py +23 -10
- scitex/bridge/_stats_plt.py +18 -5
- scitex/bridge/_stats_vis.py +16 -2
- scitex/browser/__init__.py +84 -44
- scitex/browser/automation/__init__.py +5 -1
- scitex/browser/core/BrowserMixin.py +17 -4
- scitex/browser/core/__init__.py +11 -2
- scitex/browser/remote/CaptchaHandler.py +1 -1
- scitex/browser/remote/ZenRowsAPIClient.py +1 -1
- scitex/capture/grid.py +487 -0
- scitex/capture/mcp_handlers.py +401 -0
- scitex/capture/mcp_tool_defs.py +192 -0
- scitex/capture/mcp_tools.py +241 -0
- scitex/capture/mcp_utils.py +30 -0
- scitex/cli/convert.py +421 -0
- scitex/cli/main.py +6 -4
- scitex/datetime/__init__.py +46 -0
- scitex/datetime/_linspace.py +100 -0
- scitex/datetime/_normalize_timestamp.py +306 -0
- scitex/db/_delete_duplicates.py +4 -4
- scitex/db/_sqlite3/_delete_duplicates.py +11 -2
- scitex/dev/plt/__init__.py +61 -62
- scitex/dev/plt/demo_plotters/__init__.py +0 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axhline.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axhspan.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axvline.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_axvspan.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_bar.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_barh.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_boxplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_contour.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_contourf.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_errorbar.py +30 -0
- scitex/dev/plt/demo_plotters/plot_mpl_eventplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_fill.py +30 -0
- scitex/dev/plt/demo_plotters/plot_mpl_fill_between.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hexbin.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hist.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_hist2d.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_imshow.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_pcolormesh.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_pie.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_plot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_quiver.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_scatter.py +28 -0
- scitex/dev/plt/demo_plotters/plot_mpl_stackplot.py +31 -0
- scitex/dev/plt/demo_plotters/plot_mpl_stem.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_step.py +29 -0
- scitex/dev/plt/demo_plotters/plot_mpl_violinplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_sns_barplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_boxplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_heatmap.py +28 -0
- scitex/dev/plt/demo_plotters/plot_sns_histplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_kdeplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_lineplot.py +31 -0
- scitex/dev/plt/demo_plotters/plot_sns_scatterplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_stripplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_swarmplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_sns_violinplot.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_bar.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_barh.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_box.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_boxplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_conf_mat.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_contour.py +31 -0
- scitex/dev/plt/demo_plotters/plot_stx_ecdf.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_errorbar.py +30 -0
- scitex/dev/plt/demo_plotters/plot_stx_fill_between.py +31 -0
- scitex/dev/plt/demo_plotters/plot_stx_fillv.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_heatmap.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_image.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_imshow.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_joyplot.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_kde.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_line.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_mean_ci.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_mean_std.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_median_iqr.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_raster.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_rectangle.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_scatter.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_shaded_line.py +29 -0
- scitex/dev/plt/demo_plotters/plot_stx_violin.py +28 -0
- scitex/dev/plt/demo_plotters/plot_stx_violinplot.py +28 -0
- scitex/dev/plt/mpl/get_dir_ax.py +46 -0
- scitex/dev/plt/mpl/get_signatures.py +176 -0
- scitex/dev/plt/mpl/get_signatures_details.py +522 -0
- scitex/dict/_pop_keys.py +1 -7
- scitex/dsp/__init__.py +15 -10
- scitex/dsp/add_noise.py +5 -2
- scitex/dsp/example.py +35 -22
- scitex/dsp/filt.py +8 -3
- scitex/dsp/reference.py +3 -2
- scitex/dsp/utils/__init__.py +2 -1
- scitex/dsp/utils/_differential_bandpass_filters.py +14 -4
- scitex/dt/__init__.py +39 -2
- scitex/errors.py +82 -521
- scitex/fig/__init__.py +4 -4
- scitex/fig/editor/edit/panel_loader.py +1 -1
- scitex/fig/io/_bundle.py +7 -7
- scitex/fts/README.md +262 -0
- scitex/fts/TODO.md +66 -0
- scitex/fts/__init__.py +90 -0
- scitex/fts/_bundle/README_IN_BUNDLE.md +102 -0
- scitex/fts/_bundle/_FTS.py +657 -0
- scitex/fts/_bundle/__init__.py +38 -0
- scitex/fts/_bundle/_children.py +216 -0
- scitex/fts/_bundle/_conversion/__init__.py +15 -0
- scitex/fts/_bundle/_conversion/_bundle2dict.py +44 -0
- scitex/fts/_bundle/_conversion/_dict2bundle.py +50 -0
- scitex/fts/_bundle/_dataclasses/_Axes.py +57 -0
- scitex/fts/_bundle/_dataclasses/_BBox.py +54 -0
- scitex/fts/_bundle/_dataclasses/_ColumnDef.py +72 -0
- scitex/fts/_bundle/_dataclasses/_DataFormat.py +40 -0
- scitex/fts/_bundle/_dataclasses/_DataInfo.py +135 -0
- scitex/fts/_bundle/_dataclasses/_DataSource.py +44 -0
- scitex/fts/_bundle/_dataclasses/_Node.py +319 -0
- scitex/fts/_bundle/_dataclasses/_NodeRefs.py +45 -0
- scitex/fts/_bundle/_dataclasses/_SizeMM.py +38 -0
- scitex/fts/_bundle/_dataclasses/__init__.py +35 -0
- scitex/fts/_bundle/_extractors/__init__.py +32 -0
- scitex/fts/_bundle/_extractors/_extract_bar.py +131 -0
- scitex/fts/_bundle/_extractors/_extract_line.py +71 -0
- scitex/fts/_bundle/_extractors/_extract_scatter.py +79 -0
- scitex/fts/_bundle/_loader.py +134 -0
- scitex/fts/_bundle/_mpl_helpers.py +389 -0
- scitex/fts/_bundle/_saver.py +269 -0
- scitex/fts/_bundle/_storage.py +200 -0
- scitex/fts/_bundle/_utils/__init__.py +55 -0
- scitex/fts/_bundle/_utils/_const.py +26 -0
- scitex/fts/_bundle/_utils/_errors.py +73 -0
- scitex/fts/_bundle/_utils/_generate.py +21 -0
- scitex/fts/_bundle/_utils/_types.py +76 -0
- scitex/fts/_bundle/_validation.py +434 -0
- scitex/fts/_bundle/_zipbundle.py +165 -0
- scitex/fts/_fig/__init__.py +22 -0
- scitex/fts/_fig/_backend/__init__.py +53 -0
- scitex/fts/_fig/_backend/_export.py +165 -0
- scitex/fts/_fig/_backend/_parser.py +188 -0
- scitex/fts/_fig/_backend/_render.py +538 -0
- scitex/fts/_fig/_composite.py +345 -0
- scitex/fts/_fig/_dataclasses/_ChannelEncoding.py +46 -0
- scitex/fts/_fig/_dataclasses/_Encoding.py +82 -0
- scitex/fts/_fig/_dataclasses/_Theme.py +441 -0
- scitex/fts/_fig/_dataclasses/_TraceEncoding.py +52 -0
- scitex/fts/_fig/_dataclasses/__init__.py +47 -0
- scitex/fts/_fig/_editor/__init__.py +14 -0
- scitex/fts/_fig/_editor/_cui/__init__.py +33 -0
- scitex/fts/_fig/_editor/_cui/_backend_detector.py +39 -0
- scitex/fts/_fig/_editor/_cui/_bundle_resolver.py +366 -0
- scitex/fts/_fig/_editor/_cui/_editor_launcher.py +175 -0
- scitex/fts/_fig/_editor/_cui/_manual_handler.py +52 -0
- scitex/fts/_fig/_editor/_cui/_panel_loader.py +246 -0
- scitex/fts/_fig/_editor/_cui/_path_resolver.py +66 -0
- scitex/fts/_fig/_editor/_defaults.py +300 -0
- scitex/fts/_fig/_editor/_gui/__init__.py +11 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/__init__.py +20 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_bbox.py +1339 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_core.py +1688 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_plotter.py +664 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_renderer.py +853 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/_utils.py +79 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/reset.css +41 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/typography.css +16 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/base/variables.css +85 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/buttons.css +217 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/context-menu.css +93 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/dropdown.css +57 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/forms.css +112 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/modal.css +59 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/components/sections.css +212 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/canvas.css +176 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/element-inspector.css +190 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/loading.css +59 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/overlay.css +45 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/panel-grid.css +95 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/selection.css +101 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/features/statistics.css +138 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/index.css +31 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/container.css +7 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/controls.css +56 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/css/layout/preview.css +78 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/axis.js +314 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/basic.js +107 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/alignment/distribute.js +54 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/canvas.js +172 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/dragging.js +258 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/resize.js +48 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/canvas/selection.js +71 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/api.js +288 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/state.js +143 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/core/utils.js +245 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/dev/element-inspector.js +992 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/bbox.js +339 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/element-drag.js +286 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/overlay.js +371 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/editor/preview.js +293 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/main.js +426 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/context-menu.js +152 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/shortcuts/keyboard.js +265 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/controls.js +184 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/download.js +57 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/help.js +100 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/static/js/ui/theme.js +34 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/__init__.py +124 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_html.py +851 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_scripts.py +4932 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor/templates/_styles.py +1657 -0
- scitex/fts/_fig/_editor/_gui/_flask_editor.py +36 -0
- scitex/fts/_fig/_models/_Annotations.py +115 -0
- scitex/fts/_fig/_models/_Axes.py +152 -0
- scitex/fts/_fig/_models/_Figure.py +138 -0
- scitex/fts/_fig/_models/_Guides.py +104 -0
- scitex/fts/_fig/_models/_Plot.py +123 -0
- scitex/fts/_fig/_models/_Styles.py +245 -0
- scitex/fts/_fig/_models/__init__.py +80 -0
- scitex/fts/_fig/_models/_plot_types/__init__.py +156 -0
- scitex/fts/_fig/_models/_plot_types/_bar.py +43 -0
- scitex/fts/_fig/_models/_plot_types/_box.py +38 -0
- scitex/fts/_fig/_models/_plot_types/_distribution.py +36 -0
- scitex/fts/_fig/_models/_plot_types/_errorbar.py +60 -0
- scitex/fts/_fig/_models/_plot_types/_histogram.py +30 -0
- scitex/fts/_fig/_models/_plot_types/_image.py +61 -0
- scitex/fts/_fig/_models/_plot_types/_line.py +57 -0
- scitex/fts/_fig/_models/_plot_types/_scatter.py +30 -0
- scitex/fts/_fig/_models/_plot_types/_seaborn.py +121 -0
- scitex/fts/_fig/_models/_plot_types/_violin.py +36 -0
- scitex/fts/_fig/_utils/__init__.py +129 -0
- scitex/fts/_fig/_utils/_auto_layout.py +127 -0
- scitex/fts/_fig/_utils/_calc_bounds.py +111 -0
- scitex/fts/_fig/_utils/_const_sizes.py +48 -0
- scitex/fts/_fig/_utils/_convert_coords.py +77 -0
- scitex/fts/_fig/_utils/_get_template.py +178 -0
- scitex/fts/_fig/_utils/_normalize.py +73 -0
- scitex/fts/_fig/_utils/_plot_layout.py +397 -0
- scitex/fts/_fig/_utils/_validate.py +197 -0
- scitex/fts/_kinds/__init__.py +45 -0
- scitex/fts/_kinds/_figure/__init__.py +19 -0
- scitex/fts/_kinds/_figure/_composite.py +345 -0
- scitex/fts/_kinds/_plot/__init__.py +25 -0
- scitex/fts/_kinds/_plot/_backend/__init__.py +53 -0
- scitex/fts/_kinds/_plot/_backend/_export.py +165 -0
- scitex/fts/_kinds/_plot/_backend/_parser.py +188 -0
- scitex/fts/_kinds/_plot/_backend/_render.py +538 -0
- scitex/fts/_kinds/_plot/_dataclasses/_ChannelEncoding.py +46 -0
- scitex/fts/_kinds/_plot/_dataclasses/_Encoding.py +82 -0
- scitex/fts/_kinds/_plot/_dataclasses/_Theme.py +441 -0
- scitex/fts/_kinds/_plot/_dataclasses/_TraceEncoding.py +52 -0
- scitex/fts/_kinds/_plot/_dataclasses/__init__.py +47 -0
- scitex/fts/_kinds/_plot/_models/_Annotations.py +115 -0
- scitex/fts/_kinds/_plot/_models/_Axes.py +152 -0
- scitex/fts/_kinds/_plot/_models/_Figure.py +138 -0
- scitex/fts/_kinds/_plot/_models/_Guides.py +104 -0
- scitex/fts/_kinds/_plot/_models/_Plot.py +123 -0
- scitex/fts/_kinds/_plot/_models/_Styles.py +245 -0
- scitex/fts/_kinds/_plot/_models/__init__.py +80 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/__init__.py +156 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_bar.py +43 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_box.py +38 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_distribution.py +36 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_errorbar.py +60 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_histogram.py +30 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_image.py +61 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_line.py +57 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_scatter.py +30 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_seaborn.py +121 -0
- scitex/fts/_kinds/_plot/_models/_plot_types/_violin.py +36 -0
- scitex/fts/_kinds/_plot/_utils/__init__.py +129 -0
- scitex/fts/_kinds/_plot/_utils/_auto_layout.py +127 -0
- scitex/fts/_kinds/_plot/_utils/_calc_bounds.py +111 -0
- scitex/fts/_kinds/_plot/_utils/_const_sizes.py +48 -0
- scitex/fts/_kinds/_plot/_utils/_convert_coords.py +77 -0
- scitex/fts/_kinds/_plot/_utils/_get_template.py +178 -0
- scitex/fts/_kinds/_plot/_utils/_normalize.py +73 -0
- scitex/fts/_kinds/_plot/_utils/_plot_layout.py +397 -0
- scitex/fts/_kinds/_plot/_utils/_validate.py +197 -0
- scitex/fts/_kinds/_shape/__init__.py +141 -0
- scitex/fts/_kinds/_stats/__init__.py +56 -0
- scitex/fts/_kinds/_stats/_dataclasses/_Stats.py +423 -0
- scitex/fts/_kinds/_stats/_dataclasses/__init__.py +48 -0
- scitex/fts/_kinds/_table/__init__.py +72 -0
- scitex/fts/_kinds/_table/_latex/__init__.py +93 -0
- scitex/fts/_kinds/_table/_latex/_editor/__init__.py +11 -0
- scitex/fts/_kinds/_table/_latex/_editor/_app.py +725 -0
- scitex/fts/_kinds/_table/_latex/_export.py +279 -0
- scitex/fts/_kinds/_table/_latex/_figure_exporter.py +153 -0
- scitex/fts/_kinds/_table/_latex/_stats_formatter.py +274 -0
- scitex/fts/_kinds/_table/_latex/_table_exporter.py +362 -0
- scitex/fts/_kinds/_table/_latex/_utils.py +369 -0
- scitex/fts/_kinds/_table/_latex/_validator.py +445 -0
- scitex/fts/_kinds/_text/__init__.py +77 -0
- scitex/fts/_schemas/data_info.schema.json +75 -0
- scitex/fts/_schemas/encoding.schema.json +90 -0
- scitex/fts/_schemas/node.schema.json +145 -0
- scitex/fts/_schemas/render_manifest.schema.json +62 -0
- scitex/fts/_schemas/stats.schema.json +132 -0
- scitex/fts/_schemas/theme.schema.json +141 -0
- scitex/fts/_stats/__init__.py +48 -0
- scitex/fts/_stats/_dataclasses/_Stats.py +423 -0
- scitex/fts/_stats/_dataclasses/__init__.py +48 -0
- scitex/fts/_tables/__init__.py +65 -0
- scitex/fts/_tables/_latex/__init__.py +93 -0
- scitex/fts/_tables/_latex/_editor/__init__.py +11 -0
- scitex/fts/_tables/_latex/_editor/_app.py +725 -0
- scitex/fts/_tables/_latex/_export.py +279 -0
- scitex/fts/_tables/_latex/_figure_exporter.py +153 -0
- scitex/fts/_tables/_latex/_stats_formatter.py +274 -0
- scitex/fts/_tables/_latex/_table_exporter.py +362 -0
- scitex/fts/_tables/_latex/_utils.py +369 -0
- scitex/fts/_tables/_latex/_validator.py +445 -0
- scitex/gen/__init__.py +66 -25
- scitex/gen/misc.py +28 -0
- scitex/io/__init__.py +47 -32
- scitex/io/_load.py +87 -36
- scitex/io/_load_modules/__init__.py +10 -7
- scitex/io/_load_modules/_pandas.py +6 -1
- scitex/io/_save.py +299 -1556
- scitex/io/_save_modules/__init__.py +76 -19
- scitex/io/_save_modules/_figure_utils.py +90 -0
- scitex/io/_save_modules/_image_csv.py +497 -0
- scitex/io/_save_modules/_legends.py +91 -0
- scitex/io/_save_modules/_pltz_bundle.py +356 -0
- scitex/io/_save_modules/_pltz_stx.py +536 -0
- scitex/io/_save_modules/_stx_bundle.py +104 -0
- scitex/io/_save_modules/_symlink.py +96 -0
- scitex/io/_save_modules/_yaml.py +1 -1
- scitex/io/_save_modules/_zarr.py +64 -18
- scitex/io/bundle/README.md +212 -0
- scitex/io/bundle/__init__.py +110 -0
- scitex/io/{_bundle.py → bundle/_core.py} +168 -97
- scitex/io/bundle/_nested.py +713 -0
- scitex/io/bundle/_types.py +74 -0
- scitex/io/{_zip_bundle.py → bundle/_zip.py} +93 -45
- scitex/io/utils/h5_to_zarr.py +1 -1
- scitex/logging/__init__.py +108 -13
- scitex/logging/_errors.py +508 -0
- scitex/logging/_formatters.py +30 -6
- scitex/logging/_warnings.py +261 -0
- scitex/plt/__init__.py +4 -1
- scitex/plt/_figrecipe.py +236 -0
- scitex/plt/_subplots/_AxisWrapper.py +6 -0
- scitex/plt/_subplots/_AxisWrapperMixins/_UnitAwareMixin.py +112 -1
- scitex/plt/_subplots/_FigWrapper.py +15 -0
- scitex/plt/_subplots/_SubplotsWrapper.py +125 -489
- scitex/plt/_subplots/_export_as_csv.py +11 -0
- scitex/plt/_subplots/_export_as_csv_formatters/__init__.py +2 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_pcolormesh.py +66 -0
- scitex/plt/_subplots/_export_as_csv_formatters/_format_stackplot.py +62 -0
- scitex/plt/_subplots/_export_as_csv_formatters/test_formatters.py +208 -0
- scitex/plt/_subplots/_fonts.py +71 -0
- scitex/plt/_subplots/_mm_layout.py +282 -0
- scitex/plt/gallery/__init__.py +99 -2
- scitex/plt/styles/_plot_postprocess.py +3 -1
- scitex/plt/utils/_configure_mpl.py +16 -19
- scitex/repro/_RandomStateManager.py +13 -8
- scitex/resource/__init__.py +19 -1
- scitex/resource/_utils/_get_env_info.py +13 -25
- scitex/schema/__init__.py +149 -160
- scitex/schema/_encoding.py +273 -0
- scitex/schema/_figure_elements.py +406 -0
- scitex/schema/_theme.py +360 -0
- scitex/schema/_validation.py +0 -98
- scitex/scholar/__init__.py +56 -14
- scitex/scholar/auth/ScholarAuthManager.py +1 -1
- scitex/scholar/auth/__init__.py +11 -2
- scitex/scholar/auth/providers/BaseAuthenticator.py +1 -1
- scitex/scholar/auth/providers/EZProxyAuthenticator.py +1 -1
- scitex/scholar/auth/providers/OpenAthensAuthenticator.py +1 -1
- scitex/scholar/auth/providers/ShibbolethAuthenticator.py +1 -1
- scitex/scholar/config/ScholarConfig.py +1 -1
- scitex/scholar/core/Scholar.py +1 -1
- scitex/session/_decorator.py +18 -16
- scitex/session/_lifecycle.py +9 -11
- scitex/session/template.py +9 -8
- scitex/sh/test_sh.py +72 -0
- scitex/sh/test_sh_simple.py +61 -0
- scitex/stats/__init__.py +221 -97
- scitex/stats/_schema.py +21 -22
- scitex/stats/descriptive/_circular.py +212 -351
- scitex/stats/descriptive/_describe.py +81 -132
- scitex/stats/descriptive/_nan.py +205 -433
- scitex/stats/descriptive/_real.py +127 -141
- scitex/str/_format_plot_text.py +5 -5
- scitex/str/_latex.py +26 -84
- scitex/str/_latex_fallback.py +53 -47
- scitex/web/_search_pubmed.py +5 -4
- scitex/writer/tests/test_diff_between.py +451 -0
- scitex/writer/tests/test_document_section.py +311 -0
- scitex/writer/tests/test_document_workflow.py +393 -0
- scitex/writer/tests/test_writer.py +361 -0
- scitex/writer/tests/test_writer_integration.py +303 -0
- {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/METADATA +368 -183
- {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/RECORD +412 -97
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/ARCHITECTURE_EXAMPLE.md +0 -905
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/BULLETIN_BOARD_EXAMPLE.md +0 -99
- scitex/scholar/docs/to_claude/guidelines/examples/mgmt/PROJECT_DESCRIPTION_EXAMPLE.md +0 -96
- {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/WHEEL +0 -0
- {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/entry_points.txt +0 -0
- {scitex-2.8.1.dist-info → scitex-2.10.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panel Dragging
|
|
3
|
+
* Handles drag-and-drop for panel repositioning in canvas view
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Panel Drag Initialization
|
|
8
|
+
// ============================================================================
|
|
9
|
+
function initPanelDrag(item, panelName) {
|
|
10
|
+
const handle = item.querySelector('.panel-drag-handle');
|
|
11
|
+
const label = item.querySelector('.panel-canvas-label');
|
|
12
|
+
|
|
13
|
+
// Drag from handle (always works)
|
|
14
|
+
if (handle) {
|
|
15
|
+
handle.addEventListener('mousedown', (e) => {
|
|
16
|
+
e.preventDefault();
|
|
17
|
+
e.stopPropagation();
|
|
18
|
+
startPanelDrag(e, item, panelName);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Also allow dragging from panel label
|
|
23
|
+
if (label) {
|
|
24
|
+
label.addEventListener('mousedown', (e) => {
|
|
25
|
+
e.preventDefault();
|
|
26
|
+
e.stopPropagation();
|
|
27
|
+
startPanelDrag(e, item, panelName);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Allow dragging from anywhere on the panel (except interactive elements)
|
|
32
|
+
// This enables intuitive drag behavior while preserving element selection
|
|
33
|
+
item.addEventListener('mousedown', (e) => {
|
|
34
|
+
// Skip if clicking on interactive elements (legends, text paths, etc.)
|
|
35
|
+
if (isInteractiveElement(e.target)) return;
|
|
36
|
+
|
|
37
|
+
// Skip if clicking on drag handle or label (already handled above)
|
|
38
|
+
if (e.target === handle || e.target === label) return;
|
|
39
|
+
|
|
40
|
+
// Start drag from anywhere else on the panel
|
|
41
|
+
startPanelDrag(e, item, panelName);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
// Set cursor to indicate draggability
|
|
45
|
+
item.style.cursor = 'move';
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ============================================================================
|
|
49
|
+
// Panel Drag Start
|
|
50
|
+
// ============================================================================
|
|
51
|
+
function startPanelDrag(e, item, name) {
|
|
52
|
+
e.preventDefault();
|
|
53
|
+
|
|
54
|
+
// Handle selection based on Ctrl key
|
|
55
|
+
const isCtrlPressed = e.ctrlKey || e.metaKey;
|
|
56
|
+
const wasAlreadySelected = item.classList.contains('selected');
|
|
57
|
+
|
|
58
|
+
if (isCtrlPressed) {
|
|
59
|
+
// Ctrl+Click: toggle this panel's selection (add/remove from multi-select)
|
|
60
|
+
item.classList.toggle('selected');
|
|
61
|
+
} else if (!wasAlreadySelected) {
|
|
62
|
+
// Regular click on unselected panel: select only this one
|
|
63
|
+
deselectAllPanels();
|
|
64
|
+
item.classList.add('selected');
|
|
65
|
+
}
|
|
66
|
+
// If clicking on already-selected panel without Ctrl:
|
|
67
|
+
// Don't change selection yet - could be start of multi-panel drag
|
|
68
|
+
// Selection will be finalized in stopPanelDrag based on hasMoved
|
|
69
|
+
|
|
70
|
+
// Collect all selected panels for group dragging
|
|
71
|
+
const selectedItems = Array.from(document.querySelectorAll('.panel-canvas-item.selected'));
|
|
72
|
+
if (selectedItems.length === 0) {
|
|
73
|
+
// If somehow nothing selected, select the clicked item
|
|
74
|
+
item.classList.add('selected');
|
|
75
|
+
selectedItems.push(item);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Store drag state for all selected panels
|
|
79
|
+
draggedPanel = {
|
|
80
|
+
primaryItem: item,
|
|
81
|
+
primaryName: name,
|
|
82
|
+
selectedItems: selectedItems,
|
|
83
|
+
startX: e.clientX,
|
|
84
|
+
startY: e.clientY,
|
|
85
|
+
hasMoved: false, // Track if actual drag occurred
|
|
86
|
+
wasAlreadySelected, // Track initial selection state for click handling
|
|
87
|
+
isCtrlPressed, // Track if Ctrl was pressed
|
|
88
|
+
startPositions: selectedItems.map(el => ({
|
|
89
|
+
item: el,
|
|
90
|
+
name: el.dataset.panelName,
|
|
91
|
+
x: parseFloat(el.style.left) || 0,
|
|
92
|
+
y: parseFloat(el.style.top) || 0
|
|
93
|
+
}))
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
document.addEventListener('mousemove', onPanelDrag);
|
|
97
|
+
document.addEventListener('mouseup', stopPanelDrag);
|
|
98
|
+
|
|
99
|
+
// Show position indicator for primary panel
|
|
100
|
+
updatePositionIndicator(name,
|
|
101
|
+
panelPositions[name]?.x || 0,
|
|
102
|
+
panelPositions[name]?.y || 0
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// ============================================================================
|
|
107
|
+
// Panel Drag Move
|
|
108
|
+
// ============================================================================
|
|
109
|
+
function onPanelDrag(e) {
|
|
110
|
+
if (!draggedPanel) return;
|
|
111
|
+
|
|
112
|
+
// Calculate delta from drag start
|
|
113
|
+
const dx = e.clientX - draggedPanel.startX;
|
|
114
|
+
const dy = e.clientY - draggedPanel.startY;
|
|
115
|
+
|
|
116
|
+
// Mark as moved if we've actually dragged (threshold: 3px)
|
|
117
|
+
if (Math.abs(dx) > 3 || Math.abs(dy) > 3) {
|
|
118
|
+
draggedPanel.hasMoved = true;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Snap to grid (optional: 5mm grid)
|
|
122
|
+
const gridSize = 5 * canvasScale; // 5mm grid
|
|
123
|
+
const snappedDx = Math.round(dx / gridSize) * gridSize;
|
|
124
|
+
const snappedDy = Math.round(dy / gridSize) * gridSize;
|
|
125
|
+
|
|
126
|
+
// Move all selected panels by the same delta
|
|
127
|
+
draggedPanel.startPositions.forEach(({item, name, x, y}) => {
|
|
128
|
+
const newX = x + snappedDx;
|
|
129
|
+
const newY = y + snappedDy;
|
|
130
|
+
|
|
131
|
+
// Constrain to canvas bounds (allow slight negative for edge alignment)
|
|
132
|
+
const constrainedX = Math.max(-10, newX);
|
|
133
|
+
const constrainedY = Math.max(-10, newY);
|
|
134
|
+
|
|
135
|
+
// Update pixel positions
|
|
136
|
+
item.style.left = constrainedX + 'px';
|
|
137
|
+
item.style.top = constrainedY + 'px';
|
|
138
|
+
|
|
139
|
+
panelPositions[name] = {
|
|
140
|
+
...panelPositions[name],
|
|
141
|
+
x: constrainedX,
|
|
142
|
+
y: constrainedY
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
// Update mm positions
|
|
146
|
+
panelLayoutMm[name] = {
|
|
147
|
+
...panelLayoutMm[name],
|
|
148
|
+
x_mm: constrainedX / canvasScale,
|
|
149
|
+
y_mm: constrainedY / canvasScale
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
// Show position indicator for primary panel
|
|
154
|
+
const primaryPos = panelPositions[draggedPanel.primaryName];
|
|
155
|
+
if (primaryPos) {
|
|
156
|
+
updatePositionIndicator(draggedPanel.primaryName, primaryPos.x, primaryPos.y);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Mark layout as modified
|
|
160
|
+
layoutModified = true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// ============================================================================
|
|
164
|
+
// Panel Drag Stop
|
|
165
|
+
// ============================================================================
|
|
166
|
+
function stopPanelDrag() {
|
|
167
|
+
if (draggedPanel) {
|
|
168
|
+
// Handle click (no movement) on already-selected panel without Ctrl:
|
|
169
|
+
// Finalize selection to only the clicked panel
|
|
170
|
+
if (!draggedPanel.hasMoved && draggedPanel.wasAlreadySelected && !draggedPanel.isCtrlPressed) {
|
|
171
|
+
// This was a simple click on an already-selected panel
|
|
172
|
+
// Deselect all others, keep only the clicked panel selected
|
|
173
|
+
deselectAllPanels();
|
|
174
|
+
draggedPanel.primaryItem.classList.add('selected');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Reset cursor for all selected panels
|
|
178
|
+
draggedPanel.selectedItems.forEach(item => {
|
|
179
|
+
item.style.cursor = 'move';
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
draggedPanel = null;
|
|
183
|
+
document.removeEventListener('mousemove', onPanelDrag);
|
|
184
|
+
document.removeEventListener('mouseup', stopPanelDrag);
|
|
185
|
+
|
|
186
|
+
// Update canvas size if panel moved outside
|
|
187
|
+
updateCanvasSize();
|
|
188
|
+
|
|
189
|
+
// Hide position indicator after a delay
|
|
190
|
+
setTimeout(() => {
|
|
191
|
+
const indicator = document.getElementById('position-indicator');
|
|
192
|
+
if (indicator) {
|
|
193
|
+
indicator.style.display = 'none';
|
|
194
|
+
}
|
|
195
|
+
}, 1000);
|
|
196
|
+
|
|
197
|
+
// Auto-save layout
|
|
198
|
+
if (layoutModified) {
|
|
199
|
+
saveLayout();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ============================================================================
|
|
205
|
+
// Position Indicator
|
|
206
|
+
// ============================================================================
|
|
207
|
+
function updatePositionIndicator(panelName, x, y) {
|
|
208
|
+
let indicator = document.getElementById('position-indicator');
|
|
209
|
+
if (!indicator) {
|
|
210
|
+
indicator = document.createElement('div');
|
|
211
|
+
indicator.id = 'position-indicator';
|
|
212
|
+
indicator.style.cssText = `
|
|
213
|
+
position: fixed;
|
|
214
|
+
bottom: 20px;
|
|
215
|
+
right: 20px;
|
|
216
|
+
background: rgba(0, 0, 0, 0.8);
|
|
217
|
+
color: white;
|
|
218
|
+
padding: 8px 12px;
|
|
219
|
+
border-radius: 4px;
|
|
220
|
+
font-family: monospace;
|
|
221
|
+
font-size: 12px;
|
|
222
|
+
z-index: 10000;
|
|
223
|
+
pointer-events: none;
|
|
224
|
+
`;
|
|
225
|
+
document.body.appendChild(indicator);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const xMm = (x / canvasScale).toFixed(1);
|
|
229
|
+
const yMm = (y / canvasScale).toFixed(1);
|
|
230
|
+
indicator.textContent = `${panelName}: x=${xMm}mm, y=${yMm}mm`;
|
|
231
|
+
indicator.style.display = 'block';
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// ============================================================================
|
|
235
|
+
// Manual Layout Save
|
|
236
|
+
// ============================================================================
|
|
237
|
+
function saveLayoutManually() {
|
|
238
|
+
if (layoutModified) {
|
|
239
|
+
saveLayout();
|
|
240
|
+
} else {
|
|
241
|
+
setStatus('No changes to save');
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// ============================================================================
|
|
246
|
+
// Legacy Drag Functions (for backward compatibility)
|
|
247
|
+
// ============================================================================
|
|
248
|
+
function startDrag(e, item, name) {
|
|
249
|
+
startPanelDrag(e, item, name);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function onDrag(e) {
|
|
253
|
+
onPanelDrag(e);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function stopDrag() {
|
|
257
|
+
stopPanelDrag();
|
|
258
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panel Resizing
|
|
3
|
+
* Handles panel resize operations in canvas view
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Resize Start
|
|
8
|
+
// ============================================================================
|
|
9
|
+
function startResize(e, item, name) {
|
|
10
|
+
e.preventDefault();
|
|
11
|
+
e.stopPropagation();
|
|
12
|
+
|
|
13
|
+
resizingPanel = {item, name, startX: e.clientX, startY: e.clientY};
|
|
14
|
+
document.addEventListener('mousemove', onResize);
|
|
15
|
+
document.addEventListener('mouseup', stopResize);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Resize Move
|
|
20
|
+
// ============================================================================
|
|
21
|
+
function onResize(e) {
|
|
22
|
+
if (!resizingPanel) return;
|
|
23
|
+
|
|
24
|
+
const dx = e.clientX - resizingPanel.startX;
|
|
25
|
+
const dy = e.clientY - resizingPanel.startY;
|
|
26
|
+
|
|
27
|
+
const pos = panelPositions[resizingPanel.name];
|
|
28
|
+
pos.width = Math.max(100, pos.width + dx);
|
|
29
|
+
pos.height = Math.max(100, pos.height + dy);
|
|
30
|
+
|
|
31
|
+
resizingPanel.item.style.width = pos.width + 'px';
|
|
32
|
+
resizingPanel.item.style.height = pos.height + 'px';
|
|
33
|
+
|
|
34
|
+
resizingPanel.startX = e.clientX;
|
|
35
|
+
resizingPanel.startY = e.clientY;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ============================================================================
|
|
39
|
+
// Resize Stop
|
|
40
|
+
// ============================================================================
|
|
41
|
+
function stopResize() {
|
|
42
|
+
if (resizingPanel) {
|
|
43
|
+
resizingPanel = null;
|
|
44
|
+
document.removeEventListener('mousemove', onResize);
|
|
45
|
+
document.removeEventListener('mouseup', stopResize);
|
|
46
|
+
layoutModified = true;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Panel Selection
|
|
3
|
+
* Handles panel selection and multi-selection in canvas view
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Select All Panels
|
|
8
|
+
// ============================================================================
|
|
9
|
+
function selectAllPanels() {
|
|
10
|
+
document.querySelectorAll('.panel-canvas-item').forEach(item => {
|
|
11
|
+
item.classList.add('selected');
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// Deselect All Panels
|
|
17
|
+
// ============================================================================
|
|
18
|
+
function deselectAllPanels() {
|
|
19
|
+
document.querySelectorAll('.panel-canvas-item').forEach(item => {
|
|
20
|
+
item.classList.remove('selected');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Also clear element selection in single-panel view
|
|
24
|
+
selectedElement = null;
|
|
25
|
+
hoveredElement = null;
|
|
26
|
+
updateOverlay();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ============================================================================
|
|
30
|
+
// Get Selected Panels
|
|
31
|
+
// ============================================================================
|
|
32
|
+
function getSelectedPanels() {
|
|
33
|
+
const selectedItems = document.querySelectorAll('.panel-canvas-item.selected');
|
|
34
|
+
return Array.from(selectedItems).map(item => ({
|
|
35
|
+
item: item,
|
|
36
|
+
name: item.dataset.panelName,
|
|
37
|
+
pos: panelPositions[item.dataset.panelName]
|
|
38
|
+
}));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ============================================================================
|
|
42
|
+
// Toggle Panel Selection
|
|
43
|
+
// ============================================================================
|
|
44
|
+
function togglePanelSelection(panelName) {
|
|
45
|
+
const item = document.querySelector(`.panel-canvas-item[data-panel-name="${panelName}"]`);
|
|
46
|
+
if (item) {
|
|
47
|
+
item.classList.toggle('selected');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// ============================================================================
|
|
52
|
+
// Select Single Panel
|
|
53
|
+
// ============================================================================
|
|
54
|
+
function selectPanel(panelName, clearOthers = true) {
|
|
55
|
+
if (clearOthers) {
|
|
56
|
+
deselectAllPanels();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const item = document.querySelector(`.panel-canvas-item[data-panel-name="${panelName}"]`);
|
|
60
|
+
if (item) {
|
|
61
|
+
item.classList.add('selected');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ============================================================================
|
|
66
|
+
// Check if Panel is Selected
|
|
67
|
+
// ============================================================================
|
|
68
|
+
function isPanelSelected(panelName) {
|
|
69
|
+
const item = document.querySelector(`.panel-canvas-item[data-panel-name="${panelName}"]`);
|
|
70
|
+
return item ? item.classList.contains('selected') : false;
|
|
71
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Communication
|
|
3
|
+
* Handles all server requests for preview, update, save, etc.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// Preview and Update API
|
|
8
|
+
// ============================================================================
|
|
9
|
+
function updatePreview() {
|
|
10
|
+
const data = collectOverrides();
|
|
11
|
+
|
|
12
|
+
// Skip auto-update if showing original preview (user hasn't explicitly requested update)
|
|
13
|
+
if (isShowingOriginalPreview) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
setStatus('Updating preview...');
|
|
18
|
+
|
|
19
|
+
// Preserve current selection to restore after update
|
|
20
|
+
const prevSelectedElement = selectedElement;
|
|
21
|
+
|
|
22
|
+
fetch('/update', {
|
|
23
|
+
method: 'POST',
|
|
24
|
+
headers: {'Content-Type': 'application/json'},
|
|
25
|
+
body: JSON.stringify(data)
|
|
26
|
+
})
|
|
27
|
+
.then(r => r.json())
|
|
28
|
+
.then(result => {
|
|
29
|
+
// Remove SVG wrapper if exists, show img element for re-rendered preview
|
|
30
|
+
const svgWrapper = document.getElementById('preview-svg-wrapper');
|
|
31
|
+
if (svgWrapper) {
|
|
32
|
+
svgWrapper.style.display = 'none';
|
|
33
|
+
}
|
|
34
|
+
document.getElementById('preview').style.display = 'block';
|
|
35
|
+
|
|
36
|
+
// Mark that we're no longer showing original preview
|
|
37
|
+
isShowingOriginalPreview = false;
|
|
38
|
+
|
|
39
|
+
if (result.image) {
|
|
40
|
+
document.getElementById('preview').src = 'data:image/png;base64,' + result.image;
|
|
41
|
+
|
|
42
|
+
// Store schema v0.3 metadata if available
|
|
43
|
+
if (result.schema_meta) {
|
|
44
|
+
schemaMeta = result.schema_meta;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
elementBboxes = result.bboxes || {};
|
|
48
|
+
imgSize = result.img_size || {width: 0, height: 0};
|
|
49
|
+
setStatus('Preview updated');
|
|
50
|
+
|
|
51
|
+
// Restore selection if the element still exists in the new bboxes
|
|
52
|
+
if (prevSelectedElement && elementBboxes[prevSelectedElement]) {
|
|
53
|
+
selectedElement = prevSelectedElement;
|
|
54
|
+
updateOverlay();
|
|
55
|
+
} else {
|
|
56
|
+
selectedElement = null;
|
|
57
|
+
updateOverlay();
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
.catch(err => {
|
|
61
|
+
setStatus('Error updating preview: ' + err, true);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ============================================================================
|
|
66
|
+
// Save API
|
|
67
|
+
// ============================================================================
|
|
68
|
+
function saveToBundle() {
|
|
69
|
+
const data = collectOverrides();
|
|
70
|
+
setStatus('Saving...');
|
|
71
|
+
return fetch('/save', {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: {'Content-Type': 'application/json'},
|
|
74
|
+
body: JSON.stringify(data)
|
|
75
|
+
})
|
|
76
|
+
.then(r => r.json())
|
|
77
|
+
.then(result => {
|
|
78
|
+
setStatus(result.status || 'Saved successfully');
|
|
79
|
+
})
|
|
80
|
+
.catch(err => {
|
|
81
|
+
setStatus('Error saving: ' + err, true);
|
|
82
|
+
throw err;
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ============================================================================
|
|
87
|
+
// Export API
|
|
88
|
+
// ============================================================================
|
|
89
|
+
function exportToFormat(format) {
|
|
90
|
+
const data = collectOverrides();
|
|
91
|
+
setStatus('Exporting to ' + format + '...');
|
|
92
|
+
|
|
93
|
+
// First export to bundle
|
|
94
|
+
fetch('/export', {
|
|
95
|
+
method: 'POST',
|
|
96
|
+
headers: {'Content-Type': 'application/json'},
|
|
97
|
+
body: JSON.stringify({...data, format: format})
|
|
98
|
+
})
|
|
99
|
+
.then(r => r.json())
|
|
100
|
+
.then(result => {
|
|
101
|
+
setStatus(result.status || 'Exported successfully');
|
|
102
|
+
// Then trigger download
|
|
103
|
+
downloadFile(result.path, format);
|
|
104
|
+
})
|
|
105
|
+
.catch(err => {
|
|
106
|
+
setStatus('Error exporting: ' + err, true);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function downloadFile(path, format) {
|
|
111
|
+
const filename = path.split('/').pop();
|
|
112
|
+
fetch('/download/' + filename)
|
|
113
|
+
.then(r => r.blob())
|
|
114
|
+
.then(blob => {
|
|
115
|
+
const url = URL.createObjectURL(blob);
|
|
116
|
+
const a = document.createElement('a');
|
|
117
|
+
a.href = url;
|
|
118
|
+
a.download = filename;
|
|
119
|
+
a.click();
|
|
120
|
+
URL.revokeObjectURL(url);
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ============================================================================
|
|
125
|
+
// Panel API (Multi-panel figures)
|
|
126
|
+
// ============================================================================
|
|
127
|
+
|
|
128
|
+
// Helper to normalize panel names for comparison (strip .pltz extensions)
|
|
129
|
+
function normalizePanelName(name) {
|
|
130
|
+
if (typeof name !== 'string') return '';
|
|
131
|
+
return name.replace('.pltz.d', '').replace('.pltz', '');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function loadPanelForEditing(panelName) {
|
|
135
|
+
setStatus('Loading panel ' + panelName + '...');
|
|
136
|
+
|
|
137
|
+
// Find panel index from panel name
|
|
138
|
+
// Handle both formats: array of strings ["A", "B"] or array of objects [{name: "A"}, ...]
|
|
139
|
+
// Also handle extension mismatches (e.g., "A" vs "A.pltz")
|
|
140
|
+
let panelIndex = -1;
|
|
141
|
+
const normalizedSearch = normalizePanelName(panelName);
|
|
142
|
+
|
|
143
|
+
if (panelData && panelData.panels) {
|
|
144
|
+
panelIndex = panelData.panels.findIndex(p => {
|
|
145
|
+
// Check if it's a string or an object with name property
|
|
146
|
+
if (typeof p === 'string') {
|
|
147
|
+
return normalizePanelName(p) === normalizedSearch;
|
|
148
|
+
} else if (p && p.name) {
|
|
149
|
+
return normalizePanelName(p.name) === normalizedSearch;
|
|
150
|
+
}
|
|
151
|
+
return false;
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (panelIndex === -1) {
|
|
156
|
+
setStatus('Panel not found: ' + panelName, true);
|
|
157
|
+
return Promise.reject(new Error('Panel not found: ' + panelName));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return fetch('/switch_panel/' + panelIndex)
|
|
161
|
+
.then(r => r.json())
|
|
162
|
+
.then(result => {
|
|
163
|
+
if (result.error) {
|
|
164
|
+
throw new Error(result.error);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Update panel state
|
|
168
|
+
currentPanelIndex = panelIndex;
|
|
169
|
+
|
|
170
|
+
// Update preview image
|
|
171
|
+
if (result.image) {
|
|
172
|
+
document.getElementById('preview').src = 'data:image/png;base64,' + result.image;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Update bboxes and overlays
|
|
176
|
+
elementBboxes = result.bboxes || {};
|
|
177
|
+
imgSize = result.img_size || {width: 0, height: 0};
|
|
178
|
+
|
|
179
|
+
// Sync bboxes cache
|
|
180
|
+
panelBboxesCache[panelName] = {bboxes: elementBboxes, imgSize: imgSize};
|
|
181
|
+
|
|
182
|
+
// Update overrides
|
|
183
|
+
if (result.overrides) {
|
|
184
|
+
overrides = result.overrides;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Select the element that was clicked
|
|
188
|
+
if (result.selected_element) {
|
|
189
|
+
selectedElement = result.selected_element;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Scroll to section and show properties
|
|
193
|
+
scrollToSection(selectedElement);
|
|
194
|
+
|
|
195
|
+
// Keep unified canvas view only - don't show single-panel preview
|
|
196
|
+
showingPanelGrid = true;
|
|
197
|
+
// Update panel path display in right panel header
|
|
198
|
+
const panelPath = document.getElementById('current-panel-path');
|
|
199
|
+
if (panelPath) {
|
|
200
|
+
panelPath.textContent = panelName || 'Single Panel';
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
setStatus('Panel loaded');
|
|
204
|
+
return result;
|
|
205
|
+
})
|
|
206
|
+
.catch(err => {
|
|
207
|
+
setStatus('Error loading panel: ' + err, true);
|
|
208
|
+
throw err;
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ============================================================================
|
|
213
|
+
// Layout Save API
|
|
214
|
+
// ============================================================================
|
|
215
|
+
function saveLayout() {
|
|
216
|
+
const data = {
|
|
217
|
+
layout: panelLayoutMm,
|
|
218
|
+
overrides: overrides
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
setStatus('Saving layout...');
|
|
222
|
+
|
|
223
|
+
return fetch('/save_layout', {
|
|
224
|
+
method: 'POST',
|
|
225
|
+
headers: {'Content-Type': 'application/json'},
|
|
226
|
+
body: JSON.stringify(data)
|
|
227
|
+
})
|
|
228
|
+
.then(r => r.json())
|
|
229
|
+
.then(result => {
|
|
230
|
+
setStatus('Layout saved');
|
|
231
|
+
layoutModified = false;
|
|
232
|
+
})
|
|
233
|
+
.catch(err => {
|
|
234
|
+
setStatus('Error saving layout: ' + err, true);
|
|
235
|
+
throw err;
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// ============================================================================
|
|
240
|
+
// Element Position Update API
|
|
241
|
+
// ============================================================================
|
|
242
|
+
function updateElementPosition(panelName, elementName, position) {
|
|
243
|
+
const data = {
|
|
244
|
+
panel_name: panelName,
|
|
245
|
+
element_name: elementName,
|
|
246
|
+
position: position,
|
|
247
|
+
overrides: overrides
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
return fetch('/save_element_position', {
|
|
251
|
+
method: 'POST',
|
|
252
|
+
headers: {'Content-Type': 'application/json'},
|
|
253
|
+
body: JSON.stringify(data)
|
|
254
|
+
})
|
|
255
|
+
.then(r => r.json())
|
|
256
|
+
.then(result => {
|
|
257
|
+
setStatus('Element position updated');
|
|
258
|
+
// Reload panel to show updated position
|
|
259
|
+
return loadPanelForEditing(panelName);
|
|
260
|
+
})
|
|
261
|
+
.catch(err => {
|
|
262
|
+
setStatus('Error updating element position: ' + err, true);
|
|
263
|
+
throw err;
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// ============================================================================
|
|
268
|
+
// Auto-Update Scheduling
|
|
269
|
+
// ============================================================================
|
|
270
|
+
function scheduleUpdate() {
|
|
271
|
+
clearTimeout(updateTimer);
|
|
272
|
+
updateTimer = setTimeout(updatePreview, DEBOUNCE_DELAY);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function setAutoUpdateInterval() {
|
|
276
|
+
if (autoUpdateIntervalId) {
|
|
277
|
+
clearInterval(autoUpdateIntervalId);
|
|
278
|
+
autoUpdateIntervalId = null;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const intervalMs = parseInt(document.getElementById('auto_update_interval').value);
|
|
282
|
+
if (intervalMs > 0) {
|
|
283
|
+
autoUpdateIntervalId = setInterval(() => {
|
|
284
|
+
updatePreview();
|
|
285
|
+
}, intervalMs);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|