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();
}
};