your personal website on atproto - mirror
blento.app
1import { COLUMNS } from '$lib';
2import { CardDefinitionsByType } from '$lib/cards';
3import { clamp, findValidPosition, fixAllCollisions } from '$lib/helper';
4import type { Item } from '$lib/types';
5
6/**
7 * Returns true when mirroring should still happen (i.e. user hasn't edited both layouts).
8 * editedOn: 0/undefined = never, 1 = desktop only, 2 = mobile only, 3 = both
9 */
10export function shouldMirror(editedOn: number | undefined): boolean {
11 return (editedOn ?? 0) !== 3;
12}
13
14/** Snap a value to the nearest even integer (min 2). */
15function snapEven(v: number): number {
16 return Math.max(2, Math.round(v / 2) * 2);
17}
18
19/**
20 * Compute the other layout's size for a single item, preserving aspect ratio.
21 * Clamps to the card definition's minW/maxW/minH/maxH if defined.
22 * Mutates the item in-place.
23 */
24export function mirrorItemSize(item: Item, fromMobile: boolean): void {
25 const def = CardDefinitionsByType[item.cardType];
26
27 if (fromMobile) {
28 // Mobile → Desktop: halve both dimensions, then clamp to card def constraints
29 // (constraints are in desktop units)
30 item.w = clamp(snapEven(item.mobileW / 2), def?.minW ?? 2, def?.maxW ?? COLUMNS);
31 item.h = clamp(Math.round(item.mobileH / 2), def?.minH ?? 1, def?.maxH ?? Infinity);
32 } else {
33 // Desktop → Mobile: double both dimensions
34 // (don't apply card def constraints — they're in desktop units)
35 item.mobileW = Math.min(item.w * 2, COLUMNS);
36 item.mobileH = Math.max(item.h * 2, 2);
37 }
38}
39
40/**
41 * Mirror the full layout from one view to the other.
42 * Copies sizes proportionally and maps positions, then resolves collisions.
43 * Mutates items in-place.
44 */
45export function mirrorLayout(items: Item[], fromMobile: boolean): void {
46 // Mirror sizes first
47 for (const item of items) {
48 mirrorItemSize(item, fromMobile);
49 }
50
51 if (fromMobile) {
52 // Mobile → Desktop: reflow items to use the full grid width.
53 // Sort by mobile position so items are placed in reading order.
54 const sorted = items.toSorted((a, b) => a.mobileY - b.mobileY || a.mobileX - b.mobileX);
55
56 // Place each item into the first available spot on the desktop grid
57 const placed: Item[] = [];
58 for (const item of sorted) {
59 item.x = 0;
60 item.y = 0;
61 findValidPosition(item, placed, false);
62 placed.push(item);
63 }
64 } else {
65 // Desktop → Mobile: proportional positions
66 for (const item of items) {
67 item.mobileX = clamp(Math.floor((item.x * 2) / 2) * 2, 0, COLUMNS - item.mobileW);
68 item.mobileY = Math.max(0, Math.round(item.y * 2));
69 }
70 fixAllCollisions(items, true);
71 }
72}