frontend: show installation progress properly

This commit is contained in:
Tyler Wilding 2023-02-18 16:53:46 -05:00
parent a5e3fcd1d4
commit e58a4067ce
No known key found for this signature in database
GPG key ID: 77CB07796494137E
11 changed files with 494 additions and 488 deletions

View file

@ -1,10 +1,5 @@
<script type="ts">
import { launcherConfig } from "$lib/config";
import {
gameNeedsReinstall,
isInstalling,
ProcessLogs,
} from "$lib/stores/AppStore";
import { ProcessLogs, InstallationProgress } from "$lib/stores/AppStore";
import { checkRequirements } from "$lib/setup/setup";
// components
import Progress from "./Progress.svelte";
@ -31,29 +26,44 @@
onMount(async () => {
// NOTE - potentially has problems if the user changes hardware
if (!(await launcherConfig.areRequirementsMet())) {
await checkRequirements();
}
requirementsMet = await launcherConfig.areRequirementsMet();
// TODO
// if (!(await launcherConfig.areRequirementsMet())) {
// await checkRequirements();
// }
requirementsMet = true; //await launcherConfig.areRequirementsMet();
});
async function installViaISO() {
const isoPath = await isoPrompt();
if (isoPath !== undefined) {
installing = true;
// TODO - reset installation steps
ProcessLogs.update(() => "");
// TODO - handle errors and such
// TODO - get rid of hard-coding
// TODO - methods!
$InstallationProgress.currentStep = 0;
$InstallationProgress.steps[0].status = "pending";
await extractAndValidateISO(isoPath, "jak1");
$InstallationProgress.steps[0].status = "success";
$InstallationProgress.currentStep = 1;
$InstallationProgress.steps[1].status = "pending";
await runDecompiler(isoPath, "jak1");
$InstallationProgress.steps[1].status = "success";
$InstallationProgress.currentStep = 2;
$InstallationProgress.steps[2].status = "pending";
await runCompiler(isoPath, "jak1");
$InstallationProgress.steps[2].status = "success";
$InstallationProgress.currentStep = 3;
$InstallationProgress.steps[3].status = "pending";
await finalizeInstallation("jak1");
// if (success) {
// dispatch("change");
// }
$InstallationProgress.steps[3].status = "success";
}
}
async function dispatchSetupEvent() {
dispatch("change");
}
</script>
<!-- TODO - allow passing a folder path -->
@ -67,6 +77,16 @@
<LogViewer />
{/if}
</div>
{#if $InstallationProgress.currentStep === 3 && $InstallationProgress.steps[3].status === "success"}
<div class="flex flex-col justify-end items-end mt-auto">
<div class="flex flex-row gap-2">
<Button
btnClass="border-solid border-2 border-slate-900 rounded bg-slate-900 hover:bg-slate-800 text-sm text-white font-semibold px-5 py-2"
on:click={async () => await dispatchSetupEvent()}>Continue</Button
>
</div>
</div>
{/if}
{:else}
<div class="flex flex-col justify-end items-end mt-auto">
<h1

View file

@ -1,13 +1,13 @@
<script>
import { ProcessLogs } from "$lib/stores/AppStore";
import Icon from '@iconify/svelte';
import Icon from "@iconify/svelte";
import { Accordion, AccordionItem } from "flowbite-svelte";
</script>
<Accordion class="log-accordian" defaultClass="p-0">
<AccordionItem class="bg-slate-900 rounded p-[1rem]">
<span slot="header" class="text font-semibold text-white flex gap-2">
<Icon icon="mdi:file-document-outline" width={24}/>
<Icon icon="mdi:file-document-outline" width={24} />
<span>Logs</span>
</span>
<div

View file

@ -1,26 +1,26 @@
<script lang="ts">
import { InstallStatus } from "$lib/stores/AppStore";
import { InstallationProgress } from "$lib/stores/AppStore";
import Icon from "@iconify/svelte";
// $: progress = $InstallStatus;
$: progress = $InstallationProgress;
// NOTE - useful for debugging:
let installationProgress = {
currentStep: 0,
steps: [
{
status: "success",
},
{
status: "pending",
},
{
status: "queued",
},
{
status: "queued",
},
],
};
// let progress = {
// currentStep: 0,
// steps: [
// {
// status: "success",
// },
// {
// status: "pending",
// },
// {
// status: "queued",
// },
// {
// status: "queued",
// },
// ],
// };
const iconContainerStyle =
"w-10 h-10 mx-auto border-solid border-2 border-slate-800 bg-slate-900 rounded-full text-lg text-white flex justify-center items-center";
@ -29,8 +29,7 @@
"w-full rounded items-center align-middle align-center flex-1";
// TODO - this pattern indicates these should probably be their own components...
function progressIcon(stepNum: number) {
const currentStatus = installationProgress.steps[stepNum].status;
function progressIcon(currentStatus: string) {
if (currentStatus === "success") {
return "material-symbols:check";
} else if (currentStatus === "pending") {
@ -41,17 +40,15 @@
return "mdi:hourglass";
}
function progressIconStyle(stepNum: number) {
function progressIconStyle(currentStatus: string) {
let style = "";
const currentStatus = installationProgress.steps[stepNum].status;
if (currentStatus === "pending") {
style += " animate-pulse";
}
return style;
}
function progressIconColor(stepNum: number) {
const currentStatus = installationProgress.steps[stepNum].status;
function progressIconColor(currentStatus: string) {
if (currentStatus === "success") {
return "#22c55e";
} else if (currentStatus === "pending") {
@ -62,9 +59,8 @@
return "#737373";
}
function progressBarStyle(stepNum: number) {
function progressBarStyle(currentStatus: string) {
let style = "w-full py-1 rounded";
const currentStatus = installationProgress.steps[stepNum].status;
if (currentStatus === "success") {
style += " bg-green-500";
} else if (currentStatus === "pending") {
@ -87,9 +83,9 @@
<!-- EXTRACTING AND VERIFYING -->
<div class={iconContainerStyle}>
<Icon
class={progressIconStyle(0)}
icon={progressIcon(0)}
color={progressIconColor(0)}
class={progressIconStyle(progress.steps[0].status)}
icon={progressIcon(progress.steps[0].status)}
color={progressIconColor(progress.steps[0].status)}
width={28}
height={28}
/>
@ -106,15 +102,15 @@
style="width: calc(100% - 2.5rem - 1rem); top: 50%; transform: translate(-50%, -50%)"
>
<div class={progressBarContainerStyle}>
<div class={progressBarStyle(0)} />
<div class={progressBarStyle(progress.steps[0].status)} />
</div>
</div>
<!-- DECOMPILING -->
<div class={iconContainerStyle}>
<Icon
class={progressIconStyle(1)}
icon={progressIcon(1)}
color={progressIconColor(1)}
class={progressIconStyle(progress.steps[1].status)}
icon={progressIcon(progress.steps[1].status)}
color={progressIconColor(progress.steps[1].status)}
width={28}
height={28}
/>
@ -129,15 +125,15 @@
style="width: calc(100% - 2.5rem - 1rem); top: 50%; transform: translate(-50%, -50%)"
>
<div class={progressBarContainerStyle}>
<div class={progressBarStyle(1)} />
<div class={progressBarStyle(progress.steps[1].status)} />
</div>
</div>
<!-- COMPILING -->
<div class={iconContainerStyle}>
<Icon
class={progressIconStyle(2)}
icon={progressIcon(2)}
color={progressIconColor(2)}
class={progressIconStyle(progress.steps[2].status)}
icon={progressIcon(progress.steps[2].status)}
color={progressIconColor(progress.steps[2].status)}
width={28}
height={28}
/>
@ -152,15 +148,15 @@
style="width: calc(100% - 2.5rem - 1rem); top: 50%; transform: translate(-50%, -50%)"
>
<div class={progressBarContainerStyle}>
<div class={progressBarStyle(2)} />
<div class={progressBarStyle(progress.steps[2].status)} />
</div>
</div>
<!-- READY -->
<div class={iconContainerStyle}>
<Icon
class={progressIconStyle(3)}
icon={progressIcon(3)}
color={progressIconColor(3)}
class={progressIconStyle(progress.steps[3].status)}
icon={progressIcon(progress.steps[3].status)}
color={progressIconColor(progress.steps[3].status)}
width={28}
height={28}
/>

View file

@ -1,19 +1,10 @@
<script>
import { launcherConfig } from "$lib/config";
import {
fromRoute,
getGameTitle,
getInternalName,
SupportedGame,
} from "$lib/constants";
import { fromRoute, getInternalName, SupportedGame } from "$lib/constants";
import { useParams } from "svelte-navigator";
import GameControls from "../components/games/GameControls.svelte";
import GameSetup from "../components/games/setup/GameSetup.svelte";
import { onMount } from "svelte";
import { gameNeedsReinstall, isInstalling } from "$lib/stores/AppStore";
import { isDataDirectoryUpToDate } from "$lib/utils/data-files";
import Outdated from "../components/games/setup/Outdated.svelte";
import Reinstall from "../components/games/setup/Reinstall.svelte";
import { Spinner } from "flowbite-svelte";
import { isGameInstalled } from "$lib/rpc/config";
@ -49,8 +40,8 @@
});
async function updateGameState(evt) {
gameInstalled = await launcherConfig.getInstallStatus(activeGame);
dataDirUpToDate = await isDataDirectoryUpToDate();
gameInstalled = await isGameInstalled(getInternalName(activeGame));
// TODO - check data dir?
}
</script>

View file

@ -32,35 +32,35 @@
header and the rest of the layout aren't within a shared container -->
<div class="flex flex-col h-[544px] bg-slate-900">
<!-- https://flowbite-svelte.com/components/tab#Tabs_with_icons -->
<Tabs
style="underline"
divider={false}
contentClass="p-4 pt-0 rounded-lg mt-2 pb-20 overflow-y-auto"
<Tabs
style="underline"
divider={false}
contentClass="p-4 pt-0 rounded-lg mt-2 pb-20 overflow-y-auto"
>
<TabItem
open={!activeTab || activeTab === "general"}
title="General"
activeClasses={tabItemActiveClasses}
inactiveClasses={tabItemInactiveClasses}
>
<TabItem
open={!activeTab || activeTab === "general"}
title="General"
activeClasses={tabItemActiveClasses}
inactiveClasses={tabItemInactiveClasses}
>
<General />
</TabItem>
<TabItem
open={activeTab === "folders"}
title="Folders"
activeClasses={tabItemActiveClasses}
inactiveClasses={tabItemInactiveClasses}
>
<Folders />
</TabItem>
<TabItem
open={activeTab === "versions"}
title="Version Management"
activeClasses={tabItemActiveClasses}
inactiveClasses={tabItemInactiveClasses}
>
<Versions />
</TabItem>
</Tabs>
<General />
</TabItem>
<TabItem
open={activeTab === "folders"}
title="Folders"
activeClasses={tabItemActiveClasses}
inactiveClasses={tabItemInactiveClasses}
>
<Folders />
</TabItem>
<TabItem
open={activeTab === "versions"}
title="Version Management"
activeClasses={tabItemActiveClasses}
inactiveClasses={tabItemInactiveClasses}
>
<Versions />
</TabItem>
</Tabs>
</div>
</div>

View file

@ -5,7 +5,6 @@
import { appDir, join } from "@tauri-apps/api/path";
import { removeDir, removeFile } from "@tauri-apps/api/fs";
import { SupportedGame } from "$lib/constants";
import { decompileFromFile } from "$lib/setup/setup";
import { confirm } from "@tauri-apps/api/dialog";
import {
Alert,
@ -32,8 +31,10 @@
let selectedTexturePacks: string[] = [];
$: disabled = false;
// TODO - deferring this work
onMount(async () => {
packs = await getAllTexturePacks();
// packs = await getAllTexturePacks();
});
async function handleSelectedPacks(pack) {
@ -52,7 +53,7 @@
async function handleAddTexturePack() {
try {
await texturePackPrompt();
packs = await getAllTexturePacks();
// packs = await getAllTexturePacks();
} catch (error) {
console.error(error);
}
@ -108,7 +109,7 @@
// extract texture packs in (proper) order to texture_replacements (proper order: for overridding purposes)
await extractTextures(selectedTexturePacks);
// await decompile game (similar to GameControls function, maybe that function should be moved into a seperate file)
await decompileFromFile(SupportedGame.Jak1);
// await decompileFromFile(SupportedGame.Jak1);
// should be ready to play (fingers crossed)
} catch (err) {
console.error(err);
@ -119,7 +120,8 @@
<div class="ml-20">
<div class="flex flex-col h-[560px] max-h-[560px] p-8 gap-2">
{#if packs && packs.length > 0}
TODO
<!-- {#if packs && packs.length > 0}
<Table hoverable={true}>
<TableHead>
<TableHeadCell class="!p-4" />
@ -177,6 +179,6 @@
>Compile the game with the selected packs in the order they were
selected</Tooltip
>
</ButtonGroup>
</ButtonGroup> -->
</div>
</div>

View file

@ -26,7 +26,7 @@
</script>
<div class="ml-20">
<div class="flex flex-col h-[560px] max-h-[560px] p-8 gap-2">
<div class="flex flex-col h-[544px] p-8 gap-2">
{#if $UpdateStore.shouldUpdate}
<Table hoverable={true}>
<caption

View file

@ -1,35 +1,35 @@
<script lang="ts">
import {
getInstallationDirectory,
setInstallationDirectory,
} from "$lib/rpc/config";
import { folderPrompt } from "$lib/utils/file";
import { Label, Input } from "flowbite-svelte";
import { onMount } from "svelte";
let currentInstallationDirectory = "";
onMount(async () => {
currentInstallationDirectory = await getInstallationDirectory();
});
</script>
<div class="flex flex-col gap-2 mt-2">
<div>
<Label for="default-input" class="block mb-2">Installation Directory</Label>
<Input
id="default-input"
placeholder={currentInstallationDirectory}
on:click={async () => {
const newInstallDir = await folderPrompt("Pick an Installation Folder");
if (
newInstallDir !== undefined &&
newInstallDir !== currentInstallationDirectory
) {
await setInstallationDirectory(newInstallDir);
currentInstallationDirectory = newInstallDir;
}
}}
/>
</div>
</div>
<script lang="ts">
import {
getInstallationDirectory,
setInstallationDirectory,
} from "$lib/rpc/config";
import { folderPrompt } from "$lib/utils/file";
import { Label, Input } from "flowbite-svelte";
import { onMount } from "svelte";
let currentInstallationDirectory = "";
onMount(async () => {
currentInstallationDirectory = await getInstallationDirectory();
});
</script>
<div class="flex flex-col gap-2 mt-2">
<div>
<Label for="default-input" class="block mb-2">Installation Directory</Label>
<Input
id="default-input"
placeholder={currentInstallationDirectory}
on:click={async () => {
const newInstallDir = await folderPrompt("Pick an Installation Folder");
if (
newInstallDir !== undefined &&
newInstallDir !== currentInstallationDirectory
) {
await setInstallationDirectory(newInstallDir);
currentInstallationDirectory = newInstallDir;
}
}}
/>
</div>
</div>

View file

@ -1,6 +1,4 @@
<script>
</script>
<p class="text-sm text-slate-100 dark:text-gray-00 mt-2">
Nothing yet
</p>
<script>
</script>
<p class="text-sm text-slate-100 dark:text-gray-00 mt-2">Nothing yet</p>

View file

@ -1,326 +1,323 @@
<script lang="ts">
import { Alert, Button, Tabs, TabItem, Radio } from "flowbite-svelte";
import {
Table,
TableBody,
TableBodyCell,
TableBodyRow,
TableHead,
TableHeadCell,
} from "flowbite-svelte";
import Icon from "@iconify/svelte";
import { onMount } from "svelte";
import {
downloadOfficialVersion,
getActiveVersion,
listDownloadedVersions,
openVersionFolder,
saveActiveVersionChange,
VersionFolders,
} from "$lib/rpc/versions";
let componentLoaded = false;
let currentOfficialVersion = undefined;
let selectedOfficialVersion = undefined;
const tabItemActiveClasses =
"inline-block text-sm font-bold text-center disabled:cursor-not-allowed p-4 text-orange-500 border-b-2 border-orange-500 dark:text-orange-500 dark:border-orange-500";
const tabItemInactiveClasses =
"inline-block text-sm font-normal text-center disabled:cursor-not-allowed p-4 border-b-2 border-transparent text-gray-400 hover:text-orange-300 hover:border-orange-500 dark:hover:text-orange-300 dark:text-orange-400";
interface Release {
version: string;
date: string | undefined;
githubLink: string | undefined;
downloadUrl: string | undefined; // TODO - windows/mac/linux
isDownloaded: boolean;
}
let officialReleases: Release[] = [];
onMount(async () => {
// TODO - check when this is null
currentOfficialVersion = await getActiveVersion();
selectedOfficialVersion = currentOfficialVersion;
await refreshOfficialVersionList(undefined);
// TODO - spinner
componentLoaded = true;
});
async function refreshOfficialVersionList(evt) {
// Check the backend to see if the folder has any versions
const installedVersions = await listDownloadedVersions(
VersionFolders.OFFICIAL
);
officialReleases = [];
for (const version of installedVersions) {
officialReleases = [
...officialReleases,
{
version: version,
date: undefined,
githubLink: undefined,
downloadUrl: undefined,
isDownloaded: true
},
];
}
// TODO - confirmation on deletion / tauri command to handle that
// TODO - "no releases found"
// Merge that with the actual current releases on github
// TODO - handle rate limiting
// TODO - long term - handle pagination (more than 100 releases)
// TODO - even longer term - extract this out into an API we control (avoid github rate limiting) -- will be needed for unofficial releases as well anyway
const resp = await fetch(
"https://api.github.com/repos/open-goal/jak-project/releases?per_page=100"
);
// TODO - handle error
const githubReleases = await resp.json();
for (const release of githubReleases) {
// Look to see if we already have this release downloaded and we just have to fill in some metadata about it
let foundExistingRelease = false;
for (const existingRelease of officialReleases) {
if (existingRelease.version == release.tag_name) {
existingRelease.date = release.published_at;
existingRelease.githubLink = release.html_url;
existingRelease.downloadUrl =
"https://github.com/open-goal/jak-project/releases/download/v0.1.32/opengoal-windows-v0.1.32.zip";
foundExistingRelease = true;
break;
}
}
if (foundExistingRelease) {
continue;
}
officialReleases = [
...officialReleases,
{
version: release.tag_name,
date: release.published_at,
githubLink: release.html_url,
downloadUrl:
"https://github.com/open-goal/jak-project/releases/download/v0.1.32/opengoal-windows-v0.1.32.zip",
isDownloaded: false
},
];
}
// Sort releases by published date
officialReleases = officialReleases.sort((a, b) => b.date.localeCompare(a.date));
selectedOfficialVersion = "v0.1.32";
}
async function saveOfficialVersionChange(evt) {
await saveActiveVersionChange(
VersionFolders.OFFICIAL,
selectedOfficialVersion
);
currentOfficialVersion = selectedOfficialVersion;
}
async function openOfficialVersionFolder(evt) {
openVersionFolder(VersionFolders.OFFICIAL);
}
async function onDownloadOfficialVersion(version: String, url: String) {
await downloadOfficialVersion(version, url);
}
</script>
<!-- TODO - clean up duplication here with some components for each page -->
<div>
<p class="text-sm mt-1 mb-1">
Configure your active <strong>tooling</strong> version
</p>
<Tabs style="pill" contentClass="p-4 rounded-lg mt-0 pb-20 overflow-y-auto">
<TabItem
open
activeClasses={tabItemActiveClasses}
inactiveClasses={tabItemInactiveClasses}
>
<span slot="title">Official</span>
<div class="flex items-center mb-2">
<div class="grow">
<p class="text-sm text-gray-400 dark:text-gray-300">
Official versions are from the `jak-project` GitHub repository
</p>
</div>
<div class="flex">
{#if currentOfficialVersion != selectedOfficialVersion}
<Button
btnClass="!p-2 mr-2 rounded-md dark:bg-green-500 hover:dark:bg-green-600"
on:click={saveOfficialVersionChange}
>
<Icon
icon="material-symbols:save"
width="20"
height="20"
alt="save official version change"
/>
</Button>
{/if}
<Button btnClass="!p-2 mr-2 rounded-md dark:bg-orange-500 hover:dark:bg-orange-600" on:click={refreshOfficialVersionList}>
<Icon
icon="material-symbols:refresh"
width="20"
height="20"
alt="refresh official version list"
/>
</Button>
<Button btnClass="!p-2 rounded-md dark:bg-orange-500 hover:dark:bg-orange-600" on:click={openOfficialVersionFolder}>
<Icon
icon="material-symbols:folder-open-rounded"
width="20"
height="20"
alt="open official version folder"
/>
</Button>
</div>
</div>
<Table>
<TableHead>
<TableHeadCell>
<span class="sr-only">Select</span>
</TableHeadCell>
<TableHeadCell>
<span class="sr-only">Controls</span>
</TableHeadCell>
<TableHeadCell>Version</TableHeadCell>
<TableHeadCell>Date</TableHeadCell>
<TableHeadCell>Github Link</TableHeadCell>
</TableHead>
<TableBody tableBodyClass="divide-y">
{#each officialReleases as release (release.version)}
<TableBodyRow>
<TableBodyCell tdClass="px-6 py-2 whitespace-nowrap font-medium">
{#if release.isDownloaded}
<Radio
class="disabled:cursor-not-allowed p-0"
bind:group={selectedOfficialVersion}
value={release.version}
disabled={!release.isDownloaded}
name="official-release"
/>
{/if}
</TableBodyCell>
<TableBodyCell
tdClass="px-6 py-2 whitespace-nowrap font-medium"
style="line-height: 0;"
>
<Button
btnClass="dark:bg-transparent hover:dark:bg-transparent focus:ring-0 focus:ring-offset-0"
on:click={() =>
onDownloadOfficialVersion(
release.version,
release.downloadUrl
)}
>
{#if release.isDownloaded}
<Icon
icon="ic:baseline-delete-forever"
width="24"
height="24"
color="red"
/>
{:else}
<Icon
icon="ic:baseline-download"
color="#00d500"
width="24"
height="24"
/>
{/if}
</Button>
</TableBodyCell>
<TableBodyCell tdClass="px-6 py-2 whitespace-nowrap font-medium"
>{release.version}</TableBodyCell
>
<TableBodyCell tdClass="px-6 py-2 whitespace-nowrap font-medium"
>{new Date(release.date).toLocaleDateString()}</TableBodyCell
>
<TableBodyCell tdClass="px-6 py-2 whitespace-nowrap font-medium"
><a href={release.githubLink} target="_blank" rel="noreferrer"
><Icon icon="mdi:github" width="24" height="24" /></a
>
</TableBodyCell>
</TableBodyRow>
{/each}
</TableBody>
</Table>
</TabItem>
<TabItem
activeClasses={tabItemActiveClasses}
inactiveClasses={tabItemInactiveClasses}
>
<span slot="title">Unofficial</span>
<p class="text-sm text-gray-400 dark:text-gray-300">
Unofficial versions are typically modified `jak-project` releases to
enable changes or new content
</p>
<div class="flex items-center mt-2 mb-2">
<div class="grow">
<p class="text-sm text-gray-400 dark:text-gray-300">
These are not supported by the OpenGOAL team and will have to be
manually added to the folder at this time
</p>
</div>
<div class="flex">
<Button class="!p-2 mr-2 dark:bg-orange-600 dark:hover:bg-orange-500">
<Icon
icon="material-symbols:refresh"
width="20"
height="20"
alt="refresh official version list"
/>
</Button>
<Button class="!p-2 dark:bg-orange-600 dark:hover:bg-orange-500">
<Icon
icon="material-symbols:folder-open-rounded"
width="20"
height="20"
alt="open official version folder"
/>
</Button>
</div>
</div>
</TabItem>
<TabItem
activeClasses={tabItemActiveClasses}
inactiveClasses={tabItemInactiveClasses}
>
<span slot="title">Development</span>
<p class="text-sm text-gray-400 dark:text-gray-300">
This list serves as a convenient area to stage, manage and test new
releases (either official or unofficial)
</p>
<div class="flex items-center mt-2 mb-2">
<div class="grow">
<p class="text-sm text-gray-400 dark:text-gray-300">
This list will always require manual management via it's respective
folder
</p>
</div>
<div class="flex">
<Button class="!p-2 mr-2 dark:bg-orange-600 dark:hover:bg-orange-500">
<Icon
icon="material-symbols:refresh"
width="20"
height="20"
alt="refresh official version list"
/>
</Button>
<Button class="!p-2 dark:bg-orange-600 dark:hover:bg-orange-500">
<Icon
icon="material-symbols:folder-open-rounded"
width="20"
height="20"
alt="open official version folder"
/>
</Button>
</div>
</div>
</TabItem>
</Tabs>
</div>
<script lang="ts">
import { Alert, Button, Tabs, TabItem, Radio } from "flowbite-svelte";
import {
Table,
TableBody,
TableBodyCell,
TableBodyRow,
TableHead,
TableHeadCell,
} from "flowbite-svelte";
import Icon from "@iconify/svelte";
import { onMount } from "svelte";
import {
downloadOfficialVersion,
getActiveVersion,
listDownloadedVersions,
openVersionFolder,
saveActiveVersionChange,
VersionFolders,
} from "$lib/rpc/versions";
import {
listOfficialReleases,
type OfficialRelease,
} from "$lib/utils/github";
let componentLoaded = false;
let currentOfficialVersion = undefined;
let selectedOfficialVersion = undefined;
const tabItemActiveClasses =
"inline-block text-sm font-bold text-center disabled:cursor-not-allowed p-4 text-orange-500 border-b-2 border-orange-500 dark:text-orange-500 dark:border-orange-500";
const tabItemInactiveClasses =
"inline-block text-sm font-normal text-center disabled:cursor-not-allowed p-4 border-b-2 border-transparent text-gray-400 hover:text-orange-300 hover:border-orange-500 dark:hover:text-orange-300 dark:text-orange-400";
let officialReleases: OfficialRelease[] = [];
onMount(async () => {
// TODO - check when this is null
currentOfficialVersion = await getActiveVersion();
selectedOfficialVersion = currentOfficialVersion;
await refreshOfficialVersionList(undefined);
// TODO - spinner
componentLoaded = true;
});
async function refreshOfficialVersionList(evt) {
// Check the backend to see if the folder has any versions
const installedVersions = await listDownloadedVersions(
VersionFolders.OFFICIAL
);
officialReleases = [];
for (const version of installedVersions) {
officialReleases = [
...officialReleases,
{
version: version,
date: undefined,
githubLink: undefined,
downloadUrl: undefined,
isDownloaded: true,
},
];
}
// TODO - confirmation on deletion / tauri command to handle that
// TODO - "no releases found"
// Merge that with the actual current releases on github
const githubReleases = await listOfficialReleases();
for (const release of githubReleases) {
// Look to see if we already have this release downloaded and we just have to fill in some metadata about it
let foundExistingRelease = false;
for (const existingRelease of officialReleases) {
if (existingRelease.version === release.version) {
existingRelease.date = release.date;
existingRelease.githubLink = release.githubLink;
existingRelease.downloadUrl =
"https://github.com/open-goal/jak-project/releases/download/v0.1.32/opengoal-windows-v0.1.32.zip";
foundExistingRelease = true;
break;
}
}
if (foundExistingRelease) {
continue;
}
officialReleases = [
...officialReleases,
{
version: release.version,
date: release.date,
githubLink: release.githubLink,
downloadUrl:
"https://github.com/open-goal/jak-project/releases/download/v0.1.32/opengoal-windows-v0.1.32.zip",
isDownloaded: false,
},
];
}
// Sort releases by published date
officialReleases = officialReleases.sort((a, b) =>
b.date.localeCompare(a.date)
);
selectedOfficialVersion = "v0.1.32";
}
async function saveOfficialVersionChange(evt) {
await saveActiveVersionChange(
VersionFolders.OFFICIAL,
selectedOfficialVersion
);
currentOfficialVersion = selectedOfficialVersion;
}
async function openOfficialVersionFolder(evt) {
openVersionFolder(VersionFolders.OFFICIAL);
}
async function onDownloadOfficialVersion(version: String, url: String) {
await downloadOfficialVersion(version, url);
}
</script>
<!-- TODO - clean up duplication here with some components for each page -->
<div>
<p class="text-sm mt-1 mb-1">
Configure your active <strong>tooling</strong> version
</p>
<Tabs style="pill" contentClass="p-4 rounded-lg mt-0 pb-20 overflow-y-auto">
<TabItem
open
activeClasses={tabItemActiveClasses}
inactiveClasses={tabItemInactiveClasses}
>
<span slot="title">Official</span>
<div class="flex items-center mb-2">
<div class="grow">
<p class="text-sm text-gray-400 dark:text-gray-300">
Official versions are from the `jak-project` GitHub repository
</p>
</div>
<div class="flex">
{#if currentOfficialVersion != selectedOfficialVersion}
<Button
btnClass="!p-2 mr-2 rounded-md dark:bg-green-500 hover:dark:bg-green-600 text-slate-900"
on:click={saveOfficialVersionChange}
>
<Icon
icon="material-symbols:save"
width="20"
height="20"
alt="save official version change"
/>
</Button>
{/if}
<Button
btnClass="!p-2 mr-2 rounded-md dark:bg-orange-500 hover:dark:bg-orange-600 text-slate-900"
on:click={refreshOfficialVersionList}
>
<Icon
icon="material-symbols:refresh"
width="20"
height="20"
alt="refresh official version list"
/>
</Button>
<Button
btnClass="!p-2 rounded-md dark:bg-orange-500 hover:dark:bg-orange-600 text-slate-900"
on:click={openOfficialVersionFolder}
>
<Icon
icon="material-symbols:folder-open-rounded"
width="20"
height="20"
alt="open official version folder"
/>
</Button>
</div>
</div>
<Table>
<TableHead>
<TableHeadCell>
<span class="sr-only">Select</span>
</TableHeadCell>
<TableHeadCell>
<span class="sr-only">Controls</span>
</TableHeadCell>
<TableHeadCell>Version</TableHeadCell>
<TableHeadCell>Date</TableHeadCell>
<TableHeadCell>Changes Link</TableHeadCell>
</TableHead>
<TableBody tableBodyClass="divide-y">
{#each officialReleases as release (release.version)}
<TableBodyRow>
<TableBodyCell tdClass="px-6 py-2 whitespace-nowrap font-medium">
{#if release.isDownloaded}
<Radio
class="disabled:cursor-not-allowed p-0"
bind:group={selectedOfficialVersion}
value={release.version}
disabled={!release.isDownloaded}
name="official-release"
/>
{/if}
</TableBodyCell>
<TableBodyCell
tdClass="px-6 py-2 whitespace-nowrap font-medium"
style="line-height: 0;"
>
<Button
btnClass="dark:bg-transparent hover:dark:bg-transparent focus:ring-0 focus:ring-offset-0"
on:click={() =>
onDownloadOfficialVersion(
release.version,
release.downloadUrl
)}
>
{#if release.isDownloaded}
<Icon
icon="ic:baseline-delete-forever"
width="24"
height="24"
color="red"
/>
{:else}
<Icon
icon="ic:baseline-download"
color="#00d500"
width="24"
height="24"
/>
{/if}
</Button>
</TableBodyCell>
<TableBodyCell tdClass="px-6 py-2 whitespace-nowrap font-medium"
>{release.version}</TableBodyCell
>
<TableBodyCell tdClass="px-6 py-2 whitespace-nowrap font-medium"
>{new Date(release.date).toLocaleDateString()}</TableBodyCell
>
<TableBodyCell tdClass="px-6 py-2 whitespace-nowrap font-medium"
><a href={release.githubLink} target="_blank" rel="noreferrer"
><Icon icon="mdi:github" width="24" height="24" /></a
>
</TableBodyCell>
</TableBodyRow>
{/each}
</TableBody>
</Table>
</TabItem>
<TabItem
activeClasses={tabItemActiveClasses}
inactiveClasses={tabItemInactiveClasses}
>
<span slot="title">Unofficial</span>
<p class="text-sm text-gray-400 dark:text-gray-300">
Unofficial versions are typically modified `jak-project` releases to
enable changes or new content
</p>
<div class="flex items-center mt-2 mb-2">
<div class="grow">
<p class="text-sm text-gray-400 dark:text-gray-300">
These are not supported by the OpenGOAL team and will have to be
manually added to the folder at this time
</p>
</div>
<div class="flex">
<Button btnClass="!p-2 mr-2 rounded-md dark:bg-orange-500 hover:dark:bg-orange-600 text-slate-900">
<Icon
icon="material-symbols:refresh"
width="20"
height="20"
alt="refresh official version list"
/>
</Button>
<Button btnClass="!p-2 rounded-md dark:bg-orange-500 hover:dark:bg-orange-600 text-slate-900">
<Icon
icon="material-symbols:folder-open-rounded"
width="20"
height="20"
alt="open official version folder"
/>
</Button>
</div>
</div>
</TabItem>
<TabItem
activeClasses={tabItemActiveClasses}
inactiveClasses={tabItemInactiveClasses}
>
<span slot="title">Development</span>
<p class="text-sm text-gray-400 dark:text-gray-300">
This list serves as a convenient area to stage, manage and test new
releases (either official or unofficial)
</p>
<div class="flex items-center mt-2 mb-2">
<div class="grow">
<p class="text-sm text-gray-400 dark:text-gray-300">
This list will always require manual management via it's respective
folder
</p>
</div>
<div class="flex">
<Button btnClass="!p-2 mr-2 rounded-md dark:bg-orange-500 hover:dark:bg-orange-600 text-slate-900">
<Icon
icon="material-symbols:refresh"
width="20"
height="20"
alt="refresh official version list"
/>
</Button>
<Button btnClass="!p-2 rounded-md dark:bg-orange-500 hover:dark:bg-orange-600 text-slate-900">
<Icon
icon="material-symbols:folder-open-rounded"
width="20"
height="20"
alt="open official version folder"
/>
</Button>
</div>
</div>
</TabItem>
</Tabs>
</div>

View file

@ -27,20 +27,20 @@
});
async function selectInstallationFolder(evt) {
// If not -- let's ask the user to set one up
// This is part of what allows for the user to install the games and such wherever they want
currentStatusText = "Pick an Installation Folder";
// TODO - change to a save dialog instead
const new_install_dir = await folderPrompt("Pick an Installation Folder");
// TODO - put invokes into a nice typescript interface
if (new_install_dir !== undefined) {
await invoke("set_install_directory", {"newDir": new_install_dir});
// TODO - we are kinda assuming it succeeded here, improve that
// - what if the install directory no longer exists
// - what if what they provide isn't writable?
installationDirSet = true;
finishSplash();
}
// If not -- let's ask the user to set one up
// This is part of what allows for the user to install the games and such wherever they want
currentStatusText = "Pick an Installation Folder";
// TODO - change to a save dialog instead
const new_install_dir = await folderPrompt("Pick an Installation Folder");
// TODO - put invokes into a nice typescript interface
if (new_install_dir !== undefined) {
await invoke("set_install_directory", { newDir: new_install_dir });
// TODO - we are kinda assuming it succeeded here, improve that
// - what if the install directory no longer exists
// - what if what they provide isn't writable?
installationDirSet = true;
finishSplash();
}
}
async function finishSplash(evt) {
@ -77,7 +77,9 @@
{#if installationDirSet}
<div class="splash-status-text">{currentStatusText}</div>
{:else}
<button class="splash-button" on:click={selectInstallationFolder}>Select Install Folder</button>
<button class="splash-button" on:click={selectInstallationFolder}
>Select Install Folder</button
>
{/if}
</div>
<div>