Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions plugins/mobileWallLayout/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Mobile Wall Layout

Makes the wall-mode gallery render as a single full-width column on mobile
devices, on the **Markers** (`/scenes/markers`) and **Images** (`/images`) pages.

By default, Stash's wall mode uses `react-photo-gallery`, which calculates
`position: absolute` offsets for a multi-column brick layout. On small screens
this produces items that are too small to comfortably tap and browse. This
plugin overrides those offsets so each item spans the full width of the screen,
making marker previews and images easy to scroll through on a phone.

## Behaviour

- Applies only when `window.innerWidth ≤ 960px` — covers all current phones
including large flagships in landscape (e.g. iPhone 16 Pro Max: 932px,
Galaxy S24 Ultra: 915px), while excluding tablets (iPad mini landscape: 1024px).
- Activates and deactivates automatically as you navigate between pages.
- Re-evaluates on orientation change (portrait ↔ landscape).
- Has no effect on desktop or tablet viewports.

## Implementation note

The fix injects a `<style>` tag with `!important` rules rather than setting
inline styles via JavaScript. This is necessary because `react-photo-gallery`
continuously recalculates and re-applies its own inline styles; a CSS rule with
`!important` wins unconditionally regardless of render timing.
86 changes: 86 additions & 0 deletions plugins/mobileWallLayout/mobileWallLayout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* 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.
//
// The 960px threshold covers all current phones including large flagships in
// landscape (e.g. iPhone 16 Pro Max: 932px, Galaxy S24 Ultra: 915px) while
// excluding tablets (iPad mini landscape: 1024px). The resize listener ensures
// the fix is applied/removed correctly on orientation change.

var _imagesStyleTag = null;

// Uses !important throughout so these rules win over react-photo-gallery's
// inline `style="position:absolute; top:Xpx; left:Ypx"` attributes.
var _IMAGES_CSS = [
'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'))
&& window.innerWidth <= 960;

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 });

// Re-evaluate on orientation change (portrait ↔ landscape)
window.addEventListener('resize', updateImagesPageFix);

// Run immediately for whichever page is loaded first
updateImagesPageFix();
9 changes: 9 additions & 0 deletions plugins/mobileWallLayout/mobileWallLayout.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: Mobile Wall Layout
description: >
On the Markers and Images pages, forces the wall-mode gallery to render as a
single full-width column on mobile devices (phones in portrait or landscape,
up to ~960px wide). Tablets and desktops are unaffected.
version: 1.0
ui:
javascript:
- mobileWallLayout.js