4chan-x/src/Images/ImageLoader.js
2023-04-30 13:36:40 +02:00

141 lines
4.4 KiB
JavaScript

import Callbacks from "../classes/Callbacks";
import Header from "../General/Header";
import { g, Conf, d } from "../globals/globals";
import $ from "../platform/$";
/*
* decaffeinate suggestions:
* DS101: Remove unnecessary use of Array.from
* DS102: Remove unnecessary code created because of implicit returns
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md
*/
var ImageLoader = {
init() {
if (!['index', 'thread', 'archive'].includes(g.VIEW)) { return; }
const replace = Conf['Replace JPG'] || Conf['Replace PNG'] || Conf['Replace GIF'] || Conf['Replace WEBM'];
if (!Conf['Image Prefetching'] && !replace) { return; }
Callbacks.Post.push({
name: 'Image Replace',
cb: this.node
});
$.on(d, 'PostsInserted', function() {
if (ImageLoader.prefetchEnabled || replace) {
return g.posts.forEach(ImageLoader.prefetchAll);
}
});
if (Conf['Replace WEBM']) {
$.on(d, 'scroll visibilitychange 4chanXInitFinished PostsInserted', this.playVideos);
}
if (!Conf['Image Prefetching'] || !['index', 'thread'].includes(g.VIEW)) { return; }
const el = $.el('a', {
href: 'javascript:;',
title: 'Prefetch Images',
innerHTML: '🗲︎'
}
);
$.on(el, 'click', this.toggle);
return Header.addShortcut('prefetch', el, 525);
},
node() {
if (this.isClone) { return; }
for (var file of this.files) {
if (Conf['Replace WEBM'] && file.isVideo) { ImageLoader.replaceVideo(this, file); }
ImageLoader.prefetch(this, file);
}
},
replaceVideo(post, file) {
const {thumb} = file;
const video = $.el('video', {
preload: 'none',
loop: true,
muted: true,
poster: thumb.src || thumb.dataset.src,
textContent: thumb.alt,
className: thumb.className
}
);
video.setAttribute('muted', 'muted');
video.dataset.md5 = thumb.dataset.md5;
for (var attr of ['height', 'width', 'maxHeight', 'maxWidth']) { video.style[attr] = thumb.style[attr]; }
video.src = file.url;
$.replace(thumb, video);
file.thumb = video;
return file.videoThumb = true;
},
prefetch(post, file) {
let clone, type;
const {isImage, isVideo, thumb, url} = file;
if (file.isPrefetched || !(isImage || isVideo) || post.isHidden || post.thread.isHidden) { return; }
if (isVideo) {
type = 'WEBM';
} else {
type = url.match(/\.([^.]+)$/)?.[1].toUpperCase();
if (type === 'JPEG') { type = 'JPG'; }
}
const replace = Conf[`Replace ${type}`] && !/spoiler/.test(thumb.src || thumb.dataset.src);
if (!replace && !ImageLoader.prefetchEnabled) { return; }
if ($.hasClass(doc, 'catalog-mode')) { return; }
if (![post, ...Array.from(post.clones)].some(clone => doc.contains(clone.nodes.root))) { return; }
file.isPrefetched = true;
if (file.videoThumb) {
for (clone of post.clones) { clone.file.thumb.preload = 'auto'; }
thumb.preload = 'auto';
// XXX Cloned video elements with poster in Firefox cause momentary display of image loading icon.
if ($.engine === 'gecko') {
$.on(thumb, 'loadeddata', function() { return this.removeAttribute('poster'); });
}
return;
}
const el = $.el(isImage ? 'img' : 'video');
if (isVideo) { el.preload = 'auto'; }
if (replace && isImage) {
$.on(el, 'load', function() {
for (clone of post.clones) { clone.file.thumb.src = url; }
return thumb.src = url;
});
}
return el.src = url;
},
prefetchAll(post) {
for (var file of post.files) {
ImageLoader.prefetch(post, file);
}
},
toggle() {
ImageLoader.prefetchEnabled = !ImageLoader.prefetchEnabled;
this.classList.toggle('disabled', !ImageLoader.prefetchEnabled);
if (ImageLoader.prefetchEnabled) {
g.posts.forEach(ImageLoader.prefetchAll);
}
},
playVideos() {
// Special case: Quote previews are off screen when inserted into document, but quickly moved on screen.
const qpClone = $.id('qp')?.firstElementChild;
return g.posts.forEach(function(post) {
for (post of [post, ...Array.from(post.clones)]) {
for (var file of post.files) {
if (file.videoThumb) {
var {thumb} = file;
if (Header.isNodeVisible(thumb) || (post.nodes.root === qpClone)) { thumb.play(); } else { thumb.pause(); }
}
}
}
});
}
};
export default ImageLoader;