mirror of
https://github.com/philomena-dev/philomena.git
synced 2024-12-05 00:57:59 +01:00
174 lines
5.6 KiB
JavaScript
174 lines
5.6 KiB
JavaScript
import { $$, clearEl } from './utils/dom';
|
|
import store from './utils/store';
|
|
|
|
const imageVersions = {
|
|
// [width, height]
|
|
small: [320, 240],
|
|
medium: [800, 600],
|
|
large: [1280, 1024],
|
|
};
|
|
|
|
/**
|
|
* Picks the appropriate image version for a given width and height
|
|
* of the viewport and the image dimensions.
|
|
*/
|
|
function selectVersion(imageWidth, imageHeight, imageSize, imageMime) {
|
|
let viewWidth = document.documentElement.clientWidth,
|
|
viewHeight = document.documentElement.clientHeight;
|
|
|
|
// load hires if that's what you asked for
|
|
if (store.get('serve_hidpi')) {
|
|
viewWidth *= window.devicePixelRatio || 1;
|
|
viewHeight *= window.devicePixelRatio || 1;
|
|
}
|
|
|
|
if (viewWidth > 1024 && imageHeight > 1024 && imageHeight > 2.5 * imageWidth) {
|
|
// Treat as comic-sized dimensions..
|
|
return 'tall';
|
|
}
|
|
|
|
// Find a version that is larger than the view in one/both axes
|
|
// .find() is not supported in older browsers, using a loop
|
|
for (let i = 0, versions = Object.keys(imageVersions); i < versions.length; ++i) {
|
|
const version = versions[i],
|
|
dimensions = imageVersions[version],
|
|
versionWidth = Math.min(imageWidth, dimensions[0]),
|
|
versionHeight = Math.min(imageHeight, dimensions[1]);
|
|
if (versionWidth > viewWidth || versionHeight > viewHeight) {
|
|
return version;
|
|
}
|
|
}
|
|
|
|
// If the view is larger than any available version, display the original image.
|
|
//
|
|
// Sanity check to make sure we're not serving unintentionally huge assets
|
|
// all at once (where "huge" > 25 MiB). Videos are loaded in chunks so it
|
|
// doesn't matter too much there.
|
|
if (imageMime === 'video/webm' || imageSize <= 26214400) {
|
|
return 'full';
|
|
}
|
|
|
|
return 'large';
|
|
}
|
|
|
|
/**
|
|
* Given a target container element, chooses and scales an image
|
|
* to an appropriate dimension.
|
|
*/
|
|
function pickAndResize(elem) {
|
|
const imageWidth = parseInt(elem.dataset.width, 10),
|
|
imageHeight = parseInt(elem.dataset.height, 10),
|
|
imageSize = parseInt(elem.dataset.imageSize, 10),
|
|
imageMime = elem.dataset.mimeType,
|
|
scaled = elem.dataset.scaled,
|
|
uris = JSON.parse(elem.dataset.uris);
|
|
|
|
let version = 'full';
|
|
|
|
if (scaled === 'true') {
|
|
version = selectVersion(imageWidth, imageHeight, imageSize, imageMime);
|
|
}
|
|
|
|
const uri = uris[version];
|
|
let imageFormat = /\.(\w+?)$/.exec(uri)[1];
|
|
|
|
if (version === 'full' && store.get('serve_webm') && Boolean(uris.mp4)) {
|
|
imageFormat = 'mp4';
|
|
}
|
|
|
|
// Check if we need to change to avoid flickering
|
|
if (imageFormat === 'mp4' || imageFormat === 'webm') {
|
|
for (const sourceEl of elem.querySelectorAll('video source')) {
|
|
if (sourceEl.src.endsWith(uri) || (imageFormat === 'mp4' && sourceEl.src.endsWith(uris.mp4))) return;
|
|
}
|
|
|
|
// Scrub out the target element.
|
|
clearEl(elem);
|
|
}
|
|
|
|
const muted = store.get('unmute_videos') ? '' : 'muted';
|
|
const autoplay = elem.classList.contains('hidden') ? '' : 'autoplay'; // Fix for spoilered image pages
|
|
|
|
if (imageFormat === 'mp4') {
|
|
elem.classList.add('full-height');
|
|
elem.insertAdjacentHTML(
|
|
'afterbegin',
|
|
`<video controls ${autoplay} loop ${muted} playsinline preload="auto" id="image-display"
|
|
width="${imageWidth}" height="${imageHeight}">
|
|
<source src="${uris.webm}" type="video/webm">
|
|
<source src="${uris.mp4}" type="video/mp4">
|
|
<p class="block block--fixed block--warning">
|
|
Your browser supports neither MP4/H264 nor
|
|
WebM/VP8! Please update it to the latest version.
|
|
</p>
|
|
</video>`,
|
|
);
|
|
} else if (imageFormat === 'webm') {
|
|
elem.insertAdjacentHTML(
|
|
'afterbegin',
|
|
`<video controls ${autoplay} loop ${muted} playsinline id="image-display">
|
|
<source src="${uri}" type="video/webm">
|
|
<source src="${uri.replace(/webm$/, 'mp4')}" type="video/mp4">
|
|
<p class="block block--fixed block--warning">
|
|
Your browser supports neither MP4/H264 nor
|
|
WebM/VP8! Please update it to the latest version.
|
|
</p>
|
|
</video>`,
|
|
);
|
|
const video = elem.querySelector('video');
|
|
if (scaled === 'true') {
|
|
video.className = 'image-scaled';
|
|
} else if (scaled === 'partscaled') {
|
|
video.className = 'image-partscaled';
|
|
}
|
|
} else {
|
|
let image;
|
|
if (scaled === 'true') {
|
|
image = `<picture><img id="image-display" src="${uri}" class="image-scaled"></picture>`;
|
|
} else if (scaled === 'partscaled') {
|
|
image = `<picture><img id="image-display" src="${uri}" class="image-partscaled"></picture>`;
|
|
} else {
|
|
image = `<picture><img id="image-display" src="${uri}" width="${imageWidth}" height="${imageHeight}"></picture>`;
|
|
}
|
|
if (elem.innerHTML === image) return;
|
|
|
|
clearEl(elem);
|
|
elem.insertAdjacentHTML('afterbegin', image);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Bind an event to an image container for updating an image on
|
|
* click/tap.
|
|
*/
|
|
function bindImageForClick(target) {
|
|
target.addEventListener('click', () => {
|
|
if (target.getAttribute('data-scaled') === 'true') {
|
|
target.setAttribute('data-scaled', 'partscaled');
|
|
} else if (target.getAttribute('data-scaled') === 'partscaled') {
|
|
target.setAttribute('data-scaled', 'false');
|
|
} else {
|
|
target.setAttribute('data-scaled', 'true');
|
|
}
|
|
|
|
pickAndResize(target);
|
|
});
|
|
}
|
|
|
|
function bindImageTarget(node = document) {
|
|
$$('.image-target', node).forEach(target => {
|
|
pickAndResize(target);
|
|
|
|
if (target.dataset.mimeType === 'video/webm') {
|
|
// Don't interfere with media controls on video
|
|
return;
|
|
}
|
|
|
|
bindImageForClick(target);
|
|
window.addEventListener('resize', () => {
|
|
pickAndResize(target);
|
|
});
|
|
});
|
|
}
|
|
|
|
export { bindImageTarget };
|