Skip to content

Commit

Permalink
Add JSON export to raw visualization
Browse files Browse the repository at this point in the history
  • Loading branch information
axelboc committed Sep 1, 2023
1 parent 68e54de commit 8eaa487
Show file tree
Hide file tree
Showing 14 changed files with 97 additions and 57 deletions.
17 changes: 3 additions & 14 deletions packages/app/src/__tests__/CorePack.test.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,17 @@
import { mockValues } from '@h5web/shared';
import { screen, within } from '@testing-library/react';

import {
findSelectedVisTab,
findVisTabs,
mockConsoleMethod,
renderApp,
} from '../test-utils';
import { findSelectedVisTab, findVisTabs, renderApp } from '../test-utils';
import { Vis } from '../vis-packs/core/visualizations';

test('visualize raw dataset', async () => {
await renderApp('/entities/raw');
const { selectExplorerNode } = await renderApp('/entities/raw');

await expect(findVisTabs()).resolves.toEqual([Vis.Raw]);
await expect(findSelectedVisTab()).resolves.toBe(Vis.Raw);
await expect(screen.findByText(/"int": 42/)).resolves.toBeVisible();
});

test('log raw dataset to console if too large', async () => {
const logSpy = mockConsoleMethod('log');
await renderApp('/entities/raw_large');

await selectExplorerNode('raw_large');
await expect(screen.findByText(/Too big to display/)).resolves.toBeVisible();
expect(logSpy).toHaveBeenCalledWith(mockValues.raw_large);
});

test('visualize scalar dataset', async () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/providers/h5grove/h5grove-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export class H5GroveApi extends DataProviderApi {
return url;
}

if (!hasNumericType(dataset)) {
if (format !== 'json' && !hasNumericType(dataset)) {
return undefined;
}

Expand Down
11 changes: 11 additions & 0 deletions packages/app/src/providers/mock/mock-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ export class MockApi extends DataProviderApi {
public async getValue(params: ValuesStoreParams): Promise<unknown> {
const { dataset, selection } = params;

if (dataset.name === 'raw_large') {
return { str: '.'.repeat(1_000_000) };
}

if (dataset.name === 'error_value') {
throw new Error('error');
}
Expand Down Expand Up @@ -93,6 +97,13 @@ export class MockApi extends DataProviderApi {
return url;
}

if (format === 'json') {
return async () => {
const json = JSON.stringify(value, null, 2);
return new Blob([json]);
};
}

if (
hasNumericType(dataset) &&
selection === undefined &&
Expand Down
2 changes: 1 addition & 1 deletion packages/app/src/providers/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export interface AttrValuesStore extends FetchStore<AttributeValues, Entity> {
getSingle: (entity: Entity, attrName: AttrName) => unknown;
}

export type ExportFormat = 'csv' | 'npy' | 'tiff';
export type ExportFormat = 'json' | 'csv' | 'npy' | 'tiff';
export type ExportURL = URL | (() => Promise<URL | Blob>) | undefined;

export type ProgressCallback = (prog: number[]) => void;
1 change: 1 addition & 0 deletions packages/app/src/vis-packs/core/matrix/MatrixToolbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ function MatrixToolbar(props: Props) {
format,
url: getExportURL(format),
}))}
align="right"
/>
</>
)}
Expand Down
24 changes: 24 additions & 0 deletions packages/app/src/vis-packs/core/raw/RawToolbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ExportMenu, Toolbar } from '@h5web/lib';

import type { ExportFormat, ExportURL } from '../../../providers/models';

interface Props {
getExportURL: ((format: ExportFormat) => ExportURL) | undefined;
}

function RawToolbar(props: Props) {
const { getExportURL } = props;

return (
<Toolbar>
{getExportURL && (
<ExportMenu
entries={[{ format: 'json', url: getExportURL('json') }]}
align="right"
/>
)}
</Toolbar>
);
}

export default RawToolbar;
23 changes: 21 additions & 2 deletions packages/app/src/vis-packs/core/raw/RawVisContainer.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,39 @@
import { RawVis } from '@h5web/lib';
import { assertDataset, assertNonNullShape } from '@h5web/shared';
import { createPortal } from 'react-dom';

import { useDataContext } from '../../../providers/DataProvider';
import type { VisContainerProps } from '../../models';
import VisBoundary from '../../VisBoundary';
import ValueFetcher from '../ValueFetcher';
import RawToolbar from './RawToolbar';

function RawVisContainer(props: VisContainerProps) {
const { entity } = props;
const { entity, toolbarContainer } = props;
assertDataset(entity);
assertNonNullShape(entity);

const { getExportURL } = useDataContext();

return (
<VisBoundary>
<ValueFetcher
dataset={entity}
render={(value) => <RawVis value={value} />}
render={(value) => (
<>
{toolbarContainer &&
createPortal(
<RawToolbar
getExportURL={
getExportURL &&
((format) => getExportURL(format, entity, undefined, value))
}
/>,
toolbarContainer,
)}
<RawVis value={value} />
</>
)}
/>
</VisBoundary>
);
Expand Down
11 changes: 10 additions & 1 deletion packages/h5wasm/src/h5wasm-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,16 @@ export class H5WasmApi extends ProviderApi {
selection: string | undefined,
value: Value<D>,
): ExportURL {
return this._getExportURL?.(format, dataset, selection, value);
const url = this._getExportURL?.(format, dataset, selection, value);
if (url) {
return url;
}

if (format === 'json') {
return async () => new Blob([JSON.stringify(value, null, 2)]);
}

return undefined;
}

public async cleanUp(): Promise<void> {
Expand Down
2 changes: 2 additions & 0 deletions packages/lib/src/toolbar/OverflowMenu.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
.menuList {
composes: popupInner from './Toolbar.module.css';
display: grid;
grid-template-columns: 1fr;
grid-gap: 0.25rem;
justify-items: flex-start;
margin: 0;
padding: 0.375rem 0.25rem;
list-style-type: none;
Expand Down
7 changes: 4 additions & 3 deletions packages/lib/src/toolbar/controls/ExportMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import styles from './Selector/Selector.module.css';

interface Props {
entries: ExportEntryProps[];
isSlice: boolean;
isSlice?: boolean;
align?: 'center' | 'left' | 'right';
}

function ExportMenu(props: Props) {
const { entries, isSlice } = props;
const { entries, isSlice, align = 'center' } = props;

return (
<Wrapper className={styles.wrapper}>
Expand All @@ -29,7 +30,7 @@ function ExportMenu(props: Props) {
<MdArrowDropDown className={styles.arrowIcon} />
</div>
</Button>
<Menu className={styles.menu}>
<Menu className={styles.menu} data-align={align}>
<div className={styles.list}>
{entries.map((entry) => (
<ExportEntry key={entry.format} {...entry} />
Expand Down
14 changes: 12 additions & 2 deletions packages/lib/src/toolbar/controls/Selector/Selector.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,6 @@
.menu {
position: absolute;
top: calc(100% + 0.75rem);
left: 50%;
transform: translateX(-50%); /* center menu with button */
z-index: 1; /* above overflow menu */
min-width: 100%;
padding-top: 0.25rem;
Expand All @@ -61,6 +59,18 @@
rgba(0, 0, 0, 0.1) 0px 4px 11px;
}

.menu:not([data-align]),
.menu[data-align='center'] {
left: 50%;
transform: translateX(-50%); /* center menu with button */
}
.menu[data-align='left'] {
left: 0;
}
.menu[data-align='right'] {
right: 0;
}

.list {
display: flex;
flex-direction: column;
Expand Down
17 changes: 0 additions & 17 deletions packages/lib/src/vis/raw/RawVis.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,4 @@

.fallback {
composes: fallback from global;
max-width: 26em;
line-height: 1.5;
}

.reason {
margin-top: 0;
margin-bottom: 0.75rem;
font-weight: 600;
}

.message {
margin-top: 0;
font-size: 0.875em;
}

.message > kbd {
font-weight: 600;
}
22 changes: 7 additions & 15 deletions packages/lib/src/vis/raw/RawVis.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import styles from './RawVis.module.css';

const LARGE_THRESHOLD = 1_000_000;

interface Props {
value: unknown;
}
Expand All @@ -8,23 +10,13 @@ function RawVis(props: Props) {
const { value } = props;
const valueAsStr = JSON.stringify(value, null, 2);

if (valueAsStr.length > 1000) {
console.log(value); // eslint-disable-line no-console

return (
<div className={styles.fallback}>
<p className={styles.reason}>Too big to display</p>
<p className={styles.message}>
Dataset logged to the browser's developer console instead. Press{' '}
<kbd>F12</kbd> to open the developer tools and access the console.
</p>
</div>
);
}

return (
<div className={styles.root}>
<pre className={styles.raw}>{valueAsStr}</pre>
{valueAsStr.length < LARGE_THRESHOLD ? (
<pre className={styles.raw}>{valueAsStr}</pre>
) : (
<p className={styles.fallback}>Too big to display</p>
)}
</div>
);
}
Expand Down
1 change: 0 additions & 1 deletion packages/shared/src/mock/values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,6 @@ const twoD_compound = [
export const mockValues = {
null: null,
raw: { int: 42 },
raw_large: { str: '.'.repeat(1000) },
scalar_int: 0,
scalar_int_42: 42,
scalar_str: 'foo',
Expand Down

0 comments on commit 8eaa487

Please sign in to comment.