mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2026-07-25 17:24:58 +10:00
Async downloader, better Proton support (#183)
* feat: async downloader + other fixes * feat: windows command parsing + use library path for install path * feat: better proton support * feat: style fixes and store button now uses in-app * feat: emulator rename + umu emulator fix * feat: bring process creation inline with docs * fix: clippy
This commit is contained in:
@@ -35,15 +35,18 @@
|
||||
@resume="() => resumeDownload()"
|
||||
:status="status"
|
||||
/>
|
||||
<a
|
||||
:href="remoteUrl"
|
||||
target="_blank"
|
||||
type="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="{
|
||||
path: '/store',
|
||||
query: {
|
||||
gameId: game.id,
|
||||
},
|
||||
}"
|
||||
>
|
||||
<BuildingStorefrontIcon class="mr-2 size-5" aria-hidden="true" />
|
||||
Store
|
||||
</a>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -171,7 +174,11 @@
|
||||
</div>
|
||||
|
||||
<div class="space-y-6">
|
||||
<div v-if="versionOptions && versionOptions.length > 0 && currentVersionOption">
|
||||
<div
|
||||
v-if="
|
||||
versionOptions && versionOptions.length > 0 && currentVersionOption
|
||||
"
|
||||
>
|
||||
<Listbox as="div" v-model="installVersionIndex">
|
||||
<ListboxLabel class="block text-sm/6 font-medium text-zinc-100"
|
||||
>Version</ListboxLabel
|
||||
@@ -188,7 +195,7 @@
|
||||
on
|
||||
{{ currentVersionOption.platform }} ({{
|
||||
formatKilobytes(
|
||||
currentVersionOption.size / 1024
|
||||
currentVersionOption.size.installSize / 1024,
|
||||
)
|
||||
}}B)</span
|
||||
>
|
||||
@@ -233,7 +240,8 @@
|
||||
>{{ version.displayName || version.versionPath }} on
|
||||
{{ version.platform }} ({{
|
||||
formatKilobytes(
|
||||
versionOptions[installVersionIndex].size / 1024
|
||||
versionOptions[installVersionIndex].size
|
||||
.installSize / 1024,
|
||||
)
|
||||
}}B)</span
|
||||
>
|
||||
@@ -314,8 +322,7 @@
|
||||
</div>
|
||||
<ul role="list" class="mt-2 divide-y divide-white/5">
|
||||
<li
|
||||
v-for="content in currentVersionOption
|
||||
.requiredContent"
|
||||
v-for="content in currentVersionOption.requiredContent"
|
||||
:key="content.versionId"
|
||||
:class="[
|
||||
!installDepsDisabled[content.versionId]
|
||||
@@ -353,7 +360,7 @@
|
||||
<p
|
||||
class="inline-flex items-center gap-x-1 text-xs/5 text-gray-400"
|
||||
>
|
||||
{{ formatKilobytes(content.size / 1024) }}B
|
||||
{{ formatKilobytes(content.size.installSize / 1024) }}B
|
||||
<ServerIcon class="size-3" />
|
||||
</p>
|
||||
</div>
|
||||
@@ -566,10 +573,6 @@ const id = route.params.id.toString();
|
||||
const { game: rawGame, status } = await useGame(id);
|
||||
const game = ref(rawGame);
|
||||
|
||||
const remoteUrl: string = await invoke("gen_drop_url", {
|
||||
path: `/store/${game.value.id}`,
|
||||
});
|
||||
|
||||
const bannerUrl = await useObject(game.value.mBannerObjectId);
|
||||
|
||||
// Get all available images
|
||||
@@ -577,7 +580,7 @@ const mediaUrls = await Promise.all(
|
||||
game.value.mImageCarouselObjectIds.map(async (v) => {
|
||||
const src = await useObject(v);
|
||||
return src;
|
||||
})
|
||||
}),
|
||||
);
|
||||
|
||||
const htmlDescription = micromark(game.value.mDescription);
|
||||
@@ -611,7 +614,9 @@ const installVersionIndex = ref(0);
|
||||
const installDir = ref(0);
|
||||
const installDepsDisabled = ref<{ [key: string]: boolean }>({});
|
||||
|
||||
const currentVersionOption = computed(() => versionOptions.value?.[installVersionIndex.value]);
|
||||
const currentVersionOption = computed(
|
||||
() => versionOptions.value?.[installVersionIndex.value],
|
||||
);
|
||||
async function install() {
|
||||
try {
|
||||
if (!versionOptions.value) throw new Error("Versions have not been loaded");
|
||||
@@ -661,7 +666,7 @@ async function launch() {
|
||||
try {
|
||||
const fetchedLaunchOptions = await invoke<Array<{ name: string }>>(
|
||||
"get_launch_options",
|
||||
{ id: game.value.id }
|
||||
{ id: game.value.id },
|
||||
);
|
||||
if (fetchedLaunchOptions.length == 1) {
|
||||
await launchIndex(0);
|
||||
@@ -676,7 +681,7 @@ async function launch() {
|
||||
description: `Drop failed to launch "${game.value.mName}": ${e}`,
|
||||
buttonText: "Close",
|
||||
},
|
||||
(e, c) => c()
|
||||
(e, c) => c(),
|
||||
);
|
||||
console.error(e);
|
||||
}
|
||||
@@ -707,7 +712,7 @@ async function launchIndex(index: number) {
|
||||
description: `Drop failed to launch "${game.value.mName}": ${e}`,
|
||||
buttonText: "Close",
|
||||
},
|
||||
(e, c) => c()
|
||||
(e, c) => c(),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -731,7 +736,7 @@ async function kill() {
|
||||
description: `Drop failed to stop "${game.value.mName}": ${e}`,
|
||||
buttonText: "Close",
|
||||
},
|
||||
(e, c) => c()
|
||||
(e, c) => c(),
|
||||
);
|
||||
console.error(e);
|
||||
}
|
||||
|
||||
+54
-29
@@ -9,18 +9,25 @@
|
||||
<nav class="flex flex-col" aria-label="Sidebar">
|
||||
<ul role="list" class="-mx-2 space-y-1">
|
||||
<li v-for="(item, itemIdx) in navigation" :key="item.prefix">
|
||||
<NuxtLink :href="item.route" :class="[
|
||||
itemIdx === currentNavigation
|
||||
? 'bg-zinc-800/50 text-zinc-100'
|
||||
: 'text-zinc-400 hover:bg-zinc-800/30 hover:text-zinc-200',
|
||||
'transition group flex gap-x-3 rounded-md p-2 pr-12 text-sm font-semibold leading-6',
|
||||
]">
|
||||
<component :is="item.icon" :class="[
|
||||
<NuxtLink
|
||||
:href="item.route"
|
||||
:class="[
|
||||
itemIdx === currentNavigation
|
||||
? 'text-zinc-100'
|
||||
: 'text-zinc-400 group-hover:text-zinc-200',
|
||||
'transition h-6 w-6 shrink-0',
|
||||
]" aria-hidden="true" />
|
||||
? 'bg-zinc-800/50 text-zinc-100'
|
||||
: 'text-zinc-400 hover:bg-zinc-800/30 hover:text-zinc-200',
|
||||
'transition group flex gap-x-3 rounded-md p-2 pr-12 text-sm font-semibold leading-6',
|
||||
]"
|
||||
>
|
||||
<component
|
||||
:is="item.icon"
|
||||
:class="[
|
||||
itemIdx === currentNavigation
|
||||
? 'text-zinc-100'
|
||||
: 'text-zinc-400 group-hover:text-zinc-200',
|
||||
'transition h-6 w-6 shrink-0',
|
||||
]"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
{{ item.label }}
|
||||
</NuxtLink>
|
||||
</li>
|
||||
@@ -43,7 +50,7 @@ import {
|
||||
} from "@heroicons/vue/16/solid";
|
||||
import type { Component } from "vue";
|
||||
import type { NavigationItem } from "~/types";
|
||||
import { platform } from '@tauri-apps/plugin-os';
|
||||
import { platform } from "@tauri-apps/plugin-os";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { UserIcon } from "@heroicons/vue/20/solid";
|
||||
|
||||
@@ -57,25 +64,28 @@ const systemData = await invoke<{
|
||||
const isDebugMode = ref(systemData.logLevel.toLowerCase() === "debug");
|
||||
const debugRevealed = ref(false);
|
||||
|
||||
const appState = useAppState();
|
||||
|
||||
// Track shift key state and debug reveal
|
||||
onMounted(() => {
|
||||
window.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Shift') {
|
||||
window.addEventListener("keydown", (e) => {
|
||||
if (e.key === "Shift") {
|
||||
isDebugMode.value = true;
|
||||
debugRevealed.value = true;
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('keyup', (e) => {
|
||||
if (e.key === 'Shift') {
|
||||
isDebugMode.value = debugRevealed.value || systemData.logLevel.toLowerCase() === "debug";
|
||||
|
||||
window.addEventListener("keyup", (e) => {
|
||||
if (e.key === "Shift") {
|
||||
isDebugMode.value =
|
||||
debugRevealed.value || systemData.logLevel.toLowerCase() === "debug";
|
||||
}
|
||||
});
|
||||
|
||||
// Reset debug reveal when leaving the settings page
|
||||
const router = useRouter();
|
||||
router.beforeEach((to) => {
|
||||
if (!to.path.startsWith('/settings')) {
|
||||
if (!to.path.startsWith("/settings")) {
|
||||
debugRevealed.value = false;
|
||||
isDebugMode.value = systemData.logLevel.toLowerCase() === "debug";
|
||||
}
|
||||
@@ -91,7 +101,7 @@ const navigation = computed(() => [
|
||||
icon: HomeIcon,
|
||||
},
|
||||
{
|
||||
label: "Interface",
|
||||
label: "Interface",
|
||||
route: "/settings/interface",
|
||||
prefix: "/settings/interface",
|
||||
icon: RectangleGroupIcon,
|
||||
@@ -102,27 +112,42 @@ const navigation = computed(() => [
|
||||
prefix: "/settings/downloads",
|
||||
icon: ArrowDownTrayIcon,
|
||||
},
|
||||
...(appState.value!.umuState !== "NotNeeded"
|
||||
? [
|
||||
{
|
||||
label: "Proton",
|
||||
route: "/settings/compat",
|
||||
prefix: "/settings/compat",
|
||||
icon: h("img", { src: "/proton-logo.png" }),
|
||||
},
|
||||
]
|
||||
: []),
|
||||
{
|
||||
label: "Account",
|
||||
route: "/settings/account",
|
||||
prefix: "/settings/account",
|
||||
icon: UserIcon
|
||||
icon: UserIcon,
|
||||
},
|
||||
...(isDebugMode.value ? [{
|
||||
label: "Debug Info",
|
||||
route: "/settings/debug",
|
||||
prefix: "/settings/debug",
|
||||
icon: BugAntIcon,
|
||||
}] : []),
|
||||
...(isDebugMode.value
|
||||
? [
|
||||
{
|
||||
label: "Debug Info",
|
||||
route: "/settings/debug",
|
||||
prefix: "/settings/debug",
|
||||
icon: BugAntIcon,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
]);
|
||||
|
||||
const currentPlatform = platform();
|
||||
|
||||
// Use .value to unwrap the computed ref
|
||||
const {currentNavigation} = useCurrentNavigationIndex(navigation.value);
|
||||
const { currentNavigation } = useCurrentNavigationIndex(navigation.value);
|
||||
|
||||
// Watch for navigation changes and update currentPageIndex
|
||||
watch(navigation, (newNav) => {
|
||||
currentNavigation.value = useCurrentNavigationIndex(newNav).currentNavigation.value;
|
||||
currentNavigation.value =
|
||||
useCurrentNavigationIndex(newNav).currentNavigation.value;
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
<template>
|
||||
<div class="border-b border-zinc-700 py-5">
|
||||
<h3 class="text-base font-semibold font-display leading-6 text-zinc-100">
|
||||
Proton Compatibility Layer
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="appState!.umuState === 'Installed'"
|
||||
class="rounded-md bg-green-500/10 p-4 outline outline-green-500/20"
|
||||
>
|
||||
<div class="flex">
|
||||
<div class="shrink-0">
|
||||
<CheckCircleIcon class="size-5 text-green-400" aria-hidden="true" />
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-green-200">
|
||||
UMU Launcher installed
|
||||
</h3>
|
||||
<div class="mt-2 text-sm text-green-200/85">
|
||||
<p>
|
||||
The necessary component to use the Proton Compatibility Layer is
|
||||
installed, and detected.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="rounded-md bg-red-500/15 p-4 outline outline-red-500/25">
|
||||
<div class="flex">
|
||||
<div class="shrink-0">
|
||||
<XCircleIcon class="size-5 text-red-400" aria-hidden="true" />
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-red-200">
|
||||
UMU Launcher not installed
|
||||
</h3>
|
||||
<div class="mt-2 text-sm text-red-200/80">
|
||||
<p>
|
||||
You will be unable to install or run games designed for Windows
|
||||
until you install UMU Launcher and restart Drop.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</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 class="flex">
|
||||
<div class="shrink-0">
|
||||
<ExclamationTriangleIcon class="size-5 text-yellow-400" aria-hidden="true" />
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-yellow-200">
|
||||
No default Proton layer
|
||||
</h3>
|
||||
<div class="mt-2 text-sm text-yellow-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.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="paths.data.value"
|
||||
class="mt-4 text-zinc-100 gap-x-2 inline-flex p-4 w-full items-center justify-center font-bold"
|
||||
>
|
||||
<DefaultProtonButton v-model="paths.data.value.default" />
|
||||
= Default Proton Layer
|
||||
</div>
|
||||
|
||||
<!-- autodiscovered table -->
|
||||
<div class="mt-2">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-base font-semibold text-white">
|
||||
Auto-discovered Proton Layers
|
||||
</h1>
|
||||
<p class="mt-2 text-sm text-gray-300">
|
||||
All auto-discovered Proton Layers from common paths on your system.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 flow-root">
|
||||
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
||||
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
||||
<table class="relative min-w-full divide-y divide-white/15">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
scope="col"
|
||||
class="py-3.5 pr-3 pl-4 text-left text-sm font-semibold text-white sm:pl-0"
|
||||
>
|
||||
Name
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-3.5 text-left text-sm font-semibold text-white"
|
||||
>
|
||||
Path
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-white/10">
|
||||
<tr
|
||||
v-for="path in paths.data.value?.autodiscovered"
|
||||
:key="path.path"
|
||||
>
|
||||
<td
|
||||
class="py-4 pr-3 pl-4 text-sm font-medium inline-flex items-center gap-x-2 whitespace-nowrap text-white sm:pl-0"
|
||||
>
|
||||
<DefaultProtonButton
|
||||
:path="path.path"
|
||||
v-model="paths.data.value!.default"
|
||||
/>
|
||||
{{ path.name }}
|
||||
</td>
|
||||
<td class="px-3 py-4 text-sm whitespace-nowrap text-gray-400">
|
||||
{{ path.path }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- custom table -->
|
||||
<div class="mt-8">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-base font-semibold text-white">Manual Proton Layers</h1>
|
||||
<p class="mt-2 text-sm text-gray-300">
|
||||
Add or remove custom Proton compatible layers for your games. We
|
||||
recommend
|
||||
<a
|
||||
href="https://github.com/DavidoTek/ProtonUp-Qt"
|
||||
target="_blank"
|
||||
class="text-blue-400 hover:text-blue-500"
|
||||
>ProtonUp-Qt</a
|
||||
>
|
||||
to download and manage your proton layers.
|
||||
</p>
|
||||
<p class="mt-2 text-sm text-gray-300">
|
||||
Note: deleting a custom Proton layer will
|
||||
<span class="font-bold">not</span> clear it from manually selected
|
||||
Proton layers.
|
||||
</p>
|
||||
</div>
|
||||
<div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
|
||||
<button
|
||||
@click="pickLayerModal = true"
|
||||
type="button"
|
||||
class="block rounded-md bg-blue-500 px-3 py-2 text-center text-sm font-semibold text-white shadow-xs hover:bg-blue-400 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
|
||||
>
|
||||
Add layer
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 flow-root">
|
||||
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
||||
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
||||
<table class="relative min-w-full divide-y divide-white/15">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
scope="col"
|
||||
class="py-3.5 pr-3 pl-4 text-left text-sm font-semibold text-white sm:pl-0"
|
||||
>
|
||||
Name
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-3.5 text-left text-sm font-semibold text-white"
|
||||
>
|
||||
Path
|
||||
</th>
|
||||
<th scope="col" class="py-3.5 pr-4 pl-3 sm:pr-0">
|
||||
<span class="sr-only">Delete</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-white/10">
|
||||
<tr
|
||||
v-for="(path, pathIdx) in paths.data.value?.custom"
|
||||
:key="path.path"
|
||||
>
|
||||
<td
|
||||
class="py-4 pr-3 pl-4 text-sm font-medium inline-flex items-center gap-x-2 whitespace-nowrap text-white sm:pl-0"
|
||||
>
|
||||
<DefaultProtonButton
|
||||
:path="path.path"
|
||||
v-model="paths.data.value!.default"
|
||||
/>
|
||||
{{ path.name }}
|
||||
</td>
|
||||
<td class="px-3 py-4 text-sm whitespace-nowrap text-gray-400">
|
||||
{{ path.path }}
|
||||
</td>
|
||||
<td
|
||||
class="py-4 pr-4 pl-3 text-right text-sm font-medium whitespace-nowrap sm:pr-0"
|
||||
>
|
||||
<button
|
||||
@click="() => deleteCustom(pathIdx)"
|
||||
class="text-red-400 hover:text-red-300"
|
||||
>
|
||||
Delete<span class="sr-only"></span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ModalTemplate v-model="pickLayerModal">
|
||||
<template #default>
|
||||
<div class="sm:flex sm:items-start">
|
||||
<div class="mt-3 text-center sm:mt-0 sm:text-left">
|
||||
<h3 class="text-base font-semibold text-zinc-100">
|
||||
Select your Proton layer
|
||||
</h3>
|
||||
<div class="mt-2">
|
||||
<p class="text-sm text-zinc-400">
|
||||
Select the path to your Proton layer. It should have at least two
|
||||
files, one named "proton" and one named "compatibilitytool.vdf",
|
||||
for Drop to recognise it.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-3 bg-zinc-950 ring-2 ring-zinc-800 rounded-lg text-sm">
|
||||
<span v-if="path" class="text-zinc-100">{{ path }}</span>
|
||||
<span v-else class="italic text-zinc-400">No path selected.</span>
|
||||
</div>
|
||||
|
||||
<LoadingButton :loading="false" @click="pickLayer"
|
||||
>Select path</LoadingButton
|
||||
>
|
||||
|
||||
<div
|
||||
v-if="pickError"
|
||||
class="rounded-md bg-red-500/15 p-4 outline outline-red-500/25"
|
||||
>
|
||||
<div class="flex">
|
||||
<div class="shrink-0">
|
||||
<XCircleIcon class="size-5 text-red-400" aria-hidden="true" />
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<h3 class="text-sm font-medium text-red-200">
|
||||
{{ pickError }}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #buttons>
|
||||
<LoadingButton
|
||||
@click="() => add()"
|
||||
:loading="false"
|
||||
:disabled="!path"
|
||||
type="submit"
|
||||
class="ml-2 w-full sm:w-fit"
|
||||
>
|
||||
Add
|
||||
</LoadingButton>
|
||||
<button
|
||||
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"
|
||||
@click="cancel"
|
||||
ref="cancelButtonRef"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</template>
|
||||
</ModalTemplate>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { CheckCircleIcon, XCircleIcon } from "@heroicons/vue/16/solid";
|
||||
import { ExclamationTriangleIcon } from "@heroicons/vue/24/solid";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
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 pickLayerModal = ref(false);
|
||||
const pickError = ref<string | null>(null);
|
||||
|
||||
const path = ref<string | null>(null);
|
||||
|
||||
async function pickLayer() {
|
||||
const file = await open({
|
||||
multiple: false,
|
||||
directory: true,
|
||||
canCreateDirectories: true,
|
||||
});
|
||||
path.value = file;
|
||||
pickError.value = null;
|
||||
}
|
||||
|
||||
async function add() {
|
||||
if (!path.value) return;
|
||||
pickError.value = null;
|
||||
try {
|
||||
await invoke("add_proton_layer", { path: path.value });
|
||||
path.value = null;
|
||||
pickLayerModal.value = false;
|
||||
paths.refresh();
|
||||
} catch (e) {
|
||||
pickError.value = (e as string).toString();
|
||||
}
|
||||
}
|
||||
|
||||
function cancel() {
|
||||
pickLayerModal.value = false;
|
||||
path.value = null;
|
||||
}
|
||||
|
||||
async function deleteCustom(index: number) {
|
||||
if (!paths.data.value) return;
|
||||
await invoke("remove_proton_layer", { index });
|
||||
const deleted = paths.data.value.custom.splice(index);
|
||||
if (paths.data.value.default == deleted[0].path) {
|
||||
paths.data.value.default = undefined;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
@@ -2,11 +2,15 @@
|
||||
<iframe :src="convertedStoreUrl" class="grow w-full h-full" />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ArrowTopRightOnSquareIcon,
|
||||
BuildingStorefrontIcon,
|
||||
} from "@heroicons/vue/20/solid";
|
||||
import { convertFileSrc, invoke } from "@tauri-apps/api/core";
|
||||
import { convertFileSrc } from "@tauri-apps/api/core";
|
||||
|
||||
const convertedStoreUrl = convertFileSrc("store", "server");
|
||||
const route = useRoute();
|
||||
|
||||
const gameId = route.query.gameId?.toString();
|
||||
|
||||
// This is necessary because convertFileSrc encodes the URI
|
||||
const convertedStoreUrl = convertFileSrc(`dummyvalue`, "server").replace(
|
||||
"dummyvalue",
|
||||
gameId ? `store/${gameId}` : "store",
|
||||
);
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user