From ca2b0525684d9375481add36db9d5a5783a4d522 Mon Sep 17 00:00:00 2001 From: Charlie Meister Date: Mon, 15 Apr 2024 14:48:29 +0200 Subject: [PATCH] Don't let toast elapse in background in some browsers (#7741) * WIP: try to not let toast elapse in background * add console print of error and warning messages * roughly check Toast.error occurrences for duplications * roughly check Toast.warning occurrences for console print duplications * only print toast content to console if its a string * add changelog * address review --- CHANGELOG.unreleased.md | 1 + .../dataset/composition_wizard/common.ts | 2 +- .../admin/voxelytics/workflow_list_view.tsx | 2 +- .../admin/voxelytics/workflow_view.tsx | 2 +- .../libs/browser_feature_check.tsx | 8 +++---- frontend/javascripts/libs/error_handling.ts | 1 - frontend/javascripts/libs/toast.tsx | 22 +++++++++++++++---- frontend/javascripts/oxalis/controller.tsx | 2 +- .../oxalis/model/sagas/annotation_saga.tsx | 2 +- .../oxalis/model/sagas/mesh_saga.ts | 8 +++---- .../oxalis/model/sagas/proofread_saga.ts | 2 +- .../oxalis/model/sagas/quick_select_saga.ts | 1 - .../oxalis/model_initialization.ts | 2 +- .../view/action-bar/starting_job_modals.tsx | 2 +- 14 files changed, 35 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 7afe533050..6be58b2471 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -22,6 +22,7 @@ For upgrade instructions, please check the [migration guide](MIGRATIONS.released - Duplicated annotations are opened in a new browser tab. [#7724](https://github.com/scalableminds/webknossos/pull/7724) - When proofreading segments and merging two segments, the segment item that doesn't exist anymore after the merge is automatically removed. [#7729](https://github.com/scalableminds/webknossos/pull/7729) - Changed some internal APIs to use spelling dataset instead of dataSet. This requires all connected datastores to be the latest version. [#7690](https://github.com/scalableminds/webknossos/pull/7690) +- Toasts are shown until WEBKNOSSOS is running in the active browser tab again. Also, the content of most toasts that show errors or warnings is printed to the browser's console. [#7741](https://github.com/scalableminds/webknossos/pull/7741) ### Fixed - Fixed that the Command modifier on MacOS wasn't treated correctly for some shortcuts. Also, instead of the Alt key, the ⌥ key is shown as a hint in the status bar on MacOS. [#7659](https://github.com/scalableminds/webknossos/pull/7659) diff --git a/frontend/javascripts/admin/dataset/composition_wizard/common.ts b/frontend/javascripts/admin/dataset/composition_wizard/common.ts index 6036d5f9d0..9bd5b2a2f7 100644 --- a/frontend/javascripts/admin/dataset/composition_wizard/common.ts +++ b/frontend/javascripts/admin/dataset/composition_wizard/common.ts @@ -54,8 +54,8 @@ export async function tryToFetchDatasetsByName( ); return datasets; } catch (exception) { - console.warn(exception); Toast.warning(userErrorMessage); + console.warn(exception); return null; } } diff --git a/frontend/javascripts/admin/voxelytics/workflow_list_view.tsx b/frontend/javascripts/admin/voxelytics/workflow_list_view.tsx index abef4a26f6..4cbca50fa4 100644 --- a/frontend/javascripts/admin/voxelytics/workflow_list_view.tsx +++ b/frontend/javascripts/admin/voxelytics/workflow_list_view.tsx @@ -50,8 +50,8 @@ export default function WorkflowListView() { const _workflows = (await getVoxelyticsWorkflows()).map(parseWorkflowInfo); setWorkflows(_workflows); } catch (err) { - console.error(err); Toast.error("Could not load workflow list."); + console.error(err); } finally { setIsLoading(false); } diff --git a/frontend/javascripts/admin/voxelytics/workflow_view.tsx b/frontend/javascripts/admin/voxelytics/workflow_view.tsx index 2c4ccaf9c5..88269fc995 100644 --- a/frontend/javascripts/admin/voxelytics/workflow_view.tsx +++ b/frontend/javascripts/admin/voxelytics/workflow_view.tsx @@ -372,8 +372,8 @@ export default function WorkflowView() { error: err as Error, }); } catch (accessibleBySwitchingError) { - console.log(accessibleBySwitchingError); Toast.error("Could not load workflow report."); + console.error(accessibleBySwitchingError); setLoadingState({ status: "FAILED", error: accessibleBySwitchingError as Error }); } } diff --git a/frontend/javascripts/libs/browser_feature_check.tsx b/frontend/javascripts/libs/browser_feature_check.tsx index 5f84ec0851..c5aed5130b 100644 --- a/frontend/javascripts/libs/browser_feature_check.tsx +++ b/frontend/javascripts/libs/browser_feature_check.tsx @@ -10,10 +10,6 @@ export default function checkBrowserFeatures() { new BigUint64Array(1); "hello".replaceAll("l", "k"); } catch (exception) { - console.error( - "This browser lacks support for some modern features. Exception caught during test of features:", - exception, - ); Toast.warning(
Your browser seems to be outdated.{" "} @@ -23,5 +19,9 @@ export default function checkBrowserFeatures() { to avoid errors. See console for details.
, ); + console.error( + "This browser lacks support for some modern features. Exception caught during test of features:", + exception, + ); } } diff --git a/frontend/javascripts/libs/error_handling.ts b/frontend/javascripts/libs/error_handling.ts index f31bbfd7e8..cd819d0c3e 100644 --- a/frontend/javascripts/libs/error_handling.ts +++ b/frontend/javascripts/libs/error_handling.ts @@ -174,7 +174,6 @@ class ErrorHandling { error = new Error(message); } - console.error(error); this.notify(error); if (error.toString() === "Error: Script error.") { diff --git a/frontend/javascripts/libs/toast.tsx b/frontend/javascripts/libs/toast.tsx index f8274e881b..71b47205e2 100644 --- a/frontend/javascripts/libs/toast.tsx +++ b/frontend/javascripts/libs/toast.tsx @@ -1,6 +1,7 @@ import { notification, Collapse } from "antd"; import { CloseCircleOutlined } from "@ant-design/icons"; import React from "react"; +import { animationFrame, sleep } from "./utils"; export type ToastStyle = "info" | "warning" | "success" | "error"; export type Message = { @@ -75,12 +76,12 @@ const Toast = { ); }, - message( + async message( type: ToastStyle, rawMessage: string | React.ReactNode, config: ToastConfig, details?: string, - ): void { + ): Promise { const message = this.buildContentWithDetails(rawMessage, details); const timeout = config.timeout != null ? config.timeout : 6000; const key = config.key || (typeof message === "string" ? message : undefined); @@ -94,11 +95,10 @@ const Toast = { toastMessage = message; } - const timeOutInSeconds = timeout / 1000; let toastConfig = { icon: undefined, key, - duration: sticky ? 0 : timeOutInSeconds, + duration: 0, message: toastMessage, style: {}, className: "", @@ -112,6 +112,18 @@ const Toast = { } notification[type](toastConfig); + + // Make sure that toasts don't just disappear while the user has WK in a background tab (e.g. while uploading large dataset). + // Most browsers pause requestAnimationFrame() if the current tab is not active, but Firefox does not seem to do that. + if (!sticky && key != null) { + const splitTimeout = timeout / 2; + await animationFrame(); // ensure tab is active + await sleep(splitTimeout); + await animationFrame(); + // If the user has switched the tab, show the toast again so that the user doesn't just see the toast dissapear. + await sleep(splitTimeout); + this.close(key); + } }, info(message: React.ReactNode, config: ToastConfig = {}, details?: string | undefined): void { @@ -119,6 +131,7 @@ const Toast = { }, warning(message: React.ReactNode, config: ToastConfig = {}, details?: string | undefined): void { + if (typeof message === "string") console.warn(message); this.message("warning", message, config, details); }, @@ -135,6 +148,7 @@ const Toast = { config: ToastConfig = {}, details?: string | undefined, ): void { + if (typeof message === "string") console.error(message); this.message("error", message, config, details); }, diff --git a/frontend/javascripts/oxalis/controller.tsx b/frontend/javascripts/oxalis/controller.tsx index ecce39db6f..69ffada53f 100644 --- a/frontend/javascripts/oxalis/controller.tsx +++ b/frontend/javascripts/oxalis/controller.tsx @@ -194,10 +194,10 @@ class Controller extends React.PureComponent { try { eval(content); } catch (error) { - console.error(error); Toast.error( `Error executing the task script "${script.name}". See console for more information.`, ); + console.error(error); } } } diff --git a/frontend/javascripts/oxalis/model/sagas/annotation_saga.tsx b/frontend/javascripts/oxalis/model/sagas/annotation_saga.tsx index 2828aa2bba..2f2b83658d 100644 --- a/frontend/javascripts/oxalis/model/sagas/annotation_saga.tsx +++ b/frontend/javascripts/oxalis/model/sagas/annotation_saga.tsx @@ -83,7 +83,6 @@ export function* pushAnnotationUpdateAsync(action: Action) { // we will only notify the user if the name, visibility or description could not be changed. // Otherwise, we won't notify the user and won't let the sagas crash as the actual skeleton/volume // tracings are handled separately. - console.error(error); ErrorHandling.notify(error as Error); if ( ["SET_ANNOTATION_NAME", "SET_ANNOTATION_VISIBILITY", "SET_ANNOTATION_DESCRIPTION"].includes( @@ -92,6 +91,7 @@ export function* pushAnnotationUpdateAsync(action: Action) { ) { Toast.error("Could not update annotation property. Please try again."); } + console.error(error); } } diff --git a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts index 6617dbb633..c4ddd88988 100644 --- a/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/mesh_saga.ts @@ -785,8 +785,8 @@ function* loadPrecomputedMeshForSegmentId( scale = chunkDescriptors.scale; loadingOrder = chunkDescriptors.loadingOrder; } catch (exception) { - console.warn("Mesh chunk couldn't be loaded due to", exception); Toast.warning(messages["tracing.mesh_listing_failed"]); + console.warn("Mesh chunk couldn't be loaded due to", exception); yield* put(finishedLoadingMeshAction(layerName, id)); yield* put(removeMeshAction(layerName, id)); return; @@ -808,8 +808,8 @@ function* loadPrecomputedMeshForSegmentId( try { yield* call(processTaskWithPool, loadChunksTasks, PARALLEL_PRECOMPUTED_MESH_LOADING_COUNT); } catch (exception) { - console.error(exception); Toast.warning(`Some mesh chunks could not be loaded for segment ${id}.`); + console.error(exception); } yield* put(finishedLoadingMeshAction(layerName, id)); @@ -1089,8 +1089,8 @@ function* downloadMeshCellById(cellName: string, segmentId: number, layerName: s yield* call(saveAs, blob, `${cellName}-${segmentId}.stl`); } catch (exception) { ErrorHandling.notify(exception as Error); - console.error(exception); Toast.error("Could not export to STL. See console for details"); + console.error(exception); } } @@ -1123,8 +1123,8 @@ function* downloadMeshCellsAsZIP( yield* call(saveAs, result as Blob, "mesh-export.zip"); } catch (exception) { ErrorHandling.notify(exception as Error); - console.error(exception); Toast.error("Could not export meshes as STL files. See console for details"); + console.error(exception); } } diff --git a/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts b/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts index 68d8caef7a..fe0d5b9232 100644 --- a/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/proofread_saga.ts @@ -826,10 +826,10 @@ function* getAgglomerateInfos( }> | null> { const idInfos = yield* all(positions.map((pos) => call(getMappedAndUnmapped, pos))); if (idInfos.find((idInfo) => idInfo.agglomerateId === 0 || idInfo.unmappedId === 0) != null) { - console.warn("At least one id was zero:", idInfos); Toast.warning( "One of the selected segments has the id 0 which is the background. Cannot merge/split.", ); + console.warn("At least one id was zero:", idInfos); return null; } return idInfos; diff --git a/frontend/javascripts/oxalis/model/sagas/quick_select_saga.ts b/frontend/javascripts/oxalis/model/sagas/quick_select_saga.ts index 5fa9653da5..8b5aed3a18 100644 --- a/frontend/javascripts/oxalis/model/sagas/quick_select_saga.ts +++ b/frontend/javascripts/oxalis/model/sagas/quick_select_saga.ts @@ -56,7 +56,6 @@ export default function* listenToQuickSelect(): Saga { } catch (ex) { Toast.error((ex as Error).toString()); ErrorHandling.notify(ex as Error); - console.error(ex); } finally { yield* put(setBusyBlockingInfoAction(false)); action.quickSelectGeometry.setCoordinates([0, 0, 0], [0, 0, 0]); diff --git a/frontend/javascripts/oxalis/model_initialization.ts b/frontend/javascripts/oxalis/model_initialization.ts index d6401041e0..c879f1f8d8 100644 --- a/frontend/javascripts/oxalis/model_initialization.ts +++ b/frontend/javascripts/oxalis/model_initialization.ts @@ -700,11 +700,11 @@ async function applyLayerState(stateByLayer: UrlStateByLayer) { // The name of the layer could have changed if a volume tracing was created from a viewed annotation effectiveLayerName = getLayerByName(dataset, layerName, true).name; } catch (e) { - console.error(e); Toast.error( // @ts-ignore `URL configuration values for the layer "${layerName}" are ignored, because: ${e.message}`, ); + console.error(e); // @ts-ignore ErrorHandling.notify(e, { urlLayerState: stateByLayer, diff --git a/frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx b/frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx index d367835ea5..caf5e4e746 100644 --- a/frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx +++ b/frontend/javascripts/oxalis/view/action-bar/starting_job_modals.tsx @@ -553,10 +553,10 @@ function StartJobForm(props: StartJobFormProps) { ); handleClose(); } catch (error) { - console.error(error); Toast.error( `The ${jobName} job could not be started. Please contact an administrator or look in the console for more details.`, ); + console.error(error); handleClose(); } };