mirror of
https://github.com/Drop-OSS/drop.git
synced 2025-11-10 04:22:09 +10:00
game version re-ordering
This commit is contained in:
@ -10,7 +10,7 @@
|
|||||||
"postinstall": "nuxt prepare"
|
"postinstall": "nuxt prepare"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@drop/droplet": "^0.4.4",
|
"@drop/droplet": "^0.5.0",
|
||||||
"@headlessui/vue": "^1.7.23",
|
"@headlessui/vue": "^1.7.23",
|
||||||
"@heroicons/vue": "^2.1.5",
|
"@heroicons/vue": "^2.1.5",
|
||||||
"@prisma/client": "5.20.0",
|
"@prisma/client": "5.20.0",
|
||||||
@ -28,7 +28,8 @@
|
|||||||
"turndown": "^7.2.0",
|
"turndown": "^7.2.0",
|
||||||
"uuid": "^10.0.0",
|
"uuid": "^10.0.0",
|
||||||
"vue": "latest",
|
"vue": "latest",
|
||||||
"vue-router": "latest"
|
"vue-router": "latest",
|
||||||
|
"vuedraggable": "^4.1.0"
|
||||||
},
|
},
|
||||||
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
|
"packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@ -46,7 +47,7 @@
|
|||||||
"tailwindcss": "^3.4.13"
|
"tailwindcss": "^3.4.13"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"@drop/droplet-linux-x64-gnu": "^0.4.4",
|
"@drop/droplet-linux-x64-gnu": "^0.5.0",
|
||||||
"@drop/droplet-win32-x64-msvc": "^0.4.4"
|
"@drop/droplet-win32-x64-msvc": "^0.5.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -74,7 +74,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</Listbox>
|
</Listbox>
|
||||||
|
|
||||||
<div class="flex flex-col gap-4 max-w-md" v-if="versionSettings">
|
<div class="flex flex-col gap-8 max-w-md" v-if="versionSettings">
|
||||||
<div>
|
<div>
|
||||||
<label
|
<label
|
||||||
for="startup"
|
for="startup"
|
||||||
@ -130,7 +130,41 @@
|
|||||||
<PlatformSelector v-model="versionSettings.platform">
|
<PlatformSelector v-model="versionSettings.platform">
|
||||||
Version platform
|
Version platform
|
||||||
</PlatformSelector>
|
</PlatformSelector>
|
||||||
<LoadingButton @click="startImport_wrapper" class="w-fit" :loading="importLoading">
|
<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
|
||||||
|
>Update mode</SwitchLabel
|
||||||
|
>
|
||||||
|
<SwitchDescription as="span" class="text-sm text-zinc-400"
|
||||||
|
>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.</SwitchDescription
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
<Switch
|
||||||
|
v-model="delta"
|
||||||
|
:class="[
|
||||||
|
delta ? '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="[
|
||||||
|
delta ? '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>
|
||||||
|
<LoadingButton
|
||||||
|
@click="startImport_wrapper"
|
||||||
|
class="w-fit"
|
||||||
|
:loading="importLoading"
|
||||||
|
>
|
||||||
Import
|
Import
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
<div v-if="importError" class="mt-4 w-fit rounded-md bg-red-600/10 p-4">
|
<div v-if="importError" class="mt-4 w-fit rounded-md bg-red-600/10 p-4">
|
||||||
@ -180,6 +214,10 @@ import {
|
|||||||
ListboxLabel,
|
ListboxLabel,
|
||||||
ListboxOption,
|
ListboxOption,
|
||||||
ListboxOptions,
|
ListboxOptions,
|
||||||
|
Switch,
|
||||||
|
SwitchDescription,
|
||||||
|
SwitchGroup,
|
||||||
|
SwitchLabel,
|
||||||
} from "@headlessui/vue";
|
} from "@headlessui/vue";
|
||||||
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";
|
||||||
@ -203,6 +241,7 @@ const currentlySelectedVersion = ref(-1);
|
|||||||
const versionSettings = ref<
|
const versionSettings = ref<
|
||||||
{ platform: string; startup: string; setup: string } | undefined
|
{ platform: string; startup: string; setup: string } | undefined
|
||||||
>();
|
>();
|
||||||
|
const delta = ref(false);
|
||||||
|
|
||||||
const importLoading = ref(false);
|
const importLoading = ref(false);
|
||||||
const importError = ref<string | undefined>();
|
const importError = ref<string | undefined>();
|
||||||
@ -233,6 +272,7 @@ async function startImport() {
|
|||||||
platform: versionSettings.value.platform,
|
platform: versionSettings.value.platform,
|
||||||
startup: versionSettings.value.startup,
|
startup: versionSettings.value.startup,
|
||||||
setup: versionSettings.value.setup,
|
setup: versionSettings.value.setup,
|
||||||
|
delta: delta.value
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
router.push(`/admin/task/${taskId.taskId}`);
|
router.push(`/admin/task/${taskId.taskId}`);
|
||||||
|
|||||||
@ -11,20 +11,18 @@
|
|||||||
class="mt-5 pt-5 border-t border-zinc-700 prose prose-invert prose-blue"
|
class="mt-5 pt-5 border-t border-zinc-700 prose prose-invert prose-blue"
|
||||||
></div>
|
></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div class="space-y-8">
|
||||||
<div class="px-4 py-3 bg-gray-950 rounded">
|
<div class="px-4 py-3 bg-gray-950 rounded">
|
||||||
<div class="border-b border-zinc-800 pb-3">
|
<div class="border-b border-zinc-800 pb-3">
|
||||||
<div
|
<div
|
||||||
class="-ml-4 -mt-2 flex flex-wrap items-center justify-between sm:flex-nowrap"
|
class="flex flex-wrap items-center justify-between sm:flex-nowrap"
|
||||||
>
|
>
|
||||||
<div class="ml-4 mt-2">
|
|
||||||
<h3
|
<h3
|
||||||
class="text-base font-semibold font-display leading-6 text-zinc-100"
|
class="text-base font-semibold font-display leading-6 text-zinc-100"
|
||||||
>
|
>
|
||||||
Images
|
Images
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
<div class="flex-shrink-0">
|
||||||
<div class="ml-4 mt-2 flex-shrink-0">
|
|
||||||
<button
|
<button
|
||||||
@click="() => (showUploadModal = true)"
|
@click="() => (showUploadModal = true)"
|
||||||
type="button"
|
type="button"
|
||||||
@ -90,6 +88,39 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="py-5 px-6 bg-gray-950 rounded">
|
||||||
|
<h1 class="text-2xl font-semibold font-display text-zinc-100">
|
||||||
|
Manage version order
|
||||||
|
</h1>
|
||||||
|
<div class="text-center w-full text-sm text-zinc-600">lowest</div>
|
||||||
|
<draggable
|
||||||
|
@update="() => updateVersionOrder()"
|
||||||
|
:list="game.versions"
|
||||||
|
handle=".handle"
|
||||||
|
class="mt-2 space-y-4"
|
||||||
|
>
|
||||||
|
<template #item="{ element: item }: { element: GameVersion }">
|
||||||
|
<div
|
||||||
|
class="w-full inline-flex items-center px-4 py-2 bg-zinc-900 rounded justify-between"
|
||||||
|
>
|
||||||
|
<div class="text-zinc-100 font-semibold">
|
||||||
|
{{ item.versionName }}
|
||||||
|
</div>
|
||||||
|
<div class="text-zinc-400">
|
||||||
|
{{ item.delta ? "Upgrade mode" : "" }}
|
||||||
|
</div>
|
||||||
|
<div class="inline-flex gap-x-2">
|
||||||
|
<Bars3Icon class="cursor-move w-6 h-6 text-zinc-400 handle" />
|
||||||
|
<button @click="() => deleteVersion(item.versionName)">
|
||||||
|
<TrashIcon class="w-5 h-5 text-red-600" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</draggable>
|
||||||
|
<div class="mt-2 text-center w-full text-sm text-zinc-600">highest</div>
|
||||||
|
<span class="text-zinc-100">{{ game.versions }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<UploadFileDialog
|
<UploadFileDialog
|
||||||
@ -102,7 +133,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Game } from "@prisma/client";
|
import { Bars3Icon, TrashIcon } from "@heroicons/vue/16/solid";
|
||||||
|
import type { Game, GameVersion } from "@prisma/client";
|
||||||
import markdownit from "markdown-it";
|
import markdownit from "markdown-it";
|
||||||
import UploadFileDialog from "~/components/UploadFileDialog.vue";
|
import UploadFileDialog from "~/components/UploadFileDialog.vue";
|
||||||
|
|
||||||
@ -116,9 +148,12 @@ const route = useRoute();
|
|||||||
const gameId = route.params.id.toString();
|
const gameId = route.params.id.toString();
|
||||||
const headers = useRequestHeaders(["cookie"]);
|
const headers = useRequestHeaders(["cookie"]);
|
||||||
const game = ref(
|
const game = ref(
|
||||||
await $fetch<Game>(`/api/v1/admin/game?id=${encodeURIComponent(gameId)}`, {
|
await $fetch(
|
||||||
|
`/api/v1/admin/game?id=${encodeURIComponent(gameId)}`,
|
||||||
|
{
|
||||||
headers,
|
headers,
|
||||||
})
|
}
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const md = markdownit();
|
const md = markdownit();
|
||||||
@ -167,4 +202,29 @@ async function uploadAfterImageUpload(result: Game) {
|
|||||||
if (!game.value) return;
|
if (!game.value) return;
|
||||||
game.value.mImageLibrary = result.mImageLibrary;
|
game.value.mImageLibrary = result.mImageLibrary;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function deleteVersion(versionName: string) {
|
||||||
|
await $fetch("/api/v1/admin/game/version", {
|
||||||
|
method: "DELETE",
|
||||||
|
body: {
|
||||||
|
id: gameId,
|
||||||
|
versionName: versionName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
game.value.versions.splice(
|
||||||
|
game.value.versions.findIndex((e) => e.versionName === versionName),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateVersionOrder() {
|
||||||
|
const newVersions = await $fetch("/api/v1/admin/game/version", {
|
||||||
|
method: "POST",
|
||||||
|
body: {
|
||||||
|
id: gameId,
|
||||||
|
versions: game.value.versions.map((e) => e.versionName),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
game.value.versions = newVersions;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
5
plugins/vuedraggable.ts
Normal file
5
plugins/vuedraggable.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import draggable from "vuedraggable";
|
||||||
|
|
||||||
|
export default defineNuxtPlugin((nuxtApp) => {
|
||||||
|
nuxtApp.vueApp.component("draggable", draggable);
|
||||||
|
});
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- Added the required column `versionIndex` to the `GameVersion` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "GameVersion" ADD COLUMN "delta" BOOLEAN NOT NULL DEFAULT false,
|
||||||
|
ADD COLUMN "versionIndex" INTEGER NOT NULL;
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `versionOrder` on the `Game` table. All the data in the column will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Game" DROP COLUMN "versionOrder";
|
||||||
@ -91,7 +91,6 @@ model Game {
|
|||||||
mCoverId String
|
mCoverId String
|
||||||
mImageLibrary String[] // linked to objects in s3
|
mImageLibrary String[] // linked to objects in s3
|
||||||
|
|
||||||
versionOrder String[]
|
|
||||||
versions GameVersion[]
|
versions GameVersion[]
|
||||||
libraryBasePath String @unique // Base dir for all the game versions
|
libraryBasePath String @unique // Base dir for all the game versions
|
||||||
|
|
||||||
@ -109,6 +108,9 @@ model GameVersion {
|
|||||||
setupCommand String // Command to setup game (dependencies and such)
|
setupCommand String // Command to setup game (dependencies and such)
|
||||||
dropletManifest Json // Results from droplet
|
dropletManifest Json // Results from droplet
|
||||||
|
|
||||||
|
versionIndex Int
|
||||||
|
delta Boolean @default(false)
|
||||||
|
|
||||||
@@id([gameId, versionName])
|
@@id([gameId, versionName])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,19 @@ export default defineEventHandler(async (h3) => {
|
|||||||
where: {
|
where: {
|
||||||
id: gameId,
|
id: gameId,
|
||||||
},
|
},
|
||||||
|
include: {
|
||||||
|
versions: {
|
||||||
|
orderBy: {
|
||||||
|
versionIndex: "asc",
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
versionIndex: true,
|
||||||
|
versionName: true,
|
||||||
|
platform: true,
|
||||||
|
delta: true,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!game)
|
if (!game)
|
||||||
|
|||||||
26
server/api/v1/admin/game/version.delete.ts
Normal file
26
server/api/v1/admin/game/version.delete.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import prisma from "~/server/internal/db/database";
|
||||||
|
|
||||||
|
export default defineEventHandler(async (h3) => {
|
||||||
|
const user = await h3.context.session.getAdminUser(h3);
|
||||||
|
if (!user) throw createError({ statusCode: 403 });
|
||||||
|
|
||||||
|
const body = await readBody(h3);
|
||||||
|
const gameId = body.id.toString();
|
||||||
|
const version = body.versionName.toString();
|
||||||
|
if (!gameId || !version)
|
||||||
|
throw createError({
|
||||||
|
statusCode: 400,
|
||||||
|
statusMessage: "Missing ID or versionName in body",
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.gameVersion.delete({
|
||||||
|
where: {
|
||||||
|
gameId_versionName: {
|
||||||
|
gameId: gameId,
|
||||||
|
versionName: version,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {};
|
||||||
|
});
|
||||||
40
server/api/v1/admin/game/version.post.ts
Normal file
40
server/api/v1/admin/game/version.post.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import prisma from "~/server/internal/db/database";
|
||||||
|
|
||||||
|
export default defineEventHandler(async (h3) => {
|
||||||
|
const user = await h3.context.session.getAdminUser(h3);
|
||||||
|
if (!user) throw createError({ statusCode: 403 });
|
||||||
|
|
||||||
|
const body = await readBody(h3);
|
||||||
|
const gameId = body.id?.toString();
|
||||||
|
// We expect an array of the version names for this game
|
||||||
|
const versions: string[] | undefined = body.versions;
|
||||||
|
if (!gameId || !versions || !Array.isArray(versions))
|
||||||
|
throw createError({
|
||||||
|
statusCode: 400,
|
||||||
|
statusMessage: "Missing id, versions or versions is not an array",
|
||||||
|
});
|
||||||
|
|
||||||
|
const newVersions = await prisma.$transaction(
|
||||||
|
versions.map((versionName, versionIndex) =>
|
||||||
|
prisma.gameVersion.update({
|
||||||
|
where: {
|
||||||
|
gameId_versionName: {
|
||||||
|
gameId: gameId,
|
||||||
|
versionName: versionName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
versionIndex: versionIndex,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
versionIndex: true,
|
||||||
|
versionName: true,
|
||||||
|
platform: true,
|
||||||
|
delta: true,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return newVersions;
|
||||||
|
});
|
||||||
@ -10,23 +10,24 @@ export default defineEventHandler(async (h3) => {
|
|||||||
const platform = body.platform;
|
const platform = body.platform;
|
||||||
const startup = body.startup;
|
const startup = body.startup;
|
||||||
const setup = body.setup ?? "";
|
const setup = body.setup ?? "";
|
||||||
if (
|
const delta = body.delta ?? false;
|
||||||
!gameId ||
|
if (!gameId || !versionName || !platform || (!delta && !startup))
|
||||||
!versionName ||
|
|
||||||
!platform ||
|
|
||||||
!startup
|
|
||||||
)
|
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
statusMessage:
|
statusMessage:
|
||||||
"Missing id, version, platform, setup or startup from body",
|
"Missing id, version, platform, setup or startup from body",
|
||||||
});
|
});
|
||||||
|
|
||||||
const taskId = await libraryManager.importVersion(gameId, versionName, {
|
const taskId = await libraryManager.importVersion(
|
||||||
|
gameId,
|
||||||
|
versionName,
|
||||||
|
{
|
||||||
platform,
|
platform,
|
||||||
startup,
|
startup,
|
||||||
setup,
|
setup,
|
||||||
});
|
},
|
||||||
|
delta
|
||||||
|
);
|
||||||
if (!taskId)
|
if (!taskId)
|
||||||
throw createError({
|
throw createError({
|
||||||
statusCode: 400,
|
statusCode: 400,
|
||||||
|
|||||||
12
server/api/v1/client/metadata/manifest.get.ts
Normal file
12
server/api/v1/client/metadata/manifest.get.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||||
|
|
||||||
|
export default defineClientEventHandler(async (h3) => {
|
||||||
|
const query = getQuery(h3);
|
||||||
|
const id = query.id?.toString();
|
||||||
|
const version = query.version?.toString();
|
||||||
|
if (!id || !version)
|
||||||
|
throw createError({
|
||||||
|
statusCode: 400,
|
||||||
|
statusMessage: "Missing id or version in query",
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -14,6 +14,14 @@ export default defineClientEventHandler(async (h3, {}) => {
|
|||||||
where: {
|
where: {
|
||||||
gameId: id,
|
gameId: id,
|
||||||
},
|
},
|
||||||
|
select: {
|
||||||
|
versionIndex: true,
|
||||||
|
versionName: true,
|
||||||
|
platform: true,
|
||||||
|
setupCommand: true,
|
||||||
|
launchCommand: true,
|
||||||
|
delta: true,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return versions;
|
return versions;
|
||||||
|
|||||||
@ -1,8 +1,16 @@
|
|||||||
# Drop Download System
|
# Drop P2P System
|
||||||
Drop downloads come in two types:
|
|
||||||
|
Drop clients have a variety of P2P or P2P-like methods of data transfer available
|
||||||
|
|
||||||
## Public (not quite) HTTPS downloads endpoints
|
## Public (not quite) HTTPS downloads endpoints
|
||||||
|
|
||||||
These use public HTTPS certificate, and while are authenticated, are 'public' in the sense that they aren't P2P; anyone can connect to them
|
These use public HTTPS certificate, and while are authenticated, are 'public' in the sense that they aren't P2P; anyone can connect to them
|
||||||
|
|
||||||
## Private mTLS P2P endpoints
|
## Private mTLS P2P endpoints
|
||||||
Drop clients use P2P mTLS aided by the P2P co-ordinator to transfer chunks between themselves.
|
|
||||||
|
Drop clients use P2P mTLS aided by the P2P co-ordinator to transfer chunks between themselves. This happens over HTTP.
|
||||||
|
|
||||||
|
|
||||||
|
## Private mTLS Wireguard tunnels
|
||||||
|
|
||||||
|
Drop clients can establish P2P Wireguard
|
||||||
52
server/internal/downloads/manifest.ts
Normal file
52
server/internal/downloads/manifest.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
export type DropChunk = {
|
||||||
|
permissions: number;
|
||||||
|
ids: string[];
|
||||||
|
checksums: string[];
|
||||||
|
lengths: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DropManifest = {
|
||||||
|
[key: string]: DropChunk;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DropManifestMetadata = {
|
||||||
|
manifest: DropManifest;
|
||||||
|
versionName: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type DropGeneratedManifest = DropManifest & {
|
||||||
|
[key: string]: { versionName: string };
|
||||||
|
};
|
||||||
|
|
||||||
|
class ManifestGenerator {
|
||||||
|
static generateManifest(
|
||||||
|
rootManifest: DropManifestMetadata,
|
||||||
|
...overlays: DropManifestMetadata[]
|
||||||
|
): DropGeneratedManifest {
|
||||||
|
if (overlays.length == 0) {
|
||||||
|
return Object.fromEntries(
|
||||||
|
Object.entries(rootManifest.manifest).map(([key, value]) => [
|
||||||
|
key,
|
||||||
|
Object.assign({}, value, { versionName: rootManifest.versionName }),
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recurse in verse order through versions, skipping files that already exist.
|
||||||
|
const versions = [...overlays.reverse(), rootManifest];
|
||||||
|
const manifest: DropGeneratedManifest = {};
|
||||||
|
for (const version of versions) {
|
||||||
|
for (const [filename, chunk] of Object.entries(version.manifest)) {
|
||||||
|
if (manifest[filename]) continue;
|
||||||
|
manifest[filename] = Object.assign({}, chunk, {
|
||||||
|
versionName: version.versionName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return manifest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const manifestGenerator = new ManifestGenerator();
|
||||||
|
export default manifestGenerator;
|
||||||
@ -195,7 +195,8 @@ class LibraryManager {
|
|||||||
async importVersion(
|
async importVersion(
|
||||||
gameId: string,
|
gameId: string,
|
||||||
versionName: string,
|
versionName: string,
|
||||||
metadata: { platform: string; setup: string; startup: string }
|
metadata: { platform: string; setup: string; startup: string },
|
||||||
|
delta = false
|
||||||
) {
|
) {
|
||||||
const taskId = `import:${gameId}:${versionName}`;
|
const taskId = `import:${gameId}:${versionName}`;
|
||||||
|
|
||||||
@ -238,6 +239,10 @@ class LibraryManager {
|
|||||||
|
|
||||||
log("Created manifest successfully!");
|
log("Created manifest successfully!");
|
||||||
|
|
||||||
|
const currentIndex = await prisma.gameVersion.count({
|
||||||
|
where: { gameId: gameId },
|
||||||
|
});
|
||||||
|
|
||||||
// Then, create the database object
|
// Then, create the database object
|
||||||
const version = await prisma.gameVersion.create({
|
const version = await prisma.gameVersion.create({
|
||||||
data: {
|
data: {
|
||||||
@ -247,6 +252,8 @@ class LibraryManager {
|
|||||||
setupCommand: metadata.setup,
|
setupCommand: metadata.setup,
|
||||||
launchCommand: metadata.startup,
|
launchCommand: metadata.startup,
|
||||||
dropletManifest: manifest,
|
dropletManifest: manifest,
|
||||||
|
versionIndex: currentIndex,
|
||||||
|
delta: delta,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
40
yarn.lock
40
yarn.lock
@ -296,23 +296,23 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
mime "^3.0.0"
|
mime "^3.0.0"
|
||||||
|
|
||||||
"@drop/droplet-linux-x64-gnu@0.4.4", "@drop/droplet-linux-x64-gnu@^0.4.4":
|
"@drop/droplet-linux-x64-gnu@0.5.0", "@drop/droplet-linux-x64-gnu@^0.5.0":
|
||||||
version "0.4.4"
|
version "0.5.0"
|
||||||
resolved "https://lab.deepcore.dev/api/v4/projects/57/packages/npm/@drop/droplet-linux-x64-gnu/-/@drop/droplet-linux-x64-gnu-0.4.4.tgz#6678a0923bb13d37e20cae467f45c72bc5d9fe6e"
|
resolved "https://lab.deepcore.dev/api/v4/projects/57/packages/npm/@drop/droplet-linux-x64-gnu/-/@drop/droplet-linux-x64-gnu-0.5.0.tgz#06643f7bc79de4b35a395295ef1e29fad46f32b5"
|
||||||
integrity sha1-ZnigkjuxPTfiDK5Gf0XHK8XZ/m4=
|
integrity sha1-BmQ/e8ed5LNaOVKV7x4p+tRvMrU=
|
||||||
|
|
||||||
"@drop/droplet-win32-x64-msvc@0.4.4", "@drop/droplet-win32-x64-msvc@^0.4.4":
|
"@drop/droplet-win32-x64-msvc@0.5.0", "@drop/droplet-win32-x64-msvc@^0.5.0":
|
||||||
version "0.4.4"
|
version "0.5.0"
|
||||||
resolved "https://lab.deepcore.dev/api/v4/projects/57/packages/npm/@drop/droplet-win32-x64-msvc/-/@drop/droplet-win32-x64-msvc-0.4.4.tgz#10802bb36c6ec7d69aa17ea22081e5d5f0dac3c3"
|
resolved "https://lab.deepcore.dev/api/v4/projects/57/packages/npm/@drop/droplet-win32-x64-msvc/-/@drop/droplet-win32-x64-msvc-0.5.0.tgz#abc02af2102f0faaf4561473b7a18395a0ba2b10"
|
||||||
integrity sha1-EIArs2xux9aaoX6iIIHl1fDaw8M=
|
integrity sha1-q8Aq8hAvD6r0VhRzt6GDlaC6KxA=
|
||||||
|
|
||||||
"@drop/droplet@^0.4.4":
|
"@drop/droplet@^0.5.0":
|
||||||
version "0.4.4"
|
version "0.5.0"
|
||||||
resolved "https://lab.deepcore.dev/api/v4/projects/57/packages/npm/@drop/droplet/-/@drop/droplet-0.4.4.tgz#a9b6e3a341e85703b25c7fee597261e1b239a280"
|
resolved "https://lab.deepcore.dev/api/v4/projects/57/packages/npm/@drop/droplet/-/@drop/droplet-0.5.0.tgz#27da4f7292c9b860d38bb785c0fb2bb3b9cd50a3"
|
||||||
integrity sha1-qbbjo0HoVwOyXH/uWXJh4bI5ooA=
|
integrity sha1-J9pPcpLJuGDTi7eFwPsrs7nNUKM=
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
"@drop/droplet-linux-x64-gnu" "0.4.4"
|
"@drop/droplet-linux-x64-gnu" "0.5.0"
|
||||||
"@drop/droplet-win32-x64-msvc" "0.4.4"
|
"@drop/droplet-win32-x64-msvc" "0.5.0"
|
||||||
|
|
||||||
"@esbuild/aix-ppc64@0.20.2":
|
"@esbuild/aix-ppc64@0.20.2":
|
||||||
version "0.20.2"
|
version "0.20.2"
|
||||||
@ -4887,6 +4887,11 @@ smob@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/smob/-/smob-1.5.0.tgz#85d79a1403abf128d24d3ebc1cdc5e1a9548d3ab"
|
resolved "https://registry.yarnpkg.com/smob/-/smob-1.5.0.tgz#85d79a1403abf128d24d3ebc1cdc5e1a9548d3ab"
|
||||||
integrity sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==
|
integrity sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==
|
||||||
|
|
||||||
|
sortablejs@1.14.0:
|
||||||
|
version "1.14.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/sortablejs/-/sortablejs-1.14.0.tgz#6d2e17ccbdb25f464734df621d4f35d4ab35b3d8"
|
||||||
|
integrity sha512-pBXvQCs5/33fdN1/39pPL0NZF20LeRbLQ5jtnheIPN9JQAaufGjKdWduZn4U7wCtVuzKhmRkI0DFYHYRbB2H1w==
|
||||||
|
|
||||||
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.2.0, source-map-js@^1.2.1:
|
"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.0.1, source-map-js@^1.2.0, source-map-js@^1.2.1:
|
||||||
version "1.2.1"
|
version "1.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46"
|
||||||
@ -5656,6 +5661,13 @@ vue@^3.5.5, vue@latest:
|
|||||||
"@vue/server-renderer" "3.5.10"
|
"@vue/server-renderer" "3.5.10"
|
||||||
"@vue/shared" "3.5.10"
|
"@vue/shared" "3.5.10"
|
||||||
|
|
||||||
|
vuedraggable@^4.1.0:
|
||||||
|
version "4.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/vuedraggable/-/vuedraggable-4.1.0.tgz#edece68adb8a4d9e06accff9dfc9040e66852270"
|
||||||
|
integrity sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==
|
||||||
|
dependencies:
|
||||||
|
sortablejs "1.14.0"
|
||||||
|
|
||||||
webidl-conversions@^3.0.0:
|
webidl-conversions@^3.0.0:
|
||||||
version "3.0.1"
|
version "3.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871"
|
||||||
|
|||||||
Reference in New Issue
Block a user