88 lines
2.3 KiB
Vue
88 lines
2.3 KiB
Vue
<script setup lang="ts">
|
|
import { computed, ref } from 'vue'
|
|
import AppLogo from '@/components/AppLogo.vue'
|
|
|
|
const TONE_COUNT = 4
|
|
const HEX_RADIX = 16
|
|
const ALPHA_TYPES = ['image/png', 'image/svg+xml']
|
|
const SVG_TYPE = 'image/svg+xml'
|
|
|
|
const props = withDefaults(
|
|
defineProps<{
|
|
item: { url: string; content_type: string; sha256: string }
|
|
size?: 'sm' | 'md' | 'lg' | 'xl'
|
|
}>(),
|
|
{ size: 'lg' },
|
|
)
|
|
|
|
const loaded = ref(false)
|
|
const failed = ref(false)
|
|
|
|
const hasAlpha = computed(() => ALPHA_TYPES.includes(props.item.content_type))
|
|
const isSvg = computed(() => props.item.content_type === SVG_TYPE)
|
|
const tone = computed(() => {
|
|
const digit = Number.parseInt(props.item.sha256.charAt(0), HEX_RADIX)
|
|
return Number.isNaN(digit) ? 0 : digit % TONE_COUNT
|
|
})
|
|
const iconClass = computed(
|
|
() => ({ sm: 'size-4', md: 'size-5', lg: 'size-6', xl: 'size-10' })[props.size],
|
|
)
|
|
const logoSize = computed(() => ({ sm: 16, md: 20, lg: 28, xl: 40 })[props.size])
|
|
</script>
|
|
|
|
<template>
|
|
<div
|
|
data-testid="media-thumb"
|
|
class="relative flex shrink-0 items-center justify-center overflow-hidden text-muted"
|
|
:class="hasAlpha ? 'media-thumb-checker' : ['media-thumb-tone', `media-thumb-tone-${tone}`]"
|
|
>
|
|
<span v-if="!loaded" aria-hidden="true" class="absolute flex">
|
|
<AppLogo v-if="isSvg" :size="logoSize" aria-hidden="true" />
|
|
<UIcon v-else name="i-lucide-image" class="opacity-55" :class="iconClass" />
|
|
</span>
|
|
<img
|
|
v-if="!failed && item.url"
|
|
:src="item.url"
|
|
alt=""
|
|
loading="lazy"
|
|
class="absolute inset-0 size-full object-cover"
|
|
@load="loaded = true"
|
|
@error="failed = true"
|
|
/>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.media-thumb-checker {
|
|
background: conic-gradient(
|
|
var(--ui-bg-accented) 25%,
|
|
var(--ui-bg) 0 50%,
|
|
var(--ui-bg-accented) 0 75%,
|
|
var(--ui-bg) 0
|
|
)
|
|
0 0 / 16px 16px;
|
|
}
|
|
.media-thumb-tone {
|
|
background-image: repeating-linear-gradient(
|
|
135deg,
|
|
color-mix(in srgb, var(--ui-text-muted) 12%, transparent) 0 1px,
|
|
transparent 1px 12px
|
|
);
|
|
}
|
|
.media-thumb-tone-0 {
|
|
background-color: var(--ui-color-secondary-200);
|
|
}
|
|
.dark .media-thumb-tone-0 {
|
|
background-color: var(--ui-color-secondary-300);
|
|
}
|
|
.media-thumb-tone-1 {
|
|
background-color: var(--ui-bg-accented);
|
|
}
|
|
.media-thumb-tone-2 {
|
|
background-color: color-mix(in srgb, var(--ui-border-accented) 35%, var(--ui-bg));
|
|
}
|
|
.media-thumb-tone-3 {
|
|
background-color: var(--ui-bg-elevated);
|
|
}
|
|
</style>
|