chore: Automatically display errors as toast if uncaught in actions (#6482)

Reduces plumbing
This commit is contained in:
Tom Moor
2024-02-05 16:40:29 -08:00
committed by GitHub
parent 3a125beb9e
commit d6595c15ad
5 changed files with 44 additions and 61 deletions

View File

@@ -86,19 +86,14 @@ export const clearIndexedDB = createAction({
});
export const createTestUsers = createAction({
name: "Create test users",
name: "Create 10 test users",
icon: <UserIcon />,
section: DeveloperSection,
visible: () => env.ENVIRONMENT === "development",
perform: async () => {
const count = 10;
try {
await client.post("/developer.create_test_users", { count });
toast.message(`${count} test users created`);
} catch (err) {
toast.error(err.message);
}
await client.post("/developer.create_test_users", { count });
toast.message(`${count} test users created`);
},
});

View File

@@ -251,17 +251,13 @@ export const unpublishDocument = createAction({
return;
}
try {
await document.unpublish();
await document.unpublish();
toast.success(
t("Unpublished {{ documentName }}", {
documentName: document.noun,
})
);
} catch (err) {
toast.error(err.message);
}
toast.success(
t("Unpublished {{ documentName }}", {
documentName: document.noun,
})
);
},
});
@@ -288,9 +284,7 @@ export const subscribeDocument = createAction({
}
const document = stores.documents.get(activeDocumentId);
await document?.subscribe();
toast.success(t("Subscribed to document notifications"));
},
});
@@ -540,17 +534,13 @@ export const pinDocumentToCollection = createAction({
return;
}
try {
const document = stores.documents.get(activeDocumentId);
await document?.pin(document.collectionId);
const document = stores.documents.get(activeDocumentId);
await document?.pin(document.collectionId);
const collection = stores.collections.get(activeCollectionId);
const collection = stores.collections.get(activeCollectionId);
if (!collection || !location.pathname.startsWith(collection?.url)) {
toast.success(t("Pinned to collection"));
}
} catch (err) {
toast.error(err.message);
if (!collection || !location.pathname.startsWith(collection?.url)) {
toast.success(t("Pinned to collection"));
}
},
});
@@ -583,14 +573,10 @@ export const pinDocumentToHome = createAction({
}
const document = stores.documents.get(activeDocumentId);
try {
await document?.pin();
await document?.pin();
if (location.pathname !== homePath()) {
toast.success(t("Pinned to home"));
}
} catch (err) {
toast.error(err.message);
if (location.pathname !== homePath()) {
toast.success(t("Pinned to home"));
}
},
});
@@ -641,21 +627,16 @@ export const importDocument = createAction({
input.onchange = async (ev) => {
const files = getEventFiles(ev);
try {
const file = files[0];
const document = await documents.import(
file,
activeDocumentId,
activeCollectionId,
{
publish: true,
}
);
history.push(document.url);
} catch (err) {
toast.error(err.message);
throw err;
}
const file = files[0];
const document = await documents.import(
file,
activeDocumentId,
activeCollectionId,
{
publish: true,
}
);
history.push(document.url);
};
input.click();

View File

@@ -74,13 +74,7 @@ export function actionToMenuItem(
icon,
visible,
dangerous: action.dangerous,
onClick: () => {
try {
action.perform?.(context);
} catch (err) {
toast.error(err.message);
}
},
onClick: () => performAction(action, context),
selected: action.selected?.(context),
};
}
@@ -114,10 +108,18 @@ export function actionToKBar(
keywords: action.keywords ?? "",
shortcut: action.shortcut || [],
icon: resolvedIcon,
perform: action.perform ? () => action.perform?.(context) : undefined,
perform: action.perform
? () => performAction(action, context)
: undefined,
},
].concat(
// @ts-expect-error ts-migrate(2769) FIXME: No overload matches this call.
children.map((child) => ({ ...child, parent: child.parent ?? action.id }))
);
}
export async function performAction(action: Action, context: ActionContext) {
return action.perform?.(context).catch((err: Error) => {
toast.error(err.message);
});
}

View File

@@ -1,6 +1,7 @@
/* eslint-disable react/prop-types */
import * as React from "react";
import Tooltip, { Props as TooltipProps } from "~/components/Tooltip";
import { performAction } from "~/actions";
import { Action, ActionContext } from "~/types";
export type Props = React.HTMLAttributes<HTMLButtonElement> & {
@@ -60,10 +61,10 @@ const ActionButton = React.forwardRef<HTMLButtonElement, Props>(
? (ev) => {
ev.preventDefault();
ev.stopPropagation();
const response = action.perform?.(actionContext);
const response = performAction(action, actionContext);
if (response?.finally) {
setExecuting(true);
response.finally(() => setExecuting(false));
void response.finally(() => setExecuting(false));
}
}
: rest.onClick

View File

@@ -107,6 +107,10 @@ export type Action = {
placeholder?: ((context: ActionContext) => string) | string;
selected?: (context: ActionContext) => boolean;
visible?: (context: ActionContext) => boolean;
/**
* Perform the action note this should generally not be called directly, use `performAction`
* instead. Errors will be caught and displayed to the user as a toast message.
*/
perform?: (context: ActionContext) => Promise<any> | any;
children?: ((context: ActionContext) => Action[]) | Action[];
};