diff --git a/.vscode/settings.json b/.vscode/settings.json index 0570fe39..510ff207 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,11 +4,7 @@ "strings": "on" }, "i18n-ally.extract.autoDetect": true, - "i18n-ally.extract.ignored": [ - "string >= 14", - "string.alphanumeric >= 5", - "/api/v1/admin/import/version/preload?id=${encodeURIComponent(\n gameId,\n )}&version=${encodeURIComponent(version)}" - ], + "i18n-ally.extract.ignored": ["string >= 14", "string.alphanumeric >= 5"], "i18n-ally.extract.ignoredByFiles": { "components/NewsArticleCreateButton.vue": ["[", "`", "Enter"], "pages/admin/library/sources/index.vue": ["Filesystem"], diff --git a/components/GameEditor/Version.vue b/components/GameEditor/Version.vue index 28165fad..59403aa8 100644 --- a/components/GameEditor/Version.vue +++ b/components/GameEditor/Version.vue @@ -89,7 +89,7 @@ - {{ version.versionPath }} + {{ version.versionPath }} {{ version.delta }} {{ version.versionIndex }} + @@ -293,8 +528,23 @@ import { ArrowTopRightOnSquareIcon, InformationCircleIcon, StarIcon, + WrenchScrewdriverIcon, + ArrowLongLeftIcon, + ArrowLongRightIcon, + ChevronDownIcon, + FunnelIcon, } from "@heroicons/vue/20/solid"; import { MagnifyingGlassIcon } from "@heroicons/vue/24/outline"; +import type { AdminLibraryGame } from "~/server/api/v1/admin/library/index.get"; +import { + Disclosure, + DisclosureButton, + DisclosurePanel, + Menu, + MenuButton, + MenuItem, + MenuItems, +} from "@headlessui/vue"; const { t } = useI18n(); @@ -308,31 +558,72 @@ useHead({ const searchQuery = ref(""); -const libraryState = await $dropFetch("/api/v1/admin/library"); -type LibraryStateGame = (typeof libraryState.games)[number]["game"]; +const { unimportedGames, hasLibraries } = await $dropFetch( + "/api/v1/admin/library/libraries", +); + +const route = useRoute(); +const router = useRouter(); -const toImport = ref( - Object.values(libraryState.unimportedGames).flat().length > 0, +// Hard limit on server +const pageSize = 24; +const currentIndex = ref( + route.query.page ? parseInt(route.query.page.toString()) - 1 : 0, ); +const maxIndex = ref(0); +const maxPages = computed(() => Math.ceil(maxIndex.value / pageSize)); -const libraryGames = ref< - Array< - LibraryStateGame & { - status: "online" | "offline"; - hasNotifications?: boolean; - notifications: { - noVersions?: boolean; - toImport?: boolean; - offline?: boolean; - }; - } - > ->( - libraryState.games.map((e) => { +const games = ref([]); +const gamesLoading = ref(false); + +async function fetchPage() { + gamesLoading.value = true; + const { results, count } = await $dropFetch("/api/v1/admin/library", { + query: { + skip: currentIndex.value * pageSize, + limit: pageSize, + }, + failTitle: "Failed to fetch game library", + }); + maxIndex.value = count; + games.value = results; + gamesLoading.value = false; + router.push({ + path: route.path, + query: { + ...route.query, + page: currentIndex.value + 1, + }, + }); +} + +function nextPage() { + if (currentIndex.value < maxPages.value - 1) { + currentIndex.value++; + } +} + +function previousPage() { + if (currentIndex.value > 0) { + currentIndex.value--; + } +} + +await fetchPage(); + +watch(currentIndex, () => { + fetchPage(); + document.body.scrollTop = document.documentElement.scrollTop = 0; +}); + +const toImport = ref(Object.values(unimportedGames).flat().length > 0); + +const libraryGames = computed(() => + games.value.map((e) => { if (e.status == "offline") { return { ...e.game, - status: "offline" as const, + status: "offline", hasNotifications: true, notifications: { offline: true, @@ -355,19 +646,6 @@ const libraryGames = ref< }), ); -const filteredLibraryGames = computed(() => - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore excessively deep ts - libraryGames.value.filter((e) => { - if (!searchQuery.value) return true; - const searchQueryLower = searchQuery.value.toLowerCase(); - if (e.mName.toLowerCase().includes(searchQueryLower)) return true; - if (e.mShortDescription.toLowerCase().includes(searchQueryLower)) - return true; - return false; - }), -); - async function deleteGame(id: string) { await $dropFetch(`/api/v1/admin/game/${id}`, { method: "DELETE", @@ -396,4 +674,73 @@ async function featureGame(id: string) { libraryGames.value[gameIndex].featured = !game.featured; gameFeatureLoading.value[game.id] = false; } + +const currentFilters = ref<{ [key: string]: boolean }>({}); + +function createFilterKey( + filter: { value: string }, + subfilter: { value: string }, +) { + return `${filter.value}.${subfilter.value}`; +} + +const filters = computed( + () => + ({ + version: [ + { + value: "none", + label: t("library.admin.nav.filters.version.none"), + }, + { + value: "available", + label: t("library.admin.nav.filters.version.available"), + }, + ], + metadata: [ + { + value: "featured", + label: t("library.admin.nav.filters.metadata.featured"), + }, + { + value: "noCarousel", + label: t("library.admin.nav.filters.metadata.noCarousel"), + }, + { + value: "emptyDescription", + label: t("library.admin.nav.filters.metadata.emptyDescription"), + }, + ], + }) as const, +); + +const filterScaffold = computed( + () => + ({ + version: { + title: t("library.admin.nav.filters.version.title"), + value: "version", + // eslint-disable-next-line @typescript-eslint/no-explicit-any + values: filters.value.version as any, + }, + metadata: { + title: t("library.admin.nav.filters.metadata.title"), + value: "metadata", + // eslint-disable-next-line @typescript-eslint/no-explicit-any + values: filters.value.metadata as any, + }, + }) satisfies { + [key in keyof typeof filters.value]: { + title: string; + value: string; + values: Array<{ value: string; label: string }>; + }; + }, +); + +const sortOptions = [ + { name: "Most Popular", href: "#", current: true }, + { name: "Best Rating", href: "#", current: false }, + { name: "Newest", href: "#", current: false }, +]; diff --git a/pages/admin/library/mass-import.vue b/pages/admin/library/mass-import.vue new file mode 100644 index 00000000..6b1f87dc --- /dev/null +++ b/pages/admin/library/mass-import.vue @@ -0,0 +1,351 @@ + + + diff --git a/pages/admin/task/[id]/index.vue b/pages/admin/task/[id]/index.vue index bcdb0a4f..79d52346 100644 --- a/pages/admin/task/[id]/index.vue +++ b/pages/admin/task/[id]/index.vue @@ -40,7 +40,7 @@ -