Toast updates (#405)

Toast improvements:
- Icon and color based on toast level (info, warn, error)
- Animates out
- Made it a component, removing logic from `app.svelte`
- handles multiple toast messages better using a queue

Sidebar styling logic:
This was kinda gross so I revised it a bit. I'm considering doing an
overhaul of the sidebar in the future.
This commit is contained in:
tripp 2023-12-09 15:11:06 -05:00 committed by GitHub
parent d85a4af9fa
commit 0cfbd3609a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 87 additions and 69 deletions

View file

@ -8,11 +8,9 @@
import Background from "./components/background/Background.svelte";
import Header from "./components/header/Header.svelte";
import Update from "./routes/Update.svelte";
import GameInProgress from "./components/games/GameInProgress.svelte";
import { isInDebugMode } from "$lib/utils/common";
import { Toast } from "flowbite-svelte";
import Toast from "./components/toast/Toast.svelte";
import Help from "./routes/Help.svelte";
import { toastStore } from "$lib/stores/ToastStore";
import { isLoading } from "svelte-i18n";
import { getLocale, setLocale } from "$lib/rpc/config";
import GameFeature from "./routes/GameFeature.svelte";
@ -95,16 +93,7 @@
<Route path="/update" component={Update} primary={false} />
</div>
</div>
{#if $toastStore.msg !== undefined}
<!-- TODO - make these look nice for info/warn/error levels -->
<Toast
color="green"
position="top-right"
class="w-full max-w-xs p-2 pl-4 z-50 top-20"
>
{$toastStore.msg}
</Toast>
{/if}
<Toast />
{/if}
</div>
</Router>

View file

@ -18,6 +18,7 @@
import { VersionStore } from "$lib/stores/VersionStore";
import { exceptionLog, infoLog } from "$lib/rpc/logging";
import { _ } from "svelte-i18n";
import { toastStore } from "$lib/stores/ToastStore";
let launcherVerison = null;
@ -53,6 +54,7 @@
changeLog: changeLog,
};
infoLog(`Launcher Update Available`);
toastStore.makeToast("Launcher update available!", "info");
} else {
$UpdateStore.launcher = {
updateAvailable: false,

View file

@ -12,37 +12,24 @@
$: $location.pathname;
function getNavStyle(pathname: string): string {
let style = "grow-0 shrink-0 basis-1/10 h-full bg-[#101010] px-1 z-10";
if (
!pathname.startsWith("/settings") &&
!pathname.startsWith("/faq") &&
!pathname.startsWith("/update")
) {
style += " opacity-50 hover:opacity-100 duration-500";
}
return style;
const baseStyle =
"grow-0 shrink-0 basis-1/10 h-full bg-[#101010] px-1 z-10";
const isOpaque =
pathname.startsWith("/settings") ||
pathname.startsWith("/faq") ||
pathname.startsWith("/update");
return isOpaque
? baseStyle
: `${baseStyle} opacity-50 hover:opacity-100 duration-500`;
}
function getNavItemStyle(itemName: string, pathName: string): string {
let style =
"flex items-center hover:grayscale-0 hover:opacity-100 duration-500 text-orange-400 duration-500";
if (
itemName === "jak1" &&
(pathName.startsWith("/jak1") || pathName === "/")
) {
return style;
} else if (itemName === "jak2" && pathName.startsWith("/jak2")) {
return style;
} else if (itemName === "jak3" && pathName.startsWith("/jak3")) {
return style;
} else if (itemName === "jakx" && pathName.startsWith("/jakx")) {
return style;
} else if (itemName === "settings" && pathName.startsWith("/settings")) {
return style;
} else if (itemName === "faq" && pathName === "/faq") {
return style;
}
return style + " grayscale";
const baseStyle =
"flex items-center hover:grayscale-0 hover:opacity-100 duration-500 text-orange-400";
const isActive =
pathName.startsWith(`/${itemName}`) ||
(itemName === "jak1" && pathName === "/");
return isActive ? baseStyle : `${baseStyle} grayscale`;
}
function modifyGameTitleName(gameName: string): string {

View file

@ -0,0 +1,50 @@
<script>
import { Toast } from "flowbite-svelte";
import { toastStore } from "$lib/stores/ToastStore";
import { fly } from "svelte/transition";
import IconCheck from "~icons/mdi/check";
import IconAlert from "~icons/mdi/stop-alert";
import IconAlertCircle from "~icons/mdi/alert-circle";
let open = false;
let counter;
let currentToast = null;
$: if ($toastStore.length > 0 && !currentToast) {
currentToast = $toastStore[0];
open = true;
counter = 6;
timeout();
}
function timeout() {
if (--counter > 0) return setTimeout(timeout, 1000);
open = false;
toastStore.removeToast();
currentToast = null;
}
</script>
{#if currentToast}
<Toast
{open}
dismissable={false}
position="top-right"
class="z-50 top-20"
transition={fly}
params={{ y: 200 }}
>
<svelte:fragment slot="icon">
{#if currentToast.level == "info"}
<IconCheck class="text-green-500 text-5xl" />
{:else if currentToast.level == "warn"}
<IconAlertCircle class="text-orange-500 text-5xl" />
{:else if currentToast.level == "error"}
<IconAlert class="text-red-500 text-5xl" />
{/if}
</svelte:fragment>
<div class="ps-4 text-sm font-semibold">
{currentToast.msg}
</div>
</Toast>
{/if}

View file

@ -1,6 +1,6 @@
import { toastStore } from "$lib/stores/ToastStore";
import { invoke_rpc } from "./rpc";
// TODO - toasts
// TODO - just make this a generic interface for both binaries/feature jobs
interface FeatureJobOutput {
msg: string | null;
@ -8,6 +8,7 @@ interface FeatureJobOutput {
}
function failed(msg: string): FeatureJobOutput {
toastStore.makeToast(msg, "error");
return { success: false, msg };
}

View file

@ -2,43 +2,28 @@ import { writable } from "svelte/store";
export type ToastLevel = "error" | "warn" | "info" | undefined;
interface ToastStore {
msg: string | undefined;
interface ToastMessage {
msg: string;
level: ToastLevel;
interval: any;
interval?: any;
}
const storeValue: ToastStore = {
msg: undefined,
level: undefined,
interval: undefined,
};
type ToastArray = ToastMessage[];
const messageArray: ToastArray = [];
function createToastStore() {
// TODO - the TTL isn't correct still, look into it
const { subscribe, set, update } = writable<ToastStore>(storeValue);
const ttl = 5000;
let timeoutId: NodeJS.Timer;
function ttlCheck() {
return setTimeout(() => {
update((val) => {
val.msg = undefined;
val.level = undefined;
timeoutId = undefined;
return val;
});
}, ttl);
}
const { subscribe, update } = writable<ToastArray>(messageArray);
return {
subscribe,
removeToast: () =>
update((val) => {
val = val.slice(1);
return val;
}),
makeToast: (msg: string, level: ToastLevel) =>
update((val) => {
val.msg = msg;
val.level = level;
timeoutId = ttlCheck();
val.push({ msg, level });
return val;
}),
};

View file

@ -12,6 +12,7 @@
import { VersionStore } from "$lib/stores/VersionStore";
import { saveActiveVersionChange } from "$lib/rpc/config";
import { _ } from "svelte-i18n";
import { toastStore } from "$lib/stores/ToastStore";
let versionsLoaded = false;
@ -61,6 +62,7 @@
$VersionStore.activeVersionName = $VersionStore.selectedVersions.devel;
$VersionStore.selectedVersions.official = null;
$VersionStore.selectedVersions.unofficial = null;
toastStore.makeToast("Saved game version!", "info");
}
}

View file

@ -12,6 +12,7 @@
import { VersionStore } from "$lib/stores/VersionStore";
import { saveActiveVersionChange } from "$lib/rpc/config";
import { _ } from "svelte-i18n";
import { toastStore } from "$lib/stores/ToastStore";
let versionsLoaded = false;
@ -64,6 +65,7 @@
$VersionStore.selectedVersions.unofficial;
$VersionStore.selectedVersions.official = null;
$VersionStore.selectedVersions.devel = null;
toastStore.makeToast("Saved game version!", "info");
}
}