overhaul rethumbing flashs

This commit is contained in:
2026-06-08 16:37:15 +02:00
parent c615676465
commit 69e90f8d2d
6 changed files with 540 additions and 60 deletions

View File

@@ -1658,7 +1658,8 @@ window.cancelAnimFrame = (function () {
"unmuteOverlay": "hidden",
"letterbox": "on",
"warnOnUnsupportedContent": false,
"contextMenu": "on"
"contextMenu": "on",
"splashScreen": false
};
let ruffleKeepAliveApplied = false;
@@ -1743,15 +1744,46 @@ window.cancelAnimFrame = (function () {
applyRuffleKeepAlive();
// Detect whether the item is currently behind a blur overlay.
// If so, load the SWF with autoplay:"off" so it stays paused until the user reveals it.
const mediaObj = container.closest('.media-object');
let blurActive = false;
if (mediaObj && !mediaObj.classList.contains('revealed') && localStorage.getItem('blurDetail') !== 'false') {
const blurNsfw = localStorage.getItem('blurNsfw') === 'true';
const blurNsfl = localStorage.getItem('blurNsfl') === 'true';
const blurSfw = localStorage.getItem('blurSfw') === 'true';
const blurUntagged = localStorage.getItem('blurUntagged') === 'true';
const mode = mediaObj.getAttribute('data-mode');
blurActive =
(mode === 'nsfw' && blurNsfw) ||
(mode === 'nsfl' && blurNsfl) ||
(mode === 'sfw' && blurSfw) ||
(mode === 'untagged' && blurUntagged);
}
// Update config with latest session preferences just before creation
if (window.RufflePlayer) {
window.RufflePlayer.config = window.RuffleConfig = {
...window.RuffleConfig,
"autoplay": blurActive ? "off" : "on",
"pageVisibility": window.f0ckSession?.ruffle_background === false,
"backgroundExecution": window.f0ckSession?.ruffle_background === false ? undefined : "Unthrottled"
"backgroundExecution": window.f0ckSession?.ruffle_background === false ? undefined : "Unthrottled",
"splashScreen": false
};
}
// Patch WebGL getContext to force preserveDrawingBuffer:true so canvas
// frames can be captured after Ruffle renders (WebGL clears buffers by default)
const _origGetCtx = HTMLCanvasElement.prototype.getContext;
HTMLCanvasElement.prototype.getContext = function(type, attrs) {
if (type === 'webgl' || type === 'webgl2' || type === 'experimental-webgl') {
attrs = Object.assign({}, attrs, { preserveDrawingBuffer: true });
}
return _origGetCtx.call(this, type, attrs);
};
// Restore after Ruffle has created its context
setTimeout(() => { HTMLCanvasElement.prototype.getContext = _origGetCtx; }, 8000);
function createPlayer() {
if (!window.RufflePlayer) return;
if (typeof window.RufflePlayer.newest !== 'function') return; // WASM still initializing
@@ -3683,7 +3715,158 @@ window.cancelAnimFrame = (function () {
e.preventDefault();
const reBtn = target.closest('#a_rethumb');
const itemId = reBtn.dataset.itemId;
// Helper: POST a blob as thumbnail
const submitRethumb = (blob, currentItemId) => {
const fd = new FormData();
fd.append('file', blob, 'thumbnail.jpg');
window.flashMessage(i18n.uploading || 'Uploading...');
fetch(`/api/v2/items/${currentItemId}/thumbnail`, {
method: 'POST',
headers: { 'X-CSRF-Token': window.f0ckSession?.csrf_token },
body: fd
})
.then(r => r.json())
.then(data => {
if (data.success) {
window.flashMessage(data.msg);
if (window.refreshItemThumbnails) window.refreshItemThumbnails(currentItemId);
} else {
window.flashError(data.msg || 'Upload failed');
}
})
.catch(err => { window.flashError('Upload error'); console.error(err); });
};
// For Flash items: show a capture modal instead of a file picker
const rufflePlayer = document.querySelector('#ruffle-container ruffle-player');
if (rufflePlayer) {
const tryFindCanvas = (root) => {
if (!root) return null;
const c = root.querySelector('canvas');
if (c) return c;
for (const el of root.querySelectorAll('*')) {
if (el.shadowRoot) { const found = tryFindCanvas(el.shadowRoot); if (found) return found; }
}
return null;
};
// POST blob to thumbnail API and close modal
const submitRethumb = (blob) => {
const fd = new FormData();
fd.append('file', blob, 'thumbnail.jpg');
window.flashMessage(i18n.uploading || 'Uploading...');
fetch(`/api/v2/items/${itemId}/thumbnail`, {
method: 'POST',
headers: { 'X-CSRF-Token': window.f0ckSession?.csrf_token },
body: fd
})
.then(r => r.json())
.then(data => {
if (data.success) {
window.flashMessage(data.msg);
if (window.refreshItemThumbnails) window.refreshItemThumbnails(itemId);
} else {
window.flashError(data.msg || 'Upload failed');
}
})
.catch(err => { window.flashError('Upload error'); console.error(err); });
};
// Capture current Ruffle frame → return blob via callback
const captureFrame = (cb) => {
const canvas = tryFindCanvas(rufflePlayer.shadowRoot || rufflePlayer) || tryFindCanvas(rufflePlayer);
if (!canvas || canvas.width === 0 || canvas.height === 0) {
cb(null);
return;
}
const out = document.createElement('canvas');
const MAX = 640;
const w = canvas.width, h = canvas.height;
if (w > MAX || h > MAX) {
const ratio = Math.min(MAX / w, MAX / h);
out.width = Math.round(w * ratio);
out.height = Math.round(h * ratio);
} else {
out.width = w || 320;
out.height = h || 240;
}
out.getContext('2d').drawImage(canvas, 0, 0, out.width, out.height);
out.toBlob(cb, 'image/jpeg', 0.92);
};
// Build & show modal using site CSS classes
const showCaptureModal = (initialBlob) => {
document.getElementById('rethumb-capture-modal')?.remove();
let currentBlob = initialBlob;
const overlay = document.createElement('div');
overlay.id = 'rethumb-capture-modal';
overlay.className = 'modal-overlay';
overlay.style.zIndex = '99999';
const box = document.createElement('div');
box.className = 'modal-content';
box.style.cssText = 'max-width:480px;width:90%;text-align:left;position:relative;';
const title = document.createElement('h3');
title.textContent = 'Thumbnail Preview';
title.style.marginTop = '0';
const img = document.createElement('img');
img.src = URL.createObjectURL(initialBlob);
img.style.cssText = 'width:100%;height:auto;display:block;border-radius:0;margin-bottom:4px;';
const actions = document.createElement('div');
actions.className = 'modal-actions';
const btnUse = document.createElement('button');
btnUse.textContent = 'Use as Thumbnail';
btnUse.className = 'btn-danger';
const btnRecapture = document.createElement('button');
btnRecapture.textContent = 'Capture Again';
btnRecapture.className = 'btn-secondary';
const btnCancel = document.createElement('button');
btnCancel.textContent = 'Cancel';
btnCancel.className = 'btn-secondary';
const close = () => overlay.remove();
btnUse.onclick = () => { submitRethumb(currentBlob); close(); };
btnRecapture.onclick = () => {
captureFrame((blob) => {
if (!blob) { window.flashError('Ruffle not ready'); return; }
URL.revokeObjectURL(img.src);
currentBlob = blob;
img.src = URL.createObjectURL(blob);
});
};
btnCancel.onclick = close;
overlay.onclick = (ev) => { if (ev.target === overlay) close(); };
actions.append(btnUse, btnRecapture, btnCancel);
box.append(title, img, actions);
overlay.appendChild(box);
document.body.appendChild(overlay);
};
captureFrame((blob) => {
if (blob) {
showCaptureModal(blob);
} else {
window.flashError('Ruffle not ready — play the Flash first');
}
});
return; // never open file picker for Flash items
}
// Non-Flash items: use the file picker as before
let fileInput = document.getElementById('rethumb-file-input');
if (!fileInput) {
fileInput = document.createElement('input');
@@ -3692,52 +3875,24 @@ window.cancelAnimFrame = (function () {
fileInput.accept = 'image/png, image/jpeg, image/gif, image/webp';
fileInput.style.display = 'none';
document.body.appendChild(fileInput);
fileInput.addEventListener('change', function(evt) {
const file = evt.target.files[0];
if (!file) return;
if (file.size > 5 * 1024 * 1024) {
window.flashError('File is too large (max 5MB)');
fileInput.value = '';
return;
}
const currentItemId = fileInput.dataset.itemId;
const fd = new FormData();
fd.append('file', file);
window.flashMessage(i18n.uploading || 'Uploading...');
fetch(`/api/v2/items/${currentItemId}/thumbnail`, {
method: 'POST',
headers: {
'X-CSRF-Token': window.f0ckSession?.csrf_token
},
body: fd
})
.then(r => r.json())
.then(data => {
fileInput.value = '';
if (data.success) {
window.flashMessage(data.msg);
if (window.refreshItemThumbnails) {
window.refreshItemThumbnails(currentItemId);
}
} else {
window.flashError(data.msg || 'Upload failed');
}
})
.catch(err => {
fileInput.value = '';
window.flashError('Upload error');
console.error(err);
});
submitRethumb(file, currentItemId);
fileInput.value = '';
});
}
fileInput.dataset.itemId = itemId;
fileInput.click();
} else if (target.closest('button#a_toggle')) {
e.preventDefault();
const toggleBtn = target.closest('button#a_toggle');
@@ -9849,12 +10004,18 @@ document.addEventListener('click', (e) => {
e.preventDefault();
e.stopPropagation();
mediaObj.classList.add('revealed');
// Start audio/video playback cleanly on reveal
const videoElem = mediaObj.querySelector('video') || mediaObj.querySelector('audio');
if (videoElem) {
videoElem.play().catch(() => {});
}
// Start Ruffle playback if this is a Flash item — it was loaded with autoplay:"off"
const rufflePlayer = mediaObj.querySelector('ruffle-player');
if (rufflePlayer) {
try { rufflePlayer.play(); } catch(err) {}
}
return;
}
}