feat(web): сохранять размер миниатюр при скрытых боковых панелях

В скрытом режиме панели больше не растягивают плитки: сетка пересчитывает
число колонок так, чтобы размер миниатюры остался прежним, а в строке
помещалось больше фото. Каркас публикует ширину, которую занимают панели в
текущей раскладке (рейл на широких экранах, панель на сверхшироких), сетка
использует её для пересчёта, а навигация курсора стрелками учитывает
реальное число колонок.
This commit is contained in:
iu
2026-10-20 21:38:02 +03:00
parent 45a953f8bb
commit 50b9983be4
6 changed files with 69 additions and 7 deletions
+31 -1
View File
@@ -27,17 +27,32 @@
const RAIL = { min: 176, max: 480, key: 'rail-width', def: 240 };
const PANEL = { min: 220, max: 560, key: 'panel-width', def: 288 };
// Ширина разделителя (w-1) между панелью и содержимым.
const HANDLE_PX = 4;
let railWidth = $state(RAIL.def);
let panelWidth = $state(PANEL.def);
let isFullscreen = $state(false);
let isWide = $state(false);
let isExtraWide = $state(false);
// Ширина, которую занимают панели в текущей раскладке: рейл и панель
// встают рядом с содержимым на широких экранах, на узких — поверх него.
const reserved = $derived(
(isWide ? railWidth + HANDLE_PX : 0) +
(isExtraWide && panel && panelVisible ? panelWidth + HANDLE_PX : 0)
);
$effect(() => {
// Режим скрытия действует только там, где он доступен (просмотр альбома).
if (!panelsToggle) panels.reset();
});
$effect(() => {
panels.reserved = reserved;
});
onMount(() => {
const r = parseInt(localStorage.getItem(RAIL.key) ?? '', 10);
if (r >= RAIL.min && r <= RAIL.max) railWidth = r;
@@ -47,7 +62,22 @@
const onFs = () => (isFullscreen = !!document.fullscreenElement);
document.addEventListener('fullscreenchange', onFs);
onFs();
return () => document.removeEventListener('fullscreenchange', onFs);
const mqLg = window.matchMedia('(min-width: 1024px)');
const mqXl = window.matchMedia('(min-width: 1280px)');
const sync = () => {
isWide = mqLg.matches;
isExtraWide = mqXl.matches;
};
sync();
mqLg.addEventListener('change', sync);
mqXl.addEventListener('change', sync);
return () => {
document.removeEventListener('fullscreenchange', onFs);
mqLg.removeEventListener('change', sync);
mqXl.removeEventListener('change', sync);
};
});
async function toggleFullscreen() {
+7 -3
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import Icon from './Icon.svelte';
import { density } from '$lib/stores/density.svelte';
import { grid } from '$lib/stores/grid.svelte';
import { isPanoTag } from '$lib/pano';
import type { Photo } from '$lib/types';
@@ -32,6 +32,10 @@
const GAP_PX = 8;
let gridWidth = $state(0);
$effect(() => {
grid.width = gridWidth;
});
// Offer every variant the photo actually has; the backend generates the
// medium variant asynchronously, so an incomplete set must still render.
function srcsetFor(photo: Photo): string {
@@ -44,7 +48,7 @@
const sizes = $derived(
gridWidth > 0
? `${Math.max(1, Math.round((gridWidth - (density.cols - 1) * GAP_PX) / density.cols))}px`
? `${Math.max(1, Math.round((gridWidth - (grid.cols - 1) * GAP_PX) / grid.cols))}px`
: undefined
);
@@ -76,7 +80,7 @@
<ul
class="grid gap-2"
bind:clientWidth={gridWidth}
style="grid-template-columns: repeat({density.cols}, minmax(0, 1fr));"
style="grid-template-columns: repeat({grid.cols}, minmax(0, 1fr));"
>
{#each photos as photo, i (photo.id)}
<li>
+20
View File
@@ -0,0 +1,20 @@
import { density } from './density.svelte';
import { panels } from './panels.svelte';
class GridStore {
/** Ширина контейнера сетки (px). */
width = $state(0);
/**
* Число колонок сетки. Когда боковые панели скрыты, сетка шире, поэтому
* колонок становится больше, а плитки остаются того же размера, что и с
* видимыми панелями.
*/
cols = $derived.by(() => {
const visible = this.width - panels.reserved;
if (!panels.hidden || this.width <= 0 || visible <= 0) return density.cols;
return Math.max(1, Math.round((density.cols * this.width) / visible));
});
}
export const grid = new GridStore();
+2
View File
@@ -5,6 +5,8 @@ class PanelsStore {
railPeek = $state(false);
/** Правая панель показана у кромки в скрытом режиме. */
panelPeek = $state(false);
/** Ширина (px), которую панели занимают в раскладке, — для пересчёта колонок сетки. */
reserved = $state(0);
toggle() {
this.hidden = !this.hidden;