Add component and apply to import
This commit is contained in:
@ -4,7 +4,7 @@ import { cva } from "class-variance-authority"
|
|||||||
export { default as Button } from "./Button.vue"
|
export { default as Button } from "./Button.vue"
|
||||||
|
|
||||||
export const buttonVariants = cva(
|
export const buttonVariants = cva(
|
||||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
|
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive cursor-pointer",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
|
|||||||
131
app/components/ui/file-upload/FileUpload.vue
Normal file
131
app/components/ui/file-upload/FileUpload.vue
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from "vue";
|
||||||
|
import { Motion } from "motion-v";
|
||||||
|
import { ref } from "vue";
|
||||||
|
|
||||||
|
interface FileUploadProps {
|
||||||
|
class?: HTMLAttributes["class"];
|
||||||
|
accept?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<FileUploadProps>();
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: "onChange", files: File[]): void;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const fileInputRef = ref<HTMLInputElement | null>(null);
|
||||||
|
const files = ref<File[]>([]);
|
||||||
|
const isActive = ref<boolean>(false);
|
||||||
|
|
||||||
|
function handleFileChange(newFiles: File[]) {
|
||||||
|
files.value = [...files.value, ...newFiles];
|
||||||
|
emit("onChange", files.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onFileChange(e: Event) {
|
||||||
|
const input = e.target as HTMLInputElement;
|
||||||
|
if (!input.files) return;
|
||||||
|
handleFileChange(Array.from(input.files));
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleClick() {
|
||||||
|
fileInputRef.value?.click();
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleEnter() {
|
||||||
|
isActive.value = true;
|
||||||
|
}
|
||||||
|
function handleLeave() {
|
||||||
|
isActive.value = false;
|
||||||
|
}
|
||||||
|
function handleDrop(e: DragEvent) {
|
||||||
|
isActive.value = false;
|
||||||
|
const droppedFiles = e.dataTransfer?.files ? Array.from(e.dataTransfer.files) : [];
|
||||||
|
if (droppedFiles.length) handleFileChange(droppedFiles);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<ClientOnly>
|
||||||
|
<div class="w-full" :class="[$props.class]" @dragover.prevent="handleEnter" @dragleave="handleLeave"
|
||||||
|
@drop.prevent="handleDrop" @mouseover="handleEnter" @mouseleave="handleLeave">
|
||||||
|
<div class="group/file relative block w-full cursor-pointer overflow-hidden rounded-lg p-10 bg-muted space-y-4"
|
||||||
|
@click="handleClick">
|
||||||
|
<input ref="fileInputRef" type="file" class="hidden" :accept="accept" @change="onFileChange" />
|
||||||
|
|
||||||
|
<!-- Grid pattern -->
|
||||||
|
<div
|
||||||
|
class="pointer-events-none absolute inset-0 [mask-image:radial-gradient(ellipse_at_center,white,transparent)]">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="relative mx-auto mt-10 w-full max-w-xl space-y-4">
|
||||||
|
<Motion v-for="(file, idx) in files" :key="`file-${idx}`" :initial="{ opacity: 0, scaleX: 0 }"
|
||||||
|
:animate="{ opacity: 1, scaleX: 1 }"
|
||||||
|
class="relative z-40 mx-auto flex w-full flex-col items-start justify-start overflow-hidden rounded-md p-4 shadow-sm md:h-24 text-muted-foreground bg-background">
|
||||||
|
<div class="flex w-full items-center justify-between gap-4">
|
||||||
|
<Motion as="p" :initial="{ opacity: 0 }" :animate="{ opacity: 1 }"
|
||||||
|
class="max-w-xs truncate text-base">
|
||||||
|
{{ file.name }}
|
||||||
|
</Motion>
|
||||||
|
<Motion as="p" :initial="{ opacity: 0 }" :animate="{ opacity: 1 }"
|
||||||
|
class="shadow-input w-fit shrink-0 rounded-lg px-2 py-1 text-sm">
|
||||||
|
{{ (file.size / (1024 * 1024)).toFixed(2) }} MB
|
||||||
|
</Motion>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
class="mt-2 flex w-full flex-col items-start justify-between text-sm text-muted-foreground md:flex-row md:items-center">
|
||||||
|
<Motion as="p" :initial="{ opacity: 0 }" :animate="{ opacity: 1 }"
|
||||||
|
class="rounded-md bg-muted px-1.5 py-1 text-sm">
|
||||||
|
{{ file.type || "unknown type" }}
|
||||||
|
</Motion>
|
||||||
|
</div>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<template v-if="!files.length">
|
||||||
|
<Motion as="div"
|
||||||
|
class="relative z-40 mx-auto mt-4 flex h-32 w-full max-w-32 items-center justify-center rounded-md shadow-[0px_10px_50px_rgba(0,0,0,0.1)] bg-background group-hover/file:shadow-2xl"
|
||||||
|
:initial="{
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
opacity: 1,
|
||||||
|
}" :transition="{
|
||||||
|
type: 'spring',
|
||||||
|
stiffness: 300,
|
||||||
|
damping: 20,
|
||||||
|
}" :animate="isActive
|
||||||
|
? {
|
||||||
|
x: 20,
|
||||||
|
y: -20,
|
||||||
|
opacity: 0.9,
|
||||||
|
}
|
||||||
|
: {}
|
||||||
|
">
|
||||||
|
<Icon name="heroicons:arrow-up-tray-20-solid" size="20" />
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<div class="absolute inset-0 z-30 mx-auto mt-4 flex h-32 w-full max-w-32 items-center justify-center rounded-md border border-dashed border-border bg-transparent transition-opacity"
|
||||||
|
:class="{ 'opacity-100': isActive, 'opacity-0': !isActive }" />
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Content -->
|
||||||
|
<div class="flex flex-col items-center justify-center gap-2">
|
||||||
|
<slot name="content" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</ClientOnly>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.group-hover\/file\:shadow-2xl:hover {
|
||||||
|
box-shadow: 0px 10px 20px rgba(0, 0, 0, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.transition-opacity {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
35
app/components/ui/file-upload/FileUploadGrid.vue
Normal file
35
app/components/ui/file-upload/FileUploadGrid.vue
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import type { HTMLAttributes } from "vue";
|
||||||
|
|
||||||
|
interface FileUploadGridProps {
|
||||||
|
class?: HTMLAttributes["class"];
|
||||||
|
}
|
||||||
|
|
||||||
|
defineProps<FileUploadGridProps>();
|
||||||
|
|
||||||
|
const ROWS = 11;
|
||||||
|
const COLUMNS = 41;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="flex shrink-0 scale-105 flex-wrap items-center justify-center gap-px bg-gray-100 dark:bg-neutral-900"
|
||||||
|
:class="[$props.class]"
|
||||||
|
>
|
||||||
|
<template v-for="row in ROWS">
|
||||||
|
<template
|
||||||
|
v-for="col in COLUMNS"
|
||||||
|
:key="`${row}-${col}`"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
class="flex h-10 w-10 flex-shrink-0 rounded-[2px]"
|
||||||
|
:class="[
|
||||||
|
((row - 1) * COLUMNS + (col - 1)) % 2 === 0
|
||||||
|
? 'bg-gray-50 dark:bg-neutral-950'
|
||||||
|
: 'bg-gray-50 shadow-[0px_0px_1px_3px_rgba(255,255,255,1)_inset] dark:bg-neutral-950 dark:shadow-[0px_0px_1px_3px_rgba(0,0,0,1)_inset]',
|
||||||
|
]"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
2
app/components/ui/file-upload/index.ts
Normal file
2
app/components/ui/file-upload/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export { default as FileUpload } from "./FileUpload.vue";
|
||||||
|
export { default as FileUploadGrid } from "./FileUploadGrid.vue";
|
||||||
@ -17,6 +17,7 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import { useSlots } from 'vue'
|
import { useSlots } from 'vue'
|
||||||
import 'vue-sonner/style.css'
|
import 'vue-sonner/style.css'
|
||||||
|
import Sonner from '~/components/ui/sonner/Sonner.vue';
|
||||||
|
|
||||||
const slots = useSlots()
|
const slots = useSlots()
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<ClientOnly>
|
<ClientOnly>
|
||||||
<Toaster />
|
<Sonner />
|
||||||
</ClientOnly>
|
</ClientOnly>
|
||||||
<AppSidebar />
|
<AppSidebar />
|
||||||
<SidebarInset>
|
<SidebarInset>
|
||||||
@ -25,5 +25,6 @@ import { useSlots } from 'vue'
|
|||||||
import 'vue-sonner/style.css'
|
import 'vue-sonner/style.css'
|
||||||
import AppSidebar from '~/components/ui/sidebar/AppSidebar.vue';
|
import AppSidebar from '~/components/ui/sidebar/AppSidebar.vue';
|
||||||
|
|
||||||
|
import Sonner from '~/components/ui/sonner/Sonner.vue';
|
||||||
const slots = useSlots()
|
const slots = useSlots()
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -8,11 +8,14 @@ import EmptyMedia from '@/components/ui/empty/EmptyMedia.vue';
|
|||||||
import EmptyTitle from '@/components/ui/empty/EmptyTitle.vue';
|
import EmptyTitle from '@/components/ui/empty/EmptyTitle.vue';
|
||||||
import EmptyDescription from '@/components/ui/empty/EmptyDescription.vue';
|
import EmptyDescription from '@/components/ui/empty/EmptyDescription.vue';
|
||||||
|
|
||||||
import { Download, MegaphoneOff, Play } from 'lucide-vue-next';
|
import { MegaphoneOff, Play } from 'lucide-vue-next';
|
||||||
import type { EventSourceListener } from '~/composeables/api/event-source';
|
import type { EventSourceListener } from '~/composeables/api/event-source';
|
||||||
import type { StreamProgress200Item } from '~/composeables/api/models';
|
import type { StreamProgress200Item } from '~/composeables/api/models';
|
||||||
import { streamProgress } from '~/composeables/api/progress-sse-controller/progress-sse-controller';
|
import { streamProgress } from '~/composeables/api/progress-sse-controller/progress-sse-controller';
|
||||||
import { useCurrentPlaylistStore } from '~/stores/use-current-playlist-store';
|
import { useCurrentPlaylistStore } from '~/stores/use-current-playlist-store';
|
||||||
|
import FileUpload from '@/components/ui/file-upload/FileUpload.vue';
|
||||||
|
import FileUploadGrid from '@/components/ui/file-upload/FileUploadGrid.vue';
|
||||||
|
import Button from '@/components/ui/button/Button.vue';
|
||||||
|
|
||||||
const currentPlaylistStore = useCurrentPlaylistStore();
|
const currentPlaylistStore = useCurrentPlaylistStore();
|
||||||
const progressEntries = ref<Map<string, StreamProgress200Item>>(new Map());
|
const progressEntries = ref<Map<string, StreamProgress200Item>>(new Map());
|
||||||
@ -55,6 +58,11 @@ async function listenImports() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onYoutubeClick(e: MouseEvent) {
|
||||||
|
console.log("hello")
|
||||||
|
e.stopPropagation();
|
||||||
|
}
|
||||||
|
|
||||||
function stopImports() {
|
function stopImports() {
|
||||||
if (listener) {
|
if (listener) {
|
||||||
listener.close();
|
listener.close();
|
||||||
@ -82,19 +90,23 @@ onUnmounted(() => {
|
|||||||
</Outline>
|
</Outline>
|
||||||
</template>
|
</template>
|
||||||
<div class="w-full flex flex-col p-8">
|
<div class="w-full flex flex-col p-8">
|
||||||
<Outline class="rounded-xl bg-muted flex flex-col items-center justify-center gap-1">
|
<FileUpload class="rounded-lg border border-dashed border-muted" @onChange="">
|
||||||
<Download />
|
<template #default>
|
||||||
<h4 class="scroll-m-20 text-xl font-semibold tracking-tight">
|
<FileUploadGrid />
|
||||||
Drag and drop your audio files
|
</template>
|
||||||
</h4>
|
<template #content>
|
||||||
<p class="text-sm text-muted-foreground">
|
<h4 class="z-20 scroll-m-20 text-xl font-semibold tracking-tight">
|
||||||
or
|
Drag and drop your audio files
|
||||||
</p>
|
</h4>
|
||||||
<Button variant="destructive">
|
<p class="z-20 font-normal text-muted-foreground">
|
||||||
<Play />
|
or
|
||||||
From Youtube
|
</p>
|
||||||
</Button>
|
<Button class="z-20" variant="destructive" @click="onYoutubeClick">
|
||||||
</Outline>
|
<Play />
|
||||||
|
From Youtube
|
||||||
|
</Button>
|
||||||
|
</template>
|
||||||
|
</FileUpload>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="scroll-m-20 text-2xl font-semibold tracking-tight">
|
<h3 class="scroll-m-20 text-2xl font-semibold tracking-tight">
|
||||||
Uploaded files
|
Uploaded files
|
||||||
|
|||||||
11
bun.lock
11
bun.lock
@ -19,6 +19,7 @@
|
|||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"eventsource": "^4.1.0",
|
"eventsource": "^4.1.0",
|
||||||
"lucide-vue-next": "^0.548.0",
|
"lucide-vue-next": "^0.548.0",
|
||||||
|
"motion-v": "^1.8.1",
|
||||||
"nuxt": "^4.2.0",
|
"nuxt": "^4.2.0",
|
||||||
"oidc-client-ts": "^3.3.0",
|
"oidc-client-ts": "^3.3.0",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
@ -1051,6 +1052,8 @@
|
|||||||
|
|
||||||
"fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="],
|
"fraction.js": ["fraction.js@4.3.7", "", {}, "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew=="],
|
||||||
|
|
||||||
|
"framer-motion": ["framer-motion@12.26.2", "", { "dependencies": { "motion-dom": "^12.26.2", "motion-utils": "^12.24.10", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid", "react", "react-dom"] }, "sha512-lflOQEdjquUi9sCg5Y1LrsZDlsjrHw7m0T9Yedvnk7Bnhqfkc89/Uha10J3CFhkL+TCZVCRw9eUGyM/lyYhXQA=="],
|
||||||
|
|
||||||
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
"fresh": ["fresh@2.0.0", "", {}, "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A=="],
|
||||||
|
|
||||||
"fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
|
"fs-constants": ["fs-constants@1.0.0", "", {}, "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="],
|
||||||
@ -1125,6 +1128,8 @@
|
|||||||
|
|
||||||
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
|
||||||
|
|
||||||
|
"hey-listen": ["hey-listen@1.0.8", "", {}, "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q=="],
|
||||||
|
|
||||||
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
"hookable": ["hookable@5.5.3", "", {}, "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ=="],
|
||||||
|
|
||||||
"http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
|
"http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="],
|
||||||
@ -1403,6 +1408,12 @@
|
|||||||
|
|
||||||
"mocked-exports": ["mocked-exports@0.1.1", "", {}, "sha512-aF7yRQr/Q0O2/4pIXm6PZ5G+jAd7QS4Yu8m+WEeEHGnbo+7mE36CbLSDQiXYV8bVL3NfmdeqPJct0tUlnjVSnA=="],
|
"mocked-exports": ["mocked-exports@0.1.1", "", {}, "sha512-aF7yRQr/Q0O2/4pIXm6PZ5G+jAd7QS4Yu8m+WEeEHGnbo+7mE36CbLSDQiXYV8bVL3NfmdeqPJct0tUlnjVSnA=="],
|
||||||
|
|
||||||
|
"motion-dom": ["motion-dom@12.26.2", "", { "dependencies": { "motion-utils": "^12.24.10" } }, "sha512-KLMT1BroY8oKNeliA3JMNJ+nbCIsTKg6hJpDb4jtRAJ7nCKnnpg/LTq/NGqG90Limitz3kdAnAVXecdFVGlWTw=="],
|
||||||
|
|
||||||
|
"motion-utils": ["motion-utils@12.24.10", "", {}, "sha512-x5TFgkCIP4pPsRLpKoI86jv/q8t8FQOiM/0E8QKBzfMozWHfkKap2gA1hOki+B5g3IsBNpxbUnfOum1+dgvYww=="],
|
||||||
|
|
||||||
|
"motion-v": ["motion-v@1.8.1", "", { "dependencies": { "framer-motion": "^12.25.0", "hey-listen": "^1.0.8", "motion-dom": "^12.23.23" }, "peerDependencies": { "@vueuse/core": ">=10.0.0", "vue": ">=3.0.0" } }, "sha512-OS6ve/vdNlrKTmCAHy+lxujIVTggjs9nbzl1auWiewy49FthkpCs5Wwpf40+55Ko3mbTajlKadkTlQYysrnL4A=="],
|
||||||
|
|
||||||
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
|
||||||
|
|
||||||
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
|
||||||
|
|||||||
@ -24,6 +24,7 @@
|
|||||||
"dotenv": "^17.2.3",
|
"dotenv": "^17.2.3",
|
||||||
"eventsource": "^4.1.0",
|
"eventsource": "^4.1.0",
|
||||||
"lucide-vue-next": "^0.548.0",
|
"lucide-vue-next": "^0.548.0",
|
||||||
|
"motion-v": "^1.8.1",
|
||||||
"nuxt": "^4.2.0",
|
"nuxt": "^4.2.0",
|
||||||
"oidc-client-ts": "^3.3.0",
|
"oidc-client-ts": "^3.3.0",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
|
|||||||
Reference in New Issue
Block a user