Files
CommunityScripts/plugins/mobileWallLayout/mobileWallLayout.js
2026-04-03 17:24:53 +03:00

87 lines
3.8 KiB
JavaScript

/**
* Mobile Layout Fix — Stash UI Plugin
* =====================================
* Forces full-width single-column layout on the Markers and Images (wall mode)
* pages, where react-photo-gallery sets inline position:absolute offsets that
* cause items to overlap or overflow on mobile.
*
* Fix: inject a <style> tag with !important rules that override the library's
* inline styles, making the layout rendering-order-independent. A JS-based
* approach (setting el.style directly) loses to library re-renders; CSS wins
* unconditionally.
*
* The style tag is added when entering /images or /scenes/markers and removed
* on navigation away, so it never affects other views.
*
* Architecture:
* A single MutationObserver watches for DOM changes caused by Stash's SPA
* navigation and re-evaluates which page is active.
*/
// ── Images + Markers pages: CSS injection for mobile full-width layout ────────
// Both /images (wall mode) and /scenes/markers use react-photo-gallery, which
// sets inline position:absolute styles. A <style> tag with !important beats
// inline styles regardless of render timing, avoiding the race condition that
// JS-based inline overrides suffer from.
//
// Device targeting uses `pointer: coarse` (touchscreens) rather than a pixel
// width threshold. A width-only check (e.g. <= 960px) also triggers on narrow
// desktop windows; pointer coarseness correctly identifies touch devices
// regardless of window size, and CSS media queries re-evaluate automatically on
// any relevant change — no JS resize listener needed.
var _imagesStyleTag = null;
// Uses !important throughout so these rules win over react-photo-gallery's
// inline `style="position:absolute; top:Xpx; left:Ypx"` attributes.
// Wrapped in a pointer:coarse media query so the rules are inert on desktop.
var _IMAGES_CSS = [
'@media (pointer: coarse) {',
' div.react-photo-gallery--gallery {',
' display: block !important;',
' }',
' .wall-item {',
' position: relative !important;', /* pull items back into normal flow */
' width: 100% !important;',
' height: auto !important;',
' top: auto !important;', /* neutralise calculated pixel offsets */
' left: auto !important;',
' display: block !important;',
' margin-bottom: 10px !important;',
' }',
' .wall-item img, .wall-item video {',
' width: 100% !important;',
' height: auto !important;',
' object-fit: contain !important;',
' }',
'}'
].join('\n');
function updateImagesPageFix() {
var href = window.location.href;
var onTargetPage = href.includes('/images') || href.includes('scenes/markers');
if (onTargetPage && !_imagesStyleTag) {
// Entering images or markers page — inject the fix
_imagesStyleTag = document.createElement('style');
_imagesStyleTag.id = 'mobile-layout-fix-images';
_imagesStyleTag.textContent = _IMAGES_CSS;
document.head.appendChild(_imagesStyleTag);
} else if (!onTargetPage && _imagesStyleTag) {
// Leaving — clean up so other pages are unaffected
_imagesStyleTag.remove();
_imagesStyleTag = null;
}
}
// ── Shared MutationObserver ───────────────────────────────────────────────────
// Stash is a React SPA; page "navigation" is DOM mutation, not a real load.
// Observing childList + subtree on body catches both navigation and lazy-
// loaded gallery content without needing a polling interval.
var observer = new MutationObserver(updateImagesPageFix);
observer.observe(document.body, { childList: true, subtree: true });
// Run immediately for whichever page is loaded first
updateImagesPageFix();