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.
- plexora/__init__.py +129 -0
- plexora/__main__.py +15 -0
- plexora/_url.py +105 -0
- plexora/api/__init__.py +55 -0
- plexora/api/dataset.py +398 -0
- plexora/api/http.py +19 -0
- plexora/api/plugin.py +541 -0
- plexora/api/store.py +221 -0
- plexora/cli.py +777 -0
- plexora/client/dist/354_bundle.js +2 -0
- plexora/client/dist/354_bundle.js.LICENSE.txt +20 -0
- plexora/client/dist/418_bundle.js +2 -0
- plexora/client/dist/418_bundle.js.LICENSE.txt +1 -0
- plexora/client/dist/693b935cb1f907814be9c6199a68c217.svg +19 -0
- plexora/client/dist/713c134ff47cd328a5b711526f151082.svg +18 -0
- plexora/client/dist/770_bundle.js +1 -0
- plexora/client/dist/ff743f408972e0e96ca9153ed5ae4c83.svg +23 -0
- plexora/client/dist/vendor_bundle.js +2 -0
- plexora/client/dist/vendor_bundle.js.LICENSE.txt +308 -0
- plexora/client/external/openseadragon-bin-2.4.0/LICENSE.txt +28 -0
- plexora/client/external/openseadragon-bin-2.4.0/canvas-overlay-hd.js +140 -0
- plexora/client/external/openseadragon-bin-2.4.0/changelog.txt +501 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/.gitattributes +17 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/LICENSE.txt +116 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/Toolbar.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/fullpage.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/home.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/next.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/previous.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/rotateleft.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/rotateright.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/Photoshop/zoom.psd +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/README.md +9 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/fullpage_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/home_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/next_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/previous_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateleft_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/rotateright_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomin_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_grouphover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_hover.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_pressed.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-flat-toolbar-icons-master/images/zoomout_rest.png +0 -0
- plexora/client/external/openseadragon-bin-2.4.0/openseadragon-scalebar.js +562 -0
- plexora/client/src/css/import.css +474 -0
- plexora/client/src/css/main.css +903 -0
- plexora/client/src/css/openProject.css +363 -0
- plexora/client/src/css/quickView.css +166 -0
- plexora/client/src/css/tokens.css +51 -0
- plexora/client/src/css/viewer.css +1655 -0
- plexora/client/src/img/apple-touch-icon.png +0 -0
- plexora/client/src/img/favicon.ico +0 -0
- plexora/client/src/img/logo.ai +8892 -33
- plexora/client/src/img/logo.svg +1 -0
- plexora/client/src/img/logo_with_text.ai +9011 -34
- plexora/client/src/img/logo_with_text.svg +1 -0
- plexora/client/src/js/main.js +704 -0
- plexora/client/src/js/pluginRegistry.js +108 -0
- plexora/client/src/js/services/appStatus.js +286 -0
- plexora/client/src/js/services/browsePicker.js +73 -0
- plexora/client/src/js/services/dataLayer.js +400 -0
- plexora/client/src/js/services/datasetContext.js +143 -0
- plexora/client/src/js/services/glRenderer.js +143 -0
- plexora/client/src/js/services/importFormValidation.js +257 -0
- plexora/client/src/js/services/numericData.js +100 -0
- plexora/client/src/js/services/passVariablesToFrontend.js +20 -0
- plexora/client/src/js/services/simpleEventHandler.js +25 -0
- plexora/client/src/js/vendor.js +29 -0
- plexora/client/src/js/views/channelList.js +663 -0
- plexora/client/src/js/views/colorSwatchPicker.js +202 -0
- plexora/client/src/js/views/columnClassifier.js +146 -0
- plexora/client/src/js/views/coordinateField.js +133 -0
- plexora/client/src/js/views/dataSourceField.js +276 -0
- plexora/client/src/js/views/imageViewer.js +3306 -0
- plexora/client/src/js/views/miniMap.js +787 -0
- plexora/client/src/js/views/navbarControls.js +178 -0
- plexora/client/src/js/views/openProjectPage.js +239 -0
- plexora/client/src/js/views/projectEdit.js +335 -0
- plexora/client/src/js/views/quickViewLanding.js +136 -0
- plexora/client/src/js/views/rainbow.js +326 -0
- plexora/client/src/js/views/requirementsModal.js +572 -0
- plexora/client/src/js/views/rgbImageViewer.js +115 -0
- plexora/client/src/js/views/searchableSelect.js +411 -0
- plexora/client/src/js/views/segmentationProgress.js +122 -0
- plexora/client/src/js/views/toolLoader.js +747 -0
- plexora/client/src/js/views/viewerControls.js +689 -0
- plexora/client/src/js/views/viewerManager.js +434 -0
- plexora/client/src/js/views/viewerSidebar.js +1117 -0
- plexora/client/src/js/workers/tileDecoder.js +130 -0
- plexora/client/src/shaders/frag.glsl +528 -0
- plexora/client/src/shaders/vert.glsl +12 -0
- plexora/client/templates/base.html +212 -0
- plexora/client/templates/index.html +238 -0
- plexora/client/templates/open_project.html +96 -0
- plexora/client/templates/project_columns.html +86 -0
- plexora/client/templates/project_edit.html +196 -0
- plexora/client/templates/upload.html +118 -0
- plexora/connect.py +528 -0
- plexora/datasource.py +782 -0
- plexora/jupyter.py +406 -0
- plexora/notebook_env.py +232 -0
- plexora/paths.py +486 -0
- plexora/plugins/__init__.py +12 -0
- plexora/plugins/cell_explorer/__init__.py +100 -0
- plexora/plugins/cell_explorer/server/__init__.py +11 -0
- plexora/plugins/cell_explorer/server/routes.py +167 -0
- plexora/plugins/cell_explorer/server/state.py +281 -0
- plexora/plugins/cell_explorer/server/values.py +145 -0
- plexora/plugins/cell_explorer/server/variables.py +367 -0
- plexora/plugins/cell_explorer/static/cellExplorerApi.js +158 -0
- plexora/plugins/cell_explorer/static/cellExplorerColors.js +316 -0
- plexora/plugins/cell_explorer/static/cellExplorerContinuous.js +403 -0
- plexora/plugins/cell_explorer/static/cellExplorerFigureBridge.js +133 -0
- plexora/plugins/cell_explorer/static/cellExplorerLegend.js +188 -0
- plexora/plugins/cell_explorer/static/cellExplorerRoiBridge.js +822 -0
- plexora/plugins/cell_explorer/static/cellExplorerSidebarController.js +826 -0
- plexora/plugins/cell_explorer/static/cellExplorerState.js +372 -0
- plexora/plugins/cell_explorer/static/cell_explorer.css +752 -0
- plexora/plugins/cell_explorer/templates/cell_explorer/panel.html +137 -0
- plexora/plugins/figure_builder/__init__.py +125 -0
- plexora/plugins/figure_builder/server/__init__.py +7 -0
- plexora/plugins/figure_builder/server/compose.py +365 -0
- plexora/plugins/figure_builder/server/export.py +784 -0
- plexora/plugins/figure_builder/server/export_jobs.py +148 -0
- plexora/plugins/figure_builder/server/operations.py +556 -0
- plexora/plugins/figure_builder/server/pixels.py +142 -0
- plexora/plugins/figure_builder/server/provenance.py +174 -0
- plexora/plugins/figure_builder/server/render.py +278 -0
- plexora/plugins/figure_builder/server/repository.py +701 -0
- plexora/plugins/figure_builder/server/routes.py +594 -0
- plexora/plugins/figure_builder/server/schema.py +1023 -0
- plexora/plugins/figure_builder/server/sources.py +120 -0
- plexora/plugins/figure_builder/server/textmetrics.py +155 -0
- plexora/plugins/figure_builder/static/figureActions.js +363 -0
- plexora/plugins/figure_builder/static/figureBuilderApi.js +255 -0
- plexora/plugins/figure_builder/static/figureCanvas.js +2362 -0
- plexora/plugins/figure_builder/static/figureCaptureBoxes.js +369 -0
- plexora/plugins/figure_builder/static/figureCaptureDock.js +466 -0
- plexora/plugins/figure_builder/static/figureCaptureTool.js +1034 -0
- plexora/plugins/figure_builder/static/figureConfirm.js +136 -0
- plexora/plugins/figure_builder/static/figureContextBar.js +1193 -0
- plexora/plugins/figure_builder/static/figureContextMenu.js +253 -0
- plexora/plugins/figure_builder/static/figureDocumentState.js +275 -0
- plexora/plugins/figure_builder/static/figureExportUi.js +174 -0
- plexora/plugins/figure_builder/static/figureLibrary.js +247 -0
- plexora/plugins/figure_builder/static/figureQuickEdit.js +628 -0
- plexora/plugins/figure_builder/static/figureRichText.js +634 -0
- plexora/plugins/figure_builder/static/figureSceneSnapshot.js +361 -0
- plexora/plugins/figure_builder/static/figureSchema.js +218 -0
- plexora/plugins/figure_builder/static/figureSidebarController.js +1159 -0
- plexora/plugins/figure_builder/static/figureTextEditor.js +503 -0
- plexora/plugins/figure_builder/static/figureTextPanel.js +491 -0
- plexora/plugins/figure_builder/static/figureViewOptions.js +273 -0
- plexora/plugins/figure_builder/static/figureWorkspace.js +2030 -0
- plexora/plugins/figure_builder/static/figure_builder.css +3292 -0
- plexora/plugins/figure_builder/templates/figure_builder/library.html +74 -0
- plexora/plugins/figure_builder/templates/figure_builder/workspace.html +27 -0
- plexora/plugins/figure_builder/templates/figure_builder/workspace_body.html +458 -0
- plexora/plugins/gating/__init__.py +76 -0
- plexora/plugins/gating/server/__init__.py +0 -0
- plexora/plugins/gating/server/anndata_gates.py +367 -0
- plexora/plugins/gating/server/database.py +16 -0
- plexora/plugins/gating/server/model.py +320 -0
- plexora/plugins/gating/server/routes.py +235 -0
- plexora/plugins/gating/static/csvGatingList.js +947 -0
- plexora/plugins/gating/static/gating.css +289 -0
- plexora/plugins/gating/static/gatingApi.js +221 -0
- plexora/plugins/gating/static/gatingSidebarController.js +513 -0
- plexora/plugins/gating/templates/gating/legacy.html +26 -0
- plexora/plugins/gating/templates/gating/panel.html +69 -0
- plexora/plugins/roi/__init__.py +95 -0
- plexora/plugins/roi/server/__init__.py +8 -0
- plexora/plugins/roi/server/adapters.py +655 -0
- plexora/plugins/roi/server/geojson.py +267 -0
- plexora/plugins/roi/server/geometry.py +181 -0
- plexora/plugins/roi/server/mapping.py +169 -0
- plexora/plugins/roi/server/operations.py +293 -0
- plexora/plugins/roi/server/repository.py +215 -0
- plexora/plugins/roi/server/routes.py +409 -0
- plexora/plugins/roi/server/schema.py +323 -0
- plexora/plugins/roi/static/roi.css +583 -0
- plexora/plugins/roi/static/roiApi.js +177 -0
- plexora/plugins/roi/static/roiFigureBridge.js +107 -0
- plexora/plugins/roi/static/roiGeometry.js +350 -0
- plexora/plugins/roi/static/roiRenderer.js +278 -0
- plexora/plugins/roi/static/roiSidebarController.js +1038 -0
- plexora/plugins/roi/static/roiState.js +518 -0
- plexora/plugins/roi/static/roiTools.js +993 -0
- plexora/plugins/roi/templates/roi/panel.html +240 -0
- plexora/proxy.py +57 -0
- plexora/server/models/adapters/__init__.py +94 -0
- plexora/server/models/adapters/anndata_adapter.py +388 -0
- plexora/server/models/adapters/base.py +93 -0
- plexora/server/models/adapters/classify.py +192 -0
- plexora/server/models/adapters/csv_adapter.py +105 -0
- plexora/server/models/adapters/inspection.py +265 -0
- plexora/server/models/adapters/spatialdata_adapter.py +248 -0
- plexora/server/models/centroid_tiles.py +305 -0
- plexora/server/models/data_model.py +1659 -0
- plexora/server/models/database_model.py +169 -0
- plexora/server/models/project.py +1270 -0
- plexora/server/plugins.py +199 -0
- plexora/server/routes/browse_routes.py +41 -0
- plexora/server/routes/data_routes.py +294 -0
- plexora/server/routes/import_routes.py +603 -0
- plexora/server/routes/page_routes.py +139 -0
- plexora/server/routes/project_routes.py +438 -0
- plexora/server/routes/quick_view_routes.py +62 -0
- plexora/server/routes/system_routes.py +39 -0
- plexora/server/routes/tool_routes.py +418 -0
- plexora/server/utils/addHEColumns.py +11 -0
- plexora/server/utils/fast_png.py +97 -0
- plexora/server/utils/fullConversion.py +50 -0
- plexora/server/utils/native_dialog.py +168 -0
- plexora/server/utils/pre_normalization.py +47 -0
- plexora/server/utils/segmentation_pyramid.py +598 -0
- plexora/server/utils/smallestenclosingcircle.py +126 -0
- plexora/server/utils/tiffsurgeon.py +372 -0
- plexora/server_cli.py +48 -0
- plexora/test.py +0 -0
- plexora-0.0.1.dist-info/METADATA +341 -0
- plexora-0.0.1.dist-info/RECORD +248 -0
- plexora-0.0.1.dist-info/WHEEL +5 -0
- plexora-0.0.1.dist-info/entry_points.txt +12 -0
- plexora-0.0.1.dist-info/licenses/LICENSE +207 -0
- plexora-0.0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FigureCaptureDock - the whole of Figure Builder's in-viewer chrome.
|
|
3
|
+
*
|
|
4
|
+
* There is no sidebar panel any more. Figure Builder started with one because
|
|
5
|
+
* every plugin has one, and it was the wrong home for all of it: capturing is
|
|
6
|
+
* something you do WHILE looking at the image, and a panel three hundred pixels
|
|
7
|
+
* to the left is a glance away from the thing being judged. Splitting the
|
|
8
|
+
* controls between the two -- capture on the image, "which figure?" in the
|
|
9
|
+
* sidebar -- was worse than either, because the two halves of one decision were
|
|
10
|
+
* in two places. So everything is here: the mode toggle, the instructions, the
|
|
11
|
+
* session's captures, which figure they go into, the way through to the canvas,
|
|
12
|
+
* the borrowed-viewer session that reopening a panel starts, and the way out.
|
|
13
|
+
*
|
|
14
|
+
* Mounted as a sibling of #openseadragon inside #openseadragon_wrapper -- the
|
|
15
|
+
* placement miniMap.js documents: OSD binds its mouse tracker to
|
|
16
|
+
* #openseadragon, so clicks on this chrome never reach the viewer and nothing
|
|
17
|
+
* here needs stopPropagation. Top-right because core's own furniture is
|
|
18
|
+
* elsewhere: project label top-left, overview lens bottom-left, channel legend
|
|
19
|
+
* bottom-right. The legend is the one that matters -- it is in the same column
|
|
20
|
+
* as this, so the dock measures the room above it and caps itself there rather
|
|
21
|
+
* than growing down through it as the strip fills up. See roomFor().
|
|
22
|
+
*
|
|
23
|
+
* Everything here is built in JavaScript rather than rendered into a panel slot
|
|
24
|
+
* for one reason: core has no slot over the image, and adding one to
|
|
25
|
+
* index.html for a single plugin would put a plugin-shaped hole in a template
|
|
26
|
+
* every build renders. What a plugin creates it may also style, so the classes
|
|
27
|
+
* are all `fb-` and tests/test_plugin_css_boundary.py stays satisfied.
|
|
28
|
+
*/
|
|
29
|
+
class FigureCaptureDock {
|
|
30
|
+
|
|
31
|
+
/** The one-key shortcut. C for capture; nothing in core or in the other
|
|
32
|
+
* plugins binds a bare letter, which is checked in the probe rather than
|
|
33
|
+
* assumed. */
|
|
34
|
+
static get SHORTCUT() { return "c"; }
|
|
35
|
+
|
|
36
|
+
/** Whether the strip is open. A property of this browser and this person's
|
|
37
|
+
* habits, like the current figure -- not of the figure or the project. */
|
|
38
|
+
static get STORAGE_KEY() { return "plexora.figure_builder.stripOpen"; }
|
|
39
|
+
|
|
40
|
+
/** The dock's inset from the top of the viewer, matching `top` in the
|
|
41
|
+
* stylesheet; and the clearance it keeps from whatever is below it. */
|
|
42
|
+
static get MARGIN() { return 12; }
|
|
43
|
+
static get GAP() { return 10; }
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* The least the dock may be squeezed to, in pixels.
|
|
47
|
+
*
|
|
48
|
+
* On a short window under a tall channel legend there is not enough room
|
|
49
|
+
* for both, and something has to give. It is this: about the orb, the
|
|
50
|
+
* strip's header and a glimpse of the list. A dock squeezed below that is
|
|
51
|
+
* one the user cannot capture with at all, which is worse than a dock that
|
|
52
|
+
* overlaps a legend in the one case where nothing else fits.
|
|
53
|
+
*/
|
|
54
|
+
static get MIN_HEIGHT() { return 168; }
|
|
55
|
+
|
|
56
|
+
/** Core's channel legend: bottom-right of the same wrapper the dock hangs
|
|
57
|
+
* in, and the only thing the dock can collide with. Read for its geometry
|
|
58
|
+
* and never touched -- it is core's element, and this is a plugin. */
|
|
59
|
+
static get OBSTACLE_ID() { return "viewer_channel_legend"; }
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* How tall the dock may be.
|
|
63
|
+
*
|
|
64
|
+
* The strip grows with every capture, and left to itself it grew straight
|
|
65
|
+
* down into the channel legend -- which is `pointer-events: none`, so the
|
|
66
|
+
* captures underneath it stayed clickable and the two just illegibly
|
|
67
|
+
* overlapped. Rather than a fixed cap (wrong on every window that is not
|
|
68
|
+
* the one it was chosen on), the dock takes the room that is actually free
|
|
69
|
+
* above whatever occupies the corner below it.
|
|
70
|
+
*
|
|
71
|
+
* Pure so the arithmetic can be checked without a browser.
|
|
72
|
+
*
|
|
73
|
+
* @param {number} hostHeight the viewer wrapper's height.
|
|
74
|
+
* @param {?number} obstacleTop the top of the thing below, in the same
|
|
75
|
+
* coordinates, or null when there is nothing there.
|
|
76
|
+
*/
|
|
77
|
+
static roomFor(hostHeight, obstacleTop) {
|
|
78
|
+
const margin = FigureCaptureDock.MARGIN;
|
|
79
|
+
const floor = (obstacleTop === null || obstacleTop === undefined)
|
|
80
|
+
? hostHeight - margin
|
|
81
|
+
: obstacleTop - FigureCaptureDock.GAP;
|
|
82
|
+
return Math.max(FigureCaptureDock.MIN_HEIGHT, Math.round(floor - margin));
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Is this keystroke the capture shortcut?
|
|
87
|
+
*
|
|
88
|
+
* A bare letter, so the guards are the whole of it: no modifier chord (Cmd-C
|
|
89
|
+
* is copy and always will be), and nothing while the user is typing --
|
|
90
|
+
* otherwise naming a figure "cell cores" toggles capture mode four times.
|
|
91
|
+
*/
|
|
92
|
+
static isShortcut(event) {
|
|
93
|
+
if (!event || event.metaKey || event.ctrlKey || event.altKey) return false;
|
|
94
|
+
if (typeof event.key !== "string") return false;
|
|
95
|
+
if (event.key.toLowerCase() !== FigureCaptureDock.SHORTCUT) return false;
|
|
96
|
+
return !FigureCaptureTool.isTyping();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* @param {object} handlers onToggleCapture, onSelectCapture(id),
|
|
101
|
+
* onRemoveCapture(id), onNewFigure, onChooseFigure, onOpenCanvas,
|
|
102
|
+
* onUpdatePanel, onCancelEdit, onClose.
|
|
103
|
+
*/
|
|
104
|
+
constructor(handlers) {
|
|
105
|
+
this.handlers = handlers || {};
|
|
106
|
+
this.root = null;
|
|
107
|
+
this.open = this.readOpen();
|
|
108
|
+
this._onKeyDown = (event) => {
|
|
109
|
+
if (!FigureCaptureDock.isShortcut(event)) return;
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
this.handlers.onToggleCapture?.();
|
|
112
|
+
};
|
|
113
|
+
this._onResize = () => this.layout();
|
|
114
|
+
//: Watches the legend, which changes height whenever a channel is
|
|
115
|
+
//: turned on or off -- a plugin cannot hear core's channel events, and
|
|
116
|
+
//: the box changing is the thing that actually matters here.
|
|
117
|
+
this._watch = null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// -- lifecycle -------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
mount() {
|
|
123
|
+
const host = this.host();
|
|
124
|
+
if (this.root || !host) return false;
|
|
125
|
+
|
|
126
|
+
const root = document.createElement("div");
|
|
127
|
+
root.className = "fb-dock";
|
|
128
|
+
root.innerHTML = `
|
|
129
|
+
<div class="fb-dock-head">
|
|
130
|
+
<div class="fb-dock-caption" data-role="caption"></div>
|
|
131
|
+
<button class="fb-orb" type="button" data-role="orb"
|
|
132
|
+
aria-pressed="false" title="Capture mode (C)">
|
|
133
|
+
<span class="fas fa-crop-simple" aria-hidden="true"></span>
|
|
134
|
+
<span class="fb-visually-hidden">Capture mode</span>
|
|
135
|
+
</button>
|
|
136
|
+
<button class="fb-dock-close" type="button" data-role="close"
|
|
137
|
+
title="Close Figure Builder">
|
|
138
|
+
<span class="fas fa-xmark" aria-hidden="true"></span>
|
|
139
|
+
<span class="fb-visually-hidden">Close Figure Builder</span>
|
|
140
|
+
</button>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<p class="fb-dock-error" data-role="error" hidden></p>
|
|
144
|
+
|
|
145
|
+
<section class="fb-strip" data-role="strip">
|
|
146
|
+
<button class="fb-strip-toggle" type="button" data-role="collapse"
|
|
147
|
+
aria-expanded="true">
|
|
148
|
+
<span class="fas fa-chevron-down fb-strip-chevron" aria-hidden="true"></span>
|
|
149
|
+
<span class="fb-strip-title">Captures</span>
|
|
150
|
+
<span class="fb-strip-count" data-role="count">0</span>
|
|
151
|
+
</button>
|
|
152
|
+
<div class="fb-strip-body">
|
|
153
|
+
<div class="fb-strip-items" data-role="items"></div>
|
|
154
|
+
<p class="fb-strip-empty" data-role="empty">Nothing captured yet.</p>
|
|
155
|
+
<div class="fb-strip-foot">
|
|
156
|
+
<span class="fb-strip-label">Capture into</span>
|
|
157
|
+
<div class="fb-strip-figure-row">
|
|
158
|
+
<button class="fb-strip-figure" type="button" data-role="choose"
|
|
159
|
+
title="Open a figure, or start one"></button>
|
|
160
|
+
<button class="fb-strip-add" type="button" data-role="new"
|
|
161
|
+
title="New figure">
|
|
162
|
+
<span class="fas fa-plus" aria-hidden="true"></span>
|
|
163
|
+
<span class="fb-visually-hidden">New figure</span>
|
|
164
|
+
</button>
|
|
165
|
+
</div>
|
|
166
|
+
<span class="fb-strip-meta" data-role="meta"></span>
|
|
167
|
+
<button class="fb-strip-go" type="button" data-role="canvas"></button>
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
</section>
|
|
171
|
+
|
|
172
|
+
<section class="fb-dock-edit" data-role="edit" hidden>
|
|
173
|
+
<div class="fb-dock-edit-head">
|
|
174
|
+
<span class="fas fa-arrow-rotate-left" aria-hidden="true"></span>
|
|
175
|
+
<span>Editing <strong data-role="editLabel"></strong></span>
|
|
176
|
+
</div>
|
|
177
|
+
<p class="fb-dock-edit-body">The viewer is showing this panel's captured
|
|
178
|
+
view, and the outline on the image is where its edges are. Adjust the
|
|
179
|
+
channels, contrast and overlays as usual, drag the outline to reframe,
|
|
180
|
+
then update the panel — or cancel to put the viewer back. Your
|
|
181
|
+
project's own saved channels are not touched either way.</p>
|
|
182
|
+
<div data-role="editNotes"></div>
|
|
183
|
+
<div class="fb-dock-edit-actions">
|
|
184
|
+
<button class="fb-strip-go" type="button" data-role="editUpdate">Update panel</button>
|
|
185
|
+
<button class="fb-strip-figure" type="button" data-role="editCancel">Cancel</button>
|
|
186
|
+
</div>
|
|
187
|
+
</section>`;
|
|
188
|
+
|
|
189
|
+
// Delegated: the items are rebuilt on every capture, so handlers bound
|
|
190
|
+
// to them would be rebound continuously and leak the ones replaced.
|
|
191
|
+
root.addEventListener("click", (event) => this.clicked(event));
|
|
192
|
+
host.appendChild(root);
|
|
193
|
+
this.root = root;
|
|
194
|
+
this.applyOpen();
|
|
195
|
+
this.layout();
|
|
196
|
+
|
|
197
|
+
// On the document, not on the dock: a shortcut that only worked while
|
|
198
|
+
// the pointer was over a 150px strip would be a shortcut nobody found.
|
|
199
|
+
document.addEventListener("keydown", this._onKeyDown);
|
|
200
|
+
window.addEventListener("resize", this._onResize);
|
|
201
|
+
this.watchObstacle();
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
unmount() {
|
|
206
|
+
document.removeEventListener("keydown", this._onKeyDown);
|
|
207
|
+
window.removeEventListener("resize", this._onResize);
|
|
208
|
+
this._watch?.disconnect();
|
|
209
|
+
this._watch = null;
|
|
210
|
+
this.root?.remove();
|
|
211
|
+
this.root = null;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
destroy() {
|
|
215
|
+
this.unmount();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
get mounted() {
|
|
219
|
+
return Boolean(this.root);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// -- how much room there is ------------------------------------------
|
|
223
|
+
|
|
224
|
+
/** The wrapper the dock and the legend both hang in. */
|
|
225
|
+
host() {
|
|
226
|
+
return document.getElementById("openseadragon_wrapper");
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
obstacle() {
|
|
230
|
+
return document.getElementById(FigureCaptureDock.OBSTACLE_ID);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Give the dock the height that is actually free above the legend.
|
|
235
|
+
*
|
|
236
|
+
* A max-height rather than a height: the dock is still only as tall as what
|
|
237
|
+
* is in it, and this is the ceiling the strip's scroll starts at.
|
|
238
|
+
*/
|
|
239
|
+
layout() {
|
|
240
|
+
const host = this.host();
|
|
241
|
+
if (!this.root || !host?.getBoundingClientRect) return;
|
|
242
|
+
const bounds = host.getBoundingClientRect();
|
|
243
|
+
|
|
244
|
+
const legend = this.obstacle();
|
|
245
|
+
const box = legend?.getBoundingClientRect?.();
|
|
246
|
+
// A legend with no height is one core has emptied or hidden, and a hidden
|
|
247
|
+
// obstacle is not one: measuring it anyway would cap the dock against a
|
|
248
|
+
// rectangle nobody can see.
|
|
249
|
+
const top = box && box.height ? box.top - bounds.top : null;
|
|
250
|
+
|
|
251
|
+
this.root.style.maxHeight = FigureCaptureDock.roomFor(bounds.height, top) + "px";
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Re-measure when the legend's box changes.
|
|
256
|
+
*
|
|
257
|
+
* Through a ResizeObserver rather than by listening to core: the legend is
|
|
258
|
+
* redrawn on channel changes and on colour changes, and binding to those
|
|
259
|
+
* events would be a plugin reaching into core's event vocabulary for a fact
|
|
260
|
+
* it can observe directly. Guarded, because the probes' synthetic window
|
|
261
|
+
* does not have one and neither does every browser this may meet.
|
|
262
|
+
*/
|
|
263
|
+
watchObstacle() {
|
|
264
|
+
const legend = this.obstacle();
|
|
265
|
+
if (this._watch || !legend || typeof ResizeObserver !== "function") return;
|
|
266
|
+
this._watch = new ResizeObserver(() => this.layout());
|
|
267
|
+
this._watch.observe(legend);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
el(role) {
|
|
271
|
+
return this.root?.querySelector(`[data-role="${role}"]`) || null;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// -- events ----------------------------------------------------------
|
|
275
|
+
|
|
276
|
+
clicked(event) {
|
|
277
|
+
const target = event.target.closest("[data-role]");
|
|
278
|
+
const role = target?.dataset.role;
|
|
279
|
+
const id = () => event.target.closest("[data-capture-id]")?.dataset.captureId;
|
|
280
|
+
if (role === "orb") {
|
|
281
|
+
this.handlers.onToggleCapture?.();
|
|
282
|
+
} else if (role === "close") {
|
|
283
|
+
this.handlers.onClose?.();
|
|
284
|
+
} else if (role === "collapse") {
|
|
285
|
+
this.setOpen(!this.open);
|
|
286
|
+
} else if (role === "remove") {
|
|
287
|
+
// Nested inside the item, so `closest` finds this one first --
|
|
288
|
+
// which is what stops discarding a capture from also selecting it
|
|
289
|
+
// and flying the viewer to a region that is about to be gone.
|
|
290
|
+
const captureId = id();
|
|
291
|
+
if (captureId) this.handlers.onRemoveCapture?.(captureId);
|
|
292
|
+
} else if (role === "select") {
|
|
293
|
+
const captureId = id();
|
|
294
|
+
if (captureId) this.handlers.onSelectCapture?.(captureId);
|
|
295
|
+
} else if (role === "new") {
|
|
296
|
+
this.handlers.onNewFigure?.();
|
|
297
|
+
} else if (role === "choose") {
|
|
298
|
+
this.handlers.onChooseFigure?.();
|
|
299
|
+
} else if (role === "canvas") {
|
|
300
|
+
this.handlers.onOpenCanvas?.();
|
|
301
|
+
} else if (role === "editUpdate") {
|
|
302
|
+
this.handlers.onUpdatePanel?.();
|
|
303
|
+
} else if (role === "editCancel") {
|
|
304
|
+
this.handlers.onCancelEdit?.();
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
readOpen() {
|
|
309
|
+
try {
|
|
310
|
+
return window.localStorage.getItem(FigureCaptureDock.STORAGE_KEY) !== "0";
|
|
311
|
+
} catch (error) {
|
|
312
|
+
// Private-browsing modes throw rather than answering. Defaulting to
|
|
313
|
+
// open is the recoverable direction: a strip nobody wanted is one
|
|
314
|
+
// click away from gone, a strip nobody can find is not.
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
setOpen(open) {
|
|
320
|
+
this.open = Boolean(open);
|
|
321
|
+
try {
|
|
322
|
+
window.localStorage.setItem(FigureCaptureDock.STORAGE_KEY, this.open ? "1" : "0");
|
|
323
|
+
} catch (error) {
|
|
324
|
+
/* see readOpen */
|
|
325
|
+
}
|
|
326
|
+
this.applyOpen();
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
applyOpen() {
|
|
330
|
+
if (!this.root) return;
|
|
331
|
+
this.root.classList.toggle("is-collapsed", !this.open);
|
|
332
|
+
this.el("collapse")?.setAttribute("aria-expanded", String(this.open));
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// -- rendering -------------------------------------------------------
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* @param {object} state armed, figureTitle (null when no figure
|
|
339
|
+
* is open), meta, error, editing ({label, notes} or null), selected
|
|
340
|
+
* and captures: [{id, url, caption, pending}].
|
|
341
|
+
*/
|
|
342
|
+
render(state) {
|
|
343
|
+
if (!this.root) return;
|
|
344
|
+
const armed = Boolean(state.armed);
|
|
345
|
+
const editing = state.editing || null;
|
|
346
|
+
|
|
347
|
+
this.root.classList.toggle("is-armed", armed);
|
|
348
|
+
// The capture half stands down while the viewer is showing a panel's
|
|
349
|
+
// borrowed scene: a capture taken then would be a panel of somebody
|
|
350
|
+
// else's view, and nothing on screen would say so.
|
|
351
|
+
this.root.classList.toggle("is-editing", Boolean(editing));
|
|
352
|
+
|
|
353
|
+
const orb = this.el("orb");
|
|
354
|
+
if (orb) {
|
|
355
|
+
orb.setAttribute("aria-pressed", String(armed));
|
|
356
|
+
orb.setAttribute("title", armed ? "Leave capture mode (C)" : "Capture mode (C)");
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// The instructions the sidebar used to carry, kept because they are the
|
|
360
|
+
// only place the modifiers are written down -- but only while the mode
|
|
361
|
+
// they describe is on, and never as a block that covers the image.
|
|
362
|
+
const caption = this.el("caption");
|
|
363
|
+
if (caption) {
|
|
364
|
+
// Both letters are read off the classes that own them rather than
|
|
365
|
+
// written out here, so a key that moves cannot leave the only
|
|
366
|
+
// written record of it pointing at the old one.
|
|
367
|
+
const shoot = FigureCaptureTool.SHOOT_KEY.toUpperCase();
|
|
368
|
+
const mode = FigureCaptureDock.SHORTCUT.toUpperCase();
|
|
369
|
+
caption.innerHTML = armed
|
|
370
|
+
? 'Drag the frame to move it, drag the image to redraw it. '
|
|
371
|
+
+ '<kbd>Shift</kbd> square · <kbd>Space</kbd> pan · '
|
|
372
|
+
+ `<kbd>${shoot}</kbd> capture · <kbd>Esc</kbd> exit`
|
|
373
|
+
: `Capture <kbd>${mode}</kbd>`;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
const error = this.el("error");
|
|
377
|
+
if (error) {
|
|
378
|
+
error.textContent = state.error || "";
|
|
379
|
+
error.hidden = !state.error;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
const strip = this.el("strip");
|
|
383
|
+
if (strip) strip.hidden = Boolean(editing);
|
|
384
|
+
const edit = this.el("edit");
|
|
385
|
+
if (edit) edit.hidden = !editing;
|
|
386
|
+
if (editing) this.renderEdit(editing);
|
|
387
|
+
|
|
388
|
+
const captures = state.captures || [];
|
|
389
|
+
const count = this.el("count");
|
|
390
|
+
if (count) count.textContent = String(captures.length);
|
|
391
|
+
const empty = this.el("empty");
|
|
392
|
+
if (empty) empty.hidden = captures.length > 0;
|
|
393
|
+
|
|
394
|
+
const items = this.el("items");
|
|
395
|
+
if (items) {
|
|
396
|
+
items.innerHTML = captures.map((capture, index) => {
|
|
397
|
+
const escape = FigureSchema.escapeHtml;
|
|
398
|
+
const classes = ["fb-strip-item"];
|
|
399
|
+
if (capture.pending) classes.push("is-pending");
|
|
400
|
+
if (capture.id === state.selected) classes.push("is-selected");
|
|
401
|
+
const title = escape(capture.caption || "")
|
|
402
|
+
+ (capture.pending ? " — not in a figure yet" : "");
|
|
403
|
+
const image = capture.url
|
|
404
|
+
? `<img src="${escape(capture.url)}" alt="" draggable="false">`
|
|
405
|
+
: '<span class="fb-strip-pending"></span>';
|
|
406
|
+
// The whole item selects; the button inside it discards. The
|
|
407
|
+
// number matches the label on the box out on the image.
|
|
408
|
+
return `<figure class="${classes.join(" ")}" data-role="select"
|
|
409
|
+
data-capture-id="${escape(capture.id)}"
|
|
410
|
+
title="${title} — click to go back and aim the shutter at it again">
|
|
411
|
+
${image}
|
|
412
|
+
<span class="fb-strip-index">${index + 1}</span>
|
|
413
|
+
<button class="fb-strip-remove" type="button" data-role="remove"
|
|
414
|
+
title="Discard this capture">
|
|
415
|
+
<span class="fas fa-xmark" aria-hidden="true"></span>
|
|
416
|
+
<span class="fb-visually-hidden">Discard</span>
|
|
417
|
+
</button>
|
|
418
|
+
</figure>`;
|
|
419
|
+
}).join("");
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Never a placeholder that reads like a name: "Untitled figure" is what
|
|
423
|
+
// an unnamed figure is called, and showing it when there is no figure at
|
|
424
|
+
// all would tell the user their captures are somewhere they are not.
|
|
425
|
+
const choose = this.el("choose");
|
|
426
|
+
if (choose) {
|
|
427
|
+
choose.textContent = state.figureTitle || "Not chosen yet";
|
|
428
|
+
choose.classList.toggle("is-empty", !state.figureTitle);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
const meta = this.el("meta");
|
|
432
|
+
if (meta) {
|
|
433
|
+
meta.textContent = state.meta || "";
|
|
434
|
+
meta.hidden = !state.meta;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// One label, because there is now one thing it does: the canvas is a
|
|
438
|
+
// page of its own and this leaves the viewer for it. It used to toggle a
|
|
439
|
+
// pane beside the image and say "Hide canvas" on the way back, which
|
|
440
|
+
// made the same button mean two things depending on a state nothing
|
|
441
|
+
// else on screen showed. The arrow says the page is going to change.
|
|
442
|
+
const go = this.el("canvas");
|
|
443
|
+
if (go) {
|
|
444
|
+
go.innerHTML = 'Figure Canvas '
|
|
445
|
+
+ '<span class="fas fa-arrow-up-right-from-square" aria-hidden="true"></span>';
|
|
446
|
+
go.title = "Open the Figure Canvas — leaves the viewer";
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// The strip has just changed height, and the legend may have changed
|
|
450
|
+
// too since the last look -- on a browser without a ResizeObserver this
|
|
451
|
+
// is the only thing that would notice.
|
|
452
|
+
this.watchObstacle();
|
|
453
|
+
this.layout();
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
renderEdit(editing) {
|
|
457
|
+
const label = this.el("editLabel");
|
|
458
|
+
if (label) label.textContent = editing.label || "this panel";
|
|
459
|
+
const notes = this.el("editNotes");
|
|
460
|
+
if (notes) {
|
|
461
|
+
notes.innerHTML = (editing.notes || [])
|
|
462
|
+
.map((note) => `<p class="fb-dock-edit-note">${FigureSchema.escapeHtml(note)}</p>`)
|
|
463
|
+
.join("");
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
}
|