Created
August 21, 2023 20:31
-
-
Save creazy231/f5e90e97c75c4108f59cb8bc0d02d0da to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<template> | |
<div ref="target" class="relative flex h-full w-full items-center justify-center"> | |
<template v-if="targetIsVisible"> | |
<div v-if="!loaded && loader" class="absolute inset-0 z-[2] flex items-center justify-center bg-background"> | |
<UiSpinner /> | |
</div> | |
<div | |
@click="emit('hide')" | |
v-if="full && !isIframe()" | |
class="absolute inset-0 z-1" | |
/> | |
<iframe | |
v-if="isIframe()" | |
:name="`artblocks-embed-${item.id}`" | |
:src="item.media" | |
frameBorder="0" | |
scrolling="no" | |
class="absolute inset-0 z-0 h-full w-full" | |
/> | |
<video | |
@loadeddata="imageLoaded" | |
@error="imageLoaded" | |
v-else-if="item.media_type === 'image' && (ext === 'gif' || ext === 'apng') && !item.media.includes('youtu')" | |
:key="key" | |
data-video="video-1" | |
:data-ref="`moca_video_${item.id}`" | |
class="max-h-full img-fluid w-100" | |
:class="getVideoClass()" | |
:style="[getVideoStyles()]" | |
autoplay | |
:muted="!full" | |
loop | |
playsinline | |
:controls="full" | |
> | |
<source | |
@error="handleVideoError($event, item, `moca_video_${item.id}`)" | |
:src="raw ? item.media : `${config.website.videoProcessingURL}/transformation/${urlSafeBase64(item.media)}/${videoSettings()}`" | |
> | |
</video> | |
<video | |
@loadeddata="imageLoaded" | |
@error="imageLoaded" | |
v-else-if="item.media_type === 'video' && !item.media.includes('youtu')" | |
:key="key" | |
ref="mocaVideo" | |
data-video="video-2" | |
class="max-h-full img-fluid w-100" | |
:class="getVideoClass()" | |
:style="[getVideoStyles()]" | |
autoplay | |
:muted="!full" | |
loop | |
playsinline | |
:controls="full" | |
> | |
<source | |
:src="raw ? item.media : `${config.website.videoProcessingURL}/transformation/${urlSafeBase64(item.media)}/${videoSettings()}` | |
" | |
> | |
<source :src="item.media"> | |
</video> | |
<img | |
@load="imageLoaded" | |
v-else-if="item.media_type === 'image' && ext === 'svg' && item.media.endsWith('svg') && !item.media.includes('youtu')" | |
:key="key" | |
loading="lazy" | |
:class="getImageClass()" | |
class="max-h-full" | |
:src="item.media" | |
:width="getDimension().width" | |
:height="getDimension().height" | |
:alt="item.name" | |
> | |
<NuxtImg | |
@load="imageLoaded" | |
v-else-if="((item.media_type === 'image' || (item.media && item.media.startsWith('data:'))) && !contentTypeSVG) && !item.media.includes('youtu')" | |
:key="key" | |
provider="imgproxy" | |
loading="lazy" | |
:class="getImageClass()" | |
class="max-h-full" | |
:src="item.media" | |
:width="getDimension().width" | |
:height="getDimension().height" | |
:alt="item.name" | |
nuxt-image="true" | |
/> | |
<video | |
@loadeddata="imageLoaded" | |
@error="imageLoaded" | |
v-else-if="item.media_image_type === 'image' && (image_ext === 'gif' || image_ext === 'apng')" | |
:key="key" | |
ref="mocaVideo" | |
data-video="video-3" | |
:data-ref="`moca_video_${item.id}`" | |
class="max-h-full img-fluid w-100" | |
:class="getVideoClass()" | |
:style="[getVideoStyles(true)]" | |
autoplay | |
:muted="!full" | |
loop | |
playsinline | |
:controls="full" | |
> | |
<source | |
@error="handleVideoError($event, item, `moca_video_${item.id}`, true)" | |
:src="raw ? item.media_image : `${config.website.videoProcessingURL}/transformation/${urlSafeBase64(item.media_image)}/${videoSettings()}`" | |
> | |
</video> | |
<video | |
@loadeddata="imageLoaded" | |
@error="imageLoaded" | |
v-else-if="item.media_image_type === 'video'" | |
:key="key" | |
ref="mocaVideo" | |
data-video="video-4" | |
class="max-h-full img-fluid w-100" | |
:class="getVideoClass()" | |
:style="[getVideoStyles(true)]" | |
autoplay | |
:muted="!full" | |
loop | |
playsinline | |
:controls="full" | |
> | |
<source | |
:src="raw ? item.media_image : `${config.website.videoProcessingURL}/transformation/${urlSafeBase64(item.media_image)}/${videoSettings()}`" | |
> | |
<source :src="item.media_image"> | |
</video> | |
<img | |
@load="imageLoaded" | |
v-else-if="item.media_image_type === 'image' && image_ext === 'svg' && item.media.endsWith('svg')" | |
:key="key" | |
loading="lazy" | |
:class="getImageClass()" | |
class="max-h-full" | |
:src="item.media_image" | |
:width="getDimension(true).width" | |
:height="getDimension(true).height" | |
:alt="item.name" | |
> | |
<NuxtImg | |
@load="imageLoaded" | |
v-else-if="item.media_image_type === 'image'" | |
:key="key" | |
provider="imgproxy" | |
loading="lazy" | |
:class="getImageClass()" | |
class="max-h-full" | |
:src="item.media_image" | |
:width="getDimension(true).width" | |
:height="getDimension(true).height" | |
:alt="item.name" | |
nuxt-image="true2" | |
/> | |
<p v-else :key="key"> | |
{{ item.id }} | |
</p> | |
</template> | |
</div> | |
</template> | |
<script lang="ts" setup> | |
import config from "config"; | |
import { encode } from "js-base64"; | |
import { useElementVisibility } from "@vueuse/core"; | |
import type { Ref } from "vue"; | |
interface Item { | |
id: string; | |
tokenId: string; | |
name: string; | |
contract: { | |
address: string; | |
name: string; | |
}; | |
media: string; | |
media_type: string; | |
media_info: MediaInfo; | |
media_image: string; | |
media_image_type: string; | |
media_image_info: MediaInfo; | |
} | |
interface MediaInfo { | |
content_type: string; | |
ext: string; | |
height: number; | |
width: number; | |
} | |
interface MediaSettings { | |
width: number; | |
height: number | string; | |
objectFit: string; | |
} | |
interface Dimension { | |
width: number; | |
height: number; | |
} | |
const props = defineProps({ | |
iframe: { | |
type: Boolean, | |
default: false, | |
}, | |
item: { | |
type: Object, | |
required: true, | |
}, | |
full: { | |
type: Boolean, | |
default: false, | |
}, | |
assetGrid: { | |
type: Boolean, | |
default: false, | |
}, | |
sizeWidth: { | |
type: Number, | |
default: 284, | |
}, | |
sizeHeight: { | |
type: Number, | |
default: undefined, | |
}, | |
raw: { | |
type: Boolean, | |
default: false, | |
}, | |
rounded: { | |
type: Boolean, | |
default: false, | |
}, | |
loader: { | |
type: Boolean, | |
default: true, | |
}, | |
}); | |
const emit = defineEmits([ "hide" ]); | |
function urlSafeBase64(string: string) { | |
return encode(string) | |
.replace(/=/g, "") | |
.replace(/\+/g, "-") | |
.replace(/\//g, "_"); | |
} | |
const item = ref(props.item) as Ref<Item>; | |
const loaded = ref(false); | |
const target = ref(null); | |
const mocaVideo = ref(null); | |
const targetIsVisible = useElementVisibility(target); | |
onBeforeMount(() => { | |
// Handle Whatty Club NFTs because they are defined as aPNG but only have one frame | |
if (item.value?.contract?.address === "0x064d54c858f884698ef34b1cae5d989a2414e1b6") { | |
item.value.media_info.content_type = "image/png"; | |
item.value.media_info.ext = "png"; | |
} | |
}); | |
const key = computed(() => { | |
let key = "moca_image_"; | |
if (item.value?.id) { | |
key += `${item.value.id}_`; | |
} | |
if (props.full) { | |
key += "full_"; | |
} | |
if (props.sizeHeight) { | |
key += `${props.sizeHeight}_`; | |
} | |
if (props.sizeWidth) { | |
key += `${props.sizeWidth}_`; | |
} | |
return `${key}_${loaded.value ? "loaded" : "loading"}`; | |
}); | |
const contentTypeSVG = computed(() => { | |
return item.value?.media_info?.content_type?.includes("svg") || false; | |
}); | |
const height = computed(() => { | |
return item.value?.media_info?.height || 512; | |
}); | |
const width = computed(() => { | |
return item.value?.media_info?.width || 512; | |
}); | |
const ext = computed(() => { | |
return item.value?.media_info?.ext; | |
}); | |
const image_ext = computed(() => { | |
return item.value?.media_image_info?.ext; | |
}); | |
const image_height = computed(() => { | |
return item.value?.media_image_info?.height || 512; | |
}); | |
const image_width = computed(() => { | |
return item.value?.media_image_info?.width || 512; | |
}); | |
function imageLoaded() { | |
loaded.value = true; | |
} | |
function handleVideoError(event, item: Item, ref, image = false) { | |
// https://markus.oberlehner.net/blog/refs-and-the-vue-3-composition-api/ | |
const element = event.target.parentElement; | |
if (element && !image) { | |
element[0] ? element[0].setAttribute("poster", item.media) : element.setAttribute("poster", item.media); | |
} | |
if (element && image) { | |
element[0] ? element[0].setAttribute("poster", item.media_image) : element.setAttribute("poster", item.media_image); | |
} | |
imageLoaded(); | |
} | |
function videoSettings() { | |
const settings = [ "o:mp4" ]; | |
if (props.full) { | |
settings.push(`w:${width.value}`); | |
} else if (props.sizeWidth) { | |
settings.push(`w:${props.sizeWidth}`); | |
} | |
if (props.full) { | |
settings.push(`h:${height.value}`); | |
} else if (props.sizeHeight) { | |
settings.push(`h:${props.sizeHeight}`); | |
} | |
return settings.join(","); | |
} | |
function getWidth(image = false) { | |
if (!image) { | |
if (props.full && width.value) { | |
return width.value; | |
} | |
if (props.sizeWidth) { | |
return props.sizeWidth; | |
} else if (props.sizeHeight) { | |
return Number((100 / height.value * props.sizeHeight / 100 * width.value).toFixed(0)); | |
} | |
} else { | |
if (props.full && image_width.value) { | |
return image_width.value; | |
} | |
if (props.sizeWidth) { | |
return props.sizeWidth; | |
} else if (props.sizeHeight) { | |
return Number((100 / image_height.value * props.sizeHeight / 100 * image_width.value).toFixed(0)); | |
} | |
} | |
} | |
function getHeight(image = false) { | |
if (!image) { | |
if (props.full && height.value) { | |
return height.value; | |
} | |
if (props.sizeHeight) { | |
return props.sizeHeight; | |
} else if (props.sizeWidth) { | |
return Number((100 / width.value * props.sizeWidth / 100 * height.value).toFixed(0)); | |
} | |
} else { | |
if (props.full && image_height.value) { | |
return image_height.value; | |
} | |
if (props.sizeHeight) { | |
return props.sizeHeight; | |
} else if (props.sizeWidth) { | |
return Number((100 / image_width.value * props.sizeWidth / 100 * image_height.value).toFixed(0)); | |
} | |
} | |
} | |
function getDimension(image = false) { | |
const dimension = { | |
width: getWidth(image), | |
height: getHeight(image), | |
}; | |
/** | |
* Do this while loop to max the height and width to 1280px | |
*/ | |
const maxSize = 1280; | |
if (dimension.width > 0 && dimension.height > 0) { | |
while (dimension.width > maxSize || dimension.height > maxSize) { | |
if (dimension.width > maxSize) { | |
if (dimension.height > 0) { | |
const percent = 100 / dimension.width * maxSize; | |
dimension.height = Math.round(dimension.height * percent / 100); | |
dimension.width = maxSize; | |
} else { | |
dimension.width = maxSize; | |
} | |
} | |
if (dimension.height > maxSize) { | |
if (dimension.width > 0) { | |
const percent = 100 / dimension.height * maxSize; | |
dimension.width = Math.round(dimension.width * percent / 100); | |
dimension.height = maxSize; | |
} else { | |
dimension.height = maxSize; | |
} | |
} | |
} | |
} | |
return dimension; | |
} | |
function getVideoStyles(image = false): MediaSettings { | |
const styles = { | |
width: `${getDimension(image).width}px`, | |
} as MediaSettings; | |
if (props.assetGrid) { | |
styles.height = "100%"; | |
} else { | |
styles.height = `${getDimension(image).height}px`; | |
styles.objectFit = "cover"; | |
} | |
if (props.full) { | |
styles.width = props.sizeWidth; | |
styles.height = props.sizeHeight; | |
styles.objectFit = "contain"; | |
} | |
return styles; | |
} | |
function getImageClass() { | |
if (props.assetGrid) { | |
return "object-contain w-full h-full"; | |
} else if (props.full) { | |
return "object-contain"; | |
} else { | |
return "h-full object-cover w-full h-full"; | |
} | |
} | |
function getVideoClass() { | |
if (props.assetGrid) { | |
return "object-contain w-full h-full"; | |
} else if (props.full) { | |
return "object-contain"; | |
} else { | |
return "h-full object-cover w-full h-full"; | |
} | |
} | |
function isIframe() { | |
return props.iframe && item.value?.media_type === "text" && item.value?.media_info.content_type === "text/html" && item.value?.media.includes("artblocks"); | |
} | |
</script> | |
<style lang="scss" scoped> | |
video, img { | |
@apply z-1; | |
} | |
</style> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment