diff --git a/src/pages/portfolio.astro b/src/pages/portfolio.astro index 4c5a6de..aa4b350 100644 --- a/src/pages/portfolio.astro +++ b/src/pages/portfolio.astro @@ -10,7 +10,22 @@ import WaveBackground from '../components/WaveBackground.astro'; // --- SERVER-SIDE LOGIC --- // 1. Hämta alla bilder -const imageFiles = import.meta.glob('../assets/images/portfolio/*.{jpeg,jpg,png,webp,JPG,JPEG,PNG}', { eager: true }); +const allImageFiles = import.meta.glob('../assets/images/portfolio/*.{jpeg,jpg,png,webp,JPG,JPEG,PNG}', { eager: true }); +// -s (800px) och -m (1600px) ar NEDLADDNINGSVARIANTER, inte egna galleribilder. +// Utan filtret visas varje bild tre ganger. +const imageFiles = Object.fromEntries( + Object.entries(allImageFiles).filter(([pathKey]) => !/-[sm]\.(jpe?g|png|webp)$/i.test(pathKey)) +); + +// Karta assetId -> { s, m } med Astros hashade URL:er, sa klienten kan lanka +// till ratt nedladdningsvariant. Large ar galleribilden sjalv, original gar via proxy. +const downloadVariants: Record> = {}; +for (const [pathKey, file] of Object.entries(allImageFiles) as [string, any][]) { + const m = pathKey.match(/immich-([0-9a-f-]{36})-.*-([sm])\.(?:jpe?g|png|webp)$/i); + if (!m) continue; + const id = m[1].toLowerCase(); + (downloadVariants[id] ||= {})[m[2].toLowerCase()] = file.default.src; +} function formatShutterSpeed(exposureTime: number) { if (!exposureTime) return null; @@ -150,6 +165,15 @@ const uniqueMonths = [...new Map(allMonths.map(item => [item.name, item])).value

+
+ + +
Focal--
Aperture--
@@ -158,6 +182,24 @@ const uniqueMonths = [...new Map(allMonths.map(item => [item.name, item])).value
+ + + + + @@ -239,6 +281,96 @@ const uniqueMonths = [...new Map(allMonths.map(item => [item.name, item])).value document.body.style.overflow = 'hidden'; } + // --- NEDLADDNING (Small/Medium/Large ur repot, Original via proxy) --- + const dlBtn = document.getElementById('lb-download'); + const dlMenu = document.getElementById('lb-sizes'); + const pwModal = document.getElementById('pw-modal'); + const pwInput = document.getElementById('pw-input') as HTMLInputElement; + const pwError = document.getElementById('pw-error'); + + // Astro hashar filnamnen, men assetId:t finns kvar i dem: + // /_astro/immich---..jpg + function assetIdFor(src: string): string | null { + const m = src.match(/immich-([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})-/i); + return m ? m[1].toLowerCase() : null; + } + + // Large ar filen galleriet redan visar. -s/-m ligger bredvid i samma katalog, + // men med egen Astro-hash - darfor laser vi dem ur en genererad karta. + const VARIANTS: Record> = JSON.parse( + document.getElementById('dl-variants')?.textContent || '{}' + ); + + function urlForSize(size: string): string | null { + const item = visibleItems[currentImageIndex]; + if (!item) return null; + const large = item.dataset.src || ''; + if (size === 'l') return large; + const id = assetIdFor(large); + if (!id) return null; + if (size === 'orig') return `/download/original?asset=${id}`; + return VARIANTS[id]?.[size] || null; + } + + function startDownload(url: string, filnamn?: string) { + const a = document.createElement('a'); + a.href = url; + a.download = filnamn || ''; + document.body.appendChild(a); + a.click(); + a.remove(); + } + + dlBtn?.addEventListener('click', (e) => { + e.stopPropagation(); + dlMenu?.classList.toggle('hidden'); + }); + + document.addEventListener('click', (e) => { + if (!dlMenu || dlMenu.classList.contains('hidden')) return; + if (!dlMenu.contains(e.target as Node) && e.target !== dlBtn) dlMenu.classList.add('hidden'); + }); + + document.querySelectorAll('.dl-opt').forEach((opt) => { + opt.addEventListener('click', () => { + const size = (opt as HTMLElement).dataset.size || 'l'; + dlMenu?.classList.add('hidden'); + const url = urlForSize(size); + if (!url) { alert('Den storleken saknas for den har bilden.'); return; } + if (size === 'orig') { + // Losenordet sparas bara for fliken, aldrig i localStorage. + const sparat = sessionStorage.getItem('dlpw'); + if (sparat) { startDownload(`${url}&pw=${encodeURIComponent(sparat)}`); return; } + pwError?.classList.add('hidden'); + if (pwInput) pwInput.value = ''; + pwModal?.classList.remove('hidden'); + setTimeout(() => pwInput?.focus(), 50); + } else { + startDownload(url); + } + }); + }); + + async function forsokOriginal() { + const pw = pwInput?.value || ''; + if (!pw) return; + const url = urlForSize('orig'); + if (!url) return; + // HEAD forst: da far vi 401 utan att dra igang en 70 MB-nedladdning. + try { + const r = await fetch(`${url}&pw=${encodeURIComponent(pw)}`, { method: 'HEAD' }); + if (r.status === 401) { pwError?.classList.remove('hidden'); return; } + if (!r.ok) { alert('Kunde inte hamta originalet just nu.'); return; } + } catch { /* natverksfel -> forsok anda nedan */ } + sessionStorage.setItem('dlpw', pw); + pwModal?.classList.add('hidden'); + startDownload(`${url}&pw=${encodeURIComponent(pw)}`); + } + + document.getElementById('pw-ok')?.addEventListener('click', forsokOriginal); + pwInput?.addEventListener('keydown', (e) => { if ((e as KeyboardEvent).key === 'Enter') forsokOriginal(); }); + document.getElementById('pw-cancel')?.addEventListener('click', () => pwModal?.classList.add('hidden')); + function closeLightbox() { lightbox?.classList.add('opacity-0'); setTimeout(() => { @@ -250,6 +382,7 @@ const uniqueMonths = [...new Map(allMonths.map(item => [item.name, item])).value function updateLightboxContent() { const item = visibleItems[currentImageIndex]; if (!item) return; + dlMenu?.classList.add('hidden'); lbImg.src = item.dataset.src || '';