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

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>

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>

View File

@@ -1,24 +1,16 @@
<script setup lang="ts">
import { useMediaStore } from '@/stores/media'
import { computed, ref } from 'vue'
import { computed } from 'vue'
import type { MediaType } from '@/types/Media'
import ImageWithFallback from './ImageWithFallback.vue'
const props = defineProps<{
media: MediaType
}>()
const store = useMediaStore()
const imageLoadFailed = ref(false)
const loaded = ref(false)
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>
<template>
@@ -30,40 +22,12 @@ const imageSource = computed(() => {
<router-link
: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">
<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.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>
<ImageWithFallback
:src="props.media.PosterPath"
:alt="props.media.Title"
size="w300"
class="aspect-2/3 object-cover w-full h-full transform transition-transform duration-500 hover:scale-105"
/>
</router-link>
<!-- Body -->

View File

@@ -1,9 +1,11 @@
<script setup lang="ts">
import type { MovieDetailsType } from '@/types/Movie'
import type { TvSeriesDetailsType } from '@/types/TvSeries'
import { computed, ref } from 'vue'
import { computed } from 'vue'
import { useMediaStore } from '@/stores/media'
import type { MediaType } from '@/types/Media'
import ImageWithFallback from './ImageWithFallback.vue'
import MediaTypeBadge from './MediaTypeBadge.vue'
const props = defineProps<{
type: 'movie' | 'tv'
@@ -12,7 +14,6 @@ const props = defineProps<{
tvSeries?: TvSeriesDetailsType | null
}>()
const imageLoaded = ref(false)
const store = useMediaStore()
const alreadyAdded = computed(() =>
@@ -37,20 +38,16 @@ const alreadyAdded = computed(() =>
v-motion-fade-visible-once
>
<!-- Poster -->
<figure class="shrink-0">
<span v-if="!imageLoaded" class="loading loading-ring loading-lg text-primary"></span>
<img
v-show="imageLoaded"
:src="
type === 'movie'
? 'https://image.tmdb.org/t/p/w500' + props.movie!.PosterPath
: 'https://image.tmdb.org/t/p/w500' + props.tvSeries!.PosterPath
"
alt="Poster"
class="w-full max-w-sm rounded-lg shadow-lg transform transition-transform duration-500 hover:scale-105"
@load="imageLoaded = true"
/>
</figure>
<ImageWithFallback
:src="
type === 'movie'
? props.movie!.PosterPath
: props.tvSeries!.PosterPath
"
alt="Poster"
size="w500"
class="shrink-0 w-full max-w-sm rounded-lg shadow-lg transform transition-transform duration-500 hover:scale-105"
/>
<!-- Text -->
<div class="flex-1">
@@ -82,25 +79,19 @@ const alreadyAdded = computed(() =>
<!-- Badges -->
<div class="flex flex-wrap gap-2 mb-6">
<span
<MediaTypeBadge
v-for="g in props.type === 'movie' ? props.movie!.Genres : props.tvSeries!.Genres"
: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"
>
{{ g.name }}
</span>
<span
:text="g.name"
/>
<MediaTypeBadge
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"
>
Runtime: {{ props.movie!.Runtime }} min
</span>
<span
:text="`Runtime: ${props.movie!.Runtime} min`"
/>
<MediaTypeBadge
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"
>
Seasons: {{ props.tvSeries!.NumberOfSeasons }}
</span>
:text="`Seasons: ${props.tvSeries!.NumberOfSeasons}`"
/>
</div>
<!-- Actions -->

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>