Refine map lightbox UX and map click behavior across pages

This commit is contained in:
hbrain 2026-05-31 15:19:39 +02:00
parent 7f45193a6f
commit 5408aba01a
4 changed files with 24 additions and 16 deletions

View file

@ -255,7 +255,7 @@ $baseParams = ($rawKeyword !== '') ? ['q' => $rawKeyword] : [];
$map = pathinfo($name, PATHINFO_FILENAME).".png"; $map = pathinfo($name, PATHINFO_FILENAME).".png";
if(is_file($config['videos_dir']."/".$map)): if(is_file($config['videos_dir']."/".$map)):
?> ?>
<img class="map-image" src="<?=h($config['public_videos']."/".rawurlencode($map))?>" alt="Map"> <a class="map-image-link" href="<?=h($config['public_videos']."/".rawurlencode($map))?>"><img class="map-image" src="<?=h($config['public_videos']."/".rawurlencode($map))?>" alt="Map"></a>
<?php endif; ?> <?php endif; ?>
</div> </div>
<div class="video-info"> <div class="video-info">
@ -305,6 +305,6 @@ $baseParams = ($rawKeyword !== '') ? ['q' => $rawKeyword] : [];
}); });
})(); })();
</script> </script>
<script src="js/map_lightbox.js" defer></script> <script src="js/map_lightbox.js?v=20260531c" defer></script>
</body> </body>
</html> </html>

View file

@ -1,5 +1,5 @@
(() => { (() => {
const SELECTOR = 'img.map-image'; const SELECTOR = 'a.map-image-link, img.map-image';
let lightbox = null; let lightbox = null;
let lightboxImg = null; let lightboxImg = null;
@ -22,7 +22,7 @@
const hint = document.createElement('div'); const hint = document.createElement('div');
hint.className = 'map-lightbox__hint'; hint.className = 'map-lightbox__hint';
hint.textContent = 'Esc or click outside to close'; hint.textContent = 'Esc or click/tap outside to close';
lightbox.appendChild(closeBtn); lightbox.appendChild(closeBtn);
lightbox.appendChild(lightboxImg); lightbox.appendChild(lightboxImg);
@ -30,21 +30,18 @@
document.body.appendChild(lightbox); document.body.appendChild(lightbox);
closeBtn.addEventListener('click', closeLightbox); closeBtn.addEventListener('click', closeLightbox);
lightbox.addEventListener('click', (ev) => { lightbox.addEventListener('click', (ev) => {
if (ev.target === lightbox) closeLightbox(); if (ev.target === lightbox) closeLightbox();
}); });
document.addEventListener('keydown', (ev) => { document.addEventListener('keydown', (ev) => {
if (ev.key === 'Escape' && lightbox.classList.contains('is-open')) { if (ev.key === 'Escape' && lightbox.classList.contains('is-open')) closeLightbox();
closeLightbox();
}
}); });
return lightbox; return lightbox;
} }
function openLightbox(src, alt) { function openLightbox(src, alt) {
if (!src) return;
ensureLightbox(); ensureLightbox();
lightboxImg.src = src; lightboxImg.src = src;
lightboxImg.alt = alt || 'Map preview'; lightboxImg.alt = alt || 'Map preview';
@ -59,17 +56,20 @@
lightbox.setAttribute('aria-hidden', 'true'); lightbox.setAttribute('aria-hidden', 'true');
document.body.classList.remove('map-lightbox-open'); document.body.classList.remove('map-lightbox-open');
setTimeout(() => { setTimeout(() => {
if (!lightbox.classList.contains('is-open')) { if (!lightbox.classList.contains('is-open')) lightboxImg.removeAttribute('src');
lightboxImg.removeAttribute('src');
}
}, 260); }, 260);
} }
document.addEventListener('click', (ev) => { document.addEventListener('click', (ev) => {
const target = ev.target instanceof Element ? ev.target.closest(SELECTOR) : null; const target = ev.target instanceof Element ? ev.target.closest(SELECTOR) : null;
if (!target) return; if (!target) return;
const img = target.matches('img.map-image') ? target : target.querySelector('img.map-image');
const src = img ? (img.currentSrc || img.src) : target.getAttribute('href');
if (!src) return;
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
openLightbox(target.currentSrc || target.src, target.alt || 'Map preview'); openLightbox(src, img ? img.alt : 'Map preview');
}); });
})(); })();

View file

@ -645,7 +645,7 @@ $runningJobs = active_worker_jobs($config);
<?php if($err): ?><div class="err"><?=h($err)?></div><?php endif; ?> <?php if($err): ?><div class="err"><?=h($err)?></div><?php endif; ?>
<?php if($tab==='videos'): ?><section><h2>Videos without input dir</h2><?php if(!$orphanVideos): ?><p>No orphan videos.</p><?php endif; ?><div class="admin-list"><?php foreach($orphanVideosPage as $v): $vn=basename($v); ?><form method="post" class="admin-item" onsubmit="return confirm('Delete this generated video?') js-ajax-form"><div><strong><?=h($vn)?></strong><br><span><?=h(round(filesize($v)/1048576,1))?> MB</span></div><input type="hidden" name="action" value="delete_video"><input type="hidden" name="video" value="<?=h($vn)?>"><button type="submit">Delete video</button></form><?php endforeach; ?></div><?=page_links('videos',$videoPage,$videoPages)?></section><?php endif; ?> <?php if($tab==='videos'): ?><section><h2>Videos without input dir</h2><?php if(!$orphanVideos): ?><p>No orphan videos.</p><?php endif; ?><div class="admin-list"><?php foreach($orphanVideosPage as $v): $vn=basename($v); ?><form method="post" class="admin-item" onsubmit="return confirm('Delete this generated video?') js-ajax-form"><div><strong><?=h($vn)?></strong><br><span><?=h(round(filesize($v)/1048576,1))?> MB</span></div><input type="hidden" name="action" value="delete_video"><input type="hidden" name="video" value="<?=h($vn)?>"><button type="submit">Delete video</button></form><?php endforeach; ?></div><?=page_links('videos',$videoPage,$videoPages)?></section><?php endif; ?>
<?php if($tab==='edit' && !$editDir): ?><section><h2>Existing input dirs</h2><?php if(!$dirs): ?><p>No input directories yet.</p><?php endif; ?><div class="admin-list video-list"><?php foreach($dirsPage as $d): $runStatus=input_dir_status($d); $running=input_dir_running($d); $enabled=input_dir_enabled($d); $preview=input_dir_preview($d); $state=input_dir_state($d); $fullOutput=basename((string)($state['output'] ?? '')); $previewOutput=basename((string)($state['preview_output'] ?? '')); $hasFullVideo=$fullOutput !== '' && is_file($config['videos_dir'].'/'.$fullOutput); $hasPreviewVideo=$previewOutput !== '' && is_file($config['videos_dir'].'/'.$previewOutput); $displayOutput=($preview && $hasPreviewVideo) ? $previewOutput : ($hasFullVideo ? $fullOutput : ($hasPreviewVideo ? $previewOutput : '')); $hasVideo=$displayOutput !== ''; $displayVideoPath=$hasVideo ? $config['videos_dir'].'/'.$displayOutput : ''; $previewVideo=$hasVideo && $displayOutput === $previewOutput; $canShow=$hasFullVideo && !$preview; $visible=$canShow && input_dir_visible($d); $info=$dirInfo[basename($d)] ?? input_dir_info($d); $data=read_data($d); $meta=cached_video_metadata($displayOutput, $data); $currentSignature=$info['signature'] ?? ''; $fullFingerprint=is_array($state)?(string)($state['fingerprint'] ?? ''):''; $previewFingerprint=is_array($state)?(string)($state['preview_fingerprint'] ?? ''):''; $editedSinceRender=$hasFullVideo && $currentSignature !== '' && $fullFingerprint !== '' && $currentSignature !== $fullFingerprint; $editedSincePreview=!$hasFullVideo && $previewVideo && $currentSignature !== '' && $previewFingerprint !== '' && $currentSignature !== $previewFingerprint; $staleLabel=$editedSinceRender ? 'Edited since render' : ($editedSincePreview ? 'Edited since preview' : ''); ?><article class="video-row admin-video-row<?= $visible ? ' shown' : '' ?>" data-job="<?=h(basename($d))?>"><div><?php if($hasVideo): ?><div class="video-wrapper"><video controls preload="metadata" src="<?=h($config['public_videos'].'/'.rawurlencode($displayOutput))?>"></video><?php $map_n = ($displayOutput !== '' ? pathinfo($displayOutput, PATHINFO_FILENAME) : basename($d)) . '.png'; if(is_file($config['videos_dir'].'/'.$map_n)): ?><img class="map-image" src="<?=h($config['public_videos'].'/'.rawurlencode($map_n))?>" alt="Map"><?php endif; ?></div><?php else: ?><div class="video-placeholder"><img src="assets/img/moto_travel.png" alt=""><span>No video yet</span></div><?php endif; ?></div><div class="video-info"><div class="admin-row-top"><div class="admin-switches"> <?php if($tab==='edit' && !$editDir): ?><section><h2>Existing input dirs</h2><?php if(!$dirs): ?><p>No input directories yet.</p><?php endif; ?><div class="admin-list video-list"><?php foreach($dirsPage as $d): $runStatus=input_dir_status($d); $running=input_dir_running($d); $enabled=input_dir_enabled($d); $preview=input_dir_preview($d); $state=input_dir_state($d); $fullOutput=basename((string)($state['output'] ?? '')); $previewOutput=basename((string)($state['preview_output'] ?? '')); $hasFullVideo=$fullOutput !== '' && is_file($config['videos_dir'].'/'.$fullOutput); $hasPreviewVideo=$previewOutput !== '' && is_file($config['videos_dir'].'/'.$previewOutput); $displayOutput=($preview && $hasPreviewVideo) ? $previewOutput : ($hasFullVideo ? $fullOutput : ($hasPreviewVideo ? $previewOutput : '')); $hasVideo=$displayOutput !== ''; $displayVideoPath=$hasVideo ? $config['videos_dir'].'/'.$displayOutput : ''; $previewVideo=$hasVideo && $displayOutput === $previewOutput; $canShow=$hasFullVideo && !$preview; $visible=$canShow && input_dir_visible($d); $info=$dirInfo[basename($d)] ?? input_dir_info($d); $data=read_data($d); $meta=cached_video_metadata($displayOutput, $data); $currentSignature=$info['signature'] ?? ''; $fullFingerprint=is_array($state)?(string)($state['fingerprint'] ?? ''):''; $previewFingerprint=is_array($state)?(string)($state['preview_fingerprint'] ?? ''):''; $editedSinceRender=$hasFullVideo && $currentSignature !== '' && $fullFingerprint !== '' && $currentSignature !== $fullFingerprint; $editedSincePreview=!$hasFullVideo && $previewVideo && $currentSignature !== '' && $previewFingerprint !== '' && $currentSignature !== $previewFingerprint; $staleLabel=$editedSinceRender ? 'Edited since render' : ($editedSincePreview ? 'Edited since preview' : ''); ?><article class="video-row admin-video-row<?= $visible ? ' shown' : '' ?>" data-job="<?=h(basename($d))?>"><div><?php if($hasVideo): ?><div class="video-wrapper"><video controls preload="metadata" src="<?=h($config['public_videos'].'/'.rawurlencode($displayOutput))?>"></video><?php $map_n = ($displayOutput !== '' ? pathinfo($displayOutput, PATHINFO_FILENAME) : basename($d)) . '.png'; if(is_file($config['videos_dir'].'/'.$map_n)): ?><a class="map-image-link" href="<?=h($config['public_videos'].'/'.rawurlencode($map_n))?>"><img class="map-image" src="<?=h($config['public_videos'].'/'.rawurlencode($map_n))?>" alt="Map"></a><?php endif; ?></div><?php else: ?><div class="video-placeholder"><img src="assets/img/moto_travel.png" alt=""><span>No video yet</span></div><?php endif; ?></div><div class="video-info"><div class="admin-row-top"><div class="admin-switches">
<form method="post" class="inline-form switch-form" data-switch="preview"> <form method="post" class="inline-form switch-form" data-switch="preview">
<input type="hidden" name="action" value="set_preview"> <input type="hidden" name="action" value="set_preview">
<input type="hidden" name="dir" value="<?=h(basename($d))?>"> <input type="hidden" name="dir" value="<?=h(basename($d))?>">
@ -1098,5 +1098,5 @@ function applySwitchPayload(payload){
})(); })();
</script> </script>
<script src="js/map_lightbox.js" defer></script> <script src="js/map_lightbox.js?v=20260531c" defer></script>
</body></html> </body></html>

View file

@ -45,14 +45,22 @@
} }
.describe-button{display:inline-block;margin-right:.6rem} .describe-button{display:inline-block;margin-right:.6rem}
.map-image-link {
display: block;
margin-top: 1rem;
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}
.map-image { .map-image {
width: 100%; width: 100%;
aspect-ratio: 16 / 9; aspect-ratio: 16 / 9;
object-fit: contain; object-fit: contain;
background: #111315; background: #111315;
border-radius: .5rem; border-radius: .5rem;
margin-top: 1rem; margin-top: 0;
cursor: zoom-in; cursor: zoom-in;
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
transition: transform .22s ease, box-shadow .22s ease; transition: transform .22s ease, box-shadow .22s ease;
} }
.map-image:hover { .map-image:hover {