diff --git a/eslint.config.base.js b/eslint.config.base.js index ea98b80e8..bc8b40101 100644 --- a/eslint.config.base.js +++ b/eslint.config.base.js @@ -822,6 +822,7 @@ export function createConfig(opts = {}) { '@typescript-eslint/no-unnecessary-parameter-property-assignment': 'warn', '@typescript-eslint/no-unnecessary-qualifier': 'warn', + '@typescript-eslint/no-unnecessary-type-arguments': 'warn', // downgrade '@typescript-eslint/no-unsafe-argument': 'warn', '@typescript-eslint/no-unsafe-assignment': 'off', // too tricky to fix when `any` is declared externally (JSON.parse, Array.isArray, etc.) '@typescript-eslint/no-unused-vars': [ diff --git a/packages/app/src/vis-packs/core/hooks.ts b/packages/app/src/vis-packs/core/hooks.ts index 1cd2a34f7..1f6b36b2c 100644 --- a/packages/app/src/vis-packs/core/hooks.ts +++ b/packages/app/src/vis-packs/core/hooks.ts @@ -2,13 +2,14 @@ import { createMemo } from '@h5web/shared/createMemo'; import { assertDatasetValue, isDefined } from '@h5web/shared/guards'; import { type ArrayShape, + type ArrayValue, type Dataset, type ScalarShape, type Value, } from '@h5web/shared/hdf5-models'; import { type NumArray } from '@h5web/shared/vis-models'; import { castArray } from '@h5web/shared/vis-utils'; -import { type NdArray, type TypedArray } from 'ndarray'; +import { type NdArray } from 'ndarray'; import { useMemo } from 'react'; import { type DimensionMapping } from '../../dimension-mapper/models'; @@ -100,29 +101,29 @@ export function useDatasetValues>( }); } -export function useBaseArray( +export function useBaseArray( value: T, rawDims: number[], -): T extends unknown[] | TypedArray ? NdArray : undefined; +): T extends ArrayValue ? NdArray : undefined; export function useBaseArray( - value: unknown[] | TypedArray | undefined, + value: ArrayValue | undefined, rawDims: number[], -): NdArray | undefined { +): NdArray | undefined { return useMemo(() => getBaseArray(value, rawDims), [value, rawDims]); } -export function useMappedArray( +export function useMappedArray( value: T, dims: number[], mapping: DimensionMapping, -): T extends unknown[] | TypedArray ? NdArray : undefined; +): T extends ArrayValue ? NdArray : undefined; export function useMappedArray( - value: unknown[] | TypedArray | undefined, + value: ArrayValue | undefined, dims: number[], mapping: DimensionMapping, -): NdArray | undefined { +): NdArray | undefined { const baseArray = useBaseArray(value, dims); return useMemo(() => applyMapping(baseArray, mapping), [baseArray, mapping]); diff --git a/packages/app/src/vis-packs/core/utils.ts b/packages/app/src/vis-packs/core/utils.ts index 1e6ef9545..c270d1541 100644 --- a/packages/app/src/vis-packs/core/utils.ts +++ b/packages/app/src/vis-packs/core/utils.ts @@ -11,7 +11,7 @@ import { type NumArray, } from '@h5web/shared/vis-models'; import { createArrayFromView } from '@h5web/shared/vis-utils'; -import ndarray, { type NdArray, type TypedArray } from 'ndarray'; +import ndarray, { type NdArray } from 'ndarray'; import { type DimensionMapping } from '../../dimension-mapper/models'; import { isAxis } from '../../dimension-mapper/utils'; @@ -32,29 +32,27 @@ export const INTERACTIONS_WITH_AXIAL_ZOOM = [ { shortcut: 'Ctrl+Shift+Drag', description: 'Select to zoom in Y' }, ]; -export function getBaseArray( +export function getBaseArray( value: T, rawDims: number[], -): T extends unknown[] | TypedArray ? NdArray : undefined; +): T extends ArrayValue ? NdArray : undefined; export function getBaseArray( - value: unknown[] | TypedArray | undefined, + value: ArrayValue | undefined, rawDims: number[], -): NdArray | undefined { +): NdArray | undefined { return value && ndarray(value, rawDims); } -export function applyMapping< - T extends NdArray | undefined, ->( +export function applyMapping | undefined>( baseArray: T, mapping: (number | Axis | ':')[], -): T extends NdArray ? T : undefined; +): T extends NdArray ? T : undefined; export function applyMapping( - baseArray: NdArray | undefined, + baseArray: NdArray | undefined, mapping: (number | Axis | ':')[], -): NdArray | undefined { +): NdArray | undefined { if (!baseArray) { return undefined; } @@ -95,12 +93,16 @@ export function getImageInteractions(keepRatio: boolean): InteractionInfo[] { return keepRatio ? BASE_INTERACTIONS : INTERACTIONS_WITH_AXIAL_ZOOM; } +function isBoolArray(val: ArrayValue): val is boolean[] { + return Array.isArray(val) && typeof val[0] === 'boolean'; +} + export function toNumArray(arr: ArrayValue): NumArray { - if (typeof arr[0] === 'boolean') { + if (isBoolArray(arr)) { return arr.map((val) => (val ? 1 : 0)); } - return arr as NumArray; + return arr; } const TYPE_STRINGS: Record = { diff --git a/packages/shared/src/guards.ts b/packages/shared/src/guards.ts index 132e79518..38be36593 100644 --- a/packages/shared/src/guards.ts +++ b/packages/shared/src/guards.ts @@ -142,14 +142,6 @@ export function isTypedArray(val: unknown): val is TypedArray { ); } -export function assertArrayOrTypedArray( - val: unknown, -): asserts val is unknown[] | TypedArray { - if (!Array.isArray(val) && !isTypedArray(val)) { - throw new TypeError('Expected array or typed array'); - } -} - export function isGroup(entity: Entity): entity is Group { return entity.kind === EntityKind.Group; } @@ -431,7 +423,7 @@ export function isComplexValue( function assertPrimitiveValue( type: DType, value: unknown, -): asserts value is Primitive { +): asserts value is Primitive { if (isNumericType(type)) { assertNum(value); } else if (isStringType(type)) { @@ -457,7 +449,9 @@ export function assertDatasetValue>( if (hasScalarShape(dataset)) { assertPrimitiveValue(type, value); } else { - assertArrayOrTypedArray(value); + if (!Array.isArray(value) && !isTypedArray(value)) { + throw new TypeError('Expected array or typed array'); + } if (value.length > 0) { assertPrimitiveValue(type, value[0]); diff --git a/packages/shared/src/hdf5-models.ts b/packages/shared/src/hdf5-models.ts index c695f6366..955a35066 100644 --- a/packages/shared/src/hdf5-models.ts +++ b/packages/shared/src/hdf5-models.ts @@ -207,21 +207,19 @@ export interface UnknownType { /* ----------------- */ /* ----- VALUE ----- */ -export type Primitive = T extends NumericType | EnumType - ? number +export type Primitive = T extends NumericLikeType + ? number | (T extends BooleanType ? boolean : never) // let providers choose how to return booleans : T extends StringType ? string - : T extends BooleanType - ? number | boolean // let providers choose - : T extends ComplexType - ? H5WebComplex - : T extends CompoundType - ? Primitive[] - : unknown; - -export type ArrayValue = - | Primitive[] - | (T extends NumericLikeType ? TypedArray : never); + : T extends ComplexType + ? H5WebComplex + : T extends CompoundType + ? Primitive[] + : unknown; + +export type ArrayValue = T extends NumericLikeType + ? TypedArray | number[] | (T extends BooleanType ? boolean[] : never) // don't use `Primitive` to avoid `(number | boolean)[]` + : Primitive[]; export type Value = D extends Dataset diff --git a/packages/shared/src/mock-values.ts b/packages/shared/src/mock-values.ts index 49985fc71..ce1da3f7d 100644 --- a/packages/shared/src/mock-values.ts +++ b/packages/shared/src/mock-values.ts @@ -2,7 +2,7 @@ import { range } from 'd3-array'; import ndarray, { type NdArray } from 'ndarray'; -import { type ArrayValue, type DType, type H5WebComplex } from './hdf5-models'; +import { type ArrayValue, type H5WebComplex } from './hdf5-models'; import { cplx } from './hdf5-utils'; const range1 = () => range(-20, 21); @@ -276,4 +276,4 @@ export const mockValues = { ndarray(range1().map((val) => Math.cos((val * 3.14) / 40))), Y_scatter: () => ndarray(range1().map((_, i) => ((i % 10) + (i % 5)) / 123_456)), -} satisfies Record NdArray>>; +} satisfies Record NdArray>; diff --git a/packages/shared/src/vis-utils.ts b/packages/shared/src/vis-utils.ts index f1ae4999e..8dde43342 100644 --- a/packages/shared/src/vis-utils.ts +++ b/packages/shared/src/vis-utils.ts @@ -1,9 +1,10 @@ import { format } from 'd3-format'; -import ndarray, { type NdArray, type TypedArray } from 'ndarray'; +import ndarray, { type NdArray } from 'ndarray'; import { assign } from 'ndarray-ops'; import { assertLength, isNdArray } from './guards'; import { + type ArrayValue, type BooleanType, type ComplexType, type EnumType, @@ -101,7 +102,7 @@ export function toTypedNdArray( return ndarray(Constructor.from(arr.data) as InstanceType, arr.shape); } -export function createArrayFromView( +export function createArrayFromView( view: NdArray, ): NdArray { const { data, size, shape } = view;