mirror of
https://github.com/Drop-OSS/drop.git
synced 2025-11-12 15:52:39 +10:00
Store overhaul (#142)
* feat: small library tweaks + company page * feat: new store view * fix: ci merge error * feat: add genres to store page * feat: sorting * feat: lock game/version imports while their tasks are running * feat: feature games * feat: tag based filtering * fix: make tags alphabetical * refactor: move a bunch of i18n to common * feat: add localizations for everything * fix: title description on panel * fix: feature carousel text * fix: i18n footer strings * feat: add tag page * fix: develop merge * feat: offline games support (don't error out if provider throws) * feat: tag management * feat: show library next to game import + small fixes * feat: most of the company and tag managers * feat: company text field editing * fix: small fixes + tsgo experiemental * feat: upload icon and banner * feat: store infinite scrolling and bulk import mode * fix: lint * fix: add drop-base to prettier ignore
This commit is contained in:
@ -108,6 +108,72 @@
|
||||
}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="whitespace-nowrap align-top py-4 pl-4 pr-3 text-sm font-medium text-zinc-100 sm:pl-3"
|
||||
>
|
||||
{{ $t("store.tags") }}
|
||||
</td>
|
||||
<td class="flex flex-col gap-1 px-3 py-4 text-sm text-zinc-400">
|
||||
<NuxtLink
|
||||
v-for="tag in game.tags"
|
||||
:key="tag.id"
|
||||
:href="`/store/t/${tag.id}`"
|
||||
class="w-min hover:underline hover:text-zinc-100 whitespace-nowrap"
|
||||
>
|
||||
{{ tag.name }}
|
||||
</NuxtLink>
|
||||
<span
|
||||
v-if="game.tags.length == 0"
|
||||
class="text-zinc-700 font-bold uppercase font-display"
|
||||
>{{ $t("store.noTags") }}</span
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="whitespace-nowrap align-top py-4 pl-4 pr-3 text-sm font-medium text-zinc-100 sm:pl-3"
|
||||
>
|
||||
{{ $t("store.developers", game.developers.length) }}
|
||||
</td>
|
||||
<td class="flex flex-col px-3 py-4 text-sm text-zinc-400">
|
||||
<NuxtLink
|
||||
v-for="developer in game.developers"
|
||||
:key="developer.id"
|
||||
:href="`/store/c/${developer.id}`"
|
||||
class="w-min hover:underline hover:text-zinc-100 whitespace-nowrap"
|
||||
>
|
||||
{{ developer.mName }}
|
||||
</NuxtLink>
|
||||
<span
|
||||
v-if="game.developers.length == 0"
|
||||
class="text-zinc-700 font-bold uppercase font-display"
|
||||
>{{ $t("store.noDevelopers") }}</span
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="whitespace-nowrap align-top py-4 pl-4 pr-3 text-sm font-medium text-zinc-100 sm:pl-3"
|
||||
>
|
||||
{{ $t("store.publishers", game.publishers.length) }}
|
||||
</td>
|
||||
<td class="flex flex-col px-3 py-4 text-sm text-zinc-400">
|
||||
<NuxtLink
|
||||
v-for="publisher in game.publishers"
|
||||
:key="publisher.id"
|
||||
:href="`/store/c/${publisher.id}`"
|
||||
class="w-min hover:underline hover:text-zinc-100 whitespace-nowrap"
|
||||
>
|
||||
{{ publisher.mName }}
|
||||
</NuxtLink>
|
||||
<span
|
||||
v-if="game.publishers.length == 0"
|
||||
class="text-zinc-700 font-bold uppercase font-display"
|
||||
>{{ $t("store.noPublishers") }}</span
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@ -225,6 +291,7 @@ const ratingArray = Array(5)
|
||||
|
||||
useHead({
|
||||
title: game.mName,
|
||||
link: [{ rel: "icon", href: useObject(game.mIconObjectId) }],
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
65
pages/store/c/[id]/index.vue
Normal file
65
pages/store/c/[id]/index.vue
Normal file
@ -0,0 +1,65 @@
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<div class="w-full overflow-x-hidden">
|
||||
<div class="relative overflow-hidden bg-zinc-900">
|
||||
<!-- Decorative background image and gradient -->
|
||||
<div aria-hidden="true" class="absolute inset-0">
|
||||
<div class="absolute inset-0 overflow-hidden">
|
||||
<img
|
||||
:src="useObject(company.mBannerObjectId)"
|
||||
alt=""
|
||||
class="size-full object-cover"
|
||||
/>
|
||||
</div>
|
||||
<div class="absolute inset-0 bg-zinc-900/75" />
|
||||
<div class="absolute inset-0 bg-linear-to-t from-zinc-900" />
|
||||
</div>
|
||||
|
||||
<!-- Callout -->
|
||||
<section
|
||||
aria-labelledby="sale-heading"
|
||||
class="relative mx-auto flex max-w-7xl flex-col items-center px-4 pt-32 pb-8 text-center sm:px-6 lg:px-8"
|
||||
>
|
||||
<div class="mx-auto max-w-2xl lg:max-w-none">
|
||||
<h2
|
||||
id="sale-heading"
|
||||
class="text-4xl font-bold font-display tracking-tight text-zinc-100 sm:text-5xl lg:text-6xl"
|
||||
>
|
||||
{{ company.mName }}
|
||||
</h2>
|
||||
<p class="mx-auto line-clamp-3 mt-4 max-w-xl text-xl text-zinc-400">
|
||||
{{ company.mDescription }}
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<StoreView
|
||||
:extra-options="[
|
||||
{
|
||||
name: 'Company',
|
||||
param: 'companyActions',
|
||||
multiple: true,
|
||||
options: [
|
||||
{ name: 'Published', param: 'published' },
|
||||
{ name: 'Developed', param: 'developed' },
|
||||
],
|
||||
},
|
||||
]"
|
||||
:params="{
|
||||
company: company.id,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const route = useRoute();
|
||||
const companyId = route.params.id;
|
||||
|
||||
const { company } = await $dropFetch(`/api/v1/companies/${companyId}`);
|
||||
|
||||
useHead({
|
||||
title: company.mName,
|
||||
link: [{ rel: "icon", href: useObject(company.mLogoObjectId) }],
|
||||
});
|
||||
</script>
|
||||
@ -24,14 +24,16 @@
|
||||
>
|
||||
<div class="relative text-center">
|
||||
<h3 class="text-base/7 font-semibold text-blue-300">
|
||||
{{ $t("store.recentlyAdded") }}
|
||||
{{ $t("store.featured") }}
|
||||
</h3>
|
||||
<h2
|
||||
class="text-3xl font-bold tracking-tight text-white sm:text-5xl"
|
||||
>
|
||||
{{ game.mName }}
|
||||
</h2>
|
||||
<p class="mt-3 text-lg text-zinc-300 line-clamp-2 max-w-xl">
|
||||
<p
|
||||
class="mt-3 text-lg text-zinc-300 line-clamp-2 max-w-xl mx-auto"
|
||||
>
|
||||
{{ game.mShortDescription }}
|
||||
</p>
|
||||
<div>
|
||||
@ -66,49 +68,12 @@
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<!-- new releases -->
|
||||
<div class="px-4 sm:px-12 py-4">
|
||||
<h1 class="text-zinc-100 text-2xl font-bold font-display">
|
||||
{{ $t("store.recentlyReleased") }}
|
||||
</h1>
|
||||
<NuxtLink class="text-blue-600 font-semibold">
|
||||
<i18n-t keypath="store.exploreMore" tag="span" scope="global">
|
||||
<template #arrow>
|
||||
<span aria-hidden="true">{{ $t("chars.arrow") }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</NuxtLink>
|
||||
<div class="mt-4">
|
||||
<GameCarousel :items="released" :min="12" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- recently updated -->
|
||||
<div class="px-4 sm:px-12 py-4" hydrate-on-visible>
|
||||
<h1 class="text-zinc-100 text-2xl font-bold font-display">
|
||||
{{ $t("store.recentlyUpdated") }}
|
||||
</h1>
|
||||
<NuxtLink class="text-blue-600 font-semibold">
|
||||
<i18n-t keypath="store.exploreMore" tag="span" scope="global">
|
||||
<template #arrow>
|
||||
<span aria-hidden="true">{{ $t("chars.arrow") }}</span>
|
||||
</template>
|
||||
</i18n-t>
|
||||
</NuxtLink>
|
||||
<div class="mt-4">
|
||||
<GameCarousel :items="updated" :min="12" />
|
||||
</div>
|
||||
</div>
|
||||
<StoreView />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const recent = await $dropFetch("/api/v1/store/recent");
|
||||
const updated = await $dropFetch("/api/v1/store/updated");
|
||||
const released = await $dropFetch("/api/v1/store/released");
|
||||
|
||||
// const developers = await $dropFetch("/api/v1/store/developers");
|
||||
// const publishers = await $dropFetch("/api/v1/store/publishers");
|
||||
const recent = await $dropFetch("/api/v1/store/featured");
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
|
||||
48
pages/store/t/[id]/index.vue
Normal file
48
pages/store/t/[id]/index.vue
Normal file
@ -0,0 +1,48 @@
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<template>
|
||||
<div class="w-full overflow-x-hidden">
|
||||
<div class="relative overflow-hidden bg-zinc-900">
|
||||
<!-- Decorative background image and gradient -->
|
||||
<div aria-hidden="true" class="absolute inset-0">
|
||||
<div class="absolute inset-0 overflow-hidden">
|
||||
<img alt="" class="size-full object-cover" />
|
||||
</div>
|
||||
<div class="absolute inset-0 bg-zinc-900/75" />
|
||||
<div class="absolute inset-0 bg-linear-to-t from-zinc-900" />
|
||||
</div>
|
||||
|
||||
<!-- Callout -->
|
||||
<section
|
||||
aria-labelledby="sale-heading"
|
||||
class="relative mx-auto flex max-w-7xl flex-col items-center px-4 pt-32 pb-8 text-center sm:px-6 lg:px-8"
|
||||
>
|
||||
<div class="mx-auto max-w-2xl lg:max-w-none">
|
||||
<h2
|
||||
id="sale-heading"
|
||||
class="text-4xl font-bold font-display tracking-tight text-zinc-100 sm:text-5xl lg:text-6xl"
|
||||
>
|
||||
{{ tag.name }}
|
||||
</h2>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<StoreView
|
||||
:prefilled="{
|
||||
tags: {
|
||||
[tag.id]: true,
|
||||
} as any,
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const route = useRoute();
|
||||
const tagId = route.params.id;
|
||||
|
||||
const { tag } = await $dropFetch(`/api/v1/tags/${tagId}`);
|
||||
|
||||
useHead({
|
||||
title: tag.name,
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user