2022-01-04 20:26:05 +00:00
|
|
|
<script lang="ts">
|
|
|
|
import { fileTypeFromBuffer } from 'file-type'
|
|
|
|
import { settings } from './stores'
|
2022-01-05 01:14:23 +00:00
|
|
|
import { beforeUpdate, tick } from 'svelte'
|
|
|
|
import type {EmbeddedFile} from './main';
|
2022-01-04 20:26:05 +00:00
|
|
|
|
2022-01-05 01:14:23 +00:00
|
|
|
export let file: EmbeddedFile
|
2022-01-04 20:26:05 +00:00
|
|
|
let isVideo = false
|
|
|
|
let isImage = false
|
|
|
|
let isAudio = false
|
|
|
|
let isFile = false
|
|
|
|
let url = ''
|
|
|
|
let settled = false
|
|
|
|
let contracted = true
|
|
|
|
let hovering = false
|
2022-01-05 19:12:12 +00:00
|
|
|
let ftype = '';
|
2022-01-04 20:26:05 +00:00
|
|
|
|
|
|
|
let place: HTMLDivElement
|
|
|
|
let hoverElem: HTMLDivElement
|
|
|
|
let imgElem: HTMLImageElement
|
|
|
|
let videoElem: HTMLVideoElement
|
|
|
|
let hoverVideo: HTMLVideoElement
|
|
|
|
let dims: [number, number] = [0, 0]
|
2022-01-05 01:14:23 +00:00
|
|
|
let furl: string | undefined = undefined;
|
2022-01-04 20:26:05 +00:00
|
|
|
|
2022-01-05 19:12:12 +00:00
|
|
|
let visible = false;
|
|
|
|
|
2022-01-04 20:26:05 +00:00
|
|
|
beforeUpdate(async () => {
|
|
|
|
if (settled) return
|
|
|
|
settled = true
|
2022-01-05 01:14:23 +00:00
|
|
|
|
|
|
|
const thumb = file.thumbnail || file.data;
|
|
|
|
const type = await fileTypeFromBuffer(thumb);
|
|
|
|
url = URL.createObjectURL(new Blob([thumb], { type: type?.mime }))
|
2022-01-04 20:26:05 +00:00
|
|
|
if (!type) {
|
|
|
|
isFile = true
|
2022-01-05 01:14:23 +00:00
|
|
|
return;
|
2022-01-04 20:26:05 +00:00
|
|
|
}
|
2022-01-05 19:12:12 +00:00
|
|
|
ftype = type.mime;
|
2022-01-04 20:26:05 +00:00
|
|
|
isVideo = type.mime.startsWith('video/')
|
|
|
|
isAudio = type.mime.startsWith('audio/')
|
|
|
|
isImage = type.mime.startsWith('image/')
|
|
|
|
|
|
|
|
if (isImage) contracted = !$settings.xpi
|
|
|
|
if (isVideo) {
|
|
|
|
contracted = !$settings.xpv
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2022-01-05 17:31:16 +00:00
|
|
|
let unzipping = false;
|
|
|
|
let progress = [0, 0]
|
2022-01-05 01:14:23 +00:00
|
|
|
async function unzip() {
|
|
|
|
if (!file.thumbnail)
|
|
|
|
return;
|
2022-01-05 17:31:16 +00:00
|
|
|
unzipping = true;
|
|
|
|
let lisn = new EventTarget();
|
|
|
|
lisn.addEventListener("progress", (e: any) => {
|
|
|
|
progress = e.detail
|
|
|
|
});
|
|
|
|
let full = await file.data(lisn);
|
2022-01-05 01:14:23 +00:00
|
|
|
const type = await fileTypeFromBuffer(full);
|
|
|
|
furl = URL.createObjectURL(new Blob([full], { type: type?.mime }));
|
2022-01-05 17:31:16 +00:00
|
|
|
unzipping = false;
|
2022-01-05 01:14:23 +00:00
|
|
|
if (!type)
|
|
|
|
return;
|
|
|
|
isVideo = type.mime.startsWith('video/')
|
|
|
|
isAudio = type.mime.startsWith('audio/')
|
|
|
|
isImage = type.mime.startsWith('image/')
|
|
|
|
if (hovering) {
|
|
|
|
// reset hovering to recompute proper image coordinates
|
2022-01-05 16:38:02 +00:00
|
|
|
setTimeout(recompute, 10);
|
2022-01-05 01:14:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-04 20:26:05 +00:00
|
|
|
function hasAudio(video: any) {
|
|
|
|
return (
|
|
|
|
video.mozHasAudio ||
|
2022-01-05 01:14:23 +00:00
|
|
|
!!(video.webkitAudioDecodedByteCount) ||
|
|
|
|
!!(video.audioTracks && video.audioTracks.length)
|
2022-01-04 20:26:05 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2022-01-05 01:14:23 +00:00
|
|
|
async function bepis() {
|
2022-01-04 20:26:05 +00:00
|
|
|
contracted = !contracted
|
|
|
|
if (hovering) hoverStop()
|
|
|
|
if (contracted && isVideo) {
|
|
|
|
videoElem.controls = false
|
|
|
|
videoElem.pause()
|
|
|
|
}
|
|
|
|
if (!contracted && isVideo) {
|
|
|
|
videoElem.controls = true
|
|
|
|
// has to be delayed
|
2022-01-05 18:05:54 +00:00
|
|
|
setTimeout(async () => {
|
|
|
|
videoElem.currentTime = hoverVideo.currentTime || 0;
|
|
|
|
await videoElem.play()
|
|
|
|
}, 10)
|
2022-01-05 01:14:23 +00:00
|
|
|
}
|
|
|
|
if (file.thumbnail && !furl) {
|
|
|
|
// don't know how you managed to click before hovering but oh well
|
|
|
|
unzip()
|
2022-01-04 20:26:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-01-05 01:14:23 +00:00
|
|
|
function recompute() {
|
2022-01-04 20:26:05 +00:00
|
|
|
const [sw, sh] = [visualViewport.width, visualViewport.height]
|
|
|
|
|
|
|
|
let [iw, ih] = [0, 0]
|
|
|
|
if (isImage) {
|
|
|
|
;[iw, ih] = [imgElem.naturalWidth, imgElem.naturalHeight]
|
|
|
|
} else {
|
|
|
|
;[iw, ih] = [videoElem.videoWidth, videoElem.videoHeight]
|
|
|
|
}
|
|
|
|
let scale = Math.min(1, sw / iw, sh / ih)
|
|
|
|
dims = [~~(iw * scale), ~~(ih * scale)]
|
|
|
|
|
|
|
|
hoverElem.style.width = `${dims[0]}px`
|
|
|
|
hoverElem.style.height = `${dims[1]}px`
|
2022-01-05 01:14:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function hoverStart(ev?: MouseEvent) {
|
|
|
|
if ($settings.dh)return;
|
|
|
|
if (file.thumbnail && !furl) {
|
|
|
|
unzip();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isImage && !isVideo) return
|
|
|
|
if (!contracted) return
|
|
|
|
|
|
|
|
recompute();
|
2022-01-05 16:38:02 +00:00
|
|
|
hovering = true
|
2022-01-05 01:14:23 +00:00
|
|
|
|
2022-01-05 15:56:45 +00:00
|
|
|
if (isVideo){
|
|
|
|
try {
|
|
|
|
await hoverVideo.play()
|
|
|
|
} catch (e) {
|
|
|
|
// probably didn't interact with document error, mute the video and try again?
|
|
|
|
hoverVideo.muted = true;
|
|
|
|
hoverVideo.volume = 0;
|
|
|
|
await hoverVideo.play()
|
|
|
|
}
|
|
|
|
}
|
2022-01-05 01:14:23 +00:00
|
|
|
}
|
2022-01-04 20:26:05 +00:00
|
|
|
|
|
|
|
function hoverStop(ev?: MouseEvent) {
|
2022-01-05 01:14:23 +00:00
|
|
|
if ($settings.dh) return;
|
2022-01-04 20:26:05 +00:00
|
|
|
hovering = false
|
|
|
|
if (isVideo) hoverVideo.pause()
|
|
|
|
}
|
|
|
|
|
|
|
|
function hoverUpdate(ev: MouseEvent) {
|
2022-01-05 01:14:23 +00:00
|
|
|
if ($settings.dh) return;
|
2022-01-04 20:26:05 +00:00
|
|
|
if (!contracted) return
|
|
|
|
const [sw, sh] = [visualViewport.width, visualViewport.height]
|
|
|
|
// shamelessly stolen from 4chanX
|
|
|
|
let width = dims[0]
|
|
|
|
let height = dims[1] + 25
|
|
|
|
let { clientX, clientY } = ev
|
|
|
|
let top = Math.max(0, (clientY * (sh - height)) / sh)
|
|
|
|
let threshold = sw / 2
|
|
|
|
let marginX: number | string =
|
|
|
|
(clientX <= threshold ? clientX : sw - clientX) + 45
|
|
|
|
marginX = Math.min(marginX, sw - width)
|
|
|
|
marginX = marginX + 'px'
|
|
|
|
let [left, right] = clientX <= threshold ? [marginX, ''] : ['', marginX]
|
|
|
|
let { style } = hoverElem
|
|
|
|
style.top = top + 'px'
|
|
|
|
style.left = left
|
|
|
|
style.right = right
|
|
|
|
}
|
|
|
|
|
|
|
|
function adjustAudio(ev: WheelEvent) {
|
|
|
|
if (!isVideo) return
|
|
|
|
if (hasAudio(videoElem)) {
|
|
|
|
let vol = videoElem.volume * (ev.deltaY > 0 ? 0.9 : 1.1);
|
2022-01-05 18:05:54 +00:00
|
|
|
vol = Math.max(0, Math.min(1, vol));
|
|
|
|
videoElem.volume = vol;
|
2022-01-04 20:26:05 +00:00
|
|
|
hoverVideo.volume = videoElem.volume;
|
2022-01-05 18:05:54 +00:00
|
|
|
hoverVideo.muted = vol < 0;
|
2022-01-04 20:26:05 +00:00
|
|
|
ev.preventDefault()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
2022-01-05 19:12:12 +00:00
|
|
|
|
|
|
|
{#if $settings.eye}
|
|
|
|
<span on:click={() => visible = !visible} class:fa-eye={!visible} class:fa-eye-slash={visible} class="fa clickable" />
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
|
|
{#if !$settings.eye || visible}
|
|
|
|
<!-- svelte-ignore a11y-mouse-events-have-key-events -->
|
|
|
|
<div
|
|
|
|
class:contract={contracted}
|
|
|
|
class="place fileThumb"
|
|
|
|
on:click={() => bepis()}
|
|
|
|
on:mouseover={hoverStart}
|
|
|
|
on:mouseout={hoverStop}
|
|
|
|
on:mousemove={hoverUpdate}
|
|
|
|
on:wheel={adjustAudio}
|
|
|
|
bind:this={place}
|
|
|
|
>
|
|
|
|
{#if isImage}
|
|
|
|
<img bind:this={imgElem} alt={file.filename} src={furl || url} />
|
|
|
|
{/if}
|
|
|
|
{#if isAudio}
|
|
|
|
<audio controls loop={$settings.loop} alt={file.filename}>
|
|
|
|
<source src={url} type={ftype} />
|
|
|
|
</audio>
|
|
|
|
{/if}
|
|
|
|
{#if isVideo}
|
|
|
|
<!-- svelte-ignore a11y-media-has-caption -->
|
|
|
|
<video loop={$settings.loop} bind:this={videoElem} src={furl || url} />
|
|
|
|
<!-- assoom videos will never be loaded from thumbnails -->
|
|
|
|
{/if}
|
|
|
|
{#if isFile}
|
|
|
|
<button>Download {file.filename}</button>
|
|
|
|
{/if}
|
|
|
|
</div>
|
|
|
|
<div
|
|
|
|
bind:this={hoverElem}
|
|
|
|
class:visible={hovering && contracted}
|
|
|
|
class:unzipping
|
|
|
|
class="hoverer"
|
|
|
|
>
|
|
|
|
{#if unzipping}<span class="progress">[{progress[0]} / {progress[1]}]</span
|
|
|
|
>{/if}
|
|
|
|
|
|
|
|
{#if isImage}
|
|
|
|
<img alt={file.filename} src={furl || url} />
|
|
|
|
{/if}
|
|
|
|
{#if isVideo}
|
|
|
|
<!-- svelte-ignore a11y-media-has-caption -->
|
|
|
|
<video loop={$settings.loop} bind:this={hoverVideo} src={furl || url} />
|
|
|
|
<!-- assoom videos will never be loaded from thumbnails -->
|
|
|
|
{/if}
|
|
|
|
</div>
|
|
|
|
{/if}
|
2022-01-04 20:26:05 +00:00
|
|
|
|
|
|
|
<style scoped>
|
2022-01-05 19:12:12 +00:00
|
|
|
.clickable {
|
|
|
|
cursor: pointer;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-01-04 20:26:05 +00:00
|
|
|
.place {
|
|
|
|
cursor: pointer;
|
2022-01-05 17:31:16 +00:00
|
|
|
max-width: 100vw;
|
|
|
|
max-height: 100vh;
|
|
|
|
}
|
|
|
|
|
|
|
|
.unzipping > img {
|
|
|
|
filter: brightness(0.5) blur(10px);
|
|
|
|
}
|
|
|
|
|
|
|
|
.progress {
|
|
|
|
color: black;
|
|
|
|
-webkit-text-stroke: 0.7px white;
|
|
|
|
font-weight: bold;
|
|
|
|
left: 50%;
|
|
|
|
top: 50%;
|
|
|
|
font-size: larger;
|
|
|
|
display: inline-block;
|
|
|
|
position: absolute;
|
|
|
|
z-index: 10;
|
2022-01-04 20:26:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
.hoverer {
|
|
|
|
display: none;
|
|
|
|
position: fixed;
|
|
|
|
pointer-events: none;
|
|
|
|
}
|
|
|
|
|
|
|
|
.visible {
|
|
|
|
display: block;
|
2022-01-05 15:56:45 +00:00
|
|
|
z-index: 9;
|
2022-01-04 20:26:05 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
.contract > img,
|
|
|
|
.contract > video {
|
|
|
|
max-width: 125px;
|
|
|
|
max-height: 125px;
|
|
|
|
width: auto;
|
|
|
|
height: auto;
|
|
|
|
}
|
|
|
|
|
|
|
|
.place:not(.contract) > video,
|
|
|
|
.place:not(.contract) > img,
|
|
|
|
.hoverer > video,
|
|
|
|
.hoverer > img {
|
2022-01-05 15:56:45 +00:00
|
|
|
max-width: 100vw;
|
|
|
|
max-height: 100vh;
|
2022-01-04 20:26:05 +00:00
|
|
|
}
|
|
|
|
</style>
|