Skip to content

Commit

Permalink
Merge branch 'develop' into fix-vl-layout
Browse files Browse the repository at this point in the history
  • Loading branch information
g-bar committed May 27, 2024
2 parents 6801494 + 2546874 commit 98f45c7
Show file tree
Hide file tree
Showing 9 changed files with 244 additions and 57 deletions.
31 changes: 17 additions & 14 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
"dependencies": {
"@ant-design/icons": "^5.0.1",
"@apache-arrow/es2015-esm": "^16.0.0",
"@bbp/morphoviewer": "^0.19.0",
"@bbp/morphoviewer": "^0.19.1",
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/sortable": "^8.0.0",
Expand Down Expand Up @@ -79,9 +79,9 @@
"pako": "^2.1.0",
"performant-array-to-tree": "^1.11.0",
"plotly.js-dist-min": "^2.22.0",
"react": "18.2.0",
"react": "18.3.1",
"react-canvas-confetti": "^2.0.7",
"react-dom": "18.2.0",
"react-dom": "18.3.1",
"react-error-boundary": "^4.0.4",
"react-intersection-observer": "^9.5.2",
"react-pdf": "^7.7.0",
Expand Down
27 changes: 6 additions & 21 deletions src/components/MorphoViewer/MorphoViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@ import { useEffect, useRef } from 'react';
import { ColorRamp } from './ColorRamp';
import { Scalebar } from './Scalebar';
import { Settings } from './Settings';
import { WaitingForSomaEnhancement } from './WaitingForSomaEnhancement';
import { Warning } from './Warning';
import { useEnhancedSomaService } from './hooks/neuro-morpho-viz-service';
import { useMorphoViewerSettings } from './hooks/settings';
import { useSignal } from './hooks/signal';
import { fetchSomaFromNeuroMorphoViz } from './neuro-morpho-viz-service';

import useNotification from '@/hooks/notifications';
import { logError } from '@/util/logger';
import { classNames } from '@/util/utils';
import { useAccessToken } from '@/hooks/useAccessToken';

import styles from './morpho-viewer.module.css';

Expand All @@ -30,7 +28,6 @@ export interface MorphoViewerProps {
}

export function MorphoViewer({ className, swc, contentUrl }: MorphoViewerProps) {
const accessToken = useAccessToken();
const refDiv = useRef<HTMLDivElement | null>(null);
const refMorphoCanvas = useRef(new MorphologyCanvas());
const morphoCanvas = refMorphoCanvas.current;
Expand All @@ -39,35 +36,22 @@ export function MorphoViewer({ className, swc, contentUrl }: MorphoViewerProps)
const refCanvas = useRef<HTMLCanvasElement | null>(null);
const [{ isDarkMode }] = useMorphoViewerSettings(morphoCanvas);
const [warning, setWarning] = useSignal(10000);
const notification = useNotification();
const enhancedSomaIsLoading = useEnhancedSomaService(morphoCanvas, contentUrl);

useEffect(() => {
morphoCanvas.canvas = refCanvas.current;
morphoCanvas.swc = swc;
if (contentUrl) {
fetchSomaFromNeuroMorphoViz(contentUrl, accessToken)
.then((data) => {
if (!data) return;

morphoCanvas.somaGLB = data;
morphoCanvas.paint();
})
.catch((err) => {
logError('Unable to get a GLB mesh for the soma:', err);
notification.error('An error occured while retrieving an enhanced soma.');
});
}
gizmoCanvas.attachCamera(morphoCanvas.camera);
const handleWarning = () => {
setWarning(true);
};
morphoCanvas.eventMouseWheelWithoutCtrl.addListener(handleWarning);
gizmoCanvas.attachCamera(morphoCanvas.camera);
gizmoCanvas.eventTipClick.addListener(morphoCanvas.interpolateCamera);
return () => {
morphoCanvas.eventMouseWheelWithoutCtrl.removeListener(handleWarning);
gizmoCanvas.eventTipClick.removeListener(morphoCanvas.interpolateCamera);
};
}, [morphoCanvas, gizmoCanvas, setWarning, swc, contentUrl, accessToken, notification]);
}, [morphoCanvas, gizmoCanvas, swc, setWarning]);

const handleFullscreen = () => {
const div = refDiv.current;
Expand Down Expand Up @@ -114,6 +98,7 @@ export function MorphoViewer({ className, swc, contentUrl }: MorphoViewerProps)
</div>
<Scalebar painter={morphoCanvas} />
<Warning visible={warning} />
<WaitingForSomaEnhancement visible={enhancedSomaIsLoading} />
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from 'react';
import { classNames } from '@/util/utils';

import styles from './waiting-for-soma-enhancement.module.css';

export interface WaitingForSomaEnhancementProps {
className?: string;
visible: boolean;
}

const CAPTION = 'Soma reconstruction in progress...';

export function WaitingForSomaEnhancement({ className, visible }: WaitingForSomaEnhancementProps) {
if (!visible) return null;

return (
<div className={classNames(styles.main, className)}>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<title>{CAPTION}</title>
<path d="M12,15.5A3.5,3.5 0 0,1 8.5,12A3.5,3.5 0 0,1 12,8.5A3.5,3.5 0 0,1 15.5,12A3.5,3.5 0 0,1 12,15.5M19.43,12.97C19.47,12.65 19.5,12.33 19.5,12C19.5,11.67 19.47,11.34 19.43,11L21.54,9.37C21.73,9.22 21.78,8.95 21.66,8.73L19.66,5.27C19.54,5.05 19.27,4.96 19.05,5.05L16.56,6.05C16.04,5.66 15.5,5.32 14.87,5.07L14.5,2.42C14.46,2.18 14.25,2 14,2H10C9.75,2 9.54,2.18 9.5,2.42L9.13,5.07C8.5,5.32 7.96,5.66 7.44,6.05L4.95,5.05C4.73,4.96 4.46,5.05 4.34,5.27L2.34,8.73C2.21,8.95 2.27,9.22 2.46,9.37L4.57,11C4.53,11.34 4.5,11.67 4.5,12C4.5,12.33 4.53,12.65 4.57,12.97L2.46,14.63C2.27,14.78 2.21,15.05 2.34,15.27L4.34,18.73C4.46,18.95 4.73,19.03 4.95,18.95L7.44,17.94C7.96,18.34 8.5,18.68 9.13,18.93L9.5,21.58C9.54,21.82 9.75,22 10,22H14C14.25,22 14.46,21.82 14.5,21.58L14.87,18.93C15.5,18.67 16.04,18.34 16.56,17.94L19.05,18.95C19.27,19.03 19.54,18.95 19.66,18.73L21.66,15.27C21.78,15.05 21.73,14.78 21.54,14.63L19.43,12.97Z" />
</svg>
<div>{CAPTION}</div>
</div>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './WaitingForSomaEnhancement';
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.main {
position: absolute;
left: 400px;
top: 0;
display: flex;
justify-content: space-around;
align-items: center;
gap: 1em;
padding: 0.5em;
color: var(--custom-color-accent);
}

.main > svg {
width: 1.5em;
height: 1.5em;
animation: rotation-animation linear 3s infinite;
stroke: currentColor;
fill: currentColor;
}

@keyframes rotation-animation {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { useEffect, useState } from 'react';

type Listener<T> = (args: T) => void;

export class EnhancedSomaLoaderEvent<T> {
private listeners: Listener<T>[] = [];

addListener(listener: Listener<T>) {
this.removeListener(listener);
this.listeners.push(listener);
}

removeListener(listener: Listener<T>) {
this.listeners = this.listeners.filter((item) => item !== listener);
}

dispatch(args: T) {
this.listeners.forEach((listener) => listener(args));
}
}

export function useEventValue<T>(event: EnhancedSomaLoaderEvent<T>, initialValue: T): T {
const [value, setValue] = useState(initialValue);

useEffect(() => {
event.addListener(setValue);
return () => event.removeListener(setValue);
}, [event]);

return value;
}

export function useEventHandler<T>(event: EnhancedSomaLoaderEvent<T>, handler: (args: T) => void) {
useEffect(() => {
event.addListener(handler);
return () => event.removeListener(handler);
}, [event, handler]);
}
126 changes: 126 additions & 0 deletions src/components/MorphoViewer/hooks/neuro-morpho-viz-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { useEffect } from 'react';
import { MorphologyCanvas } from '@bbp/morphoviewer';

import {
EnhancedSomaLoaderEvent,
useEventHandler,
useEventValue,
} from './neuro-morpho-viz-service-event';

import useNotification from '@/hooks/notifications';
import { thumbnailGenerationBaseUrl } from '@/config';
import { logError } from '@/util/logger';
import { createHeaders } from '@/util/utils';
import { useAccessToken } from '@/hooks/useAccessToken';

interface NeuroMorphoVizQuery {
contentUrl: string;
accessToken: string;
}

class EnhancedSomaLoader {
public readonly eventSomaGlbReceived = new EnhancedSomaLoaderEvent<ArrayBuffer | null>();

public readonly eventErrorOccured = new EnhancedSomaLoaderEvent<Error | null>();

public readonly eventFetchingInProgress = new EnhancedSomaLoaderEvent<boolean>();

private nextQuery: NeuroMorphoVizQuery | null = null;

private lastContentUrl: string | null = null;

private lastSomaGlb: ArrayBuffer | null = null;

private _fetchingInProgress = false;

async fetch(query: NeuroMorphoVizQuery) {
if (this.fetchingInProgress) {
this.nextQuery = query;
return;
}

this.nextQuery = query;
do {
const { accessToken, contentUrl } = this.nextQuery;
this.nextQuery = null;
this.fetchingInProgress = true;
try {
if (contentUrl === this.lastContentUrl) {
this.eventSomaGlbReceived.dispatch(this.lastSomaGlb);
continue;
}
const data = await fetchSomaFromNeuroMorphoViz(contentUrl, accessToken);
this.eventSomaGlbReceived.dispatch(data);
this.lastContentUrl = contentUrl;
this.lastSomaGlb = data;
} catch (ex) {
if (ex instanceof Error) this.eventErrorOccured.dispatch(ex);
else if (typeof ex === 'string') this.eventErrorOccured.dispatch(new Error(ex));
else this.eventErrorOccured.dispatch(new Error(JSON.stringify(ex)));
} finally {
this.fetchingInProgress = false;
}
} while (this.nextQuery);
}

private get fetchingInProgress() {
return this._fetchingInProgress;
}

private set fetchingInProgress(value: boolean) {
this._fetchingInProgress = value;
this.eventFetchingInProgress.dispatch(value);
}
}

async function fetchSomaFromNeuroMorphoViz(
contentUrl: string,
accessToken: string | undefined
): Promise<ArrayBuffer | null> {
if (!accessToken) return null;

const url = `${thumbnailGenerationBaseUrl}/soma/process-nexus-swc?content_url=${encodeURIComponent(contentUrl)}`;
const resp = await fetch(url, {
method: 'GET',
headers: createHeaders(accessToken, {
Accept: 'application/json',
}),
});
const data = await resp.arrayBuffer();
return data;
}

const enhancedSomaService = new EnhancedSomaLoader();

export function useEnhancedSomaService(
morphoCanvasManager: MorphologyCanvas,
contentUrl: string | undefined
) {
const accessToken = useAccessToken();
const notification = useNotification();

useEventHandler(enhancedSomaService.eventErrorOccured, (err) => {
if (!err) return;

logError('Error while fetching enhanced soma!', err);
notification.error(err.message);
});

useEventHandler(enhancedSomaService.eventSomaGlbReceived, (glb) => {
if (!glb) return;

// eslint-disable-next-line no-param-reassign
morphoCanvasManager.somaGLB = glb;
morphoCanvasManager.paint();
});

const fetchingInProgress = useEventValue(enhancedSomaService.eventFetchingInProgress, false);

useEffect(() => {
if (!contentUrl || !accessToken) return;

enhancedSomaService.fetch({ contentUrl, accessToken });
}, [contentUrl, accessToken]);

return fetchingInProgress;
}
Loading

0 comments on commit 98f45c7

Please sign in to comment.