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,255 @@
1
+ /**
2
+ * FigureBuilderApi - this plugin's own HTTP client.
3
+ *
4
+ * A plugin owns the addresses of its own routes. Core's DataLayer must never
5
+ * learn them, which is why `plugins/figure_builder/...` appears in this file
6
+ * and nowhere in plexora/client/src/ (asserted by
7
+ * tests/test_datalayer_requests.py). It is also why the Figures tab on the Open
8
+ * Project page is a server-rendered link rather than something core JavaScript
9
+ * feature-detects: detection would mean core calling a plugin route.
10
+ *
11
+ * Nothing here swallows its own errors, for the reason RoiApi documents: a save
12
+ * that quietly does nothing means the user's work exists only in a tab they are
13
+ * about to close, and a figure can represent a day of it.
14
+ *
15
+ * Requests return `{ok, status, data}` rather than throwing on a non-2xx,
16
+ * because for three of them the status IS the answer. 404 means the figure has
17
+ * been deleted -- probably in the other tab. 409 means somebody else saved
18
+ * first and there is work worth keeping on both sides. 422 means the stored
19
+ * document cannot be read by this build, and nothing should be drawn or written
20
+ * until the user has been told.
21
+ */
22
+ class FigureBuilderApi {
23
+
24
+ /**
25
+ * @param {object} [options]
26
+ * @param {function} [options.url] path -> URL. Supplied by the plugin
27
+ * context inside the viewer; on this plugin's own pages there is no
28
+ * context, so it falls back to core's global, which is the same
29
+ * function the viewer's context wraps.
30
+ */
31
+ constructor(options) {
32
+ const provided = options && options.url;
33
+ this.url = provided || ((path) => window.plexoraUrl(path));
34
+ }
35
+
36
+ _api(path) {
37
+ return this.url("plugins/figure_builder/api/" + path);
38
+ }
39
+
40
+ /** Where a figure's own page lives -- for links and for navigation. */
41
+ figureHref(figureId) {
42
+ return this.url("plugins/figure_builder/figure/" + encodeURIComponent(figureId));
43
+ }
44
+
45
+ libraryHref() {
46
+ return this.url("plugins/figure_builder/figures");
47
+ }
48
+
49
+ async _read(response) {
50
+ let data = {};
51
+ try {
52
+ data = await response.json();
53
+ } catch (e) {
54
+ // A body that is not JSON is a server fault, not an answer. Keep
55
+ // the status, which is the part still worth acting on.
56
+ data = { error: "the server sent a response that could not be read" };
57
+ }
58
+ return { ok: response.ok, status: response.status, data };
59
+ }
60
+
61
+ async _json(path, method, body) {
62
+ const response = await fetch(this._api(path), {
63
+ method: method,
64
+ headers: { "Accept": "application/json", "Content-Type": "application/json" },
65
+ body: JSON.stringify(body || {}),
66
+ });
67
+ return this._read(response);
68
+ }
69
+
70
+ async listFigures() {
71
+ const response = await fetch(this._api("figures"));
72
+ return this._read(response);
73
+ }
74
+
75
+ async createFigure(title) {
76
+ return this._json("figures", "POST", { title: title || "" });
77
+ }
78
+
79
+ async getFigure(figureId) {
80
+ const response = await fetch(this._api("figures/" + encodeURIComponent(figureId)));
81
+ return this._read(response);
82
+ }
83
+
84
+ /**
85
+ * Apply a batch of edits against the revision the client last read.
86
+ *
87
+ * One call is one undo step, and the batch is all-or-nothing. That is not
88
+ * an optimisation: a five-panel split has to undo as one action, and half a
89
+ * split landing is a figure in a state the user never asked for.
90
+ */
91
+ async patchFigure(figureId, baseRevision, operations) {
92
+ return this._json("figures/" + encodeURIComponent(figureId), "PATCH", {
93
+ base_revision: baseRevision,
94
+ operations: operations,
95
+ });
96
+ }
97
+
98
+ async replaceFigure(figureId, baseRevision, document) {
99
+ return this._json("figures/" + encodeURIComponent(figureId), "PUT", {
100
+ base_revision: baseRevision,
101
+ document: document,
102
+ });
103
+ }
104
+
105
+ async deleteFigure(figureId) {
106
+ const response = await fetch(this._api("figures/" + encodeURIComponent(figureId)),
107
+ { method: "DELETE" });
108
+ return this._read(response);
109
+ }
110
+
111
+ async duplicateFigure(figureId, title) {
112
+ return this._json("figures/" + encodeURIComponent(figureId) + "/duplicate", "POST",
113
+ { title: title || "" });
114
+ }
115
+
116
+ /**
117
+ * Store a panel's preview raster.
118
+ *
119
+ * Sent as bytes rather than as a base64 field on a JSON body: a WebP of a
120
+ * canvas crop is tens of kilobytes and base64 would add a third to every
121
+ * capture. `renderRevision` is what lets the server refuse a slow render
122
+ * that lands after a newer one.
123
+ */
124
+ async putPreview(figureId, panelId, renderRevision, blob, size) {
125
+ const query = new URLSearchParams({
126
+ render_revision: String(renderRevision),
127
+ width: String((size && size.width) || 0),
128
+ height: String((size && size.height) || 0),
129
+ });
130
+ const response = await fetch(
131
+ this._api(`figures/${encodeURIComponent(figureId)}/previews/${encodeURIComponent(panelId)}`)
132
+ + "?" + query,
133
+ { method: "POST", headers: { "Content-Type": blob.type || "image/webp" }, body: blob });
134
+ return this._read(response);
135
+ }
136
+
137
+ /**
138
+ * Where a panel's preview can be fetched from.
139
+ *
140
+ * `render_revision` rides along as a cache key rather than as something the
141
+ * server reads: the route serves whatever is current, and the changing URL
142
+ * is what stops the browser showing a panel the user has already edited.
143
+ */
144
+ previewUrl(figureId, panelId, renderRevision) {
145
+ return this._api(`figures/${encodeURIComponent(figureId)}/previews/${encodeURIComponent(panelId)}`)
146
+ + "?v=" + String(renderRevision || 0);
147
+ }
148
+
149
+ async putThumbnail(figureId, blob) {
150
+ const response = await fetch(this._api(`figures/${encodeURIComponent(figureId)}/thumbnail`), {
151
+ method: "PUT",
152
+ headers: { "Content-Type": blob.type || "image/webp" },
153
+ body: blob,
154
+ });
155
+ return this._read(response);
156
+ }
157
+
158
+ thumbnailUrl(figureId, revision) {
159
+ return this._api(`figures/${encodeURIComponent(figureId)}/thumbnail`)
160
+ + "?v=" + String(revision || 0);
161
+ }
162
+
163
+ /**
164
+ * One channel of one region of a source, as raw numbers.
165
+ *
166
+ * Returned as a typed array rather than as an image, because the caller
167
+ * composites it: Quick Edit's mini viewer keeps every channel's pixels and
168
+ * recolours them in the browser, so changing a colour or dragging a
169
+ * contrast slider costs no request at all.
170
+ */
171
+ async readPixels(figureId, sourceId, params) {
172
+ const query = new URLSearchParams({
173
+ channel: params.channel,
174
+ x: String(Math.round(params.x)), y: String(Math.round(params.y)),
175
+ w: String(Math.round(params.w)), h: String(Math.round(params.h)),
176
+ out_w: String(Math.round(params.out_w)), out_h: String(Math.round(params.out_h)),
177
+ });
178
+ const response = await fetch(
179
+ this._api(`figures/${encodeURIComponent(figureId)}`
180
+ + `/sources/${encodeURIComponent(sourceId)}/pixels`) + "?" + query);
181
+ if (!response.ok) return { ok: false, data: null };
182
+ const buffer = await response.arrayBuffer();
183
+ const [width, height] = (response.headers.get("X-Fb-Shape") || "0x0")
184
+ .split("x").map((value) => parseInt(value, 10) || 0);
185
+ return {
186
+ ok: true,
187
+ data: new Uint16Array(buffer),
188
+ width: width,
189
+ height: height,
190
+ box: (response.headers.get("X-Fb-Box") || "").split(",").map(Number),
191
+ };
192
+ }
193
+
194
+ /** A channel's intensity range, for a contrast slider's domain. */
195
+ async pixelInfo(figureId, sourceId, channel) {
196
+ const response = await fetch(
197
+ this._api(`figures/${encodeURIComponent(figureId)}`
198
+ + `/sources/${encodeURIComponent(sourceId)}/pixel_info`)
199
+ + "?" + new URLSearchParams({ channel: channel }));
200
+ return this._read(response);
201
+ }
202
+
203
+ async addAsset(figureId, filename, blob) {
204
+ const response = await fetch(
205
+ this._api(`figures/${encodeURIComponent(figureId)}/assets`)
206
+ + "?" + new URLSearchParams({ filename: filename }),
207
+ { method: "POST", headers: { "Content-Type": blob.type || "application/octet-stream" }, body: blob });
208
+ return this._read(response);
209
+ }
210
+
211
+ assetUrl(figureId, assetId) {
212
+ return this._api(`figures/${encodeURIComponent(figureId)}/assets/${encodeURIComponent(assetId)}`);
213
+ }
214
+
215
+ // -- export ----------------------------------------------------------
216
+ //
217
+ // Four calls rather than one download link. An eighteen-panel figure at 600
218
+ // DPI is minutes of rendering, longer than any browser will hold a request
219
+ // open -- so starting, following, cancelling and fetching are separate, and
220
+ // the user can watch it and change their mind.
221
+
222
+ /** What this export would be like, before any of it is rendered. */
223
+ async preflightExport(figureId, options) {
224
+ return this._json(`figures/${encodeURIComponent(figureId)}/export/preflight`,
225
+ "POST", options);
226
+ }
227
+
228
+ async startExport(figureId, options) {
229
+ return this._json(`figures/${encodeURIComponent(figureId)}/export`, "POST", options);
230
+ }
231
+
232
+ async exportStatus(figureId, jobId) {
233
+ const response = await fetch(this._api(
234
+ `figures/${encodeURIComponent(figureId)}/export/${encodeURIComponent(jobId)}`));
235
+ return this._read(response);
236
+ }
237
+
238
+ async cancelExport(figureId, jobId) {
239
+ const response = await fetch(this._api(
240
+ `figures/${encodeURIComponent(figureId)}/export/${encodeURIComponent(jobId)}`),
241
+ { method: "DELETE" });
242
+ return this._read(response);
243
+ }
244
+
245
+ exportDownloadUrl(figureId, jobId) {
246
+ return this._api(
247
+ `figures/${encodeURIComponent(figureId)}/export/${encodeURIComponent(jobId)}/download`);
248
+ }
249
+
250
+ /** What a project image looks like right now -- for capture and relinking. */
251
+ async describeSource(datasource) {
252
+ const response = await fetch(this._api("sources/" + encodeURIComponent(datasource)));
253
+ return this._read(response);
254
+ }
255
+ }