mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-10 04:22:13 +10:00
feat: launch options
This commit is contained in:
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[submodule "drop-base"]
|
||||||
|
path = drop-base
|
||||||
|
url = https://github.com/drop-oss/drop-base
|
||||||
31
components/GameOptions/Launch.vue
Normal file
31
components/GameOptions/Launch.vue
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<label for="launch" class="block text-sm/6 font-medium text-zinc-100"
|
||||||
|
>Launch string template</label
|
||||||
|
>
|
||||||
|
<div class="mt-2">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="launch"
|
||||||
|
id="launch"
|
||||||
|
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"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2 text-sm text-zinc-400" id="launch-description">
|
||||||
|
Override the launch string. Passed to system's default shell, and replaces
|
||||||
|
"{}" with the command to start the game.
|
||||||
|
<span class="font-semibold text-zinc-200"
|
||||||
|
>Leaving it blank will cause the game not to start.</span
|
||||||
|
>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { FrontendGameConfiguration } from "~/composables/game";
|
||||||
|
|
||||||
|
const model = defineModel<FrontendGameConfiguration>();
|
||||||
|
</script>
|
||||||
122
components/GameOptionsModal.vue
Normal file
122
components/GameOptionsModal.vue
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
<template>
|
||||||
|
<ModalTemplate size-class="max-w-4xl" v-model="open">
|
||||||
|
<template #default>
|
||||||
|
<div class="flex flex-row gap-x-4">
|
||||||
|
<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">
|
||||||
|
<button
|
||||||
|
@click="() => (currentTabIndex = tabIdx)"
|
||||||
|
:class="[
|
||||||
|
tabIdx == currentTabIndex
|
||||||
|
? 'bg-zinc-800 text-zinc-100'
|
||||||
|
: 'text-zinc-400 hover:bg-zinc-800 hover:text-zinc-100',
|
||||||
|
'transition w-full group flex gap-x-3 rounded-md p-2 text-sm/6 font-semibold',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="tab.icon"
|
||||||
|
:class="[
|
||||||
|
tabIdx == currentTabIndex
|
||||||
|
? 'text-zinc-100'
|
||||||
|
: 'text-gray-400 group-hover:text-zinc-100',
|
||||||
|
'size-6 shrink-0',
|
||||||
|
]"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
|
{{ tab.name }}
|
||||||
|
</button>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
<div class="border-l-2 border-zinc-800 w-full grow pl-4">
|
||||||
|
<component
|
||||||
|
v-model="configuration"
|
||||||
|
:is="tabs[currentTabIndex]?.page"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="saveError" class="mt-5 rounded-md bg-red-600/10 p-4">
|
||||||
|
<div class="flex">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<XCircleIcon class="h-5 w-5 text-red-600" aria-hidden="true" />
|
||||||
|
</div>
|
||||||
|
<div class="ml-3">
|
||||||
|
<h3 class="text-sm font-medium text-red-600">
|
||||||
|
{{ saveError }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template #buttons>
|
||||||
|
<LoadingButton
|
||||||
|
@click="() => save()"
|
||||||
|
:loading="saveLoading"
|
||||||
|
type="submit"
|
||||||
|
class="ml-2 w-full sm:w-fit"
|
||||||
|
>
|
||||||
|
Save
|
||||||
|
</LoadingButton>
|
||||||
|
<button
|
||||||
|
@click="() => (open = false)"
|
||||||
|
type="button"
|
||||||
|
class="mt-3 inline-flex w-full justify-center rounded-md bg-zinc-800 px-3 py-2 text-sm font-semibold text-zinc-100 shadow-sm ring-1 ring-inset ring-zinc-700 hover:bg-zinc-900 sm:mt-0 sm:w-auto"
|
||||||
|
ref="cancelButtonRef"
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</button>
|
||||||
|
</template>
|
||||||
|
</ModalTemplate>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Component } from "vue";
|
||||||
|
import {
|
||||||
|
RocketLaunchIcon,
|
||||||
|
ServerIcon,
|
||||||
|
TrashIcon,
|
||||||
|
XCircleIcon,
|
||||||
|
} from "@heroicons/vue/20/solid";
|
||||||
|
import Launch from "./GameOptions/Launch.vue";
|
||||||
|
import type { FrontendGameConfiguration } from "~/composables/game";
|
||||||
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
|
|
||||||
|
const open = defineModel<boolean>();
|
||||||
|
const props = defineProps<{ gameId: string }>();
|
||||||
|
const game = await useGame(props.gameId);
|
||||||
|
|
||||||
|
const configuration: Ref<FrontendGameConfiguration> = ref({
|
||||||
|
launchString: game.version!!.launchCommandTemplate,
|
||||||
|
});
|
||||||
|
|
||||||
|
const tabs: Array<{ name: string; icon: Component; page: Component }> = [
|
||||||
|
{
|
||||||
|
name: "Launch",
|
||||||
|
icon: RocketLaunchIcon,
|
||||||
|
page: Launch,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Storage",
|
||||||
|
icon: ServerIcon,
|
||||||
|
page: h("div"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const currentTabIndex = ref(0);
|
||||||
|
|
||||||
|
const saveLoading = ref(false);
|
||||||
|
const saveError = ref<undefined | string>();
|
||||||
|
async function save() {
|
||||||
|
saveLoading.value = true;
|
||||||
|
try {
|
||||||
|
await invoke("update_game_configuration", {
|
||||||
|
gameId: game.game.id,
|
||||||
|
options: configuration.value,
|
||||||
|
});
|
||||||
|
open.value = false;
|
||||||
|
} catch (e) {
|
||||||
|
saveError.value = (e as unknown as string).toString();
|
||||||
|
}
|
||||||
|
saveLoading.value = false;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@ -1,33 +1,75 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<!-- Do not add scale animations to this: https://stackoverflow.com/a/35683068 -->
|
||||||
<div class="inline-flex divide-x divide-zinc-900">
|
<div class="inline-flex divide-x divide-zinc-900">
|
||||||
<button type="button" @click="() => buttonActions[props.status.type]()" :class="[
|
<button
|
||||||
styles[props.status.type],
|
type="button"
|
||||||
showDropdown ? 'rounded-l-md' : 'rounded-md',
|
@click="() => buttonActions[props.status.type]()"
|
||||||
'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',
|
:class="[
|
||||||
]">
|
styles[props.status.type],
|
||||||
<component :is="buttonIcons[props.status.type]" class="-mr-0.5 size-5" aria-hidden="true" />
|
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]"
|
||||||
|
class="-mr-0.5 size-5"
|
||||||
|
aria-hidden="true"
|
||||||
|
/>
|
||||||
{{ buttonNames[props.status.type] }}
|
{{ buttonNames[props.status.type] }}
|
||||||
</button>
|
</button>
|
||||||
<Menu v-if="showDropdown" as="div" class="relative inline-block text-left grow">
|
<Menu
|
||||||
|
v-if="showDropdown"
|
||||||
|
as="div"
|
||||||
|
class="relative inline-block text-left grow"
|
||||||
|
>
|
||||||
<div class="h-full">
|
<div class="h-full">
|
||||||
<MenuButton :class="[
|
<MenuButton
|
||||||
styles[props.status.type],
|
:class="[
|
||||||
'inline-flex w-full h-full justify-center items-center rounded-r-md px-1 py-2 text-sm font-semibold shadow-sm group',
|
styles[props.status.type],
|
||||||
'focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2'
|
'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',
|
||||||
|
]"
|
||||||
|
>
|
||||||
<ChevronDownIcon class="size-5" aria-hidden="true" />
|
<ChevronDownIcon class="size-5" aria-hidden="true" />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<transition enter-active-class="transition ease-out duration-100" enter-from-class="transform opacity-0 scale-95"
|
<transition
|
||||||
enter-to-class="transform opacity-100 scale-100" leave-active-class="transition ease-in duration-75"
|
enter-active-class="transition ease-out duration-100"
|
||||||
leave-from-class="transform opacity-100 scale-100" leave-to-class="transform opacity-0 scale-95">
|
enter-from-class="transform opacity-0 scale-95"
|
||||||
|
enter-to-class="transform opacity-100 scale-100"
|
||||||
|
leave-active-class="transition ease-in duration-75"
|
||||||
|
leave-from-class="transform opacity-100 scale-100"
|
||||||
|
leave-to-class="transform opacity-0 scale-95"
|
||||||
|
>
|
||||||
<MenuItems
|
<MenuItems
|
||||||
class="absolute right-0 z-50 mt-2 w-32 origin-top-right rounded-md bg-zinc-900 shadow-lg ring-1 ring-zinc-100/5 focus:outline-none">
|
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">
|
<div class="py-1">
|
||||||
<MenuItem v-slot="{ active }">
|
<MenuItem v-slot="{ active }">
|
||||||
<button @click="() => emit('uninstall')"
|
<button
|
||||||
:class="[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']">
|
@click="() => emit('options')"
|
||||||
|
:class="[
|
||||||
|
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',
|
||||||
|
]"
|
||||||
|
>
|
||||||
|
Options
|
||||||
|
<Cog6ToothIcon class="size-5" />
|
||||||
|
</button>
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem v-slot="{ active }">
|
||||||
|
<button
|
||||||
|
@click="() => emit('uninstall')"
|
||||||
|
:class="[
|
||||||
|
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',
|
||||||
|
]"
|
||||||
|
>
|
||||||
Uninstall
|
Uninstall
|
||||||
<TrashIcon class="size-5" />
|
<TrashIcon class="size-5" />
|
||||||
</button>
|
</button>
|
||||||
@ -45,13 +87,13 @@ import {
|
|||||||
ChevronDownIcon,
|
ChevronDownIcon,
|
||||||
PlayIcon,
|
PlayIcon,
|
||||||
QueueListIcon,
|
QueueListIcon,
|
||||||
TrashIcon,
|
|
||||||
WrenchIcon,
|
WrenchIcon,
|
||||||
} from "@heroicons/vue/20/solid";
|
} from "@heroicons/vue/20/solid";
|
||||||
|
|
||||||
import type { Component } from "vue";
|
import type { Component } from "vue";
|
||||||
import { GameStatusEnum, type GameStatus } from "~/types.js";
|
import { GameStatusEnum, type GameStatus } from "~/types.js";
|
||||||
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue'
|
import { Menu, MenuButton, MenuItem, MenuItems } from "@headlessui/vue";
|
||||||
|
import { Cog6ToothIcon, TrashIcon } from "@heroicons/vue/24/outline";
|
||||||
|
|
||||||
const props = defineProps<{ status: GameStatus }>();
|
const props = defineProps<{ status: GameStatus }>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
@ -60,19 +102,32 @@ const emit = defineEmits<{
|
|||||||
(e: "queue"): void;
|
(e: "queue"): void;
|
||||||
(e: "uninstall"): void;
|
(e: "uninstall"): void;
|
||||||
(e: "kill"): void;
|
(e: "kill"): void;
|
||||||
|
(e: "options"): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const showDropdown = computed(() => props.status.type === GameStatusEnum.Installed || props.status.type === GameStatusEnum.SetupRequired);
|
const showDropdown = computed(
|
||||||
|
() =>
|
||||||
|
props.status.type === GameStatusEnum.Installed ||
|
||||||
|
props.status.type === GameStatusEnum.SetupRequired
|
||||||
|
);
|
||||||
|
|
||||||
const styles: { [key in GameStatusEnum]: string } = {
|
const styles: { [key in GameStatusEnum]: string } = {
|
||||||
[GameStatusEnum.Remote]: "bg-blue-600 text-white hover:bg-blue-500 focus-visible:outline-blue-600 hover:bg-blue-500",
|
[GameStatusEnum.Remote]:
|
||||||
[GameStatusEnum.Queued]: "bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
"bg-blue-600 text-white hover:bg-blue-500 focus-visible:outline-blue-600 hover:bg-blue-500",
|
||||||
[GameStatusEnum.Downloading]: "bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
[GameStatusEnum.Queued]:
|
||||||
[GameStatusEnum.SetupRequired]: "bg-yellow-600 text-white hover:bg-yellow-500 focus-visible:outline-yellow-600 hover:bg-yellow-500",
|
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
||||||
[GameStatusEnum.Installed]: "bg-green-600 text-white hover:bg-green-500 focus-visible:outline-green-600 hover:bg-green-500",
|
[GameStatusEnum.Downloading]:
|
||||||
[GameStatusEnum.Updating]: "bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
||||||
[GameStatusEnum.Uninstalling]: "bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
[GameStatusEnum.SetupRequired]:
|
||||||
[GameStatusEnum.Running]: "bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700"
|
"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]:
|
||||||
|
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
||||||
|
[GameStatusEnum.Uninstalling]:
|
||||||
|
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
||||||
|
[GameStatusEnum.Running]:
|
||||||
|
"bg-zinc-800 text-white hover:bg-zinc-700 focus-visible:outline-zinc-700 hover:bg-zinc-700",
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttonNames: { [key in GameStatusEnum]: string } = {
|
const buttonNames: { [key in GameStatusEnum]: string } = {
|
||||||
@ -83,7 +138,7 @@ const buttonNames: { [key in GameStatusEnum]: string } = {
|
|||||||
[GameStatusEnum.Installed]: "Play",
|
[GameStatusEnum.Installed]: "Play",
|
||||||
[GameStatusEnum.Updating]: "Updating",
|
[GameStatusEnum.Updating]: "Updating",
|
||||||
[GameStatusEnum.Uninstalling]: "Uninstalling",
|
[GameStatusEnum.Uninstalling]: "Uninstalling",
|
||||||
[GameStatusEnum.Running]: "Stop"
|
[GameStatusEnum.Running]: "Stop",
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttonIcons: { [key in GameStatusEnum]: Component } = {
|
const buttonIcons: { [key in GameStatusEnum]: Component } = {
|
||||||
@ -94,7 +149,7 @@ const buttonIcons: { [key in GameStatusEnum]: Component } = {
|
|||||||
[GameStatusEnum.Installed]: PlayIcon,
|
[GameStatusEnum.Installed]: PlayIcon,
|
||||||
[GameStatusEnum.Updating]: ArrowDownTrayIcon,
|
[GameStatusEnum.Updating]: ArrowDownTrayIcon,
|
||||||
[GameStatusEnum.Uninstalling]: TrashIcon,
|
[GameStatusEnum.Uninstalling]: TrashIcon,
|
||||||
[GameStatusEnum.Running]: PlayIcon
|
[GameStatusEnum.Running]: PlayIcon,
|
||||||
};
|
};
|
||||||
|
|
||||||
const buttonActions: { [key in GameStatusEnum]: () => void } = {
|
const buttonActions: { [key in GameStatusEnum]: () => void } = {
|
||||||
@ -104,7 +159,7 @@ const buttonActions: { [key in GameStatusEnum]: () => void } = {
|
|||||||
[GameStatusEnum.SetupRequired]: () => emit("launch"),
|
[GameStatusEnum.SetupRequired]: () => emit("launch"),
|
||||||
[GameStatusEnum.Installed]: () => emit("launch"),
|
[GameStatusEnum.Installed]: () => emit("launch"),
|
||||||
[GameStatusEnum.Updating]: () => emit("queue"),
|
[GameStatusEnum.Updating]: () => emit("queue"),
|
||||||
[GameStatusEnum.Uninstalling]: () => { },
|
[GameStatusEnum.Uninstalling]: () => {},
|
||||||
[GameStatusEnum.Running]: () => emit("kill")
|
[GameStatusEnum.Running]: () => emit("kill"),
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { listen } from "@tauri-apps/api/event";
|
import { listen } from "@tauri-apps/api/event";
|
||||||
import type { Game, GameStatus, GameStatusEnum } from "~/types";
|
import type { Game, GameStatus, GameStatusEnum, GameVersion } from "~/types";
|
||||||
|
|
||||||
const gameRegistry: { [key: string]: Game } = {};
|
const gameRegistry: { [key: string]: { game: Game; version?: GameVersion } } =
|
||||||
|
{};
|
||||||
|
|
||||||
const gameStatusRegistry: { [key: string]: Ref<GameStatus> } = {};
|
const gameStatusRegistry: { [key: string]: Ref<GameStatus> } = {};
|
||||||
|
|
||||||
@ -31,13 +32,14 @@ export const parseStatus = (status: SerializedGameStatus): GameStatus => {
|
|||||||
|
|
||||||
export const useGame = async (gameId: string) => {
|
export const useGame = async (gameId: string) => {
|
||||||
if (!gameRegistry[gameId]) {
|
if (!gameRegistry[gameId]) {
|
||||||
const data: { game: Game; status: SerializedGameStatus } = await invoke(
|
const data: {
|
||||||
"fetch_game",
|
game: Game;
|
||||||
{
|
status: SerializedGameStatus;
|
||||||
gameId,
|
version?: GameVersion;
|
||||||
}
|
} = await invoke("fetch_game", {
|
||||||
);
|
gameId,
|
||||||
gameRegistry[gameId] = data.game;
|
});
|
||||||
|
gameRegistry[gameId] = { game: data.game, version: data.version };
|
||||||
if (!gameStatusRegistry[gameId]) {
|
if (!gameStatusRegistry[gameId]) {
|
||||||
gameStatusRegistry[gameId] = ref(parseStatus(data.status));
|
gameStatusRegistry[gameId] = ref(parseStatus(data.status));
|
||||||
|
|
||||||
@ -53,5 +55,9 @@ export const useGame = async (gameId: string) => {
|
|||||||
|
|
||||||
const game = gameRegistry[gameId];
|
const game = gameRegistry[gameId];
|
||||||
const status = gameStatusRegistry[gameId];
|
const status = gameStatusRegistry[gameId];
|
||||||
return { game, status };
|
return { ...game, status };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type FrontendGameConfiguration = {
|
||||||
|
launchString: string;
|
||||||
|
};
|
||||||
|
|||||||
1
drop-base
Submodule
1
drop-base
Submodule
Submodule drop-base added at 26698e5b06
@ -13,5 +13,5 @@ export default defineNuxtConfig({
|
|||||||
|
|
||||||
ssr: false,
|
ssr: false,
|
||||||
|
|
||||||
extends: [["github:drop-oss/drop-base"]],
|
extends: [["./drop-base"]],
|
||||||
});
|
});
|
||||||
|
|||||||
@ -24,18 +24,16 @@
|
|||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div class="flex flex-row gap-x-4 items-stretch mb-8">
|
<div class="flex flex-row gap-x-4 items-stretch mb-8">
|
||||||
<div
|
<!-- Do not add scale animations to this: https://stackoverflow.com/a/35683068 -->
|
||||||
class="transition-transform duration-300 hover:scale-105 active:scale-95 shadow-xl"
|
<GameStatusButton
|
||||||
>
|
@install="() => installFlow()"
|
||||||
<GameStatusButton
|
@launch="() => launch()"
|
||||||
@install="() => installFlow()"
|
@queue="() => queue()"
|
||||||
@launch="() => launch()"
|
@uninstall="() => uninstall()"
|
||||||
@queue="() => queue()"
|
@kill="() => kill()"
|
||||||
@uninstall="() => uninstall()"
|
@options="() => (configureModalOpen = true)"
|
||||||
@kill="() => kill()"
|
:status="status"
|
||||||
:status="status"
|
/>
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<a
|
<a
|
||||||
:href="remoteUrl"
|
:href="remoteUrl"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@ -371,6 +369,8 @@
|
|||||||
</template>
|
</template>
|
||||||
</ModalTemplate>
|
</ModalTemplate>
|
||||||
|
|
||||||
|
<GameOptionsModal v-model="configureModalOpen" :game-id="game.id" />
|
||||||
|
|
||||||
<Transition
|
<Transition
|
||||||
enter="transition ease-out duration-300"
|
enter="transition ease-out duration-300"
|
||||||
enter-from="opacity-0"
|
enter-from="opacity-0"
|
||||||
@ -463,7 +463,6 @@ import {
|
|||||||
import { BuildingStorefrontIcon } from "@heroicons/vue/24/outline";
|
import { BuildingStorefrontIcon } from "@heroicons/vue/24/outline";
|
||||||
import { XCircleIcon } from "@heroicons/vue/24/solid";
|
import { XCircleIcon } from "@heroicons/vue/24/solid";
|
||||||
import { invoke } from "@tauri-apps/api/core";
|
import { invoke } from "@tauri-apps/api/core";
|
||||||
import { GameStatusEnum } from "~/types";
|
|
||||||
import { micromark } from "micromark";
|
import { micromark } from "micromark";
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
@ -493,6 +492,8 @@ const versionOptions = ref<
|
|||||||
const installDirs = ref<undefined | Array<string>>();
|
const installDirs = ref<undefined | Array<string>>();
|
||||||
const currentImageIndex = ref(0);
|
const currentImageIndex = ref(0);
|
||||||
|
|
||||||
|
const configureModalOpen = ref(false);
|
||||||
|
|
||||||
async function installFlow() {
|
async function installFlow() {
|
||||||
installFlowOpen.value = true;
|
installFlowOpen.value = true;
|
||||||
versionOptions.value = undefined;
|
versionOptions.value = undefined;
|
||||||
|
|||||||
@ -16,12 +16,13 @@ use crate::games::state::{GameStatusManager, GameStatusWithTransient};
|
|||||||
use crate::remote::auth::generate_authorization_header;
|
use crate::remote::auth::generate_authorization_header;
|
||||||
use crate::remote::cache::{cache_object, get_cached_object, get_cached_object_db};
|
use crate::remote::cache::{cache_object, get_cached_object, get_cached_object_db};
|
||||||
use crate::remote::requests::make_request;
|
use crate::remote::requests::make_request;
|
||||||
use crate::AppState;
|
use crate::{AppState, DB};
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct FetchGameStruct {
|
pub struct FetchGameStruct {
|
||||||
game: Game,
|
game: Game,
|
||||||
status: GameStatusWithTransient,
|
status: GameStatusWithTransient,
|
||||||
|
version: Option<GameVersion>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
#[derive(Serialize, Deserialize, Clone, Debug, Default)]
|
||||||
@ -140,6 +141,24 @@ pub fn fetch_game_logic(
|
|||||||
) -> Result<FetchGameStruct, RemoteAccessError> {
|
) -> Result<FetchGameStruct, RemoteAccessError> {
|
||||||
let mut state_handle = state.lock().unwrap();
|
let mut state_handle = state.lock().unwrap();
|
||||||
|
|
||||||
|
let handle = DB.borrow_data().unwrap();
|
||||||
|
|
||||||
|
let metadata_option = handle.applications.installed_game_version.get(&id);
|
||||||
|
let version = match metadata_option {
|
||||||
|
None => None,
|
||||||
|
Some(metadata) => Some(
|
||||||
|
handle
|
||||||
|
.applications
|
||||||
|
.game_versions
|
||||||
|
.get(&metadata.id)
|
||||||
|
.unwrap()
|
||||||
|
.get(metadata.version.as_ref().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.clone(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
drop(handle);
|
||||||
|
|
||||||
let game = state_handle.games.get(&id);
|
let game = state_handle.games.get(&id);
|
||||||
if let Some(game) = game {
|
if let Some(game) = game {
|
||||||
let status = GameStatusManager::fetch_state(&id);
|
let status = GameStatusManager::fetch_state(&id);
|
||||||
@ -147,6 +166,7 @@ pub fn fetch_game_logic(
|
|||||||
let data = FetchGameStruct {
|
let data = FetchGameStruct {
|
||||||
game: game.clone(),
|
game: game.clone(),
|
||||||
status,
|
status,
|
||||||
|
version,
|
||||||
};
|
};
|
||||||
|
|
||||||
cache_object(id, game)?;
|
cache_object(id, game)?;
|
||||||
@ -185,6 +205,7 @@ pub fn fetch_game_logic(
|
|||||||
let data = FetchGameStruct {
|
let data = FetchGameStruct {
|
||||||
game: game.clone(),
|
game: game.clone(),
|
||||||
status,
|
status,
|
||||||
|
version,
|
||||||
};
|
};
|
||||||
|
|
||||||
cache_object(id, &game)?;
|
cache_object(id, &game)?;
|
||||||
@ -196,9 +217,31 @@ pub fn fetch_game_logic_offline(
|
|||||||
id: String,
|
id: String,
|
||||||
_state: tauri::State<'_, Mutex<AppState>>,
|
_state: tauri::State<'_, Mutex<AppState>>,
|
||||||
) -> Result<FetchGameStruct, RemoteAccessError> {
|
) -> Result<FetchGameStruct, RemoteAccessError> {
|
||||||
|
let handle = DB.borrow_data().unwrap();
|
||||||
|
let metadata_option = handle.applications.installed_game_version.get(&id);
|
||||||
|
let version = match metadata_option {
|
||||||
|
None => None,
|
||||||
|
Some(metadata) => Some(
|
||||||
|
handle
|
||||||
|
.applications
|
||||||
|
.game_versions
|
||||||
|
.get(&metadata.id)
|
||||||
|
.unwrap()
|
||||||
|
.get(metadata.version.as_ref().unwrap())
|
||||||
|
.unwrap()
|
||||||
|
.clone(),
|
||||||
|
),
|
||||||
|
};
|
||||||
|
drop(handle);
|
||||||
|
|
||||||
let status = GameStatusManager::fetch_state(&id);
|
let status = GameStatusManager::fetch_state(&id);
|
||||||
let game = get_cached_object::<String, Game>(id)?;
|
let game = get_cached_object::<String, Game>(id)?;
|
||||||
Ok(FetchGameStruct { game, status })
|
|
||||||
|
Ok(FetchGameStruct {
|
||||||
|
game,
|
||||||
|
status,
|
||||||
|
version,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_game_verion_options_logic(
|
pub fn fetch_game_verion_options_logic(
|
||||||
@ -401,3 +444,54 @@ pub fn push_game_update(app_handle: &AppHandle, game_id: &String, status: GameSt
|
|||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO @quexeky fix error types (I used String lmao)
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct FrontendGameOptions {
|
||||||
|
launch_string: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
pub fn update_game_configuration(
|
||||||
|
game_id: String,
|
||||||
|
options: FrontendGameOptions,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
let mut handle = DB.borrow_data_mut().unwrap();
|
||||||
|
let installed_version = handle
|
||||||
|
.applications
|
||||||
|
.installed_game_version
|
||||||
|
.get(&game_id)
|
||||||
|
.ok_or("Game not installed")?;
|
||||||
|
|
||||||
|
|
||||||
|
let id = installed_version.id.clone();
|
||||||
|
let version = installed_version.version.clone().unwrap();
|
||||||
|
|
||||||
|
let mut existing_configuration = handle
|
||||||
|
.applications
|
||||||
|
.game_versions
|
||||||
|
.get(&id)
|
||||||
|
.unwrap()
|
||||||
|
.get(&version)
|
||||||
|
.unwrap()
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
// Add more options in here
|
||||||
|
existing_configuration.launch_command_template = options.launch_string;
|
||||||
|
|
||||||
|
// Add no more options past here
|
||||||
|
|
||||||
|
handle
|
||||||
|
.applications
|
||||||
|
.game_versions
|
||||||
|
.get_mut(&id)
|
||||||
|
.unwrap()
|
||||||
|
.insert(version.to_string(), existing_configuration);
|
||||||
|
|
||||||
|
drop(handle);
|
||||||
|
DB.save().map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
@ -33,7 +33,7 @@ use games::commands::{
|
|||||||
fetch_game, fetch_game_status, fetch_game_verion_options, fetch_library, uninstall_game,
|
fetch_game, fetch_game_status, fetch_game_verion_options, fetch_library, uninstall_game,
|
||||||
};
|
};
|
||||||
use games::downloads::commands::download_game;
|
use games::downloads::commands::download_game;
|
||||||
use games::library::Game;
|
use games::library::{update_game_configuration, Game};
|
||||||
use http::Response;
|
use http::Response;
|
||||||
use http::{header::*, response::Builder as ResponseBuilder};
|
use http::{header::*, response::Builder as ResponseBuilder};
|
||||||
use log::{debug, info, warn, LevelFilter};
|
use log::{debug, info, warn, LevelFilter};
|
||||||
@ -255,6 +255,7 @@ pub fn run() {
|
|||||||
fetch_download_dir_stats,
|
fetch_download_dir_stats,
|
||||||
fetch_game_status,
|
fetch_game_status,
|
||||||
fetch_game_verion_options,
|
fetch_game_verion_options,
|
||||||
|
update_game_configuration,
|
||||||
// Collections
|
// Collections
|
||||||
fetch_collections,
|
fetch_collections,
|
||||||
fetch_collection,
|
fetch_collection,
|
||||||
|
|||||||
@ -266,13 +266,13 @@ impl ProcessManager<'_> {
|
|||||||
.map_err(|e| ProcessError::FormatError(e.to_string()))?
|
.map_err(|e| ProcessError::FormatError(e.to_string()))?
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
info!("launching process {} in {}", launch_string, install_dir);
|
|
||||||
|
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
let mut command = Command::new("cmd");
|
let mut command = Command::new("cmd");
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
command.args(["/C", &launch_string]);
|
command.args(["/C", &launch_string]);
|
||||||
|
|
||||||
|
info!("launching (in {}): {}", install_dir, launch_string,);
|
||||||
|
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
let mut command: Command = Command::new("sh");
|
let mut command: Command = Command::new("sh");
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
|||||||
24
types.ts
24
types.ts
@ -37,6 +37,10 @@ export type Game = {
|
|||||||
mImageCarousel: string[];
|
mImageCarousel: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type GameVersion = {
|
||||||
|
launchCommandTemplate: string;
|
||||||
|
};
|
||||||
|
|
||||||
export enum AppStatus {
|
export enum AppStatus {
|
||||||
NotConfigured = "NotConfigured",
|
NotConfigured = "NotConfigured",
|
||||||
Offline = "Offline",
|
Offline = "Offline",
|
||||||
@ -54,7 +58,7 @@ export enum GameStatusEnum {
|
|||||||
Updating = "Updating",
|
Updating = "Updating",
|
||||||
Uninstalling = "Uninstalling",
|
Uninstalling = "Uninstalling",
|
||||||
SetupRequired = "SetupRequired",
|
SetupRequired = "SetupRequired",
|
||||||
Running = "Running"
|
Running = "Running",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GameStatus = {
|
export type GameStatus = {
|
||||||
@ -66,17 +70,17 @@ export enum DownloadableType {
|
|||||||
Game = "Game",
|
Game = "Game",
|
||||||
Tool = "Tool",
|
Tool = "Tool",
|
||||||
DLC = "DLC",
|
DLC = "DLC",
|
||||||
Mod = "Mod"
|
Mod = "Mod",
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DownloadableMetadata = {
|
export type DownloadableMetadata = {
|
||||||
id: string,
|
id: string;
|
||||||
version: string,
|
version: string;
|
||||||
downloadType: DownloadableType
|
downloadType: DownloadableType;
|
||||||
}
|
};
|
||||||
|
|
||||||
export type Settings = {
|
export type Settings = {
|
||||||
autostart: boolean,
|
autostart: boolean;
|
||||||
maxDownloadThreads: number,
|
maxDownloadThreads: number;
|
||||||
forceOffline: boolean
|
forceOffline: boolean;
|
||||||
}
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user