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>
<!-- TODO - perhaps eventually add a lightmode toggle -->
<html lang="en" class="dark">
<head>
<meta charset="UTF-8" />

View file

@ -158,18 +158,14 @@
"setup_prompt_selectISO": "Select your legitimately obtained ISO File",
"sidebar_help": "Help",
"sidebar_settings": "Settings",
"splash_button_deleteOldInstallDir_no": "No",
"splash_button_deleteOldInstallDir_yes": "Yes",
"splash_button_setInstallFolder_prompt": "Pick an Installation 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_selectLocale": "Select Locale",
"splash_step_loadingTranslations": "Loading Translations",
"splash_step_checkingDirectories": "Checking Directories",
"splash_step_errorOpening": "Problem opening Launcher",
"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_progressReports": "Progress Reports",
"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">
import { openMainWindow } from "$lib/rpc/window";
import { onMount } from "svelte";
import logo from "$assets/images/icon.webp";
import { folderPrompt } from "$lib/utils/file-dialogs";
import {
deleteOldDataDirectory,
getInstallationDirectory,
getLocale,
oldDataDirectoryExists,
setInstallationDirectory,
setLocale,
} from "$lib/rpc/config";
import { AVAILABLE_LOCALES } from "$lib/i18n/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 currentStatusText = "Loading Locales...";
let selectLocale = false;
let installationDirSet = true;
let stepError = undefined;
let oldDataDirToClean = false;
let loaded = false;
let timeStartedAt = 0;
let minimumTime = 500;
let stepsToDo = [
{
statusText: "splash_step_loadingTranslations",
func: async () => {
await checkLocale();
},
waitingForInteraction: false,
},
{
statusText: "splash_step_checkingDirectories",
func: async () => {
await checkDirectories();
},
waitingForInteraction: false,
},
];
let currentStepIndex = 0;
let errorText = "";
// Events
onMount(async () => {
// First, see if the user has selected a locale
await checkLocale();
currentStatusText = $_("splash_step_readingSettings");
// Ensure a default locale is set
await svelteLocale.set("en-US");
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
// the game setup
async function proceedInSteps(stepForward: boolean, stepBackward: boolean) {
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() {
const locale = await getLocale();
if (locale === null) {
// Prompt the user to select a locale
selectLocale = true;
svelteLocale.set("en-US");
stepsToDo.splice(currentStepIndex + 1, 0, {
statusText: "splash_selectLocale",
waitingForInteraction: true,
func: async () => {},
});
} else {
// Set locale and continue
setLocale(locale);
await checkDirectories();
}
}
async function checkDirectories() {
currentStatusText = $_("splash_step_checkingDirectories");
currentProgress = 15;
// Check to see if the install dir has been setup or not
const install_dir = await getInstallationDirectory();
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
installationDirSet = false;
} else {
finishSplash();
stepsToDo.splice(currentStepIndex + 1, 0, {
statusText: "splash_noInstallDirSet",
waitingForInteraction: true,
func: async () => {},
});
}
}
async function finishSplash() {
currentProgress = 50;
currentStatusText = $_("splash_step_finishingUp");
await new Promise((res) => setTimeout(res, 1000));
currentProgress = 100;
await new Promise((res) => setTimeout(res, 500));
const errorClosing = await openMainWindow();
if (!errorClosing) {
currentStatusText = $_("splash_step_errorOpening");
async function handleLocaleChange(event: any, forStep: boolean) {
await setLocale(event.detail.newLocale);
if (forStep) {
await proceedInSteps(true, false);
}
}
async function handleLocaleChange(evt: Event) {
const selectElement = evt.target as HTMLSelectElement;
setLocale(selectElement.value);
selectLocale = false;
await checkDirectories();
}
</script>
<div class="content" data-tauri-drag-region>
<div class="splash-logo no-pointer-events">
<img
src={logo}
data-testId="splash-logo"
alt="OpenGOAL logo"
aria-label="OpenGOAL logo"
draggable="false"
/>
</div>
<div class="splash-contents no-pointer-events">
{#if selectLocale}
<span class="mb-1">{$_("splash_selectLocale")}</span>
<div class="splash-select">
<select
data-testId="locale-select"
name="locales"
id="locales"
class="pointer-events emoji-font"
on:change={handleLocaleChange}
>
<option disabled selected value hidden />
{#each AVAILABLE_LOCALES as locale}
<option class="emoji-font" value={locale.id}
>{locale.flag}&nbsp;{locale.localizedName}</option
>
{/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}
{#if loaded}
<div class="splash-logo pointer-events-none">
<img
src={logo}
data-testId="splash-logo"
alt="OpenGOAL logo"
aria-label="OpenGOAL logo"
draggable="false"
/>
</div>
<div class="splash-contents pointer-events-none">
{#if errorText !== ""}
<div class="splash-status-text">
{errorText}
</div>
{:else if stepsToDo[currentStepIndex].statusText === "splash_selectLocale"}
<div class="splash-status-text">
{$_(stepsToDo[currentStepIndex].statusText)}
</div>
<SelectLocale on:change={(evt) => handleLocaleChange(evt, true)} />
{:else if stepsToDo[currentStepIndex].statusText === "splash_noInstallDirSet"}
<ChooseInstallFolder
on:complete={async () => {
await proceedInSteps(true, false);
}}
/>
{:else}
{$_("splash_noInstallDirSet")}
<div class="splash-status-text">
{$_(stepsToDo[currentStepIndex].statusText)}
</div>
{/if}
<br />
<button
data-testId="pick-install-folder-button"
class="splash-button pointer-events"
on:click={async () => {
// This is part of what allows for the user to install the games and such wherever they want
currentStatusText = $_("splash_step_pickInstallFolder");
currentProgress = 25;
const newInstallDir = await folderPrompt(
$_("splash_button_setInstallFolder_prompt"),
);
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>
</div>
<div class="splash-bar">
<div
data-tauri-drag-region
class="splash-status-bar fg"
style="width: {((currentStepIndex + 1) / stepsToDo.length) * 100}%"
/>
<div data-tauri-drag-region class="splash-status-bar bg" />
</div>
{#if stepsToDo[currentStepIndex].statusText === "splash_noInstallDirSet"}
<LocaleQuickChanger on:change={(evt) => handleLocaleChange(evt, false)} />
{/if}
</div>
<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>
{/if}
</div>
<style>
@ -221,64 +214,10 @@
background-color: #775500;
position: absolute;
}
.splash-status-bar.fg {
background-color: #ffb807;
position: absolute;
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>

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>
</head>
<style>
body {
margin: 0;
overflow: hidden;
}
#app {
height: 100vh;
background-color: #202020;
}
</style>
<body>
<div id="app"></div>
<body class="m-0 overflow-hidden">
<div id="app" class="h-screen bg-gray-900"></div>
</body>
</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 () => {
mockIPC((cmd, args) => {
console.log(`Unhandled Tauri IPC: ${cmd}`);
});
render(Splash, {});
const logo = screen.getByTestId("splash-logo");
expect(logo).toBeTruthy();
await waitFor(() => {
expect(screen.getByTestId("splash-logo")).toBeTruthy();
});
});
it("should display the locale dropdown", async () => {
@ -61,37 +65,6 @@ describe("Splash.svelte", () => {
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 () => {
// TODO - generalize into function
// return an object that tracks mock calls / args
@ -144,11 +117,13 @@ describe("Splash.svelte", () => {
});
vi.mocked(folderPrompt).mockResolvedValue("/wow/good/job/nice/folder");
render(Splash, {});
let pickInstallFolderButton = await screen.findByTestId(
"pick-install-folder-button",
);
expect(pickInstallFolderButton).toBeTruthy();
fireEvent.click(pickInstallFolderButton);
await waitFor(async () => {
let pickInstallFolderButton = await screen.findByTestId(
"pick-install-folder-button",
);
expect(pickInstallFolderButton).toBeTruthy();
fireEvent.click(pickInstallFolderButton);
});
await waitFor(() => {
expect(setInstallDirectorySet).toBeTruthy();
});
@ -182,15 +157,17 @@ describe("Splash.svelte", () => {
});
vi.mocked(folderPrompt).mockResolvedValue("/wow/good/job/nice/folder");
render(Splash, {});
let pickInstallFolderButton = await screen.findByTestId(
"pick-install-folder-button",
);
expect(pickInstallFolderButton).toBeTruthy();
fireEvent.click(pickInstallFolderButton);
await waitFor(async () => {
let pickInstallFolderButton = await screen.findByTestId(
"pick-install-folder-button",
);
expect(pickInstallFolderButton).toBeTruthy();
fireEvent.click(pickInstallFolderButton);
});
await waitFor(() => {
screen.findByText("wow that was a terrible directory");
});
pickInstallFolderButton = await screen.findByTestId(
let pickInstallFolderButton = await screen.findByTestId(
"pick-install-folder-button",
);
expect(pickInstallFolderButton).toBeTruthy();

View file

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