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,369 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FigureCaptureBoxes - where the captures came from, still on the image.
|
|
3
|
+
*
|
|
4
|
+
* A capture used to leave no mark. The shutter fired, a thumbnail appeared in
|
|
5
|
+
* the strip, and the region it came from was gone -- so building a row of
|
|
6
|
+
* comparable panels meant remembering four places on a slide by eye. These are
|
|
7
|
+
* those four places, drawn as thin outlines that stay on the image for the whole
|
|
8
|
+
* session.
|
|
9
|
+
*
|
|
10
|
+
* ## Held in IMAGE pixels, which is the opposite of the viewfinder
|
|
11
|
+
*
|
|
12
|
+
* figureCaptureTool's frame is held in SCREEN pixels on purpose: it is a
|
|
13
|
+
* viewfinder, and panning the image under it is how you aim it. A capture box is
|
|
14
|
+
* the other thing entirely -- it is a record of a region OF THE IMAGE, so it has
|
|
15
|
+
* to travel with the image, growing as you zoom in and sliding as you pan. The
|
|
16
|
+
* two live in the same corner of the same screen and mean opposite things, and
|
|
17
|
+
* getting either one backwards produces something that looks fine until the
|
|
18
|
+
* moment it matters.
|
|
19
|
+
*
|
|
20
|
+
* Both are reprojected through the SAME tool instance rather than through a
|
|
21
|
+
* second copy of the arithmetic, so a frame re-aimed at a box (which is what
|
|
22
|
+
* selecting a capture does) lands exactly on it. Two copies would agree until
|
|
23
|
+
* the day one of them was fixed.
|
|
24
|
+
*
|
|
25
|
+
* ## The interior is not a target
|
|
26
|
+
*
|
|
27
|
+
* A box can cover most of the viewer. If the whole rectangle were clickable, a
|
|
28
|
+
* session's captures would gradually cover the image in dead zones where panning
|
|
29
|
+
* and every other tool's drag stopped working. So the element itself is
|
|
30
|
+
* `pointer-events: none` and only four thin bands along its edges -- plus the
|
|
31
|
+
* label, once it is showing -- take clicks. Everything inside a capture box
|
|
32
|
+
* behaves exactly like the image, because it is the image.
|
|
33
|
+
*
|
|
34
|
+
* ## Selecting one does not restore what it looked like
|
|
35
|
+
*
|
|
36
|
+
* Centring on a box puts the viewer back over that FIELD and touches nothing
|
|
37
|
+
* about the rendering: not a channel, not a window, not an overlay. That is
|
|
38
|
+
* deliberate and it is the feature -- go back to a region, change the channels,
|
|
39
|
+
* and capture it again to get a second panel of the same field under a different
|
|
40
|
+
* rendering, in pixel-level concordance with the first. Restoring the captured
|
|
41
|
+
* scene here would make that impossible to do by accident and hard to do on
|
|
42
|
+
* purpose. Reopening a panel's full scene is a different action, lives on the
|
|
43
|
+
* canvas, and says so (see the sidebar controller's editPanel).
|
|
44
|
+
*/
|
|
45
|
+
class FigureCaptureBoxes {
|
|
46
|
+
|
|
47
|
+
/** How thick the clickable band along each edge is, in CSS pixels. Wider
|
|
48
|
+
* than the 1px outline it straddles, because a 1px hit target is a target
|
|
49
|
+
* nobody hits. */
|
|
50
|
+
static get EDGE() { return 9; }
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* How much viewer a region gets when you go back to it, as a multiple of
|
|
54
|
+
* the region itself. Two, so the capture fills about half the window.
|
|
55
|
+
*
|
|
56
|
+
* Filling the window edge to edge answers "what was in this capture" and
|
|
57
|
+
* nothing else: where it sits, what surrounds it, and whether the next one
|
|
58
|
+
* should be a little to the left are all off screen, and arriving that
|
|
59
|
+
* close is a lurch out of whatever the user was looking at. Half leaves a
|
|
60
|
+
* margin of context on every side, and it leaves the capture's own outline
|
|
61
|
+
* visible -- which is what tells the user they have arrived somewhere they
|
|
62
|
+
* have already been.
|
|
63
|
+
*/
|
|
64
|
+
static get CONTEXT() { return 2; }
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* How long the viewer has to be QUIET before it counts as having arrived,
|
|
68
|
+
* and the longest the arrival will wait for a viewer that never is.
|
|
69
|
+
*
|
|
70
|
+
* `animation-finish` is not the end of the movement. OpenSeadragon raises
|
|
71
|
+
* it when the springs reach their target and then pulls the viewport back
|
|
72
|
+
* inside its constraints, which is a second, smaller movement afterwards.
|
|
73
|
+
* A capture near an edge of the slide provokes it, and so does any capture
|
|
74
|
+
* whose context rectangle does not share the viewer's aspect ratio -- which
|
|
75
|
+
* is most of them. See centerOn().
|
|
76
|
+
*/
|
|
77
|
+
static get SETTLE_MS() { return 120; }
|
|
78
|
+
static get SETTLE_LIMIT_MS() { return 900; }
|
|
79
|
+
|
|
80
|
+
/** The viewer events that mean the picture is still moving. `resize` counts:
|
|
81
|
+
* the canvas opening beside the viewer moves the region on screen just as
|
|
82
|
+
* surely as a pan does. */
|
|
83
|
+
static get MOTION_EVENTS() { return ["animation", "animation-finish", "resize"]; }
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The region, grown about its own centre.
|
|
87
|
+
*
|
|
88
|
+
* Pure and separate so the framing can be checked without a viewer. About
|
|
89
|
+
* the CENTRE rather than the top-left, or going back to a capture would put
|
|
90
|
+
* it in the corner of the window instead of the middle of it.
|
|
91
|
+
*/
|
|
92
|
+
static contextRect(rect, grow) {
|
|
93
|
+
const factor = grow || FigureCaptureBoxes.CONTEXT;
|
|
94
|
+
const w = rect.w * factor;
|
|
95
|
+
const h = rect.h * factor;
|
|
96
|
+
return {
|
|
97
|
+
x: rect.x + (rect.w - w) / 2,
|
|
98
|
+
y: rect.y + (rect.h - h) / 2,
|
|
99
|
+
w: w,
|
|
100
|
+
h: h,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Where a captured region lands on screen, or null when it lands nowhere.
|
|
106
|
+
*
|
|
107
|
+
* Pure, and separate from the drawing, because "is this box on screen at
|
|
108
|
+
* all" is the question asked most often -- once per box per animation frame
|
|
109
|
+
* -- and the answer for a user zoomed into one corner of a slide is usually
|
|
110
|
+
* no. Boxes that are nowhere are not drawn at all rather than drawn far
|
|
111
|
+
* outside the viewer: #openseadragon_wrapper does not clip, so an undrawn
|
|
112
|
+
* box is the only kind that cannot end up over the sidebar.
|
|
113
|
+
*/
|
|
114
|
+
static placement(screenRect, bounds) {
|
|
115
|
+
if (!screenRect || !bounds || !(bounds.width > 0) || !(bounds.height > 0)) return null;
|
|
116
|
+
const width = Math.max(1, screenRect.width);
|
|
117
|
+
const height = Math.max(1, screenRect.height);
|
|
118
|
+
const left = screenRect.x;
|
|
119
|
+
const top = screenRect.y;
|
|
120
|
+
if (left + width <= 0 || top + height <= 0) return null;
|
|
121
|
+
if (left >= bounds.width || top >= bounds.height) return null;
|
|
122
|
+
return {
|
|
123
|
+
left: Math.round(left),
|
|
124
|
+
top: Math.round(top),
|
|
125
|
+
width: Math.round(width),
|
|
126
|
+
height: Math.round(height),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* @param {object} ctx the plugin context.
|
|
132
|
+
* @param {object} options tool (the FigureCaptureTool whose coordinate
|
|
133
|
+
* frame these share) and onSelect(captureId).
|
|
134
|
+
*/
|
|
135
|
+
constructor(ctx, options) {
|
|
136
|
+
this.ctx = ctx;
|
|
137
|
+
this.viewer = ctx.viewer?.viewer || null;
|
|
138
|
+
this.tool = (options && options.tool) || null;
|
|
139
|
+
this.onSelect = (options && options.onSelect) || (() => {});
|
|
140
|
+
|
|
141
|
+
//: [{id, rect}] with rect in FULL-RESOLUTION image pixels.
|
|
142
|
+
this.boxes = [];
|
|
143
|
+
this.selected = null;
|
|
144
|
+
this.root = null;
|
|
145
|
+
this._nodes = new Map();
|
|
146
|
+
|
|
147
|
+
this._onViewport = () => this.paint();
|
|
148
|
+
this._handlers = [];
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
get mounted() {
|
|
152
|
+
return Boolean(this.root);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// -- lifecycle -------------------------------------------------------
|
|
156
|
+
|
|
157
|
+
/** A sibling of #openseadragon inside #openseadragon_wrapper -- miniMap's
|
|
158
|
+
* placement, and the same reason: OSD binds its mouse tracker to
|
|
159
|
+
* #openseadragon, so a click on an edge band here never reaches the viewer
|
|
160
|
+
* underneath and nothing needs stopPropagation. */
|
|
161
|
+
mount() {
|
|
162
|
+
const host = document.getElementById("openseadragon_wrapper");
|
|
163
|
+
if (this.root || !host || !this.viewer) return false;
|
|
164
|
+
|
|
165
|
+
const root = document.createElement("div");
|
|
166
|
+
root.className = "fb-boxes";
|
|
167
|
+
// Delegated, because the nodes are rebuilt whenever the capture list
|
|
168
|
+
// changes and per-node handlers would be rebound on every shutter.
|
|
169
|
+
// Events from the edge bands still bubble here: pointer-events on an
|
|
170
|
+
// ancestor decides whether IT is hit, not whether its children's
|
|
171
|
+
// events pass through it.
|
|
172
|
+
root.addEventListener("click", (event) => {
|
|
173
|
+
const id = event.target.closest?.("[data-capture-id]")?.dataset.captureId;
|
|
174
|
+
if (id) this.onSelect(id);
|
|
175
|
+
});
|
|
176
|
+
root.addEventListener("pointerover", (event) => this.hover(event, true));
|
|
177
|
+
root.addEventListener("pointerout", (event) => this.hover(event, false));
|
|
178
|
+
host.appendChild(root);
|
|
179
|
+
this.root = root;
|
|
180
|
+
|
|
181
|
+
const on = (name, fn) => {
|
|
182
|
+
this.viewer.addHandler(name, fn);
|
|
183
|
+
this._handlers.push([name, fn]);
|
|
184
|
+
};
|
|
185
|
+
// The trio miniMap.js uses: every frame of a pan or zoom, the frame it
|
|
186
|
+
// settles on, and a viewer that changed size -- which is what opening
|
|
187
|
+
// the figure canvas beside it does.
|
|
188
|
+
on("animation", this._onViewport);
|
|
189
|
+
on("animation-finish", this._onViewport);
|
|
190
|
+
on("resize", this._onViewport);
|
|
191
|
+
on("open", this._onViewport);
|
|
192
|
+
window.addEventListener("resize", this._onViewport);
|
|
193
|
+
|
|
194
|
+
this.paint();
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
unmount() {
|
|
199
|
+
for (const [name, fn] of this._handlers) this.viewer?.removeHandler?.(name, fn);
|
|
200
|
+
this._handlers = [];
|
|
201
|
+
window.removeEventListener("resize", this._onViewport);
|
|
202
|
+
this.root?.remove();
|
|
203
|
+
this.root = null;
|
|
204
|
+
this._nodes = new Map();
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
destroy() {
|
|
208
|
+
this.unmount();
|
|
209
|
+
this.boxes = [];
|
|
210
|
+
this.selected = null;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
hover(event, on) {
|
|
214
|
+
const box = event.target.closest?.(".fb-box");
|
|
215
|
+
if (!box) return;
|
|
216
|
+
// pointerout fires when the pointer moves between two edge bands of the
|
|
217
|
+
// SAME box, which happens at every corner -- and toggling the highlight
|
|
218
|
+
// off and straight back on is a flicker on a 120ms transition. Where
|
|
219
|
+
// the pointer went is relatedTarget.
|
|
220
|
+
if (!on && box.contains?.(event.relatedTarget)) return;
|
|
221
|
+
box.classList.toggle("is-hover", on);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// -- what there is to draw -------------------------------------------
|
|
225
|
+
|
|
226
|
+
/** @param {Array} boxes [{id, rect}], rect in full-resolution image px. */
|
|
227
|
+
setBoxes(boxes) {
|
|
228
|
+
this.boxes = (boxes || []).filter((box) => box && box.id && box.rect);
|
|
229
|
+
this.paint();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
setSelected(id) {
|
|
233
|
+
this.selected = id || null;
|
|
234
|
+
this.paint();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// -- drawing ---------------------------------------------------------
|
|
238
|
+
|
|
239
|
+
node(id) {
|
|
240
|
+
let node = this._nodes.get(id);
|
|
241
|
+
if (node && node.isConnected !== false) return node;
|
|
242
|
+
node = document.createElement("div");
|
|
243
|
+
node.className = "fb-box";
|
|
244
|
+
node.dataset.captureId = id;
|
|
245
|
+
node.innerHTML = ["n", "e", "s", "w"].map((side) =>
|
|
246
|
+
`<span class="fb-box-edge fb-box-${side}"></span>`).join("")
|
|
247
|
+
+ '<span class="fb-box-tag"></span>';
|
|
248
|
+
this.root.appendChild(node);
|
|
249
|
+
this._nodes.set(id, node);
|
|
250
|
+
return node;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Put every box where the image is now.
|
|
255
|
+
*
|
|
256
|
+
* Called once per animation frame during a pan, so it does the cheap thing:
|
|
257
|
+
* the nodes are reused and only their geometry is written. A session has
|
|
258
|
+
* tens of captures, not thousands.
|
|
259
|
+
*/
|
|
260
|
+
paint() {
|
|
261
|
+
if (!this.root || !this.tool) return;
|
|
262
|
+
const bounds = this.tool.bounds();
|
|
263
|
+
const offset = this.tool.offset();
|
|
264
|
+
|
|
265
|
+
// The container is the viewer's own drawing area, and it clips: a box
|
|
266
|
+
// whose left edge is on screen and whose right edge is a mile off it
|
|
267
|
+
// would otherwise be a div a mile wide, laid over whatever is beside
|
|
268
|
+
// the viewer.
|
|
269
|
+
this.root.style.left = offset.x + "px";
|
|
270
|
+
this.root.style.top = offset.y + "px";
|
|
271
|
+
this.root.style.width = bounds.width + "px";
|
|
272
|
+
this.root.style.height = bounds.height + "px";
|
|
273
|
+
|
|
274
|
+
const live = new Set();
|
|
275
|
+
this.boxes.forEach((box, index) => {
|
|
276
|
+
const placement = FigureCaptureBoxes.placement(
|
|
277
|
+
this.tool.toScreenRect(box.rect), bounds);
|
|
278
|
+
if (!placement) return;
|
|
279
|
+
live.add(box.id);
|
|
280
|
+
const node = this.node(box.id);
|
|
281
|
+
node.style.left = placement.left + "px";
|
|
282
|
+
node.style.top = placement.top + "px";
|
|
283
|
+
node.style.width = placement.width + "px";
|
|
284
|
+
node.style.height = placement.height + "px";
|
|
285
|
+
node.classList.toggle("is-selected", box.id === this.selected);
|
|
286
|
+
const tag = node.querySelector(".fb-box-tag");
|
|
287
|
+
// Newest first in the strip, so the numbers read the same way in
|
|
288
|
+
// both places -- a box labelled 1 and a thumbnail labelled 1 that
|
|
289
|
+
// are different captures is worse than no numbers at all.
|
|
290
|
+
if (tag) tag.textContent = String(index + 1);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
// Anything not placed this pass is off screen or gone from the strip.
|
|
294
|
+
for (const [id, node] of Array.from(this._nodes.entries())) {
|
|
295
|
+
if (live.has(id)) continue;
|
|
296
|
+
node.remove();
|
|
297
|
+
this._nodes.delete(id);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// -- going back to one -----------------------------------------------
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Put the viewer back over a captured region.
|
|
305
|
+
*
|
|
306
|
+
* Over it with room around it -- CONTEXT times its own size -- rather than
|
|
307
|
+
* filling the window with it. The rendering is not touched; see the class
|
|
308
|
+
* comment.
|
|
309
|
+
*
|
|
310
|
+
* `done` is called once the viewer has STOPPED, which is not the same as
|
|
311
|
+
* once it says it has finished. The caller uses it to land the viewfinder
|
|
312
|
+
* on this exact region and to lock the shutter onto it, and that lock is
|
|
313
|
+
* what the selection is: taking it a few pixels off the region means the
|
|
314
|
+
* next movement -- OpenSeadragon's own constraint correction, arriving
|
|
315
|
+
* after `animation-finish` -- breaks it immediately, and the capture the
|
|
316
|
+
* user just clicked goes unselected before they can see it was selected.
|
|
317
|
+
*
|
|
318
|
+
* So this waits for quiet rather than for an event: every `animation`
|
|
319
|
+
* restarts a short timer, and the arrival is the timer surviving. The first
|
|
320
|
+
* wait is started here rather than by an event, because a fitBounds onto
|
|
321
|
+
* where the viewer already is raises no animation at all; and a viewer that
|
|
322
|
+
* never goes quiet gives up at SETTLE_LIMIT_MS rather than stranding the
|
|
323
|
+
* selection forever.
|
|
324
|
+
*/
|
|
325
|
+
centerOn(rect, done) {
|
|
326
|
+
const item = this.viewer?.world?.getItemAt?.(0);
|
|
327
|
+
const viewport = this.viewer?.viewport;
|
|
328
|
+
if (!item || !viewport || !rect) {
|
|
329
|
+
done?.();
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
const scale = 2 ** (this.ctx.config?.extraZoomLevels || 0);
|
|
333
|
+
const framed = FigureCaptureBoxes.contextRect(rect);
|
|
334
|
+
viewport.fitBounds(item.imageToViewportRectangle(new OpenSeadragon.Rect(
|
|
335
|
+
framed.x * scale, framed.y * scale, framed.w * scale, framed.h * scale)));
|
|
336
|
+
|
|
337
|
+
if (done) this.whenStill(done);
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/** Call `done` once the viewer has been still for SETTLE_MS. */
|
|
342
|
+
whenStill(done) {
|
|
343
|
+
let settled = false;
|
|
344
|
+
let quiet = null;
|
|
345
|
+
let limit = null;
|
|
346
|
+
|
|
347
|
+
const settle = () => {
|
|
348
|
+
if (settled) return;
|
|
349
|
+
settled = true;
|
|
350
|
+
clearTimeout(quiet);
|
|
351
|
+
clearTimeout(limit);
|
|
352
|
+
for (const name of FigureCaptureBoxes.MOTION_EVENTS) {
|
|
353
|
+
this.viewer.removeHandler?.(name, restart);
|
|
354
|
+
}
|
|
355
|
+
done();
|
|
356
|
+
};
|
|
357
|
+
const restart = () => {
|
|
358
|
+
if (settled) return;
|
|
359
|
+
clearTimeout(quiet);
|
|
360
|
+
quiet = setTimeout(settle, FigureCaptureBoxes.SETTLE_MS);
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
for (const name of FigureCaptureBoxes.MOTION_EVENTS) {
|
|
364
|
+
this.viewer.addHandler?.(name, restart);
|
|
365
|
+
}
|
|
366
|
+
restart();
|
|
367
|
+
limit = setTimeout(settle, FigureCaptureBoxes.SETTLE_LIMIT_MS);
|
|
368
|
+
}
|
|
369
|
+
}
|