aboutsummaryrefslogtreecommitdiff
path: root/src/lib/CommandPalette
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-04-26 12:40:27 +0000
committerFuwn <[email protected]>2026-04-26 12:40:27 +0000
commit5accc4e512e9bbff8d4303cb732edccaccd9b5a3 (patch)
tree3c51680e1cabb45941a7716cb769d04b8cb56c06 /src/lib/CommandPalette
parentfix(tooltip): park off-screen on create, enable glide after first placement (diff)
downloaddue.moe-5accc4e512e9bbff8d4303cb732edccaccd9b5a3.tar.xz
due.moe-5accc4e512e9bbff8d4303cb732edccaccd9b5a3.zip
feat(command-palette): add quick toggles, sync, and auth actions
Adds 13 reactive quick toggles (24h time, animations, blur adult, cover modes, hover cover, schedule list, reverse sort, data saver, notifications, language, title format cycle, outbound link target cycle), three sync actions (push/pull/disable), and login/logout entries gated on auth state. Names reflect current state so the palette doubles as a status surface.
Diffstat (limited to 'src/lib/CommandPalette')
-rw-r--r--src/lib/CommandPalette/authActions.ts48
-rw-r--r--src/lib/CommandPalette/syncActions.ts106
-rw-r--r--src/lib/CommandPalette/toggleActions.ts160
3 files changed, 314 insertions, 0 deletions
diff --git a/src/lib/CommandPalette/authActions.ts b/src/lib/CommandPalette/authActions.ts
new file mode 100644
index 00000000..c306eb34
--- /dev/null
+++ b/src/lib/CommandPalette/authActions.ts
@@ -0,0 +1,48 @@
+import { env } from "$env/dynamic/public";
+import root from "$lib/Utility/root";
+import localforage from "localforage";
+import type { CommandPaletteAction } from "./actions";
+
+export const authActions = (
+ user: string | undefined,
+): CommandPaletteAction[] => {
+ if (user)
+ return [
+ {
+ name: "Log Out",
+ url: "#",
+ preventDefault: true,
+ tags: ["auth", "sign", "out", "user"],
+ onClick: async () => {
+ await localforage.removeItem("identity");
+ await localforage.removeItem("commit");
+
+ document.cookie =
+ "user=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
+
+ window.location.href = root("/api/authentication/log-out");
+ },
+ },
+ ];
+
+ const loginUrl = `https://anilist.co/api/v2/oauth/authorize?client_id=${env.PUBLIC_ANILIST_CLIENT_ID}&redirect_uri=${env.PUBLIC_ANILIST_REDIRECT_URI}&response_type=code`;
+
+ return [
+ {
+ name: "Log In",
+ url: loginUrl,
+ preventDefault: true,
+ tags: ["auth", "sign", "in", "anilist"],
+ onClick: async () => {
+ await localforage.setItem(
+ "redirect",
+ window.location.origin +
+ window.location.pathname +
+ window.location.search,
+ );
+
+ window.location.href = loginUrl;
+ },
+ },
+ ];
+};
diff --git a/src/lib/CommandPalette/syncActions.ts b/src/lib/CommandPalette/syncActions.ts
new file mode 100644
index 00000000..c787f348
--- /dev/null
+++ b/src/lib/CommandPalette/syncActions.ts
@@ -0,0 +1,106 @@
+import { addNotification } from "$lib/Notification/store";
+import { options } from "$lib/Notification/options";
+import root from "$lib/Utility/root";
+import settings from "$stores/settings";
+import settingsSyncPulled from "$stores/settingsSyncPulled";
+import settingsSyncTimes from "$stores/settingsSyncTimes";
+import { get } from "svelte/store";
+import type { CommandPaletteAction } from "./actions";
+
+export const syncActions = (
+ identityId: number,
+ syncEnabled: boolean,
+): CommandPaletteAction[] => {
+ if (identityId <= 0) return [];
+
+ const actions: CommandPaletteAction[] = [
+ {
+ name: "Push Settings Now",
+ url: "#",
+ preventDefault: true,
+ tags: ["settings", "sync", "push", "upload", "remote"],
+ onClick: () => {
+ settings.setKey("settingsSync", true);
+
+ fetch(root(`/api/configuration`), {
+ method: "PUT",
+ body: JSON.stringify(get(settings)),
+ })
+ .then((response) => {
+ if (!response.ok) return;
+
+ addNotification(
+ options({
+ heading: "Settings Sync",
+ description: "Pushed local configuration to remote",
+ }),
+ );
+
+ settingsSyncTimes.update((times) => ({
+ ...times,
+ lastPush: new Date(),
+ }));
+ })
+ .catch((error) =>
+ console.error("Settings sync push failed", error),
+ );
+ },
+ },
+ {
+ name: "Pull Settings Now",
+ url: "#",
+ preventDefault: true,
+ tags: ["settings", "sync", "pull", "download", "remote"],
+ onClick: () => {
+ settings.setKey("settingsSync", true);
+
+ fetch(root(`/api/configuration?id=${identityId}`))
+ .then((response) => {
+ if (!response.ok) return;
+
+ return response.json().then((data) => {
+ if (!data?.configuration) {
+ addNotification(
+ options({
+ heading: "Settings Sync",
+ description: "No remote configuration found",
+ }),
+ );
+
+ return;
+ }
+
+ settings.set(data.configuration);
+ settingsSyncPulled.set(true);
+ settingsSyncTimes.set({
+ lastPull: new Date(),
+ lastPush: new Date(`${data.updated_at}Z`),
+ });
+
+ addNotification(
+ options({ heading: "Pulled remote configuration" }),
+ );
+ });
+ })
+ .catch((error) =>
+ console.error("Settings sync pull failed", error),
+ );
+ },
+ },
+ ];
+
+ if (syncEnabled)
+ actions.push({
+ name: "Disable Settings Sync",
+ url: "#",
+ preventDefault: true,
+ tags: ["settings", "sync", "disable", "off", "stop"],
+ onClick: () => {
+ settings.setKey("settingsSync", false);
+
+ addNotification(options({ heading: "Settings sync disabled" }));
+ },
+ });
+
+ return actions;
+};
diff --git a/src/lib/CommandPalette/toggleActions.ts b/src/lib/CommandPalette/toggleActions.ts
new file mode 100644
index 00000000..b93b5626
--- /dev/null
+++ b/src/lib/CommandPalette/toggleActions.ts
@@ -0,0 +1,160 @@
+import settings, { type Settings } from "$stores/settings";
+import type { CommandPaletteAction } from "./actions";
+
+const TITLE_FORMATS: Settings["displayTitleFormat"][] = [
+ "english",
+ "romaji",
+ "native",
+];
+
+const OUTBOUND_TARGETS: Settings["displayOutboundLinksTo"][] = [
+ "anilist",
+ "livechartme",
+ "animeschedule",
+ "myanimelist",
+];
+
+const OUTBOUND_LABELS: Record<Settings["displayOutboundLinksTo"], string> = {
+ anilist: "AniList",
+ livechartme: "LiveChart",
+ animeschedule: "AnimeSchedule",
+ myanimelist: "MyAnimeList",
+};
+
+type BooleanKey = NonNullable<
+ {
+ [K in keyof Settings]: Settings[K] extends boolean ? K : never;
+ }[keyof Settings]
+>;
+
+const boolToggle = (
+ current: boolean,
+ key: BooleanKey,
+ enableLabel: string,
+ disableLabel: string,
+ tags: string[],
+): CommandPaletteAction => ({
+ name: current ? disableLabel : enableLabel,
+ url: "#",
+ preventDefault: true,
+ tags,
+ onClick: () => settings.setKey(key, !current),
+});
+
+export const toggleActions = (current: Settings): CommandPaletteAction[] => {
+ const titleIndex = TITLE_FORMATS.indexOf(current.displayTitleFormat);
+ const nextTitle = TITLE_FORMATS[(titleIndex + 1) % TITLE_FORMATS.length];
+
+ const outboundIndex = OUTBOUND_TARGETS.indexOf(current.displayOutboundLinksTo);
+ const nextOutbound =
+ OUTBOUND_TARGETS[(outboundIndex + 1) % OUTBOUND_TARGETS.length];
+
+ return [
+ boolToggle(
+ current.display24HourTime,
+ "display24HourTime",
+ "Switch to 24-hour time",
+ "Switch to 12-hour time",
+ ["time", "clock", "24h", "12h", "format"],
+ ),
+ boolToggle(
+ current.displayDisableAnimations,
+ "displayDisableAnimations",
+ "Disable animations",
+ "Enable animations",
+ ["motion", "animation", "accessibility"],
+ ),
+ boolToggle(
+ current.displayBlurAdultContent,
+ "displayBlurAdultContent",
+ "Blur adult content",
+ "Show adult content unblurred",
+ ["nsfw", "adult", "blur", "censor"],
+ ),
+ boolToggle(
+ current.displayCoverModeAnime,
+ "displayCoverModeAnime",
+ "Show anime covers",
+ "Hide anime covers",
+ ["cover", "image", "anime", "display"],
+ ),
+ boolToggle(
+ current.displayCoverModeManga,
+ "displayCoverModeManga",
+ "Show manga covers",
+ "Hide manga covers",
+ ["cover", "image", "manga", "novels", "display"],
+ ),
+ boolToggle(
+ current.displayHoverCover,
+ "displayHoverCover",
+ "Enable hover cover preview",
+ "Disable hover cover preview",
+ ["cover", "hover", "preview"],
+ ),
+ boolToggle(
+ current.displayScheduleListMode,
+ "displayScheduleListMode",
+ "Schedule: list mode",
+ "Schedule: grid mode",
+ ["schedule", "list", "grid", "view"],
+ ),
+ boolToggle(
+ current.displayReverseSort,
+ "displayReverseSort",
+ "Reverse sort order",
+ "Restore default sort order",
+ ["sort", "reverse", "order"],
+ ),
+ boolToggle(
+ current.displayDataSaver,
+ "displayDataSaver",
+ "Enable data saver",
+ "Disable data saver",
+ ["data", "bandwidth", "saver", "performance"],
+ ),
+ boolToggle(
+ current.displayDisableNotifications,
+ "displayDisableNotifications",
+ "Disable in-app notifications",
+ "Enable in-app notifications",
+ ["notifications", "alerts", "toast"],
+ ),
+ {
+ name:
+ current.displayLanguage === "en"
+ ? "Switch language to 日本語"
+ : "Switch language to English",
+ url: "#",
+ preventDefault: true,
+ tags: ["language", "locale", "translate", "japanese", "english"],
+ onClick: () =>
+ settings.setKey(
+ "displayLanguage",
+ current.displayLanguage === "en" ? "ja" : "en",
+ ),
+ },
+ {
+ name: `Title format: ${current.displayTitleFormat} → ${nextTitle}`,
+ url: "#",
+ preventDefault: true,
+ tags: ["title", "format", "english", "romaji", "native"],
+ onClick: () => settings.setKey("displayTitleFormat", nextTitle),
+ },
+ {
+ name: `Outbound links: ${OUTBOUND_LABELS[current.displayOutboundLinksTo]} → ${OUTBOUND_LABELS[nextOutbound]}`,
+ url: "#",
+ preventDefault: true,
+ tags: [
+ "outbound",
+ "links",
+ "anilist",
+ "livechart",
+ "animeschedule",
+ "myanimelist",
+ "mal",
+ ],
+ onClick: () => settings.setKey("displayOutboundLinksTo", nextOutbound),
+ },
+ ];
+};