mirror of
https://github.com/mmahdium/TBW.git
synced 2026-08-14 16:22:48 +03:30
Improve and add some components
This commit is contained in:
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
<ImageWithFallback
|
||||
:src="props.media.PosterPath"
|
||||
: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
|
||||
}
|
||||
"
|
||||
size="w300"
|
||||
class="aspect-2/3 object-cover w-full h-full transform transition-transform duration-500 hover:scale-105"
|
||||
/>
|
||||
</figure>
|
||||
</router-link>
|
||||
|
||||
<!-- Body -->
|
||||
|
||||
@@ -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"
|
||||
<ImageWithFallback
|
||||
:src="
|
||||
type === 'movie'
|
||||
? 'https://image.tmdb.org/t/p/w500' + props.movie!.PosterPath
|
||||
: 'https://image.tmdb.org/t/p/w500' + props.tvSeries!.PosterPath
|
||||
? props.movie!.PosterPath
|
||||
: 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"
|
||||
size="w500"
|
||||
class="shrink-0 w-full max-w-sm rounded-lg shadow-lg transform transition-transform duration-500 hover:scale-105"
|
||||
/>
|
||||
</figure>
|
||||
|
||||
<!-- 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 -->
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user