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