plexora 0.0.1__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 (248) hide show
  1. plexora/__init__.py +129 -0
  2. plexora/__main__.py +15 -0
  3. plexora/_url.py +105 -0
  4. plexora/api/__init__.py +55 -0
  5. plexora/api/dataset.py +398 -0
  6. plexora/api/http.py +19 -0
  7. plexora/api/plugin.py +541 -0
  8. plexora/api/store.py +221 -0
  9. plexora/cli.py +777 -0
  10. plexora/client/dist/354_bundle.js +2 -0
  11. plexora/client/dist/354_bundle.js.LICENSE.txt +20 -0
  12. plexora/client/dist/418_bundle.js +2 -0
  13. plexora/client/dist/418_bundle.js.LICENSE.txt +1 -0
  14. plexora/client/dist/693b935cb1f907814be9c6199a68c217.svg +19 -0
  15. plexora/client/dist/713c134ff47cd328a5b711526f151082.svg +18 -0
  16. plexora/client/dist/770_bundle.js +1 -0
  17. plexora/client/dist/ff743f408972e0e96ca9153ed5ae4c83.svg +23 -0
  18. plexora/client/dist/vendor_bundle.js +2 -0
  19. plexora/client/dist/vendor_bundle.js.LICENSE.txt +308 -0
  20. plexora/client/external/openseadragon-bin-2.4.0/LICENSE.txt +28 -0
  21. plexora/client/external/openseadragon-bin-2.4.0/canvas-overlay-hd.js +140 -0
  22. plexora/client/external/openseadragon-bin-2.4.0/changelog.txt +501 -0
  23. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/.gitattributes +17 -0
  24. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/LICENSE.txt +116 -0
  25. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/Toolbar.png +0 -0
  26. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/fullpage.psd +0 -0
  27. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/home.psd +0 -0
  28. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/next.psd +0 -0
  29. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/previous.psd +0 -0
  30. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/rotateleft.psd +0 -0
  31. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/rotateright.psd +0 -0
  32. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/zoom.psd +0 -0
  33. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/README.md +9 -0
  34. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_grouphover.png +0 -0
  35. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_hover.png +0 -0
  36. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_pressed.png +0 -0
  37. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_rest.png +0 -0
  38. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_grouphover.png +0 -0
  39. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_hover.png +0 -0
  40. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_pressed.png +0 -0
  41. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_rest.png +0 -0
  42. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_grouphover.png +0 -0
  43. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_hover.png +0 -0
  44. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_pressed.png +0 -0
  45. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_rest.png +0 -0
  46. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_grouphover.png +0 -0
  47. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_hover.png +0 -0
  48. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_pressed.png +0 -0
  49. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_rest.png +0 -0
  50. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_grouphover.png +0 -0
  51. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_hover.png +0 -0
  52. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_pressed.png +0 -0
  53. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_rest.png +0 -0
  54. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_grouphover.png +0 -0
  55. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_hover.png +0 -0
  56. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_pressed.png +0 -0
  57. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_rest.png +0 -0
  58. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_grouphover.png +0 -0
  59. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_hover.png +0 -0
  60. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_pressed.png +0 -0
  61. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_rest.png +0 -0
  62. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_grouphover.png +0 -0
  63. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_hover.png +0 -0
  64. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_pressed.png +0 -0
  65. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_rest.png +0 -0
  66. plexora/client/external/openseadragon-bin-2.4.0/openseadragon-scalebar.js +562 -0
  67. plexora/client/src/css/import.css +474 -0
  68. plexora/client/src/css/main.css +903 -0
  69. plexora/client/src/css/openProject.css +363 -0
  70. plexora/client/src/css/quickView.css +166 -0
  71. plexora/client/src/css/tokens.css +51 -0
  72. plexora/client/src/css/viewer.css +1655 -0
  73. plexora/client/src/img/apple-touch-icon.png +0 -0
  74. plexora/client/src/img/favicon.ico +0 -0
  75. plexora/client/src/img/logo.ai +8892 -33
  76. plexora/client/src/img/logo.svg +1 -0
  77. plexora/client/src/img/logo_with_text.ai +9011 -34
  78. plexora/client/src/img/logo_with_text.svg +1 -0
  79. plexora/client/src/js/main.js +704 -0
  80. plexora/client/src/js/pluginRegistry.js +108 -0
  81. plexora/client/src/js/services/appStatus.js +286 -0
  82. plexora/client/src/js/services/browsePicker.js +73 -0
  83. plexora/client/src/js/services/dataLayer.js +400 -0
  84. plexora/client/src/js/services/datasetContext.js +143 -0
  85. plexora/client/src/js/services/glRenderer.js +143 -0
  86. plexora/client/src/js/services/importFormValidation.js +257 -0
  87. plexora/client/src/js/services/numericData.js +100 -0
  88. plexora/client/src/js/services/passVariablesToFrontend.js +20 -0
  89. plexora/client/src/js/services/simpleEventHandler.js +25 -0
  90. plexora/client/src/js/vendor.js +29 -0
  91. plexora/client/src/js/views/channelList.js +663 -0
  92. plexora/client/src/js/views/colorSwatchPicker.js +202 -0
  93. plexora/client/src/js/views/columnClassifier.js +146 -0
  94. plexora/client/src/js/views/coordinateField.js +133 -0
  95. plexora/client/src/js/views/dataSourceField.js +276 -0
  96. plexora/client/src/js/views/imageViewer.js +3306 -0
  97. plexora/client/src/js/views/miniMap.js +787 -0
  98. plexora/client/src/js/views/navbarControls.js +178 -0
  99. plexora/client/src/js/views/openProjectPage.js +239 -0
  100. plexora/client/src/js/views/projectEdit.js +335 -0
  101. plexora/client/src/js/views/quickViewLanding.js +136 -0
  102. plexora/client/src/js/views/rainbow.js +326 -0
  103. plexora/client/src/js/views/requirementsModal.js +572 -0
  104. plexora/client/src/js/views/rgbImageViewer.js +115 -0
  105. plexora/client/src/js/views/searchableSelect.js +411 -0
  106. plexora/client/src/js/views/segmentationProgress.js +122 -0
  107. plexora/client/src/js/views/toolLoader.js +747 -0
  108. plexora/client/src/js/views/viewerControls.js +689 -0
  109. plexora/client/src/js/views/viewerManager.js +434 -0
  110. plexora/client/src/js/views/viewerSidebar.js +1117 -0
  111. plexora/client/src/js/workers/tileDecoder.js +130 -0
  112. plexora/client/src/shaders/frag.glsl +528 -0
  113. plexora/client/src/shaders/vert.glsl +12 -0
  114. plexora/client/templates/base.html +212 -0
  115. plexora/client/templates/index.html +238 -0
  116. plexora/client/templates/open_project.html +96 -0
  117. plexora/client/templates/project_columns.html +86 -0
  118. plexora/client/templates/project_edit.html +196 -0
  119. plexora/client/templates/upload.html +118 -0
  120. plexora/connect.py +528 -0
  121. plexora/datasource.py +782 -0
  122. plexora/jupyter.py +406 -0
  123. plexora/notebook_env.py +232 -0
  124. plexora/paths.py +486 -0
  125. plexora/plugins/__init__.py +12 -0
  126. plexora/plugins/cell_explorer/__init__.py +100 -0
  127. plexora/plugins/cell_explorer/server/__init__.py +11 -0
  128. plexora/plugins/cell_explorer/server/routes.py +167 -0
  129. plexora/plugins/cell_explorer/server/state.py +281 -0
  130. plexora/plugins/cell_explorer/server/values.py +145 -0
  131. plexora/plugins/cell_explorer/server/variables.py +367 -0
  132. plexora/plugins/cell_explorer/static/cellExplorerApi.js +158 -0
  133. plexora/plugins/cell_explorer/static/cellExplorerColors.js +316 -0
  134. plexora/plugins/cell_explorer/static/cellExplorerContinuous.js +403 -0
  135. plexora/plugins/cell_explorer/static/cellExplorerFigureBridge.js +133 -0
  136. plexora/plugins/cell_explorer/static/cellExplorerLegend.js +188 -0
  137. plexora/plugins/cell_explorer/static/cellExplorerRoiBridge.js +822 -0
  138. plexora/plugins/cell_explorer/static/cellExplorerSidebarController.js +826 -0
  139. plexora/plugins/cell_explorer/static/cellExplorerState.js +372 -0
  140. plexora/plugins/cell_explorer/static/cell_explorer.css +752 -0
  141. plexora/plugins/cell_explorer/templates/cell_explorer/panel.html +137 -0
  142. plexora/plugins/figure_builder/__init__.py +125 -0
  143. plexora/plugins/figure_builder/server/__init__.py +7 -0
  144. plexora/plugins/figure_builder/server/compose.py +365 -0
  145. plexora/plugins/figure_builder/server/export.py +784 -0
  146. plexora/plugins/figure_builder/server/export_jobs.py +148 -0
  147. plexora/plugins/figure_builder/server/operations.py +556 -0
  148. plexora/plugins/figure_builder/server/pixels.py +142 -0
  149. plexora/plugins/figure_builder/server/provenance.py +174 -0
  150. plexora/plugins/figure_builder/server/render.py +278 -0
  151. plexora/plugins/figure_builder/server/repository.py +701 -0
  152. plexora/plugins/figure_builder/server/routes.py +594 -0
  153. plexora/plugins/figure_builder/server/schema.py +1023 -0
  154. plexora/plugins/figure_builder/server/sources.py +120 -0
  155. plexora/plugins/figure_builder/server/textmetrics.py +155 -0
  156. plexora/plugins/figure_builder/static/figureActions.js +363 -0
  157. plexora/plugins/figure_builder/static/figureBuilderApi.js +255 -0
  158. plexora/plugins/figure_builder/static/figureCanvas.js +2362 -0
  159. plexora/plugins/figure_builder/static/figureCaptureBoxes.js +369 -0
  160. plexora/plugins/figure_builder/static/figureCaptureDock.js +466 -0
  161. plexora/plugins/figure_builder/static/figureCaptureTool.js +1034 -0
  162. plexora/plugins/figure_builder/static/figureConfirm.js +136 -0
  163. plexora/plugins/figure_builder/static/figureContextBar.js +1193 -0
  164. plexora/plugins/figure_builder/static/figureContextMenu.js +253 -0
  165. plexora/plugins/figure_builder/static/figureDocumentState.js +275 -0
  166. plexora/plugins/figure_builder/static/figureExportUi.js +174 -0
  167. plexora/plugins/figure_builder/static/figureLibrary.js +247 -0
  168. plexora/plugins/figure_builder/static/figureQuickEdit.js +628 -0
  169. plexora/plugins/figure_builder/static/figureRichText.js +634 -0
  170. plexora/plugins/figure_builder/static/figureSceneSnapshot.js +361 -0
  171. plexora/plugins/figure_builder/static/figureSchema.js +218 -0
  172. plexora/plugins/figure_builder/static/figureSidebarController.js +1159 -0
  173. plexora/plugins/figure_builder/static/figureTextEditor.js +503 -0
  174. plexora/plugins/figure_builder/static/figureTextPanel.js +491 -0
  175. plexora/plugins/figure_builder/static/figureViewOptions.js +273 -0
  176. plexora/plugins/figure_builder/static/figureWorkspace.js +2030 -0
  177. plexora/plugins/figure_builder/static/figure_builder.css +3292 -0
  178. plexora/plugins/figure_builder/templates/figure_builder/library.html +74 -0
  179. plexora/plugins/figure_builder/templates/figure_builder/workspace.html +27 -0
  180. plexora/plugins/figure_builder/templates/figure_builder/workspace_body.html +458 -0
  181. plexora/plugins/gating/__init__.py +76 -0
  182. plexora/plugins/gating/server/__init__.py +0 -0
  183. plexora/plugins/gating/server/anndata_gates.py +367 -0
  184. plexora/plugins/gating/server/database.py +16 -0
  185. plexora/plugins/gating/server/model.py +320 -0
  186. plexora/plugins/gating/server/routes.py +235 -0
  187. plexora/plugins/gating/static/csvGatingList.js +947 -0
  188. plexora/plugins/gating/static/gating.css +289 -0
  189. plexora/plugins/gating/static/gatingApi.js +221 -0
  190. plexora/plugins/gating/static/gatingSidebarController.js +513 -0
  191. plexora/plugins/gating/templates/gating/legacy.html +26 -0
  192. plexora/plugins/gating/templates/gating/panel.html +69 -0
  193. plexora/plugins/roi/__init__.py +95 -0
  194. plexora/plugins/roi/server/__init__.py +8 -0
  195. plexora/plugins/roi/server/adapters.py +655 -0
  196. plexora/plugins/roi/server/geojson.py +267 -0
  197. plexora/plugins/roi/server/geometry.py +181 -0
  198. plexora/plugins/roi/server/mapping.py +169 -0
  199. plexora/plugins/roi/server/operations.py +293 -0
  200. plexora/plugins/roi/server/repository.py +215 -0
  201. plexora/plugins/roi/server/routes.py +409 -0
  202. plexora/plugins/roi/server/schema.py +323 -0
  203. plexora/plugins/roi/static/roi.css +583 -0
  204. plexora/plugins/roi/static/roiApi.js +177 -0
  205. plexora/plugins/roi/static/roiFigureBridge.js +107 -0
  206. plexora/plugins/roi/static/roiGeometry.js +350 -0
  207. plexora/plugins/roi/static/roiRenderer.js +278 -0
  208. plexora/plugins/roi/static/roiSidebarController.js +1038 -0
  209. plexora/plugins/roi/static/roiState.js +518 -0
  210. plexora/plugins/roi/static/roiTools.js +993 -0
  211. plexora/plugins/roi/templates/roi/panel.html +240 -0
  212. plexora/proxy.py +57 -0
  213. plexora/server/models/adapters/__init__.py +94 -0
  214. plexora/server/models/adapters/anndata_adapter.py +388 -0
  215. plexora/server/models/adapters/base.py +93 -0
  216. plexora/server/models/adapters/classify.py +192 -0
  217. plexora/server/models/adapters/csv_adapter.py +105 -0
  218. plexora/server/models/adapters/inspection.py +265 -0
  219. plexora/server/models/adapters/spatialdata_adapter.py +248 -0
  220. plexora/server/models/centroid_tiles.py +305 -0
  221. plexora/server/models/data_model.py +1659 -0
  222. plexora/server/models/database_model.py +169 -0
  223. plexora/server/models/project.py +1270 -0
  224. plexora/server/plugins.py +199 -0
  225. plexora/server/routes/browse_routes.py +41 -0
  226. plexora/server/routes/data_routes.py +294 -0
  227. plexora/server/routes/import_routes.py +603 -0
  228. plexora/server/routes/page_routes.py +139 -0
  229. plexora/server/routes/project_routes.py +438 -0
  230. plexora/server/routes/quick_view_routes.py +62 -0
  231. plexora/server/routes/system_routes.py +39 -0
  232. plexora/server/routes/tool_routes.py +418 -0
  233. plexora/server/utils/addHEColumns.py +11 -0
  234. plexora/server/utils/fast_png.py +97 -0
  235. plexora/server/utils/fullConversion.py +50 -0
  236. plexora/server/utils/native_dialog.py +168 -0
  237. plexora/server/utils/pre_normalization.py +47 -0
  238. plexora/server/utils/segmentation_pyramid.py +598 -0
  239. plexora/server/utils/smallestenclosingcircle.py +126 -0
  240. plexora/server/utils/tiffsurgeon.py +372 -0
  241. plexora/server_cli.py +48 -0
  242. plexora/test.py +0 -0
  243. plexora-0.0.1.dist-info/METADATA +341 -0
  244. plexora-0.0.1.dist-info/RECORD +248 -0
  245. plexora-0.0.1.dist-info/WHEEL +5 -0
  246. plexora-0.0.1.dist-info/entry_points.txt +12 -0
  247. plexora-0.0.1.dist-info/licenses/LICENSE +207 -0
  248. plexora-0.0.1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,212 @@
1
+ <!doctype html>
2
+ <head>
3
+ {% block title %}{% endblock %}
4
+ <meta content="utf-8" http-equiv="encoding">
5
+ <!-- Favicon -->
6
+ <link rel="icon" type="image/svg+xml" href="{{ data.base_url }}/client/src/img/logo.svg">
7
+ <link rel="alternate icon" href="{{ data.base_url }}/client/src/img/favicon.ico">
8
+ <link rel="apple-touch-icon" href="{{ data.base_url }}/client/src/img/apple-touch-icon.png">
9
+ <!-- Styles -->
10
+ <link rel="stylesheet" href="{{ data.base_url }}/client/src/css/tokens.css?v=20260814_unified_navbar">
11
+ <link rel="stylesheet" href="{{ data.base_url }}/client/src/css/main.css?v=20260824_icon_names">
12
+ {% block style %}{% endblock %}
13
+
14
+
15
+ <!-- Flask variables -->
16
+ <script src="{{ data.base_url }}/client/src/js/services/passVariablesToFrontend.js?v=20260811_selection_provider" type="text/javascript"></script>
17
+ <script type="text/javascript">
18
+ //<!--@formatter:off-->
19
+ window.flaskVariables = passVariablesToFrontend({{data|tojson}});
20
+ window.PLEXORA_BASE_URL = window.flaskVariables.base_url || "";
21
+ //<!--@formatter:on-->
22
+ </script>
23
+ <!-- App-wide status indicator. Loaded here, before everything else and not
24
+ deferred, because it wraps window.fetch to detect a dead/erroring
25
+ server and must be installed before any script issues a request. -->
26
+ <script src="{{ data.base_url }}/client/src/js/services/appStatus.js?v=20260816_app_status" type="text/javascript"></script>
27
+
28
+ <!-- Webpack -->
29
+ <script src="{{ data.base_url }}/client/dist/vendor_bundle.js?v=20260824_feature_source"></script>
30
+
31
+ <!-- OpenSeadragon imports-->
32
+ <script src="{{ data.base_url }}/client/external/openseadragon-bin-2.4.0/canvas-overlay-hd.js"></script>
33
+ <script src="{{ data.base_url }}/client/external/openseadragon-bin-2.4.0/openseadragon-scalebar.js"></script>
34
+
35
+ <!-- Scripts -->
36
+ <script src="{{ data.base_url }}/client/src/js/pluginRegistry.js?v=20260821_plugin_layers"></script>
37
+ <script src="{{ data.base_url }}/client/src/js/services/datasetContext.js?v=20260820_csv_markers_log1p"></script>
38
+ <script src="{{ data.base_url }}/client/src/js/views/rainbow.js?v=20260811_selection_provider"></script>
39
+ <script src="{{ data.base_url }}/client/src/js/services/dataLayer.js?v=20260819_feature_source"></script>
40
+ <script src="{{ data.base_url }}/client/src/js/services/numericData.js?v=20260819_feature_source"></script>
41
+ <script src="{{ data.base_url }}/client/src/js/views/channelList.js?v=20260811_selection_provider"></script>
42
+ <script src="{{ data.base_url }}/client/src/js/views/imageViewer.js?v=20260821_minimap2"></script>
43
+ <script src="{{ data.base_url }}/client/src/js/views/miniMap.js?v=20260821_minimap2"></script>
44
+ <script src="{{ data.base_url }}/client/src/js/views/rgbImageViewer.js?v=20260814_unified_navbar"></script>
45
+ <script src="{{ data.base_url }}/client/src/js/views/searchableSelect.js?v=20260821_cell_panel_visibility"></script>
46
+ <script src="{{ data.base_url }}/client/src/js/views/colorSwatchPicker.js?v=20260821_cell_panel_controls"></script>
47
+ <script src="{{ data.base_url }}/client/src/js/views/viewerControls.js?v=20260821_roi_composition_v2"></script>
48
+ <script src="{{ data.base_url }}/client/src/js/views/viewerSidebar.js?v=20260824_icon_names"></script>
49
+ <script src="{{ data.base_url }}/client/src/js/services/simpleEventHandler.js?v=20260811_selection_provider"></script>
50
+ <script src="{{ data.base_url }}/client/src/js/views/navbarControls.js?v=20260821_plugin_layers" defer></script>
51
+ <script src="{{ data.base_url }}/client/src/js/services/browsePicker.js?v=20260819_feature_source" defer></script>
52
+ <script src="{{ data.base_url }}/client/src/js/views/columnClassifier.js?v=20260820_csv_markers_log1p" defer></script>
53
+ <script src="{{ data.base_url }}/client/src/js/views/coordinateField.js?v=20260819_ask_cell_id" defer></script>
54
+ <script src="{{ data.base_url }}/client/src/js/views/dataSourceField.js?v=20260819_data_table_pick" defer></script>
55
+ <script src="{{ data.base_url }}/client/src/js/views/requirementsModal.js?v=20260820_optional_offer" defer></script>
56
+ <script src="{{ data.base_url }}/client/src/js/views/toolLoader.js?v=20260824_icon_names" defer></script>
57
+
58
+ {# Assets of the plugin this page opened with, if any. Declared by the
59
+ plugin (Plugin.scripts/styles) and cache-busted from its version, so
60
+ core names no plugin and the eager path here cannot drift out of sync
61
+ with the lazy path in tool_routes.py -- which is exactly what happened
62
+ when these two <script> tags were maintained by hand: they were once
63
+ omitted entirely, leaving the panel rendered but inert. #}
64
+ {% for url in data.active_tool_styles %}
65
+ <link rel="stylesheet" href="{{ url }}">
66
+ {% endfor %}
67
+ {% for url in data.active_tool_scripts %}
68
+ <script src="{{ url }}"></script>
69
+ {% endfor %}
70
+
71
+ </head>
72
+
73
+
74
+ <body>
75
+ <nav id="topBar" class="navbar navbar-expand-sm navbar-dark">
76
+ <a id="title" class="navbar-brand mb-0 h1" href="{{ data.base_url }}/">
77
+ <img src="{{ data.base_url }}/client/src/img/logo.svg" alt="" id="navbar-logo">
78
+ Plexora
79
+ </a>
80
+ <div class="collapse navbar-collapse" id="navbarSupportedContent">
81
+ <ul class="navbar-nav me-auto">
82
+
83
+ <li class="nav-item dropdown">
84
+ <span class="nav-link dropdown-toggle" id="navbarFileDropdown" role="button" data-bs-toggle="dropdown"
85
+ aria-haspopup="true" aria-expanded="false">
86
+ File
87
+ </span>
88
+ <div class="dropdown-menu" aria-labelledby="navbarFileDropdown">
89
+ <a class="dropdown-item" href="{{ data.base_url }}/upload_page">Create Project&hellip;</a>
90
+ <a class="dropdown-item" href="{{ data.base_url }}/open_project">Open Project&hellip;</a>
91
+ {# Ways in that an installed plugin asked for. Data, not
92
+ markup: the plugin supplies a label and a path inside its
93
+ own namespace, and this renders it with core's classes,
94
+ so contributing an entry cannot restyle or script the
95
+ menu. Renders nothing on a core-only build, which is what
96
+ keeps this page byte-identical without plugins. #}
97
+ {% for item in data.plugin_nav_items if item.menu == 'file' %}
98
+ <a class="dropdown-item" id="{{ item.id }}" href="{{ item.href }}">{{ item.label }}</a>
99
+ {% endfor %}
100
+ <div class="dropdown-divider"></div>
101
+ <a class="dropdown-item" id="nav_add_image" href="#">Add Image&hellip;</a>
102
+ <div class="dropdown-divider"></div>
103
+ <div class="dropdown-item nav-submenu-parent{% if not data.datasource %} disabled{% endif %}"
104
+ id="nav_export_menu" tabindex="0">
105
+ <span>Export Image</span>
106
+ <span class="fas fa-chevron-right nav-submenu-caret"></span>
107
+ <div class="dropdown-menu nav-submenu">
108
+ <button type="button" id="nav_export_image_png" class="dropdown-item"
109
+ {% if not data.datasource %}disabled{% endif %}>PNG&hellip;</button>
110
+ <button type="button" id="nav_export_image_pdf" class="dropdown-item"
111
+ {% if not data.datasource %}disabled{% endif %}>PDF&hellip;</button>
112
+ </div>
113
+ </div>
114
+ {# Quit ends the server process. In notebook/hosted mode
115
+ that process belongs to the kernel or the hub, not to the
116
+ person clicking, so the button is not offered -- and
117
+ /shutdown refuses it there too. #}
118
+ {% if not data.notebook_mode %}
119
+ <div class="dropdown-divider"></div>
120
+ <button type="button" id="nav_quit" class="dropdown-item text-danger">Quit</button>
121
+ {% endif %}
122
+ </div>
123
+ </li>
124
+
125
+ <li class="nav-item dropdown">
126
+ <span class="nav-link dropdown-toggle" id="navbarEditDropdown" role="button" data-bs-toggle="dropdown"
127
+ data-bs-auto-close="outside" aria-haspopup="true" aria-expanded="false">
128
+ Edit
129
+ </span>
130
+ <div class="dropdown-menu" aria-labelledby="navbarEditDropdown">
131
+ <span id="nav_undo" class="dropdown-item disabled" aria-disabled="true">Undo</span>
132
+ <span id="nav_redo" class="dropdown-item disabled" aria-disabled="true">Redo</span>
133
+ </div>
134
+ </li>
135
+
136
+ {% if data.datasource and data.image_kind != 'rgb' and data.available_tools %}
137
+ <li class="nav-item dropdown">
138
+ <span class="nav-link dropdown-toggle" id="navbarToolsDropdown" role="button"
139
+ data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
140
+ Tools
141
+ </span>
142
+ <div class="dropdown-menu" aria-labelledby="navbarToolsDropdown">
143
+ {% for tool in data.available_tools %}
144
+ <a class="dropdown-item" data-tool="{{ tool.name }}"
145
+ href="{{ data.base_url }}/{{ data.datasource }}/tools/{{ tool.name }}">{{ tool.label }}</a>
146
+ {% endfor %}
147
+ </div>
148
+ </li>
149
+ {% endif %}
150
+
151
+ <li class="nav-item dropdown">
152
+ <span class="nav-link dropdown-toggle" id="navbarViewDropdown" role="button" data-bs-toggle="dropdown"
153
+ data-bs-auto-close="outside" aria-haspopup="true" aria-expanded="false">
154
+ View
155
+ </span>
156
+ <div class="dropdown-menu" aria-labelledby="navbarViewDropdown">
157
+ <label class="dropdown-item nav-check-item">
158
+ <input type="checkbox" id="nav_toggle_sidebar" {% if not data.datasource %}disabled{% endif %}>
159
+ Show Sidebar
160
+ </label>
161
+ <label class="dropdown-item nav-check-item">
162
+ <input type="checkbox" id="nav_toggle_scalebar" checked
163
+ {% if not data.datasource or data.image_kind == 'rgb' %}disabled{% endif %}>
164
+ Show Scalebar
165
+ </label>
166
+ <div class="dropdown-divider"></div>
167
+ {# Mirrors the sidebar's Cells control. Radios rather than
168
+ checkboxes because the four are one choice -- the pair of
169
+ checkboxes this replaced let the menu show a combination
170
+ the viewer cannot draw. Which of them are usable depends
171
+ on the project and is settled client-side, where the
172
+ config is (navbarControls.js). #}
173
+ <label class="dropdown-item nav-check-item">
174
+ <input type="radio" name="nav_cell_mode" id="nav_cell_mode_none" value="none" checked
175
+ {% if not data.datasource or data.image_kind == 'rgb' %}disabled{% endif %}>
176
+ No Cells
177
+ </label>
178
+ <label class="dropdown-item nav-check-item">
179
+ <input type="radio" name="nav_cell_mode" id="nav_cell_mode_centroids" value="centroids"
180
+ {% if not data.datasource or data.image_kind == 'rgb' %}disabled{% endif %}>
181
+ Show Centroids
182
+ </label>
183
+ <label class="dropdown-item nav-check-item">
184
+ <input type="radio" name="nav_cell_mode" id="nav_cell_mode_outlines" value="outlines"
185
+ {% if not data.datasource or data.image_kind == 'rgb' %}disabled{% endif %}>
186
+ Show Outlines
187
+ </label>
188
+ <label class="dropdown-item nav-check-item">
189
+ <input type="radio" name="nav_cell_mode" id="nav_cell_mode_filled" value="filled"
190
+ {% if not data.datasource or data.image_kind == 'rgb' %}disabled{% endif %}>
191
+ Fill Cells
192
+ </label>
193
+ <label class="dropdown-item nav-check-item"
194
+ data-tooltip="Reloads the image in high resolution -- may be slower.">
195
+ <input type="checkbox" id="nav_toggle_hd"
196
+ {% if not data.datasource or data.image_kind == 'rgb' %}disabled{% endif %}>
197
+ HD Mode
198
+ </label>
199
+ </div>
200
+ </li>
201
+
202
+ </ul>
203
+ </div>
204
+ <!-- Sits outside .navbar-collapse so it stays visible when the navbar
205
+ collapses. Filled in by appStatus.js. -->
206
+ <div id="app_status"></div>
207
+ </nav>
208
+
209
+ {% block content %}{% endblock %}
210
+
211
+ </body>
212
+ </html>
@@ -0,0 +1,238 @@
1
+ {% extends 'base.html' %}
2
+
3
+ {% block style %}
4
+ <link rel="stylesheet" href="{{ data.base_url }}/client/src/css/viewer.css?v=20260823_viewer_refactor">
5
+ <link rel="stylesheet" href="{{ data.base_url }}/client/src/css/quickView.css?v=20260815_gating_fixes">
6
+ {% endblock %}
7
+
8
+ {% block content %}
9
+
10
+ {% if data.datasource %}
11
+ <div id="container" class="container">
12
+ <div id="bodyDiv" class="viewer-shell">
13
+ <aside id="viewer_sidebar" class="viewer-sidebar">
14
+ <div class="sidebar-header">
15
+ <div>
16
+ <span class="sidebar-kicker">Plexora</span>
17
+ <h1>Viewer</h1>
18
+ </div>
19
+ <button id="sidebar_collapse_button" class="icon-button" title="Collapse controls" type="button">
20
+ <span class="fas fa-chevron-left"></span>
21
+ </button>
22
+ </div>
23
+
24
+ {% if data.image_kind != 'rgb' %}
25
+ {# Foldable, like a plugin's card, but fixed in place: this is core's
26
+ own section, so it has no grip and no remove button. Only the
27
+ chevron the plugin cards also carry, because a long channel list
28
+ is the thing that pushes the plugin stack off the bottom of the
29
+ sidebar. The whole title row is the toggle -- a 24px chevron is a
30
+ small target for something the user reaches for repeatedly. #}
31
+ <section class="sidebar-section" id="image_channel_section">
32
+ <div class="section-heading">
33
+ <h2>
34
+ <button id="image_channel_collapse" class="section-collapse" type="button"
35
+ aria-expanded="true" aria-controls="image_channel_body"
36
+ title="Collapse or expand image channels">
37
+ <span class="fas fa-chevron-down section-collapse-chevron"></span>
38
+ <span class="fas fa-layer-group section-heading-icon"></span>
39
+ Image Channels
40
+ </button>
41
+ </h2>
42
+ <div class="section-actions">
43
+ <button id="channels_upload_icon" class="icon-button" title="Rename channels from a CSV (one name per row, in channel order)" type="button">
44
+ <span class="fas fa-file-arrow-up"></span>
45
+ </button>
46
+ </div>
47
+ </div>
48
+ {# Everything the chevron folds away. A wrapper rather than
49
+ hiding the section's children one by one, so the heading --
50
+ and the CSV-rename button that sits in it -- stay reachable
51
+ while folded. #}
52
+ <div id="image_channel_body" class="section-body">
53
+ <form id="channels_arrow-upload-form">
54
+ <input id="channels-upload-from-arrow" class="hidden" type="file">
55
+ <input type="hidden" id="channels_upload-arrow-datasource" name="channels_upload-arrow-datasource">
56
+ </form>
57
+ <div class="selected-count">
58
+ <span id="num-selected-channels">0</span>
59
+ <span>active</span>
60
+ <span class="selected-count-max">· <span id="max-channels">15</span> max</span>
61
+ </div>
62
+ <div id="channel_slot_list" class="channel-slot-list"></div>
63
+ <button id="add_channel_button" class="sidebar-action secondary" type="button">
64
+ <span class="fas fa-plus"></span>
65
+ Add Channel
66
+ </button>
67
+ </div>
68
+ </section>
69
+ {% endif %}
70
+
71
+ {# Plugin sidebar mount point: rendered server-side on a direct or
72
+ bookmarked ?tool=<name> load; otherwise left empty and filled
73
+ client-side by toolLoader.js on first open, via the
74
+ /tools/<tool>/panel endpoint, so opening or closing mid-session
75
+ never requires a page navigation.
76
+
77
+ Which template lands here comes from the active plugin's own
78
+ Plugin.panels mapping, keyed by this slot id. Applicability is
79
+ already decided in page_routes (a tool is only active if this
80
+ datasource satisfies what the plugin declared it needs), so
81
+ there is nothing image-kind-specific to test here. #}
82
+ <div id="tool_panel_slot" data-tool-mount="{{ data.active_tool }}"{% if not data.active_tool_panels.tool_panel_slot %} class="tool-panel-hidden"{% endif %}>
83
+ {% if data.active_tool_panels.tool_panel_slot %}
84
+ {% include data.active_tool_panels.tool_panel_slot %}
85
+ {% endif %}
86
+ </div>
87
+
88
+ {% if data.image_kind != 'rgb' %}
89
+ <section class="sidebar-section compact">
90
+ {# How cells are drawn, as one choice rather than two toggles.
91
+ Exactly one representation is active at a time -- there is no
92
+ meaningful "filled AND outlined" state, and the two checkboxes
93
+ this replaced could express one ("Outlines" plus "Centroids")
94
+ that the renderer then had to arbitrate.
95
+
96
+ Geometry is core's, deliberately: a plugin that colours cells
97
+ supplies the colours and nothing else, so every such plugin
98
+ gets these three representations without shipping three
99
+ renderers of its own.
100
+
101
+ Every option but None starts disabled and viewerControls.init()
102
+ enables what this project can actually draw. Rendering them
103
+ live and switching them off a moment later would offer, briefly,
104
+ a Filled button that cannot fill.
105
+
106
+ One line, with HD. The "Cells" caption this row used to carry
107
+ is on the group's aria-label instead: four buttons reading
108
+ None/Centroids/Outlines/Filled do not need a word above them
109
+ to say what they are about, and the space it was taking is the
110
+ space HD now sits in. #}
111
+ <div class="cell-display-row">
112
+ <div class="cell-mode-control" id="cell_display_control" role="radiogroup" aria-label="Cells">
113
+ <div class="cell-mode-options">
114
+ <button type="button" id="cell_mode_none" class="cell-mode-option is-active"
115
+ data-cell-mode="none" role="radio" aria-checked="true">None</button>
116
+ <button type="button" id="cell_mode_centroids" class="cell-mode-option"
117
+ data-cell-mode="centroids" role="radio" aria-checked="false" disabled
118
+ title="Needs cell coordinates">Centroids</button>
119
+ <button type="button" id="cell_mode_outlines" class="cell-mode-option"
120
+ data-cell-mode="outlines" role="radio" aria-checked="false" disabled
121
+ title="Needs a segmentation mask">Outlines</button>
122
+ <button type="button" id="cell_mode_filled" class="cell-mode-option"
123
+ data-cell-mode="filled" role="radio" aria-checked="false" disabled
124
+ title="Needs a segmentation mask">Filled</button>
125
+ </div>
126
+ </div>
127
+ {# HD reads as one more button in the row rather than a
128
+ stray checkbox beside a segmented control. The input is
129
+ still a real checkbox -- everything that reads
130
+ #viewer_controls_hd.checked is unchanged -- it is just
131
+ drawn as the <span> next to it, which is why the span is
132
+ a sibling and not a wrapper. #}
133
+ <div id="viewer_controls_panel">
134
+ <label class="cell-toggle" data-tooltip="Reloads the image in high resolution -- may be slower.">
135
+ <input id="viewer_controls_hd" type="checkbox">
136
+ <span>HD</span>
137
+ </label>
138
+ </div>
139
+ </div>
140
+
141
+ {# Only while centroids are what is drawn. Size is meaningless
142
+ for the other three, and a control that is present but inert
143
+ reads as broken rather than as not applicable. #}
144
+ <div id="cell_point_size_row" class="cell-point-size" hidden>
145
+ <label class="control-label" for="cell_point_size">Point size</label>
146
+ <input id="cell_point_size" type="range"
147
+ min="0.4" max="3" step="0.1" value="1">
148
+ </div>
149
+
150
+ {# How strongly the ACTIVE plugin's layer sits over what is under
151
+ it -- the tissue, and any layer stacked below it. Core's and
152
+ shared, for the same reason the mode buttons are: compositing
153
+ belongs to the viewer, so every plugin that colours cells gets
154
+ this without shipping a slider of its own. Hidden until a
155
+ plugin registers a layer; there is nothing to fade a plain
156
+ white cell layer against. #}
157
+ <div id="cell_layer_opacity_row" class="cell-layer-opacity" hidden>
158
+ <label class="control-label" for="cell_layer_opacity">Opacity</label>
159
+ <input id="cell_layer_opacity" type="range"
160
+ min="0" max="100" step="1" value="70">
161
+ <span id="cell_layer_opacity_value" class="cell-layer-opacity-value">70%</span>
162
+ </div>
163
+ </section>
164
+ {% endif %}
165
+ </aside>
166
+
167
+ <div id="openseadragon_wrapper">
168
+ <button id="sidebar_expand_button" class="sidebar-expand-button" title="Show controls" type="button">
169
+ <span class="fas fa-chevron-right"></span>
170
+ </button>
171
+ <div id="openseadragon">
172
+ <div id="openseadragon_loader" class="viewer-loader">
173
+ <div class="lds-ellipsis">
174
+ <div></div>
175
+ <div></div>
176
+ <div></div>
177
+ <div></div>
178
+ </div>
179
+ </div>
180
+ </div>
181
+ </div>
182
+
183
+ <div id="legacy_controls_mount" aria-hidden="true">
184
+ <div id="channel_list_wrapper">
185
+ <div id="lowRow1" class="row">
186
+ <div id="boxHeading1" class="col-auto">
187
+ <span class="box-heading">Channels</span>
188
+ </div>
189
+ <div id="download-channels-header-div"></div>
190
+ <div class="col-auto" id="selected-channels-header-div"></div>
191
+ </div>
192
+ <div class="list" id="channel_list">
193
+ <div class="row">
194
+ <div class="col-auto">
195
+ <div class="lds-ellipsis" id="channel_list_loader"></div>
196
+ </div>
197
+ </div>
198
+ </div>
199
+ </div>
200
+
201
+ <div id="tool_panel_legacy_slot" data-tool-mount="{{ data.active_tool }}">
202
+ {% if data.active_tool_panels.tool_panel_legacy_slot %}
203
+ {% include data.active_tool_panels.tool_panel_legacy_slot %}
204
+ {% endif %}
205
+ </div>
206
+ </div>
207
+ </div>
208
+ </div>
209
+ <script src="{{ data.base_url }}/client/src/js/main.js?v=20260821_roi_composition_v2" defer></script>
210
+ {% else %}
211
+ <div id="quick_view_landing" class="quick-view-landing">
212
+ <div class="quick-view-card">
213
+ <span class="sidebar-kicker">Plexora</span>
214
+ <h1>Take a Quick Look</h1>
215
+ <p class="quick-view-subtitle">Open a local image. It loads straight into the viewer -- no setup required.</p>
216
+
217
+ <div id="quick_view_dropzone" class="quick-view-dropzone" tabindex="0">
218
+ <span class="fas fa-folder-open quick-view-dropzone-icon"></span>
219
+ <div class="quick-view-dropzone-text">Click here to open an image</div>
220
+ <div class="quick-view-dropzone-hint">Opens a file browser on this machine</div>
221
+ </div>
222
+
223
+ <div id="quick_view_path_fallback" class="quick-view-path-fallback" hidden>
224
+ <label for="quick_view_path_input" class="quick-view-path-label">Image file path</label>
225
+ <div class="quick-view-path-row">
226
+ <input id="quick_view_path_input" type="text" autocomplete="off" spellcheck="false" placeholder="C:\path\to\image.ome.tif">
227
+ <button id="quick_view_path_load" class="sidebar-action" type="button" disabled>Load</button>
228
+ </div>
229
+ </div>
230
+
231
+ <div id="quick_view_status" class="quick-view-status" hidden></div>
232
+
233
+ <p class="quick-view-footer">Need segmentation, gating, or a feature table? Use the <a href="{{ data.base_url }}/upload_page">full import wizard</a> instead.</p>
234
+ </div>
235
+ </div>
236
+ <script src="{{ data.base_url }}/client/src/js/views/quickViewLanding.js?v=20260811_selection_provider" defer></script>
237
+ {% endif %}
238
+ {% endblock %}
@@ -0,0 +1,96 @@
1
+ {% extends 'base.html' %}
2
+ {% block style %}
3
+ <link rel="stylesheet" href="{{ data.base_url }}/client/src/css/openProject.css?v=20260824_shared_roots">
4
+ {% endblock %}
5
+ {% block content %}
6
+ <title>Plexora - Open Project</title>
7
+ <div class="open-project-page">
8
+ <div class="open-project-header">
9
+ <div>
10
+ <h1>Open Project</h1>
11
+ <p class="open-project-subtitle" id="project-count">Loading&hellip;</p>
12
+ </div>
13
+ <a class="btn btn-primary open-project-create" href="{{ data.base_url }}/upload_page">
14
+ <span class="fas fa-plus"></span> Create Project&hellip;
15
+ </a>
16
+ </div>
17
+
18
+ {# [ Projects ] [ Figures ] ... -- the strip only exists once something else
19
+ is on it. A single tab reading "Projects" beside nothing is a control
20
+ that does not control anything, so a core-only build renders no strip at
21
+ all rather than a degenerate one. #}
22
+ {% set page_tabs = data.plugin_nav_items | selectattr('menu', 'equalto', 'open_project') | list %}
23
+ {% if page_tabs %}
24
+ <div class="open-project-tabs" role="tablist">
25
+ <span class="open-project-tab is-active" role="tab" aria-selected="true">Projects</span>
26
+ {% for item in page_tabs %}
27
+ <a class="open-project-tab" id="{{ item.id }}" role="tab" aria-selected="false"
28
+ href="{{ item.href }}">{{ item.label }}</a>
29
+ {% endfor %}
30
+ </div>
31
+ {% endif %}
32
+
33
+ <div class="open-project-toolbar">
34
+ <div class="open-project-search">
35
+ <span class="fas fa-magnifying-glass"></span>
36
+ <input type="text" id="project-search" placeholder="Search projects&hellip;" autocomplete="off">
37
+ </div>
38
+ <select id="project-sort" class="open-project-sort">
39
+ <option value="lastOpenedAt">Recently Opened</option>
40
+ <option value="createdAt">Date Created</option>
41
+ <option value="name">Name (A&ndash;Z)</option>
42
+ </select>
43
+ <div class="open-project-view-toggle" role="group" aria-label="View">
44
+ <button type="button" id="project-view-grid" class="view-toggle-button" title="Grid view">
45
+ <span class="fas fa-grip"></span>
46
+ </button>
47
+ <button type="button" id="project-view-list" class="view-toggle-button" title="List view">
48
+ <span class="fas fa-list"></span>
49
+ </button>
50
+ </div>
51
+ </div>
52
+
53
+ <div id="project-results" class="project-results project-grid"></div>
54
+
55
+ <div id="project-empty-state" class="open-project-state" hidden>
56
+ <span class="fas fa-folder-open open-project-state-icon"></span>
57
+ <p>No projects yet</p>
58
+ <a class="btn btn-primary" href="{{ data.base_url }}/upload_page">Create your first project&hellip;</a>
59
+ </div>
60
+
61
+ <div id="project-no-results" class="open-project-state" hidden>
62
+ <span class="fas fa-magnifying-glass open-project-state-icon"></span>
63
+ <p>No projects match your search</p>
64
+ </div>
65
+
66
+ <div class="modal fade" id="deleteProjectModal" tabindex="-1" aria-hidden="true">
67
+ <div class="modal-dialog modal-dialog-centered">
68
+ <div class="modal-content">
69
+ <div class="modal-header">
70
+ <h5 class="modal-title">Delete project?</h5>
71
+ <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
72
+ </div>
73
+ <div class="modal-body">
74
+ <p>Delete <strong id="deleteProjectModalName"></strong>? This removes its data from disk and cannot be undone.</p>
75
+ </div>
76
+ <div class="modal-footer">
77
+ <button type="button" class="btn btn-outline-light" data-bs-dismiss="modal">Cancel</button>
78
+ {# A button, not a link: deleting posts, so there is no URL
79
+ for a prefetcher or a stale bookmark to follow.
80
+
81
+ Dismissed by the data API rather than from the click
82
+ handler, which used to call window.bootstrap.Modal --
83
+ undefined here, because vendor.js imports bootstrap as an
84
+ ES module. That registers this data API but never assigns
85
+ the global, so the optional chain the handler used
86
+ evaluated to undefined and the dialog just sat there
87
+ after a successful delete. #}
88
+ <button id="deleteProjectModalConfirm" type="button" class="btn btn-danger"
89
+ data-bs-dismiss="modal">Delete</button>
90
+ </div>
91
+ </div>
92
+ </div>
93
+ </div>
94
+ </div>
95
+ <script src="{{ data.base_url }}/client/src/js/views/openProjectPage.js?v=20260824_shared_roots" defer></script>
96
+ {% endblock %}
@@ -0,0 +1,86 @@
1
+ {% extends 'base.html' %}
2
+ {% block style %}
3
+ <link rel="stylesheet" href="{{ data.base_url }}/client/src/css/import.css?v=20260819_feature_source">
4
+ {% endblock %}
5
+ {% block content %}
6
+ <title>Plexora - Columns</title>
7
+ {#
8
+ The one screen that survives from the old two-step import, and the only one.
9
+ Which columns are markers is a fact about the dataset that every plugin then
10
+ reads, so it is worth one confirmation -- and it is the only thing about a
11
+ CSV that cannot be worked out reliably from the file.
12
+ #}
13
+ <div class="import-page">
14
+ <div id="upload-parent">
15
+ <div class="import-card import-card-wide">
16
+ <span class="import-kicker">{{ data.datasetName }}</span>
17
+ <h1>Confirm Columns</h1>
18
+ <p class="import-subtitle">We have guessed which columns hold marker intensities and which
19
+ describe the cells. Correct anything we got wrong before continuing.</p>
20
+
21
+ <div id="classifier"></div>
22
+
23
+ <div class="import-actions">
24
+ <button id="columns_save" class="btn btn-primary" type="button">Continue</button>
25
+ </div>
26
+ <div id="columns_error" class="import-alert" role="alert" hidden></div>
27
+ </div>
28
+ </div>
29
+ </div>
30
+ <script src="{{ data.base_url }}/client/src/js/views/columnClassifier.js?v=20260820_csv_markers_log1p"></script>
31
+ <script src="{{ data.base_url }}/client/src/js/views/segmentationProgress.js?v=20260817_single_import"></script>
32
+ <script>
33
+ (function () {
34
+ const data = {{ data|tojson }};
35
+ const classifier = PlexoraColumnClassifier.mount(
36
+ document.getElementById("classifier"),
37
+ {
38
+ markers: data.markers,
39
+ metadata: data.metadata,
40
+ roles: data.roles,
41
+ roleLabels: data.roleLabels,
42
+ },
43
+ );
44
+
45
+ const button = document.getElementById("columns_save");
46
+ const error = document.getElementById("columns_error");
47
+
48
+ button.addEventListener("click", async () => {
49
+ button.disabled = true;
50
+ error.hidden = true;
51
+ const task = window.PlexoraStatus?.begin("Saving columns");
52
+ try {
53
+ const response = await fetch(
54
+ plexoraUrl(`project/${encodeURIComponent(data.datasetName)}/columns`),
55
+ {
56
+ method: "POST",
57
+ headers: { "Content-Type": "application/json" },
58
+ body: JSON.stringify(classifier.value()),
59
+ },
60
+ );
61
+ const result = await response.json();
62
+ if (!response.ok || !result.success) {
63
+ throw new Error(result.error || "Could not save the columns");
64
+ }
65
+ task?.done();
66
+ // A mask supplied at import is still converting; the overlay
67
+ // waits for it rather than opening a viewer with no cells.
68
+ const viewerUrl = plexoraUrl(encodeURIComponent(data.datasetName));
69
+ if (result.segmentation_pending) {
70
+ awaitSegmentationThenOpen({
71
+ datasource: data.datasetName,
72
+ redirectUrl: viewerUrl,
73
+ });
74
+ } else {
75
+ window.location.href = viewerUrl;
76
+ }
77
+ } catch (e) {
78
+ task?.fail("Save failed");
79
+ error.textContent = e.message;
80
+ error.hidden = false;
81
+ button.disabled = false;
82
+ }
83
+ });
84
+ })();
85
+ </script>
86
+ {% endblock %}