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">
|
<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 -->
|
||||||
|
|||||||
@@ -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 -->
|
||||||
|
|||||||
@@ -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