mirror of
https://github.com/Drop-OSS/drop.git
synced 2025-11-09 20:12:10 +10:00
Compare commits
11 Commits
d7ca0b080e
...
weblate
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ce1f3124f | |||
| 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",
|
||||
|
||||
@ -3,16 +3,16 @@
|
||||
"devices": {
|
||||
"capabilities": "Capacités",
|
||||
"lastConnected": "Dernière Connexion",
|
||||
"noDevices": "Aucun appareil n'est connecté à vôtre compte.",
|
||||
"noDevices": "Aucun appareil connecté à vôtre compte.",
|
||||
"platform": "Plateforme",
|
||||
"revoke": "Révoquer",
|
||||
"subheader": "Gérer les appareils authorisés à accéder à votre compte Drop.",
|
||||
"title": "Appareils"
|
||||
},
|
||||
"notifications": {
|
||||
"all": "Tout voir {arrow}",
|
||||
"all": "Voir tout {arrow}",
|
||||
"desc": "Voir et gérer vos notifications.",
|
||||
"markAllAsRead": "Tout marqué comme lu",
|
||||
"markAllAsRead": "Marquer tout comme lu",
|
||||
"markAsRead": "Marquer comme lu",
|
||||
"none": "Pas de notification",
|
||||
"notifications": "Notifications",
|
||||
@ -62,6 +62,7 @@
|
||||
"description": "Utiliser un code pour vous connecter à votre client Drop si vous ne pouvez pas ouvrir un navigateur web sur votre appareil.",
|
||||
"title": "Connecter votre client Drop"
|
||||
},
|
||||
"confirmPassword": "Confirmez @:auth.password",
|
||||
"displayName": "Nom d'Affichage",
|
||||
"email": "Email",
|
||||
"password": "Mot de passe",
|
||||
@ -147,6 +148,7 @@
|
||||
"auth": {
|
||||
"disabled": "Compte invalide or désactivé. Merci de contacter l'administrateur du serveur.",
|
||||
"invalidInvite": "Invitation invalide ou expirée",
|
||||
"invalidPassState": "Le mot de passe enregistré est invalide. Merci de contacter l'administrateur du serveur.",
|
||||
"invalidUserOrPass": "Nom d'utilisateur ou password invalide.",
|
||||
"inviteIdRequired": "id est requis pour récupérer l'invitation",
|
||||
"method": {
|
||||
@ -155,6 +157,10 @@
|
||||
"usernameTaken": "Nom d'utilisateur déjà pris."
|
||||
},
|
||||
"backHome": "{arrow} Retour a l'accueil",
|
||||
"externalUrl": {
|
||||
"subtitle": "Ce message n'est visible qu'aux administrateurs.",
|
||||
"title": "Accès via une EXTERNAL_URL différente. Veuillez consulter la documentation."
|
||||
},
|
||||
"game": {
|
||||
"banner": {
|
||||
"description": "Drop a échoué a mettre à jour l'image de la bannière : {0}",
|
||||
@ -215,6 +221,7 @@
|
||||
"revokeClient": "Échec de la révocation du client",
|
||||
"revokeClientFull": "Échec de la revocation du client {0}",
|
||||
"signIn": "Se connecter {arrow}",
|
||||
"support": "Assistance Discord",
|
||||
"unknown": "Une erreur inconnue est survenue",
|
||||
"upload": {
|
||||
"description": "Drop n'a pas pu uploader le fichier : {0}",
|
||||
@ -254,7 +261,11 @@
|
||||
"admin": {
|
||||
"admin": "Administration",
|
||||
"metadata": "Méta",
|
||||
"settings": "Paramètres",
|
||||
"settings": {
|
||||
"store": "Store",
|
||||
"title": "Paramètres",
|
||||
"tokens": "API tokens"
|
||||
},
|
||||
"tasks": "Tâches",
|
||||
"users": "Utilisateurs"
|
||||
},
|
||||
@ -327,6 +338,8 @@
|
||||
},
|
||||
"withoutMetadata": "Importer sans les données méta"
|
||||
},
|
||||
"libraryHint": "Pas de bibliothèque configurée.",
|
||||
"libraryHintDocsLink": "Qu'est-ce que cela veut dire ? {arrow}",
|
||||
"metadata": {
|
||||
"companies": {
|
||||
"action": "Gérer {arrow}",
|
||||
@ -340,15 +353,25 @@
|
||||
"description": "Les sociétés organisent les jeux par qui les a développer ou éditer.",
|
||||
"editor": {
|
||||
"action": "Ajouter un jeu {plus}",
|
||||
"descriptionPlaceholder": "{'<'}description{'>'}",
|
||||
"developed": "Développé",
|
||||
"libraryDescription": "Ajouter, supprimer ou personnaliser ce que cette société a développé et/ou publié.",
|
||||
"libraryTitle": "Bibliothèque de jeux",
|
||||
"noDescription": "(pas de description)",
|
||||
"published": "Publié",
|
||||
"uploadBanner": "Uploader bannière",
|
||||
"uploadIcon": "Uplader icône"
|
||||
"uploadIcon": "Uplader icône",
|
||||
"websitePlaceholder": "{'<'}site web{'>'}"
|
||||
},
|
||||
"modals": {
|
||||
"createDescription": "Créez une société pour mieux organizer vos jeux.",
|
||||
"createFieldDescription": "Description de la Société",
|
||||
"createFieldDescriptionPlaceholder": "Un petit studio indépendant qui...",
|
||||
"createFieldName": "Nom de la société",
|
||||
"createFieldNamePlaceholder": "Ma nouvelle société...",
|
||||
"createFieldWebsite": "Site web de la société",
|
||||
"createFieldWebsitePlaceholder": "https://exemple com/",
|
||||
"createTitle": "Créer une société",
|
||||
"nameDescription": "Éditer le nom de la société. Ce nom est utilisé pour trouver les jeux nouvellement importés.",
|
||||
"nameTitle": "Éditer le nom de la société",
|
||||
"shortDeckDescription": "Éditer la description de la company. Cela n'affecte pas la description longue (markdown).",
|
||||
@ -384,12 +407,15 @@
|
||||
"create": "Créer une source",
|
||||
"createDesc": "Drop va utiliser cette source pour accéder à votre bibliothèque de jeux, et les rendre disponible.",
|
||||
"desc": "Configurer vos sources de bibliothèques où Drop va regarder pour les nouveaux jeux et versions à importer.",
|
||||
"documentationLink": "Documentation {arrow}",
|
||||
"edit": "Éditer la source",
|
||||
"fsDesc": "Importe les jeux à partir d'un chemin d'accès sur le disque. Cela requière une structure des dossiers basées sur la version, et qui supporte les jeux archivés.",
|
||||
"fsFlatDesc": "Importe les jeux à partir d'un chemin d’accès sur le disque, mais sans le sous-dossier version séparé. Utile pour migrer une bibliothèque vers Drop.",
|
||||
"fsFlatTitle": "Compatibilité",
|
||||
"fsPath": "Chemin d’accès",
|
||||
"fsPathDesc": "Un chemin d’accès absolu à votre bibliothèque de jeux.",
|
||||
"fsPathPlaceholder": "/mnt/jeux",
|
||||
"fsTitle": "Drop-style",
|
||||
"link": "Sources {arrow}",
|
||||
"nameDesc": "Le nom de votre source, pour référence.",
|
||||
"namePlaceholder": "Mes Nouvelle Source",
|
||||
@ -447,6 +473,7 @@
|
||||
"checkLater": "Vérifier plus tard pour les mises à jour.",
|
||||
"delete": "Supprimer l'Article",
|
||||
"filter": {
|
||||
"all": "Tous les temps",
|
||||
"month": "Ce mois",
|
||||
"week": "Cette semaine",
|
||||
"year": "Cette année"
|
||||
@ -509,15 +536,19 @@
|
||||
"store": {
|
||||
"about": "À propos",
|
||||
"commingSoon": "prochainement",
|
||||
"developers": "Développeurs | Développeur | Développeurs",
|
||||
"exploreMore": "Explorer plus {arrow}",
|
||||
"featured": "Mis en avant",
|
||||
"images": "Images de Jeux",
|
||||
"lookAt": "Découvrez le maintenant",
|
||||
"noDevelopers": "Pas de développeur",
|
||||
"noGame": "pas de jeu",
|
||||
"noFeatured": "PAS DE JEU MIS EN AVANT",
|
||||
"noGame": "PAS DE JEU",
|
||||
"noImages": "Pas d'image",
|
||||
"noPublishers": "Pas d'éditeur.",
|
||||
"noTags": "Pas de tag",
|
||||
"openAdminDashboard": "Ouvrir dans le Tableau de Bord d'Administration",
|
||||
"openFeatured": "Mettez des étoiles aux jeux dans l'administration de la bibliothèque {arrow}",
|
||||
"platform": "Plateforme | Plateforme | Plateformes",
|
||||
"publishers": "Éditeurs | Éditeur | Éditeurs",
|
||||
"rating": "Note",
|
||||
@ -544,7 +575,9 @@
|
||||
"back": "{arrow} Retour aux Tâches",
|
||||
"completedTasksTitle": "Tâches complétées",
|
||||
"dailyScheduledTitle": "Tâches quotidiennes planifiées",
|
||||
"execute": "{arrow} Exécuter",
|
||||
"noTasksRunning": "Pas de tâche en cours",
|
||||
"progress": "{0]%",
|
||||
"runningTasksTitle": "Tâches en cours d'exécution",
|
||||
"scheduled": {
|
||||
"checkUpdateDescription": "Vérifier si Drop a une mise à jour.",
|
||||
@ -588,6 +621,7 @@
|
||||
"description": "Drop supporte une variété de \"mécanismes d'authentification\". Lorsque vous les activez ou les désactivez, ils sont affichés sur la page de connection pour que les utilisateurs puissent les sélectionner. Cliquer sur le menu à points pour configurer le mécanisme d'authentification.",
|
||||
"disabled": "Désactivé",
|
||||
"enabled": "Activé",
|
||||
"enabledKey": "Activée ?",
|
||||
"oidc": "OpenID Connect",
|
||||
"simple": "Simple (nom d'utilisateur/mot de passe)",
|
||||
"srOpenOptions": "Ouvrir les options",
|
||||
@ -605,7 +639,7 @@
|
||||
"createInvitation": "Créer invitation",
|
||||
"description": "L'authentification simple utilise un système d'invitations pour créer les utilisateurs. Tu peux créer une invitation et optionnellement spécifier le nom d'utilisateur ou email de cet utilisateur, et un lien magique sera généré un lien magique qui peut être utilisé pour créer le compte.",
|
||||
"expires": "Expire : {expiry}",
|
||||
"invitationTitle": "invitations",
|
||||
"invitationTitle": "Invitations",
|
||||
"invite3Days": "3 jours",
|
||||
"invite6Months": "6 mois",
|
||||
"inviteAdminSwitchDescription": "Créer cet utilisateur en tant qu'adminstrateur",
|
||||
|
||||
@ -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