Optimize media loading across app (#10592)

* v-image and "lazy load" working

* fixed vars

* all the other img uses

* No longer require access token in url for files

* Add lazy loading and size limits

* Rename map-component source prop

* Fix lint warning

* Update app/src/views/public/public-view.vue

Co-authored-by: ian <licitdev@gmail.com>

* Fix lint

* Fix missing file type icon

* Fix null imageInfo error

* Use video.js for media playback

* Fix .js file display

* Update package-lock.json

* Update package-lock.json

* update package.json

* Update pnpm-lock.yaml

* Remove unrelated addition on VDatePicker

* Remove folder abstraction

* Use image data based aspect ratio on preview

* Base app rate throttle on API rate limit config

* Configure app rate limit throttle based on api config

* Convert v-image to script[setup]

* Convert v-media to script[setup]

* Cleanup v-media

* Remove unneeded addTokenToUrl usages

* Remove video.js

It doesn't do authorization headers for mp4/mp3, so it's pointless

Co-authored-by: rijkvanzanten <rijkvanzanten@me.com>
Co-authored-by: ian <licitdev@gmail.com>
This commit is contained in:
Jay Cammarano
2022-07-20 15:52:38 -04:00
committed by GitHub
parent 9d738d12ce
commit 820457690f
27 changed files with 259 additions and 155 deletions

View File

@@ -29,6 +29,7 @@ import VForm from './v-form';
import VHover from './v-hover/';
import VHighlight from './v-highlight.vue';
import VIcon from './v-icon/';
import VImage from './v-image.vue';
import VIconFile from './v-icon-file.vue';
import VInfo from './v-info/';
import VInput from './v-input/';
@@ -81,6 +82,7 @@ export function registerComponents(app: App): void {
app.component('VHover', VHover);
app.component('VHighlight', VHighlight);
app.component('VIcon', VIcon);
app.component('VImage', VImage);
app.component('VIconFile', VIconFile);
app.component('VInfo', VInfo);
app.component('VInput', VInput);

View File

@@ -0,0 +1,87 @@
<template>
<img ref="imageElement" :src="inView ? srcData : emptyPixel" v-bind="attrsWithoutSrc" />
</template>
<script lang="ts" setup>
import { ref, computed, onMounted, onUnmounted, useAttrs } from 'vue';
import { omit } from 'lodash';
import api from '@/api';
interface Props {
src: string;
}
const props = defineProps<Props>();
const emit = defineEmits(['error']);
const attrs = useAttrs();
const imageElement = ref<HTMLImageElement>();
const inView = ref(false);
const emptyPixel =
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=';
const srcData = ref<string>(emptyPixel);
let loaded = false;
const observer = new IntersectionObserver(async (entries) => {
if (entries.length === 0) return;
inView.value = entries[0].isIntersecting;
if (entries[0].isIntersecting && !loaded && props.src) {
try {
const res = await api.get(props.src, {
responseType: 'arraybuffer',
params: {
download: true,
},
});
if (res.headers['content-type'].startsWith('image') === false) return;
const contentType = res.headers['content-type'];
const data = new Uint8Array(res.data);
// 5mb
if (data.length > 1048576 * 5) {
emit('error', new Error('Image too big to render'));
return;
}
let raw = '';
data.forEach((byte) => {
raw += String.fromCharCode(byte);
});
const base64 = btoa(raw);
srcData.value = `data:${contentType};base64,${base64}`;
} catch (err) {
emit('error', err);
} finally {
loaded = true;
}
}
});
onMounted(() => {
if (!imageElement.value) return;
observer.observe(imageElement.value);
});
onUnmounted(() => {
observer.disconnect();
});
const attrsWithoutSrc = computed(() => omit(attrs, ['src']));
</script>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
inheritAttrs: false,
});
</script>