Skip to content

Instantly share code, notes, and snippets.

@creazy231
Created August 21, 2023 20:31
Show Gist options
  • Save creazy231/f5e90e97c75c4108f59cb8bc0d02d0da to your computer and use it in GitHub Desktop.
Save creazy231/f5e90e97c75c4108f59cb8bc0d02d0da to your computer and use it in GitHub Desktop.
<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