Add first lightbox tests
This commit is contained in:
@ -1,5 +1,12 @@
|
||||
<script lang="ts">
|
||||
import Photo from './Photo.svelte';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { fade } from 'svelte/transition';
|
||||
import { flip } from 'svelte/animate';
|
||||
import { getFileName } from '$lib/util/links';
|
||||
import { str, strf } from '$lib/data/language';
|
||||
import Icon from './Icon.svelte';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
/** The album. */
|
||||
export let album: Album;
|
||||
@ -9,12 +16,111 @@
|
||||
|
||||
/** Base URL */
|
||||
export let base: string;
|
||||
|
||||
/** The gallery container. */
|
||||
let container: HTMLUListElement;
|
||||
|
||||
/** The test item. */
|
||||
let testItem: Item | undefined = undefined;
|
||||
|
||||
/** The click handler for the image. */
|
||||
async function clickHandler(event: MouseEvent, i: number) {
|
||||
const item = items ? items[i] : album.items[i];
|
||||
if (!event.shiftKey && !event.ctrlKey) {
|
||||
event.preventDefault();
|
||||
if (!testItem) {
|
||||
testItem = item;
|
||||
window.addEventListener('keydown', escHandler);
|
||||
dispatch('lightbox', { album, item });
|
||||
} else {
|
||||
testItem = undefined;
|
||||
window.removeEventListener('keydown', escHandler);
|
||||
dispatch('lightbox-close', { album, item });
|
||||
}
|
||||
} else {
|
||||
dispatch('open', { album, item });
|
||||
}
|
||||
}
|
||||
|
||||
/** The right click handler for the image. */
|
||||
async function rightClickHandler(event: MouseEvent) {
|
||||
console.log('right click', event);
|
||||
}
|
||||
|
||||
/** The arrow key handler for the gallery. */
|
||||
async function arrowKeyHandler(event: KeyboardEvent, i: number) {
|
||||
if (event.key === 'ArrowLeft') {
|
||||
container.children[i - 1]?.querySelector('a')?.focus();
|
||||
} else if (event.key === 'ArrowRight') {
|
||||
container.children[i + 1]?.querySelector('a')?.focus();
|
||||
}
|
||||
}
|
||||
|
||||
/** The escape handler for the viewbox. */
|
||||
async function escHandler(event: KeyboardEvent) {
|
||||
if (event.key === 'Escape') {
|
||||
testItem = undefined;
|
||||
window.removeEventListener('keydown', escHandler);
|
||||
}
|
||||
}
|
||||
|
||||
const thumbnail = (item: Item, size: number | 's' | 'm' | 'l' | 'full' = 's') => `${base}/t/${size}/${item.item}`;
|
||||
|
||||
function getDetails(item: Item) {
|
||||
const filename = getFileName(item.item);
|
||||
const titleOrNull = item.title ? $strf(item.title) : null;
|
||||
const descriptionOrNull = item.description ? $strf(item.description) : null;
|
||||
const title = titleOrNull || filename;
|
||||
const alt = titleOrNull || descriptionOrNull || filename;
|
||||
const author = item.authors ? (
|
||||
Array.isArray(item.authors) ?
|
||||
item.authors[0]:
|
||||
typeof item.authors === 'string' ?
|
||||
item.authors:
|
||||
"unknown author"):
|
||||
album.authors ? (
|
||||
Array.isArray(album.authors) ?
|
||||
album.authors[0]:
|
||||
typeof album.authors === 'string' ?
|
||||
album.authors:
|
||||
"unknown author"):
|
||||
"Luca Bosin";
|
||||
const thumbnails = [
|
||||
[0, thumbnail(item, 's')],
|
||||
[1600, thumbnail(item, 'm')],
|
||||
[2560, thumbnail(item, 'l')]
|
||||
];
|
||||
const href = `${base}/i/${item.item}`;
|
||||
return { filename, title, alt, author, thumbnails, href };
|
||||
}
|
||||
</script>
|
||||
|
||||
<section>
|
||||
<ul>
|
||||
{#each (items || album.items) as item (item.item)}
|
||||
<Photo {base} {album} {item} />
|
||||
<ul bind:this={container}>
|
||||
{#each items || album.items as item, i (item.item)}
|
||||
{@const details = getDetails(item)}
|
||||
{@const numThumbnails = details.thumbnails.length}
|
||||
<li class:test={testItem == item} aria-label={details.title} aria-details={details.alt} animate:flip={{duration: 700}}>
|
||||
<figure style:--image="url('{thumbnail(item, 3)}')">
|
||||
<picture>
|
||||
{#each {length: numThumbnails} as _, j (j)}
|
||||
{@const [size, thumbnail] = details.thumbnails[numThumbnails - 1 - j]}
|
||||
{#if j === numThumbnails - 1}
|
||||
<img src={`${thumbnail}`} alt={details.alt} loading={i > 5 ? 'lazy' : 'eager'} title={details.title} />
|
||||
{:else}
|
||||
<source media="(min-width: {size}px)" srcset={`${thumbnail}`} />
|
||||
{/if}
|
||||
{/each}
|
||||
</picture>
|
||||
<figcaption>
|
||||
<strong>{details.title}</strong>
|
||||
<cite>{details.author}</cite>
|
||||
</figcaption>
|
||||
</figure>
|
||||
<a href={details.href} tabindex="0" aria-label={$str('open-name', details.title)} on:click={e => clickHandler(e, i)} on:contextmenu|preventDefault={rightClickHandler} on:keydown={e => arrowKeyHandler(e, i)}>
|
||||
<Icon class="icon" mdi="arrow-expand" size={2.25}/>
|
||||
</a>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</section>
|
||||
@ -37,4 +143,135 @@
|
||||
list-style: none;
|
||||
gap: 1em;
|
||||
}
|
||||
|
||||
li {
|
||||
position: relative;
|
||||
height: fit-content;
|
||||
border-radius: 10px;
|
||||
transition: border-radius .5s;
|
||||
}
|
||||
|
||||
figure {
|
||||
width: var(--image-width, 28.125em);
|
||||
height: var(--image-height, 18.75em);
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
overflow: hidden;
|
||||
border-radius: 10px;
|
||||
transition: background-color .5s, background-image .5s;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
li:not(.test) figure {
|
||||
background-color: rgba(0,0,0,.25);
|
||||
background-image: linear-gradient(rgba(0,0,0,.125), rgba(0,0,0,.25)), var(--image);
|
||||
}
|
||||
|
||||
picture {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
img {
|
||||
width: var(--image-width, 28.125em);
|
||||
height: var(--image-height, 18.75em);
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
top: 0;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
clip-path: polygon(0 0, 100% 0, 100% 100%, 0 100%);
|
||||
transform: scale(1.001);
|
||||
transition: transform .5s cubic-bezier(.23,.13,.45,.97), clip-path .5s cubic-bezier(.23,.13,.45,.97);
|
||||
}
|
||||
|
||||
li:not(.test) img {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
display: grid;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
padding: 1.5em;
|
||||
opacity: 0;
|
||||
transition: opacity .5s, background-color .5s;
|
||||
z-index: 1;
|
||||
gap: .25em;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
strong {
|
||||
font-size: 1.25em;
|
||||
letter-spacing: .05em;
|
||||
}
|
||||
|
||||
cite {
|
||||
font-size: .75em;
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
opacity: .8;
|
||||
}
|
||||
|
||||
a {
|
||||
display: grid;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: transparent;;
|
||||
color: transparent;
|
||||
text-decoration: none;
|
||||
transition: background-color .5s, color .5s;
|
||||
justify-content: center;
|
||||
align-content: center;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
li:not(.test) a:focus {
|
||||
outline: 2px solid var(--main-text-color);
|
||||
}
|
||||
|
||||
li:not(.test):hover img {
|
||||
transform: scale(1.25);
|
||||
}
|
||||
|
||||
li:hover figcaption {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
li:hover a {
|
||||
background-color: rgba(0,0,0,.75);
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
li.test {
|
||||
--image-width: 100%;
|
||||
--image-height: 100vh;
|
||||
z-index: 1;
|
||||
position: fixed;
|
||||
border-radius: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
li.test figure {
|
||||
background-color: rgba(0,0,0,0);
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
li.test figcaption {
|
||||
background-color: rgba(0,0,0,.75);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
li.test a {
|
||||
background-color: rgba(0,0,0,.75);
|
||||
z-index: -1;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user