Fill download page

Add Library Selector
This commit is contained in:
2025-10-12 02:21:44 +02:00
parent 25b7ef5495
commit f0bacc86bd
6 changed files with 102 additions and 23 deletions

View File

@@ -0,0 +1,33 @@
<template>
<USelect
v-model="library"
placeholder="Library"
icon="i-lucide-library-big"
color="secondary"
:items="libraries?.map((l) => l.key)"
class="w-xs">
<template #default="{ modelValue }">
<p v-if="modelValue">
<span class="mr-2">{{ libraries?.find((l) => l.key == modelValue)?.libraryName }}</span>
<span class="text-secondary">({{ libraries?.find((l) => l.key == modelValue)?.basePath }})</span>
</p>
</template>
<template #item="{ item }">
<p>
<span class="mr-2">{{ libraries?.find((l) => l.key == item)?.libraryName }}</span>
<span class="text-secondary">({{ libraries?.find((l) => l.key == item)?.basePath }})</span>
</p>
</template>
</USelect>
</template>
<script setup lang="ts">
export interface LibrarySelectProps {
libraryId?: string;
}
const props = defineProps<LibrarySelectProps>();
const library = ref(props.libraryId);
const { data: libraries } = await useApi('/v2/FileLibrary', { key: FetchKeys.FileLibraries });
</script>

View File

@@ -1,15 +0,0 @@
<template>
<MangaDetailPage :manga="manga" back-path="/search" />
</template>
<script setup lang="ts">
import MangaDetailPage from '~/components/MangaDetailPage.vue';
const route = useRoute();
const mangaId = route.params.MangaId as string;
const { data: manga } = await useApi('/v2/Manga/{MangaId}', {
path: { MangaId: mangaId },
key: FetchKeys.Manga.Id(mangaId),
});
</script>

View File

@@ -0,0 +1,43 @@
<template>
<MangaDetailPage :manga="manga" back-path="/search">
<UCard>
<template #default>
<div class="flex flex-row gap-2 w-full justify-center">
<LibrarySelect v-bind="libraryId" />
<UButton color="primary" :disabled="!libraryId" :loading="loading" @click="onDownloadClick">
Download from {{ mangaConnector?.name }}
<template #trailing>
<NuxtImg :src="mangaConnector?.iconUrl" class="h-lh" />
</template>
</UButton>
</div>
</template>
</UCard>
</MangaDetailPage>
</template>
<script setup lang="ts">
const route = useRoute();
const mangaId = route.params.mangaId as string;
const mangaConnectorName = route.params.mangaconnectorName as string;
const { data: manga } = await useApi('/v2/Manga/{MangaId}', {
path: { MangaId: mangaId },
key: FetchKeys.Manga.Id(mangaId),
});
const libraryId = ref({ libraryId: manga.value?.fileLibraryId });
const { data: mangaConnector } = await useApi('/v2/MangaConnector/{MangaConnectorName}', {
path: { MangaConnectorName: mangaConnectorName },
key: FetchKeys.MangaConnector.Id(mangaConnectorName),
});
const loading = ref(false);
const onDownloadClick = async () => {
loading.value = true;
await useApi('/v2/Manga/{MangaId}/SetAsDownloadFrom/{MangaConnectorName}/{IsRequested}', {
method: 'POST',
path: { MangaId: mangaId, MangaConnectorName: mangaConnectorName, IsRequested: true },
});
loading.value = false;
};
</script>

View File

@@ -2,6 +2,7 @@
<MangaDetailPage :manga="manga"> <MangaDetailPage :manga="manga">
<ChaptersList v-if="manga" :manga-id="manga.key" /> <ChaptersList v-if="manga" :manga-id="manga.key" />
<template #actions> <template #actions>
<LibrarySelect :library-id="libraryId" />
<UButton variant="soft" color="warning" icon="i-lucide-trash" /> <UButton variant="soft" color="warning" icon="i-lucide-trash" />
</template> </template>
</MangaDetailPage> </MangaDetailPage>
@@ -17,4 +18,5 @@ const { data: manga } = await useApi('/v2/Manga/{MangaId}', {
path: { MangaId: mangaId }, path: { MangaId: mangaId },
key: FetchKeys.Manga.Id(mangaId), key: FetchKeys.Manga.Id(mangaId),
}); });
const libraryId = ref(manga.value?.fileLibraryId);
</script> </script>

View File

@@ -1,9 +1,17 @@
<template> <template>
<UPageBody> <UPageBody>
<UPageSection :ui="{ container: 'gap-4 sm:gap-4 lg:gap-4 ' }"> <UPageSection
:ui="{ container: 'gap-4 sm:gap-4 lg:gap-4 py-4 sm:py-4 lg:py-4 gap-4 sm:gap-4 lg:gap-4' }"
class="h-fit">
<UButton variant="soft" to="/" icon="i-lucide-arrow-left" class="w-min">Back</UButton> <UButton variant="soft" to="/" icon="i-lucide-arrow-left" class="w-min">Back</UButton>
<div class="flex flex-row w-full h-full justify-between gap-4"> <div class="flex flex-row w-full h-full justify-between gap-4">
<UStepper v-model="activeStep" orientation="vertical" :items="items" class="h-full" disabled color="secondary" /> <UStepper
v-model="activeStep"
orientation="vertical"
:items="items"
class="h-full"
disabled
color="secondary" />
<UCard class="grow"> <UCard class="grow">
<div class="flex flex-col justify-between gap-2"> <div class="flex flex-col justify-between gap-2">
<UInput v-model="query" class="w-full" :disabled="busy" /> <UInput v-model="query" class="w-full" :disabled="busy" />
@@ -27,9 +35,13 @@
</UCard> </UCard>
</div> </div>
</UPageSection> </UPageSection>
<UPageSection v-if="searchResult.length > 0" :ui="{ container: 'py-0 sm:py-0 lg:py-0' }"> <UPageSection
v-if="searchResult.length > 0"
:ui="{ container: 'gap-4 sm:gap-4 lg:gap-4 py-4 sm:py-4 lg:py-4 gap-4 sm:gap-4 lg:gap-4' }">
<template #description> <template #description>
<p class="text-lg">Result for '{{ searchQuery }}'</p> <p class="text-lg">
Result for <span class="text-secondary">'{{ searchQuery }}'</span>
</p>
</template> </template>
<template #default> <template #default>
<div class="relative flex flex-row flex-wrap gap-6 mt-0"> <div class="relative flex flex-row flex-wrap gap-6 mt-0">
@@ -40,7 +52,7 @@
:expanded="i === expanded" :expanded="i === expanded"
@click="expanded = expanded === i ? -1 : i"> @click="expanded = expanded === i ? -1 : i">
<template #actions="manga"> <template #actions="manga">
<UButton :to="`download/${manga.key}`">Download</UButton> <UButton :to="`download/${connector.name}/${manga.key}`">Download</UButton>
</template> </template>
</MangaCard> </MangaCard>
</div> </div>
@@ -98,8 +110,12 @@ const performSearch = () => {
const search = async (query: string): Promise<MinimalManga[]> => { const search = async (query: string): Promise<MinimalManga[]> => {
if (isUrl(query)) { if (isUrl(query)) {
const { data } = await useApi('/v2/Search/Url', { body: JSON.stringify(query), method: 'POST' }); const { data } = await useApi('/v2/Search/Url', { body: JSON.stringify(query), method: 'POST' });
if (data.value) return [data.value]; if (data.value) {
else return Promise.reject(); connector.value = connectors.value?.find(
(c) => c.name == data.value.mangaConnectorIds[0]?.mangaConnectorName
);
return [data.value];
} else return Promise.reject();
} else if (connector.value.name) { } else if (connector.value.name) {
const { data } = await useApi('/v2/Search/{MangaConnectorName}/{Query}', { const { data } = await useApi('/v2/Search/{MangaConnectorName}/{Query}', {
path: { MangaConnectorName: connector.value.name, Query: query }, path: { MangaConnectorName: connector.value.name, Query: query },

View File

@@ -45,7 +45,7 @@ const apiUrl = ref(config.public.openFetch.api.baseURL);
const reloading = ref(false); const reloading = ref(false);
const setUrl = async () => { const setUrl = async () => {
reloading.value = true; reloading.value = true;
config.public.openFetch.api.baseURL = apiUrl.value; config.public.openFetch.api.baseURL = apiUrl.value.endsWith('/') ? apiUrl.value : apiUrl.value + '/';
await refreshNuxtData(); await refreshNuxtData();
reloading.value = false; reloading.value = false;
}; };