your personal website on atproto - mirror
blento.app
1import { COLUMNS } from '$lib';
2import { CardDefinitionsByType } from '$lib/cards';
3import { clamp } from '$lib/helper';
4import { findValidPosition } from './algorithms';
5import type { Item } from '$lib/types';
6
7export type LayoutMode = 'desktop-leads' | 'mobile-leads' | 'independent';
8
9/**
10 * Determine whether mirroring should happen and in which direction.
11 * Returns 'desktop' or 'mobile' for the source layout, or false if no mirroring.
12 *
13 * @param editedOn - bitflag: 0=never, 1=desktop, 2=mobile, 3=both
14 * @param layoutMode - explicit override (takes precedence when set)
15 * @param editingMobile - true if the current edit is on mobile
16 */
17export function shouldMirror(
18 editedOn: number | undefined,
19 layoutMode: LayoutMode | undefined,
20 editingMobile: boolean
21): boolean {
22 if (layoutMode) {
23 if (layoutMode === 'independent') return false;
24 if (layoutMode === 'desktop-leads') return !editingMobile;
25 if (layoutMode === 'mobile-leads') return editingMobile;
26 return false;
27 }
28 // Legacy behavior: mirror as long as both layouts haven't been edited
29 return (editedOn ?? 0) !== 3;
30}
31
32/** Snap a value to the nearest even integer (min 2). */
33function snapEven(v: number): number {
34 return Math.max(2, Math.round(v / 2) * 2);
35}
36
37/**
38 * Compute the other layout's size for a single item, preserving aspect ratio.
39 * Clamps to the card definition's minW/maxW/minH/maxH if defined.
40 * Mutates the item in-place.
41 */
42export function mirrorItemSize(item: Item, fromMobile: boolean): void {
43 const def = CardDefinitionsByType[item.cardType];
44
45 if (fromMobile) {
46 // Mobile → Desktop: halve both dimensions, then clamp to card def constraints
47 // (constraints are in desktop units)
48 item.w = clamp(snapEven(item.mobileW / 2), def?.minW ?? 2, def?.maxW ?? COLUMNS);
49 item.h = clamp(Math.round(item.mobileH / 2), def?.minH ?? 1, def?.maxH ?? Infinity);
50 } else {
51 // Desktop → Mobile: double both dimensions
52 // (don't apply card def constraints — they're in desktop units)
53 item.mobileW = Math.min(item.w * 2, COLUMNS);
54 item.mobileH = Math.max(item.h * 2, 2);
55 }
56}
57
58/**
59 * Mirror the full layout from one view to the other.
60 * Copies sizes proportionally and reflows items in reading order, then resolves collisions.
61 * Mutates items in-place.
62 */
63export function mirrorLayout(items: Item[], fromMobile: boolean): void {
64 // Mirror sizes first
65 for (const item of items) {
66 mirrorItemSize(item, fromMobile);
67 }
68
69 if (fromMobile) {
70 // Mobile → Desktop: reflow items in mobile reading order
71 const sorted = items.toSorted((a, b) => a.mobileY - b.mobileY || a.mobileX - b.mobileX);
72 const placed: Item[] = [];
73 for (const item of sorted) {
74 item.x = 0;
75 item.y = 0;
76 findValidPosition(item, placed, false);
77 placed.push(item);
78 }
79 } else {
80 // Desktop → Mobile: reflow items in desktop reading order
81 const sorted = items.toSorted((a, b) => a.y - b.y || a.x - b.x);
82 const placed: Item[] = [];
83 for (const item of sorted) {
84 item.mobileX = 0;
85 item.mobileY = 0;
86 findValidPosition(item, placed, true);
87 placed.push(item);
88 }
89 }
90}