splash: cleanup splash implementation, allow changing locale during installation dir step (#552)

This commit is contained in:
Tyler Wilding 2024-09-26 21:35:31 -04:00 committed by GitHub
parent bb69285beb
commit a2fa5fc8a0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 282 additions and 253 deletions

View file

@ -1,5 +1,4 @@
<!doctype html> <!doctype html>
<!-- TODO - perhaps eventually add a lightmode toggle -->
<html lang="en" class="dark"> <html lang="en" class="dark">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />

View file

@ -158,18 +158,14 @@
"setup_prompt_selectISO": "Select your legitimately obtained ISO File", "setup_prompt_selectISO": "Select your legitimately obtained ISO File",
"sidebar_help": "Help", "sidebar_help": "Help",
"sidebar_settings": "Settings", "sidebar_settings": "Settings",
"splash_button_deleteOldInstallDir_no": "No",
"splash_button_deleteOldInstallDir_yes": "Yes",
"splash_button_setInstallFolder_prompt": "Pick an Installation Folder", "splash_button_setInstallFolder_prompt": "Pick an Installation Folder",
"splash_button_setInstallFolder": "Set Install Folder", "splash_button_setInstallFolder": "Set Install Folder",
"splash_deleteOldInstallDir": "The old installation folder is no longer needed, delete it?",
"splash_noInstallDirSet": "No installation folder set!", "splash_noInstallDirSet": "No installation folder set!",
"splash_selectLocale": "Select Locale", "splash_selectLocale": "Select Locale",
"splash_step_loadingTranslations": "Loading Translations",
"splash_step_checkingDirectories": "Checking Directories", "splash_step_checkingDirectories": "Checking Directories",
"splash_step_errorOpening": "Problem opening Launcher", "splash_step_errorOpening": "Problem opening Launcher",
"splash_step_finishingUp": "Finishing Up", "splash_step_finishingUp": "Finishing Up",
"splash_step_pickInstallFolder": "Pick an Installation Folder",
"splash_step_readingSettings": "Reading Settings",
"temp_jak2_indev_header": "Jak II is Currently in Development", "temp_jak2_indev_header": "Jak II is Currently in Development",
"temp_jak2_indev_progressReports": "Progress Reports", "temp_jak2_indev_progressReports": "Progress Reports",
"temp_jak2_indev_subheader": "In the meantime, check out our latest progress reports showcasing what we've accomplished so far", "temp_jak2_indev_subheader": "In the meantime, check out our latest progress reports showcasing what we've accomplished so far",

View file

@ -1,179 +1,172 @@
<script lang="ts"> <script lang="ts">
import { openMainWindow } from "$lib/rpc/window";
import { onMount } from "svelte"; import { onMount } from "svelte";
import logo from "$assets/images/icon.webp"; import logo from "$assets/images/icon.webp";
import { folderPrompt } from "$lib/utils/file-dialogs";
import { import {
deleteOldDataDirectory,
getInstallationDirectory, getInstallationDirectory,
getLocale, getLocale,
oldDataDirectoryExists,
setInstallationDirectory,
setLocale, setLocale,
} from "$lib/rpc/config"; } from "$lib/rpc/config";
import { AVAILABLE_LOCALES } from "$lib/i18n/i18n";
import { locale as svelteLocale, _ } from "svelte-i18n"; import { locale as svelteLocale, _ } from "svelte-i18n";
import SelectLocale from "./components/SelectLocale.svelte";
import ChooseInstallFolder from "./components/ChooseInstallFolder.svelte";
import LocaleQuickChanger from "./components/LocaleQuickChanger.svelte";
import { openMainWindow } from "$lib/rpc/window";
let currentProgress = 10; let loaded = false;
let currentStatusText = "Loading Locales..."; let timeStartedAt = 0;
let minimumTime = 500;
let selectLocale = false; let stepsToDo = [
let installationDirSet = true; {
let stepError = undefined; statusText: "splash_step_loadingTranslations",
let oldDataDirToClean = false; func: async () => {
await checkLocale();
},
waitingForInteraction: false,
},
{
statusText: "splash_step_checkingDirectories",
func: async () => {
await checkDirectories();
},
waitingForInteraction: false,
},
];
let currentStepIndex = 0;
let errorText = "";
// Events // Events
onMount(async () => { onMount(async () => {
// First, see if the user has selected a locale // Ensure a default locale is set
await checkLocale(); await svelteLocale.set("en-US");
currentStatusText = $_("splash_step_readingSettings"); timeStartedAt = Date.now();
stepsToDo.push({
statusText: "splash_step_finishingUp",
func: async () => {
let currentTime = Date.now();
if (currentTime - timeStartedAt < minimumTime) {
await new Promise((res) =>
setTimeout(res, minimumTime - (currentTime - timeStartedAt)),
);
}
const errorClosing = await openMainWindow();
if (!errorClosing) {
errorText = $_("splash_step_errorOpening");
}
},
waitingForInteraction: false,
});
loaded = true;
await proceedInSteps(false, false);
}); });
// TODO - cleanup this code and make it easier to add steps like with async function proceedInSteps(stepForward: boolean, stepBackward: boolean) {
// the game setup if (stepForward) {
currentStepIndex++;
if (currentStepIndex >= stepsToDo.length) {
currentStepIndex = stepsToDo.length - 1;
}
}
if (stepBackward) {
currentStepIndex--;
if (currentStepIndex < 0) {
currentStepIndex = 0;
}
}
// Process as many steps as we can
while (
currentStepIndex < stepsToDo.length &&
!stepsToDo[currentStepIndex].waitingForInteraction
) {
await new Promise((res) => setTimeout(res, 125));
await stepsToDo[currentStepIndex].func();
currentStepIndex++;
}
if (currentStepIndex >= stepsToDo.length) {
currentStepIndex = stepsToDo.length - 1;
}
}
async function checkLocale() { async function checkLocale() {
const locale = await getLocale(); const locale = await getLocale();
if (locale === null) { if (locale === null) {
// Prompt the user to select a locale // Prompt the user to select a locale
selectLocale = true; stepsToDo.splice(currentStepIndex + 1, 0, {
svelteLocale.set("en-US"); statusText: "splash_selectLocale",
waitingForInteraction: true,
func: async () => {},
});
} else { } else {
// Set locale and continue // Set locale and continue
setLocale(locale); setLocale(locale);
await checkDirectories();
} }
} }
async function checkDirectories() { async function checkDirectories() {
currentStatusText = $_("splash_step_checkingDirectories");
currentProgress = 15;
// Check to see if the install dir has been setup or not // Check to see if the install dir has been setup or not
const install_dir = await getInstallationDirectory(); const install_dir = await getInstallationDirectory();
if (install_dir === null) { if (install_dir === null) {
// Check to see if they have the old data directory, ask them if they'd like us to
// remove it
currentProgress = 25;
const hasOldDataDir = await oldDataDirectoryExists();
if (hasOldDataDir) {
oldDataDirToClean = true;
}
// If not -- let's ask the user to set one up // If not -- let's ask the user to set one up
installationDirSet = false; stepsToDo.splice(currentStepIndex + 1, 0, {
} else { statusText: "splash_noInstallDirSet",
finishSplash(); waitingForInteraction: true,
func: async () => {},
});
} }
} }
async function finishSplash() { async function handleLocaleChange(event: any, forStep: boolean) {
currentProgress = 50; await setLocale(event.detail.newLocale);
currentStatusText = $_("splash_step_finishingUp"); if (forStep) {
await new Promise((res) => setTimeout(res, 1000)); await proceedInSteps(true, false);
currentProgress = 100;
await new Promise((res) => setTimeout(res, 500));
const errorClosing = await openMainWindow();
if (!errorClosing) {
currentStatusText = $_("splash_step_errorOpening");
} }
} }
async function handleLocaleChange(evt: Event) {
const selectElement = evt.target as HTMLSelectElement;
setLocale(selectElement.value);
selectLocale = false;
await checkDirectories();
}
</script> </script>
<div class="content" data-tauri-drag-region> <div class="content" data-tauri-drag-region>
<div class="splash-logo no-pointer-events"> {#if loaded}
<img <div class="splash-logo pointer-events-none">
src={logo} <img
data-testId="splash-logo" src={logo}
alt="OpenGOAL logo" data-testId="splash-logo"
aria-label="OpenGOAL logo" alt="OpenGOAL logo"
draggable="false" aria-label="OpenGOAL logo"
/> draggable="false"
</div> />
<div class="splash-contents no-pointer-events"> </div>
{#if selectLocale} <div class="splash-contents pointer-events-none">
<span class="mb-1">{$_("splash_selectLocale")}</span> {#if errorText !== ""}
<div class="splash-select"> <div class="splash-status-text">
<select {errorText}
data-testId="locale-select" </div>
name="locales" {:else if stepsToDo[currentStepIndex].statusText === "splash_selectLocale"}
id="locales" <div class="splash-status-text">
class="pointer-events emoji-font" {$_(stepsToDo[currentStepIndex].statusText)}
on:change={handleLocaleChange} </div>
> <SelectLocale on:change={(evt) => handleLocaleChange(evt, true)} />
<option disabled selected value hidden /> {:else if stepsToDo[currentStepIndex].statusText === "splash_noInstallDirSet"}
{#each AVAILABLE_LOCALES as locale} <ChooseInstallFolder
<option class="emoji-font" value={locale.id} on:complete={async () => {
>{locale.flag}&nbsp;{locale.localizedName}</option await proceedInSteps(true, false);
> }}
{/each} />
</select>
</div>
{:else if oldDataDirToClean}
{$_("splash_deleteOldInstallDir")}
<br />
<span>
<button
data-testId="delete-old-data-dir-button"
class="splash-button pointer-events"
on:click={() => {
oldDataDirToClean = false;
deleteOldDataDirectory();
}}>{$_("splash_button_deleteOldInstallDir_yes")}</button
>
<button
data-testId="dont-delete-old-data-dir-button"
class="splash-button pointer-events"
on:click={() => {
oldDataDirToClean = false;
}}>{$_("splash_button_deleteOldInstallDir_no")}</button
>
</span>
{:else if !installationDirSet}
{#if stepError !== undefined}
{stepError}
{:else} {:else}
{$_("splash_noInstallDirSet")} <div class="splash-status-text">
{$_(stepsToDo[currentStepIndex].statusText)}
</div>
{/if} {/if}
<br /> </div>
<button <div class="splash-bar">
data-testId="pick-install-folder-button" <div
class="splash-button pointer-events" data-tauri-drag-region
on:click={async () => { class="splash-status-bar fg"
// This is part of what allows for the user to install the games and such wherever they want style="width: {((currentStepIndex + 1) / stepsToDo.length) * 100}%"
currentStatusText = $_("splash_step_pickInstallFolder"); />
currentProgress = 25; <div data-tauri-drag-region class="splash-status-bar bg" />
const newInstallDir = await folderPrompt( </div>
$_("splash_button_setInstallFolder_prompt"), {#if stepsToDo[currentStepIndex].statusText === "splash_noInstallDirSet"}
); <LocaleQuickChanger on:change={(evt) => handleLocaleChange(evt, false)} />
if (newInstallDir !== undefined) {
const result = await setInstallationDirectory(newInstallDir);
if (result !== null) {
stepError = result;
} else {
installationDirSet = true;
finishSplash();
}
}
}}>{$_("splash_button_setInstallFolder")}</button
>
{:else}
<div class="splash-status-text">{currentStatusText}</div>
{/if} {/if}
</div> {/if}
<div class="splash-bar">
<div
data-tauri-drag-region
class="splash-status-bar fg"
style="width: {currentProgress}%"
/>
<div data-tauri-drag-region class="splash-status-bar bg" />
</div>
</div> </div>
<style> <style>
@ -221,64 +214,10 @@
background-color: #775500; background-color: #775500;
position: absolute; position: absolute;
} }
.splash-status-bar.fg { .splash-status-bar.fg {
background-color: #ffb807; background-color: #ffb807;
position: absolute; position: absolute;
z-index: 999; z-index: 999;
} }
.splash-button {
margin-top: 5px;
appearance: none;
background-color: #ffb807;
border-radius: 6px;
box-sizing: border-box;
color: #000;
cursor: pointer;
display: inline-block;
font-family: "Roboto Mono", monospace;
font-size: 8pt;
font-weight: 700;
position: relative;
text-align: center;
text-decoration: none;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
vertical-align: middle;
white-space: nowrap;
}
.splash-button:focus:not(:focus-visible):not(.focus-visible) {
box-shadow: none;
outline: none;
}
.splash-button:hover {
background-color: #775500;
}
.splash-button:focus {
outline: none;
}
.splash-button:active {
background-color: #775500;
}
.no-pointer-events {
pointer-events: none;
}
.pointer-events {
pointer-events: auto;
}
.mb-1 {
margin-bottom: 1rem;
}
.emoji-font {
font-family: "Twemoji Country Flags", "Roboto Mono";
}
</style> </style>

View file

@ -0,0 +1,38 @@
<script lang="ts">
import { setInstallationDirectory } from "$lib/rpc/config";
import { folderPrompt } from "$lib/utils/file-dialogs";
import { createEventDispatcher, onMount } from "svelte";
import { _ } from "svelte-i18n";
const dispatch = createEventDispatcher();
let stepError = "";
// Events
onMount(async () => {});
</script>
{#if stepError !== ""}
<p class="text-wrap text-pretty">{stepError}</p>
{:else}
<p class="text-wrap text-pretty">{$_("splash_noInstallDirSet")}</p>
{/if}
<button
data-testId="pick-install-folder-button"
class="splash-button pointer-events-auto bg-orange-500 p-1 mt-1 text-black font-bold rounded hover:bg-orange-700"
on:click={async () => {
stepError = "";
// This is part of what allows for the user to install the games and such wherever they want
const newInstallDir = await folderPrompt(
$_("splash_button_setInstallFolder_prompt"),
);
if (newInstallDir !== undefined) {
const result = await setInstallationDirectory(newInstallDir);
if (result !== null) {
stepError = result;
} else {
dispatch("complete", {});
}
}
}}>{$_("splash_button_setInstallFolder")}</button
>

View file

@ -0,0 +1,50 @@
<script lang="ts">
import { createEventDispatcher, onMount } from "svelte";
import { AVAILABLE_LOCALES } from "$lib/i18n/i18n";
import { _ } from "svelte-i18n";
import { getLocale } from "$lib/rpc/config";
const dispatch = createEventDispatcher();
let currentLocale = "en-US";
// Events
onMount(async () => {
const locale = await getLocale();
if (locale !== null) {
currentLocale = locale;
}
});
</script>
<select
data-testId="locale-select"
name="locales"
id="locales"
title={$_("splash_selectLocale")}
class="emoji-font pointer-events-auto !p-0 !pl-1 !pr-1 !pt-1 text-sm bg-gray-700 mb-1 absolute top-0 border-transparent focus:border-transparent focus:ring-0"
on:change={(evt) => {
let newLocale = evt.target.value;
dispatch("change", {
newLocale: newLocale,
});
}}
>
{#each AVAILABLE_LOCALES as locale}
{#if locale.id === currentLocale}
<option value={locale.id} selected
><span class="emoji-font">{locale.flag}</span></option
>
{:else}
<option value={locale.id}
><span class="emoji-font">{locale.flag}</span></option
>
{/if}
{/each}
</select>
<style>
.emoji-font {
font-family: "Twemoji Country Flags", "Roboto Mono";
}
</style>

View file

@ -0,0 +1,37 @@
<script lang="ts">
import { createEventDispatcher, onMount } from "svelte";
import { AVAILABLE_LOCALES } from "$lib/i18n/i18n";
import { _ } from "svelte-i18n";
const dispatch = createEventDispatcher();
// Events
onMount(async () => {});
</script>
<select
data-testId="locale-select"
name="locales"
id="locales"
class="pointer-events-auto p-0 pl-1 text-xs bg-gray-700 mt-1"
on:change={(evt) => {
let newLocale = evt.target.value;
dispatch("change", {
newLocale: newLocale,
});
}}
>
<option disabled selected value hidden />
{#each AVAILABLE_LOCALES as locale}
<option value={locale.id}
><span class="emoji-font">{locale.flag}</span
>&nbsp;{locale.localizedName}</option
>
{/each}
</select>
<style>
.emoji-font {
font-family: "Twemoji Country Flags", "Roboto Mono";
}
</style>

View file

@ -8,19 +8,7 @@
<title>Splash</title> <title>Splash</title>
</head> </head>
<style> <body class="m-0 overflow-hidden">
body { <div id="app" class="h-screen bg-gray-900"></div>
margin: 0;
overflow: hidden;
}
#app {
height: 100vh;
background-color: #202020;
}
</style>
<body>
<div id="app"></div>
</body> </body>
</html> </html>

View file

@ -0,0 +1,4 @@
/* Write your global styles here, in PostCSS syntax */
@tailwind base;
@tailwind components;
@tailwind utilities;

View file

@ -21,9 +21,13 @@ describe("Splash.svelte", () => {
}); });
it("should render the splash", async () => { it("should render the splash", async () => {
mockIPC((cmd, args) => {
console.log(`Unhandled Tauri IPC: ${cmd}`);
});
render(Splash, {}); render(Splash, {});
const logo = screen.getByTestId("splash-logo"); await waitFor(() => {
expect(logo).toBeTruthy(); expect(screen.getByTestId("splash-logo")).toBeTruthy();
});
}); });
it("should display the locale dropdown", async () => { it("should display the locale dropdown", async () => {
@ -61,37 +65,6 @@ describe("Splash.svelte", () => {
expect(localeSelect.value).toBe("en-US"); expect(localeSelect.value).toBe("en-US");
}); });
it("should prompt user to delete old data directory - delete it", async () => {
// TODO - generalize into function
// return an object that tracks mock calls / args
let oldDataDirDeleted = false;
mockIPC((cmd, args) => {
if (cmd === "get_locale") {
return "en-US";
} else if (cmd === "get_install_directory") {
return null;
} else if (cmd === "has_old_data_directory") {
return true;
} else if (cmd === "delete_old_data_directory") {
oldDataDirDeleted = true;
} else {
console.log(`Unhandled Tauri IPC: ${cmd}`);
}
});
render(Splash, {});
const deleteOldDataDirButton = await screen.findByTestId(
"delete-old-data-dir-button",
);
expect(deleteOldDataDirButton).toBeTruthy();
// delete the dir, it'll go away
fireEvent.click(deleteOldDataDirButton);
expect(oldDataDirDeleted).toBeTruthy();
const pickInstallFolderButton = await screen.findByTestId(
"pick-install-folder-button",
);
expect(pickInstallFolderButton).toBeTruthy();
});
it("should prompt user to select installation directory - cancelled dialog", async () => { it("should prompt user to select installation directory - cancelled dialog", async () => {
// TODO - generalize into function // TODO - generalize into function
// return an object that tracks mock calls / args // return an object that tracks mock calls / args
@ -144,11 +117,13 @@ describe("Splash.svelte", () => {
}); });
vi.mocked(folderPrompt).mockResolvedValue("/wow/good/job/nice/folder"); vi.mocked(folderPrompt).mockResolvedValue("/wow/good/job/nice/folder");
render(Splash, {}); render(Splash, {});
let pickInstallFolderButton = await screen.findByTestId( await waitFor(async () => {
"pick-install-folder-button", let pickInstallFolderButton = await screen.findByTestId(
); "pick-install-folder-button",
expect(pickInstallFolderButton).toBeTruthy(); );
fireEvent.click(pickInstallFolderButton); expect(pickInstallFolderButton).toBeTruthy();
fireEvent.click(pickInstallFolderButton);
});
await waitFor(() => { await waitFor(() => {
expect(setInstallDirectorySet).toBeTruthy(); expect(setInstallDirectorySet).toBeTruthy();
}); });
@ -182,15 +157,17 @@ describe("Splash.svelte", () => {
}); });
vi.mocked(folderPrompt).mockResolvedValue("/wow/good/job/nice/folder"); vi.mocked(folderPrompt).mockResolvedValue("/wow/good/job/nice/folder");
render(Splash, {}); render(Splash, {});
let pickInstallFolderButton = await screen.findByTestId( await waitFor(async () => {
"pick-install-folder-button", let pickInstallFolderButton = await screen.findByTestId(
); "pick-install-folder-button",
expect(pickInstallFolderButton).toBeTruthy(); );
fireEvent.click(pickInstallFolderButton); expect(pickInstallFolderButton).toBeTruthy();
fireEvent.click(pickInstallFolderButton);
});
await waitFor(() => { await waitFor(() => {
screen.findByText("wow that was a terrible directory"); screen.findByText("wow that was a terrible directory");
}); });
pickInstallFolderButton = await screen.findByTestId( let pickInstallFolderButton = await screen.findByTestId(
"pick-install-folder-button", "pick-install-folder-button",
); );
expect(pickInstallFolderButton).toBeTruthy(); expect(pickInstallFolderButton).toBeTruthy();

View file

@ -1,6 +1,7 @@
import { initLocales } from "$lib/i18n/i18n"; import { initLocales } from "$lib/i18n/i18n";
import App from "./Splash.svelte"; import App from "./Splash.svelte";
import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill"; import { polyfillCountryFlagEmojis } from "country-flag-emoji-polyfill";
import "./splash.postcss";
// Register Translations // Register Translations
export default (async () => { export default (async () => {