Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified cypress/snapshots/app.cy.ts/auximage.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/auxspectrum.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/bgr_image.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/bgra_image.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/compound_1D.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/default_slice.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/fillvalue_1D.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/fillvalue_2D.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_2D.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_2D_complex.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_2D_inverted_cmap.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_4d_default.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_4d_remapped.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_4d_sliced.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_4d_zeros.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_domain.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/heatmap_flip.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/line_1D.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/line_1D_points.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/line_complex_1D.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/line_complex_constant.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/line_constant.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/logspectrum.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/matrix_1D.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/nxheatmap.snap.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified cypress/snapshots/app.cy.ts/nxheatmap_complex_2d.snap.png
Binary file modified cypress/snapshots/app.cy.ts/nxline.snap.png
Binary file modified cypress/snapshots/app.cy.ts/nxline_complex_2d_aux.snap.png
Binary file modified cypress/snapshots/app.cy.ts/nxrgb.snap.png
Binary file modified cypress/snapshots/app.cy.ts/nxrgba.snap.png
Binary file modified cypress/snapshots/app.cy.ts/nxscatter.snap.png
Binary file modified cypress/snapshots/app.cy.ts/rgb_image.snap.png
Binary file modified cypress/snapshots/app.cy.ts/rgba_image.snap.png
Binary file modified cypress/snapshots/app.cy.ts/wide.snap.png
2 changes: 1 addition & 1 deletion packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
"ndarray-ops": "1.2.2",
"react-error-boundary": "6.1.1",
"react-icons": "5.4.0",
"react-reflex": "4.2.7",
"react-resizable-panels": "4.7.1",
"three": "0.182.0",
"zustand": "5.0.11"
},
Expand Down
41 changes: 35 additions & 6 deletions packages/app/src/App.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -115,18 +115,47 @@
.sidebarArea {
display: flex;
flex-direction: column;
scrollbar-width: thin;
background-color: var(--primary-bg);
overflow-x: hidden;
overflow-y: auto;
}

.root[data-sidebar-collapsed] .sidebarArea {
display: none;
}

.splitter {
width: 5px !important;
border-color: lightgray !important;
transition: background-color 0.2s ease-in-out !important;
position: relative;
z-index: 1;
width: 2px;
background-color: #ddd;
}

.splitter:focus {
outline: none;
}

.splitter::before {
content: '';
position: absolute;
top: 0;
bottom: 0;
left: -2px;
width: 6px;
background-color: #ddd;
transition: opacity 0.1s ease-in-out;
cursor: ew-resize;
opacity: 0;
}

.splitter:not([data-separator='inactive'])::before,
.splitter:hover::before /* splitter becomes "inactive" after resizing even if still hovered */ {
opacity: 1;
transition-delay: 0.3s; /* delay showing (but not hiding) */
}

.splitter:hover {
background-color: lightgray !important;
.splitter:focus-visible::before {
opacity: 1;
}

.mainArea {
Expand Down
70 changes: 53 additions & 17 deletions packages/app/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import '@h5web/lib'; // eslint-disable-line import/no-duplicates -- make sure lib styles come first in CSS bundle

import { KeepZoomProvider } from '@h5web/lib'; // eslint-disable-line import/no-duplicates
import { useToggle } from '@react-hookz/web';
import { Suspense, useState } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { ReflexContainer, ReflexElement, ReflexSplitter } from 'react-reflex';
import {
Group,
Panel,
Separator,
useDefaultLayout,
usePanelRef,
} from 'react-resizable-panels';

import styles from './App.module.css';
import BreadcrumbsBar from './breadcrumbs/BreadcrumbsBar';
Expand All @@ -18,6 +23,10 @@ import Sidebar from './Sidebar';
import VisConfigProvider from './VisConfigProvider';
import Visualizer from './visualizer/Visualizer';

const SIDEBAR_ID = 'h5w-sidebar';
const MAIN_AREA_ID = 'h5w-main-area';
const RESIZE_TARGET_MIN_SIZE = { coarse: 6, fine: 6 }; // match CSS width (.splitter::before)

interface Props {
sidebarOpen?: boolean;
initialPath?: string;
Expand All @@ -36,7 +45,6 @@ function App(props: Props) {
} = props;

const [selectedPath, setSelectedPath] = useState<string>(initialPath);
const [isSidebarOpen, toggleSidebarOpen] = useToggle(initialSidebarOpen);
const [isInspecting, setInspecting] = useState(false);

const { valuesStore } = useDataContext();
Expand All @@ -45,6 +53,17 @@ function App(props: Props) {
valuesStore.abortAll('entity changed', true);
}

const { defaultLayout, onLayoutChanged } = useDefaultLayout({
id: 'h5web:layout',
});
Comment on lines +56 to +58
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hook provides local storage persistence.


const sidebarPanelRef = usePanelRef();
const [isSidebarOpen, setSidebarOpen] = useState(
initialSidebarOpen
? !defaultLayout || defaultLayout[SIDEBAR_ID] > 0
: false,
);

return (
<ErrorBoundary
FallbackComponent={ErrorFallback}
Expand All @@ -54,32 +73,49 @@ function App(props: Props) {
}
}}
>
<ReflexContainer
<Group
className={styles.root}
resizeTargetMinimumSize={RESIZE_TARGET_MIN_SIZE}
defaultLayout={initialSidebarOpen ? defaultLayout : undefined}
onLayoutChanged={onLayoutChanged}
data-fullscreen-root
data-allow-dark-mode={disableDarkMode ? undefined : ''}
orientation="vertical"
data-sidebar-collapsed={!isSidebarOpen || undefined}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to actually hide the content of the sidebar with CSS when the sidebar is collapsed.

>
<ReflexElement
<Panel
id={SIDEBAR_ID}
className={styles.sidebarArea}
style={{ display: isSidebarOpen ? undefined : 'none' }}
flex={25}
panelRef={sidebarPanelRef}
defaultSize={initialSidebarOpen ? '25%' : '0%'}
minSize={150}
collapsible
onResize={({ inPixels: size }) => {
const isNowOpen = size > 0;
if (
(isNowOpen && !isSidebarOpen) ||
(!isNowOpen && isSidebarOpen)
) {
setSidebarOpen(isNowOpen);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition is to avoid unnecessary re-renders while resizing.

}
}}
>
<Sidebar selectedPath={selectedPath} onSelect={onSelectPath} />
</ReflexElement>
</Panel>

<ReflexSplitter
className={styles.splitter}
style={{ display: isSidebarOpen ? undefined : 'none' }}
/>
<Separator className={styles.splitter} />

<ReflexElement className={styles.mainArea} flex={75} minSize={500}>
<Panel id={MAIN_AREA_ID} className={styles.mainArea} minSize={500}>
<BreadcrumbsBar
path={selectedPath}
isSidebarOpen={isSidebarOpen}
isInspecting={isInspecting}
onToggleSidebar={toggleSidebarOpen}
onToggleSidebar={() => {
if (isSidebarOpen) {
sidebarPanelRef.current?.collapse();
} else {
sidebarPanelRef.current?.expand();
}
}}
onChangeInspecting={setInspecting}
onSelectPath={onSelectPath}
getFeedbackURL={getFeedbackURL}
Expand Down Expand Up @@ -107,8 +143,8 @@ function App(props: Props) {
</KeepZoomProvider>
</DimMappingProvider>
</VisConfigProvider>
</ReflexElement>
</ReflexContainer>
</Panel>
</Group>
</ErrorBoundary>
);
}
Expand Down
1 change: 1 addition & 0 deletions packages/app/src/Sidebar.module.css
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.tabBar {
flex: none;
display: flex;
height: var(--toolbar-height);
font-size: 1.25rem;
Expand Down
4 changes: 2 additions & 2 deletions packages/app/src/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ function Sidebar(props: Props) {
}

return (
<div>
<>
<div className={styles.tabBar}>
<button
className={styles.tab}
Expand Down Expand Up @@ -63,7 +63,7 @@ function Sidebar(props: Props) {
getSearchablePaths={getSearchablePaths}
/>
)}
</div>
</>
);
}

Expand Down
20 changes: 11 additions & 9 deletions packages/app/src/__tests__/BreadcrumbsBar.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ test('toggle sidebar', async () => {

// Hide
await toggleBtn.click();
await expect
.element(page.getByRole('treeitem', { name: 'source.h5' }))
Comment on lines +16 to +17
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Has to be async because of the local isSidebarOpen state synchronisation.

.not.toBeInTheDocument();
expect(toggleBtn).toHaveAttribute('aria-pressed', 'false');
expect(
page.getByRole('treeitem', { name: 'source.h5' }),
).not.toBeInTheDocument();

// Show
await toggleBtn.click();
await expect.element(getExplorerItem('source.h5')).toBeVisible();
expect(toggleBtn).toHaveAttribute('aria-pressed', 'true');
expect(getExplorerItem('source.h5')).toBeVisible();
});

test('switch between "display" and "inspect" modes', async () => {
Expand Down Expand Up @@ -53,11 +53,13 @@ test('navigate with breadcrumbs', async () => {
// Hide sidebar to show root crumb
const toggleBtn = page.getByRole('button', { name: 'Toggle sidebar' });
await toggleBtn.click();
expect(
page.getByRole('heading', {
name: 'source.h5 / entities / empty_dataset',
}),
).toBeVisible();
await expect
.element(
page.getByRole('heading', {
name: 'source.h5 / entities / empty_dataset',
}),
)
.toBeVisible();

// Select parent crumb
await page.getByRole('button', { name: 'entities' }).click();
Expand Down
45 changes: 43 additions & 2 deletions packages/app/src/__tests__/Explorer.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,14 +28,55 @@ test('toggle sidebar', async () => {
expect(sidebarBtn).toHaveAttribute('aria-pressed', 'true');

await sidebarBtn.click();
expect(getExplorerItem('source.h5')).not.toBeInTheDocument();
await expect.element(getExplorerItem('source.h5')).not.toBeInTheDocument();
expect(sidebarBtn).toHaveAttribute('aria-pressed', 'false');

await sidebarBtn.click();
expect(getExplorerItem('source.h5')).toBeVisible();
await expect.element(getExplorerItem('source.h5')).toBeVisible();
expect(sidebarBtn).toHaveAttribute('aria-pressed', 'true');
});

test('resize and collapse/expand sidebar with keyboard', async () => {
const { user } = await renderApp();

const splitter = page.getByRole('separator');
expect(splitter).toHaveAttribute('aria-valuenow', '25');

// Resize sidebar to minimum width
await user.type(splitter, '{ArrowLeft}{ArrowLeft}{ArrowLeft}{ArrowLeft}');
expect(splitter).toHaveAttribute('aria-valuenow', '7.817'); // min size (150px) / viewport (1920px)

// Collapse
await user.type(splitter, '{ArrowLeft}');
expect(splitter).toHaveAttribute('aria-valuenow', '0'); // collapsed
await expect.element(getExplorerItem('source.h5')).not.toBeInTheDocument();

// Expand
await user.type(splitter, '{ArrowRight}');
expect(splitter).toHaveAttribute('aria-valuenow', '7.817');
await expect.element(getExplorerItem('source.h5')).toBeVisible();
});

test('remember sidebar width when toggling', async () => {
const { user } = await renderApp();

const splitter = page.getByRole('separator');
expect(splitter).toHaveAttribute('aria-valuenow', '25');

// Resize sidebar
await user.type(splitter, '{ArrowRight}');
expect(splitter).toHaveAttribute('aria-valuenow', '30');

// Collapse
const sidebarBtn = page.getByRole('button', { name: 'Toggle sidebar' });
await sidebarBtn.click();
expect(splitter).toHaveAttribute('aria-valuenow', '0');

// Expand
await sidebarBtn.click();
expect(splitter).toHaveAttribute('aria-valuenow', '30');
});

test('navigate groups in explorer', async () => {
const { selectExplorerNode } = await renderApp();

Expand Down
4 changes: 2 additions & 2 deletions packages/app/src/explorer/Explorer.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,11 @@
}

.btn:hover,
.btn:focus {
.btn:focus-visible {
background-color: var(--primary-light);
}

.btn:focus {
.btn:focus-visible {
outline: 1px solid var(--primary-dark);
outline-offset: -1px;
}
Expand Down
1 change: 0 additions & 1 deletion packages/app/src/global-styles.css
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
/* Global app styles for demo */
@import 'react-reflex/styles.css';
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No more global styles 🎉

@import '@h5web/lib/global-styles.css';
1 change: 0 additions & 1 deletion packages/app/src/styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,4 @@
Includes global app styles and distributed lib styles (so users don't have to import two stylesheets).
Output is later concatenated with local app styles. */

import 'react-reflex/styles.css';
import '@h5web/lib/styles.css'; // distributed lib styles
1 change: 1 addition & 0 deletions packages/app/vitest.browser.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export default mergeConfig(
setupFiles: 'src/setupTests.ts',
restoreMocks: true,
pool: 'threads',
testTimeout: 10_000,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's 15s by default, which I find to be quite long when debugging failing tests locally. Don't want to set it too low, though, even if our longest test lasts only about 4s.


browser: {
provider: playwright(),
Expand Down
Loading
Loading