mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2026-07-25 01:13:57 +10:00
Game updates (#187)
* refactor: split umu launcher * feat: latest version picker + fixes * feat: frontend latest changes * feat: game update detection w/ setting * feat: fixes and refactor for game update * fix: windows ui * fix: deps * feat: update modifications * feat: missing ui and lock update * fix: create install dir on init * fix: clippy * fix: clippy x2 * feat: add configuration option to toggle updates * feat: uninstall dropdown on partiallyinstalled
This commit is contained in:
@@ -11,7 +11,7 @@
|
||||
class="block w-full rounded-md bg-zinc-800 px-3 py-1.5 text-base text-zinc-100 outline-1 -outline-offset-1 outline-zinc-800 placeholder:text-zinc-400 focus:outline-2 focus:-outline-offset-2 focus:outline-blue-600 sm:text-sm/6"
|
||||
placeholder="{}"
|
||||
aria-describedby="launch-description"
|
||||
v-model="model.launchString"
|
||||
v-model="model.launchTemplate"
|
||||
/>
|
||||
</div>
|
||||
<p class="mt-2 text-sm text-zinc-400" id="launch-description">
|
||||
@@ -129,9 +129,9 @@
|
||||
</span>
|
||||
</li>
|
||||
</ListboxOption>
|
||||
<li v-else class="italic text-zinc-400 py-2 pr-9 pl-3"
|
||||
>No auto-discovered layers.</li
|
||||
>
|
||||
<li v-else class="italic text-zinc-400 py-2 pr-9 pl-3">
|
||||
No auto-discovered layers.
|
||||
</li>
|
||||
<h1 class="text-white text-sm font-semibold bg-zinc-900 py-2 px-2">
|
||||
Manually added
|
||||
</h1>
|
||||
@@ -170,9 +170,9 @@
|
||||
</span>
|
||||
</li>
|
||||
</ListboxOption>
|
||||
<li v-else class="italic text-zinc-400 py-2 pr-9 pl-3"
|
||||
>No manually added layers.</li
|
||||
>
|
||||
<li v-else class="italic text-zinc-400 py-2 pr-9 pl-3">
|
||||
No manually added layers.
|
||||
</li>
|
||||
</ListboxOptions>
|
||||
</transition>
|
||||
</div>
|
||||
@@ -190,7 +190,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import type { FrontendGameConfiguration, ProtonPath } from "~/composables/game";
|
||||
import type { ProtonPath } from "~/composables/game";
|
||||
import {
|
||||
Listbox,
|
||||
ListboxButton,
|
||||
@@ -201,8 +201,9 @@ import {
|
||||
import { ChevronUpDownIcon } from "@heroicons/vue/16/solid";
|
||||
import { CheckIcon } from "@heroicons/vue/20/solid";
|
||||
import { WrenchIcon } from "@heroicons/vue/24/solid";
|
||||
import type { GameVersion } from "~/types";
|
||||
|
||||
const model = defineModel<FrontendGameConfiguration>({ required: true });
|
||||
const model = defineModel<GameVersion["userConfiguration"]>({ required: true });
|
||||
|
||||
const props = defineProps<{
|
||||
protonEnabled: boolean;
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<div class="space-y-8">
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<div>
|
||||
<h3 class="text-sm font-medium leading-6 text-zinc-100">
|
||||
Enable update checks
|
||||
</h3>
|
||||
<p class="mt-1 text-sm leading-6 text-zinc-400">
|
||||
Drop will automatically check for updates from your server
|
||||
</p>
|
||||
</div>
|
||||
<Switch
|
||||
v-model="model.enableUpdates"
|
||||
:class="[
|
||||
model.enableUpdates ? 'bg-blue-600' : 'bg-zinc-700',
|
||||
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out',
|
||||
]"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
model.enableUpdates ? 'translate-x-5' : 'translate-x-0',
|
||||
'pointer-events-none relative inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
|
||||
]"
|
||||
/>
|
||||
</Switch>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Switch } from "@headlessui/vue";
|
||||
import type { GameVersion } from '~/types';
|
||||
|
||||
const model = defineModel<GameVersion["userConfiguration"]>({ required: true });
|
||||
</script>
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<ModalTemplate size-class="max-w-4xl" v-model="open">
|
||||
<template #default>
|
||||
<div class="flex flex-row gap-x-4">
|
||||
<div class="flex flex-row gap-x-4 h-96">
|
||||
<nav class="flex flex-1 flex-col" aria-label="Sidebar">
|
||||
<ul role="list" class="-mx-2 space-y-1">
|
||||
<li v-for="(tab, tabIdx) in tabs" :key="tab.name">
|
||||
@@ -29,7 +29,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<div class="border-l-2 border-zinc-800 w-full grow pl-4">
|
||||
<div class="border-l-2 border-zinc-800 w-full grow pl-4 overflow-y-scroll">
|
||||
<component
|
||||
v-model="configuration"
|
||||
:is="tabs[currentTabIndex]?.page"
|
||||
@@ -80,8 +80,10 @@ import {
|
||||
XCircleIcon,
|
||||
} from "@heroicons/vue/20/solid";
|
||||
import Launch from "./GameOptions/Launch.vue";
|
||||
import type { FrontendGameConfiguration } from "~/composables/game";
|
||||
import Updates from "./GameOptions/Updates.vue";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { ArrowPathIcon } from "@heroicons/vue/24/solid";
|
||||
import type { GameVersion } from "~/types";
|
||||
|
||||
const appState = useAppState();
|
||||
|
||||
@@ -89,17 +91,16 @@ const open = defineModel<boolean>();
|
||||
const props = defineProps<{ gameId: string }>();
|
||||
const game = await useGame(props.gameId);
|
||||
|
||||
const configuration: Ref<FrontendGameConfiguration> = ref({
|
||||
launchString: game.version!.userConfiguration.launchTemplate,
|
||||
overrideProtonPath: game.version!.userConfiguration.overrideProtonPath,
|
||||
});
|
||||
const configuration: Ref<GameVersion["userConfiguration"]> = ref(game.version.value!.userConfiguration);
|
||||
|
||||
const hasWindows = !!(
|
||||
game.version!.setups.find((v) => v.platform === "Windows") ??
|
||||
game.version!.launches.find((v) => v.platform === "Windows")
|
||||
game.version.value!.setups.find((v) => v.platform === "Windows") ??
|
||||
game.version.value!.launches.find((v) => v.platform === "Windows")
|
||||
);
|
||||
|
||||
const protonEnabled = !!(appState.value!.umuState !== "NotNeeded" && hasWindows);
|
||||
const protonEnabled = !!(
|
||||
appState.value!.umuState !== "NotNeeded" && hasWindows
|
||||
);
|
||||
|
||||
const tabs: Array<{ name: string; icon: Component; page: Component }> = [
|
||||
{
|
||||
@@ -107,6 +108,11 @@ const tabs: Array<{ name: string; icon: Component; page: Component }> = [
|
||||
icon: RocketLaunchIcon,
|
||||
page: Launch,
|
||||
},
|
||||
{
|
||||
name: "Updates",
|
||||
icon: ArrowPathIcon,
|
||||
page: Updates,
|
||||
},
|
||||
{
|
||||
name: "Storage",
|
||||
icon: ServerIcon,
|
||||
|
||||
@@ -3,19 +3,19 @@
|
||||
<div class="inline-flex divide-x divide-zinc-900">
|
||||
<button
|
||||
type="button"
|
||||
@click="() => buttonActions[props.status.type]()"
|
||||
@click="() => fetchStatusStyleData($props.status).action()"
|
||||
:class="[
|
||||
styles[props.status.type],
|
||||
fetchStatusStyleData($props.status).style,
|
||||
showDropdown ? 'rounded-l-md' : 'rounded-md',
|
||||
'inline-flex uppercase font-display items-center gap-x-2 px-4 py-3 text-md font-semibold shadow-sm focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
|
||||
]"
|
||||
>
|
||||
<component
|
||||
:is="buttonIcons[props.status.type]"
|
||||
:is="fetchStatusStyleData($props.status).icon"
|
||||
class="-mr-0.5 size-5"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{{ buttonNames[props.status.type] }}
|
||||
{{ fetchStatusStyleData($props.status).buttonName }}
|
||||
</button>
|
||||
<Menu
|
||||
v-if="showDropdown"
|
||||
@@ -25,7 +25,7 @@
|
||||
<div class="h-full">
|
||||
<MenuButton
|
||||
:class="[
|
||||
styles[props.status.type],
|
||||
fetchStatusStyleData($props.status).style,
|
||||
'inline-flex w-full h-full justify-center items-center rounded-r-md px-1 py-2 text-sm font-semibold shadow-sm group',
|
||||
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
|
||||
]"
|
||||
@@ -46,6 +46,21 @@
|
||||
class="absolute right-0 z-[500] mt-2 w-32 origin-top-right rounded-md bg-zinc-900 shadow-lg ring-1 ring-zinc-100/5 focus:outline-none"
|
||||
>
|
||||
<div class="py-1">
|
||||
<MenuItem v-slot="{ active }">
|
||||
<button
|
||||
@click="() => emit('install')"
|
||||
:class="[
|
||||
active
|
||||
? 'bg-zinc-800 text-zinc-100 outline-none'
|
||||
: 'text-zinc-400',
|
||||
'w-full px-4 py-2 text-sm inline-flex justify-between',
|
||||
]"
|
||||
>
|
||||
Install
|
||||
<ArrowDownTrayIcon class="size-5" />
|
||||
</button>
|
||||
</MenuItem>
|
||||
|
||||
<MenuItem v-if="showOptions" v-slot="{ active }">
|
||||
<button
|
||||
@click="() => emit('options')"
|
||||
@@ -53,7 +68,7 @@
|
||||
active
|
||||
? 'bg-zinc-800 text-zinc-100 outline-none'
|
||||
: 'text-zinc-400',
|
||||
'w-full block px-4 py-2 text-sm inline-flex justify-between',
|
||||
'w-full px-4 py-2 text-sm inline-flex justify-between',
|
||||
]"
|
||||
>
|
||||
Options
|
||||
@@ -67,7 +82,7 @@
|
||||
active
|
||||
? 'bg-zinc-800 text-zinc-100 outline-none'
|
||||
: 'text-zinc-400',
|
||||
'w-full block px-4 py-2 text-sm inline-flex justify-between',
|
||||
'w-full inline-flex px-4 py-2 text-sm justify-between',
|
||||
]"
|
||||
>
|
||||
Uninstall
|
||||
@@ -93,9 +108,14 @@ import {
|
||||
} from "@heroicons/vue/20/solid";
|
||||
|
||||
import type { Component } from "vue";
|
||||
import { GameStatusEnum, type GameStatus } from "~/types.js";
|
||||
import {
|
||||
type EmptyGameStatusEnum,
|
||||
InstalledType,
|
||||
type GameStatus,
|
||||
} from "~/types.js";
|
||||
import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/vue";
|
||||
import { Cog6ToothIcon, TrashIcon } from "@heroicons/vue/24/outline";
|
||||
import { ArrowsRightLeftIcon, ArrowUpTrayIcon } from "@heroicons/vue/24/solid";
|
||||
|
||||
const props = defineProps<{ status: GameStatus }>();
|
||||
const emit = defineEmits<{
|
||||
@@ -108,76 +128,105 @@ const emit = defineEmits<{
|
||||
(e: "resume"): void;
|
||||
}>();
|
||||
|
||||
const showDropdown = computed(
|
||||
() =>
|
||||
props.status.type === GameStatusEnum.Installed ||
|
||||
props.status.type === GameStatusEnum.SetupRequired ||
|
||||
props.status.type === GameStatusEnum.PartiallyInstalled
|
||||
);
|
||||
interface StatusStyleData {
|
||||
style: string;
|
||||
buttonName: string;
|
||||
icon: Component;
|
||||
action: () => void;
|
||||
}
|
||||
|
||||
function fetchStatusStyleData(status: GameStatus): StatusStyleData {
|
||||
if (status.type === "Installed") {
|
||||
if (status.install_type.type === InstalledType.Installed) {
|
||||
return {
|
||||
style:
|
||||
"bg-green-600 text-white hover:bg-green-500 focus-visible:outline-green-600 hover:bg-green-500",
|
||||
buttonName: "Play",
|
||||
icon: PlayIcon,
|
||||
action: () => emit("launch"),
|
||||
};
|
||||
}
|
||||
if (status.install_type.type === InstalledType.SetupRequired) {
|
||||
return {
|
||||
style:
|
||||
"bg-yellow-600 text-white hover:bg-yellow-500 focus-visible:outline-yellow-600 hover:bg-yellow-500",
|
||||
buttonName: "Setup",
|
||||
icon: WrenchIcon,
|
||||
action: () => emit("launch"),
|
||||
};
|
||||
}
|
||||
if (status.install_type.type === InstalledType.PartiallyInstalled) {
|
||||
return {
|
||||
style:
|
||||
"bg-blue-600 text-white hover:bg-blue-500 focus-visible:outline-blue-600 hover:bg-blue-500",
|
||||
buttonName: "Resume",
|
||||
icon: ArrowDownTrayIcon,
|
||||
action: () => emit("resume"),
|
||||
};
|
||||
}
|
||||
throw "Non-exhaustive install type: " + JSON.stringify(status.install_type);
|
||||
}
|
||||
return {
|
||||
style: styles[status.type],
|
||||
buttonName: buttonNames[status.type],
|
||||
icon: buttonIcons[status.type],
|
||||
action: buttonActions[status.type],
|
||||
};
|
||||
}
|
||||
|
||||
const showDropdown = computed(() => props.status.type === "Installed");
|
||||
|
||||
const showOptions = computed(
|
||||
() => props.status.type === GameStatusEnum.Installed
|
||||
() =>
|
||||
showDropdown.value &&
|
||||
props.status.type === "Installed" &&
|
||||
props.status.install_type.type !== InstalledType.PartiallyInstalled,
|
||||
);
|
||||
|
||||
const styles: { [key in GameStatusEnum]: string } = {
|
||||
[GameStatusEnum.Remote]:
|
||||
const styles: { [key in EmptyGameStatusEnum]: string } = {
|
||||
Remote:
|
||||
"bg-blue-600 text-white hover:bg-blue-500 focus-visible:outline-blue-600 hover:bg-blue-500",
|
||||
[GameStatusEnum.Queued]:
|
||||
Queued:
|
||||
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
||||
[GameStatusEnum.Downloading]:
|
||||
Downloading:
|
||||
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
||||
[GameStatusEnum.Validating]:
|
||||
Validating:
|
||||
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
||||
[GameStatusEnum.SetupRequired]:
|
||||
"bg-yellow-600 text-white hover:bg-yellow-500 focus-visible:outline-yellow-600 hover:bg-yellow-500",
|
||||
[GameStatusEnum.Installed]:
|
||||
"bg-green-600 text-white hover:bg-green-500 focus-visible:outline-green-600 hover:bg-green-500",
|
||||
[GameStatusEnum.Updating]:
|
||||
Updating:
|
||||
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
||||
[GameStatusEnum.Uninstalling]:
|
||||
Uninstalling:
|
||||
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
||||
[GameStatusEnum.Running]:
|
||||
Running:
|
||||
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
||||
[GameStatusEnum.PartiallyInstalled]:
|
||||
"bg-blue-600 text-white hover:bg-blue-500 focus-visible:outline-blue-600 hover:bg-blue-500",
|
||||
};
|
||||
|
||||
const buttonNames: { [key in GameStatusEnum]: string } = {
|
||||
[GameStatusEnum.Remote]: "Install",
|
||||
[GameStatusEnum.Queued]: "Queued",
|
||||
[GameStatusEnum.Downloading]: "Downloading",
|
||||
[GameStatusEnum.Validating]: "Validating",
|
||||
[GameStatusEnum.SetupRequired]: "Setup",
|
||||
[GameStatusEnum.Installed]: "Play",
|
||||
[GameStatusEnum.Updating]: "Updating",
|
||||
[GameStatusEnum.Uninstalling]: "Uninstalling",
|
||||
[GameStatusEnum.Running]: "Stop",
|
||||
[GameStatusEnum.PartiallyInstalled]: "Resume",
|
||||
const buttonNames: { [key in EmptyGameStatusEnum]: string } = {
|
||||
Remote: "Install",
|
||||
Queued: "Queued",
|
||||
Downloading: "Downloading",
|
||||
Validating: "Validating",
|
||||
Updating: "Updating",
|
||||
Uninstalling: "Uninstalling",
|
||||
Running: "Stop",
|
||||
};
|
||||
|
||||
const buttonIcons: { [key in GameStatusEnum]: Component } = {
|
||||
[GameStatusEnum.Remote]: ArrowDownTrayIcon,
|
||||
[GameStatusEnum.Queued]: QueueListIcon,
|
||||
[GameStatusEnum.Downloading]: ArrowDownTrayIcon,
|
||||
[GameStatusEnum.Validating]: ServerIcon,
|
||||
[GameStatusEnum.SetupRequired]: WrenchIcon,
|
||||
[GameStatusEnum.Installed]: PlayIcon,
|
||||
[GameStatusEnum.Updating]: ArrowDownTrayIcon,
|
||||
[GameStatusEnum.Uninstalling]: TrashIcon,
|
||||
[GameStatusEnum.Running]: StopIcon,
|
||||
[GameStatusEnum.PartiallyInstalled]: ArrowDownTrayIcon,
|
||||
const buttonIcons: { [key in EmptyGameStatusEnum]: Component } = {
|
||||
Remote: ArrowDownTrayIcon,
|
||||
Queued: QueueListIcon,
|
||||
Downloading: ArrowDownTrayIcon,
|
||||
Validating: ServerIcon,
|
||||
Updating: ArrowDownTrayIcon,
|
||||
Uninstalling: TrashIcon,
|
||||
Running: StopIcon,
|
||||
};
|
||||
|
||||
const buttonActions: { [key in GameStatusEnum]: () => void } = {
|
||||
[GameStatusEnum.Remote]: () => emit("install"),
|
||||
[GameStatusEnum.Queued]: () => emit("queue"),
|
||||
[GameStatusEnum.Downloading]: () => emit("queue"),
|
||||
[GameStatusEnum.Validating]: () => emit("queue"),
|
||||
[GameStatusEnum.SetupRequired]: () => emit("launch"),
|
||||
[GameStatusEnum.Installed]: () => emit("launch"),
|
||||
[GameStatusEnum.Updating]: () => emit("queue"),
|
||||
[GameStatusEnum.Uninstalling]: () => {},
|
||||
[GameStatusEnum.Running]: () => emit("kill"),
|
||||
[GameStatusEnum.PartiallyInstalled]: () => emit("resume"),
|
||||
const buttonActions: { [key in EmptyGameStatusEnum]: () => void } = {
|
||||
Remote: () => emit("install"),
|
||||
Queued: () => emit("queue"),
|
||||
Downloading: () => emit("queue"),
|
||||
Validating: () => emit("queue"),
|
||||
Updating: () => emit("queue"),
|
||||
Uninstalling: () => {},
|
||||
Running: () => emit("kill"),
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -1,16 +1,24 @@
|
||||
<template>
|
||||
<NuxtLink to="/settings/compat">
|
||||
<HeaderWidget
|
||||
v-if="appState && appState.umuState !== 'NotNeeded'"
|
||||
:problem="notInstalled"
|
||||
>
|
||||
<img src="/proton-logo.png" class="relative z-50 size-5 brightness-[30%]" />
|
||||
<NuxtLink
|
||||
v-if="onLinux"
|
||||
to="/settings/compat"
|
||||
>
|
||||
<HeaderWidget :problem="protonError">
|
||||
<img
|
||||
src="/proton-logo.png"
|
||||
class="relative z-50 size-5 brightness-[30%]"
|
||||
/>
|
||||
</HeaderWidget>
|
||||
</NuxtLink>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const appState = useAppState();
|
||||
const onLinux = appState.value?.umuState !== "NotNeeded";
|
||||
const paths = onLinux ? await useProtonPaths() : undefined;
|
||||
|
||||
const notInstalled = appState.value?.umuState === "NotInstalled";
|
||||
const protonError = computed(
|
||||
() =>
|
||||
appState.value?.umuState === "NotInstalled" || !paths?.data.value.default,
|
||||
);
|
||||
</script>
|
||||
|
||||
@@ -64,7 +64,7 @@
|
||||
v-for="item in nav.items"
|
||||
:key="nav.id"
|
||||
:class="[
|
||||
'transition-all duration-300 rounded-lg flex items-center px-1 py-1.5 hover:scale-105 active:scale-95 hover:shadow-lg hover:shadow-zinc-950/50',
|
||||
'transition-all duration-300 rounded-lg flex items-center px-1 py-0.5 hover:scale-105 active:scale-95 hover:shadow-lg hover:shadow-zinc-950/50',
|
||||
currentNavigation == item.id
|
||||
? 'bg-zinc-800 text-zinc-100 shadow-md shadow-zinc-950/20'
|
||||
: item.isInstalled.value
|
||||
@@ -83,21 +83,24 @@
|
||||
alt=""
|
||||
/>
|
||||
</div>
|
||||
<div class="truncate inline-flex items-center gap-x-2">
|
||||
<div class="truncate flex flex-col">
|
||||
<p class="text-sm whitespace-nowrap font-display font-semibold">
|
||||
{{ item.label }}
|
||||
</p>
|
||||
<p
|
||||
class="truncate text-[10px] font-bold uppercase font-display"
|
||||
:class="[
|
||||
gameStatusTextStyle[games[item.id].status.value.type],
|
||||
getGameStatusStyleText(games[item.id].status.value)[0],
|
||||
]"
|
||||
>
|
||||
{{ gameStatusText[games[item.id].status.value.type] }}
|
||||
{{ getGameStatusStyleText(games[item.id].status.value)[1] }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</NuxtLink>
|
||||
<span v-if="nav.items.length == 0" class="text-xs text-zinc-500 mx-auto"
|
||||
>No games in this category</span
|
||||
>
|
||||
</DisclosurePanel>
|
||||
</Disclosure>
|
||||
</TransitionGroup>
|
||||
@@ -138,7 +141,8 @@ import {
|
||||
} from "@heroicons/vue/20/solid";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import {
|
||||
GameStatusEnum,
|
||||
type EmptyGameStatusEnum,
|
||||
InstalledType,
|
||||
type Collection as Collection,
|
||||
type Game,
|
||||
type GameStatus,
|
||||
@@ -147,31 +151,44 @@ import { TransitionGroup } from "vue";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
|
||||
// Style information
|
||||
const gameStatusTextStyle: { [key in GameStatusEnum]: string } = {
|
||||
[GameStatusEnum.Installed]: "text-green-500",
|
||||
[GameStatusEnum.Downloading]: "text-zinc-400",
|
||||
[GameStatusEnum.Validating]: "text-blue-300",
|
||||
[GameStatusEnum.Running]: "text-blue-500",
|
||||
[GameStatusEnum.Remote]: "text-zinc-700",
|
||||
[GameStatusEnum.Queued]: "text-zinc-400",
|
||||
[GameStatusEnum.Updating]: "text-zinc-400",
|
||||
[GameStatusEnum.Uninstalling]: "text-zinc-100",
|
||||
[GameStatusEnum.SetupRequired]: "text-yellow-500",
|
||||
[GameStatusEnum.PartiallyInstalled]: "text-gray-400",
|
||||
const gameStatusTextStyle: { [key in EmptyGameStatusEnum]: string } = {
|
||||
Downloading: "text-zinc-400",
|
||||
Validating: "text-blue-300",
|
||||
Running: "text-blue-500",
|
||||
Remote: "text-zinc-700",
|
||||
Queued: "text-zinc-400",
|
||||
Updating: "text-zinc-400",
|
||||
Uninstalling: "text-zinc-100",
|
||||
};
|
||||
const gameStatusText: { [key in GameStatusEnum]: string } = {
|
||||
[GameStatusEnum.Remote]: "Not installed",
|
||||
[GameStatusEnum.Queued]: "Queued",
|
||||
[GameStatusEnum.Downloading]: "Downloading...",
|
||||
[GameStatusEnum.Validating]: "Validating...",
|
||||
[GameStatusEnum.Installed]: "Installed",
|
||||
[GameStatusEnum.Updating]: "Updating...",
|
||||
[GameStatusEnum.Uninstalling]: "Uninstalling...",
|
||||
[GameStatusEnum.SetupRequired]: "Setup required",
|
||||
[GameStatusEnum.Running]: "Running",
|
||||
[GameStatusEnum.PartiallyInstalled]: "Partially installed",
|
||||
const gameStatusText: { [key in EmptyGameStatusEnum]: string } = {
|
||||
Remote: "Not installed",
|
||||
Queued: "Queued",
|
||||
Downloading: "Downloading...",
|
||||
Validating: "Validating...",
|
||||
Updating: "Updating...",
|
||||
Uninstalling: "Uninstalling...",
|
||||
Running: "Running",
|
||||
};
|
||||
|
||||
function getGameStatusStyleText(status: GameStatus): [string, string] {
|
||||
if (status.type === "Installed") {
|
||||
if (status.install_type.type === InstalledType.Installed) {
|
||||
return ["text-green-500", "Installed"];
|
||||
}
|
||||
if (status.install_type.type === InstalledType.PartiallyInstalled) {
|
||||
return ["text-gray-400", "Partially installed"];
|
||||
}
|
||||
if (status.install_type.type === InstalledType.SetupRequired) {
|
||||
return ["text-yellow-500", "Setup required"];
|
||||
}
|
||||
throw (
|
||||
"Non-exhaustive installed type, missing: " +
|
||||
JSON.stringify(status.install_type)
|
||||
);
|
||||
}
|
||||
return [gameStatusTextStyle[status.type], gameStatusText[status.type]];
|
||||
}
|
||||
|
||||
const router = useRouter();
|
||||
|
||||
const searchQuery = ref("");
|
||||
@@ -277,25 +294,22 @@ await new Promise<void>((r) => {
|
||||
|
||||
const navigation = computed(() =>
|
||||
collections.value.map((collection) => {
|
||||
const items = collection.entries
|
||||
.map(({ game }) => {
|
||||
const status = games[game.id].status;
|
||||
const items = collection.entries.map(({ game }) => {
|
||||
const status = games[game.id].status;
|
||||
|
||||
const isInstalled = computed(
|
||||
() => status.value.type != GameStatusEnum.Remote,
|
||||
);
|
||||
const isInstalled = computed(() => status.value.type != "Remote");
|
||||
|
||||
const item = {
|
||||
label: game.mName,
|
||||
route: `/library/${game.id}`,
|
||||
prefix: `/library/${game.id}`,
|
||||
icon: game.mIconObjectId,
|
||||
isInstalled,
|
||||
id: game.id,
|
||||
type: game.type,
|
||||
};
|
||||
return item;
|
||||
});
|
||||
const item = {
|
||||
label: game.mName,
|
||||
route: `/library/${game.id}`,
|
||||
prefix: `/library/${game.id}`,
|
||||
icon: game.mIconObjectId,
|
||||
isInstalled,
|
||||
id: game.id,
|
||||
type: game.type,
|
||||
};
|
||||
return item;
|
||||
});
|
||||
|
||||
return {
|
||||
id: collection.id,
|
||||
|
||||
+20
-30
@@ -1,50 +1,45 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import type { Game, GameStatus, GameStatusEnum, GameVersion } from "~/types";
|
||||
import type {
|
||||
Game,
|
||||
GameStatus,
|
||||
GameStatusEnum,
|
||||
GameVersion,
|
||||
RawGameStatus,
|
||||
} from "~/types";
|
||||
|
||||
const gameRegistry: { [key: string]: { game: Game; version?: GameVersion } } =
|
||||
const gameRegistry: { [key: string]: { game: Game; version: Ref<GameVersion | undefined> } } =
|
||||
{};
|
||||
|
||||
const gameStatusRegistry: { [key: string]: Ref<GameStatus> } = {};
|
||||
|
||||
type OptionGameStatus = { [key in GameStatusEnum]: { version_name?: string } };
|
||||
export type SerializedGameStatus = [
|
||||
{ type: GameStatusEnum },
|
||||
OptionGameStatus | null,
|
||||
];
|
||||
|
||||
export const parseStatus = (status: SerializedGameStatus): GameStatus => {
|
||||
export const parseStatus = (status: RawGameStatus): GameStatus => {
|
||||
console.log(status[0]);
|
||||
if (status[0]) {
|
||||
return {
|
||||
type: status[0].type,
|
||||
};
|
||||
} else if (status[1]) {
|
||||
const [[gameStatus, options]] = Object.entries(status[1]);
|
||||
return {
|
||||
type: gameStatus as GameStatusEnum,
|
||||
...options,
|
||||
};
|
||||
} else {
|
||||
throw new Error("No game status");
|
||||
return status[0];
|
||||
}
|
||||
if (status[1]) {
|
||||
return status[1];
|
||||
}
|
||||
throw new Error("No game status: " + JSON.stringify(status));
|
||||
};
|
||||
|
||||
export const useGame = async (gameId: string) => {
|
||||
if (!gameRegistry[gameId]) {
|
||||
const data: {
|
||||
game: Game;
|
||||
status: SerializedGameStatus;
|
||||
status: RawGameStatus;
|
||||
version?: GameVersion;
|
||||
} = await invoke("fetch_game", {
|
||||
gameId,
|
||||
});
|
||||
gameRegistry[gameId] = { game: data.game, version: data.version };
|
||||
gameRegistry[gameId] = { game: data.game, version: ref(data.version) };
|
||||
if (!gameStatusRegistry[gameId]) {
|
||||
gameStatusRegistry[gameId] = ref(parseStatus(data.status));
|
||||
|
||||
listen(`update_game/${gameId}`, (event) => {
|
||||
const payload: {
|
||||
status: SerializedGameStatus;
|
||||
status: RawGameStatus;
|
||||
version?: GameVersion;
|
||||
} = event.payload as any;
|
||||
gameStatusRegistry[gameId].value = parseStatus(payload.status);
|
||||
@@ -57,7 +52,7 @@ export const useGame = async (gameId: string) => {
|
||||
* on transient state updates.
|
||||
*/
|
||||
if (payload.version) {
|
||||
gameRegistry[gameId].version = payload.version;
|
||||
gameRegistry[gameId].version.value = payload.version;
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -68,11 +63,6 @@ export const useGame = async (gameId: string) => {
|
||||
return { ...game, status };
|
||||
};
|
||||
|
||||
export type FrontendGameConfiguration = {
|
||||
launchString: string;
|
||||
overrideProtonPath?: string;
|
||||
};
|
||||
|
||||
export type LaunchResult =
|
||||
| { result: "Success" }
|
||||
| { result: "InstallRequired"; data: [string, string] };
|
||||
@@ -102,4 +92,4 @@ export type VersionOption = {
|
||||
export type ProtonPath = {
|
||||
path: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
|
||||
interface ProtonPaths {
|
||||
data: Ref<{
|
||||
autodiscovered: ProtonPath[];
|
||||
custom: ProtonPath[];
|
||||
default?: string;
|
||||
}>;
|
||||
refresh: () => Promise<void>;
|
||||
}
|
||||
|
||||
const protonPaths = useState<ProtonPaths["data"]["value"]>(
|
||||
"proton_paths",
|
||||
undefined,
|
||||
);
|
||||
|
||||
export const useProtonPaths = async (): Promise<ProtonPaths> => {
|
||||
const refresh = async () => {
|
||||
protonPaths.value = await invoke("fetch_proton_paths");
|
||||
};
|
||||
if (protonPaths.value)
|
||||
return {
|
||||
data: protonPaths,
|
||||
refresh,
|
||||
};
|
||||
|
||||
await refresh();
|
||||
return {
|
||||
data: protonPaths,
|
||||
refresh,
|
||||
};
|
||||
};
|
||||
+2
-1
@@ -7,7 +7,8 @@
|
||||
"build": "nuxt generate",
|
||||
"dev": "nuxt dev",
|
||||
"postinstall": "nuxt prepare",
|
||||
"tauri": "tauri"
|
||||
"tauri": "tauri",
|
||||
"typecheck": "nuxt typecheck"
|
||||
},
|
||||
"dependencies": {
|
||||
"@headlessui/vue": "^1.7.23",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
>
|
||||
<LibrarySearch />
|
||||
</div>
|
||||
|
||||
<div class="grow overflow-y-auto">
|
||||
<NuxtErrorBoundary>
|
||||
<NuxtPage />
|
||||
|
||||
@@ -16,14 +16,45 @@
|
||||
</div>
|
||||
|
||||
<div class="relative z-10">
|
||||
<div class="px-8 pb-4">
|
||||
<div class="px-8">
|
||||
<h1
|
||||
class="text-5xl text-zinc-100 font-bold font-display drop-shadow-lg mb-8"
|
||||
class="text-5xl text-zinc-100 font-bold font-display drop-shadow-lg"
|
||||
>
|
||||
{{ game.mName }}
|
||||
</h1>
|
||||
<div class="relative" v-if="status.type === 'Installed' && status.install_type.type != InstalledType.PartiallyInstalled">
|
||||
<div
|
||||
v-if="!version?.userConfiguration?.enableUpdates"
|
||||
class="absolute mt-1 inline-flex items-center gap-x-1 text-xs text-zinc-400"
|
||||
>
|
||||
Version pinned
|
||||
<svg
|
||||
class="size-3 text-blue-600"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M19.1835 7.80516L16.2188 4.83755C14.1921 2.8089 13.1788 1.79457 12.0904 2.03468C11.0021 2.2748 10.5086 3.62155 9.5217 6.31506L8.85373 8.1381C8.59063 8.85617 8.45908 9.2152 8.22239 9.49292C8.11619 9.61754 7.99536 9.72887 7.86251 9.82451C7.56644 10.0377 7.19811 10.1392 6.46145 10.3423C4.80107 10.8 3.97088 11.0289 3.65804 11.5721C3.5228 11.8069 3.45242 12.0735 3.45413 12.3446C3.45809 12.9715 4.06698 13.581 5.28476 14.8L6.69935 16.2163L2.22345 20.6964C1.92552 20.9946 1.92552 21.4782 2.22345 21.7764C2.52138 22.0746 3.00443 22.0746 3.30236 21.7764L7.77841 17.2961L9.24441 18.7635C10.4699 19.9902 11.0827 20.6036 11.7134 20.6045C11.9792 20.6049 12.2404 20.5358 12.4713 20.4041C13.0192 20.0914 13.2493 19.2551 13.7095 17.5825C13.9119 16.8472 14.013 16.4795 14.2254 16.1835C14.3184 16.054 14.4262 15.9358 14.5468 15.8314C14.8221 15.593 15.1788 15.459 15.8922 15.191L17.7362 14.4981C20.4 13.4973 21.7319 12.9969 21.9667 11.9115C22.2014 10.826 21.1954 9.81905 19.1835 7.80516Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
<div
|
||||
v-else-if="!status.update_available"
|
||||
class="absolute mt-1 inline-flex items-center gap-x-1 text-xs text-zinc-400"
|
||||
>
|
||||
Up to date <CheckCircleIcon class="size-3 text-green-600" />
|
||||
</div>
|
||||
<div
|
||||
v-else-if="status.update_available"
|
||||
class="absolute mt-1 inline-flex items-center gap-x-1 text-xs text-zinc-400"
|
||||
>
|
||||
Update available <ArrowDownTrayIcon class="size-3 text-blue-600" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-x-4 items-stretch mb-8">
|
||||
<div class="mt-8 flex flex-row gap-x-4 items-stretch">
|
||||
<!-- Do not add scale animations to this: https://stackoverflow.com/a/35683068 -->
|
||||
<GameStatusButton
|
||||
@install="() => installFlow()"
|
||||
@@ -35,6 +66,13 @@
|
||||
@resume="() => resumeDownload()"
|
||||
:status="status"
|
||||
/>
|
||||
<button
|
||||
v-if="status.type === 'Installed' && status.update_available"
|
||||
class="transition-transform duration-300 hover:scale-105 active:scale-95 inline-flex gap-x-2 items-center rounded-md bg-blue-600 px-6 font-semibold text-white shadow-xl backdrop-blur-sm hover:bg-blue-700 uppercase font-display"
|
||||
@click="() => installFlow()"
|
||||
>
|
||||
Update <ArrowDownTrayIcon class="size-5" />
|
||||
</button>
|
||||
<NuxtLink
|
||||
class="transition-transform duration-300 hover:scale-105 active:scale-95 inline-flex items-center rounded-md bg-zinc-800/50 px-6 font-semibold text-white shadow-xl backdrop-blur-sm hover:bg-zinc-800/80 uppercase font-display"
|
||||
:to="{
|
||||
@@ -51,7 +89,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Main content -->
|
||||
<div class="w-full bg-zinc-900 px-8 py-6">
|
||||
<div class="mt-8 w-full bg-zinc-900 px-8">
|
||||
<div class="grid grid-cols-[2fr,1fr] gap-8">
|
||||
<div class="space-y-6">
|
||||
<div class="bg-zinc-800/50 rounded-xl p-6 backdrop-blur-sm">
|
||||
@@ -68,19 +106,22 @@
|
||||
Game Images
|
||||
</h2>
|
||||
<div class="relative">
|
||||
<div v-if="mediaUrls.length > 0">
|
||||
<div v-if="game.mImageCarouselObjectIds.length > 0">
|
||||
<div
|
||||
class="relative aspect-video rounded-lg overflow-hidden cursor-pointer group"
|
||||
>
|
||||
<div
|
||||
class="absolute inset-0"
|
||||
@click="fullscreenImage = mediaUrls[currentImageIndex]"
|
||||
@click="
|
||||
fullscreenImage =
|
||||
game.mImageCarouselObjectIds[currentImageIndex]
|
||||
"
|
||||
>
|
||||
<TransitionGroup name="slide" tag="div" class="h-full">
|
||||
<img
|
||||
v-for="(url, index) in mediaUrls"
|
||||
v-for="(url, index) in game.mImageCarouselObjectIds"
|
||||
:key="index"
|
||||
:src="url"
|
||||
:src="useObject(url)"
|
||||
class="absolute inset-0 w-full h-full object-cover"
|
||||
v-show="index === currentImageIndex"
|
||||
/>
|
||||
@@ -92,7 +133,7 @@
|
||||
>
|
||||
<div class="pointer-events-auto">
|
||||
<button
|
||||
v-if="mediaUrls.length > 1"
|
||||
v-if="game.mImageCarouselObjectIds.length > 1"
|
||||
@click.stop="previousImage()"
|
||||
class="p-2 rounded-full bg-zinc-900/50 text-zinc-100 hover:bg-zinc-900/80 transition-all duration-300 hover:scale-110"
|
||||
>
|
||||
@@ -101,7 +142,7 @@
|
||||
</div>
|
||||
<div class="pointer-events-auto">
|
||||
<button
|
||||
v-if="mediaUrls.length > 1"
|
||||
v-if="game.mImageCarouselObjectIds.length > 1"
|
||||
@click.stop="nextImage()"
|
||||
class="p-2 rounded-full bg-zinc-900/50 text-zinc-100 hover:bg-zinc-900/80 transition-all duration-300 hover:scale-110"
|
||||
>
|
||||
@@ -125,7 +166,7 @@
|
||||
class="absolute -bottom-2 left-1/2 -translate-x-1/2 flex gap-x-2"
|
||||
>
|
||||
<button
|
||||
v-for="(_, index) in mediaUrls"
|
||||
v-for="(_, index) in game.mImageCarouselObjectIds"
|
||||
:key="index"
|
||||
@click.stop="currentImageIndex = index"
|
||||
class="w-1.5 h-1.5 rounded-full transition-all"
|
||||
@@ -174,11 +215,7 @@
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div
|
||||
v-if="
|
||||
versionOptions && versionOptions.length > 0 && currentVersionOption
|
||||
"
|
||||
>
|
||||
<div v-if="versionOptions && versionOptions.length > 0">
|
||||
<Listbox as="div" v-model="installVersionIndex">
|
||||
<ListboxLabel class="block text-sm/6 font-medium text-zinc-100"
|
||||
>Version</ListboxLabel
|
||||
@@ -187,18 +224,9 @@
|
||||
<ListboxButton
|
||||
class="relative w-full cursor-default rounded-md bg-zinc-800 py-1.5 pl-3 pr-10 text-left text-zinc-100 shadow-sm ring-1 ring-inset ring-zinc-700 focus:outline-none focus:ring-2 focus:ring-blue-600 sm:text-sm/6"
|
||||
>
|
||||
<span class="block truncate"
|
||||
>{{
|
||||
currentVersionOption.displayName ||
|
||||
currentVersionOption.versionPath
|
||||
}}
|
||||
on
|
||||
{{ currentVersionOption.platform }} ({{
|
||||
formatKilobytes(
|
||||
currentVersionOption.size.installSize / 1024,
|
||||
)
|
||||
}}B)</span
|
||||
>
|
||||
<span class="block truncate">{{
|
||||
formatVersionOptionText(installVersionIndex)
|
||||
}}</span>
|
||||
<span
|
||||
class="pointer-events-none absolute inset-y-0 right-0 flex items-center pr-2"
|
||||
>
|
||||
@@ -209,6 +237,48 @@
|
||||
</span>
|
||||
</ListboxButton>
|
||||
|
||||
<div
|
||||
v-if="installVersionIndex == -1"
|
||||
class="mt-3 rounded-md bg-blue-500/10 p-2 outline outline-blue-500/20"
|
||||
>
|
||||
<div class="flex">
|
||||
<div class="shrink-0">
|
||||
<InformationCircleIcon
|
||||
class="size-4 text-blue-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-2 flex-1 md:flex md:justify-between">
|
||||
<p class="text-xs text-blue-300">
|
||||
"Latest" will notify you when there is a new version
|
||||
available. Choose another version to pin this game's
|
||||
version.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-else
|
||||
class="mt-3 rounded-md bg-blue-500/10 p-2 outline outline-blue-500/20"
|
||||
>
|
||||
<div class="flex">
|
||||
<div class="shrink-0">
|
||||
<InformationCircleIcon
|
||||
class="size-4 text-blue-400"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
<div class="ml-2 flex-1 md:flex md:justify-between">
|
||||
<p class="text-xs text-blue-300">
|
||||
This game will be pinned to "{{
|
||||
currentVersionOption?.displayName ||
|
||||
currentVersionOption?.versionPath
|
||||
}}"
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<transition
|
||||
leave-active-class="transition ease-in duration-100"
|
||||
leave-from-class="opacity-100"
|
||||
@@ -217,6 +287,39 @@
|
||||
<ListboxOptions
|
||||
class="absolute z-10 mt-1 max-h-60 w-full overflow-auto rounded-md bg-zinc-900 py-1 text-base shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm"
|
||||
>
|
||||
<ListboxOption
|
||||
as="template"
|
||||
:value="-1"
|
||||
v-slot="{ active, selected }"
|
||||
>
|
||||
<li
|
||||
:class="[
|
||||
active ? 'bg-blue-600 text-white' : 'text-zinc-300',
|
||||
'relative cursor-default select-none py-2 pl-3 pr-9',
|
||||
]"
|
||||
>
|
||||
<span
|
||||
:class="[
|
||||
selected
|
||||
? 'font-semibold text-zinc-100'
|
||||
: 'font-normal',
|
||||
'block truncate',
|
||||
]"
|
||||
>{{ formatVersionOptionText(-1) }}</span
|
||||
>
|
||||
|
||||
<span
|
||||
v-if="selected"
|
||||
:class="[
|
||||
active ? 'text-white' : 'text-blue-600',
|
||||
'absolute inset-y-0 right-0 flex items-center pr-4',
|
||||
]"
|
||||
>
|
||||
<CheckIcon class="h-5 w-5" aria-hidden="true" />
|
||||
</span>
|
||||
</li>
|
||||
</ListboxOption>
|
||||
|
||||
<ListboxOption
|
||||
as="template"
|
||||
v-for="(version, versionIdx) in versionOptions"
|
||||
@@ -237,13 +340,7 @@
|
||||
: 'font-normal',
|
||||
'block truncate',
|
||||
]"
|
||||
>{{ version.displayName || version.versionPath }} on
|
||||
{{ version.platform }} ({{
|
||||
formatKilobytes(
|
||||
versionOptions[installVersionIndex].size
|
||||
.installSize / 1024,
|
||||
)
|
||||
}}B)</span
|
||||
>{{ formatVersionOptionText(versionIdx) }}</span
|
||||
>
|
||||
|
||||
<span
|
||||
@@ -464,7 +561,11 @@
|
||||
Don't.
|
||||
-->
|
||||
<GameOptionsModal
|
||||
v-if="status.type === GameStatusEnum.Installed"
|
||||
v-if="
|
||||
status.type === 'Installed' &&
|
||||
(status.install_type.type == InstalledType.Installed ||
|
||||
status.install_type.type == InstalledType.SetupRequired)
|
||||
"
|
||||
v-model="configureModalOpen"
|
||||
:game-id="game.id"
|
||||
/>
|
||||
@@ -494,14 +595,14 @@
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="mediaUrls.length > 1"
|
||||
v-if="game.mImageCarouselObjectIds.length > 1"
|
||||
@click.stop="previousImage()"
|
||||
class="absolute left-4 p-3 rounded-full bg-zinc-900/50 text-zinc-100 hover:bg-zinc-900 transition-colors"
|
||||
>
|
||||
<ChevronLeftIcon class="size-6" />
|
||||
</button>
|
||||
<button
|
||||
v-if="mediaUrls.length > 1"
|
||||
v-if="game.mImageCarouselObjectIds.length > 1"
|
||||
@click.stop="nextImage()"
|
||||
class="absolute right-4 p-3 rounded-full bg-zinc-900/50 text-zinc-100 hover:bg-zinc-900 transition-colors"
|
||||
>
|
||||
@@ -515,10 +616,10 @@
|
||||
@click.stop
|
||||
>
|
||||
<img
|
||||
v-for="(url, index) in mediaUrls"
|
||||
v-for="(url, index) in game.mImageCarouselObjectIds"
|
||||
v-show="currentImageIndex === index"
|
||||
:key="index"
|
||||
:src="url"
|
||||
:src="useObject(url)"
|
||||
class="max-h-[90vh] max-w-[90vw] object-contain"
|
||||
:alt="`${game.mName} screenshot ${index + 1}`"
|
||||
/>
|
||||
@@ -528,7 +629,8 @@
|
||||
class="absolute bottom-4 left-1/2 -translate-x-1/2 px-4 py-2 rounded-full bg-zinc-900/50 backdrop-blur-sm"
|
||||
>
|
||||
<p class="text-zinc-100 text-sm font-medium">
|
||||
{{ currentImageIndex + 1 }} / {{ mediaUrls.length }}
|
||||
{{ currentImageIndex + 1 }} /
|
||||
{{ game.mImageCarouselObjectIds.length }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -559,31 +661,30 @@ import {
|
||||
ArrowsPointingOutIcon,
|
||||
PhotoIcon,
|
||||
PlayIcon,
|
||||
InformationCircleIcon,
|
||||
} from "@heroicons/vue/20/solid";
|
||||
import { BuildingStorefrontIcon } from "@heroicons/vue/24/outline";
|
||||
import { MinusIcon, ServerIcon, XCircleIcon } from "@heroicons/vue/24/solid";
|
||||
import {
|
||||
ArrowDownTrayIcon,
|
||||
CheckCircleIcon,
|
||||
MapPinIcon,
|
||||
MinusIcon,
|
||||
ServerIcon,
|
||||
XCircleIcon,
|
||||
} from "@heroicons/vue/24/solid";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { micromark } from "micromark";
|
||||
import { GameStatusEnum } from "~/types";
|
||||
import { InstalledType } from "~/types";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const id = route.params.id.toString();
|
||||
|
||||
const { game: rawGame, status } = await useGame(id);
|
||||
const game = ref(rawGame);
|
||||
const { game, status, version } = await useGame(id);
|
||||
|
||||
const bannerUrl = await useObject(game.value.mBannerObjectId);
|
||||
const bannerUrl = await useObject(game.mBannerObjectId);
|
||||
|
||||
// Get all available images
|
||||
const mediaUrls = await Promise.all(
|
||||
game.value.mImageCarouselObjectIds.map(async (v) => {
|
||||
const src = await useObject(v);
|
||||
return src;
|
||||
}),
|
||||
);
|
||||
|
||||
const htmlDescription = micromark(game.value.mDescription);
|
||||
const htmlDescription = micromark(game.mDescription);
|
||||
|
||||
const installFlowOpen = ref(false);
|
||||
const versionOptions = ref<undefined | Array<VersionOption>>();
|
||||
@@ -596,10 +697,11 @@ async function installFlow() {
|
||||
installFlowOpen.value = true;
|
||||
versionOptions.value = undefined;
|
||||
installDirs.value = undefined;
|
||||
installError.value = undefined;
|
||||
|
||||
try {
|
||||
versionOptions.value = await invoke("fetch_game_version_options", {
|
||||
gameId: game.value.id,
|
||||
gameId: game.id,
|
||||
});
|
||||
installDirs.value = await invoke("fetch_download_dir_stats");
|
||||
} catch (error) {
|
||||
@@ -610,21 +712,20 @@ async function installFlow() {
|
||||
|
||||
const installLoading = ref(false);
|
||||
const installError = ref<string | undefined>();
|
||||
const installVersionIndex = ref(0);
|
||||
const installVersionIndex = ref(-1);
|
||||
const installDir = ref(0);
|
||||
const installDepsDisabled = ref<{ [key: string]: boolean }>({});
|
||||
|
||||
const currentVersionOption = computed(
|
||||
() => versionOptions.value?.[installVersionIndex.value],
|
||||
);
|
||||
async function install() {
|
||||
try {
|
||||
if (!versionOptions.value) throw new Error("Versions have not been loaded");
|
||||
installLoading.value = true;
|
||||
const versionOption = versionOptions.value[installVersionIndex.value];
|
||||
const versionOption =
|
||||
versionOptions.value[Math.max(installVersionIndex.value, 0)];
|
||||
const isLatest = installVersionIndex.value == -1;
|
||||
|
||||
const games = [
|
||||
{ gameId: game.value.id, versionId: versionOption.versionId },
|
||||
{ gameId: game.id, versionId: versionOption.versionId },
|
||||
...versionOption.requiredContent
|
||||
.filter((v) => !installDepsDisabled.value[v.versionId])
|
||||
.map((v) => ({ gameId: v.gameId, versionId: v.versionId })),
|
||||
@@ -636,6 +737,7 @@ async function install() {
|
||||
versionId: game.versionId,
|
||||
installDir: installDir.value,
|
||||
targetPlatform: versionOption.platform,
|
||||
enableUpdates: isLatest,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -647,9 +749,23 @@ async function install() {
|
||||
installLoading.value = false;
|
||||
}
|
||||
|
||||
const currentVersionOption = computed(
|
||||
() => versionOptions.value?.[Math.max(installVersionIndex.value, 0)],
|
||||
);
|
||||
|
||||
function formatVersionOptionText(index: number) {
|
||||
if (!versionOptions.value) return undefined;
|
||||
const versionOption = versionOptions.value[Math.max(index, 0)];
|
||||
const template = `${versionOption.displayName || versionOption.versionPath} on ${versionOption.platform}, ${formatKilobytes(versionOption.size.installSize / 1024)}B`;
|
||||
if (index == -1) {
|
||||
return `Latest (${template})`;
|
||||
}
|
||||
return template;
|
||||
}
|
||||
|
||||
async function resumeDownload() {
|
||||
try {
|
||||
await invoke("resume_download", { gameId: game.value.id });
|
||||
await invoke("resume_download", { gameId: game.id });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
@@ -659,14 +775,17 @@ const launchOptions = ref<Array<{ name: string }> | undefined>(undefined);
|
||||
const launchOptionsOpen = computed(() => launchOptions.value !== undefined);
|
||||
|
||||
async function launch() {
|
||||
if (status.value.type == GameStatusEnum.SetupRequired) {
|
||||
if (
|
||||
status.value.type == "Installed" &&
|
||||
status.value.install_type.type == InstalledType.SetupRequired
|
||||
) {
|
||||
await launchIndex(0);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const fetchedLaunchOptions = await invoke<Array<{ name: string }>>(
|
||||
"get_launch_options",
|
||||
{ id: game.value.id },
|
||||
{ id: game.id },
|
||||
);
|
||||
if (fetchedLaunchOptions.length == 1) {
|
||||
await launchIndex(0);
|
||||
@@ -677,8 +796,8 @@ async function launch() {
|
||||
createModal(
|
||||
ModalType.Notification,
|
||||
{
|
||||
title: `Couldn't run "${game.value.mName}"`,
|
||||
description: `Drop failed to launch "${game.value.mName}": ${e}`,
|
||||
title: `Couldn't run "${game.mName}"`,
|
||||
description: `Drop failed to launch "${game.mName}": ${e}`,
|
||||
buttonText: "Close",
|
||||
},
|
||||
(e, c) => c(),
|
||||
@@ -695,7 +814,7 @@ async function launchIndex(index: number) {
|
||||
launchOptions.value = undefined;
|
||||
try {
|
||||
const result = await invoke<LaunchResult>("launch_game", {
|
||||
id: game.value.id,
|
||||
id: game.id,
|
||||
index,
|
||||
});
|
||||
if (result.result == "InstallRequired") {
|
||||
@@ -708,8 +827,8 @@ async function launchIndex(index: number) {
|
||||
createModal(
|
||||
ModalType.Notification,
|
||||
{
|
||||
title: `Couldn't run "${game.value.mName}"`,
|
||||
description: `Drop failed to launch "${game.value.mName}": ${e}`,
|
||||
title: `Couldn't run "${game.mName}"`,
|
||||
description: `Drop failed to launch "${game.mName}": ${e}`,
|
||||
buttonText: "Close",
|
||||
},
|
||||
(e, c) => c(),
|
||||
@@ -722,18 +841,18 @@ async function queue() {
|
||||
}
|
||||
|
||||
async function uninstall() {
|
||||
await invoke("uninstall_game", { gameId: game.value.id });
|
||||
await invoke("uninstall_game", { gameId: game.id });
|
||||
}
|
||||
|
||||
async function kill() {
|
||||
try {
|
||||
await invoke("kill_game", { gameId: game.value.id });
|
||||
await invoke("kill_game", { gameId: game.id });
|
||||
} catch (e) {
|
||||
createModal(
|
||||
ModalType.Notification,
|
||||
{
|
||||
title: `Couldn't stop "${game.value.mName}"`,
|
||||
description: `Drop failed to stop "${game.value.mName}": ${e}`,
|
||||
title: `Couldn't stop "${game.mName}"`,
|
||||
description: `Drop failed to stop "${game.mName}": ${e}`,
|
||||
buttonText: "Close",
|
||||
},
|
||||
(e, c) => c(),
|
||||
@@ -743,12 +862,14 @@ async function kill() {
|
||||
}
|
||||
|
||||
function nextImage() {
|
||||
currentImageIndex.value = (currentImageIndex.value + 1) % mediaUrls.length;
|
||||
currentImageIndex.value =
|
||||
(currentImageIndex.value + 1) % game.mImageCarouselObjectIds.length;
|
||||
}
|
||||
|
||||
function previousImage() {
|
||||
currentImageIndex.value =
|
||||
(currentImageIndex.value - 1 + mediaUrls.length) % mediaUrls.length;
|
||||
(currentImageIndex.value - 1 + game.mImageCarouselObjectIds.length) %
|
||||
game.mImageCarouselObjectIds.length;
|
||||
}
|
||||
|
||||
const fullscreenImage = ref<string | null>(null);
|
||||
|
||||
@@ -45,18 +45,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="!paths.data.value?.default" class="mt-4 rounded-md bg-yellow-500/15 p-4 outline outline-yellow-500/25">
|
||||
<div
|
||||
v-if="!paths.data.value?.default"
|
||||
class="mt-4 rounded-md bg-red-500/15 p-4 outline outline-red-500/25"
|
||||
>
|
||||
<div class="flex">
|
||||
<div class="shrink-0">
|
||||
<ExclamationTriangleIcon class="size-5 text-yellow-400" aria-hidden="true" />
|
||||
<XCircleIcon class="size-5 text-red-400" aria-hidden="true" />
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-yellow-200">
|
||||
<h3 class="text-sm font-medium text-red-200">
|
||||
No default Proton layer
|
||||
</h3>
|
||||
<div class="mt-2 text-sm text-yellow-200/80">
|
||||
<div class="mt-2 text-sm text-red-200/80">
|
||||
<p>
|
||||
You won't be able to launch any Windows games without overriding their Proton layer in game settings. Please select a default layer below using the stars.
|
||||
You won't be able to launch any Windows games without overriding
|
||||
their Proton layer in game settings. Please select a default layer
|
||||
below using the stars.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -290,12 +295,7 @@ import { open } from "@tauri-apps/plugin-dialog";
|
||||
|
||||
const appState = useAppState();
|
||||
|
||||
const paths = useAsyncData<{
|
||||
autodiscovered: ProtonPath[];
|
||||
custom: ProtonPath[];
|
||||
default?: string;
|
||||
}>("proton_paths", async () => await invoke("fetch_proton_paths"));
|
||||
|
||||
const paths = await useProtonPaths();
|
||||
const pickLayerModal = ref(false);
|
||||
const pickError = ref<string | null>(null);
|
||||
|
||||
|
||||
Generated
+66
-42
@@ -4,6 +4,20 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
devalue@>=5.1.0 <5.6.2: '>=5.6.2'
|
||||
devalue@>=5.3.0 <=5.6.1: '>=5.6.2'
|
||||
diff@>=6.0.0 <8.0.3: '>=8.0.3'
|
||||
h3@<=1.15.4: '>=1.15.5'
|
||||
lodash@>=4.0.0 <=4.17.22: '>=4.17.23'
|
||||
markdown-it@>=13.0.0 <14.1.1: '>=14.1.1'
|
||||
node-forge@<1.3.2: '>=1.3.2'
|
||||
seroval@<1.4.1: '>=1.4.1'
|
||||
seroval@<=1.4.0: '>=1.4.1'
|
||||
tar@<7.5.7: '>=7.5.7'
|
||||
tar@<=7.5.2: '>=7.5.3'
|
||||
tar@<=7.5.3: '>=7.5.4'
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
@@ -33,8 +47,8 @@ importers:
|
||||
specifier: ^2.16.1
|
||||
version: 2.16.3
|
||||
markdown-it:
|
||||
specifier: ^14.1.0
|
||||
version: 14.1.0
|
||||
specifier: '>=14.1.1'
|
||||
version: 14.1.1
|
||||
micromark:
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.2
|
||||
@@ -1820,8 +1834,8 @@ packages:
|
||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
devalue@5.5.0:
|
||||
resolution: {integrity: sha512-69sM5yrHfFLJt0AZ9QqZXGCPfJ7fQjvpln3Rq5+PS03LD32Ost1Q9N+eEnaQwGRIriKkMImXD56ocjQmfjbV3w==}
|
||||
devalue@5.6.2:
|
||||
resolution: {integrity: sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==}
|
||||
|
||||
devlop@1.1.0:
|
||||
resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==}
|
||||
@@ -1829,8 +1843,8 @@ packages:
|
||||
didyoumean@1.2.2:
|
||||
resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==}
|
||||
|
||||
diff@8.0.2:
|
||||
resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==}
|
||||
diff@8.0.3:
|
||||
resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==}
|
||||
engines: {node: '>=0.3.1'}
|
||||
|
||||
dlv@1.1.3:
|
||||
@@ -2102,8 +2116,8 @@ packages:
|
||||
resolution: {integrity: sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
h3@1.15.4:
|
||||
resolution: {integrity: sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==}
|
||||
h3@1.15.5:
|
||||
resolution: {integrity: sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==}
|
||||
|
||||
has-flag@4.0.0:
|
||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
@@ -2404,8 +2418,8 @@ packages:
|
||||
lodash.uniq@4.5.0:
|
||||
resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==}
|
||||
|
||||
lodash@4.17.21:
|
||||
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
|
||||
lodash@4.17.23:
|
||||
resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==}
|
||||
|
||||
lru-cache@10.4.3:
|
||||
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
|
||||
@@ -2426,8 +2440,8 @@ packages:
|
||||
magicast@0.5.1:
|
||||
resolution: {integrity: sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==}
|
||||
|
||||
markdown-it@14.1.0:
|
||||
resolution: {integrity: sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==}
|
||||
markdown-it@14.1.1:
|
||||
resolution: {integrity: sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==}
|
||||
hasBin: true
|
||||
|
||||
math-intrinsics@1.1.0:
|
||||
@@ -2639,8 +2653,8 @@ packages:
|
||||
encoding:
|
||||
optional: true
|
||||
|
||||
node-forge@1.3.1:
|
||||
resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==}
|
||||
node-forge@1.3.3:
|
||||
resolution: {integrity: sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==}
|
||||
engines: {node: '>= 6.13.0'}
|
||||
|
||||
node-gyp-build@4.8.4:
|
||||
@@ -2650,6 +2664,9 @@ packages:
|
||||
node-mock-http@1.0.3:
|
||||
resolution: {integrity: sha512-jN8dK25fsfnMrVsEhluUTPkBFY+6ybu7jSB1n+ri/vOGjJxU8J9CZhpSGkHXSkFjtUhbmoncG/YG9ta5Ludqog==}
|
||||
|
||||
node-mock-http@1.0.4:
|
||||
resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==}
|
||||
|
||||
node-releases@2.0.27:
|
||||
resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==}
|
||||
|
||||
@@ -3367,8 +3384,8 @@ packages:
|
||||
serialize-javascript@6.0.2:
|
||||
resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==}
|
||||
|
||||
seroval@1.4.0:
|
||||
resolution: {integrity: sha512-BdrNXdzlofomLTiRnwJTSEAaGKyHHZkbMXIywOh7zlzp4uZnXErEwl9XZ+N1hJSNpeTtNxWvVwN0wUzAIQ4Hpg==}
|
||||
seroval@1.5.0:
|
||||
resolution: {integrity: sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
serve-placeholder@2.0.2:
|
||||
@@ -3568,8 +3585,8 @@ packages:
|
||||
tar-stream@3.1.7:
|
||||
resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==}
|
||||
|
||||
tar@7.5.2:
|
||||
resolution: {integrity: sha512-7NyxrTE4Anh8km8iEy7o0QYPs+0JKBTj5ZaqHg6B39erLg0qYXN3BijtShwbsNSvQ+LN75+KV+C4QR/f6Gwnpg==}
|
||||
tar@7.5.9:
|
||||
resolution: {integrity: sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
terser@5.44.1:
|
||||
@@ -3645,6 +3662,9 @@ packages:
|
||||
ufo@1.6.1:
|
||||
resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==}
|
||||
|
||||
ufo@1.6.3:
|
||||
resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
|
||||
|
||||
ultrahtml@1.6.0:
|
||||
resolution: {integrity: sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==}
|
||||
|
||||
@@ -4375,7 +4395,7 @@ snapshots:
|
||||
node-fetch: 2.7.0
|
||||
nopt: 8.1.0
|
||||
semver: 7.7.3
|
||||
tar: 7.5.2
|
||||
tar: 7.5.9
|
||||
transitivePeerDependencies:
|
||||
- encoding
|
||||
- supports-color
|
||||
@@ -4441,7 +4461,7 @@ snapshots:
|
||||
'@nuxt/devtools-wizard@3.1.0':
|
||||
dependencies:
|
||||
consola: 3.4.2
|
||||
diff: 8.0.2
|
||||
diff: 8.0.3
|
||||
execa: 8.0.1
|
||||
magicast: 0.5.1
|
||||
pathe: 2.0.3
|
||||
@@ -4550,11 +4570,11 @@ snapshots:
|
||||
consola: 3.4.2
|
||||
defu: 6.1.4
|
||||
destr: 2.0.5
|
||||
devalue: 5.5.0
|
||||
devalue: 5.6.2
|
||||
errx: 0.1.0
|
||||
escape-string-regexp: 5.0.0
|
||||
exsolve: 1.0.8
|
||||
h3: 1.15.4
|
||||
h3: 1.15.5
|
||||
impound: 1.0.0
|
||||
klona: 2.0.6
|
||||
mocked-exports: 0.1.1
|
||||
@@ -4645,7 +4665,7 @@ snapshots:
|
||||
exsolve: 1.0.8
|
||||
externality: 1.0.2
|
||||
get-port-please: 3.2.0
|
||||
h3: 1.15.4
|
||||
h3: 1.15.5
|
||||
jiti: 2.6.1
|
||||
knitwork: 1.3.0
|
||||
magic-string: 0.30.21
|
||||
@@ -4658,7 +4678,7 @@ snapshots:
|
||||
pkg-types: 2.3.0
|
||||
postcss: 8.5.6
|
||||
rollup-plugin-visualizer: 6.0.5(rollup@4.53.3)
|
||||
seroval: 1.4.0
|
||||
seroval: 1.5.0
|
||||
std-env: 3.10.0
|
||||
ufo: 1.6.1
|
||||
unenv: 2.0.0-rc.24
|
||||
@@ -4700,7 +4720,7 @@ snapshots:
|
||||
c12: 3.3.2(magicast@0.5.1)
|
||||
consola: 3.4.2
|
||||
defu: 6.1.4
|
||||
h3: 1.15.4
|
||||
h3: 1.15.5
|
||||
klona: 2.0.6
|
||||
ohash: 2.0.11
|
||||
pathe: 2.0.3
|
||||
@@ -5400,7 +5420,7 @@ snapshots:
|
||||
graceful-fs: 4.2.11
|
||||
is-stream: 2.0.1
|
||||
lazystream: 1.0.1
|
||||
lodash: 4.17.21
|
||||
lodash: 4.17.23
|
||||
normalize-path: 3.0.0
|
||||
readable-stream: 4.7.0
|
||||
|
||||
@@ -5804,7 +5824,7 @@ snapshots:
|
||||
|
||||
detect-libc@2.1.2: {}
|
||||
|
||||
devalue@5.5.0: {}
|
||||
devalue@5.6.2: {}
|
||||
|
||||
devlop@1.1.0:
|
||||
dependencies:
|
||||
@@ -5812,7 +5832,7 @@ snapshots:
|
||||
|
||||
didyoumean@1.2.2: {}
|
||||
|
||||
diff@8.0.2: {}
|
||||
diff@8.0.3: {}
|
||||
|
||||
dlv@1.1.3: {}
|
||||
|
||||
@@ -6105,16 +6125,16 @@ snapshots:
|
||||
dependencies:
|
||||
duplexer: 0.1.2
|
||||
|
||||
h3@1.15.4:
|
||||
h3@1.15.5:
|
||||
dependencies:
|
||||
cookie-es: 1.2.2
|
||||
crossws: 0.3.5
|
||||
defu: 6.1.4
|
||||
destr: 2.0.5
|
||||
iron-webcrypto: 1.2.1
|
||||
node-mock-http: 1.0.3
|
||||
node-mock-http: 1.0.4
|
||||
radix3: 1.1.2
|
||||
ufo: 1.6.1
|
||||
ufo: 1.6.3
|
||||
uncrypto: 0.1.3
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
@@ -6416,11 +6436,11 @@ snapshots:
|
||||
crossws: 0.3.5
|
||||
defu: 6.1.4
|
||||
get-port-please: 3.2.0
|
||||
h3: 1.15.4
|
||||
h3: 1.15.5
|
||||
http-shutdown: 1.2.2
|
||||
jiti: 2.6.1
|
||||
mlly: 1.8.0
|
||||
node-forge: 1.3.1
|
||||
node-forge: 1.3.3
|
||||
pathe: 1.1.2
|
||||
std-env: 3.10.0
|
||||
ufo: 1.6.1
|
||||
@@ -6441,7 +6461,7 @@ snapshots:
|
||||
|
||||
lodash.uniq@4.5.0: {}
|
||||
|
||||
lodash@4.17.21: {}
|
||||
lodash@4.17.23: {}
|
||||
|
||||
lru-cache@10.4.3: {}
|
||||
|
||||
@@ -6473,7 +6493,7 @@ snapshots:
|
||||
'@babel/types': 7.28.5
|
||||
source-map-js: 1.2.1
|
||||
|
||||
markdown-it@14.1.0:
|
||||
markdown-it@14.1.1:
|
||||
dependencies:
|
||||
argparse: 2.0.1
|
||||
entities: 4.5.0
|
||||
@@ -6729,7 +6749,7 @@ snapshots:
|
||||
exsolve: 1.0.8
|
||||
globby: 15.0.0
|
||||
gzip-size: 7.0.0
|
||||
h3: 1.15.4
|
||||
h3: 1.15.5
|
||||
hookable: 5.5.3
|
||||
httpxy: 0.1.7
|
||||
ioredis: 5.8.2
|
||||
@@ -6808,12 +6828,14 @@ snapshots:
|
||||
dependencies:
|
||||
whatwg-url: 5.0.0
|
||||
|
||||
node-forge@1.3.1: {}
|
||||
node-forge@1.3.3: {}
|
||||
|
||||
node-gyp-build@4.8.4: {}
|
||||
|
||||
node-mock-http@1.0.3: {}
|
||||
|
||||
node-mock-http@1.0.4: {}
|
||||
|
||||
node-releases@2.0.27: {}
|
||||
|
||||
nopt@8.1.0:
|
||||
@@ -6856,11 +6878,11 @@ snapshots:
|
||||
cookie-es: 2.0.0
|
||||
defu: 6.1.4
|
||||
destr: 2.0.5
|
||||
devalue: 5.5.0
|
||||
devalue: 5.6.2
|
||||
errx: 0.1.0
|
||||
escape-string-regexp: 5.0.0
|
||||
exsolve: 1.0.8
|
||||
h3: 1.15.4
|
||||
h3: 1.15.5
|
||||
hookable: 5.5.3
|
||||
ignore: 7.0.5
|
||||
impound: 1.0.0
|
||||
@@ -7637,7 +7659,7 @@ snapshots:
|
||||
dependencies:
|
||||
randombytes: 2.1.0
|
||||
|
||||
seroval@1.4.0: {}
|
||||
seroval@1.5.0: {}
|
||||
|
||||
serve-placeholder@2.0.2:
|
||||
dependencies:
|
||||
@@ -7863,7 +7885,7 @@ snapshots:
|
||||
- bare-abort-controller
|
||||
- react-native-b4a
|
||||
|
||||
tar@7.5.2:
|
||||
tar@7.5.9:
|
||||
dependencies:
|
||||
'@isaacs/fs-minipass': 4.0.1
|
||||
chownr: 3.0.0
|
||||
@@ -7934,6 +7956,8 @@ snapshots:
|
||||
|
||||
ufo@1.6.1: {}
|
||||
|
||||
ufo@1.6.3: {}
|
||||
|
||||
ultrahtml@1.6.0: {}
|
||||
|
||||
uncrypto@0.1.3: {}
|
||||
@@ -8022,7 +8046,7 @@ snapshots:
|
||||
anymatch: 3.1.3
|
||||
chokidar: 4.0.3
|
||||
destr: 2.0.5
|
||||
h3: 1.15.4
|
||||
h3: 1.15.5
|
||||
lru-cache: 10.4.3
|
||||
node-fetch-native: 1.6.7
|
||||
ofetch: 1.5.1
|
||||
|
||||
@@ -1,3 +1,17 @@
|
||||
onlyBuiltDependencies:
|
||||
- '@parcel/watcher'
|
||||
- esbuild
|
||||
|
||||
overrides:
|
||||
devalue@>=5.1.0 <5.6.2: '>=5.6.2'
|
||||
devalue@>=5.3.0 <=5.6.1: '>=5.6.2'
|
||||
diff@>=6.0.0 <8.0.3: '>=8.0.3'
|
||||
h3@<=1.15.4: '>=1.15.5'
|
||||
lodash@>=4.0.0 <=4.17.22: '>=4.17.23'
|
||||
markdown-it@>=13.0.0 <14.1.1: '>=14.1.1'
|
||||
node-forge@<1.3.2: '>=1.3.2'
|
||||
seroval@<1.4.1: '>=1.4.1'
|
||||
seroval@<=1.4.0: '>=1.4.1'
|
||||
tar@<7.5.7: '>=7.5.7'
|
||||
tar@<=7.5.2: '>=7.5.3'
|
||||
tar@<=7.5.3: '>=7.5.4'
|
||||
|
||||
@@ -7,7 +7,7 @@ export default {
|
||||
"./plugins/**/*.{js,ts}",
|
||||
"./app.vue",
|
||||
"./error.vue",
|
||||
"../libs/drop-base/**/*.{js,vue,ts}"
|
||||
"../libs/drop-base/**/*.{js,vue,ts}",
|
||||
],
|
||||
theme: {
|
||||
extend: {
|
||||
@@ -17,5 +17,5 @@ export default {
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: [require("@tailwindcss/forms"), require('@tailwindcss/typography')],
|
||||
plugins: [require("@tailwindcss/forms"), require("@tailwindcss/typography")],
|
||||
};
|
||||
|
||||
+31
-15
@@ -53,6 +53,7 @@ export type GameVersion = {
|
||||
userConfiguration: {
|
||||
launchTemplate: string;
|
||||
overrideProtonPath: string;
|
||||
enableUpdates: boolean
|
||||
};
|
||||
setups: Array<{ platform: string }>;
|
||||
launches: Array<{ platform: string }>;
|
||||
@@ -67,24 +68,39 @@ export enum AppStatus {
|
||||
ServerUnavailable = "ServerUnavailable",
|
||||
}
|
||||
|
||||
export enum GameStatusEnum {
|
||||
Remote = "Remote",
|
||||
Queued = "Queued",
|
||||
Downloading = "Downloading",
|
||||
Validating = "Validating",
|
||||
Installed = "Installed",
|
||||
Updating = "Updating",
|
||||
Uninstalling = "Uninstalling",
|
||||
SetupRequired = "SetupRequired",
|
||||
Running = "Running",
|
||||
export type EmptyGameStatusEnum =
|
||||
| "Remote"
|
||||
| "Queued"
|
||||
| "Downloading"
|
||||
| "Validating"
|
||||
| "Updating"
|
||||
| "Uninstalling"
|
||||
| "Running";
|
||||
|
||||
export enum InstalledType {
|
||||
PartiallyInstalled = "PartiallyInstalled",
|
||||
SetupRequired = "SetupRequired",
|
||||
Installed = "Installed",
|
||||
}
|
||||
|
||||
export type GameStatus = {
|
||||
type: GameStatusEnum;
|
||||
version_name?: string;
|
||||
install_dir?: string;
|
||||
};
|
||||
export interface InstalledGameStatusData {
|
||||
install_type: { type: InstalledType };
|
||||
version_id: string;
|
||||
install_dir: string;
|
||||
update_available: boolean;
|
||||
}
|
||||
|
||||
export type GameStatus =
|
||||
| {
|
||||
type: EmptyGameStatusEnum;
|
||||
}
|
||||
| ({
|
||||
type: "Installed";
|
||||
} & InstalledGameStatusData);
|
||||
|
||||
export type GameStatusEnum = GameStatus["type"];
|
||||
|
||||
export type RawGameStatus = [GameStatus | null, GameStatus | null];
|
||||
|
||||
export enum DownloadableType {
|
||||
Game = "Game",
|
||||
|
||||
Reference in New Issue
Block a user