mirror of
https://github.com/Drop-OSS/drop-app.git
synced 2025-11-14 00:31:33 +10:00
fix: refactoring and error handling
This commit is contained in:
@ -3,22 +3,30 @@
|
||||
class="mx-auto w-full relative flex flex-col justify-center pt-72 overflow-hidden"
|
||||
>
|
||||
<div class="absolute inset-0 z-0">
|
||||
<img
|
||||
:src="bannerUrl"
|
||||
class="w-full h-[24rem] object-cover blur-sm scale-105"
|
||||
<img
|
||||
:src="bannerUrl"
|
||||
class="w-full h-[24rem] object-cover blur-sm scale-105"
|
||||
/>
|
||||
<div
|
||||
class="absolute inset-0 bg-gradient-to-t from-zinc-900 via-zinc-900/80 to-transparent opacity-90"
|
||||
/>
|
||||
<div
|
||||
class="absolute inset-0 bg-gradient-to-r from-zinc-900/95 via-zinc-900/80 to-transparent opacity-90"
|
||||
/>
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-zinc-900 via-zinc-900/80 to-transparent opacity-90" />
|
||||
<div class="absolute inset-0 bg-gradient-to-r from-zinc-900/95 via-zinc-900/80 to-transparent opacity-90" />
|
||||
</div>
|
||||
|
||||
<div class="relative z-10">
|
||||
<div class="px-8 pb-4">
|
||||
<h1 class="text-5xl text-zinc-100 font-bold font-display drop-shadow-lg mb-8">
|
||||
<h1
|
||||
class="text-5xl text-zinc-100 font-bold font-display drop-shadow-lg mb-8"
|
||||
>
|
||||
{{ game.mName }}
|
||||
</h1>
|
||||
|
||||
|
||||
<div class="flex flex-row gap-x-4 items-stretch mb-8">
|
||||
<div class="transition-transform duration-300 hover:scale-105 active:scale-95 shadow-xl">
|
||||
<div
|
||||
class="transition-transform duration-300 hover:scale-105 active:scale-95 shadow-xl"
|
||||
>
|
||||
<GameStatusButton
|
||||
@install="() => installFlow()"
|
||||
@launch="() => launch()"
|
||||
@ -45,84 +53,54 @@
|
||||
<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">
|
||||
<h2 class="text-xl font-display font-semibold text-zinc-100 mb-4">About</h2>
|
||||
<div class="max-h-48 overflow-y-auto custom-scrollbar">
|
||||
<p class="text-zinc-400">
|
||||
{{ game.mDescription || "No description available." }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-zinc-800/50 rounded-xl p-6 backdrop-blur-sm">
|
||||
<h2 class="text-xl font-display font-semibold text-zinc-100 mb-4">Installation</h2>
|
||||
<dl class="grid grid-cols-1 gap-4 text-sm">
|
||||
<div>
|
||||
<dt class="text-zinc-400">Status</dt>
|
||||
<dd class="text-zinc-200 font-medium flex items-center gap-x-2">
|
||||
<span
|
||||
class="size-2 rounded-full"
|
||||
:class="{
|
||||
'bg-green-500': status.type === GameStatusEnum.Installed || status.type === GameStatusEnum.Running,
|
||||
'bg-yellow-500': status.type === GameStatusEnum.SetupRequired,
|
||||
'bg-blue-500 animate-pulse': status.type === GameStatusEnum.Downloading,
|
||||
'bg-zinc-500': status.type === GameStatusEnum.Remote
|
||||
}"
|
||||
/>
|
||||
{{
|
||||
status.type === GameStatusEnum.Installed ? 'Installed' :
|
||||
status.type === GameStatusEnum.Running ? 'Running' :
|
||||
status.type === GameStatusEnum.SetupRequired ? 'Setup Required' :
|
||||
status.type === GameStatusEnum.Downloading ? 'Downloading' :
|
||||
'Not Installed'
|
||||
}}
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
<div
|
||||
v-html="htmlDescription"
|
||||
class="prose prose-invert prose-blue overflow-y-auto custom-scrollbar max-w-none"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="space-y-6">
|
||||
<div class="bg-zinc-800/50 rounded-xl p-6 backdrop-blur-sm">
|
||||
<h2 class="text-xl font-display font-semibold text-zinc-100 mb-4">Game Images</h2>
|
||||
<div class="relative pb-8">
|
||||
<h2 class="text-xl font-display font-semibold text-zinc-100 mb-4">
|
||||
Game Images
|
||||
</h2>
|
||||
<div class="relative">
|
||||
<div v-if="mediaUrls.length > 0">
|
||||
<div
|
||||
<div
|
||||
class="relative aspect-video rounded-lg overflow-hidden cursor-pointer group"
|
||||
>
|
||||
<div
|
||||
<div
|
||||
class="absolute inset-0"
|
||||
@click="fullscreenImage = mediaUrls[currentImageIndex]"
|
||||
>
|
||||
<TransitionGroup
|
||||
name="slide"
|
||||
tag="div"
|
||||
class="h-full"
|
||||
>
|
||||
<img
|
||||
v-for="(url, index) in mediaUrls"
|
||||
<TransitionGroup name="slide" tag="div" class="h-full">
|
||||
<img
|
||||
v-for="(url, index) in mediaUrls"
|
||||
:key="url"
|
||||
:src="url"
|
||||
:src="url"
|
||||
class="absolute inset-0 w-full h-full object-cover"
|
||||
v-show="index === currentImageIndex"
|
||||
/>
|
||||
</TransitionGroup>
|
||||
</div>
|
||||
|
||||
<div class="absolute inset-0 flex items-center justify-between px-4 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none">
|
||||
<div
|
||||
class="absolute inset-0 flex items-center justify-between px-4 opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none"
|
||||
>
|
||||
<div class="pointer-events-auto">
|
||||
<button
|
||||
<button
|
||||
v-if="mediaUrls.length > 1"
|
||||
@click.stop="previousImage()"
|
||||
@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"
|
||||
>
|
||||
<ChevronLeftIcon class="size-5" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="pointer-events-auto">
|
||||
<button
|
||||
<button
|
||||
v-if="mediaUrls.length > 1"
|
||||
@click.stop="nextImage()"
|
||||
@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"
|
||||
>
|
||||
<ChevronRightIcon class="size-5" />
|
||||
@ -130,14 +108,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none" />
|
||||
<div class="absolute bottom-4 right-4 flex items-center gap-x-2 text-white opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none">
|
||||
<div
|
||||
class="absolute inset-0 bg-gradient-to-t from-black/50 to-transparent opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none"
|
||||
/>
|
||||
<div
|
||||
class="absolute bottom-4 right-4 flex items-center gap-x-2 text-white opacity-0 group-hover:opacity-100 transition-opacity pointer-events-none"
|
||||
>
|
||||
<ArrowsPointingOutIcon class="size-5" />
|
||||
<span class="text-sm font-medium">View Fullscreen</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="absolute -bottom-2 left-1/2 -translate-x-1/2 flex gap-x-2">
|
||||
<div
|
||||
class="absolute -bottom-2 left-1/2 -translate-x-1/2 flex gap-x-2"
|
||||
>
|
||||
<button
|
||||
v-for="(_, index) in mediaUrls"
|
||||
:key="index"
|
||||
@ -146,19 +130,21 @@
|
||||
:class="[
|
||||
currentImageIndex === index
|
||||
? 'bg-zinc-100 scale-125'
|
||||
: 'bg-zinc-600 hover:bg-zinc-500'
|
||||
: 'bg-zinc-600 hover:bg-zinc-500',
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else
|
||||
<div
|
||||
v-else
|
||||
class="aspect-video rounded-lg overflow-hidden bg-zinc-900/50 flex flex-col items-center justify-center text-center px-4"
|
||||
>
|
||||
<PhotoIcon class="size-12 text-zinc-700 mb-2" />
|
||||
<p class="text-zinc-600 font-medium">No images available</p>
|
||||
<p class="text-zinc-700 text-sm">Game screenshots will appear here when available</p>
|
||||
<PhotoIcon class="size-12 text-zinc-500 mb-2" />
|
||||
<p class="text-zinc-400 font-medium">No images available</p>
|
||||
<p class="text-zinc-500 text-sm">
|
||||
Game screenshots will appear here when available
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -385,7 +371,7 @@
|
||||
</template>
|
||||
</ModalTemplate>
|
||||
|
||||
<Transition
|
||||
<Transition
|
||||
enter="transition ease-out duration-300"
|
||||
enter-from="opacity-0"
|
||||
enter-to="opacity-100"
|
||||
@ -393,54 +379,56 @@
|
||||
leave-from="opacity-100"
|
||||
leave-to="opacity-0"
|
||||
>
|
||||
<div
|
||||
v-if="fullscreenImage"
|
||||
class="fixed inset-0 z-50 bg-black/95 flex items-center justify-center"
|
||||
<div
|
||||
v-if="fullscreenImage"
|
||||
class="fixed inset-0 z-50 bg-black/95 flex items-center justify-center"
|
||||
@click="fullscreenImage = null"
|
||||
>
|
||||
<div
|
||||
<div
|
||||
class="relative w-full h-full flex items-center justify-center"
|
||||
@click.stop
|
||||
>
|
||||
<button
|
||||
<button
|
||||
class="absolute top-4 right-4 p-2 rounded-full bg-zinc-900/50 text-zinc-100 hover:bg-zinc-900 transition-colors"
|
||||
@click.stop="fullscreenImage = null"
|
||||
>
|
||||
<XMarkIcon class="size-6" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
<button
|
||||
v-if="mediaUrls.length > 1"
|
||||
@click.stop="previousImage()"
|
||||
@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
|
||||
<button
|
||||
v-if="mediaUrls.length > 1"
|
||||
@click.stop="nextImage()"
|
||||
@click.stop="nextImage()"
|
||||
class="absolute right-4 p-3 rounded-full bg-zinc-900/50 text-zinc-100 hover:bg-zinc-900 transition-colors"
|
||||
>
|
||||
<ChevronRightIcon class="size-6" />
|
||||
</button>
|
||||
|
||||
<TransitionGroup
|
||||
name="slide"
|
||||
<TransitionGroup
|
||||
name="slide"
|
||||
tag="div"
|
||||
class="w-full h-full flex items-center justify-center"
|
||||
@click.stop
|
||||
>
|
||||
<img
|
||||
<img
|
||||
v-for="(url, index) in mediaUrls"
|
||||
v-show="currentImageIndex === index"
|
||||
:key="url"
|
||||
:src="url"
|
||||
:src="url"
|
||||
class="max-h-[90vh] max-w-[90vw] object-contain"
|
||||
:alt="`${game.mName} screenshot ${index + 1}`"
|
||||
/>
|
||||
</TransitionGroup>
|
||||
|
||||
<div class="absolute bottom-4 left-1/2 -translate-x-1/2 px-4 py-2 rounded-full bg-zinc-900/50 backdrop-blur-sm">
|
||||
<div
|
||||
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 }}
|
||||
</p>
|
||||
@ -476,6 +464,7 @@ import { BuildingStorefrontIcon } from "@heroicons/vue/24/outline";
|
||||
import { XCircleIcon } from "@heroicons/vue/24/solid";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { GameStatusEnum } from "~/types";
|
||||
import { micromark } from "micromark";
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
@ -491,7 +480,11 @@ const remoteUrl: string = await invoke("gen_drop_url", {
|
||||
const bannerUrl = await useObject(game.value.mBannerId);
|
||||
|
||||
// Get all available images
|
||||
const mediaUrls = await Promise.all(game.value.mImageCarousel.map((id) => useObject(id)));
|
||||
const mediaUrls = await Promise.all(
|
||||
game.value.mImageCarousel.map((id) => useObject(id))
|
||||
);
|
||||
|
||||
const htmlDescription = micromark(game.value.mDescription);
|
||||
|
||||
const installFlowOpen = ref(false);
|
||||
const versionOptions = ref<
|
||||
@ -522,8 +515,7 @@ const installVersionIndex = ref(0);
|
||||
const installDir = ref(0);
|
||||
async function install() {
|
||||
try {
|
||||
if (!versionOptions.value)
|
||||
throw new Error("Versions have not been loaded");
|
||||
if (!versionOptions.value) throw new Error("Versions have not been loaded");
|
||||
installLoading.value = true;
|
||||
await invoke("download_game", {
|
||||
gameId: game.value.id,
|
||||
@ -585,7 +577,8 @@ function nextImage() {
|
||||
}
|
||||
|
||||
function previousImage() {
|
||||
currentImageIndex.value = (currentImageIndex.value - 1 + mediaUrls.length) % mediaUrls.length;
|
||||
currentImageIndex.value =
|
||||
(currentImageIndex.value - 1 + mediaUrls.length) % mediaUrls.length;
|
||||
}
|
||||
|
||||
const fullscreenImage = ref<string | null>(null);
|
||||
|
||||
Reference in New Issue
Block a user