Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
5d2eda5
feat: small library tweaks + company page
DecDuck Jul 2, 2025
59a9dd0
feat: new store view
DecDuck Jul 20, 2025
0799413
fix: ci merge error
DecDuck Jul 20, 2025
9ad8ebc
feat: add genres to store page
DecDuck Jul 20, 2025
cff2c09
feat: sorting
DecDuck Jul 21, 2025
cb019db
feat: lock game/version imports while their tasks are running
DecDuck Jul 21, 2025
b8b187e
feat: feature games
DecDuck Jul 21, 2025
c409476
feat: tag based filtering
DecDuck Jul 21, 2025
1642ab1
fix: make tags alphabetical
DecDuck Jul 21, 2025
0ab7fa3
refactor: move a bunch of i18n to common
DecDuck Jul 21, 2025
93d6220
feat: add localizations for everything
DecDuck Jul 21, 2025
0cc91f2
fix: title description on panel
DecDuck Jul 22, 2025
3ac2ab4
fix: feature carousel text
DecDuck Jul 22, 2025
c734e56
fix: i18n footer strings
DecDuck Jul 22, 2025
2ebce1a
feat: add tag page
DecDuck Jul 25, 2025
906607e
Merge branch 'develop' into 64-store-overhaul
DecDuck Jul 26, 2025
139ff6d
fix: develop merge
DecDuck Jul 26, 2025
fc4848f
feat: offline games support (don't error out if provider throws)
DecDuck Jul 27, 2025
228b9d3
feat: tag management
DecDuck Jul 27, 2025
73f299c
feat: show library next to game import + small fixes
DecDuck Jul 27, 2025
61bfab3
feat: most of the company and tag managers
DecDuck Jul 27, 2025
bc54fc5
feat: company text field editing
DecDuck Jul 27, 2025
72feb6d
fix: small fixes + tsgo experiemental
DecDuck Jul 28, 2025
1c311dc
feat: upload icon and banner
DecDuck Jul 30, 2025
52ca719
feat: store infinite scrolling and bulk import mode
DecDuck Jul 30, 2025
f25db46
fix: lint
DecDuck Jul 30, 2025
704c1cb
fix: add drop-base to prettier ignore
DecDuck Jul 30, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1 +1 @@
drop-base/
drop-base/
4 changes: 0 additions & 4 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,6 @@ type(scope)!: subject
```

- `type`: the type of the commit is one of the following:

- `feat`: new features.
- `fix`: bug fixes.
- `docs`: documentation changes.
Expand All @@ -165,7 +164,6 @@ type(scope)!: subject
- `scope`: section of the codebase that the commit makes changes to. If it makes changes to
many sections, or if no section in particular is modified, leave blank without the parentheses.
Examples:

- Commit that changes the `git` plugin:

```
Expand All @@ -179,7 +177,6 @@ type(scope)!: subject
```

For changes to plugins or themes, the scope should be the plugin or theme name:

- ✅ `fix(agnoster): commit subject`
- ❌ `fix(theme/agnoster): commit subject`

Expand Down Expand Up @@ -209,7 +206,6 @@ type(scope)!: subject
to specify other details, you can use the commit body, but it won't be visible.

Formatting tricks: the commit subject may contain:

- Links to related issues or PRs by writing `#issue`. This will be highlighted by the changelog tool:

```
Expand Down
33 changes: 7 additions & 26 deletions components/AddLibraryButton.vue
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@
</Menu>
</div>

<CreateCollectionModal
<ModalCreateCollection
v-model="createCollectionModal"
:game-id="props.gameId"
/>
Expand Down Expand Up @@ -122,20 +122,9 @@ async function toggleLibrary() {
body: {
id: props.gameId,
},
failTitle: t("errors.library.add.title"),
});
await refreshLibrary();
} catch (e) {
createModal(
ModalType.Notification,
{
title: t("errors.library.add.title"),
description: t("errors.library.add.desc", [
// @ts-expect-error attempt to display statusMessage on error
e?.statusMessage ?? t("errors.unknown"),
]),
},
(_, c) => c(),
);
} finally {
isLibraryLoading.value = false;
}
Expand All @@ -147,26 +136,18 @@ async function toggleCollection(id: string) {
if (!collection) return;
const index = collection.entries.findIndex((e) => e.gameId == props.gameId);

await $dropFetch(`/api/v1/collection/${id}/entry`, {
await $dropFetch(`/api/v1/collection/:id/entry`, {
method: index == -1 ? "POST" : "DELETE",
params: { id },
body: {
id: props.gameId,
},
failTitle: t("errors.library.add.title"),
});

await refreshCollection(id);
} catch (e) {
createModal(
ModalType.Notification,
{
title: t("errors.library.add.title"),
description: t("errors.library.add.desc", [
// @ts-expect-error attempt to display statusMessage on error
e?.statusMessage ?? t("errors.unknown"),
]),
},
(_, c) => c(),
);
} finally {
/* empty */
}
}
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,11 @@
<li v-for="game in filteredLibrary" :key="game.id" class="flex">
<NuxtLink
:to="`/library/game/${game.id}`"
class="flex flex-row items-center w-full p-1 rounded-md transition-all duration-200 hover:bg-zinc-800 hover:scale-105 hover:shadow-lg active:scale-95"
class="flex flex-row items-center w-full p-2 rounded-md transition-all duration-200 hover:bg-zinc-800 hover:scale-105 hover:shadow-lg active:scale-95"
>
<img
:src="useObject(game.mCoverObjectId)"
class="h-9 w-9 flex-shrink-0 rounded transition-all duration-300 group-hover:scale-105 hover:rotate-[-2deg] hover:shadow-lg"
:src="useObject(game.mIconObjectId)"
class="h-5 flex-shrink-0 rounded transition-all duration-300 group-hover:scale-105 hover:rotate-[-2deg] hover:shadow-lg"
alt=""
/>
<div class="min-w-0 flex-1 pl-2.5">
Expand Down
File renamed without changes.
4 changes: 1 addition & 3 deletions components/GameCarousel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,7 @@ const props = defineProps<{
width?: number;
}>();

const { showGamePanelTextDecoration } = await $dropFetch(
`/api/v1/admin/settings`,
);
const { showGamePanelTextDecoration } = await $dropFetch(`/api/v1/settings`);

const currentComponent = ref<HTMLDivElement>();

Expand Down
99 changes: 66 additions & 33 deletions components/GameEditor/Metadata.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,14 @@
class="relative inline-flex gap-x-3 items-center rounded-md bg-blue-600 px-3 py-2 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
@click="() => (showEditCoreMetadata = true)"
>
{{ $t("edit") }} <PencilIcon class="size-4" />
{{ $t("common.edit") }} <PencilIcon class="size-4" />
</button>
</div>

<div class="grid grid-cols-1 lg:grid-cols-3 gap-4 pt-8">
<MultiItemSelector v-model="currentTags" :items="tags" />
</div>

<!-- image carousel pick -->
<div class="border-b border-zinc-700">
<div class="border-b border-zinc-700 py-4">
Expand Down Expand Up @@ -268,7 +272,7 @@
</div>
</div>
</div>
<UploadFileDialog
<ModalUploadFile
v-model="showUploadModal"
:options="{ id: game.id }"
accept="image/*"
Expand Down Expand Up @@ -314,7 +318,7 @@
class="mt-3 inline-flex w-full justify-center rounded-md bg-zinc-900 px-3 py-2 text-sm font-semibold text-zinc-100 shadow-sm ring-1 ring-inset ring-zinc-700 hover:bg-zinc-950 transition-all duration-200 hover:scale-105 hover:shadow-lg active:scale-95 sm:mt-0 sm:w-auto"
@click="showAddCarouselModal = false"
>
{{ $t("close") }}
{{ $t("common.close") }}
</button>
</template>
</ModalTemplate>
Expand All @@ -335,7 +339,7 @@
class="inline-flex items-center gap-x-1.5 rounded-md bg-blue-600 px-1.5 py-0.5 text-sm font-semibold text-white shadow-sm hover:bg-blue-500 transition-all duration-200 hover:scale-105 hover:shadow-lg hover:shadow-blue-500/25 active:scale-95 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
@click="() => insertImageAtCursor(image)"
>
{{ $t("insert") }}
{{ $t("common.insert") }}
</button>
</div>
</div>
Expand Down Expand Up @@ -424,7 +428,7 @@
:class="['inline-flex w-full shadow-sm sm:ml-3 sm:w-auto']"
@click="() => coreMetadataUpdate_wrapper()"
>
{{ $t("save") }}
{{ $t("common.save") }}
</LoadingButton>
<button
ref="cancelButtonRef"
Expand All @@ -440,7 +444,7 @@
</template>

<script setup lang="ts">
import type { GameModel } from "~/prisma/client/models";
import type { GameModel, GameTagModel } from "~/prisma/client/models";
import { micromark } from "micromark";
import {
CheckIcon,
Expand All @@ -451,25 +455,42 @@ import {
import type { SerializeObject } from "nitropack";
import type { H3Error } from "h3";

definePageMeta({
layout: "admin",
});

const showUploadModal = ref(false);
const showAddCarouselModal = ref(false);
const showAddImageDescriptionModal = ref(false);
const showEditCoreMetadata = ref(false);
const mobileShowFinalDescription = ref(true);

const game = defineModel<SerializeObject<GameModel>>() as Ref<
SerializeObject<GameModel>
>;
type ModelType = SerializeObject<GameModel & { tags: Array<GameTagModel> }>;
const game = defineModel<ModelType>() as Ref<ModelType>;
if (!game.value)
throw createError({
statusCode: 500,
statusMessage: "Game not provided to editor component",
});

const currentTags = ref<{ [key: string]: boolean }>(
Object.fromEntries(game.value.tags.map((e) => [e.id, true])),
);
const tags = (await $dropFetch("/api/v1/admin/tags")).map(
(e) => ({ name: e.name, param: e.id }) satisfies StoreSortOption,
);

watch(
currentTags,
async (v) => {
await $dropFetch(`/api/v1/admin/game/:id/tags`, {
method: "PATCH",
params: {
id: game.value.id,
},
body: { tags: Object.keys(v) },
failTitle: "Failed to update game tags",
});
},
{ deep: true },
);

const { t } = useI18n();

// I don't know why I split these fields off.
Expand All @@ -493,7 +514,7 @@ function coreMetadataUploadFiles(e: InputEvent) {
{
title: t("errors.upload.title"),
description: t("errors.upload.description", [t("errors.unknown")]),
buttonText: t("close"),
buttonText: t("common.close"),
},
(e, c) => c(),
);
Expand All @@ -510,14 +531,16 @@ async function coreMetadataUpdate() {
formData.append("icon", newIcon);
}

formData.append("id", game.value.id);
formData.append("name", coreMetadataName.value);
formData.append("description", coreMetadataDescription.value);

const result = await $dropFetch(`/api/v1/admin/game/metadata`, {
method: "POST",
body: formData,
});
const result = await $dropFetch(
`/api/v1/admin/game/${game.value.id}/metadata`,
{
method: "POST",
body: formData,
},
);
return result;
}

Expand All @@ -532,14 +555,16 @@ function coreMetadataUpdate_wrapper() {
description: t("errors.game.metadata.description", [
(e as H3Error)?.statusMessage ?? t("errors.unknown"),
]),
buttonText: t("close"),
buttonText: t("common.close"),
},
(e, c) => c(),
);
})
.then((newGame) => {
console.log(newGame);
if (!newGame) return;
Object.assign(game.value, newGame);
coreMetadataIconUrl.value = useObject(newGame.mIconObjectId);
})
.finally(() => {
coreMetadataLoading.value = false;
Expand Down Expand Up @@ -573,10 +598,12 @@ watch(descriptionHTML, (_v) => {
savingTimeout = setTimeout(async () => {
try {
descriptionSaving.value = DescriptionSavingState.Loading;
await $dropFetch("/api/v1/admin/game", {
await $dropFetch(`/api/v1/admin/game/:id`, {
method: "PATCH",
body: {
params: {
id: game.value.id,
},
body: {
mDescription: game.value.mDescription,
} satisfies PatchGameBody,
});
Expand All @@ -589,7 +616,7 @@ watch(descriptionHTML, (_v) => {
description: t("errors.game.description.description", [
(e as H3Error)?.statusMessage ?? t("errors.unknown"),
]),
buttonText: t("close"),
buttonText: t("common.close"),
},
(e, c) => c(),
);
Expand Down Expand Up @@ -617,10 +644,12 @@ function insertImageAtCursor(id: string) {
async function updateBannerImage(id: string) {
try {
if (game.value.mBannerObjectId == id) return;
const { mBannerObjectId } = await $dropFetch("/api/v1/admin/game", {
const { mBannerObjectId } = await $dropFetch(`/api/v1/admin/game/:id`, {
method: "PATCH",
body: {
params: {
id: game.value.id,
},
body: {
mBannerObjectId: id,
} satisfies PatchGameBody,
});
Expand All @@ -633,7 +662,7 @@ async function updateBannerImage(id: string) {
description: t("errors.game.banner.description", [
(e as H3Error)?.statusMessage ?? t("errors.unknown"),
]),
buttonText: t("close"),
buttonText: t("common.close"),
},
(e, c) => c(),
);
Expand All @@ -643,10 +672,12 @@ async function updateBannerImage(id: string) {
async function updateCoverImage(id: string) {
try {
if (game.value.mCoverObjectId == id) return;
const { mCoverObjectId } = await $dropFetch("/api/v1/admin/game", {
const { mCoverObjectId } = await $dropFetch(`/api/v1/admin/game/:id`, {
method: "PATCH",
body: {
params: {
id: game.value.id,
},
body: {
mCoverObjectId: id,
} satisfies PatchGameBody,
});
Expand All @@ -659,7 +690,7 @@ async function updateCoverImage(id: string) {
description: t("errors.game.cover.description", [
(e as H3Error)?.statusMessage ?? t("errors.unknown"),
]),
buttonText: t("close"),
buttonText: t("common.close"),
},
(e, c) => c(),
);
Expand Down Expand Up @@ -688,7 +719,7 @@ async function deleteImage(id: string) {
description: t("errors.game.deleteImage.description", [
(e as H3Error)?.statusMessage ?? t("errors.unknown"),
]),
buttonText: t("close"),
buttonText: t("common.close"),
},
(e, c) => c(),
);
Expand All @@ -715,10 +746,12 @@ function removeImageFromCarousel(id: string) {

async function updateImageCarousel() {
try {
await $dropFetch("/api/v1/admin/game", {
await $dropFetch(`/api/v1/admin/game/:id`, {
method: "PATCH",
body: {
params: {
id: game.value.id,
},
body: {
mImageCarouselObjectIds: game.value.mImageCarouselObjectIds,
} satisfies PatchGameBody,
});
Expand All @@ -730,7 +763,7 @@ async function updateImageCarousel() {
description: t("errors.game.carousel.description", [
(e as H3Error)?.statusMessage ?? t("errors.unknown"),
]),
buttonText: t("close"),
buttonText: t("common.close"),
},
(e, c) => c(),
);
Expand Down
Loading