search series: frontend, created ImagePreview component

This commit is contained in:
FrenchGithubUser
2025-09-18 16:01:47 +02:00
parent 413d6993d1
commit 23bc2e6b47
23 changed files with 258 additions and 77 deletions
+69
View File
@@ -422,6 +422,23 @@ export interface paths {
patch?: never;
trace?: never;
};
"/api/search/series": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** @description Case insensitive */
get: operations["Search series"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/api/search/title-groups/lite": {
parameters: {
query?: never;
@@ -1590,6 +1607,14 @@ export interface components {
page_size: number;
tags?: string[] | null;
};
SearchSeriesQuery: {
name?: string | null;
/** Format: int32 */
page: number;
/** Format: int32 */
page_size: number;
tags?: string[] | null;
};
SearchTorrentRequestsQuery: {
/** Format: int64 */
page?: number | null;
@@ -1628,6 +1653,25 @@ export interface components {
id: number;
name: string;
};
SeriesSearchResponse: {
results: components["schemas"]["SeriesSearchResult"][];
/** Format: int64 */
total_items: number;
};
SeriesSearchResult: {
banners?: string[] | null;
covers: string[];
/** Format: date-time */
created_at: string;
/** Format: int64 */
created_by_id: number;
/** Format: int64 */
id: number;
name: string;
tags: string[];
/** Format: int64 */
title_groups_amount: number;
};
/** @enum {string} */
Source: "CD" | "DVD5" | "DVD9" | "Vinyl" | "Web" | "Soundboard" | "SACD" | "DAT" | "Cassette" | "Blu-Ray" | "LaserDisc" | "HD-DVD" | "HDTV" | "PDTV" | "TV" | "VHS" | "Mixed" | "Physical Book";
StaffPm: {
@@ -3192,6 +3236,31 @@ export interface operations {
};
};
};
"Search series": {
parameters: {
query: {
name?: string | null;
tags?: string[] | null;
page: number;
page_size: number;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successfully got the series and some data about them */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["SeriesSearchResponse"];
};
};
};
};
"Search title group info": {
parameters: {
query: {
+15
View File
@@ -0,0 +1,15 @@
<template>
<Image :src="imageLink" :alt preview>
<template #previewicon>
<i class="pi pi-search"></i>
</template>
</Image>
</template>
<script setup lang="ts">
import { Image } from 'primevue'
defineProps<{
imageLink: string
alt?: string
}>()
</script>
+1
View File
@@ -16,6 +16,7 @@ const userStore = useUserStore()
const menuItems = ref([
{ label: 'Torrents', route: '/torrents' },
{ label: 'Series', route: '/series' },
{ label: 'Collages', route: '/collages' },
{ label: 'Requests', route: '/torrent-requests' },
{ label: 'Forum', route: '/forum' },
@@ -1,10 +1,6 @@
<template>
<div class="affiliated-artist">
<Image :src="affiliated_artist.artist.pictures[0] ?? '/default_artist_picture.svg'" :preview="affiliated_artist.artist.pictures.length !== 0">
<template #previewicon>
<i class="pi pi-search"></i>
</template>
</Image>
<ImagePreview :imageLink="affiliated_artist.artist.pictures[0] ?? '/default_artist_picture.svg'" />
<RouterLink :to="`/artist/${affiliated_artist.artist.id}`">
<div class="name">{{ affiliated_artist.artist.name }}</div>
</RouterLink>
@@ -21,8 +17,8 @@
<script setup lang="ts">
import type { AffiliatedArtistHierarchy } from '@/services/api/artistService'
import { Image } from 'primevue'
import { useI18n } from 'vue-i18n'
import ImagePreview from '../ImagePreview.vue'
const { t } = useI18n()
@@ -1,10 +1,6 @@
<template>
<div class="affiliated-entity">
<Image :src="affiliatedEntity.entity.pictures[0] ?? ''" :preview="affiliatedEntity.entity.pictures.length !== 0">
<template #previewicon>
<i class="pi pi-search"></i>
</template>
</Image>
<ImagePreview :imageLink="affiliatedEntity.entity.pictures[0] ?? ''" />
<RouterLink :to="`/entity/${affiliatedEntity.entity.id}`">
<div class="name">{{ affiliatedEntity.entity.name }}</div>
</RouterLink>
@@ -20,8 +16,8 @@
<script setup lang="ts">
import type { AffiliatedEntityHierarchy } from '@/services/api/entityService'
import { Image } from 'primevue'
import { useI18n } from 'vue-i18n'
import ImagePreview from '../ImagePreview.vue'
const { t } = useI18n()
@@ -1,11 +1,7 @@
<template>
<ContentContainer class="header-wrapper artist-full-header">
<div class="header">
<Image class="artist-pictures" v-if="artist.pictures?.length" :src="artist.pictures[0]" preview>
<template #previewicon>
<i class="pi pi-search"></i>
</template>
</Image>
<ImagePreview :imageLink="artist.pictures[0]" />
<div class="textual-information">
<div class="name">{{ artist.name }}</div>
<div class="description">
@@ -19,7 +15,7 @@
import ContentContainer from '@/components/ContentContainer.vue'
import BBCodeRenderer from '@/components/community/BBCodeRenderer.vue'
import { type Artist } from '@/services/api/artistService'
import { Image } from 'primevue'
import type ImagePreview from '../ImagePreview.vue'
defineProps<{
artist: Artist
@@ -1,10 +1,6 @@
<template>
<div class="artist-sidebar">
<Image class="artist-pictures" :src="artist.pictures[0] ?? '/default_artist_picture.svg'" :preview="artist.pictures.length !== 0">
<template #previewicon>
<i class="pi pi-search"></i>
</template>
</Image>
<ImagePreview class="artist-pictures" :imageLink="artist.pictures[0] ?? '/default_artist_picture.svg'" />
<ContentContainer v-if="artist.description" :container-title="t('general.description')">
<div class="description">
<BBCodeRenderer :content="artist.description" />
@@ -16,8 +12,8 @@
import ContentContainer from '@/components/ContentContainer.vue'
import BBCodeRenderer from '@/components/community/BBCodeRenderer.vue'
import { type Artist } from '@/services/api/artistService'
import { Image } from 'primevue'
import { useI18n } from 'vue-i18n'
import ImagePreview from '../ImagePreview.vue'
const { t } = useI18n()
@@ -48,11 +48,4 @@ const emit = defineEmits<{
// collage: Collage
// }>()
</script>
<style scoped>
#collage-sidebar {
display: block;
> * {
margin-bottom: 10px;
}
}
</style>
<style scoped></style>
@@ -1,11 +1,7 @@
<template>
<ContentContainer id="series-full-header" class="header-wrapper">
<div class="header">
<Image class="series-covers" :src="series.covers[0]" preview>
<template #previewicon>
<i class="pi pi-search"></i>
</template>
</Image>
<ImagePreview :imageLink="series.covers[0]" />
<div class="textual-information">
<div class="name">{{ series.name }}</div>
<div class="description">
@@ -20,7 +16,7 @@
import ContentContainer from '@/components/ContentContainer.vue'
import BBCodeRenderer from '@/components/community/BBCodeRenderer.vue'
import type { Series } from '@/services/api/seriesService'
import { Image } from 'primevue'
import ImagePreview from '../ImagePreview.vue'
defineProps<{
series: Series
@@ -0,0 +1,47 @@
<template>
<ContentContainer>
<Form @submit="fetchSeries">
<FloatLabel>
<InputText v-model="form.name" name="name" size="small" />
<label for="name">{{ t('general.name') }}</label>
</FloatLabel>
<div class="wrapper-center" style="margin-top: 15px">
<Button :label="t('general.search')" type="submit" :loading />
</div>
</Form>
</ContentContainer>
</template>
<script setup lang="ts">
import { searchSeries, type SeriesSearchResponse, type SearchSeriesQuery } from '@/services/api/seriesService'
import ContentContainer from '../ContentContainer.vue'
import { InputText, Button, FloatLabel } from 'primevue'
import { Form } from '@primevue/forms'
import { onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const form = ref<SearchSeriesQuery>({
name: '',
page: 1,
page_size: 50,
tags: [],
})
const loading = ref(false)
const seriesSearchResponse = ref<SeriesSearchResponse>()
onMounted(async () => {
await fetchSeries()
})
const fetchSeries = async () => {
loading.value = true
seriesSearchResponse.value = await searchSeries(form.value).finally(() => (loading.value = false))
emit('gotResults', seriesSearchResponse.value)
}
const emit = defineEmits<{
gotResults: [SeriesSearchResponse]
}>()
</script>
<style scoped></style>
@@ -1,10 +1,6 @@
<template>
<div id="series-sidebar">
<Image class="series-covers" :src="series.covers[0]" preview>
<template #previewicon>
<i class="pi pi-search"></i>
</template>
</Image>
<ImagePreview :imageLink="series.covers[0]" class="series-covers" />
<ContentContainer class="header-wrapper" container-title="Description">
<div class="description">
<BBCodeRenderer :content="series.description" />
@@ -17,7 +13,7 @@
import ContentContainer from '@/components/ContentContainer.vue'
import BBCodeRenderer from '@/components/community/BBCodeRenderer.vue'
import type { Series } from '@/services/api/seriesService'
import { Image } from 'primevue'
import ImagePreview from '../ImagePreview.vue'
defineProps<{
series: Series
@@ -0,0 +1,44 @@
<template>
<DataTable :value="series" size="small" id="series-table">
<Column style="width: 2em">
<template #body="slotProps">
<ImagePreview :imageLink="slotProps.data.covers[0]" class="cover" />
</template>
</Column>
<Column :header="t('general.name')">
<template #body="slotProps">
<RouterLink :to="`/series/${slotProps.data.id}`">{{ slotProps.data.name }}</RouterLink>
</template>
</Column>
<Column :header="t('series.entry', 2)" field="title_groups_amount" />
<Column :header="t('general.tags')">
<template #body="slotProps">
{{ slotProps.data.tags.join(', ') }}
</template>
</Column>
</DataTable>
</template>
<script setup lang="ts">
import { Column, DataTable } from 'primevue'
import { RouterLink } from 'vue-router'
import { useI18n } from 'vue-i18n'
import type { SeriesSearchResult } from '@/services/api/seriesService'
import ImagePreview from '../ImagePreview.vue'
defineProps<{
series: SeriesSearchResult[]
}>()
const { t } = useI18n()
</script>
<style>
#series-table {
.cover {
img {
width: 10em;
border-radius: 7px;
}
}
}
</style>
@@ -12,11 +12,7 @@
class="carousel"
>
<template #item="slotProps">
<Image :src="slotProps.item" preview>
<template #previewicon>
<i class="pi pi-search"></i>
</template>
</Image>
<ImagePreview :imageLink="slotProps.item" />
</template>
</Galleria>
<div class="textual-information">
@@ -67,13 +63,13 @@
import ContentContainer from '@/components/ContentContainer.vue'
import { Galleria } from 'primevue'
import Image from 'primevue/image'
import AffiliatedArtist from '@/components/artist/AffiliatedArtist.vue'
import ExternalLink from '@/components/ExternalLink.vue'
import type { TitleGroup } from '@/services/api/torrentService'
import { useI18n } from 'vue-i18n'
import type { SeriesLite } from '@/services/api/seriesService'
import type { AffiliatedArtistHierarchy } from '@/services/api/artistService'
import ImagePreview from '../ImagePreview.vue'
const { t } = useI18n()
@@ -4,11 +4,7 @@
<!-- TODO : add tags and other potentially useful information -->
<!-- TODO : clicking on a torrent should redirect to the title_group page
edit the titlegrouptable component to have a prop that allows this -->
<Image :src="title_group.covers[0]" preview class="cover">
<template #previewicon>
<i class="pi pi-search"></i>
</template>
</Image>
<ImagePreview class="cover" :imageLink="title_group.covers[0]" />
<div class="right">
<div class="title">
<RouterLink :to="`/title-group/${title_group.id}`">{{ title_group.name }}</RouterLink>
@@ -20,10 +16,10 @@
</ContentContainer>
</template>
<script setup lang="ts">
import { Image } from 'primevue'
import TitleGroupTable from './TitleGroupTable.vue'
import ContentContainer from '../ContentContainer.vue'
import type { TitleGroupHierarchyLite } from '@/services/api/artistService'
import ImagePreview from '../ImagePreview.vue'
defineProps<{
title_group: TitleGroupHierarchyLite
@@ -10,11 +10,7 @@
class="carousel"
>
<template #item="slotProps">
<Image :src="slotProps.item" preview>
<template #previewicon>
<i class="pi pi-search"></i>
</template>
</Image>
<ImagePreview :imageLink="slotProps.item" />
</template>
</Galleria>
<ContentContainer :container-title="t('general.link', 2)">
@@ -61,7 +57,6 @@
</template>
<script setup lang="ts">
import { Galleria } from 'primevue'
import Image from 'primevue/image'
import AffiliatedArtist from '@/components/artist/AffiliatedArtist.vue'
import ExternalLink from '@/components/ExternalLink.vue'
import MasterGroupLink from '@/components/MasterGroupLink.vue'
@@ -72,6 +67,7 @@ import AffiliatedEntity from '../artist/AffiliatedEntity.vue'
import type { SeriesLite } from '@/services/api/seriesService'
import type { AffiliatedArtistHierarchy } from '@/services/api/artistService'
import type { AffiliatedEntityHierarchy } from '@/services/api/entityService'
import ImagePreview from '../ImagePreview.vue'
const { t } = useI18n()
@@ -121,11 +121,7 @@
<AccordionContent>
<div class="screenshots-container">
<div v-for="(screenshot, index) in slotProps.data.screenshots" :key="index" class="screenshot">
<Image :src="screenshot" preview class="screenshot-image">
<template #previewicon>
<i class="pi pi-search"></i>
</template>
</Image>
<ImagePreview class="screenshot-image" :imageLink="screenshot" />
</div>
</div>
</AccordionContent>
@@ -162,7 +158,6 @@ import { computed, onMounted, ref } from 'vue'
import DataTable from 'primevue/datatable'
import TorrentSlug from '../torrent/TorrentSlug.vue'
import Column from 'primevue/column'
import Image from 'primevue/image'
import BBCodeRenderer from '@/components/community/BBCodeRenderer.vue'
import DOMPurify from 'dompurify'
import Accordion from 'primevue/accordion'
@@ -190,6 +185,7 @@ import { RouterLink } from 'vue-router'
import CreateOrEditTorrent from '../torrent/CreateOrEditTorrent.vue'
import { useUserStore } from '@/stores/user'
import { useEditionGroupStore } from '@/stores/editionGroup'
import ImagePreview from '../ImagePreview.vue'
interface Props {
title_group: TitleGroup | TitleGroupHierarchyLite
+2 -6
View File
@@ -1,10 +1,6 @@
<template>
<div id="user-sidebar">
<Image :src="user.avatar ?? '/default_user_avatar.jpg'" :alt="user.username + '\'s avatar'" preview>
<template #previewicon>
<i class="pi pi-search"></i>
</template>
</Image>
<ImagePreview :imageLink="user.avatar ?? '/default_user_avatar.jpg'" :alt="user.username + '\'s avatar'" />
<ContentContainer :container-title="t('community.statistics')" class="stats-container">
{{ t('user.joined_at') }}:
<span v-tooltip.top="formatDate(user.created_at)">{{ timeAgo(user.created_at) }}</span>
@@ -51,10 +47,10 @@
<script setup lang="ts">
import type { PublicUser, User } from '@/services/api/userService'
import { Image } from 'primevue'
import ContentContainer from '../ContentContainer.vue'
import { useI18n } from 'vue-i18n'
import { bytesToReadable, timeAgo, formatDate } from '@/services/helpers'
import ImagePreview from '../ImagePreview.vue'
const { t } = useI18n()
+2 -1
View File
@@ -91,7 +91,8 @@
}
},
"series": {
"series": "Series"
"series": "Series",
"entry": "Entry | Entries"
},
"forum": {
"forum": "Forum | Forums",
+6 -1
View File
@@ -81,7 +81,12 @@ const router = createRouter({
meta: {
dynamicDocumentTitle: true,
},
component: () => import('../views/SeriesView.vue'),
component: () => import('../views/series/SeriesView.vue'),
},
{
path: '/series',
name: 'Search series',
component: () => import('../views/series/SeriesSearchView.vue'),
},
{
path: '/artist/:id',
@@ -7,6 +7,16 @@ export type Series = components['schemas']['Series']
export type SeriesLite = components['schemas']['SeriesLite']
export type SearchSeriesQuery = components['schemas']['SearchSeriesQuery']
export type SeriesSearchResponse = components['schemas']['SeriesSearchResponse']
export type SeriesSearchResult = components['schemas']['SeriesSearchResult']
export const getSeries = async (id: number): Promise<SeriesAndTitleGroupHierarchyLite> => {
return (await api.get<SeriesAndTitleGroupHierarchyLite>('/series?id=' + id)).data
}
export const searchSeries = async (form: SearchSeriesQuery): Promise<SeriesSearchResponse> => {
return (await api.get<SeriesSearchResponse>('/search/series', { params: form })).data
}
-1
View File
@@ -236,7 +236,6 @@ const fetchTitleGroup = async () => {
}
onBeforeRouteLeave((to, from, next) => {
console.log(to, from, next)
if (to.name !== 'UploadTorrent' && to.name !== 'NewTorrentRequest') {
titleGroupStore.$reset()
useEditionGroupStore().$reset()
@@ -0,0 +1,41 @@
<template>
<div class="wrapper-center actions">
<RouterLink to="/new-series">
<i class="pi pi-plus" v-tooltip.top="t('series.new_series')" />
</RouterLink>
<i class="pi pi-bookmark" v-tooltip.top="t('series.bookmarked_series')" />
</div>
<SeriesSearchForm @gotResults="gotResults" style="margin-bottom: 15px" />
<SeriesTable :series />
</template>
<script setup lang="ts">
import type { SeriesSearchResult } from '@/services/api/seriesService'
import { ref } from 'vue'
import SeriesTable from '@/components/series/SeriesTable.vue'
import { useI18n } from 'vue-i18n'
import SeriesSearchForm from '@/components/series/SeriesSearchForm.vue'
import type { SeriesSearchResponse } from '@/services/api/seriesService'
const { t } = useI18n()
const series = ref<SeriesSearchResult[]>([])
const gotResults = (seriesSearchResponse: SeriesSearchResponse) => {
series.value = seriesSearchResponse.results
}
</script>
<style scoped>
#series-view {
display: flex;
}
.actions {
i {
margin: 10px;
color: white;
}
}
.main-content {
width: 80%;
margin-right: 10px;
}
</style>