Improve and add some components

This commit is contained in:
2025-10-29 09:59:24 +03:30
parent 1e112a2719
commit b0af5246bb
5 changed files with 125 additions and 75 deletions
+60
View File
@@ -0,0 +1,60 @@
<script setup lang="ts">
import { computed, ref } from 'vue'
interface Props {
src: string
alt: string
size?: string
}
const props = withDefaults(defineProps<Props>(), {
size: 'w300'
})
const imageLoadFailed = ref(false)
const loaded = ref(false)
const imageSource = computed(() => {
if (!props.src) {
return ''
}
return `https://image.tmdb.org/t/p/${props.size}${props.src}`
})
</script>
<template>
<figure class="overflow-hidden flex items-center justify-center bg-gray-50">
<span v-if="!loaded" class="loading loading-ring loading-lg text-primary"></span>
<div v-else-if="imageLoadFailed" class="flex items-center justify-center">
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-12 h-12 text-gray-300"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"
/>
</svg>
</div>
<img
v-show="loaded && !imageLoadFailed"
:src="imageSource"
:alt="props.alt"
class="object-cover w-full h-full"
@load="loaded = true"
@error="
() => {
imageLoadFailed = true
loaded = true
}
"
/>
</figure>
</template>
+17
View File
@@ -0,0 +1,17 @@
<script setup lang="ts">
interface Props {
size?: 'xs' | 'sm' | 'md' | 'lg'
color?: string
}
const props = withDefaults(defineProps<Props>(), {
size: 'md',
color: 'primary'
})
</script>
<template>
<span
:class="`loading loading-ring loading-${props.size} text-${props.color}`"
></span>
</template>
+8 -44
View File
@@ -1,24 +1,16 @@
<script setup lang="ts"> <script setup lang="ts">
import { useMediaStore } from '@/stores/media' import { useMediaStore } from '@/stores/media'
import { computed, ref } from 'vue' import { computed } from 'vue'
import type { MediaType } from '@/types/Media' import type { MediaType } from '@/types/Media'
import ImageWithFallback from './ImageWithFallback.vue'
const props = defineProps<{ const props = defineProps<{
media: MediaType media: MediaType
}>() }>()
const store = useMediaStore() const store = useMediaStore()
const imageLoadFailed = ref(false)
const loaded = ref(false)
const alreadyAdded = computed(() => store.mediaList.some((media) => media.Id === props.media.Id)) const alreadyAdded = computed(() => store.mediaList.some((media) => media.Id === props.media.Id))
const imageSource = computed(() => {
if (!props.media.PosterPath) {
return ''
}
return `https://image.tmdb.org/t/p/w300${props.media.PosterPath}`
})
</script> </script>
<template> <template>
@@ -30,40 +22,12 @@ const imageSource = computed(() => {
<router-link <router-link
:to="{ name: 'details', params: { type: props.media.MediaType, id: props.media.Id } }" :to="{ name: 'details', params: { type: props.media.MediaType, id: props.media.Id } }"
> >
<figure class="overflow-hidden flex items-center justify-center aspect-2/3 bg-gray-50"> <ImageWithFallback
<span v-if="!loaded" class="loading loading-ring loading-lg text-primary"></span> :src="props.media.PosterPath"
:alt="props.media.Title"
<div v-else-if="imageLoadFailed" class="flex items-center justify-center"> size="w300"
<svg class="aspect-2/3 object-cover w-full h-full transform transition-transform duration-500 hover:scale-105"
xmlns="http://www.w3.org/2000/svg" />
fill="none"
viewBox="0 0 24 24"
stroke-width="1.5"
stroke="currentColor"
class="w-12 h-12 text-gray-300"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"
/>
</svg>
</div>
<img
v-show="loaded && !imageLoadFailed"
:src="imageSource"
:alt="props.media.Title"
class="object-cover w-full h-full transform transition-transform duration-500 hover:scale-105"
@load="loaded = true"
@error="
() => {
imageLoadFailed = true
loaded = true
}
"
/>
</figure>
</router-link> </router-link>
<!-- Body --> <!-- Body -->
+22 -31
View File
@@ -1,9 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import type { MovieDetailsType } from '@/types/Movie' import type { MovieDetailsType } from '@/types/Movie'
import type { TvSeriesDetailsType } from '@/types/TvSeries' import type { TvSeriesDetailsType } from '@/types/TvSeries'
import { computed, ref } from 'vue' import { computed } from 'vue'
import { useMediaStore } from '@/stores/media' import { useMediaStore } from '@/stores/media'
import type { MediaType } from '@/types/Media' import type { MediaType } from '@/types/Media'
import ImageWithFallback from './ImageWithFallback.vue'
import MediaTypeBadge from './MediaTypeBadge.vue'
const props = defineProps<{ const props = defineProps<{
type: 'movie' | 'tv' type: 'movie' | 'tv'
@@ -12,7 +14,6 @@ const props = defineProps<{
tvSeries?: TvSeriesDetailsType | null tvSeries?: TvSeriesDetailsType | null
}>() }>()
const imageLoaded = ref(false)
const store = useMediaStore() const store = useMediaStore()
const alreadyAdded = computed(() => const alreadyAdded = computed(() =>
@@ -37,20 +38,16 @@ const alreadyAdded = computed(() =>
v-motion-fade-visible-once v-motion-fade-visible-once
> >
<!-- Poster --> <!-- Poster -->
<figure class="shrink-0"> <ImageWithFallback
<span v-if="!imageLoaded" class="loading loading-ring loading-lg text-primary"></span> :src="
<img type === 'movie'
v-show="imageLoaded" ? props.movie!.PosterPath
:src=" : props.tvSeries!.PosterPath
type === 'movie' "
? 'https://image.tmdb.org/t/p/w500' + props.movie!.PosterPath alt="Poster"
: 'https://image.tmdb.org/t/p/w500' + props.tvSeries!.PosterPath size="w500"
" class="shrink-0 w-full max-w-sm rounded-lg shadow-lg transform transition-transform duration-500 hover:scale-105"
alt="Poster" />
class="w-full max-w-sm rounded-lg shadow-lg transform transition-transform duration-500 hover:scale-105"
@load="imageLoaded = true"
/>
</figure>
<!-- Text --> <!-- Text -->
<div class="flex-1"> <div class="flex-1">
@@ -82,25 +79,19 @@ const alreadyAdded = computed(() =>
<!-- Badges --> <!-- Badges -->
<div class="flex flex-wrap gap-2 mb-6"> <div class="flex flex-wrap gap-2 mb-6">
<span <MediaTypeBadge
v-for="g in props.type === 'movie' ? props.movie!.Genres : props.tvSeries!.Genres" v-for="g in props.type === 'movie' ? props.movie!.Genres : props.tvSeries!.Genres"
:key="g.id" :key="g.id"
class="px-3 py-1 rounded-md text-sm bg-linear-to-r from-gray-100 to-gray-200 text-gray-700 border border-gray-300" :text="g.name"
> />
{{ g.name }} <MediaTypeBadge
</span>
<span
v-if="props.type === 'movie'" v-if="props.type === 'movie'"
class="px-3 py-1 rounded-md text-sm bg-linear-to-r from-gray-100 to-gray-200 text-gray-700 border border-gray-300" :text="`Runtime: ${props.movie!.Runtime} min`"
> />
Runtime: {{ props.movie!.Runtime }} min <MediaTypeBadge
</span>
<span
v-if="props.type === 'tv'" v-if="props.type === 'tv'"
class="px-3 py-1 rounded-md text-sm bg-linear-to-r from-gray-100 to-gray-200 text-gray-700 border border-gray-300" :text="`Seasons: ${props.tvSeries!.NumberOfSeasons}`"
> />
Seasons: {{ props.tvSeries!.NumberOfSeasons }}
</span>
</div> </div>
<!-- Actions --> <!-- Actions -->
+18
View File
@@ -0,0 +1,18 @@
<script setup lang="ts">
interface Props {
text: string
type?: 'info' | 'success' | 'warning' | 'error' | 'primary' | 'secondary' | 'accent' | 'ghost'
}
const props = withDefaults(defineProps<Props>(), {
type: 'primary'
})
</script>
<template>
<span
:class="`px-3 py-1 rounded-md text-sm bg-linear-to-r from-gray-100 to-gray-200 ${type ? 'badge-' + type : ''} text-gray-700 border border-gray-300`"
>
{{ props.text }}
</span>
</template>