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.
Files changed (73) hide show
  1. package/README.md +134 -0
  2. package/dist-lib/index.js +21492 -0
  3. package/dist-lib/index.js.map +1 -0
  4. package/dist-lib/react.js +10790 -0
  5. package/dist-lib/react.js.map +1 -0
  6. package/dist-lib/style.css +1 -0
  7. package/dist-lib/types/components/canvas-area.d.ts +10 -0
  8. package/dist-lib/types/components/dialogs/imaging-upload-confirmation-dialog.d.ts +6 -0
  9. package/dist-lib/types/components/dialogs/remove-dialog.d.ts +6 -0
  10. package/dist-lib/types/components/dialogs/save-dialog.d.ts +14 -0
  11. package/dist-lib/types/components/dialogs/session-delete-confirmation-dialog.d.ts +6 -0
  12. package/dist-lib/types/components/dialogs/settings-dialog.d.ts +6 -0
  13. package/dist-lib/types/components/drag-mode-selector.d.ts +9 -0
  14. package/dist-lib/types/components/file-list.d.ts +12 -0
  15. package/dist-lib/types/components/footer.d.ts +1 -0
  16. package/dist-lib/types/components/freebrowse.d.ts +32 -0
  17. package/dist-lib/types/components/header.d.ts +6 -0
  18. package/dist-lib/types/components/image-canvas.d.ts +7 -0
  19. package/dist-lib/types/components/image-uploader.d.ts +6 -0
  20. package/dist-lib/types/components/sidebar.d.ts +56 -0
  21. package/dist-lib/types/components/tabs/ai-annotation-tab.d.ts +20 -0
  22. package/dist-lib/types/components/tabs/data-tab.d.ts +6 -0
  23. package/dist-lib/types/components/tabs/drawing-tab.d.ts +17 -0
  24. package/dist-lib/types/components/tabs/nvd-tab.d.ts +6 -0
  25. package/dist-lib/types/components/tabs/scene-details-tab.d.ts +22 -0
  26. package/dist-lib/types/components/tabs/surface-details-tab.d.ts +20 -0
  27. package/dist-lib/types/components/ui/button.d.ts +10 -0
  28. package/dist-lib/types/components/ui/checkbox.d.ts +4 -0
  29. package/dist-lib/types/components/ui/dialog.d.ts +15 -0
  30. package/dist-lib/types/components/ui/input.d.ts +5 -0
  31. package/dist-lib/types/components/ui/label.d.ts +4 -0
  32. package/dist-lib/types/components/ui/labeled-slider-with-input.d.ts +14 -0
  33. package/dist-lib/types/components/ui/radio-group.d.ts +5 -0
  34. package/dist-lib/types/components/ui/scroll-area.d.ts +5 -0
  35. package/dist-lib/types/components/ui/select.d.ts +5 -0
  36. package/dist-lib/types/components/ui/slider.d.ts +4 -0
  37. package/dist-lib/types/components/ui/tabs.d.ts +7 -0
  38. package/dist-lib/types/components/ui/toggle-group.d.ts +7 -0
  39. package/dist-lib/types/components/ui/toggle.d.ts +9 -0
  40. package/dist-lib/types/components/view-selector.d.ts +7 -0
  41. package/dist-lib/types/components/viewer-shell.d.ts +13 -0
  42. package/dist-lib/types/hooks/use-ai-capabilities.d.ts +1 -0
  43. package/dist-lib/types/hooks/use-ai-session.d.ts +9 -0
  44. package/dist-lib/types/hooks/use-drawing.d.ts +27 -0
  45. package/dist-lib/types/hooks/use-file-loading.d.ts +15 -0
  46. package/dist-lib/types/hooks/use-magic-wand.d.ts +15 -0
  47. package/dist-lib/types/hooks/use-mesh-layers.d.ts +21 -0
  48. package/dist-lib/types/hooks/use-save.d.ts +12 -0
  49. package/dist-lib/types/hooks/use-surfaces.d.ts +16 -0
  50. package/dist-lib/types/hooks/use-viewer-options.d.ts +42 -0
  51. package/dist-lib/types/hooks/use-volumes.d.ts +39 -0
  52. package/dist-lib/types/lib/confirmations.d.ts +4 -0
  53. package/dist-lib/types/lib/default-niivue-options.d.ts +16 -0
  54. package/dist-lib/types/lib/deployment-config.d.ts +66 -0
  55. package/dist-lib/types/lib/niivue-helpers.d.ts +27 -0
  56. package/dist-lib/types/lib/nvd-volume-urls.d.ts +26 -0
  57. package/dist-lib/types/lib/random-session-name.d.ts +1 -0
  58. package/dist-lib/types/lib/utils.d.ts +2 -0
  59. package/dist-lib/types/lib.d.ts +16 -0
  60. package/dist-lib/types/mount.d.ts +49 -0
  61. package/dist-lib/types/react.d.ts +10 -0
  62. package/dist-lib/types/store/ai-slice.d.ts +25 -0
  63. package/dist-lib/types/store/drawing-slice.d.ts +7 -0
  64. package/dist-lib/types/store/index.d.ts +21 -0
  65. package/dist-lib/types/store/location-slice.d.ts +7 -0
  66. package/dist-lib/types/store/niivue-store-sync.d.ts +41 -0
  67. package/dist-lib/types/store/niivue-sync.d.ts +57 -0
  68. package/dist-lib/types/store/save-slice.d.ts +9 -0
  69. package/dist-lib/types/store/surface-slice.d.ts +15 -0
  70. package/dist-lib/types/store/types.d.ts +69 -0
  71. package/dist-lib/types/store/viewer-slice.d.ts +31 -0
  72. package/dist-lib/types/store/volume-slice.d.ts +13 -0
  73. 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,2 @@
1
+ import { type ClassValue } from "clsx";
2
+ export declare function cn(...inputs: ClassValue[]): 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
+ };