mirror of
https://github.com/Drop-OSS/drop.git
synced 2025-11-09 20:12:10 +10:00
Compare commits
10 Commits
0b9a715bf2
...
develop
| Author | SHA1 | Date | |
|---|---|---|---|
| dfa30c8a65 | |||
| 289034d0c8 | |||
| 2a23f4d14c | |||
| b20d355527 | |||
| fa9620eac1 | |||
| a201b62c04 | |||
| 9bf164ab77 | |||
| 97c6f3490c | |||
| f5cb856d3d | |||
| 67de1f6c02 |
@ -4,9 +4,10 @@
|
||||
v-for="(_, i) in amount"
|
||||
:key="i"
|
||||
:class="[
|
||||
carousel.currentSlide == i ? 'bg-blue-600 w-6' : 'bg-zinc-700 w-3',
|
||||
carousel.currentSlide === i ? 'bg-blue-600 w-6' : 'bg-zinc-700 w-3',
|
||||
'transition-all cursor-pointer h-2 rounded-full',
|
||||
]"
|
||||
@click="slideTo(i)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
@ -18,8 +19,8 @@ const carousel = inject(injectCarousel)!;
|
||||
|
||||
const amount = carousel.maxSlide - carousel.minSlide + 1;
|
||||
|
||||
// function slideTo(index: number) {
|
||||
// const offsetIndex = index + carousel.minSlide;
|
||||
// carousel.nav.slideTo(offsetIndex);
|
||||
// }
|
||||
function slideTo(index: number) {
|
||||
const offsetIndex = index + carousel.minSlide;
|
||||
carousel.nav.slideTo(offsetIndex);
|
||||
}
|
||||
</script>
|
||||
|
||||
@ -29,6 +29,23 @@
|
||||
|
||||
<div class="grid grid-cols-1 lg:grid-cols-3 gap-4 pt-8">
|
||||
<MultiItemSelector v-model="currentTags" :items="tags" />
|
||||
<div class="flex flex-col">
|
||||
<label
|
||||
for="releaseDate"
|
||||
class="text-sm/6 font-medium text-zinc-100"
|
||||
>
|
||||
{{ $t("library.admin.game.editReleaseDate") }}
|
||||
</label>
|
||||
<div class="mt-2">
|
||||
<input
|
||||
id="releaseDate"
|
||||
v-model="releaseDate"
|
||||
type="date"
|
||||
name="releaseDate"
|
||||
class="block w-full rounded-md bg-zinc-800 px-3 py-1.5 text-base text-zinc-100 outline outline-1 -outline-offset-1 outline-zinc-700 placeholder:text-zinc-400 focus:outline focus:outline-2 focus:-outline-offset-2 focus:outline-blue-600 sm:text-sm/6"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- image carousel pick -->
|
||||
@ -491,11 +508,38 @@ watch(
|
||||
{ deep: true },
|
||||
);
|
||||
|
||||
const releaseDate = ref(
|
||||
game.value.mReleased
|
||||
? new Date(game.value.mReleased).toISOString().substring(0, 10)
|
||||
: "",
|
||||
);
|
||||
|
||||
watch(releaseDate, async (newDate) => {
|
||||
const body: PatchGameBody = {};
|
||||
|
||||
if (newDate) {
|
||||
const parsed = new Date(newDate);
|
||||
if (!isNaN(parsed.getTime())) {
|
||||
body.mReleased = parsed;
|
||||
}
|
||||
}
|
||||
|
||||
await $dropFetch(`/api/v1/admin/game/:id`, {
|
||||
method: "PATCH",
|
||||
params: {
|
||||
id: game.value.id,
|
||||
},
|
||||
body,
|
||||
failTitle: "Failed to update release date",
|
||||
});
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
// I don't know why I split these fields off.
|
||||
const coreMetadataName = ref(game.value.mName);
|
||||
const coreMetadataDescription = ref(game.value.mShortDescription);
|
||||
|
||||
const coreMetadataIconUrl = ref(useObject(game.value.mIconObjectId));
|
||||
const coreMetadataIconFileUpload = ref<FileList | undefined>();
|
||||
const coreMetadataLoading = ref(false);
|
||||
@ -561,7 +605,6 @@ function coreMetadataUpdate_wrapper() {
|
||||
);
|
||||
})
|
||||
.then((newGame) => {
|
||||
console.log(newGame);
|
||||
if (!newGame) return;
|
||||
Object.assign(game.value, newGame);
|
||||
coreMetadataIconUrl.value = useObject(newGame.mIconObjectId);
|
||||
|
||||
@ -51,14 +51,19 @@
|
||||
@update="() => updateVersionOrder()"
|
||||
>
|
||||
<template
|
||||
#item="{ element: item }: { element: GameVersionModel }"
|
||||
#item="{ element: item }: { element: GameVersionModelWithSize }"
|
||||
>
|
||||
<div
|
||||
class="w-full inline-flex items-center px-4 py-2 bg-zinc-800 rounded justify-between"
|
||||
class="w-full inline-flex items-center px-4 py-2 bg-zinc-800 rounded justify-between w-full flex"
|
||||
>
|
||||
<div class="text-zinc-100 font-semibold">
|
||||
<div class="text-zinc-100 font-semibold flex-none">
|
||||
{{ item.versionName }}
|
||||
</div>
|
||||
<div
|
||||
class="text-right text-zinc-400 text-xs font-normal flex-auto pr-4"
|
||||
>
|
||||
{{ item.size && formatBytes(item.size) }}
|
||||
</div>
|
||||
<div class="text-zinc-400">
|
||||
{{ item.delta ? $t("library.admin.version.delta") : "" }}
|
||||
</div>
|
||||
@ -117,6 +122,7 @@ import { Bars3Icon, TrashIcon } from "@heroicons/vue/24/solid";
|
||||
import type { SerializeObject } from "nitropack";
|
||||
import type { H3Error } from "h3";
|
||||
import { ExclamationCircleIcon } from "@heroicons/vue/24/outline";
|
||||
import { formatBytes } from "~/server/internal/utils/files";
|
||||
|
||||
// TODO implement UI for this page
|
||||
|
||||
@ -130,7 +136,11 @@ const canImport = computed(
|
||||
() => hasDeleted.value || props.unimportedVersions.length > 0,
|
||||
);
|
||||
|
||||
type GameAndVersions = GameModel & { versions: GameVersionModel[] };
|
||||
type GameVersionModelWithSize = GameVersionModel & { size: number };
|
||||
|
||||
type GameAndVersions = GameModel & {
|
||||
versions: GameVersionModelWithSize[];
|
||||
};
|
||||
const game = defineModel<SerializeObject<GameAndVersions>>() as Ref<
|
||||
SerializeObject<GameAndVersions>
|
||||
>;
|
||||
|
||||
19
components/Icons/GamepadIcon.vue
Normal file
19
components/Icons/GamepadIcon.vue
Normal file
@ -0,0 +1,19 @@
|
||||
<template>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
>
|
||||
<line x1="6" y1="11" x2="10" y2="11" />
|
||||
<line x1="8" y1="9" x2="8" y2="13" />
|
||||
<line x1="15" y1="12" x2="15.01" y2="12" />
|
||||
<line x1="18" y1="10" x2="18.01" y2="10" />
|
||||
<path
|
||||
d="M17.32 5H6.68a4 4 0 00-3.978 3.59c-.006.052-.01.101-.017.152C2.604 9.416 2 14.456 2 16a3 3 0 003 3c1 0 1.5-.5 2-1l1.414-1.414A2 2 0 019.828 16h4.344a2 2 0 011.414.586L17 18c.5.5 1 1 2 1a3 3 0 003-3c0-1.545-.604-6.584-.685-7.258-.007-.05-.011-.1-.017-.151A4 4 0 0017.32 5z"
|
||||
/>
|
||||
</svg>
|
||||
</template>
|
||||
45
components/PieChart/PieChart.vue
Normal file
45
components/PieChart/PieChart.vue
Normal file
@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<h2 v-if="title" class="text-lg mb-4 w-full">{{ title }}</h2>
|
||||
<div class="flex flex-col xl:flex-row gap-4">
|
||||
<div class="relative flex grow max-w-[12rem]">
|
||||
<svg class="aspect-square grow relative inline" viewBox="0 0 100 100">
|
||||
<PieChartPieSlice
|
||||
v-for="slice in slices"
|
||||
:key="`${slice.percentage}-${slice.totalPercentage}`"
|
||||
:slice="slice"
|
||||
/>
|
||||
</svg>
|
||||
<div class="absolute inset-0 bg-zinc-900 rounded-full m-12" />
|
||||
</div>
|
||||
<ul class="flex flex-col gap-y-1 justify-center text-left">
|
||||
<li
|
||||
v-for="slice in slices"
|
||||
:key="slice.value"
|
||||
class="text-sm inline-flex items-center gap-x-1"
|
||||
>
|
||||
<span
|
||||
class="size-3 inline-block rounded-sm"
|
||||
:class="CHART_COLOURS[slice.color].bg"
|
||||
/>
|
||||
{{
|
||||
$t("common.labelValueColon", {
|
||||
label: slice.label,
|
||||
value: slice.value,
|
||||
})
|
||||
}}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { generateSlices } from "~/components/PieChart/utils";
|
||||
import type { SliceData } from "~/components/PieChart/types";
|
||||
|
||||
const { data, title = undefined } = defineProps<{
|
||||
data: SliceData[];
|
||||
title?: string | undefined;
|
||||
}>();
|
||||
|
||||
const slices = generateSlices(data);
|
||||
</script>
|
||||
35
components/PieChart/PieSlice.vue
Normal file
35
components/PieChart/PieSlice.vue
Normal file
@ -0,0 +1,35 @@
|
||||
<template>
|
||||
<path
|
||||
v-if="slice.percentage !== 0 && slice.percentage !== 100"
|
||||
:class="[CHART_COLOURS[slice.color].fill]"
|
||||
:d="`
|
||||
M ${slice.start}
|
||||
A ${slice.radius},${slice.radius} 0 ${getFlags(slice.percentage)} ${polarToCartesian(slice.center, slice.radius, percent2Degrees(slice.totalPercentage))}
|
||||
L ${slice.center}
|
||||
z
|
||||
`"
|
||||
stroke-width="2"
|
||||
/>
|
||||
<circle
|
||||
v-if="slice.percentage === 100"
|
||||
:r="slice.radius"
|
||||
:cx="slice.center.x"
|
||||
:cy="slice.center.y"
|
||||
:class="[CHART_COLOURS[slice.color].fill]"
|
||||
stroke-width="2"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Slice } from "~/components/PieChart/types";
|
||||
import {
|
||||
getFlags,
|
||||
percent2Degrees,
|
||||
polarToCartesian,
|
||||
} from "~/components/PieChart/utils";
|
||||
import { CHART_COLOURS } from "~/utils/colors";
|
||||
|
||||
const { slice } = defineProps<{
|
||||
slice: Slice;
|
||||
}>();
|
||||
</script>
|
||||
19
components/PieChart/types.d.ts
vendored
Normal file
19
components/PieChart/types.d.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
import type Tuple from "~/utils/tuple";
|
||||
import type { ChartColour } from "~/utils/colors";
|
||||
|
||||
export type Slice = {
|
||||
start: Tuple;
|
||||
center: Tuple;
|
||||
percentage: number;
|
||||
totalPercentage: number;
|
||||
radius: number;
|
||||
color: ChartColour;
|
||||
label: string;
|
||||
value: number;
|
||||
};
|
||||
|
||||
export type SliceData = {
|
||||
value: number;
|
||||
color?: ChartColour;
|
||||
label: string;
|
||||
};
|
||||
50
components/PieChart/utils.ts
Normal file
50
components/PieChart/utils.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import Tuple from "~/utils/tuple";
|
||||
import type { Slice, SliceData } from "~/components/PieChart/types";
|
||||
import { sum, lastItem } from "~/utils/array";
|
||||
|
||||
export const START = new Tuple(50, 10);
|
||||
export const CENTER = new Tuple(50, 50);
|
||||
export const RADIUS = 40;
|
||||
|
||||
export const polarToCartesian = (
|
||||
center: Tuple,
|
||||
radius: number,
|
||||
angleInDegrees: number,
|
||||
) => {
|
||||
const angleInRadians = ((angleInDegrees - 90) * Math.PI) / 180;
|
||||
const x = center.x + radius * Math.cos(angleInRadians);
|
||||
const y = center.y + radius * Math.sin(angleInRadians);
|
||||
return new Tuple(x, y);
|
||||
};
|
||||
|
||||
export const percent2Degrees = (percentage: number) => (360 * percentage) / 100;
|
||||
|
||||
export function generateSlices(data: SliceData[]): Slice[] {
|
||||
return data.reduce((accumulator, currentValue, index, array) => {
|
||||
const percentage =
|
||||
(currentValue.value * 100) / sum(array.map((slice) => slice.value));
|
||||
return [
|
||||
...accumulator,
|
||||
{
|
||||
start: accumulator.length
|
||||
? polarToCartesian(
|
||||
CENTER,
|
||||
RADIUS,
|
||||
percent2Degrees(lastItem(accumulator).totalPercentage),
|
||||
)
|
||||
: START,
|
||||
radius: RADIUS,
|
||||
percentage: percentage,
|
||||
totalPercentage:
|
||||
sum(accumulator.map((element) => element.percentage)) + percentage,
|
||||
center: CENTER,
|
||||
color: PIE_COLOURS[index % PIE_COLOURS.length],
|
||||
label: currentValue.label,
|
||||
value: currentValue.value,
|
||||
},
|
||||
];
|
||||
}, [] as Slice[]);
|
||||
}
|
||||
|
||||
export const getFlags = (percentage: number) =>
|
||||
percentage > 50 ? new Tuple(1, 1) : new Tuple(0, 1);
|
||||
31
components/ProgressBar.vue
Normal file
31
components/ProgressBar.vue
Normal file
@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'relative h-5 rounded-xl overflow-hidden',
|
||||
CHART_COLOURS[backgroundColor].bg,
|
||||
]"
|
||||
>
|
||||
<div
|
||||
:style="{ width: `${percentage}%` }"
|
||||
:class="['transition-all h-full', CHART_COLOURS[color].bg]"
|
||||
/>
|
||||
<span
|
||||
class="absolute inset-0 flex items-center justify-center text-blue-200 text-sm font-bold font-display"
|
||||
>
|
||||
{{ $t("tasks.admin.progress", [Math.round(percentage * 10) / 10]) }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type ChartColour, CHART_COLOURS } from "~/utils/colors";
|
||||
const {
|
||||
percentage,
|
||||
color = "blue",
|
||||
backgroundColor = "zinc",
|
||||
} = defineProps<{
|
||||
percentage: number;
|
||||
color?: ChartColour;
|
||||
backgroundColor?: ChartColour;
|
||||
}>();
|
||||
</script>
|
||||
43
components/RankingList.vue
Normal file
43
components/RankingList.vue
Normal file
@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<table v-if="items.length > 0" class="w-full mt-4 space-y-6">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-white/10">
|
||||
<tr v-for="item in items" :key="`${item.rank}-${item.name}`">
|
||||
<td
|
||||
class="my-2 size-7 rounded-sm bg-zinc-950 ring ring-zinc-800 inline-flex items-center justify-center font-bold font-display text-blue-500"
|
||||
>
|
||||
{{ item.rank }}
|
||||
</td>
|
||||
<td class="w-full font-bold px-2">{{ item.name }}</td>
|
||||
<td
|
||||
class="text-right text-sm font-semibold text-zinc-500 whitespace-nowrap"
|
||||
>
|
||||
{{ item.value }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<p
|
||||
v-else
|
||||
class="w-full p-2 text-center uppercase text-sm font-display font-bold text-zinc-700"
|
||||
>
|
||||
{{ $t("common.noData") }}
|
||||
</p>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
export type RankItem = {
|
||||
rank: number;
|
||||
name: string;
|
||||
value: string;
|
||||
};
|
||||
const { items } = defineProps<{
|
||||
items: RankItem[];
|
||||
}>();
|
||||
</script>
|
||||
193
components/SourceTable.vue
Normal file
193
components/SourceTable.vue
Normal file
@ -0,0 +1,193 @@
|
||||
<template>
|
||||
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
||||
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
||||
<table class="min-w-full divide-y divide-zinc-700">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
scope="col"
|
||||
class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-zinc-100 sm:pl-3"
|
||||
>
|
||||
{{ $t("common.name") }}
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-3.5 text-left text-sm font-semibold text-zinc-100"
|
||||
>
|
||||
{{ $t("type") }}
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-3.5 text-left text-sm font-semibold text-zinc-100"
|
||||
>
|
||||
{{ $t("library.admin.sources.working") }}
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-3.5 text-left text-sm font-semibold text-zinc-100"
|
||||
>
|
||||
{{ $t("options") }}
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-3.5 text-left text-sm font-semibold text-zinc-100"
|
||||
>
|
||||
{{ $t("library.admin.sources.totalSpace") }}
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-3.5 text-left text-sm font-semibold text-zinc-100"
|
||||
>
|
||||
{{ $t("library.admin.sources.freeSpace") }}
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-3.5 text-left text-sm font-semibold text-zinc-100"
|
||||
>
|
||||
{{ $t("library.admin.sources.utilizationPercentage") }}
|
||||
</th>
|
||||
<th
|
||||
v-if="editSource || deleteSource"
|
||||
scope="col"
|
||||
class="relative py-3.5 pl-3 pr-4 sm:pr-3"
|
||||
>
|
||||
<span class="sr-only">{{ $t("actions") }}</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(source, sourceIdx) in sources"
|
||||
:key="source.id"
|
||||
class="even:bg-zinc-800"
|
||||
>
|
||||
<td
|
||||
class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-zinc-100 sm:pl-3"
|
||||
>
|
||||
{{ source.name }}
|
||||
</td>
|
||||
<td
|
||||
class="whitespace-nowrap px-3 py-4 text-sm text-zinc-400 inline-flex gap-x-1 items-center"
|
||||
>
|
||||
<component
|
||||
:is="optionsMetadata[source.backend].icon"
|
||||
class="size-5 text-zinc-400"
|
||||
/>
|
||||
{{ optionsMetadata[source.backend].title }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-zinc-400">
|
||||
<CheckIcon v-if="source.working" class="size-5 text-green-500" />
|
||||
<XMarkIcon v-else class="size-5 text-red-500" />
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-zinc-400">
|
||||
{{ source.options }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-zinc-400">
|
||||
{{ source.fsStats && formatBytes(source.fsStats.totalSpace) }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-zinc-400">
|
||||
{{ source.fsStats && formatBytes(source.fsStats.freeSpace) }}
|
||||
</td>
|
||||
<td
|
||||
class="align-middle flex flex-cols-5 whitespace-nowrap px-3 py-4 text-sm text-zinc-400"
|
||||
>
|
||||
<div class="flex-auto content-right">
|
||||
<ProgressBar
|
||||
v-if="source.fsStats"
|
||||
:percentage="
|
||||
getPercentage(
|
||||
source.fsStats.freeSpace,
|
||||
source.fsStats.totalSpace,
|
||||
)
|
||||
"
|
||||
:color="
|
||||
getBarColor(
|
||||
getPercentage(
|
||||
source.fsStats.freeSpace,
|
||||
source.fsStats.totalSpace,
|
||||
),
|
||||
)
|
||||
"
|
||||
background-color="slate"
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
<td
|
||||
v-if="editSource || deleteSource"
|
||||
class="relative whitespace-nowrap py-4 pl-3 pr-3 text-right text-sm font-medium space-x-2"
|
||||
>
|
||||
<button
|
||||
v-if="editSource"
|
||||
class="text-blue-500 hover:text-blue-400"
|
||||
@click="() => editSource(sourceIdx)"
|
||||
>
|
||||
{{ $t("common.edit") }}
|
||||
<span class="sr-only">
|
||||
{{ $t("chars.srComma", [source.name]) }}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-if="deleteSource"
|
||||
class="text-red-500 hover:text-red-400"
|
||||
@click="() => deleteSource(sourceIdx)"
|
||||
>
|
||||
{{ $t("delete") }}
|
||||
<span class="sr-only">
|
||||
{{ $t("chars.srComma", [source.name]) }}
|
||||
</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { WorkingLibrarySource } from "~/server/api/v1/admin/library/sources/index.get";
|
||||
import type { LibraryBackend } from "~/prisma/client/enums";
|
||||
import { BackwardIcon, CheckIcon, XMarkIcon } from "@heroicons/vue/24/outline";
|
||||
import { DropLogo } from "#components";
|
||||
import { formatBytes } from "~/server/internal/utils/files";
|
||||
import { getBarColor } from "~/utils/colors";
|
||||
|
||||
const {
|
||||
sources,
|
||||
deleteSource = undefined,
|
||||
editSource = undefined,
|
||||
} = defineProps<{
|
||||
sources: WorkingLibrarySource[];
|
||||
summaryMode?: boolean;
|
||||
deleteSource?: (id: number) => void;
|
||||
editSource?: (id: number) => void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const optionsMetadata: {
|
||||
[key in LibraryBackend]: {
|
||||
title: string;
|
||||
description: string;
|
||||
docsLink: string;
|
||||
icon: Component;
|
||||
};
|
||||
} = {
|
||||
Filesystem: {
|
||||
title: t("library.admin.sources.fsTitle"),
|
||||
description: t("library.admin.sources.fsDesc"),
|
||||
docsLink: "https://docs.droposs.org/docs/library#drop-style",
|
||||
icon: DropLogo,
|
||||
},
|
||||
FlatFilesystem: {
|
||||
title: t("library.admin.sources.fsFlatTitle"),
|
||||
description: t("library.admin.sources.fsFlatDesc"),
|
||||
docsLink: "https://docs.droposs.org/docs/library#flat-style-or-compat",
|
||||
icon: BackwardIcon,
|
||||
},
|
||||
};
|
||||
|
||||
const getPercentage = (value: number, total: number) =>
|
||||
((total - value) * 100) / total;
|
||||
</script>
|
||||
@ -1,3 +1,12 @@
|
||||
<i18n>
|
||||
{
|
||||
"en": {
|
||||
"↓": "↓",
|
||||
"↑": "↑"
|
||||
}
|
||||
}
|
||||
</i18n>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div>
|
||||
@ -176,9 +185,12 @@
|
||||
active ? 'bg-zinc-900 outline-hidden' : '',
|
||||
'w-full text-left block px-4 py-2 text-sm',
|
||||
]"
|
||||
@click="() => (currentSort = option.param)"
|
||||
@click.prevent="handleSortClick(option, $event)"
|
||||
>
|
||||
{{ option.name }}
|
||||
<span v-if="currentSort === option.param">
|
||||
{{ sortOrder === "asc" ? $t("↑") : $t("↓") }}
|
||||
</span>
|
||||
</button>
|
||||
</MenuItem>
|
||||
</div>
|
||||
@ -292,7 +304,7 @@
|
||||
<div
|
||||
v-if="games?.length ?? 0 > 0"
|
||||
ref="product-grid"
|
||||
class="relative lg:col-span-4 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-4 xl:grid-cols-6 2xl:grid-cols-7 gap-4"
|
||||
class="relative lg:col-span-4 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-4"
|
||||
>
|
||||
<!-- Your content -->
|
||||
<GamePanel
|
||||
@ -389,8 +401,13 @@ const sorts: Array<StoreSortOption> = [
|
||||
name: "Recently Added",
|
||||
param: "recent",
|
||||
},
|
||||
{
|
||||
name: "Name",
|
||||
param: "name",
|
||||
},
|
||||
];
|
||||
const currentSort = ref(sorts[0].param);
|
||||
const sortOrder = ref<"asc" | "desc">("desc");
|
||||
|
||||
const options: Array<StoreFilterOption> = [
|
||||
...(tags.length > 0
|
||||
@ -466,7 +483,7 @@ async function updateGames(query: string, resetGames: boolean) {
|
||||
results: Array<SerializeObject<GameModel>>;
|
||||
count: number;
|
||||
}>(
|
||||
`/api/v1/store?take=50&skip=${resetGames ? 0 : games.value?.length || 0}&sort=${currentSort.value}${query ? "&" + query : ""}`,
|
||||
`/api/v1/store?take=50&skip=${resetGames ? 0 : games.value?.length || 0}&sort=${currentSort.value}&order=${sortOrder.value}${query ? "&" + query : ""}`,
|
||||
);
|
||||
if (resetGames) {
|
||||
games.value = newValues.results;
|
||||
@ -483,6 +500,19 @@ watch(filterQuery, (newUrl) => {
|
||||
watch(currentSort, (_) => {
|
||||
updateGames(filterQuery.value, true);
|
||||
});
|
||||
watch(sortOrder, (_) => {
|
||||
updateGames(filterQuery.value, true);
|
||||
});
|
||||
|
||||
await updateGames(filterQuery.value, true);
|
||||
|
||||
function handleSortClick(option: StoreSortOption, event: MouseEvent) {
|
||||
event.stopPropagation();
|
||||
if (currentSort.value === option.param) {
|
||||
sortOrder.value = sortOrder.value === "asc" ? "desc" : "asc";
|
||||
} else {
|
||||
currentSort.value = option.param;
|
||||
sortOrder.value = option.param === "name" ? "asc" : "desc";
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
52
components/TileWithLink.vue
Normal file
52
components/TileWithLink.vue
Normal file
@ -0,0 +1,52 @@
|
||||
<template>
|
||||
<div
|
||||
:class="[
|
||||
'border border-zinc-800 rounded-xl h-full px-6 py-4 relative bg-zinc-950/30',
|
||||
{ 'min-h-50 pb-15': link, 'lg:pb-4': !link },
|
||||
]"
|
||||
>
|
||||
<h1
|
||||
v-if="props.title"
|
||||
:class="[
|
||||
'font-semibold text-lg w-full',
|
||||
{ 'mb-3': !props.subtitle && link },
|
||||
]"
|
||||
>
|
||||
{{ props.title }}
|
||||
<div v-if="rightTitle" class="float-right">{{ props.rightTitle }}</div>
|
||||
</h1>
|
||||
<h2
|
||||
v-if="props.subtitle"
|
||||
:class="['text-zinc-400 text-sm w-full', { 'mb-3': link }]"
|
||||
>
|
||||
{{ props.subtitle }}
|
||||
<div v-if="rightTitle" class="float-right">{{ props.rightTitle }}</div>
|
||||
</h2>
|
||||
|
||||
<slot />
|
||||
|
||||
<div v-if="props.link" class="absolute bottom-5 right-5">
|
||||
<NuxtLink
|
||||
:to="props.link.url"
|
||||
class="transition text-sm/6 font-semibold text-zinc-400 hover:text-zinc-100 inline-flex gap-x-2 items-center duration-200 hover:scale-105"
|
||||
>
|
||||
{{ props.link.label }}
|
||||
<ArrowRightIcon class="h-4 w-4" aria-hidden="true" />
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ArrowRightIcon } from "@heroicons/vue/20/solid";
|
||||
|
||||
const props = defineProps<{
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
rightTitle?: string;
|
||||
link?: {
|
||||
url: string;
|
||||
label: string;
|
||||
};
|
||||
}>();
|
||||
</script>
|
||||
@ -117,7 +117,9 @@
|
||||
"servers": "Servers",
|
||||
"srLoading": "Loading…",
|
||||
"tags": "Tags",
|
||||
"today": "Today"
|
||||
"today": "Today",
|
||||
"labelValueColon": "{label}: {value}",
|
||||
"noData": "No data"
|
||||
},
|
||||
"delete": "Delete",
|
||||
"drop": {
|
||||
@ -268,6 +270,8 @@
|
||||
"store": "Store",
|
||||
"tokens": "API tokens"
|
||||
},
|
||||
"home": "Home",
|
||||
"library": "Library",
|
||||
"tasks": "Tasks",
|
||||
"users": "Users"
|
||||
},
|
||||
@ -276,7 +280,24 @@
|
||||
},
|
||||
"helpUsTranslate": "Help us translate Drop {arrow}",
|
||||
"highest": "highest",
|
||||
"home": "Home",
|
||||
"home": {
|
||||
"admin": {
|
||||
"title": "Home",
|
||||
"subheader": "Instance summary",
|
||||
"games": "Games",
|
||||
"librarySources": "Library sources",
|
||||
"version": "Version",
|
||||
"activeInactiveUsers": "Active/inactive users",
|
||||
"activeUsers": "Active users",
|
||||
"inactiveUsers": "Inactive users",
|
||||
"goToUsers": "Go to users",
|
||||
"users": "Users",
|
||||
"biggestGamesToDownload": "Biggest games to download",
|
||||
"latestVersionOnly": "Latest version only",
|
||||
"biggestGamesOnServer": "Biggest games on server",
|
||||
"allVersionsCombined": "All versions combined"
|
||||
}
|
||||
},
|
||||
"library": {
|
||||
"addGames": "All Games",
|
||||
"addToLib": "Add to Library",
|
||||
@ -292,6 +313,7 @@
|
||||
"deleteImage": "Delete image",
|
||||
"editGameDescription": "Game Description",
|
||||
"editGameName": "Game Name",
|
||||
"editReleaseDate": "Release Date",
|
||||
"imageCarousel": "Image Carousel",
|
||||
"imageCarouselDescription": "Customise what images and what order are shown on the store page.",
|
||||
"imageCarouselEmpty": "No images added to the carousel yet.",
|
||||
@ -423,7 +445,11 @@
|
||||
"namePlaceholder": "My New Source",
|
||||
"sources": "Library Sources",
|
||||
"typeDesc": "The type of your source. Changes the required options.",
|
||||
"working": "Working?"
|
||||
"working": "Working?",
|
||||
"freeSpace": "Free space",
|
||||
"totalSpace": "Total space",
|
||||
"utilizationPercentage": "Utilization percentage",
|
||||
"percentage": "{number}%"
|
||||
},
|
||||
"subheader": "As you add folders to your library sources, Drop will detect it and prompt you to import it. Each game needs to be imported before you can import a version.",
|
||||
"title": "Libraries",
|
||||
@ -553,6 +579,7 @@
|
||||
"openFeatured": "Star games in Admin Library {arrow}",
|
||||
"platform": "Platform | Platform | Platforms",
|
||||
"publishers": "Publishers | Publisher | Publishers",
|
||||
"size": "Size",
|
||||
"rating": "Rating",
|
||||
"readLess": "Click to read less",
|
||||
"readMore": "Click to read more",
|
||||
|
||||
@ -174,9 +174,14 @@ import { XMarkIcon } from "@heroicons/vue/24/solid";
|
||||
const i18nHead = useLocaleHead();
|
||||
|
||||
const navigation: Array<NavigationItem & { icon: Component }> = [
|
||||
{ label: $t("home"), route: "/admin", prefix: "/admin", icon: HomeIcon },
|
||||
{
|
||||
label: $t("userHeader.links.library"),
|
||||
label: $t("header.admin.home"),
|
||||
route: "/admin",
|
||||
prefix: "/admin",
|
||||
icon: HomeIcon,
|
||||
},
|
||||
{
|
||||
label: $t("header.admin.library"),
|
||||
route: "/admin/library",
|
||||
prefix: "/admin/library",
|
||||
icon: ServerStackIcon,
|
||||
|
||||
@ -256,6 +256,7 @@ export default defineNuxtConfig({
|
||||
"https://www.giantbomb.com",
|
||||
"https://images.pcgamingwiki.com",
|
||||
"https://images.igdb.com",
|
||||
"https://*.steamstatic.com",
|
||||
],
|
||||
},
|
||||
strictTransportSecurity: false,
|
||||
|
||||
@ -21,7 +21,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@discordapp/twemoji": "^16.0.1",
|
||||
"@drop-oss/droplet": "3.0.1",
|
||||
"@drop-oss/droplet": "3.2.0",
|
||||
"@headlessui/vue": "^1.7.23",
|
||||
"@heroicons/vue": "^2.1.5",
|
||||
"@lobomfz/prismark": "0.0.3",
|
||||
@ -33,7 +33,7 @@
|
||||
"@vueuse/nuxt": "13.6.0",
|
||||
"argon2": "^0.43.0",
|
||||
"arktype": "^2.1.10",
|
||||
"axios": "^1.7.7",
|
||||
"axios": "^1.12.0",
|
||||
"bcryptjs": "^3.0.2",
|
||||
"cheerio": "^1.0.0",
|
||||
"cookie-es": "^2.0.0",
|
||||
|
||||
@ -1,6 +1,147 @@
|
||||
<template><div /></template>
|
||||
<template>
|
||||
<div class="space-y-4">
|
||||
<div class="sm:flex sm:items-center">
|
||||
<div class="sm:flex-auto">
|
||||
<h1 class="text-2xl font-semibold text-zinc-100">
|
||||
{{ t("home.admin.title") }}
|
||||
</h1>
|
||||
<p class="mt-2 text-base text-zinc-400">
|
||||
{{ t("home.admin.subheader") }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<main
|
||||
class="mx-auto max-w-md lg:max-w-none md:max-w-none w-full py-2 text-zinc-100"
|
||||
>
|
||||
<div class="grid grid-cols-6 gap-4">
|
||||
<div class="col-span-6 lg:col-span-1 md:col-span-3 row-span-1">
|
||||
<TileWithLink>
|
||||
<div class="h-full flex">
|
||||
<div class="flex-1 my-auto">
|
||||
<DropLogo />
|
||||
</div>
|
||||
<div
|
||||
class="flex-6 lg:flex-2 my-auto text-center flex lg:inline mx-4"
|
||||
>
|
||||
<div class="text-2xl flex-1 font-bold">{{ version }}</div>
|
||||
<div class="text-xs flex-1 text-left lg:text-center">
|
||||
{{ t("home.admin.version") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TileWithLink>
|
||||
</div>
|
||||
|
||||
<div class="col-span-6 lg:col-span-1 md:col-span-3">
|
||||
<TileWithLink>
|
||||
<div class="h-full flex">
|
||||
<div class="flex-1 my-auto">
|
||||
<GamepadIcon />
|
||||
</div>
|
||||
<div
|
||||
class="flex-6 lg:flex-2 my-auto text-center flex lg:inline mx-4"
|
||||
>
|
||||
<div class="text-3xl flex-1 font-bold">{{ gameCount }}</div>
|
||||
<div class="text-xs flex-1 text-left lg:text-center">
|
||||
{{ t("home.admin.games") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TileWithLink>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="col-span-6 lg:col-span-1 md:col-span-3 row-span-1 lg:col-start-1 lg:row-start-2"
|
||||
>
|
||||
<TileWithLink>
|
||||
<div class="h-full flex">
|
||||
<div class="flex-1 my-auto">
|
||||
<ServerStackIcon />
|
||||
</div>
|
||||
<div
|
||||
class="flex-6 lg:flex-2 my-auto text-center flex lg:inline mx-4"
|
||||
>
|
||||
<div class="text-3xl flex-1 font-bold">
|
||||
{{ sources.length }}
|
||||
</div>
|
||||
<div class="text-xs flex-1 text-left lg:text-center">
|
||||
{{ t("home.admin.librarySources") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TileWithLink>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="col-span-6 lg:col-span-1 md:col-span-3 row-span-1 lg:col-start-2 lg:row-start-2"
|
||||
>
|
||||
<TileWithLink>
|
||||
<div class="h-full flex">
|
||||
<div class="flex-1 my-auto">
|
||||
<UserGroupIcon />
|
||||
</div>
|
||||
<div
|
||||
class="flex-6 lg:flex-2 my-auto text-center flex lg:inline mx-4"
|
||||
>
|
||||
<div class="text-3xl flex-1 font-bold">
|
||||
{{ userStats.userCount }}
|
||||
</div>
|
||||
<div class="text-xs flex-1 text-left lg:text-center">
|
||||
{{ t("home.admin.users") }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</TileWithLink>
|
||||
</div>
|
||||
|
||||
<div class="col-span-6 row-span-1 lg:col-span-2 lg:row-span-2">
|
||||
<TileWithLink
|
||||
:link="{
|
||||
url: '/admin/users',
|
||||
label: t('home.admin.goToUsers'),
|
||||
}"
|
||||
:title="t('home.admin.activeInactiveUsers')"
|
||||
>
|
||||
<PieChart :data="pieChartData" />
|
||||
</TileWithLink>
|
||||
</div>
|
||||
<div class="col-span-6">
|
||||
<TileWithLink
|
||||
title="Library"
|
||||
:link="{ url: '/admin/library', label: 'Go to library' }"
|
||||
>
|
||||
<SourceTable :sources="sources" />
|
||||
</TileWithLink>
|
||||
</div>
|
||||
<div class="col-span-6 lg:col-span-2">
|
||||
<TileWithLink
|
||||
:title="t('home.admin.biggestGamesToDownload')"
|
||||
:subtitle="t('home.admin.latestVersionOnly')"
|
||||
>
|
||||
<RankingList :items="biggestGamesLatest.map(gameToRankItem)" />
|
||||
</TileWithLink>
|
||||
</div>
|
||||
<div class="col-span-6 lg:col-span-2">
|
||||
<TileWithLink
|
||||
:title="t('home.admin.biggestGamesOnServer')"
|
||||
:subtitle="t('home.admin.allVersionsCombined')"
|
||||
>
|
||||
<RankingList :items="biggestGamesCombined.map(gameToRankItem)" />
|
||||
</TileWithLink>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { formatBytes } from "~/server/internal/utils/files";
|
||||
import GamepadIcon from "~/components/Icons/GamepadIcon.vue";
|
||||
import DropLogo from "~/components/DropLogo.vue";
|
||||
import { ServerStackIcon, UserGroupIcon } from "@heroicons/vue/24/outline";
|
||||
import type { GameSize } from "~/server/internal/gamesize";
|
||||
import type { RankItem } from "~/components/RankingList.vue";
|
||||
|
||||
definePageMeta({
|
||||
layout: "admin",
|
||||
});
|
||||
@ -8,4 +149,29 @@ definePageMeta({
|
||||
useHead({
|
||||
title: "Home",
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const {
|
||||
version,
|
||||
gameCount,
|
||||
sources,
|
||||
userStats,
|
||||
biggestGamesLatest,
|
||||
biggestGamesCombined,
|
||||
} = await $dropFetch("/api/v1/admin/home");
|
||||
|
||||
const gameToRankItem = (game: GameSize, rank: number): RankItem => ({
|
||||
rank: rank + 1,
|
||||
name: game.gameName,
|
||||
value: formatBytes(game.size),
|
||||
});
|
||||
|
||||
const pieChartData = [
|
||||
{
|
||||
label: t("home.admin.inactiveUsers"),
|
||||
value: userStats.userCount - userStats.activeSessions,
|
||||
},
|
||||
{ label: t("home.admin.activeUsers"), value: userStats.activeSessions },
|
||||
];
|
||||
</script>
|
||||
|
||||
@ -18,99 +18,12 @@
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-8 flow-root">
|
||||
<div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
|
||||
<div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
|
||||
<table class="min-w-full divide-y divide-zinc-700">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
scope="col"
|
||||
class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-zinc-100 sm:pl-3"
|
||||
>
|
||||
{{ $t("common.name") }}
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-3.5 text-left text-sm font-semibold text-zinc-100"
|
||||
>
|
||||
{{ $t("type") }}
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-3.5 text-left text-sm font-semibold text-zinc-100"
|
||||
>
|
||||
{{ $t("library.admin.sources.working") }}
|
||||
</th>
|
||||
<th
|
||||
scope="col"
|
||||
class="px-3 py-3.5 text-left text-sm font-semibold text-zinc-100"
|
||||
>
|
||||
{{ $t("options") }}
|
||||
</th>
|
||||
<th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-3">
|
||||
<span class="sr-only">{{ $t("common.edit") }}</span>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr
|
||||
v-for="(source, sourceIdx) in sources"
|
||||
:key="source.id"
|
||||
class="even:bg-zinc-800"
|
||||
>
|
||||
<td
|
||||
class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-zinc-100 sm:pl-3"
|
||||
>
|
||||
{{ source.name }}
|
||||
</td>
|
||||
<td
|
||||
class="whitespace-nowrap px-3 py-4 text-sm text-zinc-400 inline-flex gap-x-1 items-center"
|
||||
>
|
||||
<component
|
||||
:is="optionsMetadata[source.backend].icon"
|
||||
class="size-5 text-zinc-400"
|
||||
/>
|
||||
{{ optionsMetadata[source.backend].title }}
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-zinc-400">
|
||||
<CheckIcon
|
||||
v-if="source.working"
|
||||
class="size-5 text-green-500"
|
||||
/>
|
||||
<XMarkIcon v-else class="size-5 text-red-500" />
|
||||
</td>
|
||||
<td class="whitespace-nowrap px-3 py-4 text-sm text-zinc-400">
|
||||
{{ source.options }}
|
||||
</td>
|
||||
<td
|
||||
class="relative whitespace-nowrap py-4 pl-3 pr-3 text-right text-sm font-medium space-x-2"
|
||||
>
|
||||
<button
|
||||
class="text-blue-500 hover:text-blue-400"
|
||||
@click="() => edit(sourceIdx)"
|
||||
>
|
||||
{{ $t("common.edit") }}
|
||||
<span class="sr-only">
|
||||
{{ $t("chars.srComma", [source.name]) }}
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
class="text-red-500 hover:text-red-400"
|
||||
@click="() => deleteSource(sourceIdx)"
|
||||
>
|
||||
{{ $t("delete") }}
|
||||
<span class="sr-only">
|
||||
{{ $t("chars.srComma", [source.name]) }}
|
||||
</span>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4 flow-root">
|
||||
<SourceTable
|
||||
:sources="sources"
|
||||
:edit-source="edit"
|
||||
:delete-source="deleteSource"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ModalTemplate v-model="actionSourceOpen">
|
||||
@ -313,7 +226,7 @@ import {
|
||||
XCircleIcon,
|
||||
ArrowTopRightOnSquareIcon,
|
||||
} from "@heroicons/vue/20/solid";
|
||||
import { BackwardIcon, CheckIcon, XMarkIcon } from "@heroicons/vue/24/outline";
|
||||
import { BackwardIcon } from "@heroicons/vue/24/outline";
|
||||
import { FetchError } from "ofetch";
|
||||
import type { Component } from "vue";
|
||||
import type { LibraryBackend } from "~/prisma/client/enums";
|
||||
|
||||
@ -53,18 +53,7 @@
|
||||
:log="parseTaskLog(task.log.at(-(idx + 1)))"
|
||||
/>
|
||||
</div>
|
||||
<div class="relative h-5 rounded-xl bg-zinc-950 overflow-hidden">
|
||||
<div
|
||||
:style="{ width: `${task.progress}%` }"
|
||||
class="transition-all bg-blue-600 h-full"
|
||||
/>
|
||||
<span
|
||||
class="absolute inset-0 flex items-center justify-center text-blue-200 text-sm font-bold font-display"
|
||||
>{{
|
||||
$t("tasks.admin.progress", [Math.round(task.progress * 10) / 10])
|
||||
}}</span
|
||||
>
|
||||
</div>
|
||||
<ProgressBar :percentage="task.progress" />
|
||||
</div>
|
||||
<div v-else role="status" class="w-full flex items-center justify-center">
|
||||
<svg
|
||||
|
||||
@ -72,7 +72,7 @@
|
||||
{{ $t("store.images") }}
|
||||
</h2>
|
||||
<div class="relative">
|
||||
<VueCarousel :items-to-show="1">
|
||||
<VueCarousel :items-to-show="1" :wrap-around="true">
|
||||
<VueSlide
|
||||
v-for="image in game.mImageCarouselObjectIds"
|
||||
:key="image"
|
||||
|
||||
@ -86,6 +86,27 @@
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-zinc-100 sm:pl-3"
|
||||
>
|
||||
{{ $t("store.size") }}
|
||||
</td>
|
||||
<td
|
||||
v-if="size"
|
||||
class="whitespace-nowrap inline-flex gap-x-4 px-3 py-4 text-sm text-zinc-400"
|
||||
>
|
||||
{{ formatBytes(size) }}
|
||||
</td>
|
||||
<td
|
||||
v-else
|
||||
class="whitespace-nowrap inline-flex gap-x-4 px-3 py-4 text-sm text-zinc-400 italic"
|
||||
>
|
||||
<span class="font-semibold text-blue-600">{{
|
||||
$t("store.commingSoon")
|
||||
}}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-zinc-100 sm:pl-3"
|
||||
@ -183,7 +204,7 @@
|
||||
{{ game.mShortDescription }}
|
||||
</p>
|
||||
<div class="mt-6 py-4 rounded">
|
||||
<VueCarousel :items-to-show="1">
|
||||
<VueCarousel :items-to-show="1" :wrap-around="true">
|
||||
<VueSlide
|
||||
v-for="image in game.mImageCarouselObjectIds"
|
||||
:key="image"
|
||||
@ -246,13 +267,14 @@ import { ArrowTopRightOnSquareIcon } from "@heroicons/vue/24/outline";
|
||||
import { StarIcon } from "@heroicons/vue/24/solid";
|
||||
import { micromark } from "micromark";
|
||||
import type { PlatformClient } from "~/composables/types";
|
||||
import { formatBytes } from "~/server/internal/utils/files";
|
||||
|
||||
const route = useRoute();
|
||||
const gameId = route.params.id.toString();
|
||||
|
||||
const user = useUser();
|
||||
|
||||
const { game, rating } = await $dropFetch(`/api/v1/games/${gameId}`);
|
||||
const { game, rating, size } = await $dropFetch(`/api/v1/games/${gameId}`);
|
||||
|
||||
// Preview description (first 30 lines)
|
||||
const showPreview = ref(true);
|
||||
|
||||
409
pnpm-lock.yaml
generated
409
pnpm-lock.yaml
generated
@ -4,6 +4,9 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
droplet: link:../../.local/share/pnpm/global/5/node_modules/@drop-oss/droplet
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
@ -12,14 +15,14 @@ importers:
|
||||
specifier: ^16.0.1
|
||||
version: 16.0.1
|
||||
'@drop-oss/droplet':
|
||||
specifier: 3.0.1
|
||||
version: 3.0.1
|
||||
specifier: 3.2.0
|
||||
version: 3.2.0
|
||||
'@headlessui/vue':
|
||||
specifier: ^1.7.23
|
||||
version: 1.7.23(vue@3.5.21(typescript@5.8.3))
|
||||
version: 1.7.23(vue@3.5.22(typescript@5.8.3))
|
||||
'@heroicons/vue':
|
||||
specifier: ^2.1.5
|
||||
version: 2.2.0(vue@3.5.21(typescript@5.8.3))
|
||||
version: 2.2.0(vue@3.5.22(typescript@5.8.3))
|
||||
'@lobomfz/prismark':
|
||||
specifier: 0.0.3
|
||||
version: 0.0.3
|
||||
@ -31,7 +34,7 @@ importers:
|
||||
version: 1.10.0(@netlify/blobs@9.1.2)(db0@0.3.2)(ioredis@5.7.0)(magicast@0.3.5)
|
||||
'@nuxtjs/i18n':
|
||||
specifier: ^9.5.5
|
||||
version: 9.5.6(@vue/compiler-dom@3.5.21)(eslint@9.31.0(jiti@2.5.1))(magicast@0.3.5)(rollup@4.46.2)(vue@3.5.21(typescript@5.8.3))
|
||||
version: 9.5.6(@vue/compiler-dom@3.5.22)(eslint@9.31.0(jiti@2.5.1))(magicast@0.3.5)(rollup@4.46.2)(vue@3.5.22(typescript@5.8.3))
|
||||
'@prisma/client':
|
||||
specifier: ^6.11.1
|
||||
version: 6.12.0(prisma@6.12.0(typescript@5.8.3))(typescript@5.8.3)
|
||||
@ -40,7 +43,7 @@ importers:
|
||||
version: 4.1.11(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))
|
||||
'@vueuse/nuxt':
|
||||
specifier: 13.6.0
|
||||
version: 13.6.0(magicast@0.3.5)(nuxt@3.17.7(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.16.5)(@vue/compiler-sfc@3.5.21)(db0@0.3.2)(eslint@9.31.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.46.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue-tsc@3.0.4(typescript@5.8.3))(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3))
|
||||
version: 13.6.0(magicast@0.3.5)(nuxt@3.17.7(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.16.5)(@vue/compiler-sfc@3.5.22)(db0@0.3.2)(eslint@9.31.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.46.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue-tsc@3.0.4(typescript@5.8.3))(yaml@2.8.0))(vue@3.5.22(typescript@5.8.3))
|
||||
argon2:
|
||||
specifier: ^0.43.0
|
||||
version: 0.43.1
|
||||
@ -48,8 +51,8 @@ importers:
|
||||
specifier: ^2.1.10
|
||||
version: 2.1.20
|
||||
axios:
|
||||
specifier: ^1.7.7
|
||||
version: 1.11.0
|
||||
specifier: ^1.12.0
|
||||
version: 1.12.0
|
||||
bcryptjs:
|
||||
specifier: ^3.0.2
|
||||
version: 3.0.2
|
||||
@ -79,7 +82,7 @@ importers:
|
||||
version: 8.0.2
|
||||
nuxt:
|
||||
specifier: ^3.17.4
|
||||
version: 3.17.7(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.16.5)(@vue/compiler-sfc@3.5.21)(db0@0.3.2)(eslint@9.31.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.46.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue-tsc@3.0.4(typescript@5.8.3))(yaml@2.8.0)
|
||||
version: 3.17.7(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.16.5)(@vue/compiler-sfc@3.5.22)(db0@0.3.2)(eslint@9.31.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.46.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue-tsc@3.0.4(typescript@5.8.3))(yaml@2.8.0)
|
||||
nuxt-security:
|
||||
specifier: 2.2.0
|
||||
version: 2.2.0(magicast@0.3.5)(rollup@4.46.2)
|
||||
@ -112,26 +115,26 @@ importers:
|
||||
version: 3.1.2(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))
|
||||
vue:
|
||||
specifier: latest
|
||||
version: 3.5.21(typescript@5.8.3)
|
||||
version: 3.5.22(typescript@5.8.3)
|
||||
vue-router:
|
||||
specifier: latest
|
||||
version: 4.5.1(vue@3.5.21(typescript@5.8.3))
|
||||
version: 4.5.1(vue@3.5.22(typescript@5.8.3))
|
||||
vue3-carousel:
|
||||
specifier: ^0.16.0
|
||||
version: 0.16.0(vue@3.5.21(typescript@5.8.3))
|
||||
version: 0.16.0(vue@3.5.22(typescript@5.8.3))
|
||||
vue3-carousel-nuxt:
|
||||
specifier: ^1.1.5
|
||||
version: 1.1.6(magicast@0.3.5)(vue@3.5.21(typescript@5.8.3))
|
||||
version: 1.1.6(magicast@0.3.5)(vue@3.5.22(typescript@5.8.3))
|
||||
vuedraggable:
|
||||
specifier: ^4.1.0
|
||||
version: 4.1.0(vue@3.5.21(typescript@5.8.3))
|
||||
version: 4.1.0(vue@3.5.22(typescript@5.8.3))
|
||||
devDependencies:
|
||||
'@intlify/eslint-plugin-vue-i18n':
|
||||
specifier: ^4.0.1
|
||||
version: 4.0.1(eslint@9.31.0(jiti@2.5.1))(jsonc-eslint-parser@2.4.0)(vue-eslint-parser@10.2.0(eslint@9.31.0(jiti@2.5.1)))(yaml-eslint-parser@1.3.0)
|
||||
'@nuxt/eslint':
|
||||
specifier: ^1.3.0
|
||||
version: 1.7.1(@typescript-eslint/utils@8.38.0(eslint@9.31.0(jiti@2.5.1))(typescript@5.8.3))(@vue/compiler-sfc@3.5.21)(eslint@9.31.0(jiti@2.5.1))(magicast@0.3.5)(typescript@5.8.3)(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))
|
||||
version: 1.7.1(@typescript-eslint/utils@8.38.0(eslint@9.31.0(jiti@2.5.1))(typescript@5.8.3))(@vue/compiler-sfc@3.5.22)(eslint@9.31.0(jiti@2.5.1))(magicast@0.3.5)(typescript@5.8.3)(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))
|
||||
'@tailwindcss/forms':
|
||||
specifier: ^0.5.9
|
||||
version: 0.5.10(tailwindcss@4.1.11)
|
||||
@ -370,67 +373,67 @@ packages:
|
||||
'@discordapp/twemoji@16.0.1':
|
||||
resolution: {integrity: sha512-figLiBWzjS5cyrAjLaGjM8AAaowO3qvK8rg5bA2dElB4qsaPMvBVlFDMO2d3x+nC1igt7kgWH4dvNmvvUHUF8w==}
|
||||
|
||||
'@drop-oss/droplet-darwin-arm64@3.0.1':
|
||||
resolution: {integrity: sha512-LXe8vsXUBL96boI78H6oXpSaPVwF4cCwJ5l/QVtsOWMebNo6gk9wICDZ+5IoR/Ol32t1a1lk+DjbD1zeGenPxg==}
|
||||
'@drop-oss/droplet-darwin-arm64@3.2.0':
|
||||
resolution: {integrity: sha512-dH/vRFxuLjOzYBBvDG140wKcx4LmFxBJ5iTjZrWzV641wiRjx8B38niWXuqZ2ZADkCL4muOvgRGFJ4W1N/j6jQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@drop-oss/droplet-darwin-universal@3.0.1':
|
||||
resolution: {integrity: sha512-Mf2gjC24u6s8djV/3slZvwdr4+h0qBu2OYXBUSDfR4H/VJwV5TstnWVKF+U8d1hjmHE9eLO8elbGNnpQmSoTOQ==}
|
||||
'@drop-oss/droplet-darwin-universal@3.2.0':
|
||||
resolution: {integrity: sha512-k7Xhzs2mXrQcm3SLhLNDBkUaCWqtbQ6dyme1ubsG9PZEcvv25T//8CNVFEsHVZTKqj5nF41iSh4Wz1Qn6VxkVw==}
|
||||
engines: {node: '>= 10'}
|
||||
os: [darwin]
|
||||
|
||||
'@drop-oss/droplet-darwin-x64@3.0.1':
|
||||
resolution: {integrity: sha512-4IIDl/E+hzZ2Vt9m4FMPlZEXwj1EwE6qXyUidACK6TTFqpjLpsEHKuhv1FOxGyJ8qkvagtyPCc+cs1TxoZD6FA==}
|
||||
'@drop-oss/droplet-darwin-x64@3.2.0':
|
||||
resolution: {integrity: sha512-GvRwQrtcC1Dq6YyXxBGSFj+WasnIa1dk9t2lCaR9OQdh3qp2did21o2poo1Sgdjg+mI2lUdgZ6w0yXJlL1vl+A==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@drop-oss/droplet-linux-arm64-gnu@3.0.1':
|
||||
resolution: {integrity: sha512-klGvlLf1xSMT3iYsIAaBbmbir1ZJWtcVyOMUlsfc1lkJ8mgyB+PrW4BsnYj7Pp4G34n7WsOChjC8TdJDBBuBWg==}
|
||||
'@drop-oss/droplet-linux-arm64-gnu@3.2.0':
|
||||
resolution: {integrity: sha512-ZqH0xTEeSeJF77vy8rZDxHEV8JMaN0khdg6ptpnbBfc56J5jt6wS3NlHK8M0ZVlDqqZnXMS1vUO0b6rfmQodKw==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@drop-oss/droplet-linux-arm64-musl@3.0.1':
|
||||
resolution: {integrity: sha512-oOjvGETlrJGC1RlNhUoVS9N89Rn/0DqBauVz3BBFjJTKSd5jU3/gLzwgmfkKDGVEU5lyGPAn2WQroiESEG9wdA==}
|
||||
'@drop-oss/droplet-linux-arm64-musl@3.2.0':
|
||||
resolution: {integrity: sha512-TTw44PggYfp3RJkvNhXH89duuuvONEA8c8oRBCzCczRf3hDnbzCQLaB1UlnIlESsJZXXiFSDIBV2/0kkpB+Ukg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@drop-oss/droplet-linux-riscv64-gnu@3.0.1':
|
||||
resolution: {integrity: sha512-Zf3gUsWq9Hqb275MOi7PJDhmJz7Qa/Y1XMen880bxPaOeDFqFOoKUxUr2/qv1MYp6tT3zO27NprGsHirYWqsyA==}
|
||||
'@drop-oss/droplet-linux-riscv64-gnu@3.2.0':
|
||||
resolution: {integrity: sha512-Ee/PfkoG8pm/9C3LFXJleIi5N8V5cK+44p+iDaneAo6gj5k67zYzuga3mJVswTgd3fncG1cw+xPqBl4PUWc1pg==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@drop-oss/droplet-linux-x64-gnu@3.0.1':
|
||||
resolution: {integrity: sha512-sskblycJdtNJVnRHjPHhwHkQUfQNaDIWDzXOzEaBPOcDKqYA7od7VMDAseqBkrKDn7l8bBUtRXFAipdsO8hffw==}
|
||||
'@drop-oss/droplet-linux-x64-gnu@3.2.0':
|
||||
resolution: {integrity: sha512-L2M/MEoe5Y74MTtzpEWHIvdyRSPLgM1WLzpb/xRNCWe8d6FcUFDgdMlbd6rDj5t4Q6JEzyMIHUciVRaYIv+ShA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@drop-oss/droplet-linux-x64-musl@3.0.1':
|
||||
resolution: {integrity: sha512-lh+1M6UAf5+ET1/ZEFRsB3shFHjkT/9Ql9akr/vyUue91TWPmP71meqVkCugWDhP6lxBt56jg2VVrJfmPAsK6w==}
|
||||
'@drop-oss/droplet-linux-x64-musl@3.2.0':
|
||||
resolution: {integrity: sha512-F/uQUAHWbhiiAtoyKHQHPgjG7jJd8pQX6sCgdf5ufCdwFLvHEdu9pO0qN+xpzaACceIKX4Vip0vUwQwEzYhAKA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@drop-oss/droplet-win32-arm64-msvc@3.0.1':
|
||||
resolution: {integrity: sha512-caQDPoDNJyyJXUEijw+hGTy0wmCrW5efTqBwnvMcQ282EOilg1d5WeJ31pfEcuLYF4MK1t9uaLcG6jZ9YLtzEQ==}
|
||||
'@drop-oss/droplet-win32-arm64-msvc@3.2.0':
|
||||
resolution: {integrity: sha512-x7i1KKL8vQGcXbKIyH56LCEdQxLKNEk/KFjuD/YGrbBJ/+Q+fh46hLK+Sx4I/HzPHecd5g3xc2kVgO7+DgjhYA==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@drop-oss/droplet-win32-x64-msvc@3.0.1':
|
||||
resolution: {integrity: sha512-bp8KwewF/T3JkVeJWkg86U3b0cGQD9i8k92x6HYPtnF5nLPAb2UIUEJgmYYFNPFe36RECBV7PIIG0ujdT1ELQw==}
|
||||
'@drop-oss/droplet-win32-x64-msvc@3.2.0':
|
||||
resolution: {integrity: sha512-lC8a456IQ0ArzX40IlStolV4GIdl26xF9PikcuQ9r+n4VDqWSHb8A0Wwj87leU3QdoMu+Y2nlA1QHKgpVSEuoQ==}
|
||||
engines: {node: '>= 10'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@drop-oss/droplet@3.0.1':
|
||||
resolution: {integrity: sha512-YhtgpwNqEHO8R03yf9Xb5LXuaLWkQvY+2lxOD1PwzpGI5V9PKlDE+x1IJBmdBF5bDPDGk9MxQidGtnYQuAEBEA==}
|
||||
'@drop-oss/droplet@3.2.0':
|
||||
resolution: {integrity: sha512-+3zw3MPriMrj8HlKAq2VTlXEPOXN0homusjmQcBRzVx7GjtGvb5Y9YIHs16qfn8zdTEDi5twrtsUBQYkVjU2bQ==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
'@emnapi/core@1.4.5':
|
||||
@ -2081,26 +2084,26 @@ packages:
|
||||
'@vue/compiler-core@3.5.18':
|
||||
resolution: {integrity: sha512-3slwjQrrV1TO8MoXgy3aynDQ7lslj5UqDxuHnrzHtpON5CBinhWjJETciPngpin/T3OuW3tXUf86tEurusnztw==}
|
||||
|
||||
'@vue/compiler-core@3.5.21':
|
||||
resolution: {integrity: sha512-8i+LZ0vf6ZgII5Z9XmUvrCyEzocvWT+TeR2VBUVlzIH6Tyv57E20mPZ1bCS+tbejgUgmjrEh7q/0F0bibskAmw==}
|
||||
'@vue/compiler-core@3.5.22':
|
||||
resolution: {integrity: sha512-jQ0pFPmZwTEiRNSb+i9Ow/I/cHv2tXYqsnHKKyCQ08irI2kdF5qmYedmF8si8mA7zepUFmJ2hqzS8CQmNOWOkQ==}
|
||||
|
||||
'@vue/compiler-dom@3.5.18':
|
||||
resolution: {integrity: sha512-RMbU6NTU70++B1JyVJbNbeFkK+A+Q7y9XKE2EM4NLGm2WFR8x9MbAtWxPPLdm0wUkuZv9trpwfSlL6tjdIa1+A==}
|
||||
|
||||
'@vue/compiler-dom@3.5.21':
|
||||
resolution: {integrity: sha512-jNtbu/u97wiyEBJlJ9kmdw7tAr5Vy0Aj5CgQmo+6pxWNQhXZDPsRr1UWPN4v3Zf82s2H3kF51IbzZ4jMWAgPlQ==}
|
||||
'@vue/compiler-dom@3.5.22':
|
||||
resolution: {integrity: sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==}
|
||||
|
||||
'@vue/compiler-sfc@3.5.18':
|
||||
resolution: {integrity: sha512-5aBjvGqsWs+MoxswZPoTB9nSDb3dhd1x30xrrltKujlCxo48j8HGDNj3QPhF4VIS0VQDUrA1xUfp2hEa+FNyXA==}
|
||||
|
||||
'@vue/compiler-sfc@3.5.21':
|
||||
resolution: {integrity: sha512-SXlyk6I5eUGBd2v8Ie7tF6ADHE9kCR6mBEuPyH1nUZ0h6Xx6nZI29i12sJKQmzbDyr2tUHMhhTt51Z6blbkTTQ==}
|
||||
'@vue/compiler-sfc@3.5.22':
|
||||
resolution: {integrity: sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==}
|
||||
|
||||
'@vue/compiler-ssr@3.5.18':
|
||||
resolution: {integrity: sha512-xM16Ak7rSWHkM3m22NlmcdIM+K4BMyFARAfV9hYFl+SFuRzrZ3uGMNW05kA5pmeMa0X9X963Kgou7ufdbpOP9g==}
|
||||
|
||||
'@vue/compiler-ssr@3.5.21':
|
||||
resolution: {integrity: sha512-vKQ5olH5edFZdf5ZrlEgSO1j1DMA4u23TVK5XR1uMhvwnYvVdDF0nHXJUblL/GvzlShQbjhZZ2uvYmDlAbgo9w==}
|
||||
'@vue/compiler-ssr@3.5.22':
|
||||
resolution: {integrity: sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==}
|
||||
|
||||
'@vue/compiler-vue2@2.7.16':
|
||||
resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==}
|
||||
@ -2127,25 +2130,25 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
'@vue/reactivity@3.5.21':
|
||||
resolution: {integrity: sha512-3ah7sa+Cwr9iiYEERt9JfZKPw4A2UlbY8RbbnH2mGCE8NwHkhmlZt2VsH0oDA3P08X3jJd29ohBDtX+TbD9AsA==}
|
||||
'@vue/reactivity@3.5.22':
|
||||
resolution: {integrity: sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==}
|
||||
|
||||
'@vue/runtime-core@3.5.21':
|
||||
resolution: {integrity: sha512-+DplQlRS4MXfIf9gfD1BOJpk5RSyGgGXD/R+cumhe8jdjUcq/qlxDawQlSI8hCKupBlvM+3eS1se5xW+SuNAwA==}
|
||||
'@vue/runtime-core@3.5.22':
|
||||
resolution: {integrity: sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==}
|
||||
|
||||
'@vue/runtime-dom@3.5.21':
|
||||
resolution: {integrity: sha512-3M2DZsOFwM5qI15wrMmNF5RJe1+ARijt2HM3TbzBbPSuBHOQpoidE+Pa+XEaVN+czbHf81ETRoG1ltztP2em8w==}
|
||||
'@vue/runtime-dom@3.5.22':
|
||||
resolution: {integrity: sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==}
|
||||
|
||||
'@vue/server-renderer@3.5.21':
|
||||
resolution: {integrity: sha512-qr8AqgD3DJPJcGvLcJKQo2tAc8OnXRcfxhOJCPF+fcfn5bBGz7VCcO7t+qETOPxpWK1mgysXvVT/j+xWaHeMWA==}
|
||||
'@vue/server-renderer@3.5.22':
|
||||
resolution: {integrity: sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==}
|
||||
peerDependencies:
|
||||
vue: 3.5.21
|
||||
vue: 3.5.22
|
||||
|
||||
'@vue/shared@3.5.18':
|
||||
resolution: {integrity: sha512-cZy8Dq+uuIXbxCZpuLd2GJdeSO/lIzIspC2WtkqIpje5QyFbvLaI5wZtdUjLHjGZrlVX6GilejatWwVYYRc8tA==}
|
||||
|
||||
'@vue/shared@3.5.21':
|
||||
resolution: {integrity: sha512-+2k1EQpnYuVuu3N7atWyG3/xoFWIVJZq4Mz8XNOdScFI0etES75fbny/oU4lKWk/577P1zmg0ioYvpGEDZ3DLw==}
|
||||
'@vue/shared@3.5.22':
|
||||
resolution: {integrity: sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==}
|
||||
|
||||
'@vueuse/core@13.6.0':
|
||||
resolution: {integrity: sha512-DJbD5fV86muVmBgS9QQPddVX7d9hWYswzlf4bIyUD2dj8GC46R1uNClZhVAmsdVts4xb2jwp1PbpuiA50Qee1A==}
|
||||
@ -2305,8 +2308,8 @@ packages:
|
||||
peerDependencies:
|
||||
postcss: ^8.1.0
|
||||
|
||||
axios@1.11.0:
|
||||
resolution: {integrity: sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==}
|
||||
axios@1.12.0:
|
||||
resolution: {integrity: sha512-oXTDccv8PcfjZmPGlWsPSwtOJCZ/b6W5jAMCNcfwJbCzDckwG0jrYJFaWH1yvivfCXjVzV/SPDEhMB3Q+DSurg==}
|
||||
|
||||
b4a@1.6.7:
|
||||
resolution: {integrity: sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==}
|
||||
@ -5730,8 +5733,8 @@ packages:
|
||||
peerDependencies:
|
||||
vue: ^3.5.0
|
||||
|
||||
vue@3.5.21:
|
||||
resolution: {integrity: sha512-xxf9rum9KtOdwdRkiApWL+9hZEMWE90FHh8yS1+KJAiWYh+iGWV1FquPjoO9VUHQ+VIhsCXNNyZ5Sf4++RVZBA==}
|
||||
vue@3.5.22:
|
||||
resolution: {integrity: sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==}
|
||||
peerDependencies:
|
||||
typescript: '*'
|
||||
peerDependenciesMeta:
|
||||
@ -6133,48 +6136,48 @@ snapshots:
|
||||
jsonfile: 5.0.0
|
||||
universalify: 0.1.2
|
||||
|
||||
'@drop-oss/droplet-darwin-arm64@3.0.1':
|
||||
'@drop-oss/droplet-darwin-arm64@3.2.0':
|
||||
optional: true
|
||||
|
||||
'@drop-oss/droplet-darwin-universal@3.0.1':
|
||||
'@drop-oss/droplet-darwin-universal@3.2.0':
|
||||
optional: true
|
||||
|
||||
'@drop-oss/droplet-darwin-x64@3.0.1':
|
||||
'@drop-oss/droplet-darwin-x64@3.2.0':
|
||||
optional: true
|
||||
|
||||
'@drop-oss/droplet-linux-arm64-gnu@3.0.1':
|
||||
'@drop-oss/droplet-linux-arm64-gnu@3.2.0':
|
||||
optional: true
|
||||
|
||||
'@drop-oss/droplet-linux-arm64-musl@3.0.1':
|
||||
'@drop-oss/droplet-linux-arm64-musl@3.2.0':
|
||||
optional: true
|
||||
|
||||
'@drop-oss/droplet-linux-riscv64-gnu@3.0.1':
|
||||
'@drop-oss/droplet-linux-riscv64-gnu@3.2.0':
|
||||
optional: true
|
||||
|
||||
'@drop-oss/droplet-linux-x64-gnu@3.0.1':
|
||||
'@drop-oss/droplet-linux-x64-gnu@3.2.0':
|
||||
optional: true
|
||||
|
||||
'@drop-oss/droplet-linux-x64-musl@3.0.1':
|
||||
'@drop-oss/droplet-linux-x64-musl@3.2.0':
|
||||
optional: true
|
||||
|
||||
'@drop-oss/droplet-win32-arm64-msvc@3.0.1':
|
||||
'@drop-oss/droplet-win32-arm64-msvc@3.2.0':
|
||||
optional: true
|
||||
|
||||
'@drop-oss/droplet-win32-x64-msvc@3.0.1':
|
||||
'@drop-oss/droplet-win32-x64-msvc@3.2.0':
|
||||
optional: true
|
||||
|
||||
'@drop-oss/droplet@3.0.1':
|
||||
'@drop-oss/droplet@3.2.0':
|
||||
optionalDependencies:
|
||||
'@drop-oss/droplet-darwin-arm64': 3.0.1
|
||||
'@drop-oss/droplet-darwin-universal': 3.0.1
|
||||
'@drop-oss/droplet-darwin-x64': 3.0.1
|
||||
'@drop-oss/droplet-linux-arm64-gnu': 3.0.1
|
||||
'@drop-oss/droplet-linux-arm64-musl': 3.0.1
|
||||
'@drop-oss/droplet-linux-riscv64-gnu': 3.0.1
|
||||
'@drop-oss/droplet-linux-x64-gnu': 3.0.1
|
||||
'@drop-oss/droplet-linux-x64-musl': 3.0.1
|
||||
'@drop-oss/droplet-win32-arm64-msvc': 3.0.1
|
||||
'@drop-oss/droplet-win32-x64-msvc': 3.0.1
|
||||
'@drop-oss/droplet-darwin-arm64': 3.2.0
|
||||
'@drop-oss/droplet-darwin-universal': 3.2.0
|
||||
'@drop-oss/droplet-darwin-x64': 3.2.0
|
||||
'@drop-oss/droplet-linux-arm64-gnu': 3.2.0
|
||||
'@drop-oss/droplet-linux-arm64-musl': 3.2.0
|
||||
'@drop-oss/droplet-linux-riscv64-gnu': 3.2.0
|
||||
'@drop-oss/droplet-linux-x64-gnu': 3.2.0
|
||||
'@drop-oss/droplet-linux-x64-musl': 3.2.0
|
||||
'@drop-oss/droplet-win32-arm64-msvc': 3.2.0
|
||||
'@drop-oss/droplet-win32-x64-msvc': 3.2.0
|
||||
|
||||
'@emnapi/core@1.4.5':
|
||||
dependencies:
|
||||
@ -6431,14 +6434,14 @@ snapshots:
|
||||
|
||||
'@fastify/busboy@3.1.1': {}
|
||||
|
||||
'@headlessui/vue@1.7.23(vue@3.5.21(typescript@5.8.3))':
|
||||
'@headlessui/vue@1.7.23(vue@3.5.22(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@tanstack/vue-virtual': 3.13.12(vue@3.5.21(typescript@5.8.3))
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
'@tanstack/vue-virtual': 3.13.12(vue@3.5.22(typescript@5.8.3))
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
|
||||
'@heroicons/vue@2.2.0(vue@3.5.21(typescript@5.8.3))':
|
||||
'@heroicons/vue@2.2.0(vue@3.5.22(typescript@5.8.3))':
|
||||
dependencies:
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
|
||||
'@humanfs/core@0.19.1': {}
|
||||
|
||||
@ -6453,7 +6456,7 @@ snapshots:
|
||||
|
||||
'@humanwhocodes/retry@0.4.3': {}
|
||||
|
||||
'@intlify/bundle-utils@10.0.1(vue-i18n@10.0.8(vue@3.5.21(typescript@5.8.3)))':
|
||||
'@intlify/bundle-utils@10.0.1(vue-i18n@10.0.8(vue@3.5.22(typescript@5.8.3)))':
|
||||
dependencies:
|
||||
'@intlify/message-compiler': 11.1.11
|
||||
'@intlify/shared': 11.1.11
|
||||
@ -6465,7 +6468,7 @@ snapshots:
|
||||
source-map-js: 1.2.1
|
||||
yaml-eslint-parser: 1.3.0
|
||||
optionalDependencies:
|
||||
vue-i18n: 10.0.8(vue@3.5.21(typescript@5.8.3))
|
||||
vue-i18n: 10.0.8(vue@3.5.22(typescript@5.8.3))
|
||||
|
||||
'@intlify/core-base@10.0.8':
|
||||
dependencies:
|
||||
@ -6526,12 +6529,12 @@ snapshots:
|
||||
|
||||
'@intlify/shared@11.1.11': {}
|
||||
|
||||
'@intlify/unplugin-vue-i18n@6.0.8(@vue/compiler-dom@3.5.21)(eslint@9.31.0(jiti@2.5.1))(rollup@4.46.2)(typescript@5.8.3)(vue-i18n@10.0.8(vue@3.5.21(typescript@5.8.3)))(vue@3.5.21(typescript@5.8.3))':
|
||||
'@intlify/unplugin-vue-i18n@6.0.8(@vue/compiler-dom@3.5.22)(eslint@9.31.0(jiti@2.5.1))(rollup@4.46.2)(typescript@5.8.3)(vue-i18n@10.0.8(vue@3.5.22(typescript@5.8.3)))(vue@3.5.22(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@eslint-community/eslint-utils': 4.7.0(eslint@9.31.0(jiti@2.5.1))
|
||||
'@intlify/bundle-utils': 10.0.1(vue-i18n@10.0.8(vue@3.5.21(typescript@5.8.3)))
|
||||
'@intlify/bundle-utils': 10.0.1(vue-i18n@10.0.8(vue@3.5.22(typescript@5.8.3)))
|
||||
'@intlify/shared': 11.1.11
|
||||
'@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.1.11)(@vue/compiler-dom@3.5.21)(vue-i18n@10.0.8(vue@3.5.21(typescript@5.8.3)))(vue@3.5.21(typescript@5.8.3))
|
||||
'@intlify/vue-i18n-extensions': 8.0.0(@intlify/shared@11.1.11)(@vue/compiler-dom@3.5.22)(vue-i18n@10.0.8(vue@3.5.22(typescript@5.8.3)))(vue@3.5.22(typescript@5.8.3))
|
||||
'@rollup/pluginutils': 5.2.0(rollup@4.46.2)
|
||||
'@typescript-eslint/scope-manager': 8.38.0
|
||||
'@typescript-eslint/typescript-estree': 8.38.0(typescript@5.8.3)
|
||||
@ -6543,9 +6546,9 @@ snapshots:
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
unplugin: 1.16.1
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
optionalDependencies:
|
||||
vue-i18n: 10.0.8(vue@3.5.21(typescript@5.8.3))
|
||||
vue-i18n: 10.0.8(vue@3.5.22(typescript@5.8.3))
|
||||
transitivePeerDependencies:
|
||||
- '@vue/compiler-dom'
|
||||
- eslint
|
||||
@ -6555,14 +6558,14 @@ snapshots:
|
||||
|
||||
'@intlify/utils@0.13.0': {}
|
||||
|
||||
'@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.1.11)(@vue/compiler-dom@3.5.21)(vue-i18n@10.0.8(vue@3.5.21(typescript@5.8.3)))(vue@3.5.21(typescript@5.8.3))':
|
||||
'@intlify/vue-i18n-extensions@8.0.0(@intlify/shared@11.1.11)(@vue/compiler-dom@3.5.22)(vue-i18n@10.0.8(vue@3.5.22(typescript@5.8.3)))(vue@3.5.22(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.0
|
||||
optionalDependencies:
|
||||
'@intlify/shared': 11.1.11
|
||||
'@vue/compiler-dom': 3.5.21
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
vue-i18n: 10.0.8(vue@3.5.21(typescript@5.8.3))
|
||||
'@vue/compiler-dom': 3.5.22
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
vue-i18n: 10.0.8(vue@3.5.22(typescript@5.8.3))
|
||||
|
||||
'@ioredis/commands@1.3.0': {}
|
||||
|
||||
@ -6811,12 +6814,12 @@ snapshots:
|
||||
prompts: 2.4.2
|
||||
semver: 7.7.2
|
||||
|
||||
'@nuxt/devtools@2.6.2(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3))':
|
||||
'@nuxt/devtools@2.6.2(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.22(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@nuxt/devtools-kit': 2.6.2(magicast@0.3.5)(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))
|
||||
'@nuxt/devtools-wizard': 2.6.2
|
||||
'@nuxt/kit': 3.18.0(magicast@0.3.5)
|
||||
'@vue/devtools-core': 7.7.7(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3))
|
||||
'@vue/devtools-core': 7.7.7(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.22(typescript@5.8.3))
|
||||
'@vue/devtools-kit': 7.7.7
|
||||
birpc: 2.5.0
|
||||
consola: 3.4.2
|
||||
@ -6843,7 +6846,7 @@ snapshots:
|
||||
tinyglobby: 0.2.14
|
||||
vite: 7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
|
||||
vite-plugin-inspect: 11.3.2(@nuxt/kit@3.18.0(magicast@0.3.5))(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))
|
||||
vite-plugin-vue-tracer: 1.0.0(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3))
|
||||
vite-plugin-vue-tracer: 1.0.0(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.22(typescript@5.8.3))
|
||||
which: 5.0.0
|
||||
ws: 8.18.3
|
||||
transitivePeerDependencies:
|
||||
@ -6852,7 +6855,7 @@ snapshots:
|
||||
- utf-8-validate
|
||||
- vue
|
||||
|
||||
'@nuxt/eslint-config@1.7.1(@typescript-eslint/utils@8.38.0(eslint@9.31.0(jiti@2.5.1))(typescript@5.8.3))(@vue/compiler-sfc@3.5.21)(eslint@9.31.0(jiti@2.5.1))(typescript@5.8.3)':
|
||||
'@nuxt/eslint-config@1.7.1(@typescript-eslint/utils@8.38.0(eslint@9.31.0(jiti@2.5.1))(typescript@5.8.3))(@vue/compiler-sfc@3.5.22)(eslint@9.31.0(jiti@2.5.1))(typescript@5.8.3)':
|
||||
dependencies:
|
||||
'@antfu/install-pkg': 1.1.0
|
||||
'@clack/prompts': 0.11.0
|
||||
@ -6871,7 +6874,7 @@ snapshots:
|
||||
eslint-plugin-regexp: 2.9.0(eslint@9.31.0(jiti@2.5.1))
|
||||
eslint-plugin-unicorn: 60.0.0(eslint@9.31.0(jiti@2.5.1))
|
||||
eslint-plugin-vue: 10.4.0(@typescript-eslint/parser@8.38.0(eslint@9.31.0(jiti@2.5.1))(typescript@5.8.3))(eslint@9.31.0(jiti@2.5.1))(vue-eslint-parser@10.2.0(eslint@9.31.0(jiti@2.5.1)))
|
||||
eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.21)(eslint@9.31.0(jiti@2.5.1))
|
||||
eslint-processor-vue-blocks: 2.0.0(@vue/compiler-sfc@3.5.22)(eslint@9.31.0(jiti@2.5.1))
|
||||
globals: 16.3.0
|
||||
local-pkg: 1.1.1
|
||||
pathe: 2.0.3
|
||||
@ -6892,11 +6895,11 @@ snapshots:
|
||||
- supports-color
|
||||
- typescript
|
||||
|
||||
'@nuxt/eslint@1.7.1(@typescript-eslint/utils@8.38.0(eslint@9.31.0(jiti@2.5.1))(typescript@5.8.3))(@vue/compiler-sfc@3.5.21)(eslint@9.31.0(jiti@2.5.1))(magicast@0.3.5)(typescript@5.8.3)(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))':
|
||||
'@nuxt/eslint@1.7.1(@typescript-eslint/utils@8.38.0(eslint@9.31.0(jiti@2.5.1))(typescript@5.8.3))(@vue/compiler-sfc@3.5.22)(eslint@9.31.0(jiti@2.5.1))(magicast@0.3.5)(typescript@5.8.3)(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@eslint/config-inspector': 1.1.0(eslint@9.31.0(jiti@2.5.1))
|
||||
'@nuxt/devtools-kit': 2.6.2(magicast@0.3.5)(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))
|
||||
'@nuxt/eslint-config': 1.7.1(@typescript-eslint/utils@8.38.0(eslint@9.31.0(jiti@2.5.1))(typescript@5.8.3))(@vue/compiler-sfc@3.5.21)(eslint@9.31.0(jiti@2.5.1))(typescript@5.8.3)
|
||||
'@nuxt/eslint-config': 1.7.1(@typescript-eslint/utils@8.38.0(eslint@9.31.0(jiti@2.5.1))(typescript@5.8.3))(@vue/compiler-sfc@3.5.22)(eslint@9.31.0(jiti@2.5.1))(typescript@5.8.3)
|
||||
'@nuxt/eslint-plugin': 1.7.1(eslint@9.31.0(jiti@2.5.1))(typescript@5.8.3)
|
||||
'@nuxt/kit': 4.0.2(magicast@0.3.5)
|
||||
chokidar: 4.0.3
|
||||
@ -7106,12 +7109,12 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- magicast
|
||||
|
||||
'@nuxt/vite-builder@3.17.7(@types/node@22.16.5)(eslint@9.31.0(jiti@2.5.1))(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.46.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(vue-tsc@3.0.4(typescript@5.8.3))(vue@3.5.21(typescript@5.8.3))(yaml@2.8.0)':
|
||||
'@nuxt/vite-builder@3.17.7(@types/node@22.16.5)(eslint@9.31.0(jiti@2.5.1))(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.46.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(vue-tsc@3.0.4(typescript@5.8.3))(vue@3.5.22(typescript@5.8.3))(yaml@2.8.0)':
|
||||
dependencies:
|
||||
'@nuxt/kit': 3.17.7(magicast@0.3.5)
|
||||
'@rollup/plugin-replace': 6.0.2(rollup@4.46.2)
|
||||
'@vitejs/plugin-vue': 5.2.4(vite@6.3.5(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3))
|
||||
'@vitejs/plugin-vue-jsx': 4.2.0(vite@6.3.5(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3))
|
||||
'@vitejs/plugin-vue': 5.2.4(vite@6.3.5(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.22(typescript@5.8.3))
|
||||
'@vitejs/plugin-vue-jsx': 4.2.0(vite@6.3.5(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.22(typescript@5.8.3))
|
||||
autoprefixer: 10.4.21(postcss@8.5.6)
|
||||
consola: 3.4.2
|
||||
cssnano: 7.1.0(postcss@8.5.6)
|
||||
@ -7139,7 +7142,7 @@ snapshots:
|
||||
vite: 6.3.5(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
|
||||
vite-node: 3.2.4(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
|
||||
vite-plugin-checker: 0.10.1(eslint@9.31.0(jiti@2.5.1))(optionator@0.9.4)(typescript@5.8.3)(vite@6.3.5(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue-tsc@3.0.4(typescript@5.8.3))
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
vue-bundle-renderer: 2.1.2
|
||||
transitivePeerDependencies:
|
||||
- '@biomejs/biome'
|
||||
@ -7166,11 +7169,11 @@ snapshots:
|
||||
- vue-tsc
|
||||
- yaml
|
||||
|
||||
'@nuxtjs/i18n@9.5.6(@vue/compiler-dom@3.5.21)(eslint@9.31.0(jiti@2.5.1))(magicast@0.3.5)(rollup@4.46.2)(vue@3.5.21(typescript@5.8.3))':
|
||||
'@nuxtjs/i18n@9.5.6(@vue/compiler-dom@3.5.22)(eslint@9.31.0(jiti@2.5.1))(magicast@0.3.5)(rollup@4.46.2)(vue@3.5.22(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@intlify/h3': 0.6.1
|
||||
'@intlify/shared': 10.0.8
|
||||
'@intlify/unplugin-vue-i18n': 6.0.8(@vue/compiler-dom@3.5.21)(eslint@9.31.0(jiti@2.5.1))(rollup@4.46.2)(typescript@5.8.3)(vue-i18n@10.0.8(vue@3.5.21(typescript@5.8.3)))(vue@3.5.21(typescript@5.8.3))
|
||||
'@intlify/unplugin-vue-i18n': 6.0.8(@vue/compiler-dom@3.5.22)(eslint@9.31.0(jiti@2.5.1))(rollup@4.46.2)(typescript@5.8.3)(vue-i18n@10.0.8(vue@3.5.22(typescript@5.8.3)))(vue@3.5.22(typescript@5.8.3))
|
||||
'@intlify/utils': 0.13.0
|
||||
'@miyaneee/rollup-plugin-json5': 1.2.0(rollup@4.46.2)
|
||||
'@nuxt/kit': 3.18.0(magicast@0.3.5)
|
||||
@ -7190,9 +7193,9 @@ snapshots:
|
||||
typescript: 5.8.3
|
||||
ufo: 1.6.1
|
||||
unplugin: 2.3.5
|
||||
unplugin-vue-router: 0.12.0(vue-router@4.5.1(vue@3.5.21(typescript@5.8.3)))(vue@3.5.21(typescript@5.8.3))
|
||||
vue-i18n: 10.0.8(vue@3.5.21(typescript@5.8.3))
|
||||
vue-router: 4.5.1(vue@3.5.21(typescript@5.8.3))
|
||||
unplugin-vue-router: 0.12.0(vue-router@4.5.1(vue@3.5.22(typescript@5.8.3)))(vue@3.5.22(typescript@5.8.3))
|
||||
vue-i18n: 10.0.8(vue@3.5.22(typescript@5.8.3))
|
||||
vue-router: 4.5.1(vue@3.5.22(typescript@5.8.3))
|
||||
transitivePeerDependencies:
|
||||
- '@vue/compiler-dom'
|
||||
- eslint
|
||||
@ -7670,10 +7673,10 @@ snapshots:
|
||||
|
||||
'@tanstack/virtual-core@3.13.12': {}
|
||||
|
||||
'@tanstack/vue-virtual@3.13.12(vue@3.5.21(typescript@5.8.3))':
|
||||
'@tanstack/vue-virtual@3.13.12(vue@3.5.22(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@tanstack/virtual-core': 3.13.12
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
|
||||
'@tokenizer/token@0.3.0': {}
|
||||
|
||||
@ -7821,11 +7824,11 @@ snapshots:
|
||||
'@typescript-eslint/types': 8.38.0
|
||||
eslint-visitor-keys: 4.2.1
|
||||
|
||||
'@unhead/vue@2.0.13(vue@3.5.21(typescript@5.8.3))':
|
||||
'@unhead/vue@2.0.13(vue@3.5.22(typescript@5.8.3))':
|
||||
dependencies:
|
||||
hookable: 5.5.3
|
||||
unhead: 2.0.13
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
|
||||
'@unrs/resolver-binding-android-arm-eabi@1.11.1':
|
||||
optional: true
|
||||
@ -7905,21 +7908,21 @@ snapshots:
|
||||
- rollup
|
||||
- supports-color
|
||||
|
||||
'@vitejs/plugin-vue-jsx@4.2.0(vite@6.3.5(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3))':
|
||||
'@vitejs/plugin-vue-jsx@4.2.0(vite@6.3.5(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.22(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@babel/core': 7.28.0
|
||||
'@babel/plugin-transform-typescript': 7.28.0(@babel/core@7.28.0)
|
||||
'@rolldown/pluginutils': 1.0.0-beta.29
|
||||
'@vue/babel-plugin-jsx': 1.4.0(@babel/core@7.28.0)
|
||||
vite: 6.3.5(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitejs/plugin-vue@5.2.4(vite@6.3.5(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3))':
|
||||
'@vitejs/plugin-vue@5.2.4(vite@6.3.5(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.22(typescript@5.8.3))':
|
||||
dependencies:
|
||||
vite: 6.3.5(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
|
||||
'@volar/language-core@2.4.20':
|
||||
dependencies:
|
||||
@ -7933,7 +7936,7 @@ snapshots:
|
||||
path-browserify: 1.0.1
|
||||
vscode-uri: 3.1.0
|
||||
|
||||
'@vue-macros/common@1.16.1(vue@3.5.21(typescript@5.8.3))':
|
||||
'@vue-macros/common@1.16.1(vue@3.5.22(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@vue/compiler-sfc': 3.5.18
|
||||
ast-kit: 1.4.3
|
||||
@ -7942,17 +7945,17 @@ snapshots:
|
||||
pathe: 2.0.3
|
||||
picomatch: 4.0.3
|
||||
optionalDependencies:
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
|
||||
'@vue-macros/common@3.0.0-beta.15(vue@3.5.21(typescript@5.8.3))':
|
||||
'@vue-macros/common@3.0.0-beta.15(vue@3.5.22(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@vue/compiler-sfc': 3.5.21
|
||||
'@vue/compiler-sfc': 3.5.22
|
||||
ast-kit: 2.1.1
|
||||
local-pkg: 1.1.1
|
||||
magic-string-ast: 1.0.0
|
||||
unplugin-utils: 0.2.4
|
||||
optionalDependencies:
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
|
||||
'@vue/babel-helper-vue-transform-on@1.4.0': {}
|
||||
|
||||
@ -7979,7 +7982,7 @@ snapshots:
|
||||
'@babel/helper-module-imports': 7.27.1
|
||||
'@babel/helper-plugin-utils': 7.27.1
|
||||
'@babel/parser': 7.28.4
|
||||
'@vue/compiler-sfc': 3.5.21
|
||||
'@vue/compiler-sfc': 3.5.22
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@ -7991,10 +7994,10 @@ snapshots:
|
||||
estree-walker: 2.0.2
|
||||
source-map-js: 1.2.1
|
||||
|
||||
'@vue/compiler-core@3.5.21':
|
||||
'@vue/compiler-core@3.5.22':
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.4
|
||||
'@vue/shared': 3.5.21
|
||||
'@vue/shared': 3.5.22
|
||||
entities: 4.5.0
|
||||
estree-walker: 2.0.2
|
||||
source-map-js: 1.2.1
|
||||
@ -8004,10 +8007,10 @@ snapshots:
|
||||
'@vue/compiler-core': 3.5.18
|
||||
'@vue/shared': 3.5.18
|
||||
|
||||
'@vue/compiler-dom@3.5.21':
|
||||
'@vue/compiler-dom@3.5.22':
|
||||
dependencies:
|
||||
'@vue/compiler-core': 3.5.21
|
||||
'@vue/shared': 3.5.21
|
||||
'@vue/compiler-core': 3.5.22
|
||||
'@vue/shared': 3.5.22
|
||||
|
||||
'@vue/compiler-sfc@3.5.18':
|
||||
dependencies:
|
||||
@ -8021,13 +8024,13 @@ snapshots:
|
||||
postcss: 8.5.6
|
||||
source-map-js: 1.2.1
|
||||
|
||||
'@vue/compiler-sfc@3.5.21':
|
||||
'@vue/compiler-sfc@3.5.22':
|
||||
dependencies:
|
||||
'@babel/parser': 7.28.4
|
||||
'@vue/compiler-core': 3.5.21
|
||||
'@vue/compiler-dom': 3.5.21
|
||||
'@vue/compiler-ssr': 3.5.21
|
||||
'@vue/shared': 3.5.21
|
||||
'@vue/compiler-core': 3.5.22
|
||||
'@vue/compiler-dom': 3.5.22
|
||||
'@vue/compiler-ssr': 3.5.22
|
||||
'@vue/shared': 3.5.22
|
||||
estree-walker: 2.0.2
|
||||
magic-string: 0.30.19
|
||||
postcss: 8.5.6
|
||||
@ -8038,10 +8041,10 @@ snapshots:
|
||||
'@vue/compiler-dom': 3.5.18
|
||||
'@vue/shared': 3.5.18
|
||||
|
||||
'@vue/compiler-ssr@3.5.21':
|
||||
'@vue/compiler-ssr@3.5.22':
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.5.21
|
||||
'@vue/shared': 3.5.21
|
||||
'@vue/compiler-dom': 3.5.22
|
||||
'@vue/shared': 3.5.22
|
||||
|
||||
'@vue/compiler-vue2@2.7.16':
|
||||
dependencies:
|
||||
@ -8050,7 +8053,7 @@ snapshots:
|
||||
|
||||
'@vue/devtools-api@6.6.4': {}
|
||||
|
||||
'@vue/devtools-core@7.7.7(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3))':
|
||||
'@vue/devtools-core@7.7.7(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.22(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@vue/devtools-kit': 7.7.7
|
||||
'@vue/devtools-shared': 7.7.7
|
||||
@ -8058,7 +8061,7 @@ snapshots:
|
||||
nanoid: 5.1.5
|
||||
pathe: 2.0.3
|
||||
vite-hot-client: 2.1.0(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
transitivePeerDependencies:
|
||||
- vite
|
||||
|
||||
@ -8089,55 +8092,55 @@ snapshots:
|
||||
optionalDependencies:
|
||||
typescript: 5.8.3
|
||||
|
||||
'@vue/reactivity@3.5.21':
|
||||
'@vue/reactivity@3.5.22':
|
||||
dependencies:
|
||||
'@vue/shared': 3.5.21
|
||||
'@vue/shared': 3.5.22
|
||||
|
||||
'@vue/runtime-core@3.5.21':
|
||||
'@vue/runtime-core@3.5.22':
|
||||
dependencies:
|
||||
'@vue/reactivity': 3.5.21
|
||||
'@vue/shared': 3.5.21
|
||||
'@vue/reactivity': 3.5.22
|
||||
'@vue/shared': 3.5.22
|
||||
|
||||
'@vue/runtime-dom@3.5.21':
|
||||
'@vue/runtime-dom@3.5.22':
|
||||
dependencies:
|
||||
'@vue/reactivity': 3.5.21
|
||||
'@vue/runtime-core': 3.5.21
|
||||
'@vue/shared': 3.5.21
|
||||
'@vue/reactivity': 3.5.22
|
||||
'@vue/runtime-core': 3.5.22
|
||||
'@vue/shared': 3.5.22
|
||||
csstype: 3.1.3
|
||||
|
||||
'@vue/server-renderer@3.5.21(vue@3.5.21(typescript@5.8.3))':
|
||||
'@vue/server-renderer@3.5.22(vue@3.5.22(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@vue/compiler-ssr': 3.5.21
|
||||
'@vue/shared': 3.5.21
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
'@vue/compiler-ssr': 3.5.22
|
||||
'@vue/shared': 3.5.22
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
|
||||
'@vue/shared@3.5.18': {}
|
||||
|
||||
'@vue/shared@3.5.21': {}
|
||||
'@vue/shared@3.5.22': {}
|
||||
|
||||
'@vueuse/core@13.6.0(vue@3.5.21(typescript@5.8.3))':
|
||||
'@vueuse/core@13.6.0(vue@3.5.22(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@types/web-bluetooth': 0.0.21
|
||||
'@vueuse/metadata': 13.6.0
|
||||
'@vueuse/shared': 13.6.0(vue@3.5.21(typescript@5.8.3))
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
'@vueuse/shared': 13.6.0(vue@3.5.22(typescript@5.8.3))
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
|
||||
'@vueuse/metadata@13.6.0': {}
|
||||
|
||||
'@vueuse/nuxt@13.6.0(magicast@0.3.5)(nuxt@3.17.7(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.16.5)(@vue/compiler-sfc@3.5.21)(db0@0.3.2)(eslint@9.31.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.46.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue-tsc@3.0.4(typescript@5.8.3))(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3))':
|
||||
'@vueuse/nuxt@13.6.0(magicast@0.3.5)(nuxt@3.17.7(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.16.5)(@vue/compiler-sfc@3.5.22)(db0@0.3.2)(eslint@9.31.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.46.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue-tsc@3.0.4(typescript@5.8.3))(yaml@2.8.0))(vue@3.5.22(typescript@5.8.3))':
|
||||
dependencies:
|
||||
'@nuxt/kit': 4.0.2(magicast@0.3.5)
|
||||
'@vueuse/core': 13.6.0(vue@3.5.21(typescript@5.8.3))
|
||||
'@vueuse/core': 13.6.0(vue@3.5.22(typescript@5.8.3))
|
||||
'@vueuse/metadata': 13.6.0
|
||||
local-pkg: 1.1.1
|
||||
nuxt: 3.17.7(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.16.5)(@vue/compiler-sfc@3.5.21)(db0@0.3.2)(eslint@9.31.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.46.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue-tsc@3.0.4(typescript@5.8.3))(yaml@2.8.0)
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
nuxt: 3.17.7(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.16.5)(@vue/compiler-sfc@3.5.22)(db0@0.3.2)(eslint@9.31.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.46.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue-tsc@3.0.4(typescript@5.8.3))(yaml@2.8.0)
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
transitivePeerDependencies:
|
||||
- magicast
|
||||
|
||||
'@vueuse/shared@13.6.0(vue@3.5.21(typescript@5.8.3))':
|
||||
'@vueuse/shared@13.6.0(vue@3.5.22(typescript@5.8.3))':
|
||||
dependencies:
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
|
||||
'@whatwg-node/disposablestack@0.0.6':
|
||||
dependencies:
|
||||
@ -8286,7 +8289,7 @@ snapshots:
|
||||
postcss: 8.5.6
|
||||
postcss-value-parser: 4.2.0
|
||||
|
||||
axios@1.11.0:
|
||||
axios@1.12.0:
|
||||
dependencies:
|
||||
follow-redirects: 1.15.11
|
||||
form-data: 4.0.4
|
||||
@ -8846,7 +8849,7 @@ snapshots:
|
||||
detective-vue2@2.2.0(typescript@5.8.3):
|
||||
dependencies:
|
||||
'@dependents/detective-less': 5.0.1
|
||||
'@vue/compiler-sfc': 3.5.21
|
||||
'@vue/compiler-sfc': 3.5.22
|
||||
detective-es6: 5.0.1
|
||||
detective-sass: 6.0.1
|
||||
detective-scss: 5.0.1
|
||||
@ -9146,9 +9149,9 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@typescript-eslint/parser': 8.38.0(eslint@9.31.0(jiti@2.5.1))(typescript@5.8.3)
|
||||
|
||||
eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.21)(eslint@9.31.0(jiti@2.5.1)):
|
||||
eslint-processor-vue-blocks@2.0.0(@vue/compiler-sfc@3.5.22)(eslint@9.31.0(jiti@2.5.1)):
|
||||
dependencies:
|
||||
'@vue/compiler-sfc': 3.5.21
|
||||
'@vue/compiler-sfc': 3.5.22
|
||||
eslint: 9.31.0(jiti@2.5.1)
|
||||
|
||||
eslint-scope@8.4.0:
|
||||
@ -10499,16 +10502,16 @@ snapshots:
|
||||
- rollup
|
||||
- supports-color
|
||||
|
||||
nuxt@3.17.7(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.16.5)(@vue/compiler-sfc@3.5.21)(db0@0.3.2)(eslint@9.31.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.46.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue-tsc@3.0.4(typescript@5.8.3))(yaml@2.8.0):
|
||||
nuxt@3.17.7(@netlify/blobs@9.1.2)(@parcel/watcher@2.5.1)(@types/node@22.16.5)(@vue/compiler-sfc@3.5.22)(db0@0.3.2)(eslint@9.31.0(jiti@2.5.1))(ioredis@5.7.0)(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.46.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue-tsc@3.0.4(typescript@5.8.3))(yaml@2.8.0):
|
||||
dependencies:
|
||||
'@nuxt/cli': 3.27.0(magicast@0.3.5)
|
||||
'@nuxt/devalue': 2.0.2
|
||||
'@nuxt/devtools': 2.6.2(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3))
|
||||
'@nuxt/devtools': 2.6.2(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.22(typescript@5.8.3))
|
||||
'@nuxt/kit': 3.17.7(magicast@0.3.5)
|
||||
'@nuxt/schema': 3.17.7
|
||||
'@nuxt/telemetry': 2.6.6(magicast@0.3.5)
|
||||
'@nuxt/vite-builder': 3.17.7(@types/node@22.16.5)(eslint@9.31.0(jiti@2.5.1))(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.46.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(vue-tsc@3.0.4(typescript@5.8.3))(vue@3.5.21(typescript@5.8.3))(yaml@2.8.0)
|
||||
'@unhead/vue': 2.0.13(vue@3.5.21(typescript@5.8.3))
|
||||
'@nuxt/vite-builder': 3.17.7(@types/node@22.16.5)(eslint@9.31.0(jiti@2.5.1))(lightningcss@1.30.1)(magicast@0.3.5)(optionator@0.9.4)(rollup@4.46.2)(sass@1.89.2)(terser@5.43.1)(typescript@5.8.3)(vue-tsc@3.0.4(typescript@5.8.3))(vue@3.5.22(typescript@5.8.3))(yaml@2.8.0)
|
||||
'@unhead/vue': 2.0.13(vue@3.5.22(typescript@5.8.3))
|
||||
'@vue/shared': 3.5.18
|
||||
c12: 3.2.0(magicast@0.3.5)
|
||||
chokidar: 4.0.3
|
||||
@ -10555,13 +10558,13 @@ snapshots:
|
||||
unctx: 2.4.1
|
||||
unimport: 5.2.0
|
||||
unplugin: 2.3.5
|
||||
unplugin-vue-router: 0.14.0(@vue/compiler-sfc@3.5.21)(vue-router@4.5.1(vue@3.5.21(typescript@5.8.3)))(vue@3.5.21(typescript@5.8.3))
|
||||
unplugin-vue-router: 0.14.0(@vue/compiler-sfc@3.5.22)(vue-router@4.5.1(vue@3.5.22(typescript@5.8.3)))(vue@3.5.22(typescript@5.8.3))
|
||||
unstorage: 1.16.1(@netlify/blobs@9.1.2)(db0@0.3.2)(ioredis@5.7.0)
|
||||
untyped: 2.0.0
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
vue-bundle-renderer: 2.1.2
|
||||
vue-devtools-stub: 0.1.0
|
||||
vue-router: 4.5.1(vue@3.5.21(typescript@5.8.3))
|
||||
vue-router: 4.5.1(vue@3.5.22(typescript@5.8.3))
|
||||
optionalDependencies:
|
||||
'@parcel/watcher': 2.5.1
|
||||
'@types/node': 22.16.5
|
||||
@ -11858,10 +11861,10 @@ snapshots:
|
||||
pathe: 2.0.3
|
||||
picomatch: 4.0.3
|
||||
|
||||
unplugin-vue-router@0.12.0(vue-router@4.5.1(vue@3.5.21(typescript@5.8.3)))(vue@3.5.21(typescript@5.8.3)):
|
||||
unplugin-vue-router@0.12.0(vue-router@4.5.1(vue@3.5.22(typescript@5.8.3)))(vue@3.5.22(typescript@5.8.3)):
|
||||
dependencies:
|
||||
'@babel/types': 7.28.2
|
||||
'@vue-macros/common': 1.16.1(vue@3.5.21(typescript@5.8.3))
|
||||
'@vue-macros/common': 1.16.1(vue@3.5.22(typescript@5.8.3))
|
||||
ast-walker-scope: 0.6.2
|
||||
chokidar: 4.0.3
|
||||
fast-glob: 3.3.3
|
||||
@ -11876,14 +11879,14 @@ snapshots:
|
||||
unplugin-utils: 0.2.4
|
||||
yaml: 2.8.0
|
||||
optionalDependencies:
|
||||
vue-router: 4.5.1(vue@3.5.21(typescript@5.8.3))
|
||||
vue-router: 4.5.1(vue@3.5.22(typescript@5.8.3))
|
||||
transitivePeerDependencies:
|
||||
- vue
|
||||
|
||||
unplugin-vue-router@0.14.0(@vue/compiler-sfc@3.5.21)(vue-router@4.5.1(vue@3.5.21(typescript@5.8.3)))(vue@3.5.21(typescript@5.8.3)):
|
||||
unplugin-vue-router@0.14.0(@vue/compiler-sfc@3.5.22)(vue-router@4.5.1(vue@3.5.22(typescript@5.8.3)))(vue@3.5.22(typescript@5.8.3)):
|
||||
dependencies:
|
||||
'@vue-macros/common': 3.0.0-beta.15(vue@3.5.21(typescript@5.8.3))
|
||||
'@vue/compiler-sfc': 3.5.21
|
||||
'@vue-macros/common': 3.0.0-beta.15(vue@3.5.22(typescript@5.8.3))
|
||||
'@vue/compiler-sfc': 3.5.22
|
||||
ast-walker-scope: 0.8.1
|
||||
chokidar: 4.0.3
|
||||
fast-glob: 3.3.3
|
||||
@ -11898,7 +11901,7 @@ snapshots:
|
||||
unplugin-utils: 0.2.4
|
||||
yaml: 2.8.0
|
||||
optionalDependencies:
|
||||
vue-router: 4.5.1(vue@3.5.21(typescript@5.8.3))
|
||||
vue-router: 4.5.1(vue@3.5.22(typescript@5.8.3))
|
||||
transitivePeerDependencies:
|
||||
- vue
|
||||
|
||||
@ -12077,7 +12080,7 @@ snapshots:
|
||||
tinyglobby: 0.2.14
|
||||
vite: 7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
|
||||
|
||||
vite-plugin-vue-tracer@1.0.0(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.21(typescript@5.8.3)):
|
||||
vite-plugin-vue-tracer@1.0.0(vite@7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0))(vue@3.5.22(typescript@5.8.3)):
|
||||
dependencies:
|
||||
estree-walker: 3.0.3
|
||||
exsolve: 1.0.7
|
||||
@ -12085,7 +12088,7 @@ snapshots:
|
||||
pathe: 2.0.3
|
||||
source-map-js: 1.2.1
|
||||
vite: 7.0.6(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0)
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
|
||||
vite@6.3.5(@types/node@22.16.5)(jiti@2.5.1)(lightningcss@1.30.1)(sass@1.89.2)(terser@5.43.1)(yaml@2.8.0):
|
||||
dependencies:
|
||||
@ -12141,17 +12144,17 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
vue-i18n@10.0.8(vue@3.5.21(typescript@5.8.3)):
|
||||
vue-i18n@10.0.8(vue@3.5.22(typescript@5.8.3)):
|
||||
dependencies:
|
||||
'@intlify/core-base': 10.0.8
|
||||
'@intlify/shared': 10.0.8
|
||||
'@vue/devtools-api': 6.6.4
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
|
||||
vue-router@4.5.1(vue@3.5.21(typescript@5.8.3)):
|
||||
vue-router@4.5.1(vue@3.5.22(typescript@5.8.3)):
|
||||
dependencies:
|
||||
'@vue/devtools-api': 6.6.4
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
|
||||
vue-tsc@3.0.4(typescript@5.8.3):
|
||||
dependencies:
|
||||
@ -12159,36 +12162,36 @@ snapshots:
|
||||
'@vue/language-core': 3.0.4(typescript@5.8.3)
|
||||
typescript: 5.8.3
|
||||
|
||||
vue3-carousel-nuxt@1.1.6(magicast@0.3.5)(vue@3.5.21(typescript@5.8.3)):
|
||||
vue3-carousel-nuxt@1.1.6(magicast@0.3.5)(vue@3.5.22(typescript@5.8.3)):
|
||||
dependencies:
|
||||
'@nuxt/kit': 3.18.0(magicast@0.3.5)
|
||||
vue3-carousel: 0.15.1(vue@3.5.21(typescript@5.8.3))
|
||||
vue3-carousel: 0.15.1(vue@3.5.22(typescript@5.8.3))
|
||||
transitivePeerDependencies:
|
||||
- magicast
|
||||
- vue
|
||||
|
||||
vue3-carousel@0.15.1(vue@3.5.21(typescript@5.8.3)):
|
||||
vue3-carousel@0.15.1(vue@3.5.22(typescript@5.8.3)):
|
||||
dependencies:
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
|
||||
vue3-carousel@0.16.0(vue@3.5.21(typescript@5.8.3)):
|
||||
vue3-carousel@0.16.0(vue@3.5.22(typescript@5.8.3)):
|
||||
dependencies:
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
|
||||
vue@3.5.21(typescript@5.8.3):
|
||||
vue@3.5.22(typescript@5.8.3):
|
||||
dependencies:
|
||||
'@vue/compiler-dom': 3.5.21
|
||||
'@vue/compiler-sfc': 3.5.21
|
||||
'@vue/runtime-dom': 3.5.21
|
||||
'@vue/server-renderer': 3.5.21(vue@3.5.21(typescript@5.8.3))
|
||||
'@vue/shared': 3.5.21
|
||||
'@vue/compiler-dom': 3.5.22
|
||||
'@vue/compiler-sfc': 3.5.22
|
||||
'@vue/runtime-dom': 3.5.22
|
||||
'@vue/server-renderer': 3.5.22(vue@3.5.22(typescript@5.8.3))
|
||||
'@vue/shared': 3.5.22
|
||||
optionalDependencies:
|
||||
typescript: 5.8.3
|
||||
|
||||
vuedraggable@4.1.0(vue@3.5.21(typescript@5.8.3)):
|
||||
vuedraggable@4.1.0(vue@3.5.22(typescript@5.8.3)):
|
||||
dependencies:
|
||||
sortablejs: 1.14.0
|
||||
vue: 3.5.21(typescript@5.8.3)
|
||||
vue: 3.5.22(typescript@5.8.3)
|
||||
|
||||
web-streams-polyfill@3.3.3: {}
|
||||
|
||||
|
||||
@ -1 +1,4 @@
|
||||
overrides:
|
||||
droplet: link:../../.local/share/pnpm/global/5/node_modules/@drop-oss/droplet
|
||||
|
||||
shamefullyHoist: true
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
-- AlterEnum
|
||||
ALTER TYPE "MetadataSource" ADD VALUE 'Steam';
|
||||
|
||||
-- DropIndex
|
||||
DROP INDEX "GameTag_name_idx";
|
||||
|
||||
-- CreateIndex
|
||||
CREATE INDEX "GameTag_name_idx" ON "GameTag" USING GIST ("name" gist_trgm_ops(siglen=32));
|
||||
@ -1,6 +1,7 @@
|
||||
enum MetadataSource {
|
||||
Manual
|
||||
GiantBomb
|
||||
Steam
|
||||
PCGamingWiki
|
||||
IGDB
|
||||
Metacritic
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import libraryManager from "~/server/internal/library";
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["game:delete"]);
|
||||
@ -7,11 +7,7 @@ export default defineEventHandler(async (h3) => {
|
||||
|
||||
const gameId = getRouterParam(h3, "id")!;
|
||||
|
||||
await prisma.game.delete({
|
||||
where: {
|
||||
id: gameId,
|
||||
},
|
||||
});
|
||||
libraryManager.deleteGame(gameId);
|
||||
|
||||
return {};
|
||||
});
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import type { GameVersion } from "~/prisma/client/client";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import libraryManager from "~/server/internal/library";
|
||||
@ -28,10 +29,22 @@ export default defineEventHandler(async (h3) => {
|
||||
if (!game || !game.libraryId)
|
||||
throw createError({ statusCode: 404, statusMessage: "Game ID not found" });
|
||||
|
||||
const getGameVersionSize = async (version: GameVersion) => {
|
||||
const size = await libraryManager.getGameVersionSize(
|
||||
gameId,
|
||||
version.versionName,
|
||||
);
|
||||
return { ...version, size };
|
||||
};
|
||||
const gameWithVersionSize = {
|
||||
...game,
|
||||
versions: await Promise.all(game.versions.map(getGameVersionSize)),
|
||||
};
|
||||
|
||||
const unimportedVersions = await libraryManager.fetchUnimportedGameVersions(
|
||||
game.libraryId,
|
||||
game.libraryPath,
|
||||
);
|
||||
|
||||
return { game, unimportedVersions };
|
||||
return { game: gameWithVersionSize, unimportedVersions };
|
||||
});
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { type } from "arktype";
|
||||
import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import libraryManager from "~/server/internal/library";
|
||||
|
||||
const DeleteVersion = type({
|
||||
id: "string",
|
||||
@ -20,15 +20,7 @@ export default defineEventHandler<{ body: typeof DeleteVersion }>(
|
||||
const gameId = body.id.toString();
|
||||
const version = body.versionName.toString();
|
||||
|
||||
await prisma.gameVersion.delete({
|
||||
where: {
|
||||
gameId_versionName: {
|
||||
gameId: gameId,
|
||||
versionName: version,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await libraryManager.deleteGameVersion(gameId, version);
|
||||
return {};
|
||||
},
|
||||
);
|
||||
|
||||
27
server/api/v1/admin/home/index.get.ts
Normal file
27
server/api/v1/admin/home/index.get.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import { systemConfig } from "~/server/internal/config/sys-conf";
|
||||
import libraryManager from "~/server/internal/library";
|
||||
import userStatsManager from "~/server/internal/userstats";
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["game:read"]);
|
||||
if (!allowed) throw createError({ statusCode: 403 });
|
||||
|
||||
const sources = await libraryManager.fetchLibraries();
|
||||
const userStats = await userStatsManager.getUserStats();
|
||||
|
||||
const biggestGamesCombined =
|
||||
await libraryManager.getBiggestGamesCombinedVersions(5);
|
||||
const biggestGamesLatest =
|
||||
await libraryManager.getBiggestGamesLatestVersions(5);
|
||||
|
||||
return {
|
||||
gameCount: await prisma.game.count(),
|
||||
version: systemConfig.getDropVersion(),
|
||||
userStats,
|
||||
sources,
|
||||
biggestGamesLatest,
|
||||
biggestGamesCombined,
|
||||
};
|
||||
});
|
||||
@ -2,7 +2,10 @@ import type { LibraryModel } from "~/prisma/client/models";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import libraryManager from "~/server/internal/library";
|
||||
|
||||
export type WorkingLibrarySource = LibraryModel & { working: boolean };
|
||||
export type WorkingLibrarySource = LibraryModel & {
|
||||
working: boolean;
|
||||
fsStats?: { freeSpace: number; totalSpace: number } | undefined;
|
||||
};
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, [
|
||||
|
||||
@ -3,8 +3,8 @@ import { readDropValidatedBody, throwingArktype } from "~/server/arktype";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import libraryManager from "~/server/internal/library";
|
||||
import type { WorkingLibrarySource } from "~/server/api/v1/admin/library/sources/index.get";
|
||||
import { libraryConstructors } from "~/server/plugins/05.library-init";
|
||||
import type { WorkingLibrarySource } from "./index.get";
|
||||
|
||||
const UpdateLibrarySource = type({
|
||||
id: "string",
|
||||
@ -49,8 +49,8 @@ export default defineEventHandler<{ body: typeof UpdateLibrarySource.infer }>(
|
||||
},
|
||||
});
|
||||
|
||||
await libraryManager.removeLibrary(source.id);
|
||||
await libraryManager.addLibrary(newLibrary);
|
||||
libraryManager.removeLibrary(source.id);
|
||||
libraryManager.addLibrary(newLibrary);
|
||||
|
||||
const workingSource: WorkingLibrarySource = {
|
||||
...updatedSource,
|
||||
|
||||
@ -6,7 +6,7 @@ import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import libraryManager from "~/server/internal/library";
|
||||
import { libraryConstructors } from "~/server/plugins/05.library-init";
|
||||
import type { WorkingLibrarySource } from "./index.get";
|
||||
import type { WorkingLibrarySource } from "~/server/api/v1/admin/library/sources/index.get";
|
||||
|
||||
const CreateLibrarySource = type({
|
||||
name: "string",
|
||||
@ -52,11 +52,12 @@ export default defineEventHandler<{ body: typeof CreateLibrarySource.infer }>(
|
||||
},
|
||||
});
|
||||
|
||||
await libraryManager.addLibrary(library);
|
||||
libraryManager.addLibrary(library);
|
||||
|
||||
const workingSource: WorkingLibrarySource = {
|
||||
...source,
|
||||
working: true,
|
||||
fsStats: library.fsStats(),
|
||||
};
|
||||
|
||||
return workingSource;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { defineEventHandler, createError } from "h3";
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import userStatsManager from "~/server/internal/userstats";
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const allowed = await aclManager.allowSystemACL(h3, ["user:delete"]);
|
||||
@ -27,5 +28,6 @@ export default defineEventHandler(async (h3) => {
|
||||
throw createError({ statusCode: 404, statusMessage: "User not found." });
|
||||
|
||||
await prisma.user.delete({ where: { id: userId } });
|
||||
await userStatsManager.deleteUser();
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
@ -6,6 +6,7 @@ import objectHandler from "~/server/internal/objects";
|
||||
import { type } from "arktype";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { throwingArktype } from "~/server/arktype";
|
||||
import userStatsManager from "~/server/internal/userstats";
|
||||
|
||||
export const SharedRegisterValidator = type({
|
||||
username: "string >= 5",
|
||||
@ -86,5 +87,6 @@ export default defineEventHandler<{
|
||||
prisma.invitation.delete({ where: { id: user.invitation } }),
|
||||
]);
|
||||
|
||||
await userStatsManager.addUser();
|
||||
return linkMec.user;
|
||||
});
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { defineClientEventHandler } from "~/server/internal/clients/event-handler";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import libraryManager from "~/server/internal/library";
|
||||
|
||||
export default defineClientEventHandler(async (h3) => {
|
||||
const query = getQuery(h3);
|
||||
@ -26,5 +27,8 @@ export default defineClientEventHandler(async (h3) => {
|
||||
statusMessage: "Game version not found",
|
||||
});
|
||||
|
||||
return gameVersion;
|
||||
return {
|
||||
...gameVersion,
|
||||
size: libraryManager.getGameVersionSize(id, version),
|
||||
};
|
||||
});
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import aclManager from "~/server/internal/acls";
|
||||
import prisma from "~/server/internal/db/database";
|
||||
import libraryManager from "~/server/internal/library";
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
const userId = await aclManager.getUserIdACL(h3, ["store:read"]);
|
||||
@ -51,5 +52,7 @@ export default defineEventHandler(async (h3) => {
|
||||
},
|
||||
});
|
||||
|
||||
return { game, rating };
|
||||
const size = await libraryManager.getGameVersionSize(game.id);
|
||||
|
||||
return { game, rating, size };
|
||||
});
|
||||
|
||||
@ -18,7 +18,8 @@ const StoreRead = type({
|
||||
company: "string?",
|
||||
companyActions: "string = 'published,developed'",
|
||||
|
||||
sort: "'default' | 'newest' | 'recent' = 'default'",
|
||||
sort: "'default' | 'newest' | 'recent' | 'name' = 'default'",
|
||||
order: "'asc' | 'desc' = 'desc'",
|
||||
});
|
||||
|
||||
export default defineEventHandler(async (h3) => {
|
||||
@ -101,10 +102,13 @@ export default defineEventHandler(async (h3) => {
|
||||
switch (options.sort) {
|
||||
case "default":
|
||||
case "newest":
|
||||
sort.mReleased = "desc";
|
||||
sort.mReleased = options.order;
|
||||
break;
|
||||
case "recent":
|
||||
sort.created = "desc";
|
||||
sort.created = options.order;
|
||||
break;
|
||||
case "name":
|
||||
sort.mName = options.order;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@ -8,6 +8,7 @@ import type {
|
||||
} from "./capabilities";
|
||||
import capabilityManager from "./capabilities";
|
||||
import type { PeerImpl } from "../tasks";
|
||||
import userStatsManager from "~/server/internal/userstats";
|
||||
|
||||
export enum AuthMode {
|
||||
Callback = "callback",
|
||||
@ -136,7 +137,7 @@ export class ClientHandler {
|
||||
statusCode: 400,
|
||||
statusMessage: "Client has not connected yet. Please try again later.",
|
||||
});
|
||||
await client.peer.send(
|
||||
client.peer.send(
|
||||
JSON.stringify({ type: "token", value: `${clientId}/${token}` }),
|
||||
);
|
||||
}
|
||||
@ -166,6 +167,7 @@ export class ClientHandler {
|
||||
lastConnected: new Date(),
|
||||
},
|
||||
});
|
||||
await userStatsManager.cacheUserSessions();
|
||||
|
||||
for (const [capability, configuration] of Object.entries(
|
||||
metadata.data.capabilities,
|
||||
@ -191,6 +193,7 @@ export class ClientHandler {
|
||||
id,
|
||||
},
|
||||
});
|
||||
await userStatsManager.cacheUserStats();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import type { GameVersionModel } from "~/prisma/client/models";
|
||||
import prisma from "../db/database";
|
||||
import { sum } from "~/utils/array";
|
||||
|
||||
export type DropChunk = {
|
||||
permissions: number;
|
||||
@ -102,6 +103,14 @@ class ManifestGenerator {
|
||||
|
||||
return manifest;
|
||||
}
|
||||
|
||||
calculateManifestSize(manifest: DropManifest) {
|
||||
return sum(
|
||||
Object.values(manifest)
|
||||
.map((chunk) => chunk.lengths)
|
||||
.flat(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export const manifestGenerator = new ManifestGenerator();
|
||||
|
||||
236
server/internal/gamesize/index.ts
Normal file
236
server/internal/gamesize/index.ts
Normal file
@ -0,0 +1,236 @@
|
||||
import cacheHandler from "../cache";
|
||||
import prisma from "../db/database";
|
||||
import manifestGenerator from "../downloads/manifest";
|
||||
import { sum } from "../../../utils/array";
|
||||
import type { Game, GameVersion } from "~/prisma/client/client";
|
||||
|
||||
export type GameSize = {
|
||||
gameName: string;
|
||||
size: number;
|
||||
gameId: string;
|
||||
};
|
||||
|
||||
export type VersionSize = GameSize & {
|
||||
latest: boolean;
|
||||
};
|
||||
|
||||
type VersionsSizes = {
|
||||
[versionName: string]: VersionSize;
|
||||
};
|
||||
|
||||
type GameVersionsSize = {
|
||||
[gameId: string]: VersionsSizes;
|
||||
};
|
||||
|
||||
class GameSizeManager {
|
||||
private gameVersionsSizesCache =
|
||||
cacheHandler.createCache<GameVersionsSize>("gameVersionsSizes");
|
||||
// All versions sizes combined
|
||||
private gameSizesCache = cacheHandler.createCache<GameSize>("gameSizes");
|
||||
|
||||
private async clearGameVersionsSizesCache() {
|
||||
(await this.gameVersionsSizesCache.getKeys()).map((key) =>
|
||||
this.gameVersionsSizesCache.remove(key),
|
||||
);
|
||||
}
|
||||
|
||||
private async clearGameSizesCache() {
|
||||
(await this.gameSizesCache.getKeys()).map((key) =>
|
||||
this.gameSizesCache.remove(key),
|
||||
);
|
||||
}
|
||||
|
||||
// All versions of a game combined
|
||||
async getCombinedGameSize(gameId: string) {
|
||||
const versions = await prisma.gameVersion.findMany({
|
||||
where: { gameId },
|
||||
});
|
||||
const sizes = await Promise.all(
|
||||
versions.map((version) =>
|
||||
manifestGenerator.calculateManifestSize(
|
||||
JSON.parse(version.dropletManifest as string),
|
||||
),
|
||||
),
|
||||
);
|
||||
return sum(sizes);
|
||||
}
|
||||
|
||||
async getGameVersionSize(
|
||||
gameId: string,
|
||||
versionName?: string,
|
||||
): Promise<number | null> {
|
||||
if (!versionName) {
|
||||
const version = await prisma.gameVersion.findFirst({
|
||||
where: { gameId },
|
||||
orderBy: {
|
||||
versionIndex: "desc",
|
||||
},
|
||||
});
|
||||
if (!version) {
|
||||
return null;
|
||||
}
|
||||
versionName = version.versionName;
|
||||
}
|
||||
|
||||
const manifest = await manifestGenerator.generateManifest(
|
||||
gameId,
|
||||
versionName,
|
||||
);
|
||||
if (!manifest) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return manifestGenerator.calculateManifestSize(manifest);
|
||||
}
|
||||
|
||||
private async isLatestVersion(
|
||||
gameVersions: GameVersion[],
|
||||
version: GameVersion,
|
||||
): Promise<boolean> {
|
||||
return gameVersions.length > 0
|
||||
? gameVersions[0].versionName === version.versionName
|
||||
: false;
|
||||
}
|
||||
|
||||
async getBiggestGamesLatestVersion(top: number): Promise<VersionSize[]> {
|
||||
const gameIds = await this.gameVersionsSizesCache.getKeys();
|
||||
const latestGames = await Promise.all(
|
||||
gameIds.map(async (gameId) => {
|
||||
const versionsSizes = await this.gameVersionsSizesCache.get(gameId);
|
||||
if (!versionsSizes) {
|
||||
return null;
|
||||
}
|
||||
const latestVersionName = Object.keys(versionsSizes).find(
|
||||
(versionName) => versionsSizes[versionName].latest,
|
||||
);
|
||||
if (!latestVersionName) {
|
||||
return null;
|
||||
}
|
||||
return versionsSizes[latestVersionName] || null;
|
||||
}),
|
||||
);
|
||||
return latestGames
|
||||
.filter((game) => game !== null)
|
||||
.sort((gameA, gameB) => gameB.size - gameA.size)
|
||||
.slice(0, top);
|
||||
}
|
||||
|
||||
async isGameVersionsSizesCacheEmpty() {
|
||||
return (await this.gameVersionsSizesCache.getKeys()).length === 0;
|
||||
}
|
||||
|
||||
async isGameSizesCacheEmpty() {
|
||||
return (await this.gameSizesCache.getKeys()).length === 0;
|
||||
}
|
||||
|
||||
async cacheAllCombinedGames() {
|
||||
await this.clearGameSizesCache();
|
||||
const games = await prisma.game.findMany({ include: { versions: true } });
|
||||
|
||||
await Promise.all(games.map((game) => this.cacheCombinedGame(game)));
|
||||
}
|
||||
|
||||
async cacheCombinedGame(game: Game) {
|
||||
const size = await this.getCombinedGameSize(game.id);
|
||||
if (!size) {
|
||||
this.gameSizesCache.remove(game.id);
|
||||
return;
|
||||
}
|
||||
const gameSize = {
|
||||
size,
|
||||
gameName: game.mName,
|
||||
gameId: game.id,
|
||||
};
|
||||
await this.gameSizesCache.set(game.id, gameSize);
|
||||
}
|
||||
|
||||
async cacheAllGameVersions() {
|
||||
await this.clearGameVersionsSizesCache();
|
||||
const games = await prisma.game.findMany({
|
||||
include: {
|
||||
versions: {
|
||||
orderBy: {
|
||||
versionIndex: "desc",
|
||||
},
|
||||
take: 1,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await Promise.all(games.map((game) => this.cacheGameVersion(game)));
|
||||
}
|
||||
|
||||
async cacheGameVersion(
|
||||
game: Game & { versions: GameVersion[] },
|
||||
versionName?: string,
|
||||
) {
|
||||
const cacheVersion = async (version: GameVersion) => {
|
||||
const size = await this.getGameVersionSize(game.id, version.versionName);
|
||||
if (!version.versionName || !size) {
|
||||
return;
|
||||
}
|
||||
|
||||
const versionsSizes = {
|
||||
[version.versionName]: {
|
||||
size,
|
||||
gameName: game.mName,
|
||||
gameId: game.id,
|
||||
latest: await this.isLatestVersion(game.versions, version),
|
||||
},
|
||||
};
|
||||
const allVersionsSizes =
|
||||
(await this.gameVersionsSizesCache.get(game.id)) || {};
|
||||
await this.gameVersionsSizesCache.set(game.id, {
|
||||
...allVersionsSizes,
|
||||
...versionsSizes,
|
||||
});
|
||||
};
|
||||
|
||||
if (versionName) {
|
||||
const version = await prisma.gameVersion.findFirst({
|
||||
where: { gameId: game.id, versionName },
|
||||
});
|
||||
if (!version) {
|
||||
return;
|
||||
}
|
||||
cacheVersion(version);
|
||||
return;
|
||||
}
|
||||
if ("versions" in game) {
|
||||
await Promise.all(game.versions.map(cacheVersion));
|
||||
}
|
||||
}
|
||||
|
||||
async getBiggestGamesAllVersions(top: number): Promise<GameSize[]> {
|
||||
const gameIds = await this.gameSizesCache.getKeys();
|
||||
const allGames = await Promise.all(
|
||||
gameIds.map(async (gameId) => await this.gameSizesCache.get(gameId)),
|
||||
);
|
||||
return allGames
|
||||
.filter((game) => game !== null)
|
||||
.sort((gameA, gameB) => gameB.size - gameA.size)
|
||||
.slice(0, top);
|
||||
}
|
||||
|
||||
async deleteGameVersion(gameId: string, version: string) {
|
||||
const game = await prisma.game.findFirst({ where: { id: gameId } });
|
||||
if (game) {
|
||||
await this.cacheCombinedGame(game);
|
||||
}
|
||||
const versionsSizes = await this.gameVersionsSizesCache.get(gameId);
|
||||
if (!versionsSizes) {
|
||||
return;
|
||||
}
|
||||
// Remove the version from the VersionsSizes object
|
||||
const { [version]: _, ...updatedVersionsSizes } = versionsSizes;
|
||||
await this.gameVersionsSizesCache.set(gameId, updatedVersionsSizes);
|
||||
}
|
||||
|
||||
async deleteGame(gameId: string) {
|
||||
this.gameSizesCache.remove(gameId);
|
||||
this.gameVersionsSizesCache.remove(gameId);
|
||||
}
|
||||
}
|
||||
|
||||
export const manager = new GameSizeManager();
|
||||
export default manager;
|
||||
@ -15,6 +15,8 @@ import { GameNotFoundError, type LibraryProvider } from "./provider";
|
||||
import { logger } from "../logging";
|
||||
import type { GameModel } from "~/prisma/client/models";
|
||||
import { createHash } from "node:crypto";
|
||||
import type { WorkingLibrarySource } from "~/server/api/v1/admin/library/sources/index.get";
|
||||
import gameSizeManager from "~/server/internal/gamesize";
|
||||
|
||||
export function createGameImportTaskId(libraryId: string, libraryPath: string) {
|
||||
return createHash("md5")
|
||||
@ -39,13 +41,19 @@ class LibraryManager {
|
||||
this.libraries.delete(id);
|
||||
}
|
||||
|
||||
async fetchLibraries() {
|
||||
async fetchLibraries(): Promise<WorkingLibrarySource[]> {
|
||||
const libraries = await prisma.library.findMany({});
|
||||
const libraryWithMetadata = libraries.map((e) => ({
|
||||
...e,
|
||||
working: this.libraries.has(e.id),
|
||||
}));
|
||||
return libraryWithMetadata;
|
||||
|
||||
const libraryWithMetadata = libraries.map(async (library) => {
|
||||
const theLibrary = this.libraries.get(library.id);
|
||||
const working = this.libraries.has(library.id);
|
||||
return {
|
||||
...library,
|
||||
working,
|
||||
fsStats: working ? theLibrary?.fsStats() : undefined,
|
||||
};
|
||||
});
|
||||
return await Promise.all(libraryWithMetadata);
|
||||
}
|
||||
|
||||
async fetchGamesByLibrary() {
|
||||
@ -334,6 +342,8 @@ class LibraryManager {
|
||||
acls: ["system:import:version:read"],
|
||||
});
|
||||
|
||||
await libraryManager.cacheCombinedGameSize(gameId);
|
||||
await libraryManager.cacheGameVersionSize(gameId, versionName);
|
||||
progress(100);
|
||||
},
|
||||
});
|
||||
@ -363,6 +373,68 @@ class LibraryManager {
|
||||
if (!library) return undefined;
|
||||
return await library.readFile(game, version, filename, options);
|
||||
}
|
||||
|
||||
async deleteGameVersion(gameId: string, version: string) {
|
||||
await prisma.gameVersion.delete({
|
||||
where: {
|
||||
gameId_versionName: {
|
||||
gameId: gameId,
|
||||
versionName: version,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
await gameSizeManager.deleteGameVersion(gameId, version);
|
||||
}
|
||||
|
||||
async deleteGame(gameId: string) {
|
||||
await prisma.game.delete({
|
||||
where: {
|
||||
id: gameId,
|
||||
},
|
||||
});
|
||||
gameSizeManager.deleteGame(gameId);
|
||||
}
|
||||
|
||||
async getGameVersionSize(
|
||||
gameId: string,
|
||||
versionName?: string,
|
||||
): Promise<number | null> {
|
||||
return gameSizeManager.getGameVersionSize(gameId, versionName);
|
||||
}
|
||||
|
||||
async getBiggestGamesCombinedVersions(top: number) {
|
||||
if (await gameSizeManager.isGameSizesCacheEmpty()) {
|
||||
await gameSizeManager.cacheAllCombinedGames();
|
||||
}
|
||||
return gameSizeManager.getBiggestGamesAllVersions(top);
|
||||
}
|
||||
|
||||
async getBiggestGamesLatestVersions(top: number) {
|
||||
if (await gameSizeManager.isGameVersionsSizesCacheEmpty()) {
|
||||
await gameSizeManager.cacheAllGameVersions();
|
||||
}
|
||||
return gameSizeManager.getBiggestGamesLatestVersion(top);
|
||||
}
|
||||
|
||||
async cacheCombinedGameSize(gameId: string) {
|
||||
const game = await prisma.game.findFirst({ where: { id: gameId } });
|
||||
if (!game) {
|
||||
return;
|
||||
}
|
||||
await gameSizeManager.cacheCombinedGame(game);
|
||||
}
|
||||
|
||||
async cacheGameVersionSize(gameId: string, versionName: string) {
|
||||
const game = await prisma.game.findFirst({
|
||||
where: { id: gameId },
|
||||
include: { versions: true },
|
||||
});
|
||||
if (!game) {
|
||||
return;
|
||||
}
|
||||
await gameSizeManager.cacheGameVersion(game, versionName);
|
||||
}
|
||||
}
|
||||
|
||||
export const libraryManager = new LibraryManager();
|
||||
|
||||
@ -57,6 +57,8 @@ export abstract class LibraryProvider<CFG> {
|
||||
filename: string,
|
||||
options?: { start?: number; end?: number },
|
||||
): Promise<ReadableStream | undefined>;
|
||||
|
||||
abstract fsStats(): { freeSpace: number; totalSpace: number } | undefined;
|
||||
}
|
||||
|
||||
export class GameNotFoundError extends Error {}
|
||||
|
||||
@ -8,6 +8,7 @@ import { LibraryBackend } from "~/prisma/client/enums";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import droplet, { DropletHandler } from "@drop-oss/droplet";
|
||||
import { fsStats } from "~/server/internal/utils/files";
|
||||
|
||||
export const FilesystemProviderConfig = type({
|
||||
baseDir: "string",
|
||||
@ -122,4 +123,8 @@ export class FilesystemProvider
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
fsStats() {
|
||||
return fsStats(this.config.baseDir);
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,6 +6,7 @@ import fs from "fs";
|
||||
import path from "path";
|
||||
import droplet from "@drop-oss/droplet";
|
||||
import { DROPLET_HANDLER } from "./filesystem";
|
||||
import { fsStats } from "~/server/internal/utils/files";
|
||||
|
||||
export const FlatFilesystemProviderConfig = type({
|
||||
baseDir: "string",
|
||||
@ -113,4 +114,8 @@ export class FlatFilesystemProvider
|
||||
|
||||
return stream.getStream();
|
||||
}
|
||||
|
||||
fsStats() {
|
||||
return fsStats(this.config.baseDir);
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,7 +191,7 @@ export class GiantBombProvider implements MetadataProvider {
|
||||
|
||||
const res = await publisher(pub.name);
|
||||
if (res === undefined) {
|
||||
context?.logger.warn(`Failed to import publisher "${pub}"`);
|
||||
context?.logger.warn(`Failed to import publisher "${pub.name}"`);
|
||||
continue;
|
||||
}
|
||||
context?.logger.info(`Imported publisher "${pub.name}"`);
|
||||
@ -208,10 +208,10 @@ export class GiantBombProvider implements MetadataProvider {
|
||||
|
||||
const res = await developer(dev.name);
|
||||
if (res === undefined) {
|
||||
context?.logger.warn(`Failed to import developer "${dev}"`);
|
||||
context?.logger.warn(`Failed to import developer "${dev.name}"`);
|
||||
continue;
|
||||
}
|
||||
context?.logger.info(`Imported developer "${dev}"`);
|
||||
context?.logger.info(`Imported developer "${dev.name}"`);
|
||||
developers.push(res);
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,7 +72,7 @@ interface IGDBCompanyWebsite extends IGDBItem {
|
||||
}
|
||||
|
||||
interface IGDBCover extends IGDBItem {
|
||||
url: string;
|
||||
image_id: string;
|
||||
}
|
||||
|
||||
interface IGDBSearchStub extends IGDBItem {
|
||||
@ -179,7 +179,7 @@ export class IGDBProvider implements MetadataProvider {
|
||||
|
||||
if (response.status !== 200)
|
||||
throw new Error(
|
||||
`Error in IDGB \nStatus Code: ${response.status}\n${response.data}`,
|
||||
`Error in IGDB \nStatus Code: ${response.status}\n${response.data}`,
|
||||
);
|
||||
|
||||
this.accessToken = response.data.access_token;
|
||||
@ -187,7 +187,7 @@ export class IGDBProvider implements MetadataProvider {
|
||||
seconds: response.data.expires_in,
|
||||
});
|
||||
|
||||
logger.info("IDGB done authorizing with twitch");
|
||||
logger.info("IGDB done authorizing with twitch");
|
||||
}
|
||||
|
||||
private async refreshCredentials() {
|
||||
@ -246,39 +246,47 @@ export class IGDBProvider implements MetadataProvider {
|
||||
return <T[]>response.data;
|
||||
}
|
||||
|
||||
private async _getMediaInternal(mediaID: IGDBID, type: string) {
|
||||
private async _getMediaInternal(
|
||||
mediaID: IGDBID,
|
||||
type: string,
|
||||
size: string = "t_thumb",
|
||||
) {
|
||||
if (mediaID === undefined)
|
||||
throw new Error(
|
||||
`IGDB mediaID when getting item of type ${type} was undefined`,
|
||||
);
|
||||
|
||||
const body = `where id = ${mediaID}; fields url;`;
|
||||
const body = `where id = ${mediaID}; fields image_id;`;
|
||||
const response = await this.request<IGDBCover>(type, body);
|
||||
|
||||
let result = "";
|
||||
if (!response.length || !response[0].image_id) {
|
||||
throw new Error(`No image_id found for ${type} with id ${mediaID}`);
|
||||
}
|
||||
|
||||
response.forEach((cover) => {
|
||||
if (cover.url.startsWith("https:")) {
|
||||
result = cover.url;
|
||||
} else {
|
||||
// twitch *sometimes* provides it in the format "//images.igdb.com"
|
||||
result = `https:${cover.url}`;
|
||||
}
|
||||
});
|
||||
const imageId = response[0].image_id;
|
||||
const result = `https://images.igdb.com/igdb/image/upload/${size}/${imageId}.jpg`;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private async getCoverURL(id: IGDBID) {
|
||||
return await this._getMediaInternal(id, "covers");
|
||||
return await this._getMediaInternal(id, "covers", "t_cover_big");
|
||||
}
|
||||
|
||||
private async getArtworkURL(id: IGDBID) {
|
||||
return await this._getMediaInternal(id, "artworks");
|
||||
return await this._getMediaInternal(id, "artworks", "t_1080p");
|
||||
}
|
||||
|
||||
private async getScreenshotURL(id: IGDBID) {
|
||||
return await this._getMediaInternal(id, "screenshots", "t_1080p");
|
||||
}
|
||||
|
||||
private async getIconURL(id: IGDBID) {
|
||||
return await this._getMediaInternal(id, "covers", "t_thumb");
|
||||
}
|
||||
|
||||
private async getCompanyLogoURl(id: IGDBID) {
|
||||
return await this._getMediaInternal(id, "company_logos");
|
||||
return await this._getMediaInternal(id, "company_logos", "t_original");
|
||||
}
|
||||
|
||||
private trimMessage(msg: string, len: number) {
|
||||
@ -327,7 +335,7 @@ export class IGDBProvider implements MetadataProvider {
|
||||
let icon = "";
|
||||
const cover = response[i].cover;
|
||||
if (cover !== undefined) {
|
||||
icon = await this.getCoverURL(cover);
|
||||
icon = await this.getIconURL(cover);
|
||||
} else {
|
||||
icon = "";
|
||||
}
|
||||
@ -355,23 +363,26 @@ export class IGDBProvider implements MetadataProvider {
|
||||
const currentGame = (await this.request<IGDBGameFull>("games", body)).at(0);
|
||||
if (!currentGame) throw new Error("No game found on IGDB with that id");
|
||||
|
||||
context?.logger.info("Using IDGB provider.");
|
||||
context?.logger.info("Using IGDB provider.");
|
||||
|
||||
let iconRaw;
|
||||
let iconRaw, coverRaw;
|
||||
const cover = currentGame.cover;
|
||||
|
||||
if (cover !== undefined) {
|
||||
context?.logger.info("Found cover URL, using...");
|
||||
iconRaw = await this.getCoverURL(cover);
|
||||
iconRaw = await this.getIconURL(cover);
|
||||
coverRaw = await this.getCoverURL(cover);
|
||||
} else {
|
||||
context?.logger.info("Missing cover URL, using fallback...");
|
||||
iconRaw = jdenticon.toPng(id, 512);
|
||||
coverRaw = iconRaw;
|
||||
}
|
||||
|
||||
const icon = createObject(iconRaw);
|
||||
const coverID = createObject(coverRaw);
|
||||
let banner;
|
||||
|
||||
const images = [icon];
|
||||
const images = [coverID];
|
||||
for (const art of currentGame.artworks ?? []) {
|
||||
const objectId = createObject(await this.getArtworkURL(art));
|
||||
if (!banner) {
|
||||
@ -384,6 +395,11 @@ export class IGDBProvider implements MetadataProvider {
|
||||
banner = createObject(jdenticon.toPng(id, 512));
|
||||
}
|
||||
|
||||
for (const screenshot of currentGame.screenshots ?? []) {
|
||||
const objectId = createObject(await this.getScreenshotURL(screenshot));
|
||||
images.push(objectId);
|
||||
}
|
||||
|
||||
context?.progress(20);
|
||||
|
||||
const publishers: CompanyModel[] = [];
|
||||
@ -452,13 +468,25 @@ export class IGDBProvider implements MetadataProvider {
|
||||
|
||||
const genres = await this.getGenres(currentGame.genres);
|
||||
|
||||
const deck = this.trimMessage(currentGame.summary, 280);
|
||||
let description = "";
|
||||
let shortDescription = "";
|
||||
|
||||
if (currentGame.summary.length > (currentGame.storyline?.length ?? 0)) {
|
||||
description = currentGame.summary;
|
||||
shortDescription = this.trimMessage(
|
||||
currentGame.storyline ?? currentGame.summary,
|
||||
280,
|
||||
);
|
||||
} else {
|
||||
description = currentGame.storyline ?? currentGame.summary;
|
||||
shortDescription = this.trimMessage(currentGame.summary, 280);
|
||||
}
|
||||
|
||||
const metadata = {
|
||||
id: currentGame.id.toString(),
|
||||
name: currentGame.name,
|
||||
shortDescription: deck,
|
||||
description: currentGame.summary,
|
||||
shortDescription,
|
||||
description,
|
||||
released,
|
||||
|
||||
genres,
|
||||
@ -471,7 +499,7 @@ export class IGDBProvider implements MetadataProvider {
|
||||
|
||||
icon,
|
||||
bannerId: banner,
|
||||
coverId: icon,
|
||||
coverId: coverID,
|
||||
images,
|
||||
};
|
||||
|
||||
|
||||
1022
server/internal/metadata/steam.ts
Normal file
1022
server/internal/metadata/steam.ts
Normal file
File diff suppressed because it is too large
Load Diff
68
server/internal/userstats/index.ts
Normal file
68
server/internal/userstats/index.ts
Normal file
@ -0,0 +1,68 @@
|
||||
/*
|
||||
Handles managing collections
|
||||
*/
|
||||
|
||||
import cacheHandler from "../cache";
|
||||
import prisma from "../db/database";
|
||||
import { DateTime } from "luxon";
|
||||
|
||||
class UserStatsManager {
|
||||
// Caches the user's core library
|
||||
private userStatsCache = cacheHandler.createCache<number>("userStats");
|
||||
|
||||
async cacheUserSessions() {
|
||||
const activeSessions =
|
||||
(
|
||||
await prisma.client.groupBy({
|
||||
by: ["userId"],
|
||||
where: {
|
||||
id: { not: "system" },
|
||||
lastConnected: {
|
||||
gt: DateTime.now().minus({ months: 1 }).toISO(),
|
||||
},
|
||||
},
|
||||
})
|
||||
).length || 0;
|
||||
await this.userStatsCache.set("activeSessions", activeSessions);
|
||||
}
|
||||
|
||||
private async cacheUserCount() {
|
||||
const userCount =
|
||||
(await prisma.user.count({
|
||||
where: { id: { not: "system" } },
|
||||
})) || 0;
|
||||
await this.userStatsCache.set("userCount", userCount);
|
||||
}
|
||||
|
||||
async cacheUserStats() {
|
||||
await this.cacheUserSessions();
|
||||
await this.cacheUserCount();
|
||||
}
|
||||
|
||||
async getUserStats() {
|
||||
let activeSessions = await this.userStatsCache.get("activeSessions");
|
||||
let userCount = await this.userStatsCache.get("userCount");
|
||||
|
||||
if (activeSessions === null || userCount === null) {
|
||||
await this.cacheUserStats();
|
||||
activeSessions = (await this.userStatsCache.get("activeSessions")) || 0;
|
||||
userCount = (await this.userStatsCache.get("userCount")) || 0;
|
||||
}
|
||||
|
||||
return { activeSessions, userCount };
|
||||
}
|
||||
|
||||
async addUser() {
|
||||
const userCount = (await this.userStatsCache.get("userCount")) || 0;
|
||||
await this.userStatsCache.set("userCount", userCount + 1);
|
||||
}
|
||||
|
||||
async deleteUser() {
|
||||
const userCount = (await this.userStatsCache.get("userCount")) || 1;
|
||||
await this.userStatsCache.set("userCount", userCount - 1);
|
||||
await this.cacheUserSessions();
|
||||
}
|
||||
}
|
||||
|
||||
export const manager = new UserStatsManager();
|
||||
export default manager;
|
||||
47
server/internal/utils/files.ts
Normal file
47
server/internal/utils/files.ts
Normal file
@ -0,0 +1,47 @@
|
||||
import fs from "fs";
|
||||
import nodePath from "path";
|
||||
|
||||
export function fsStats(folderPath: string) {
|
||||
const stats = fs.statfsSync(folderPath);
|
||||
const freeSpace = stats.bavail * stats.bsize;
|
||||
const totalSpace = stats.blocks * stats.bsize;
|
||||
return { freeSpace, totalSpace };
|
||||
}
|
||||
|
||||
export function getFolderSize(folderPath: string): number {
|
||||
const files = fs.readdirSync(folderPath, { withFileTypes: true });
|
||||
|
||||
const paths = files.map((file) => {
|
||||
const path = nodePath.join(folderPath, file.name);
|
||||
if (file.isDirectory()) {
|
||||
return getFolderSize(path);
|
||||
}
|
||||
if (file.isFile()) {
|
||||
return fs.statSync(path).size;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
|
||||
return paths
|
||||
.flat(Infinity)
|
||||
.reduce(
|
||||
(accumulator: number, currentValue: number) => accumulator + currentValue,
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
export function formatBytes(bytes: number): string {
|
||||
if (bytes < 1024) {
|
||||
return `${bytes} B`;
|
||||
}
|
||||
if (bytes >= 1024 && bytes < Math.pow(1024, 2)) {
|
||||
return `${(bytes / 1024).toFixed(2)} KiB`;
|
||||
}
|
||||
if (bytes >= Math.pow(1024, 2) && bytes < Math.pow(1024, 3)) {
|
||||
return `${(bytes / (1024 * 1024)).toFixed(2)} MiB`;
|
||||
}
|
||||
if (bytes >= Math.pow(1024, 3) && bytes < Math.pow(1024, 4)) {
|
||||
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
|
||||
}
|
||||
return `${(bytes / Math.pow(1024, 4)).toFixed(2)} TiB`;
|
||||
}
|
||||
@ -5,11 +5,13 @@ import { GiantBombProvider } from "../internal/metadata/giantbomb";
|
||||
import { IGDBProvider } from "../internal/metadata/igdb";
|
||||
import { ManualMetadataProvider } from "../internal/metadata/manual";
|
||||
import { PCGamingWikiProvider } from "../internal/metadata/pcgamingwiki";
|
||||
import { SteamProvider } from "../internal/metadata/steam";
|
||||
import { logger } from "~/server/internal/logging";
|
||||
|
||||
export default defineNitroPlugin(async (_nitro) => {
|
||||
const metadataProviders = [
|
||||
GiantBombProvider,
|
||||
SteamProvider,
|
||||
PCGamingWikiProvider,
|
||||
IGDBProvider,
|
||||
];
|
||||
|
||||
6
utils/array.ts
Normal file
6
utils/array.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export const sum = (array: number[]) =>
|
||||
array.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
|
||||
|
||||
export function lastItem<T>(array: T[]) {
|
||||
return array[array.length - 1];
|
||||
}
|
||||
76
utils/colors.ts
Normal file
76
utils/colors.ts
Normal file
@ -0,0 +1,76 @@
|
||||
export const CHART_COLOURS = {
|
||||
// Bar colours
|
||||
red: {
|
||||
fill: "fill-red-700",
|
||||
bg: "bg-red-700",
|
||||
},
|
||||
orange: {
|
||||
fill: "fill-orange-800",
|
||||
bg: "bg-orange-800",
|
||||
},
|
||||
blue: {
|
||||
fill: "fill-blue-900",
|
||||
bg: "bg-blue-900",
|
||||
},
|
||||
|
||||
// Pie colours
|
||||
lightblue: {
|
||||
fill: "fill-blue-400",
|
||||
bg: "bg-blue-400",
|
||||
},
|
||||
dropblue: {
|
||||
fill: "fill-blue-600",
|
||||
bg: "bg-blue-600",
|
||||
},
|
||||
green: {
|
||||
fill: "fill-green-500",
|
||||
bg: "bg-green-500",
|
||||
},
|
||||
yellow: {
|
||||
fill: "fill-yellow-800",
|
||||
bg: "bg-yellow-800",
|
||||
},
|
||||
purple: {
|
||||
fill: "fill-purple-500",
|
||||
bg: "bg-purple-500",
|
||||
},
|
||||
zinc: {
|
||||
fill: "fill-zinc-950",
|
||||
bg: "bg-zinc-950",
|
||||
},
|
||||
pink: {
|
||||
fill: "fill-pink-800",
|
||||
bg: "bg-pink-800",
|
||||
},
|
||||
|
||||
lime: {
|
||||
fill: "fill-lime-600",
|
||||
bg: "bg-lime-600",
|
||||
},
|
||||
emerald: {
|
||||
fill: "fill-emerald-500",
|
||||
bg: "bg-emerald-500",
|
||||
},
|
||||
slate: {
|
||||
fill: "fill-slate-800",
|
||||
bg: "bg-slate-800",
|
||||
},
|
||||
};
|
||||
export const PIE_COLOURS: ChartColour[] = [
|
||||
"lightblue",
|
||||
"dropblue",
|
||||
"purple",
|
||||
"emerald",
|
||||
];
|
||||
|
||||
export type ChartColour = keyof typeof CHART_COLOURS;
|
||||
|
||||
export function getBarColor(percentage: number): ChartColour {
|
||||
if (percentage <= 70) {
|
||||
return "blue";
|
||||
}
|
||||
if (percentage > 70 && percentage <= 90) {
|
||||
return "orange";
|
||||
}
|
||||
return "red";
|
||||
}
|
||||
13
utils/tuple.ts
Normal file
13
utils/tuple.ts
Normal file
@ -0,0 +1,13 @@
|
||||
export default class Tuple {
|
||||
x: number;
|
||||
y: number;
|
||||
|
||||
constructor(x: number, y: number) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `${this.x},${this.y}`;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user