mirror of
https://github.com/C9Glax/tranga-website.git
synced 2025-10-11 21:29:50 +02:00
nuxt rewrite
This commit is contained in:
37
website/app/components/AddLibraryModal.vue
Normal file
37
website/app/components/AddLibraryModal.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<UModal v-bind="$props" title="Add Library">
|
||||
<template #body>
|
||||
<div class="flex flex-col gap-2">
|
||||
<UFormField label="Library Name" required>
|
||||
<UInput v-model="name" placeholder="Name for the library" class="w-full" :disabled="busy" />
|
||||
</UFormField>
|
||||
<UFormField label="Directory Path" required>
|
||||
<UInput v-model="path" placeholder="Path for the library" class="w-full" :disabled="busy" />
|
||||
</UFormField>
|
||||
<UButton icon="i-lucide-plus" @click="onAddClick" :loading="busy">Add</UButton>
|
||||
</div>
|
||||
</template>
|
||||
</UModal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { components } from '#open-fetch-schemas/api';
|
||||
|
||||
const name = ref('');
|
||||
const path = ref('');
|
||||
|
||||
const model = computed((): components['schemas']['CreateLibraryRecord'] => {
|
||||
return { basePath: path.value, libraryName: name.value };
|
||||
});
|
||||
|
||||
const config = useRuntimeConfig();
|
||||
const busy = ref(false);
|
||||
const onAddClick = () => {
|
||||
busy.value = true;
|
||||
$fetch(new Request(`${config.public.openFetch.api.baseURL}v2/FileLibrary`), { method: 'PUT', body: model.value })
|
||||
.then(() => emit('change'))
|
||||
.finally(() => (busy.value = false));
|
||||
};
|
||||
|
||||
const emit = defineEmits(['change']);
|
||||
</script>
|
31
website/app/components/FileLibraries.vue
Normal file
31
website/app/components/FileLibraries.vue
Normal file
@@ -0,0 +1,31 @@
|
||||
<template>
|
||||
<UPageList divide>
|
||||
<UPageCard
|
||||
v-for="l in fileLibraries"
|
||||
variant="ghost"
|
||||
icon="i-lucide-library-big"
|
||||
:title="l.libraryName"
|
||||
:description="l.basePath"
|
||||
orientation="horizontal">
|
||||
<UButton color="warning" @click="deleteLibrary(l)" :loading="busy">Delete</UButton>
|
||||
</UPageCard>
|
||||
</UPageList>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { components } from '#open-fetch-schemas/api';
|
||||
type FileLibrary = components['schemas']['FileLibrary'];
|
||||
const { data: fileLibraries, refresh } = useApi('/v2/FileLibrary');
|
||||
|
||||
const config = useRuntimeConfig();
|
||||
const busy = ref(false);
|
||||
const deleteLibrary = (l: FileLibrary) => {
|
||||
busy.value = true;
|
||||
$fetch(new Request(`${config.public.openFetch.api.baseURL}v2/FileLibrary/${l.key}`), { method: 'DELETE' }).finally(
|
||||
() => {
|
||||
refresh();
|
||||
busy.value = false;
|
||||
}
|
||||
);
|
||||
};
|
||||
</script>
|
48
website/app/components/MangaCard.vue
Normal file
48
website/app/components/MangaCard.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<UCard
|
||||
v-if="!expanded"
|
||||
:ui="{ body: 'p-0 sm:p-0', root: 'overflow-visible' }"
|
||||
class="relative h-[350px] mt-2"
|
||||
@click="$emit('click')">
|
||||
<MangaCover :manga="manga" blur />
|
||||
<div class="absolute -top-4 -right-4 flex flex-col bg-pink rounded-full">
|
||||
<MangaconnectorIcon v-for="m in manga.mangaConnectorIds" v-bind="m" />
|
||||
</div>
|
||||
</UCard>
|
||||
<UCard
|
||||
v-else
|
||||
orientation="horizontal"
|
||||
reverse
|
||||
class="relative max-w-[600px] w-full h-[350px] mt-2"
|
||||
:ui="{ body: 'p-0 sm:p-0', root: 'overflow-visible' }"
|
||||
@click="$emit('click')">
|
||||
<div class="flex flex-row w-full h-full basis-auto">
|
||||
<MangaCover :manga="manga" class="shrink-0" />
|
||||
<div class="absolute -top-4 -right-4 flex flex-col bg-pink rounded-full">
|
||||
<MangaconnectorIcon v-for="m in manga.mangaConnectorIds" v-bind="m" />
|
||||
</div>
|
||||
<div class="flex flex-col h-[350px] shrink mx-2">
|
||||
<p class="font-semibold text-xl">{{ manga.name }}</p>
|
||||
<p class="max-h-30 overflow-y-hidden grow">{{ manga.description }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute bottom-0 w-full p-2 flex flex-row justify-end">
|
||||
<slot name="actions" v-bind="manga" />
|
||||
</div>
|
||||
</UCard>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { components } from '#open-fetch-schemas/api';
|
||||
import type { PageCardProps } from '#ui/components/PageCard.vue';
|
||||
type Manga = components['schemas']['Manga'];
|
||||
type MinimalManga = components['schemas']['MinimalManga'];
|
||||
|
||||
defineProps<MangaCardProps>();
|
||||
defineEmits(['click']);
|
||||
|
||||
export interface MangaCardProps extends PageCardProps {
|
||||
manga: Manga | MinimalManga;
|
||||
expanded?: boolean;
|
||||
}
|
||||
</script>
|
28
website/app/components/MangaCover.vue
Normal file
28
website/app/components/MangaCover.vue
Normal file
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div class="relative w-[240px] h-[350px] rounded-lg overflow-clip">
|
||||
<div
|
||||
v-if="blur"
|
||||
class="absolute l-0 t-0 w-full h-full rounded-lg overflow-clip"
|
||||
style="
|
||||
background: linear-gradient(150deg, rgba(245, 169, 184, 0.3) 50%, rgba(91, 206, 250, 0.2));
|
||||
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
|
||||
backdrop-filter: blur(2px) brightness(70%);
|
||||
-webkit-backdrop-filter: blur(2px) brightness(70%);
|
||||
">
|
||||
<p class="p-3 text-xl font-semibold max-h-full overflow-clip">{{ manga?.name }}</p>
|
||||
</div>
|
||||
<LazyNuxtImg
|
||||
v-if="manga || mangaId"
|
||||
:src="`${$config.public.openFetch.api.baseURL}v2/Manga/${manga ? manga.key : mangaId}/Cover/Medium`"
|
||||
class="w-full h-full object-cover" />
|
||||
<USkeleton v-else class="w-full h-full object-cover" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { components } from '#open-fetch-schemas/api';
|
||||
type Manga = components['schemas']['Manga'];
|
||||
type MinimalManga = components['schemas']['MinimalManga'];
|
||||
|
||||
defineProps<{ manga?: Manga | MinimalManga; mangaId?: string; blur?: boolean }>();
|
||||
</script>
|
46
website/app/components/MangaDetailPage.vue
Normal file
46
website/app/components/MangaDetailPage.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<UPage class="p-4 h-full">
|
||||
<template #left>
|
||||
<div class="flex flex-col gap-2 border-r-2 pr-4">
|
||||
<MangaCover :manga="manga" class="self-center" />
|
||||
<p v-if="manga" class="font-semibold text-xl">
|
||||
{{ manga.name }}
|
||||
<MangaconnectorIcon v-for="m in manga.mangaConnectorIds" v-bind="m" />
|
||||
</p>
|
||||
<USkeleton v-else class="text-xl h-20 w-full" />
|
||||
<div v-if="manga" class="flex flex-row gap-1 flex-wrap">
|
||||
<UBadge variant="outline" v-for="author in manga.authors" color="neutral">{{ author.name }}</UBadge>
|
||||
<UBadge variant="outline" v-for="tag in manga.tags">{{ tag }}</UBadge>
|
||||
<NuxtLink v-for="link in manga.links" :to="link.url">
|
||||
<UBadge variant="outline" color="warning">{{ link.provider }}</UBadge>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
<USkeleton v-else class="w-full h-lh" />
|
||||
<p v-if="manga" class="max-h-30 overflow-y-hidden grow">
|
||||
{{ manga.description }}
|
||||
</p>
|
||||
<USkeleton v-else class="w-full h-30" />
|
||||
</div>
|
||||
</template>
|
||||
<UPageBody class="mt-0 relative">
|
||||
<div>
|
||||
<UButton variant="soft" to="/" icon="i-lucide-arrow-left">Back</UButton>
|
||||
<p v-if="title" class="text-3xl">{{ title }}</p>
|
||||
</div>
|
||||
|
||||
<slot />
|
||||
</UPageBody>
|
||||
</UPage>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { components } from '#open-fetch-schemas/api';
|
||||
type Manga = components['schemas']['Manga'];
|
||||
|
||||
export interface MangaDetailPageProps {
|
||||
manga?: Manga;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
defineProps<MangaDetailPageProps>();
|
||||
</script>
|
25
website/app/components/MangaconnectorIcon.vue
Normal file
25
website/app/components/MangaconnectorIcon.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<div class="w-6 h-6 inline-block align-middle m-1">
|
||||
<NuxtLink :href="$props.websiteUrl ?? ''">
|
||||
<NuxtImg
|
||||
v-if="mangaConnector"
|
||||
:src="mangaConnector?.iconUrl"
|
||||
:class="[
|
||||
'w-full rounded-full outline-2 -outline-offset-1',
|
||||
props.useForDownload ? 'outline-green-500' : 'outline-red-500',
|
||||
]" />
|
||||
<p v-else>{{ mangaConnectorName }}</p>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { components } from '#open-fetch-schemas/api';
|
||||
type MangaConnectorId = components['schemas']['MangaConnectorId'];
|
||||
|
||||
const props = defineProps<MangaConnectorId>();
|
||||
|
||||
const { data: mangaConnector } = useApi('/v2/MangaConnector/{MangaConnectorName}', {
|
||||
path: { MangaConnectorName: props.mangaConnectorName },
|
||||
});
|
||||
</script>
|
Reference in New Issue
Block a user