diff --git a/apps/demo/src/index.tsx b/apps/demo/src/index.tsx index 6ba3ccb19..4f8616b32 100644 --- a/apps/demo/src/index.tsx +++ b/apps/demo/src/index.tsx @@ -1,11 +1,13 @@ import './styles.css'; // global styles -import { assertNonNull } from '@h5web/app'; +import { assertNonNull, enableBigIntSerialization } from '@h5web/app'; import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import DemoApp from './DemoApp'; +enableBigIntSerialization(); // for `RawVis` and `MetadataViewer` + const rootElem = document.querySelector('#root'); assertNonNull(rootElem); diff --git a/cypress/snapshots/app.cy.ts/bgr_image.snap.png b/cypress/snapshots/app.cy.ts/bgr_image.snap.png index 8a17a115f..4346173ad 100644 Binary files a/cypress/snapshots/app.cy.ts/bgr_image.snap.png and b/cypress/snapshots/app.cy.ts/bgr_image.snap.png differ diff --git a/cypress/snapshots/app.cy.ts/compound_1D.snap.png b/cypress/snapshots/app.cy.ts/compound_1D.snap.png index ca3cdda42..342167a2c 100644 Binary files a/cypress/snapshots/app.cy.ts/compound_1D.snap.png and b/cypress/snapshots/app.cy.ts/compound_1D.snap.png differ diff --git a/cypress/snapshots/app.cy.ts/heatmap_2D.snap.png b/cypress/snapshots/app.cy.ts/heatmap_2D.snap.png index 2d45f5029..4e277ab11 100644 Binary files a/cypress/snapshots/app.cy.ts/heatmap_2D.snap.png and b/cypress/snapshots/app.cy.ts/heatmap_2D.snap.png differ diff --git a/cypress/snapshots/app.cy.ts/heatmap_2D_complex.snap.png b/cypress/snapshots/app.cy.ts/heatmap_2D_complex.snap.png index cfcaafd26..97e784bcc 100644 Binary files a/cypress/snapshots/app.cy.ts/heatmap_2D_complex.snap.png and b/cypress/snapshots/app.cy.ts/heatmap_2D_complex.snap.png differ diff --git a/cypress/snapshots/app.cy.ts/heatmap_2D_inverted_cmap.snap.png b/cypress/snapshots/app.cy.ts/heatmap_2D_inverted_cmap.snap.png index bbd2513af..b32c88e40 100644 Binary files a/cypress/snapshots/app.cy.ts/heatmap_2D_inverted_cmap.snap.png and b/cypress/snapshots/app.cy.ts/heatmap_2D_inverted_cmap.snap.png differ diff --git a/cypress/snapshots/app.cy.ts/heatmap_4d_default.snap.png b/cypress/snapshots/app.cy.ts/heatmap_4d_default.snap.png index c9b65f119..73a0d1ccb 100644 Binary files a/cypress/snapshots/app.cy.ts/heatmap_4d_default.snap.png and b/cypress/snapshots/app.cy.ts/heatmap_4d_default.snap.png differ diff --git a/cypress/snapshots/app.cy.ts/heatmap_4d_remapped.snap.png b/cypress/snapshots/app.cy.ts/heatmap_4d_remapped.snap.png index 1ce19b668..79c364a87 100644 Binary files a/cypress/snapshots/app.cy.ts/heatmap_4d_remapped.snap.png and b/cypress/snapshots/app.cy.ts/heatmap_4d_remapped.snap.png differ diff --git a/cypress/snapshots/app.cy.ts/heatmap_4d_sliced.snap.png b/cypress/snapshots/app.cy.ts/heatmap_4d_sliced.snap.png index 96bc89c2e..1608b88ed 100644 Binary files a/cypress/snapshots/app.cy.ts/heatmap_4d_sliced.snap.png and b/cypress/snapshots/app.cy.ts/heatmap_4d_sliced.snap.png differ diff --git a/cypress/snapshots/app.cy.ts/heatmap_4d_zeros.snap.png b/cypress/snapshots/app.cy.ts/heatmap_4d_zeros.snap.png index fd6cee207..efde36009 100644 Binary files a/cypress/snapshots/app.cy.ts/heatmap_4d_zeros.snap.png and b/cypress/snapshots/app.cy.ts/heatmap_4d_zeros.snap.png differ diff --git a/cypress/snapshots/app.cy.ts/heatmap_domain.snap.png b/cypress/snapshots/app.cy.ts/heatmap_domain.snap.png index f7d944e77..785b1099f 100644 Binary files a/cypress/snapshots/app.cy.ts/heatmap_domain.snap.png and b/cypress/snapshots/app.cy.ts/heatmap_domain.snap.png differ diff --git a/cypress/snapshots/app.cy.ts/line_1D.snap.png b/cypress/snapshots/app.cy.ts/line_1D.snap.png index fe8980f68..0ffae626b 100644 Binary files a/cypress/snapshots/app.cy.ts/line_1D.snap.png and b/cypress/snapshots/app.cy.ts/line_1D.snap.png differ diff --git a/cypress/snapshots/app.cy.ts/line_complex_1D.snap.png b/cypress/snapshots/app.cy.ts/line_complex_1D.snap.png index 23b316761..f889224d5 100644 Binary files a/cypress/snapshots/app.cy.ts/line_complex_1D.snap.png and b/cypress/snapshots/app.cy.ts/line_complex_1D.snap.png differ diff --git a/cypress/snapshots/app.cy.ts/matrix_1D.snap.png b/cypress/snapshots/app.cy.ts/matrix_1D.snap.png index 731e6168f..3be70aae1 100644 Binary files a/cypress/snapshots/app.cy.ts/matrix_1D.snap.png and b/cypress/snapshots/app.cy.ts/matrix_1D.snap.png differ diff --git a/cypress/snapshots/app.cy.ts/rgb_image.snap.png b/cypress/snapshots/app.cy.ts/rgb_image.snap.png index f3e9d8adb..49e0aa862 100644 Binary files a/cypress/snapshots/app.cy.ts/rgb_image.snap.png and b/cypress/snapshots/app.cy.ts/rgb_image.snap.png differ diff --git a/packages/app/README.md b/packages/app/README.md index eae05d487..18097ba89 100644 --- a/packages/app/README.md +++ b/packages/app/README.md @@ -389,6 +389,44 @@ import { getFeedbackMailto } from '@h5web/app'; }} /> ``` +#### `enableBigIntSerialization` + +Invoke this function before rendering your application to allow the _Raw_ +visualization and metadata inspector to serialize and display +[big integers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#bigint_type): + +```jsx +enableBigIntSerialization(); +createRoot(document.querySelector('#root')).render(); +``` + +This is recommended if you work with a provider that supports 64-bit integers — +i.e. one that may provide dataset and attribute values that include primitive +`bigint` numbers — currently only [`MockProvider`](#mockprovider). + +The _Raw_ visualization and metadata inspector rely on `JSON.stringify()` to +render dataset and attribute values. By default, `JSON.stringify()` does not +know how to serialize `bigint` numbers and throws an error if it encounters one. +`enableBigIntSerialization()` teaches `JSON.stringify()` to convert big integers +to strings: + +```js +> JSON.stringify(123n); +TypeError: Do not know how to serialize a BigInt + +> enableBigIntSerialization(); +> JSON.stringify(123n); +"123n" +``` + +> The `n` suffix (i.e. the same suffix used for `bigint` literals as +> demonstrated above) is added to help distinguish big integer strings from +> other strings. + +> If you're application already implements `bigint` serialization, you don't +> need to call `enableBigIntSerialization()`. Doing so would override the +> existing implementation, which might have unintended effects. + ### Context The viewer component `App` communicates with its wrapping data provider through diff --git a/packages/app/src/index.ts b/packages/app/src/index.ts index 343490d05..d01b0c98f 100644 --- a/packages/app/src/index.ts +++ b/packages/app/src/index.ts @@ -6,6 +6,7 @@ export { default as MockProvider } from './providers/mock/MockProvider'; export { default as HsdsProvider } from './providers/hsds/HsdsProvider'; export { default as H5GroveProvider } from './providers/h5grove/H5GroveProvider'; +export { enableBigIntSerialization } from './utils'; export { getFeedbackMailto } from './breadcrumbs/utils'; export type { FeedbackContext } from './breadcrumbs/models'; export type { ExportFormat, ExportURL } from './providers/models'; diff --git a/packages/app/src/providers/mock/mock-file.ts b/packages/app/src/providers/mock/mock-file.ts index c6862940d..20a46495c 100644 --- a/packages/app/src/providers/mock/mock-file.ts +++ b/packages/app/src/providers/mock/mock-file.ts @@ -55,6 +55,11 @@ export function makeMockFile(): GroupWithChildren { scalar('raw_large', undefined), // generated dynamically by `MockProvider` dataset('raw_png', opaqueType(), [], PNG_RED_DOT), scalar('scalar_num', 0, { attributes: [scalarAttr('attr', 0)] }), + scalar('scalar_bigint', BigInt(Number.MAX_SAFE_INTEGER) + 1n, { + attributes: [ + scalarAttr('attr', BigInt(Number.MAX_SAFE_INTEGER) + 1n), + ], + }), scalar('scalar_str', 'foo', { attributes: [scalarAttr('attr', 'foo')], }), @@ -108,6 +113,7 @@ export function makeMockFile(): GroupWithChildren { group('nD_datasets', [ array('oneD_linear'), array('oneD'), + array('oneD_bigint'), array('oneD_cplx'), array('oneD_compound', { type: printableCompoundType({ @@ -121,6 +127,7 @@ export function makeMockFile(): GroupWithChildren { array('oneD_bool'), array('oneD_enum', { type: enumType(uintType(8), ENUM_MAPPING) }), array('twoD'), + array('twoD_bigint'), array('twoD_cplx'), array('twoD_compound', { type: printableCompoundType({ diff --git a/packages/app/src/utils.ts b/packages/app/src/utils.ts index 472976fbc..7090cc9c2 100644 --- a/packages/app/src/utils.ts +++ b/packages/app/src/utils.ts @@ -5,3 +5,13 @@ import type { AttrName } from './providers/models'; export function hasAttribute(entity: Entity, attributeName: AttrName) { return entity.attributes.some((attr) => attr.name === attributeName); } + +export function enableBigIntSerialization(): void { + // eslint-disable-next-line no-extend-native + Object.defineProperty(BigInt.prototype, 'toJSON', { + configurable: true, + value: function toJSON(this: bigint) { + return `${this.toString()}n`; + }, + }); +} diff --git a/packages/shared/src/mock-values.ts b/packages/shared/src/mock-values.ts index 2043b1ea1..d1dcddb29 100644 --- a/packages/shared/src/mock-values.ts +++ b/packages/shared/src/mock-values.ts @@ -17,6 +17,11 @@ const range9 = () => range(1, 11); const oneD = () => ndarray(range1().map((val) => val ** 2)); +const oneD_bigint = () => + ndarray( + range8().map((_, i) => BigInt(Number.MAX_SAFE_INTEGER) + BigInt(i - 5)), + ); + const oneD_bool = () => ndarray([true, false, false, true, true, true, false, true, false, false]); @@ -89,6 +94,7 @@ const threeD_rgb = () => export const mockValues = { oneD, oneD_linear: () => ndarray(range1()), + oneD_bigint, oneD_cplx, oneD_compound, oneD_bool, @@ -103,6 +109,15 @@ export const mockValues = { shapeTwoD, ); }, + twoD_bigint: () => { + const { data: dataOneDBigInt } = oneD_bigint(); + return ndarray( + range5().flatMap((_, i) => + dataOneDBigInt.map((val) => val + 1n + BigInt(i) * 10n), + ), + [3, 5], + ); + }, twoD_cplx: () => ndarray( [