Skip to content

Error Toast

The ErrorToast.vue component is used to display errors and warnings generated by abnormal vehicle or mission statuses.

This component uses the Button and Sonner components from shadcn/vue.

MISSING: Toasts do not persist through page refreshes. This can be added when this component is integrated with the backend.

Because we have two application windows or screens, we need a way to synchronize the toasts and interactions between them. Since we do not have backend integration for toasts yet, the current solution is to use Tauri listeners/emitters. This approach will very likely be replaced in the future.

As mentioned earlier, these toasts do not persist on page refreshes. If you refresh one screen/page, the toasts will be out of sync.

First, we need a data structure to store the toasts. Just for our purposes, we can use Maps which offer fast lookups, flexible types, and preserved order of the added toasts.

src/components/ErrorToast.vue
const toastMap = new Map<string, string>();
var id: String;

Next, setup the listeners for each event— creating toast, dismissing toast, and dismissing all toasts.

When creating the toast, we create a Dismiss button under the action property.

Do note that Dismiss All is emitted by a test button, opposed to a button that is located on the toast itself like the regular Dismiss.

src/components/ErrorToast.vue
listen("create-toast", (event) => {
const { id, type, title, description } = event.payload as {
id: string;
type: "error" | "warning" | "info";
title: string;
description: string;
};
const toastId = toast[type](title, {
id,
description,
duration: Infinity,
action: { label: "Dismiss", onClick: () => emit("dismiss-toast", { id }) } // Dismiss button on toast
});
toastMap.set(id, String(toastId));
});
listen("dismiss-toast", (event) => {
const { id } = event.payload as { id: string };
toast.dismiss(id);
});
listen("dismiss-all-toasts", (event) => {
toast.dismiss();
});

Lastly, setup the test buttons to emit the events. Here’s one button as an example.

src/components/ErrorToast.vue
<Button
variant="outline"
@click="
() => {
id = generateId();
emit('create-toast', {
id,
type: 'error',
title: 'Error: Connection Failure',
description: `Unable to connect to \${vehicle}`
});
}
"
>
Connection Error
</Button>

We do not want the toasts to cover the sidebars, which contain critical information. Thus, we position the ErrorToast component based on which screen it is.

On the Camera Screen, the toasts will be positioned at the bottom-right. On the Over View screen, the toasts will be positioned at the bottom-left.

In the toast component, we retrieve the route and then set toasterPosition as the result of the computation.

src/components/ErrorToast.vue
const route = useRoute();
const toasterPosition = computed(() => {
return route.path === "/" ? "bottom-right" : "bottom-left"; // '/' is the route of Camera Screen
});

Then under <template>, add the position property.

src/components/ErrorToast.vue
<template>
<Toaster richColors :position="toasterPosition" />
...

If you also want to know the routes of each screen, go to the respective view and add this:

src/views/TestScreen.vue
import { useRoute } from 'vue-router'
const route = useRoute()
...
<template>
<div class="text-xs text-gray-500 fixed bottom-2 right-2 z-50">
Route: {{ route.name }} ({{ route.path }})
</div>
...

By default, the Sonner component has a set expiration and limit for the toasts. We can adjust this in the following ways.

Under an event listener that creates a toast, we set the duration property to Infinity.

src/components/ErrorToast.vue
listen("create-toast", (event) => {
...
const toastId = toast[type](title, {
id,
description,
duration: Infinity, // Set duration to infinity
action: { label: "Dismiss", onClick: () => emit("dismiss-toast", { id }) }
});
...
});

Alternatively, we simply set the TOAST_REMOVE_DELAY to a high number. The number is in seconds.

src/components/ui/toast/use-toast.ts
const TOAST_REMOVE_DELAY = 1000000

You might be able to get away with removing TOAST_REMOVE_DELAY instead of having both ways to remove toast expiration, but this has not been tested yet.

To remove the toast limit, simply comment out this line:

src/components/ui/toast/use-toast.ts
const TOAST_LIMIT = 5

If you want to restore this limit, you need to slice the list of toasts stored in the state by adding .slice(0, TOAST_LIMIT).

src/components/ui/toast/use-toast.ts
function dispatch(action: Action) {
switch (action.type) {
case actionTypes.ADD_TOAST:
state.value.toasts = [action.toast, ...state.value.toasts].slice(0, TOAST_LIMIT) // Add this
break
...

Here is the list of Errors or Warnings that can be generated.

  • Error: Connection Failure
    Unable to connect to [vehicle]
  • Error: Abnormal Status
    Abnormal [vehicle] status (low battery, system error/failure)!
  • Warning: Signal Integrity
    Weak signal integrity/connection lost to [vehicle]!
  • Warning: Keep-Out
    [Vehicle] within [distance] ft of keep-out zone!
  • Warning: Vehicle Proximity
    [Vehicle 1] and [Vehicle 2] are within [distance] ft of each other!

Error Toast