your personal website on atproto - mirror
blento.app
1import { COLUMNS } from '$lib';
2import { CardDefinitionsByType } from '$lib/cards';
3import { clamp, 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 const minW = def?.minW ?? 2;
27 const maxW = def?.maxW ?? COLUMNS;
28 const minH = def?.minH ?? 2;
29 const maxH = def?.maxH ?? Infinity;
30
31 if (fromMobile) {
32 const srcW = item.mobileW;
33 const srcH = item.mobileH;
34 // Full-width cards stay full-width
35 item.w = srcW >= COLUMNS ? COLUMNS : clamp(snapEven(srcW / 2), minW, maxW);
36 item.h = clamp(snapEven((srcH * item.w) / srcW), minH, maxH);
37 } else {
38 const srcW = item.w;
39 const srcH = item.h;
40 // Full-width cards stay full-width
41 if (srcW >= COLUMNS) {
42 item.mobileW = clamp(COLUMNS, minW, Math.min(maxW, COLUMNS));
43 } else {
44 const scaleFactor = Math.min(2, COLUMNS / srcW);
45 item.mobileW = clamp(snapEven(srcW * scaleFactor), minW, Math.min(maxW, COLUMNS));
46 }
47 item.mobileH = clamp(snapEven((srcH * item.mobileW) / srcW), minH, maxH);
48 }
49}
50
51/**
52 * Mirror the full layout from one view to the other.
53 * Copies sizes proportionally and maps positions, then resolves collisions.
54 * Mutates items in-place.
55 */
56export function mirrorLayout(items: Item[], fromMobile: boolean): void {
57 for (const item of items) {
58 mirrorItemSize(item, fromMobile);
59
60 if (fromMobile) {
61 // Mobile → Desktop positions
62 item.x = clamp(Math.floor(item.mobileX / 2 / 2) * 2, 0, COLUMNS - item.w);
63 item.y = Math.max(0, Math.round(item.mobileY / 2));
64 } else {
65 // Desktop → Mobile positions
66 item.mobileX = clamp(Math.floor((item.x * 2) / 2) * 2, 0, COLUMNS - item.mobileW);
67 item.mobileY = Math.max(0, Math.round(item.y * 2));
68 }
69 }
70
71 // Resolve collisions on the target layout
72 fixAllCollisions(items, !fromMobile);
73}