feat: version hiding

This commit is contained in:
DecDuck
2025-08-19 16:37:29 +10:00
parent 6d89b7e510
commit e63f701148
8 changed files with 78 additions and 69 deletions

View File

@ -308,8 +308,10 @@
"umuLauncherId": "UMU Launcher ID", "umuLauncherId": "UMU Launcher ID",
"umuOverride": "Override UMU Launcher Game ID", "umuOverride": "Override UMU Launcher Game ID",
"umuOverrideDesc": "By default, Drop uses a non-ID when launching with UMU Launcher. In order to get the right patches for some games, you may have to manually set this field.", "umuOverrideDesc": "By default, Drop uses a non-ID when launching with UMU Launcher. In order to get the right patches for some games, you may have to manually set this field.",
"updateMode": "Update mode", "updateMode": "Update/delta mode",
"updateModeDesc": "When enabled, these files will be installed on top of (overwriting) the previous version's. If multiple \"update modes\" are chained together, they are applied in order.", "updateModeDesc": "When enabled, these files will be installed on top of (overwriting) the previous version's. If multiple \"update modes\" are chained together, they are applied in order.",
"hide": "Hide version",
"hideDesc": "Hide version so clients cannot install it. Useful if you are using delta versions to save space, but don't want to allow users to install partial games.",
"version": "Select version to import" "version": "Select version to import"
}, },
"withoutMetadata": "Import without metadata" "withoutMetadata": "Import without metadata"

View File

@ -398,6 +398,36 @@
/> />
</Switch> </Switch>
</SwitchGroup> </SwitchGroup>
<!-- setup mode -->
<SwitchGroup as="div" class="flex items-center justify-between">
<span class="flex flex-grow flex-col">
<SwitchLabel
as="span"
class="text-sm font-medium leading-6 text-zinc-100"
passive
>{{ $t("library.admin.import.version.hide") }}</SwitchLabel
>
<SwitchDescription as="span" class="text-sm text-zinc-400">{{
$t("library.admin.import.version.hideDesc")
}}</SwitchDescription>
</span>
<Switch
v-model="versionSettings.hide"
:class="[
versionSettings.hide ? 'bg-blue-600' : 'bg-zinc-800',
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-blue-600 focus:ring-offset-2',
]"
>
<span
aria-hidden="true"
:class="[
versionSettings.hide ? 'translate-x-5' : 'translate-x-0',
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
]"
/>
</Switch>
</SwitchGroup>
<Disclosure v-slot="{ open }" as="div" class="py-2"> <Disclosure v-slot="{ open }" as="div" class="py-2">
<dt> <dt>
<DisclosureButton <DisclosureButton
@ -548,6 +578,7 @@ import {
import { XCircleIcon } from "@heroicons/vue/16/solid"; import { XCircleIcon } from "@heroicons/vue/16/solid";
import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid"; import { CheckIcon, ChevronUpDownIcon } from "@heroicons/vue/20/solid";
import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/vue/24/solid"; import { ChevronDownIcon, ChevronUpIcon } from "@heroicons/vue/24/solid";
import type { ImportVersion } from "~/server/api/v1/admin/import/version/index.post";
definePageMeta({ definePageMeta({
layout: "admin", layout: "admin",
@ -561,18 +592,7 @@ const versions = await $dropFetch(
`/api/v1/admin/import/version?id=${encodeURIComponent(gameId)}`, `/api/v1/admin/import/version?id=${encodeURIComponent(gameId)}`,
); );
const currentlySelectedVersion = ref(-1); const currentlySelectedVersion = ref(-1);
const versionSettings = ref<{ const versionSettings = ref<Partial<typeof ImportVersion.infer>>({
platform: PlatformClient | undefined;
onlySetup: boolean;
launch: string;
launchArgs: string;
setup: string;
setupArgs: string;
delta: boolean;
umuId: string;
}>({
platform: undefined, platform: undefined,
launch: "", launch: "",
launchArgs: "", launchArgs: "",
@ -581,6 +601,7 @@ const versionSettings = ref<{
delta: false, delta: false,
onlySetup: false, onlySetup: false,
umuId: "", umuId: "",
hide: false,
}); });
const versionGuesses = const versionGuesses =

View File

@ -0,0 +1,8 @@
-- DropIndex
DROP INDEX "GameTag_name_idx";
-- AlterTable
ALTER TABLE "GameVersion" ADD COLUMN "hidden" BOOLEAN NOT NULL DEFAULT false;
-- CreateIndex
CREATE INDEX "GameTag_name_idx" ON "GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));

View File

@ -56,12 +56,11 @@ model GameTag {
id String @id @default(uuid()) id String @id @default(uuid())
name String @unique name String @unique
games Game[] games Game[]
@@index([name(ops: raw("gist_trgm_ops(siglen=32)"))], type: Gist) @@index([name(ops: raw("gist_trgm_ops(siglen=32)"))], type: Gist)
} }
model GameRating { model GameRating {
id String @id @default(uuid()) id String @id @default(uuid())
@ -95,6 +94,7 @@ model GameVersion {
setupCommand String @default("") // Command to setup game (dependencies and such) setupCommand String @default("") // Command to setup game (dependencies and such)
setupArgs String[] setupArgs String[]
onlySetup Boolean @default(false) onlySetup Boolean @default(false)
hidden Boolean @default(false)
umuIdOverride String? umuIdOverride String?

View File

@ -1,20 +1,25 @@
import { type } from "arktype"; import { type } from "arktype";
import { PlatformClient } from "~/composables/types";
import { readDropValidatedBody, throwingArktype } from "~/server/arktype"; import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
import aclManager from "~/server/internal/acls"; import aclManager from "~/server/internal/acls";
import prisma from "~/server/internal/db/database"; import prisma from "~/server/internal/db/database";
import libraryManager from "~/server/internal/library"; import libraryManager from "~/server/internal/library";
import { parsePlatform } from "~/server/internal/utils/parseplatform";
const ImportVersion = type({ export const ImportVersion = type({
id: "string", id: "string",
version: "string", version: "string",
platform: "string", platform: type.valueOf(PlatformClient),
launch: "string = ''", launch: "string = ''",
launchArgs: "string = ''", launchArgs: "string = ''",
setup: "string = ''", setup: "string = ''",
setupArgs: "string = ''", setupArgs: "string = ''",
onlySetup: "boolean = false", onlySetup: "boolean = false",
hide: "boolean = false",
delta: "boolean = false", delta: "boolean = false",
umuId: "string = ''", umuId: "string = ''",
}).configure(throwingArktype); }).configure(throwingArktype);
@ -34,15 +39,12 @@ export default defineEventHandler(async (h3) => {
onlySetup, onlySetup,
delta, delta,
umuId, umuId,
hide,
} = await readDropValidatedBody(h3, ImportVersion); } = await readDropValidatedBody(h3, ImportVersion);
const platformParsed = parsePlatform(platform);
if (!platformParsed)
throw createError({ statusCode: 400, statusMessage: "Invalid platform." });
if (delta) { if (delta) {
const validOverlayVersions = await prisma.gameVersion.count({ const validOverlayVersions = await prisma.gameVersion.count({
where: { gameId: id, platform: platformParsed, delta: false }, where: { gameId: id, platform, delta: false },
}); });
if (validOverlayVersions == 0) if (validOverlayVersions == 0)
throw createError({ throw createError({
@ -75,6 +77,7 @@ export default defineEventHandler(async (h3) => {
launchArgs, launchArgs,
setup, setup,
setupArgs, setupArgs,
hide,
umuId, umuId,
delta, delta,

View File

@ -17,6 +17,7 @@ export default defineClientEventHandler(async (h3) => {
gameId: id, gameId: id,
versionName: version, versionName: version,
}, },
hidden: false,
}, },
}); });

View File

@ -13,6 +13,7 @@ export default defineClientEventHandler(async (h3) => {
const versions = await prisma.gameVersion.findMany({ const versions = await prisma.gameVersion.findMany({
where: { where: {
gameId: id, gameId: id,
hidden: false,
}, },
orderBy: { orderBy: {
versionIndex: "desc", // Latest one first versionIndex: "desc", // Latest one first

View File

@ -15,6 +15,7 @@ import { GameNotFoundError, type LibraryProvider } from "./provider";
import { logger } from "../logging"; import { logger } from "../logging";
import type { GameModel } from "~/prisma/client/models"; import type { GameModel } from "~/prisma/client/models";
import { createHash } from "node:crypto"; import { createHash } from "node:crypto";
import type { ImportVersion } from "~/server/api/v1/admin/import/version/index.post";
export function createGameImportTaskId(libraryId: string, libraryPath: string) { export function createGameImportTaskId(libraryId: string, libraryPath: string) {
return createHash("md5") return createHash("md5")
@ -231,18 +232,7 @@ class LibraryManager {
async importVersion( async importVersion(
gameId: string, gameId: string,
versionName: string, versionName: string,
metadata: { metadata: Omit<typeof ImportVersion.infer, "id" | "version">,
platform: string;
onlySetup: boolean;
setup: string;
setupArgs: string;
launch: string;
launchArgs: string;
delta: boolean;
umuId: string;
},
) { ) {
const taskId = createVersionImportTaskId(gameId, versionName); const taskId = createVersionImportTaskId(gameId, versionName);
@ -286,41 +276,24 @@ class LibraryManager {
}); });
// Then, create the database object // Then, create the database object
if (metadata.onlySetup) { await prisma.gameVersion.create({
await prisma.gameVersion.create({ data: {
data: { gameId: gameId,
gameId: gameId, versionName: versionName,
versionName: versionName, dropletManifest: manifest,
dropletManifest: manifest, versionIndex: currentIndex,
versionIndex: currentIndex, delta: metadata.delta,
delta: metadata.delta, umuIdOverride: metadata.umuId,
umuIdOverride: metadata.umuId, platform: platform,
platform: platform,
onlySetup: true, hidden: metadata.hide,
setupCommand: metadata.setup, onlySetup: metadata.onlySetup,
setupArgs: metadata.setupArgs.split(" "), setupCommand: metadata.setup,
}, setupArgs: metadata.setupArgs.split(" "),
}); launchCommand: metadata.launch,
} else { launchArgs: metadata.launchArgs.split(" "),
await prisma.gameVersion.create({ },
data: { });
gameId: gameId,
versionName: versionName,
dropletManifest: manifest,
versionIndex: currentIndex,
delta: metadata.delta,
umuIdOverride: metadata.umuId,
platform: platform,
onlySetup: false,
setupCommand: metadata.setup,
setupArgs: metadata.setupArgs.split(" "),
launchCommand: metadata.launch,
launchArgs: metadata.launchArgs.split(" "),
},
});
}
logger.info("Successfully created version!"); logger.info("Successfully created version!");