From c0cfc9138408d2942218792df9e5c25373e4841f Mon Sep 17 00:00:00 2001 From: Axel Bocciarelli Date: Thu, 30 May 2024 16:03:30 +0200 Subject: [PATCH] Reduce delay when slicing with h5wasm through viewer config --- packages/app/README.md | 29 +++++++++++++++++++ .../src/dimension-mapper/SlicingSlider.tsx | 5 ++-- packages/app/src/providers/DataProvider.tsx | 9 +++++- .../src/providers/h5grove/H5GroveProvider.tsx | 20 +++++++++++-- .../app/src/providers/hsds/HsdsProvider.tsx | 27 +++++++++++++++-- .../app/src/providers/mock/MockProvider.tsx | 19 ++++++++++-- packages/app/src/providers/models.ts | 4 +++ packages/h5wasm/README.md | 15 ++++++++++ packages/h5wasm/src/H5WasmProvider.tsx | 20 +++++++++++-- .../src/local/H5WasmLocalFileProvider.tsx | 19 ++++++++++-- 10 files changed, 154 insertions(+), 13 deletions(-) diff --git a/packages/app/README.md b/packages/app/README.md index 95844d8b3..ebc40f6bc 100644 --- a/packages/app/README.md +++ b/packages/app/README.md @@ -286,6 +286,17 @@ specific export scenarios. In this case, or if you don't provide a function at all, `H5GroveProvider` falls back to generating URLs based on the `/data` endpoint and `format` query param. +#### `viewerConfig: Partial` (optional) + +Customise the behaviour of the viewer: + +- `slicingTiming: number` – debounce delay for the dimension slicing sliders + (default: 250) + + Should be high-enough to not overload your h5grove server and to give enough + time for the data to reach the client. This depends on the size of your users' + datasets, on your users' bandwidths, and on the performance of your server. + #### `key?: Key` (optional) If the content of the current file changes and you want to ensure that the @@ -359,6 +370,17 @@ See this time, so if you don't provide your own, the export menu will remain disabled in the toolbar. +#### `viewerConfig: Partial` (optional) + +Customise the behaviour of the viewer: + +- `slicingTiming: number` – debounce delay for the dimension slicing sliders + (default: 250) + + Should be high-enough to not overload your HSDS server and to give enough time + for the data to reach the client. This depends on the size of your users' + datasets, on your users' bandwidths, and on the performance of your server. + #### `key?: Key` (optional) See @@ -382,6 +404,13 @@ See `MockProvider` provides a very basic fallback implementation of `getExportURL` that can generate only client-side CSV exports of 1D datasets. +#### `viewerConfig: Partial` (optional) + +Customise the behaviour of the viewer: + +- `slicingTiming: number` – debounce delay for the dimension slicing sliders + (default: 20) + ### Utilities #### `getFeedbackMailto` diff --git a/packages/app/src/dimension-mapper/SlicingSlider.tsx b/packages/app/src/dimension-mapper/SlicingSlider.tsx index 4cffe153b..85adfbafe 100644 --- a/packages/app/src/dimension-mapper/SlicingSlider.tsx +++ b/packages/app/src/dimension-mapper/SlicingSlider.tsx @@ -2,11 +2,11 @@ import { useDebouncedCallback, useMeasure } from '@react-hookz/web'; import { useState } from 'react'; import ReactSlider from 'react-slider'; +import { useViewerConfig } from '../providers/DataProvider'; import styles from './SlicingSlider.module.css'; const ID = 'h5w-slider'; const MIN_HEIGHT_PER_MARK = 25; -const SLICING_DEBOUNCE_DELAY = 250; interface Props { dimension: number; @@ -17,12 +17,13 @@ interface Props { function SlicingSlider(props: Props) { const { dimension, maxIndex, initialValue, onChange } = props; + const { slicingTiming } = useViewerConfig(); const [value, setValue] = useState(initialValue); const onDebouncedChange = useDebouncedCallback( onChange, [onChange], - SLICING_DEBOUNCE_DELAY, + slicingTiming, ); const [containerSize, containerRef] = useMeasure(); diff --git a/packages/app/src/providers/DataProvider.tsx b/packages/app/src/providers/DataProvider.tsx index fa5242749..fbf943087 100644 --- a/packages/app/src/providers/DataProvider.tsx +++ b/packages/app/src/providers/DataProvider.tsx @@ -13,6 +13,7 @@ import type { EntitiesStore, ProgressCallback, ValuesStore, + ViewerConfig, } from './models'; export interface DataContextValue { @@ -21,6 +22,7 @@ export interface DataContextValue { entitiesStore: EntitiesStore; valuesStore: ValuesStore; attrValuesStore: AttrValuesStore; + viewerConfig: ViewerConfig; // Undocumented getExportURL?: DataProviderApi['getExportURL']; @@ -34,13 +36,17 @@ const DataContext = createContext({} as DataContextValue); export function useDataContext() { return useContext(DataContext); } +export function useViewerConfig() { + return useContext(DataContext).viewerConfig; +} interface Props { api: DataProviderApi; + viewerConfig: ViewerConfig; } function DataProvider(props: PropsWithChildren) { - const { api, children } = props; + const { api, viewerConfig, children } = props; const entitiesStore = useMemo(() => { const childCache = new Map>(); @@ -110,6 +116,7 @@ function DataProvider(props: PropsWithChildren) { entitiesStore, valuesStore, attrValuesStore, + viewerConfig, getExportURL: api.getExportURL?.bind(api), addProgressListener: api.addProgressListener.bind(api), removeProgressListener: api.removeProgressListener.bind(api), diff --git a/packages/app/src/providers/h5grove/H5GroveProvider.tsx b/packages/app/src/providers/h5grove/H5GroveProvider.tsx index 59b7768ba..20b252516 100644 --- a/packages/app/src/providers/h5grove/H5GroveProvider.tsx +++ b/packages/app/src/providers/h5grove/H5GroveProvider.tsx @@ -4,24 +4,40 @@ import { useMemo } from 'react'; import type { DataProviderApi } from '../api'; import DataProvider from '../DataProvider'; +import type { ViewerConfig } from '../models'; import { H5GroveApi } from './h5grove-api'; +const DEFAULT_VIEWER_CONFIG: ViewerConfig = { + slicingTiming: 250, +}; + interface Props { url: string; filepath: string; axiosConfig?: AxiosRequestConfig; getExportURL?: DataProviderApi['getExportURL']; + viewerConfig?: Partial; } function H5GroveProvider(props: PropsWithChildren) { - const { url, filepath, axiosConfig, getExportURL, children } = props; + const { url, filepath, axiosConfig, getExportURL, viewerConfig, children } = + props; const api = useMemo( () => new H5GroveApi(url, filepath, axiosConfig, getExportURL), [filepath, url, axiosConfig, getExportURL], ); - return {children}; + const mergedViewerConfig = useMemo( + () => ({ ...DEFAULT_VIEWER_CONFIG, ...viewerConfig }), + [viewerConfig], + ); + + return ( + + {children} + + ); } export default H5GroveProvider; diff --git a/packages/app/src/providers/hsds/HsdsProvider.tsx b/packages/app/src/providers/hsds/HsdsProvider.tsx index 11069c938..900998377 100644 --- a/packages/app/src/providers/hsds/HsdsProvider.tsx +++ b/packages/app/src/providers/hsds/HsdsProvider.tsx @@ -3,25 +3,48 @@ import { useMemo } from 'react'; import type { DataProviderApi } from '../api'; import DataProvider from '../DataProvider'; +import type { ViewerConfig } from '../models'; import { HsdsApi } from './hsds-api'; +const DEFAULT_VIEWER_CONFIG: ViewerConfig = { + slicingTiming: 250, +}; + interface Props { url: string; username: string; password: string; filepath: string; getExportURL?: DataProviderApi['getExportURL']; + viewerConfig?: Partial; } function HsdsProvider(props: PropsWithChildren) { - const { url, username, password, filepath, getExportURL, children } = props; + const { + url, + username, + password, + filepath, + getExportURL, + viewerConfig, + children, + } = props; const api = useMemo( () => new HsdsApi(url, username, password, filepath, getExportURL), [filepath, password, url, username, getExportURL], ); - return {children}; + const mergedViewerConfig = useMemo( + () => ({ ...DEFAULT_VIEWER_CONFIG, ...viewerConfig }), + [viewerConfig], + ); + + return ( + + {children} + + ); } export default HsdsProvider; diff --git a/packages/app/src/providers/mock/MockProvider.tsx b/packages/app/src/providers/mock/MockProvider.tsx index 96e73798d..71112c166 100644 --- a/packages/app/src/providers/mock/MockProvider.tsx +++ b/packages/app/src/providers/mock/MockProvider.tsx @@ -3,18 +3,33 @@ import { useMemo } from 'react'; import type { DataProviderApi } from '../api'; import DataProvider from '../DataProvider'; +import type { ViewerConfig } from '../models'; import { MockApi } from './mock-api'; +const DEFAULT_VIEWER_CONFIG: ViewerConfig = { + slicingTiming: 20, +}; + interface Props { getExportURL?: DataProviderApi['getExportURL']; + viewerConfig?: Partial; } function MockProvider(props: PropsWithChildren) { - const { getExportURL, children } = props; + const { getExportURL, viewerConfig, children } = props; const api = useMemo(() => new MockApi(getExportURL), [getExportURL]); - return {children}; + const mergedViewerConfig = useMemo( + () => ({ ...DEFAULT_VIEWER_CONFIG, ...viewerConfig }), + [viewerConfig], + ); + + return ( + + {children} + + ); } export default MockProvider; diff --git a/packages/app/src/providers/models.ts b/packages/app/src/providers/models.ts index 16cbad752..277b24acc 100644 --- a/packages/app/src/providers/models.ts +++ b/packages/app/src/providers/models.ts @@ -29,6 +29,10 @@ export interface AttrValuesStore extends FetchStore { getSingle: (entity: Entity, attrName: AttrName) => unknown; } +export interface ViewerConfig { + slicingTiming: number; +} + export type ExportFormat = 'json' | 'csv' | 'npy' | 'tiff'; export type ExportURL = URL | (() => Promise) | undefined; diff --git a/packages/h5wasm/README.md b/packages/h5wasm/README.md index d687ed697..70e2a1713 100644 --- a/packages/h5wasm/README.md +++ b/packages/h5wasm/README.md @@ -184,6 +184,16 @@ async function getPlugin(name: Plugin): Promise { } ``` +#### `viewerConfig: Partial` (optional) + +Customise the behaviour of the viewer: + +- `slicingTiming: number` – debounce delay for the dimension slicing sliders + (default: 20) + + You may want to choose a higher number to reduce lag when viewing very large + datasets. + ### `H5WasmProvider` - `filename: string` (required) - the name of the file @@ -212,3 +222,8 @@ See See [`H5WasmLocalFileProvider#getPlugin`](#getplugin-name-plugin--promisearraybuffer--undefined) + +#### `viewerConfig: Partial` (optional) + +See +[`H5WasmLocalFileProvider#viewerConfig`](#viewerconfig-partialviewerconfig-optional) diff --git a/packages/h5wasm/src/H5WasmProvider.tsx b/packages/h5wasm/src/H5WasmProvider.tsx index 45980518f..4b71a46e5 100644 --- a/packages/h5wasm/src/H5WasmProvider.tsx +++ b/packages/h5wasm/src/H5WasmProvider.tsx @@ -1,20 +1,27 @@ import type { DataProviderApi } from '@h5web/app'; import { DataProvider } from '@h5web/app'; +import type { ViewerConfig } from '@h5web/app/src/providers/models'; import type { PropsWithChildren } from 'react'; import { useMemo, useRef } from 'react'; import { H5WasmApi } from './h5wasm-api'; import type { Plugin } from './models'; +const DEFAULT_VIEWER_CONFIG: ViewerConfig = { + slicingTiming: 20, +}; + interface Props { filename: string; buffer: ArrayBuffer; getExportURL?: DataProviderApi['getExportURL']; getPlugin?: (name: Plugin) => Promise; + viewerConfig?: Partial; } function H5WasmProvider(props: PropsWithChildren) { - const { filename, buffer, getExportURL, getPlugin, children } = props; + const { filename, buffer, getExportURL, getPlugin, viewerConfig, children } = + props; const prevApiRef = useRef(); @@ -27,7 +34,16 @@ function H5WasmProvider(props: PropsWithChildren) { return newApi; }, [buffer, filename, getExportURL, getPlugin]); - return {children}; + const mergedViewerConfig = useMemo( + () => ({ ...DEFAULT_VIEWER_CONFIG, ...viewerConfig }), + [viewerConfig], + ); + + return ( + + {children} + + ); } export default H5WasmProvider; diff --git a/packages/h5wasm/src/local/H5WasmLocalFileProvider.tsx b/packages/h5wasm/src/local/H5WasmLocalFileProvider.tsx index 2ddd79ff0..3e249b259 100644 --- a/packages/h5wasm/src/local/H5WasmLocalFileProvider.tsx +++ b/packages/h5wasm/src/local/H5WasmLocalFileProvider.tsx @@ -1,19 +1,25 @@ import type { DataProviderApi } from '@h5web/app'; import { DataProvider } from '@h5web/app'; +import type { ViewerConfig } from '@h5web/app/src/providers/models'; import type { PropsWithChildren } from 'react'; import { useMemo, useRef } from 'react'; import type { Plugin } from '../models'; import { H5WasmLocalFileApi } from './h5wasm-local-file-api'; +const DEFAULT_VIEWER_CONFIG: ViewerConfig = { + slicingTiming: 20, +}; + interface Props { file: File; getExportURL?: DataProviderApi['getExportURL']; getPlugin?: (name: Plugin) => Promise; + viewerConfig?: Partial; } function H5WasmLocalFileProvider(props: PropsWithChildren) { - const { file, getExportURL, getPlugin, children } = props; + const { file, getExportURL, getPlugin, viewerConfig, children } = props; const prevApiRef = useRef(); @@ -26,7 +32,16 @@ function H5WasmLocalFileProvider(props: PropsWithChildren) { return newApi; }, [file, getExportURL, getPlugin]); - return {children}; + const mergedViewerConfig = useMemo( + () => ({ ...DEFAULT_VIEWER_CONFIG, ...viewerConfig }), + [viewerConfig], + ); + + return ( + + {children} + + ); } export default H5WasmLocalFileProvider;