freebrowse 2.5.0-next.1
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.
- package/README.md +134 -0
- package/dist-lib/index.js +21492 -0
- package/dist-lib/index.js.map +1 -0
- package/dist-lib/react.js +10790 -0
- package/dist-lib/react.js.map +1 -0
- package/dist-lib/style.css +1 -0
- package/dist-lib/types/components/canvas-area.d.ts +10 -0
- package/dist-lib/types/components/dialogs/imaging-upload-confirmation-dialog.d.ts +6 -0
- package/dist-lib/types/components/dialogs/remove-dialog.d.ts +6 -0
- package/dist-lib/types/components/dialogs/save-dialog.d.ts +14 -0
- package/dist-lib/types/components/dialogs/session-delete-confirmation-dialog.d.ts +6 -0
- package/dist-lib/types/components/dialogs/settings-dialog.d.ts +6 -0
- package/dist-lib/types/components/drag-mode-selector.d.ts +9 -0
- package/dist-lib/types/components/file-list.d.ts +12 -0
- package/dist-lib/types/components/footer.d.ts +1 -0
- package/dist-lib/types/components/freebrowse.d.ts +32 -0
- package/dist-lib/types/components/header.d.ts +6 -0
- package/dist-lib/types/components/image-canvas.d.ts +7 -0
- package/dist-lib/types/components/image-uploader.d.ts +6 -0
- package/dist-lib/types/components/sidebar.d.ts +56 -0
- package/dist-lib/types/components/tabs/ai-annotation-tab.d.ts +20 -0
- package/dist-lib/types/components/tabs/data-tab.d.ts +6 -0
- package/dist-lib/types/components/tabs/drawing-tab.d.ts +17 -0
- package/dist-lib/types/components/tabs/nvd-tab.d.ts +6 -0
- package/dist-lib/types/components/tabs/scene-details-tab.d.ts +22 -0
- package/dist-lib/types/components/tabs/surface-details-tab.d.ts +20 -0
- package/dist-lib/types/components/ui/button.d.ts +10 -0
- package/dist-lib/types/components/ui/checkbox.d.ts +4 -0
- package/dist-lib/types/components/ui/dialog.d.ts +15 -0
- package/dist-lib/types/components/ui/input.d.ts +5 -0
- package/dist-lib/types/components/ui/label.d.ts +4 -0
- package/dist-lib/types/components/ui/labeled-slider-with-input.d.ts +14 -0
- package/dist-lib/types/components/ui/radio-group.d.ts +5 -0
- package/dist-lib/types/components/ui/scroll-area.d.ts +5 -0
- package/dist-lib/types/components/ui/select.d.ts +5 -0
- package/dist-lib/types/components/ui/slider.d.ts +4 -0
- package/dist-lib/types/components/ui/tabs.d.ts +7 -0
- package/dist-lib/types/components/ui/toggle-group.d.ts +7 -0
- package/dist-lib/types/components/ui/toggle.d.ts +9 -0
- package/dist-lib/types/components/view-selector.d.ts +7 -0
- package/dist-lib/types/components/viewer-shell.d.ts +13 -0
- package/dist-lib/types/hooks/use-ai-capabilities.d.ts +1 -0
- package/dist-lib/types/hooks/use-ai-session.d.ts +9 -0
- package/dist-lib/types/hooks/use-drawing.d.ts +27 -0
- package/dist-lib/types/hooks/use-file-loading.d.ts +15 -0
- package/dist-lib/types/hooks/use-magic-wand.d.ts +15 -0
- package/dist-lib/types/hooks/use-mesh-layers.d.ts +21 -0
- package/dist-lib/types/hooks/use-save.d.ts +12 -0
- package/dist-lib/types/hooks/use-surfaces.d.ts +16 -0
- package/dist-lib/types/hooks/use-viewer-options.d.ts +42 -0
- package/dist-lib/types/hooks/use-volumes.d.ts +39 -0
- package/dist-lib/types/lib/confirmations.d.ts +4 -0
- package/dist-lib/types/lib/default-niivue-options.d.ts +16 -0
- package/dist-lib/types/lib/deployment-config.d.ts +66 -0
- package/dist-lib/types/lib/niivue-helpers.d.ts +27 -0
- package/dist-lib/types/lib/nvd-volume-urls.d.ts +26 -0
- package/dist-lib/types/lib/random-session-name.d.ts +1 -0
- package/dist-lib/types/lib/utils.d.ts +2 -0
- package/dist-lib/types/lib.d.ts +16 -0
- package/dist-lib/types/mount.d.ts +49 -0
- package/dist-lib/types/react.d.ts +10 -0
- package/dist-lib/types/store/ai-slice.d.ts +25 -0
- package/dist-lib/types/store/drawing-slice.d.ts +7 -0
- package/dist-lib/types/store/index.d.ts +21 -0
- package/dist-lib/types/store/location-slice.d.ts +7 -0
- package/dist-lib/types/store/niivue-store-sync.d.ts +41 -0
- package/dist-lib/types/store/niivue-sync.d.ts +57 -0
- package/dist-lib/types/store/save-slice.d.ts +9 -0
- package/dist-lib/types/store/surface-slice.d.ts +15 -0
- package/dist-lib/types/store/types.d.ts +69 -0
- package/dist-lib/types/store/viewer-slice.d.ts +31 -0
- package/dist-lib/types/store/volume-slice.d.ts +13 -0
- package/package.json +130 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { NiiVue, NVExtensionContext } from "@niivue/niivue";
|
|
2
|
+
import type { DrawingOptions } from "../store/types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Flood from the drawing layer's *current* bitmap and apply the result. Used for
|
|
5
|
+
* the one-shot commit path (a click with no preceding hover preview). Exported
|
|
6
|
+
* for unit testing against a fake ctx. Guards on drawing/volume so a click before
|
|
7
|
+
* a layer/volume exists is a no-op.
|
|
8
|
+
*/
|
|
9
|
+
export declare function runMagicWand(ctx: NVExtensionContext, opts: DrawingOptions, seed: [number, number, number], sliceType: number): Promise<void>;
|
|
10
|
+
/**
|
|
11
|
+
* Wires the wand engine to the niivue instance. One extension context per
|
|
12
|
+
* instance; params are read imperatively at event time (so changing them never
|
|
13
|
+
* re-subscribes).
|
|
14
|
+
*/
|
|
15
|
+
export declare function useMagicWand(nvRef: React.RefObject<NiiVue | null>): void;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import NiiVue from "@niivue/niivue";
|
|
2
|
+
/**
|
|
3
|
+
* Mesh scalar-overlay layer handlers. Command-only: handlers issue nv.* layer
|
|
4
|
+
* commands (addMeshLayer/removeMeshLayer/setMeshLayerProperty), which emit
|
|
5
|
+
* meshUpdated -> the event adapter bumps layerVersion. getLayers re-reads
|
|
6
|
+
* nv.meshes[i].layers at render, so the UI follows without imperative writes.
|
|
7
|
+
* Layers are addressed by (meshIndex, layerIndex) — niivue-mono meshes have no id.
|
|
8
|
+
*/
|
|
9
|
+
export declare function useMeshLayers(nvRef: React.RefObject<NiiVue | null>): {
|
|
10
|
+
layerFileInputRef: import("react").RefObject<HTMLInputElement | null>;
|
|
11
|
+
getLayers: () => import("@niivue/niivue").NVMeshLayer[];
|
|
12
|
+
addLayerFromFile: (file: File) => Promise<void>;
|
|
13
|
+
removeLayer: (layerIndex: number) => void;
|
|
14
|
+
handleLayerOpacityChange: (value: number) => void;
|
|
15
|
+
handleLayerCalMinChange: (value: number) => void;
|
|
16
|
+
handleLayerCalMaxChange: (value: number) => void;
|
|
17
|
+
handleLayerColormapChange: (colormap: string) => Promise<void>;
|
|
18
|
+
handleLayerUseNegativeCmapChange: (checked: boolean) => Promise<void>;
|
|
19
|
+
handleAddLayerFiles: () => void;
|
|
20
|
+
handleLayerFileChange: (e: React.ChangeEvent<HTMLInputElement>) => Promise<void>;
|
|
21
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { NvdFormat } from "../store/types.js";
|
|
2
|
+
import type { NiiVue } from "@niivue/niivue";
|
|
3
|
+
export declare function useSave(nvRef: React.RefObject<NiiVue | null>): {
|
|
4
|
+
handleSaveScene: (isDownload?: boolean) => Promise<void>;
|
|
5
|
+
handleConfirmSave: () => Promise<void>;
|
|
6
|
+
handleCancelSave: () => void;
|
|
7
|
+
handleVolumeUrlChange: (index: number, url: string) => void;
|
|
8
|
+
handleVolumeCheckboxChange: (index: number, enabled: boolean) => void;
|
|
9
|
+
handleDocumentLocationChange: (location: string) => void;
|
|
10
|
+
handleDocumentCheckboxChange: (enabled: boolean) => void;
|
|
11
|
+
handleDocumentFormatChange: (format: NvdFormat) => void;
|
|
12
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { NiiVue } from "@niivue/niivue";
|
|
2
|
+
/**
|
|
3
|
+
* Surface (mesh) handlers. Command-only: handlers issue nv.* commands and the
|
|
4
|
+
* derived `surfaces` list follows via the event adapter (which rebuilds it from
|
|
5
|
+
* nv.meshes on meshLoaded/meshRemoved/meshUpdated). Handlers never write the
|
|
6
|
+
* surfaces slice directly.
|
|
7
|
+
*/
|
|
8
|
+
export declare function useSurfaces(nvRef: React.RefObject<NiiVue | null>, debouncedGLUpdate: () => void): {
|
|
9
|
+
updateSurfaceDetails: () => void;
|
|
10
|
+
toggleSurfaceVisibility: (surfaceIndex: number) => void;
|
|
11
|
+
removeSurface: (surfaceIndex: number) => void;
|
|
12
|
+
handleRemoveSurfaceClick: (surfaceIndex: number) => void;
|
|
13
|
+
handleSurfaceOpacityChange: (newOpacity: number) => void;
|
|
14
|
+
handleSurfaceColorChange: (hexColor: string) => void;
|
|
15
|
+
handleMeshShaderChange: (shaderName: string) => void;
|
|
16
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { type NiiVue } from "@niivue/niivue";
|
|
2
|
+
import type { DragMode } from "../components/drag-mode-selector.js";
|
|
3
|
+
import type { ViewMode } from "../store/types.js";
|
|
4
|
+
/**
|
|
5
|
+
* Viewer-option handlers. Event-driven model: handlers issue commands to the
|
|
6
|
+
* niivue instance (`nv.*`) and the Zustand store follows via the event adapter
|
|
7
|
+
* (registerNiiVueEvents / createStoreSyncTarget) — handlers do NOT write the
|
|
8
|
+
* store themselves. The single exception is crosshairWidth/crosshairVisible,
|
|
9
|
+
* which is store-owned UI state (niivue has only one crosshairWidth where 0
|
|
10
|
+
* means hidden, so it can't remember the width while hidden).
|
|
11
|
+
*
|
|
12
|
+
* One-time init on mount, chosen by `init` (only the viewer component that owns
|
|
13
|
+
* the mount passes it; other consumers of the handlers leave it at "none"):
|
|
14
|
+
* - `"apply"` (the app, which built the instance): push the store's viewer
|
|
15
|
+
* defaults onto the instance once (`applyViewerOptions`).
|
|
16
|
+
* - `"sync"` (a host-owned instance): never touch the instance's settings;
|
|
17
|
+
* seed the store FROM it instead (`syncViewerOptionsFromNiiVue`), so the
|
|
18
|
+
* UI reflects what the host configured.
|
|
19
|
+
* - `"none"` (default): handlers only.
|
|
20
|
+
*/
|
|
21
|
+
export type ViewerOptionsInit = "apply" | "sync" | "none";
|
|
22
|
+
export declare function useViewerOptions(nvRef: React.RefObject<NiiVue | null>, init?: ViewerOptionsInit): {
|
|
23
|
+
viewerOptions: import("../store/types.js").ViewerOptions;
|
|
24
|
+
setViewerOptions: (options: import("../store/types.js").ViewerOptions | ((prev: import("../store/types.js").ViewerOptions) => import("../store/types.js").ViewerOptions)) => void;
|
|
25
|
+
applyViewerOptions: () => void;
|
|
26
|
+
syncViewerOptionsFromNiiVue: () => void;
|
|
27
|
+
debouncedGLUpdate: () => void;
|
|
28
|
+
handleViewMode: (mode: ViewMode) => void;
|
|
29
|
+
handleDragModeChange: (mode: DragMode) => void;
|
|
30
|
+
handleCrosshairWidthChange: (value: number) => void;
|
|
31
|
+
handleCrosshairGapChange: (value: number) => void;
|
|
32
|
+
handleInterpolateVoxelsChange: (checked: boolean) => void;
|
|
33
|
+
handleCrosshairVisibleChange: (visible: boolean) => void;
|
|
34
|
+
handleCrosshairColorChange: (color: string) => void;
|
|
35
|
+
handleRulerWidthChange: (value: number) => void;
|
|
36
|
+
handleRulerVisibleChange: (visible: boolean) => void;
|
|
37
|
+
handleOverlayOutlineWidthChange: (value: number) => void;
|
|
38
|
+
handleColorbarChange: (checked: boolean) => void;
|
|
39
|
+
handleRadiologicalChange: (checked: boolean) => void;
|
|
40
|
+
handleSagittalNoseLeftChange: (checked: boolean) => void;
|
|
41
|
+
resetViewAndContrast: () => void;
|
|
42
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { NiiVue } from "@niivue/niivue";
|
|
2
|
+
/**
|
|
3
|
+
* Built-in colormaps that are categorical/label maps (as opposed to continuous
|
|
4
|
+
* gradients). When one of these is selected, FreeBrowse applies it as a label
|
|
5
|
+
* colormap (setColormapLabel) so the volume renders as discrete regions.
|
|
6
|
+
* niivue-mono does not tag colormaps as label-vs-continuous, so this list is
|
|
7
|
+
* curated here; matched case-insensitively against nv.colormaps. Only palettes
|
|
8
|
+
* we've made opaque (alpha 255) are included — random/nih still ship alpha 64
|
|
9
|
+
* and would wash out, so they're excluded pending the Phase-7 systemic fix.
|
|
10
|
+
*/
|
|
11
|
+
export declare const LABEL_COLORMAPS: Set<string>;
|
|
12
|
+
/**
|
|
13
|
+
* After a document loads, attach the label LUT to every volume whose
|
|
14
|
+
* `colormap` names a categorical palette but that carries no `colormapLabel`.
|
|
15
|
+
*
|
|
16
|
+
* niivue-mono applies a document's `colormap` as a continuous gradient
|
|
17
|
+
* (niivue/mono#69), so a migrated legacy scene -- `aseg` with `Freesurfer`,
|
|
18
|
+
* say -- would render as a smooth ramp. A document the app itself saved after
|
|
19
|
+
* a dropdown change already embeds `colormapLabel` and is left alone. Same
|
|
20
|
+
* dual call as `handleColormapChange`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function applyLabelColormapsAfterLoad(nv: NiiVue): Promise<void>;
|
|
23
|
+
export declare function useVolumes(nvRef: React.RefObject<NiiVue | null>, debouncedGLUpdate: () => void, removeSurface: (surfaceIndex: number) => void): {
|
|
24
|
+
getVolumes: () => import("@niivue/niivue").NVImage[];
|
|
25
|
+
toggleImageVisibility: (id: string) => void;
|
|
26
|
+
handleOpacityChange: (newOpacity: number) => void;
|
|
27
|
+
handleFrameChange: (newFrame: number) => void;
|
|
28
|
+
handleContrastMinChange: (newContrastMin: number) => void;
|
|
29
|
+
handleContrastMaxChange: (newContrastMax: number) => void;
|
|
30
|
+
handleColormapChange: (event: React.ChangeEvent<HTMLSelectElement>) => void;
|
|
31
|
+
handleMoveVolumeUp: () => void;
|
|
32
|
+
handleMoveVolumeDown: () => void;
|
|
33
|
+
removeVolume: (imageIndex: number) => void;
|
|
34
|
+
handleRemoveVolumeClick: (imageIndex: number) => void;
|
|
35
|
+
handleEditVolume: (imageIndex: number) => Promise<void>;
|
|
36
|
+
canEditVolume: (imageIndex: number) => boolean;
|
|
37
|
+
handleConfirmRemove: () => void;
|
|
38
|
+
handleCancelRemove: () => void;
|
|
39
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function requestImagingUploadConfirmation(): Promise<boolean>;
|
|
2
|
+
export declare function resolveImagingUploadConfirmation(ok: boolean): void;
|
|
3
|
+
export declare function requestSessionDeleteConfirmation(): Promise<boolean>;
|
|
4
|
+
export declare function resolveSessionDeleteConfirmation(ok: boolean): void;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { NiiVue, type NiiVueOptions } from "@niivue/niivue";
|
|
2
|
+
/**
|
|
3
|
+
* The options FreeBrowse constructs its NiiVue instance with. Shared by the
|
|
4
|
+
* app entry (`main.tsx`) and the library (`createFreeBrowseInstance`) so both
|
|
5
|
+
* produce identical instances. No `backend` option: niivue picks WebGPU when
|
|
6
|
+
* `navigator.gpu` exists and falls back to WebGL2 otherwise.
|
|
7
|
+
*/
|
|
8
|
+
export declare const DEFAULT_NIIVUE_OPTIONS: Partial<NiiVueOptions>;
|
|
9
|
+
/** Overrides the QA route applies on top of the defaults. */
|
|
10
|
+
export declare const QA_VIEWER_NIIVUE_OPTIONS: Partial<NiiVueOptions>;
|
|
11
|
+
/**
|
|
12
|
+
* Build a NiiVue instance the way FreeBrowse does. A host that wants FreeBrowse
|
|
13
|
+
* to own the instance calls this (or lets `mountFreeBrowse` call it); a host
|
|
14
|
+
* that already has an instance passes its own instead.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createFreeBrowseInstance(overrides?: Partial<NiiVueOptions>): NiiVue;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime configuration for one FreeBrowse instance (app or embedded library).
|
|
3
|
+
*
|
|
4
|
+
* These are *deployment* (operator/host-controlled) settings, distinct from
|
|
5
|
+
* per-user preferences, and must never be sourced from user-writable storage.
|
|
6
|
+
*
|
|
7
|
+
* Defaults come from Vite env vars so the app behaves exactly as before
|
|
8
|
+
* (`VITE_SERVERLESS`, `VITE_DISABLE_DOWNLOAD`); an embedding host overrides
|
|
9
|
+
* them at runtime through `configureFreeBrowse` (or the `mountFreeBrowse`
|
|
10
|
+
* options), since a published package has one build and no env.
|
|
11
|
+
*
|
|
12
|
+
* Consumers read through `deploymentConfig` (getters, always current) and the
|
|
13
|
+
* `dataUrl` / `aiUrl` helpers rather than caching values at import time.
|
|
14
|
+
*/
|
|
15
|
+
export interface FreeBrowseBackend {
|
|
16
|
+
/** Base URL of the `/data` file API (listings, `.nvd` + `.nii` uploads). */
|
|
17
|
+
dataUrl: string;
|
|
18
|
+
/** Base URL of the `/ai` session API. */
|
|
19
|
+
aiUrl: string;
|
|
20
|
+
}
|
|
21
|
+
export interface FreeBrowseConfig {
|
|
22
|
+
/**
|
|
23
|
+
* Backend endpoints, or `null` for a backend-less ("serverless") viewer: no
|
|
24
|
+
* Save-to-server, no Data/NVD tabs, no AI capability probe.
|
|
25
|
+
*/
|
|
26
|
+
backend: FreeBrowseBackend | null;
|
|
27
|
+
/**
|
|
28
|
+
* Lock down client-side data export for secure deployments: disables the
|
|
29
|
+
* Download button and no-ops niivue's save-to-disk methods. Not a guarantee
|
|
30
|
+
* against a determined/malicious user — it stops well-intentioned exports.
|
|
31
|
+
*/
|
|
32
|
+
downloadDisabled: boolean;
|
|
33
|
+
/** localStorage key for user preferences, or `false` to keep them in memory. */
|
|
34
|
+
persist: string | false;
|
|
35
|
+
/** Read `?nvd=` / `?vol=` from the page URL on startup (app: yes; library: no). */
|
|
36
|
+
readUrlParams: boolean;
|
|
37
|
+
/** Publish the instance as `window.freebrowse.nv` (app: yes; library: no). */
|
|
38
|
+
exposeGlobal: boolean;
|
|
39
|
+
}
|
|
40
|
+
export declare const DEFAULT_BACKEND: FreeBrowseBackend;
|
|
41
|
+
export declare const DEFAULT_PERSIST_KEY = "freebrowse-user-settings";
|
|
42
|
+
/** Override part of the configuration; returns the effective config. */
|
|
43
|
+
export declare function configureFreeBrowse(partial: Partial<FreeBrowseConfig>): Readonly<FreeBrowseConfig>;
|
|
44
|
+
/** Back to the env-derived defaults (tests). */
|
|
45
|
+
export declare function resetFreeBrowseConfig(): void;
|
|
46
|
+
/** The effective configuration (live view; do not mutate). */
|
|
47
|
+
export declare function getFreeBrowseConfig(): Readonly<FreeBrowseConfig>;
|
|
48
|
+
/**
|
|
49
|
+
* The two flags most of the UI keys off. Getters, so a value read inside a
|
|
50
|
+
* render or effect is always the configured one.
|
|
51
|
+
*/
|
|
52
|
+
export declare const deploymentConfig: {
|
|
53
|
+
/** No backend configured (file:// builds, or an embedding host without one). */
|
|
54
|
+
readonly serverless: boolean;
|
|
55
|
+
readonly downloadDisabled: boolean;
|
|
56
|
+
};
|
|
57
|
+
/** `dataUrl("nvd")` → `<backend.dataUrl>/nvd`. Throws when no backend is configured. */
|
|
58
|
+
export declare function dataUrl(path?: string): string;
|
|
59
|
+
/** `aiUrl("session/list")` → `<backend.aiUrl>/session/list`. Throws when no backend. */
|
|
60
|
+
export declare function aiUrl(path?: string): string;
|
|
61
|
+
/**
|
|
62
|
+
* Strip the configured data base from a URL, returning the path relative to
|
|
63
|
+
* the data root (`"/data/x/y.nii.gz"` → `"x/y.nii.gz"`), or null if the URL is
|
|
64
|
+
* not under it. Accepts the bare relative spelling too (`"data/x"`).
|
|
65
|
+
*/
|
|
66
|
+
export declare function relativeToDataRoot(url: string): string | null;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Map from view mode name to NiiVue slice type and render settings.
|
|
3
|
+
*/
|
|
4
|
+
export declare const sliceTypeMap: {
|
|
5
|
+
[type: string]: {
|
|
6
|
+
sliceType: number;
|
|
7
|
+
showRender: number;
|
|
8
|
+
};
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Convert an RGBA [0-255] tuple to a hex color string (ignoring alpha).
|
|
12
|
+
*/
|
|
13
|
+
export declare function rgba255ToHex(rgba255: [number, number, number, number]): string;
|
|
14
|
+
/**
|
|
15
|
+
* Convert a Uint8Array to a base64 string efficiently for large arrays.
|
|
16
|
+
*/
|
|
17
|
+
export declare function uint8ArrayToBase64(uint8Array: Uint8Array): string;
|
|
18
|
+
/**
|
|
19
|
+
* Gzip a byte array with the browser-native `CompressionStream('gzip')`.
|
|
20
|
+
*
|
|
21
|
+
* niivue-mono's `saveVolume` returns raw (uncompressed) bytes only for an empty
|
|
22
|
+
* filename — a `.gz` filename triggers a browser download instead of returning
|
|
23
|
+
* bytes. So the AI upload path exports raw bytes via `saveVolume({filename:''})`
|
|
24
|
+
* and gzips them here (matching old FreeBrowse's `saveToUint8Array(name.gz)`),
|
|
25
|
+
* with no extra dependency.
|
|
26
|
+
*/
|
|
27
|
+
export declare function gzipUint8Array(bytes: Uint8Array): Promise<Uint8Array>;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Point each volume entry at the backend path it is being uploaded to, for the
|
|
3
|
+
* "save to server" flow.
|
|
4
|
+
*
|
|
5
|
+
* The document is serialized with `linkData: true`, so a volume that already has
|
|
6
|
+
* a fetchable url carries no bytes. This rewrites that url to the *destination*
|
|
7
|
+
* — which `linkData` cannot do, because it keys off where a volume was loaded
|
|
8
|
+
* from (`shouldLinkVolume` in niivue's `documentLinkData.ts`), not where it is
|
|
9
|
+
* going. FreeBrowse's save dialog collects a path for a file being uploaded in
|
|
10
|
+
* the same submit, which does not exist yet and is not `volume.url`.
|
|
11
|
+
*
|
|
12
|
+
* Any residual embedded data is dropped: a `blob:`/`data:` url fails niivue's
|
|
13
|
+
* `isLinkableUrl`, so those volumes embed as a fallback.
|
|
14
|
+
*
|
|
15
|
+
* A `null` entry means the user gave no destination for that volume, so its
|
|
16
|
+
* bytes are KEPT and the document still round-trips. (The previous
|
|
17
|
+
* `stripEmbeddedData` deleted them regardless, producing an entry that pointed
|
|
18
|
+
* at nothing.)
|
|
19
|
+
*
|
|
20
|
+
* Meshes are deliberately untouched: niivue's mesh URL-restore path does not
|
|
21
|
+
* reapply scalar-overlay layers or tract options, so a linked mesh loses that
|
|
22
|
+
* state silently.
|
|
23
|
+
*
|
|
24
|
+
* Mutates and returns `doc`.
|
|
25
|
+
*/
|
|
26
|
+
export declare function retargetVolumeUrls(doc: Record<string, unknown>, volumeUrls: (string | null)[]): Record<string, unknown>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function randomSessionName(): string;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `freebrowse` — framework-agnostic entry. React is bundled; the host only
|
|
3
|
+
* needs `@niivue/niivue` (peer) and `import "freebrowse/style.css"`.
|
|
4
|
+
*
|
|
5
|
+
* import { NiiVue } from "@niivue/niivue";
|
|
6
|
+
* import { mountFreeBrowse } from "freebrowse";
|
|
7
|
+
* import "freebrowse/style.css";
|
|
8
|
+
* const nv = new NiiVue();
|
|
9
|
+
* const fb = mountFreeBrowse(document.getElementById("viewer"), { nv });
|
|
10
|
+
* await nv.addVolume({ url: "..." }); // the FreeBrowse UI follows
|
|
11
|
+
* fb.destroy();
|
|
12
|
+
*/
|
|
13
|
+
export { mountFreeBrowse } from "./mount.js";
|
|
14
|
+
export type { MountOptions, FreeBrowseHandle } from "./mount.js";
|
|
15
|
+
export { createFreeBrowseInstance, DEFAULT_NIIVUE_OPTIONS, } from "./lib/default-niivue-options.js";
|
|
16
|
+
export { configureFreeBrowse, getFreeBrowseConfig, type FreeBrowseBackend, type FreeBrowseConfig, } from "./lib/deployment-config.js";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { NiiVue } from "@niivue/niivue";
|
|
2
|
+
import { type FreeBrowseBackend } from "./lib/deployment-config.js";
|
|
3
|
+
export interface MountOptions {
|
|
4
|
+
/**
|
|
5
|
+
* The NiiVue instance to show. When given, the host owns it: FreeBrowse
|
|
6
|
+
* seeds its UI from the instance's current settings and never pushes its
|
|
7
|
+
* own defaults onto it. When omitted, FreeBrowse creates one with
|
|
8
|
+
* `createFreeBrowseInstance()` and applies its defaults.
|
|
9
|
+
*/
|
|
10
|
+
nv?: NiiVue;
|
|
11
|
+
/**
|
|
12
|
+
* Backend endpoints for Save-to-server, the Data/NVD tabs and AI sessions.
|
|
13
|
+
* Omitted (or `null`): no backend — those features are hidden.
|
|
14
|
+
*/
|
|
15
|
+
backend?: FreeBrowseBackend | null;
|
|
16
|
+
/** localStorage key for user preferences (default `freebrowse-user-settings`), or `false` to keep them in memory. */
|
|
17
|
+
persist?: string | false;
|
|
18
|
+
/** Read `?nvd=` / `?vol=` from the host page URL on startup. Default false. */
|
|
19
|
+
readUrlParams?: boolean;
|
|
20
|
+
/** Also publish the instance as `window.freebrowse.nv`. Default false. */
|
|
21
|
+
exposeGlobal?: boolean;
|
|
22
|
+
/** Disable the Download button and niivue's save-to-disk methods. Default false. */
|
|
23
|
+
downloadDisabled?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Allow loading by drag-and-drop (niivue's canvas drop and FreeBrowse's
|
|
26
|
+
* drop zone). `false` also skips the drop zone and shows the canvas from
|
|
27
|
+
* the start, for hosts that load data themselves. Default true.
|
|
28
|
+
*/
|
|
29
|
+
dragDrop?: boolean;
|
|
30
|
+
/** Show the sidebar initially (the header button still toggles it). Default true. */
|
|
31
|
+
sidebar?: boolean;
|
|
32
|
+
/** Show the footer (coordinate readout) initially. Default true. */
|
|
33
|
+
footer?: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface FreeBrowseHandle {
|
|
36
|
+
/** The instance FreeBrowse is showing (the one passed in, or the one it created). */
|
|
37
|
+
nv: NiiVue;
|
|
38
|
+
/** Unmount the UI and detach FreeBrowse's event listeners from the instance. */
|
|
39
|
+
destroy(): void;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Mount the FreeBrowse UI into `container` and return a handle.
|
|
43
|
+
*
|
|
44
|
+
* One mount at a time: FreeBrowse's UI state is a single store, so a second
|
|
45
|
+
* concurrent mount throws. Call `destroy()` on the previous handle first.
|
|
46
|
+
*
|
|
47
|
+
* Styles are not injected — import `freebrowse/style.css` once in the host.
|
|
48
|
+
*/
|
|
49
|
+
export declare function mountFreeBrowse(container: HTMLElement, options?: MountOptions): FreeBrowseHandle;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `freebrowse/react` — for hosts that already render React 19: the component
|
|
3
|
+
* itself (React and ReactDOM are peer dependencies on this entry, not bundled).
|
|
4
|
+
* Configure once with `configureFreeBrowse` before the first render, and import
|
|
5
|
+
* `freebrowse/style.css` in the host.
|
|
6
|
+
*/
|
|
7
|
+
export { default as FreeBrowse } from "./components/freebrowse.js";
|
|
8
|
+
export type { FreeBrowseProps } from "./components/freebrowse.js";
|
|
9
|
+
export { configureFreeBrowse, getFreeBrowseConfig, type FreeBrowseBackend, type FreeBrowseConfig, } from "./lib/deployment-config.js";
|
|
10
|
+
export { createFreeBrowseInstance, DEFAULT_NIIVUE_OPTIONS, } from "./lib/default-niivue-options.js";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { StateCreator } from "zustand";
|
|
2
|
+
export type AiSessionSummary = {
|
|
3
|
+
session_id: string;
|
|
4
|
+
session_name: string;
|
|
5
|
+
created_at: string;
|
|
6
|
+
volume_path: string | null;
|
|
7
|
+
volume_path_root: "data" | "session" | null;
|
|
8
|
+
annotation_path: string | null;
|
|
9
|
+
result_path: string | null;
|
|
10
|
+
last_inference_ml_id: string | null;
|
|
11
|
+
last_inference_at: string | null;
|
|
12
|
+
};
|
|
13
|
+
export type AiActiveSession = {
|
|
14
|
+
session_id: string;
|
|
15
|
+
session_name: string;
|
|
16
|
+
};
|
|
17
|
+
export interface AiSlice {
|
|
18
|
+
aiEnabled: boolean | null;
|
|
19
|
+
aiSessions: AiSessionSummary[];
|
|
20
|
+
aiActiveSession: AiActiveSession | null;
|
|
21
|
+
setAiEnabled: (enabled: boolean | null) => void;
|
|
22
|
+
setAiSessions: (sessions: AiSessionSummary[]) => void;
|
|
23
|
+
setAiActiveSession: (session: AiActiveSession | null) => void;
|
|
24
|
+
}
|
|
25
|
+
export declare const createAiSlice: StateCreator<AiSlice>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { StateCreator } from "zustand";
|
|
2
|
+
import type { DrawingOptions } from "./types";
|
|
3
|
+
export interface DrawingSlice {
|
|
4
|
+
drawingOptions: DrawingOptions;
|
|
5
|
+
setDrawingOptions: (options: DrawingOptions | ((prev: DrawingOptions) => DrawingOptions)) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare const createDrawingSlice: StateCreator<DrawingSlice>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type VolumeSlice } from "./volume-slice";
|
|
2
|
+
import { type SurfaceSlice } from "./surface-slice";
|
|
3
|
+
import { type DrawingSlice } from "./drawing-slice";
|
|
4
|
+
import { type ViewerSlice } from "./viewer-slice";
|
|
5
|
+
import { type SaveSlice } from "./save-slice";
|
|
6
|
+
import { type LocationSlice } from "./location-slice";
|
|
7
|
+
import { type AiSlice } from "./ai-slice";
|
|
8
|
+
export type FreeBrowseStore = VolumeSlice & SurfaceSlice & DrawingSlice & ViewerSlice & SaveSlice & LocationSlice & AiSlice;
|
|
9
|
+
export declare const useFreeBrowseStore: import("zustand").UseBoundStore<Omit<import("zustand").StoreApi<FreeBrowseStore>, "persist" | "setState"> & {
|
|
10
|
+
setState(partial: FreeBrowseStore | Partial<FreeBrowseStore> | ((state: FreeBrowseStore) => FreeBrowseStore | Partial<FreeBrowseStore>), replace?: false | undefined): unknown;
|
|
11
|
+
setState(state: FreeBrowseStore | ((state: FreeBrowseStore) => FreeBrowseStore), replace: true): unknown;
|
|
12
|
+
persist: {
|
|
13
|
+
setOptions: (options: Partial<import("zustand/middleware").PersistOptions<FreeBrowseStore, unknown, unknown>>) => void;
|
|
14
|
+
clearStorage: () => void;
|
|
15
|
+
rehydrate: () => Promise<void> | void;
|
|
16
|
+
hasHydrated: () => boolean;
|
|
17
|
+
onHydrate: (fn: (state: FreeBrowseStore) => void) => () => void;
|
|
18
|
+
onFinishHydration: (fn: (state: FreeBrowseStore) => void) => () => void;
|
|
19
|
+
getOptions: () => Partial<import("zustand/middleware").PersistOptions<FreeBrowseStore, unknown, unknown>>;
|
|
20
|
+
};
|
|
21
|
+
}>;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { StateCreator } from "zustand";
|
|
2
|
+
import type { LocationData } from "./types";
|
|
3
|
+
export interface LocationSlice {
|
|
4
|
+
locationData: LocationData | null;
|
|
5
|
+
setLocationData: (data: LocationData | null) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare const createLocationSlice: StateCreator<LocationSlice>;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { ViewerOptions } from "./types.js";
|
|
2
|
+
import type { NiiVueEventSource, NiiVueSyncTarget } from "./niivue-sync";
|
|
3
|
+
/**
|
|
4
|
+
* Concrete NiiVueSyncTarget backed by the FreeBrowse Zustand store.
|
|
5
|
+
*
|
|
6
|
+
* This is the adapter that makes the store a *derived view* of niivue state:
|
|
7
|
+
* `registerNiiVueEvents(nv, createStoreSyncTarget(nv))` routes every relevant
|
|
8
|
+
* niivue event into the store, translating niivue-mono property names/values
|
|
9
|
+
* into FreeBrowse's store shape. Because both FreeBrowse's own command wrappers
|
|
10
|
+
* and external callers (e.g. `window.freebrowse.nv`) drive niivue through the
|
|
11
|
+
* same events, the UI follows either path identically.
|
|
12
|
+
*
|
|
13
|
+
* The volume/surface UI reads live `nv.volumes`/`nv.meshes` and re-renders on a
|
|
14
|
+
* version-counter bump, so those events bump the counter rather than mirroring
|
|
15
|
+
* a list into the store. Viewer options and location are mirrored into store
|
|
16
|
+
* slices.
|
|
17
|
+
*/
|
|
18
|
+
/** niivue reader surface this adapter needs beyond the event target. */
|
|
19
|
+
export type NiiVueReader = NiiVueEventSource & {
|
|
20
|
+
sliceType: number;
|
|
21
|
+
showRender: number;
|
|
22
|
+
crosshairWidth?: number;
|
|
23
|
+
crosshairGap?: number;
|
|
24
|
+
crosshairColor?: number[];
|
|
25
|
+
rulerWidth?: number;
|
|
26
|
+
isRulerVisible?: boolean;
|
|
27
|
+
volumeIsNearestInterpolation?: boolean;
|
|
28
|
+
volumeOutlineWidth?: number;
|
|
29
|
+
isColorbarVisible?: boolean;
|
|
30
|
+
isRadiological?: boolean;
|
|
31
|
+
secondaryDragMode?: number;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Read the instance's current settings as a viewerOptions patch — the
|
|
35
|
+
* nv -> store direction, for seeding the store from an instance the host
|
|
36
|
+
* built (so FreeBrowse never pushes its own defaults onto it). Only settings
|
|
37
|
+
* the instance actually reports (not undefined) are included, so a partial
|
|
38
|
+
* reader (or the test mock) leaves the rest of the store alone.
|
|
39
|
+
*/
|
|
40
|
+
export declare function readViewerFromNiiVue(nv: NiiVueReader): Partial<ViewerOptions>;
|
|
41
|
+
export declare function createStoreSyncTarget(nv: NiiVueReader): NiiVueSyncTarget;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* registerNiiVueEvents — the single event-driven sync bridge between a
|
|
3
|
+
* niivue-mono `NiiVue` instance and FreeBrowse's UI state.
|
|
4
|
+
*
|
|
5
|
+
* niivue-mono's controller extends `EventTarget` and emits a `CustomEvent` for
|
|
6
|
+
* every state mutation that goes through its API (setters + methods). By making
|
|
7
|
+
* FreeBrowse's store a *derived view* updated only from these events, both
|
|
8
|
+
* FreeBrowse's own command wrappers and external code (a developer holding
|
|
9
|
+
* `window.freebrowse.nv`) drive the UI through the identical path.
|
|
10
|
+
*
|
|
11
|
+
* Dedupe strategy: every flat scalar setter emits a generic
|
|
12
|
+
* `change {property, value}`, and a handful ALSO emit a specific event
|
|
13
|
+
* (`sliceTypeChange`, `azimuthElevationChange`, `drawingEnabled`,
|
|
14
|
+
* `penValueChanged`). We subscribe to `change` as the single source of truth for
|
|
15
|
+
* scalars and deliberately ignore those scalar-mirror events, so each scalar is
|
|
16
|
+
* handled exactly once. Specific events are used ONLY where `change` carries no
|
|
17
|
+
* equivalent: structural volume/mesh changes, drawing actions, location, etc.
|
|
18
|
+
*
|
|
19
|
+
* Phase 0: this is the dispatcher skeleton + its {@link NiiVueSyncTarget}
|
|
20
|
+
* contract, verified against the event-emitting mock. The concrete adapter that
|
|
21
|
+
* maps these intent callbacks onto the Zustand store (with niivue->store name
|
|
22
|
+
* and value translation) is added as hooks convert in Phase 2+.
|
|
23
|
+
*/
|
|
24
|
+
/** Minimal shape of a niivue instance this bridge needs. */
|
|
25
|
+
export interface NiiVueEventSource extends EventTarget {
|
|
26
|
+
volumes: unknown[];
|
|
27
|
+
meshes: unknown[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Intent-level sink for niivue state changes. Phase 2+ provides a concrete
|
|
31
|
+
* implementation backed by the Zustand store; tests provide spies.
|
|
32
|
+
*/
|
|
33
|
+
export interface NiiVueSyncTarget {
|
|
34
|
+
/** A flat viewer/scene scalar changed (niivue property name + new value). */
|
|
35
|
+
onViewerOptionChange(property: string, value: unknown): void;
|
|
36
|
+
/** A drawing-related scalar changed (pen value, opacity, colormap, ...). */
|
|
37
|
+
onDrawingOptionChange(property: string, value: unknown): void;
|
|
38
|
+
/** The volume list changed (add / remove / reorder / update) — re-read `volumes`. */
|
|
39
|
+
onVolumesChanged(volumes: unknown[]): void;
|
|
40
|
+
/** A single volume's properties changed, with the exact applied subset. */
|
|
41
|
+
onVolumeUpdated(index: number, changes: Record<string, unknown>): void;
|
|
42
|
+
/** The mesh list or a mesh/layer changed — re-read `meshes`. */
|
|
43
|
+
onSurfacesChanged(meshes: unknown[]): void;
|
|
44
|
+
/** Drawing state changed; `action` is create/close/undo/stroke/load/update. */
|
|
45
|
+
onDrawingChanged(action: string): void;
|
|
46
|
+
/** Crosshair moved (interaction or a location-emitting method). */
|
|
47
|
+
onLocationChange(location: unknown): void;
|
|
48
|
+
/** A document finished loading (volumes/meshes/scene already applied). */
|
|
49
|
+
onDocumentLoaded(): void;
|
|
50
|
+
/** A colormap was registered by name. */
|
|
51
|
+
onColormapAdded(name: string): void;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Subscribe `target` to all relevant events on `nv`. Returns a teardown that
|
|
55
|
+
* removes every listener (call it on unmount / instance replacement).
|
|
56
|
+
*/
|
|
57
|
+
export declare function registerNiiVueEvents(nv: NiiVueEventSource, target: NiiVueSyncTarget): () => void;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { StateCreator } from "zustand";
|
|
2
|
+
import type { SaveState } from "./types";
|
|
3
|
+
export interface SaveSlice {
|
|
4
|
+
saveDialogOpen: boolean;
|
|
5
|
+
saveState: SaveState;
|
|
6
|
+
setSaveDialogOpen: (open: boolean) => void;
|
|
7
|
+
setSaveState: (state: SaveState | ((prev: SaveState) => SaveState)) => void;
|
|
8
|
+
}
|
|
9
|
+
export declare const createSaveSlice: StateCreator<SaveSlice>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { StateCreator } from "zustand";
|
|
2
|
+
import type { SurfaceDetails } from "./types";
|
|
3
|
+
export interface SurfaceSlice {
|
|
4
|
+
surfaces: SurfaceDetails[];
|
|
5
|
+
currentSurfaceIndex: number | null;
|
|
6
|
+
surfaceToRemove: number | null;
|
|
7
|
+
selectedLayerIndex: number | null;
|
|
8
|
+
layerVersion: number;
|
|
9
|
+
setSurfaces: (surfaces: SurfaceDetails[] | ((prev: SurfaceDetails[]) => SurfaceDetails[])) => void;
|
|
10
|
+
setCurrentSurfaceIndex: (index: number | null) => void;
|
|
11
|
+
setSurfaceToRemove: (index: number | null) => void;
|
|
12
|
+
setSelectedLayerIndex: (index: number | null) => void;
|
|
13
|
+
incrementLayerVersion: () => void;
|
|
14
|
+
}
|
|
15
|
+
export declare const createSurfaceSlice: StateCreator<SurfaceSlice>;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { SerializeOptions } from "@niivue/niivue";
|
|
2
|
+
import type { DragMode } from "../components/drag-mode-selector.js";
|
|
3
|
+
export type SurfaceDetails = {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
visible: boolean;
|
|
7
|
+
opacity: number;
|
|
8
|
+
rgba255: [number, number, number, number];
|
|
9
|
+
shaderType: string;
|
|
10
|
+
};
|
|
11
|
+
export type ViewMode = "axial" | "coronal" | "sagittal" | "ACS" | "ACSR" | "render";
|
|
12
|
+
export type ViewerOptions = {
|
|
13
|
+
viewMode: ViewMode;
|
|
14
|
+
crosshairWidth: number;
|
|
15
|
+
crosshairGap: number;
|
|
16
|
+
crosshairVisible: boolean;
|
|
17
|
+
crosshairColor: [number, number, number, number];
|
|
18
|
+
rulerWidth: number;
|
|
19
|
+
rulerVisible: boolean;
|
|
20
|
+
interpolateVoxels: boolean;
|
|
21
|
+
dragMode: DragMode;
|
|
22
|
+
overlayOutlineWidth: number;
|
|
23
|
+
isColorbar: boolean;
|
|
24
|
+
isRadiologicalConvention: boolean;
|
|
25
|
+
sagittalNoseLeft: boolean;
|
|
26
|
+
};
|
|
27
|
+
export type DrawingOptions = {
|
|
28
|
+
enabled: boolean;
|
|
29
|
+
mode: "none" | "pen" | "wand";
|
|
30
|
+
penValue: number;
|
|
31
|
+
penFill: boolean;
|
|
32
|
+
penErases: boolean;
|
|
33
|
+
opacity: number;
|
|
34
|
+
colormap: string;
|
|
35
|
+
magicWand2dOnly: boolean;
|
|
36
|
+
magicWandMaxDistanceMM: number;
|
|
37
|
+
magicWandThresholdPercent: number;
|
|
38
|
+
filename: string;
|
|
39
|
+
};
|
|
40
|
+
export type LocationData = {
|
|
41
|
+
mm: [number, number, number];
|
|
42
|
+
voxels: Array<{
|
|
43
|
+
name: string;
|
|
44
|
+
voxel: [number, number, number];
|
|
45
|
+
value: number;
|
|
46
|
+
}>;
|
|
47
|
+
};
|
|
48
|
+
export type SaveVolumeState = {
|
|
49
|
+
enabled: boolean;
|
|
50
|
+
isExternal: boolean;
|
|
51
|
+
url: string;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* On-disk niivue Document encoding chosen at save time. Derived from niivue's
|
|
55
|
+
* own option type so a future upstream format is a compile error at the save
|
|
56
|
+
* dialog rather than a silent divergence.
|
|
57
|
+
*/
|
|
58
|
+
export type NvdFormat = NonNullable<SerializeOptions["format"]>;
|
|
59
|
+
export type SaveState = {
|
|
60
|
+
isDownloadMode: boolean;
|
|
61
|
+
document: {
|
|
62
|
+
enabled: boolean;
|
|
63
|
+
location: string;
|
|
64
|
+
/** Both are native niivue encodings: JSON is portable and diffable,
|
|
65
|
+
* CBOR is the compact binary default. */
|
|
66
|
+
format: NvdFormat;
|
|
67
|
+
};
|
|
68
|
+
volumes: SaveVolumeState[];
|
|
69
|
+
};
|