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,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pluginRegistry.js - where client-side plugins announce themselves.
|
|
3
|
+
*
|
|
4
|
+
* A plugin's script self-registers at load time; main.js then activates
|
|
5
|
+
* whatever is registered. Core never names a concrete plugin class.
|
|
6
|
+
*
|
|
7
|
+
* This replaces window.AppModules, which held an array core only ever read as
|
|
8
|
+
* `registry[0]` -- a single slot dressed up as a list, matching the old
|
|
9
|
+
* one-module-per-process server. Several plugins can now be active at once, so
|
|
10
|
+
* registration is keyed by name and activation iterates.
|
|
11
|
+
*
|
|
12
|
+
* Plugin definition (only `name` is required):
|
|
13
|
+
*
|
|
14
|
+
* Plexora.registerPlugin({
|
|
15
|
+
* name: "gating",
|
|
16
|
+
*
|
|
17
|
+
* // Build the plugin's main object. ctx: { config, columns, dataLayer,
|
|
18
|
+
* // eventHandler, dataset, store, viewer, url, onCleanup }
|
|
19
|
+
* createInstance(ctx): object,
|
|
20
|
+
*
|
|
21
|
+
* // Optional sidebar panel controller. ctx adds { sidebar, instance }.
|
|
22
|
+
* // May implement setup/fetchSaved/applyOrDefault/persistIfNeeded/onShow.
|
|
23
|
+
* //
|
|
24
|
+
* // onHide() is the counterpart of onShow(): toolLoader.js calls it when
|
|
25
|
+
* // this tool's panel is closed or another tool is opened over it. A
|
|
26
|
+
* // controller that only owns widgets inside its own panel can ignore it --
|
|
27
|
+
* // the panel is merely hidden and its state is kept, which is what makes
|
|
28
|
+
* // reopening instant. One that reaches OUTSIDE its panel must not: viewer
|
|
29
|
+
* // canvas handlers and document-level keyboard shortcuts go on listening
|
|
30
|
+
* // to a panel the user cannot see, so two tools loaded at once both act on
|
|
31
|
+
* // the same keypress. Stand those down in onHide() and re-arm in onShow().
|
|
32
|
+
* //
|
|
33
|
+
* // onVisibilityChange(on) is a different question from onShow/onHide, and
|
|
34
|
+
* // the difference is the point of the card model: SHOWN is "this is the
|
|
35
|
+
* // tool being worked on", VISIBLE is "this tool's drawing is on screen".
|
|
36
|
+
* // A plugin that colours cells needs nothing here -- core switches its
|
|
37
|
+
* // layer for it. A plugin that draws its own overlay (ROI) does, or the
|
|
38
|
+
* // eye on its card is a button with nothing behind it.
|
|
39
|
+
* createSidebarController(ctx): object | null,
|
|
40
|
+
*
|
|
41
|
+
* // Wire event-bus handlers. ctx adds { viewer, channelList, instance }
|
|
42
|
+
* // plus the core actions updateSeaDragonSelection /
|
|
43
|
+
* // updateCentroidsForGate / runSegmentationGate.
|
|
44
|
+
* bindEvents(ctx): void,
|
|
45
|
+
*
|
|
46
|
+
* // Declares that this plugin draws cells in the viewer. It gets a LAYER of
|
|
47
|
+
* // its own -- its own colours, its own gate, its own mode and opacity --
|
|
48
|
+
* // and a card in the sidebar. Several may be live at once and the card
|
|
49
|
+
* // order is the order they composite in, so a phenotype map and a gate can
|
|
50
|
+
* // be looked at together. See ImageViewer.registerCellLayer.
|
|
51
|
+
* ownsCellLayer: boolean,
|
|
52
|
+
*
|
|
53
|
+
* // How this plugin would like the mask drawn when its layer is first
|
|
54
|
+
* // turned on: "filled" | "outlines" | "centroids". A tool that colours
|
|
55
|
+
* // every cell by a phenotype wants filled; one that marks a few cells
|
|
56
|
+
* // wants outlines over visible tissue. Ignored when the project's recorded
|
|
57
|
+
* // layer or the mask itself cannot do it, and it never overrules a choice
|
|
58
|
+
* // the user has already made. Defaults to "outlines". See enableCellLayer.
|
|
59
|
+
* preferredCellMode: string,
|
|
60
|
+
*
|
|
61
|
+
* // Which representations this plugin can actually work with, as a subset
|
|
62
|
+
* // of ["centroids", "outlines", "filled"]. The shared Cells control offers
|
|
63
|
+
* // the intersection of this and what the project can draw while this
|
|
64
|
+
* // plugin's layer is the active one, so a tool that has no use for one of
|
|
65
|
+
* // them is not offering a button whose result it did not design for.
|
|
66
|
+
* // Omit for "whatever the project can do", which is the common case.
|
|
67
|
+
* supportedCellModes: string[],
|
|
68
|
+
*
|
|
69
|
+
* // Release anything global. Called before the plugin is torn down.
|
|
70
|
+
* // Prefer ctx.onCleanup(fn), which is invoked for you.
|
|
71
|
+
* destroy(): void,
|
|
72
|
+
* });
|
|
73
|
+
*/
|
|
74
|
+
window.Plexora = window.Plexora || {};
|
|
75
|
+
|
|
76
|
+
window.Plexora.plugins = (function () {
|
|
77
|
+
const byName = new Map();
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
/** Register a plugin definition. Re-registering a name replaces it,
|
|
81
|
+
* which is what makes a script that gets loaded twice harmless. */
|
|
82
|
+
register(definition) {
|
|
83
|
+
if (!definition || !definition.name) {
|
|
84
|
+
console.error("Plexora.registerPlugin: definition needs a name", definition);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
byName.set(definition.name, definition);
|
|
88
|
+
},
|
|
89
|
+
|
|
90
|
+
get(name) {
|
|
91
|
+
return byName.get(name) || null;
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
/** Every registered definition, in registration order. */
|
|
95
|
+
all() {
|
|
96
|
+
return Array.from(byName.values());
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
get size() {
|
|
100
|
+
return byName.size;
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
})();
|
|
104
|
+
|
|
105
|
+
/** Convenience alias, so a plugin script reads as one call. */
|
|
106
|
+
window.Plexora.registerPlugin = function (definition) {
|
|
107
|
+
window.Plexora.plugins.register(definition);
|
|
108
|
+
};
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PlexoraStatus -- the application's single status indicator.
|
|
3
|
+
*
|
|
4
|
+
* Lives at the far right of the navbar on every page that extends base.html.
|
|
5
|
+
* Three states, using the shared accent tokens from tokens.css:
|
|
6
|
+
*
|
|
7
|
+
* green "Live" nothing outstanding, the view is current
|
|
8
|
+
* orange <1-2 word label> something is running that has no other visible sign
|
|
9
|
+
* red <short reason> the server is unreachable, or a request failed
|
|
10
|
+
*
|
|
11
|
+
* Why this exists as one system rather than per feature: the app used to
|
|
12
|
+
* indicate work with `d3.select("body").style("cursor", "progress")` at two
|
|
13
|
+
* call sites in main.js -- both of which set and cleared the cursor
|
|
14
|
+
* *synchronously around an async call*, so they were on screen for microseconds
|
|
15
|
+
* and told the user nothing -- plus an opacity fade on individual auto buttons.
|
|
16
|
+
* Anything slow that wasn't one of those (auto-contrast, tiles streaming in
|
|
17
|
+
* after a channel toggle) was completely silent, which is what made a slow
|
|
18
|
+
* operation feel like a broken one.
|
|
19
|
+
*
|
|
20
|
+
* Usage:
|
|
21
|
+
* const task = PlexoraStatus.begin("Auto-contrast");
|
|
22
|
+
* ... ; task.done(); // or task.fail("Fit failed")
|
|
23
|
+
* await PlexoraStatus.track("Saving", promise);
|
|
24
|
+
*
|
|
25
|
+
* Tasks are refcounted, so overlapping work from unrelated features composes
|
|
26
|
+
* without either one clearing the other's indication. The label shown is the
|
|
27
|
+
* most recently begun task.
|
|
28
|
+
*
|
|
29
|
+
* Loaded early and NOT deferred, because it wraps window.fetch and must be in
|
|
30
|
+
* place before any other script issues a request.
|
|
31
|
+
*/
|
|
32
|
+
(function () {
|
|
33
|
+
"use strict";
|
|
34
|
+
|
|
35
|
+
const LIVE_LABEL = "Live";
|
|
36
|
+
// Don't flash orange for work that finishes almost immediately -- a warm
|
|
37
|
+
// channel toggle is ~120 ms and a blink there reads as a glitch.
|
|
38
|
+
const SHOW_DELAY_MS = 150;
|
|
39
|
+
// ...and once shown, hold it long enough to actually be read.
|
|
40
|
+
const MIN_BUSY_MS = 400;
|
|
41
|
+
// One dropped poll is a hiccup; two in a row is a dead server. At 5 s that
|
|
42
|
+
// bounds the "server died while the page sat idle" detection at ~10 s --
|
|
43
|
+
// measured 16 s at a 10 s interval, which is a long time to keep claiming
|
|
44
|
+
// everything is fine. The probe does no work server-side, so the extra
|
|
45
|
+
// traffic is 12 empty 204s a minute.
|
|
46
|
+
const HEALTH_INTERVAL_MS = 5000;
|
|
47
|
+
const HEALTH_FAILURES_BEFORE_ERROR = 2;
|
|
48
|
+
|
|
49
|
+
const tasks = new Map();
|
|
50
|
+
let nextId = 1;
|
|
51
|
+
let errorLabel = null;
|
|
52
|
+
let healthFailures = 0;
|
|
53
|
+
let unloading = false;
|
|
54
|
+
|
|
55
|
+
let rootEl = null;
|
|
56
|
+
let labelEl = null;
|
|
57
|
+
let rendered = null;
|
|
58
|
+
let showTimer = null;
|
|
59
|
+
let hideTimer = null;
|
|
60
|
+
let busySince = 0;
|
|
61
|
+
|
|
62
|
+
function desiredState() {
|
|
63
|
+
if (errorLabel) return { state: "error", label: errorLabel };
|
|
64
|
+
if (tasks.size) {
|
|
65
|
+
let label = LIVE_LABEL;
|
|
66
|
+
for (const value of tasks.values()) label = value; // most recent wins
|
|
67
|
+
return { state: "busy", label };
|
|
68
|
+
}
|
|
69
|
+
return { state: "live", label: LIVE_LABEL };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function paint(next) {
|
|
73
|
+
rendered = next;
|
|
74
|
+
if (!rootEl) return;
|
|
75
|
+
rootEl.dataset.state = next.state;
|
|
76
|
+
rootEl.setAttribute("title", next.state === "error"
|
|
77
|
+
? `Plexora: ${next.label}`
|
|
78
|
+
: next.state === "busy" ? `Plexora is working: ${next.label}` : "Plexora is up to date");
|
|
79
|
+
if (labelEl.textContent !== next.label) labelEl.textContent = next.label;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function sync() {
|
|
83
|
+
const next = desiredState();
|
|
84
|
+
|
|
85
|
+
if (next.state === "error") {
|
|
86
|
+
if (showTimer) { clearTimeout(showTimer); showTimer = null; }
|
|
87
|
+
if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; }
|
|
88
|
+
paint(next);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (next.state === "busy") {
|
|
93
|
+
if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; }
|
|
94
|
+
if (rendered && rendered.state === "busy") { paint(next); return; }
|
|
95
|
+
if (showTimer) return;
|
|
96
|
+
showTimer = setTimeout(() => {
|
|
97
|
+
showTimer = null;
|
|
98
|
+
const now = desiredState();
|
|
99
|
+
if (now.state === "busy") { busySince = Date.now(); paint(now); }
|
|
100
|
+
}, SHOW_DELAY_MS);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Going back to live.
|
|
105
|
+
if (showTimer) { clearTimeout(showTimer); showTimer = null; }
|
|
106
|
+
if (rendered && rendered.state === "busy") {
|
|
107
|
+
const held = Date.now() - busySince;
|
|
108
|
+
if (held < MIN_BUSY_MS) {
|
|
109
|
+
if (!hideTimer) {
|
|
110
|
+
hideTimer = setTimeout(() => { hideTimer = null; sync(); }, MIN_BUSY_MS - held);
|
|
111
|
+
}
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
paint(next);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function begin(label) {
|
|
119
|
+
const id = nextId++;
|
|
120
|
+
tasks.set(id, String(label || "Working"));
|
|
121
|
+
sync();
|
|
122
|
+
let settled = false;
|
|
123
|
+
return {
|
|
124
|
+
done() {
|
|
125
|
+
if (settled) return;
|
|
126
|
+
settled = true;
|
|
127
|
+
tasks.delete(id);
|
|
128
|
+
sync();
|
|
129
|
+
},
|
|
130
|
+
fail(reason) {
|
|
131
|
+
if (settled) return;
|
|
132
|
+
settled = true;
|
|
133
|
+
tasks.delete(id);
|
|
134
|
+
setError(reason || "Error");
|
|
135
|
+
},
|
|
136
|
+
relabel(next) {
|
|
137
|
+
if (settled) return;
|
|
138
|
+
tasks.set(id, String(next));
|
|
139
|
+
sync();
|
|
140
|
+
},
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function track(label, promise) {
|
|
145
|
+
const task = begin(label);
|
|
146
|
+
return Promise.resolve(promise).then(
|
|
147
|
+
(value) => { task.done(); return value; },
|
|
148
|
+
(err) => { task.done(); throw err; },
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function setError(label) {
|
|
153
|
+
errorLabel = String(label || "Error");
|
|
154
|
+
sync();
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function clearError() {
|
|
158
|
+
if (!errorLabel) return;
|
|
159
|
+
errorLabel = null;
|
|
160
|
+
sync();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// --- transport: every fetch in the app reports its own health -----------
|
|
164
|
+
// Wrapping once here covers all ~40 call sites (26 in dataLayer.js alone,
|
|
165
|
+
// each with its own try/catch that swallows the failure) without touching
|
|
166
|
+
// any of them. Deliberately does NOT mark requests busy: most are fast, and
|
|
167
|
+
// a generic label would be less useful than the specific ones features
|
|
168
|
+
// supply via begin(). Errors are the part no feature reports today.
|
|
169
|
+
const nativeFetch = window.fetch ? window.fetch.bind(window) : null;
|
|
170
|
+
if (nativeFetch) {
|
|
171
|
+
window.fetch = function plexoraFetch(...args) {
|
|
172
|
+
return nativeFetch(...args).then((response) => {
|
|
173
|
+
if (response.status >= 500) {
|
|
174
|
+
setError(`Server error ${response.status}`);
|
|
175
|
+
} else {
|
|
176
|
+
healthFailures = 0;
|
|
177
|
+
clearError();
|
|
178
|
+
}
|
|
179
|
+
return response;
|
|
180
|
+
}, (err) => {
|
|
181
|
+
// A request cancelled by navigation isn't a server problem.
|
|
182
|
+
if (!unloading && !(err && err.name === "AbortError")) {
|
|
183
|
+
setError("Disconnected");
|
|
184
|
+
}
|
|
185
|
+
throw err;
|
|
186
|
+
});
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
window.addEventListener("beforeunload", () => { unloading = true; });
|
|
191
|
+
|
|
192
|
+
// --- transport: liveness while idle -------------------------------------
|
|
193
|
+
// An idle page issues no requests, so without this a server that died five
|
|
194
|
+
// minutes ago still shows green. Uses nativeFetch so a failed poll doesn't
|
|
195
|
+
// recurse through the wrapper's own error handling.
|
|
196
|
+
function pollHealth() {
|
|
197
|
+
if (!nativeFetch || document.visibilityState !== "visible") return;
|
|
198
|
+
nativeFetch(plexoraUrl("health"), { cache: "no-store" }).then((r) => {
|
|
199
|
+
if (!r.ok) throw new Error(String(r.status));
|
|
200
|
+
healthFailures = 0;
|
|
201
|
+
clearError();
|
|
202
|
+
}).catch(() => {
|
|
203
|
+
healthFailures += 1;
|
|
204
|
+
if (healthFailures >= HEALTH_FAILURES_BEFORE_ERROR) setError("Disconnected");
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// --- viewer: tiles still streaming in -----------------------------------
|
|
209
|
+
// "A channel was switched on but hasn't finished loading."
|
|
210
|
+
//
|
|
211
|
+
// Tracked per TiledImage rather than through the Viewer's aggregate
|
|
212
|
+
// fully-loaded-change: that aggregate only recomputes when some TiledImage
|
|
213
|
+
// raises its own fully-loaded-change, and a freshly added image doesn't
|
|
214
|
+
// raise anything on the way in (it starts un-loaded, which is not a change
|
|
215
|
+
// for it). So the viewer's cached _fullyLoaded stays true through the whole
|
|
216
|
+
// load and then matches again once the image finishes -- meaning it fires
|
|
217
|
+
// no event at all for a newly added channel. Verified: zero events across a
|
|
218
|
+
// channel toggle.
|
|
219
|
+
//
|
|
220
|
+
// Tiles go through OSD's own XHR rather than fetch, so tile-load-failed is
|
|
221
|
+
// the only way this path can surface an error.
|
|
222
|
+
function watchViewer(viewer) {
|
|
223
|
+
if (!viewer || !viewer.addHandler || viewer.__plexoraStatusWatched) return;
|
|
224
|
+
viewer.__plexoraStatusWatched = true;
|
|
225
|
+
|
|
226
|
+
const pending = new Set();
|
|
227
|
+
let loading = null;
|
|
228
|
+
function refresh() {
|
|
229
|
+
if (pending.size && !loading) {
|
|
230
|
+
loading = begin("Loading");
|
|
231
|
+
} else if (!pending.size && loading) {
|
|
232
|
+
loading.done();
|
|
233
|
+
loading = null;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function trackItem(item) {
|
|
237
|
+
if (!item || !item.addHandler || item.__plexoraStatusTracked) return;
|
|
238
|
+
item.__plexoraStatusTracked = true;
|
|
239
|
+
const update = () => {
|
|
240
|
+
if (item.getFullyLoaded && item.getFullyLoaded()) pending.delete(item);
|
|
241
|
+
else pending.add(item);
|
|
242
|
+
refresh();
|
|
243
|
+
};
|
|
244
|
+
item.addHandler("fully-loaded-change", update);
|
|
245
|
+
update();
|
|
246
|
+
}
|
|
247
|
+
function forget(item) {
|
|
248
|
+
pending.delete(item);
|
|
249
|
+
refresh();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (viewer.world) {
|
|
253
|
+
viewer.world.addHandler("add-item", (event) => trackItem(event.item));
|
|
254
|
+
viewer.world.addHandler("remove-item", (event) => forget(event.item));
|
|
255
|
+
for (let i = 0; i < viewer.world.getItemCount(); i++) {
|
|
256
|
+
trackItem(viewer.world.getItemAt(i));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
viewer.addHandler("tile-load-failed", () => setError("Tile failed"));
|
|
260
|
+
viewer.addHandler("close", () => { pending.clear(); refresh(); });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// --- mount ---------------------------------------------------------------
|
|
264
|
+
function mount() {
|
|
265
|
+
rootEl = document.getElementById("app_status");
|
|
266
|
+
if (!rootEl) return;
|
|
267
|
+
rootEl.classList.add("app-status");
|
|
268
|
+
rootEl.innerHTML =
|
|
269
|
+
'<span class="app-status-glyph" aria-hidden="true"></span>' +
|
|
270
|
+
'<span class="app-status-label"></span>';
|
|
271
|
+
labelEl = rootEl.querySelector(".app-status-label");
|
|
272
|
+
rootEl.setAttribute("role", "status");
|
|
273
|
+
rootEl.setAttribute("aria-live", "polite");
|
|
274
|
+
rendered = null;
|
|
275
|
+
paint(desiredState());
|
|
276
|
+
window.setInterval(pollHealth, HEALTH_INTERVAL_MS);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
if (document.readyState === "loading") {
|
|
280
|
+
document.addEventListener("DOMContentLoaded", mount);
|
|
281
|
+
} else {
|
|
282
|
+
mount();
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
window.PlexoraStatus = { begin, track, setError, clearError, watchViewer };
|
|
286
|
+
})();
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* browsePicker.js -- shared client for POST /browse_path (see
|
|
3
|
+
* server/utils/native_dialog.py), which pops a native OS file/folder dialog
|
|
4
|
+
* on the machine the server runs on and hands back the chosen path. Used by
|
|
5
|
+
* both the quick-view landing page and the upload page's per-field "Browse..."
|
|
6
|
+
* buttons; only works when there's a real desktop session for the dialog to
|
|
7
|
+
* appear on, so every caller needs an `onUnavailable` fallback (e.g. a manual
|
|
8
|
+
* text input) for the headless/remote-server case.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @function browseForPath - opens a native file ("file") or folder
|
|
13
|
+
* ("directory") dialog and calls back with the chosen path.
|
|
14
|
+
* @param mode - "file" or "directory"
|
|
15
|
+
* @param filter - narrows the file-type dropdown for mode="file": one of
|
|
16
|
+
* "image", "csv", "h5ad", or "any" (default -- just "All files"). Ignored
|
|
17
|
+
* for mode="directory". Must match one of native_dialog.py's FILTERS keys.
|
|
18
|
+
* @param onPicked - called with the chosen path string; not called if the
|
|
19
|
+
* user cancels the dialog (result.path === null)
|
|
20
|
+
* @param onUnavailable - called (with the error) if the dialog itself
|
|
21
|
+
* couldn't be shown at all -- no desktop session, tkinter missing, etc.
|
|
22
|
+
*/
|
|
23
|
+
async function browseForPath({mode = "file", filter = "any", onPicked, onUnavailable} = {}) {
|
|
24
|
+
try {
|
|
25
|
+
const response = await fetch(plexoraUrl("browse_path"), {
|
|
26
|
+
method: "POST",
|
|
27
|
+
headers: {"Content-Type": "application/json"},
|
|
28
|
+
body: JSON.stringify({mode: mode, filter: filter}),
|
|
29
|
+
});
|
|
30
|
+
const result = await response.json();
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
throw new Error(result.error);
|
|
33
|
+
}
|
|
34
|
+
if (result.path && onPicked) {
|
|
35
|
+
onPicked(result.path);
|
|
36
|
+
}
|
|
37
|
+
// result.path === null just means the user cancelled the dialog.
|
|
38
|
+
} catch (error) {
|
|
39
|
+
if (onUnavailable) {
|
|
40
|
+
onUnavailable(error);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @function attachBrowseButton - wires a "Browse..." button to fill a text
|
|
47
|
+
* input with the picked path, dispatching an `input` event afterward so any
|
|
48
|
+
* existing onkeyup/oninput live-validation on that field runs unchanged.
|
|
49
|
+
*/
|
|
50
|
+
function attachBrowseButton(buttonEl, inputEl, {mode = "file", filter = "any"} = {}) {
|
|
51
|
+
if (!buttonEl || !inputEl) return;
|
|
52
|
+
buttonEl.addEventListener("click", () => {
|
|
53
|
+
browseForPath({
|
|
54
|
+
mode,
|
|
55
|
+
filter,
|
|
56
|
+
onPicked: (path) => {
|
|
57
|
+
inputEl.value = path;
|
|
58
|
+
inputEl.dispatchEvent(new Event("input"));
|
|
59
|
+
inputEl.dispatchEvent(new Event("keyup"));
|
|
60
|
+
},
|
|
61
|
+
// A dialog that cannot be shown must say so. This used to swallow
|
|
62
|
+
// the error, which turned a rejected filter name into a button
|
|
63
|
+
// that looked ordinary and did nothing when clicked -- with the
|
|
64
|
+
// path input right next to it, there was no way to tell the
|
|
65
|
+
// difference between "no dialog here" and "this button is broken".
|
|
66
|
+
onUnavailable: (error) => {
|
|
67
|
+
console.error("browsePicker: could not open the file dialog.", error);
|
|
68
|
+
window.PlexoraStatus?.begin?.("Browse")?.fail?.(
|
|
69
|
+
error?.message || "Could not open the file browser — type the path instead.");
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
}
|