tangled
alpha
login
or
join now
flo-bit.dev
/
blento
your personal website on atproto - mirror
blento.app
20
fork
atom
overview
issues
pulls
pipelines
commit
Florian
4 days ago
5399f15e
8e698c7d
+145
-144
4 changed files
expand all
collapse all
unified
split
src
lib
helper.ts
layout.ts
website
EditableWebsite.svelte
layout-mirror.ts
-140
src/lib/helper.ts
···
3
import { CardDefinitionsByType } from './cards';
4
import { deleteRecord, getCDNImageBlobUrl, putRecord, uploadBlob } from '$lib/atproto';
5
import * as TID from '@atcute/tid';
6
-
import { overlaps } from './layout';
7
-
8
export function clamp(value: number, min: number, max: number): number {
9
return Math.min(Math.max(value, min), max);
10
}
···
28
'bg-pink-500',
29
'bg-rose-500'
30
];
31
-
32
33
export function sortItems(a: Item, b: Item) {
34
return a.y * COLUMNS + a.x - b.y * COLUMNS - b.x;
···
50
a.color === b.color &&
51
a.page === b.page
52
);
53
-
}
54
-
55
-
export function setPositionOfNewItem(
56
-
newItem: Item,
57
-
items: Item[],
58
-
viewportCenter?: { gridY: number; isMobile: boolean }
59
-
) {
60
-
if (viewportCenter) {
61
-
const { gridY, isMobile } = viewportCenter;
62
-
63
-
if (isMobile) {
64
-
// Place at viewport center Y
65
-
newItem.mobileY = Math.max(0, Math.round(gridY - newItem.mobileH / 2));
66
-
newItem.mobileY = Math.floor(newItem.mobileY / 2) * 2;
67
-
68
-
// Try to find a free X at this Y
69
-
let found = false;
70
-
for (
71
-
newItem.mobileX = 0;
72
-
newItem.mobileX <= COLUMNS - newItem.mobileW;
73
-
newItem.mobileX += 2
74
-
) {
75
-
if (!items.some((item) => overlaps(newItem, item, true))) {
76
-
found = true;
77
-
break;
78
-
}
79
-
}
80
-
if (!found) {
81
-
newItem.mobileX = 0;
82
-
}
83
-
84
-
// Desktop: derive from mobile
85
-
newItem.y = Math.max(0, Math.round(newItem.mobileY / 2));
86
-
found = false;
87
-
for (newItem.x = 0; newItem.x <= COLUMNS - newItem.w; newItem.x += 2) {
88
-
if (!items.some((item) => overlaps(newItem, item, false))) {
89
-
found = true;
90
-
break;
91
-
}
92
-
}
93
-
if (!found) {
94
-
newItem.x = 0;
95
-
}
96
-
} else {
97
-
// Place at viewport center Y
98
-
newItem.y = Math.max(0, Math.round(gridY - newItem.h / 2));
99
-
100
-
// Try to find a free X at this Y
101
-
let found = false;
102
-
for (newItem.x = 0; newItem.x <= COLUMNS - newItem.w; newItem.x += 2) {
103
-
if (!items.some((item) => overlaps(newItem, item, false))) {
104
-
found = true;
105
-
break;
106
-
}
107
-
}
108
-
if (!found) {
109
-
newItem.x = 0;
110
-
}
111
-
112
-
// Mobile: derive from desktop
113
-
newItem.mobileY = Math.max(0, Math.round(newItem.y * 2));
114
-
found = false;
115
-
for (
116
-
newItem.mobileX = 0;
117
-
newItem.mobileX <= COLUMNS - newItem.mobileW;
118
-
newItem.mobileX += 2
119
-
) {
120
-
if (!items.some((item) => overlaps(newItem, item, true))) {
121
-
found = true;
122
-
break;
123
-
}
124
-
}
125
-
if (!found) {
126
-
newItem.mobileX = 0;
127
-
}
128
-
}
129
-
return;
130
-
}
131
-
132
-
let foundPosition = false;
133
-
while (!foundPosition) {
134
-
for (newItem.x = 0; newItem.x <= COLUMNS - newItem.w; newItem.x++) {
135
-
const collision = items.find((item) => overlaps(newItem, item, false));
136
-
if (!collision) {
137
-
foundPosition = true;
138
-
break;
139
-
}
140
-
}
141
-
if (!foundPosition) newItem.y += 1;
142
-
}
143
-
144
-
let foundMobilePosition = false;
145
-
while (!foundMobilePosition) {
146
-
for (newItem.mobileX = 0; newItem.mobileX <= COLUMNS - newItem.mobileW; newItem.mobileX += 1) {
147
-
const collision = items.find((item) => overlaps(newItem, item, true));
148
-
149
-
if (!collision) {
150
-
foundMobilePosition = true;
151
-
break;
152
-
}
153
-
}
154
-
if (!foundMobilePosition) newItem.mobileY! += 1;
155
-
}
156
-
}
157
-
158
-
/**
159
-
* Find a valid position for a new item in a single mode (desktop or mobile).
160
-
* This modifies the item's position properties in-place.
161
-
*/
162
-
export function findValidPosition(newItem: Item, items: Item[], mobile: boolean) {
163
-
if (mobile) {
164
-
let foundPosition = false;
165
-
newItem.mobileY = 0;
166
-
while (!foundPosition) {
167
-
for (newItem.mobileX = 0; newItem.mobileX <= COLUMNS - newItem.mobileW; newItem.mobileX++) {
168
-
const collision = items.find((item) => overlaps(newItem, item, true));
169
-
if (!collision) {
170
-
foundPosition = true;
171
-
break;
172
-
}
173
-
}
174
-
if (!foundPosition) newItem.mobileY! += 1;
175
-
}
176
-
} else {
177
-
let foundPosition = false;
178
-
newItem.y = 0;
179
-
while (!foundPosition) {
180
-
for (newItem.x = 0; newItem.x <= COLUMNS - newItem.w; newItem.x++) {
181
-
const collision = items.find((item) => overlaps(newItem, item, false));
182
-
if (!collision) {
183
-
foundPosition = true;
184
-
break;
185
-
}
186
-
}
187
-
if (!foundPosition) newItem.y += 1;
188
-
}
189
-
}
190
}
191
192
export async function refreshData(data: { updatedAt?: number; handle: string }) {
···
3
import { CardDefinitionsByType } from './cards';
4
import { deleteRecord, getCDNImageBlobUrl, putRecord, uploadBlob } from '$lib/atproto';
5
import * as TID from '@atcute/tid';
0
0
6
export function clamp(value: number, min: number, max: number): number {
7
return Math.min(Math.max(value, min), max);
8
}
···
26
'bg-pink-500',
27
'bg-rose-500'
28
];
0
29
30
export function sortItems(a: Item, b: Item) {
31
return a.y * COLUMNS + a.x - b.y * COLUMNS - b.x;
···
47
a.color === b.color &&
48
a.page === b.page
49
);
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
50
}
51
52
export async function refreshData(data: { updatedAt?: number; handle: string }) {
+142
src/lib/layout.ts
···
108
const compacted = verticalCompactor.compact(layout, COLUMNS) as LayoutItem[];
109
applyLayout(items, compacted, mobile);
110
}
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
···
108
const compacted = verticalCompactor.compact(layout, COLUMNS) as LayoutItem[];
109
applyLayout(items, compacted, mobile);
110
}
111
+
112
+
export function setPositionOfNewItem(
113
+
newItem: Item,
114
+
items: Item[],
115
+
viewportCenter?: { gridY: number; isMobile: boolean }
116
+
) {
117
+
const desktopLayout = toLayout(items, false);
118
+
const mobileLayout = toLayout(items, true);
119
+
120
+
function hasCollision(mobile: boolean): boolean {
121
+
const layout = mobile ? mobileLayout : desktopLayout;
122
+
return getFirstCollision(layout, toLayoutItem(newItem, mobile)) !== undefined;
123
+
}
124
+
125
+
if (viewportCenter) {
126
+
const { gridY, isMobile } = viewportCenter;
127
+
128
+
if (isMobile) {
129
+
// Place at viewport center Y
130
+
newItem.mobileY = Math.max(0, Math.round(gridY - newItem.mobileH / 2));
131
+
newItem.mobileY = Math.floor(newItem.mobileY / 2) * 2;
132
+
133
+
// Try to find a free X at this Y
134
+
let found = false;
135
+
for (
136
+
newItem.mobileX = 0;
137
+
newItem.mobileX <= COLUMNS - newItem.mobileW;
138
+
newItem.mobileX += 2
139
+
) {
140
+
if (!hasCollision(true)) {
141
+
found = true;
142
+
break;
143
+
}
144
+
}
145
+
if (!found) {
146
+
newItem.mobileX = 0;
147
+
}
148
+
149
+
// Desktop: derive from mobile
150
+
newItem.y = Math.max(0, Math.round(newItem.mobileY / 2));
151
+
found = false;
152
+
for (newItem.x = 0; newItem.x <= COLUMNS - newItem.w; newItem.x += 2) {
153
+
if (!hasCollision(false)) {
154
+
found = true;
155
+
break;
156
+
}
157
+
}
158
+
if (!found) {
159
+
newItem.x = 0;
160
+
}
161
+
} else {
162
+
// Place at viewport center Y
163
+
newItem.y = Math.max(0, Math.round(gridY - newItem.h / 2));
164
+
165
+
// Try to find a free X at this Y
166
+
let found = false;
167
+
for (newItem.x = 0; newItem.x <= COLUMNS - newItem.w; newItem.x += 2) {
168
+
if (!hasCollision(false)) {
169
+
found = true;
170
+
break;
171
+
}
172
+
}
173
+
if (!found) {
174
+
newItem.x = 0;
175
+
}
176
+
177
+
// Mobile: derive from desktop
178
+
newItem.mobileY = Math.max(0, Math.round(newItem.y * 2));
179
+
found = false;
180
+
for (
181
+
newItem.mobileX = 0;
182
+
newItem.mobileX <= COLUMNS - newItem.mobileW;
183
+
newItem.mobileX += 2
184
+
) {
185
+
if (!hasCollision(true)) {
186
+
found = true;
187
+
break;
188
+
}
189
+
}
190
+
if (!found) {
191
+
newItem.mobileX = 0;
192
+
}
193
+
}
194
+
return;
195
+
}
196
+
197
+
let foundPosition = false;
198
+
while (!foundPosition) {
199
+
for (newItem.x = 0; newItem.x <= COLUMNS - newItem.w; newItem.x++) {
200
+
if (!hasCollision(false)) {
201
+
foundPosition = true;
202
+
break;
203
+
}
204
+
}
205
+
if (!foundPosition) newItem.y += 1;
206
+
}
207
+
208
+
let foundMobilePosition = false;
209
+
while (!foundMobilePosition) {
210
+
for (newItem.mobileX = 0; newItem.mobileX <= COLUMNS - newItem.mobileW; newItem.mobileX += 1) {
211
+
if (!hasCollision(true)) {
212
+
foundMobilePosition = true;
213
+
break;
214
+
}
215
+
}
216
+
if (!foundMobilePosition) newItem.mobileY! += 1;
217
+
}
218
+
}
219
+
220
+
/**
221
+
* Find a valid position for a new item in a single mode (desktop or mobile).
222
+
* This modifies the item's position properties in-place.
223
+
*/
224
+
export function findValidPosition(newItem: Item, items: Item[], mobile: boolean) {
225
+
const layout = toLayout(items, mobile);
226
+
227
+
if (mobile) {
228
+
let foundPosition = false;
229
+
newItem.mobileY = 0;
230
+
while (!foundPosition) {
231
+
for (newItem.mobileX = 0; newItem.mobileX <= COLUMNS - newItem.mobileW; newItem.mobileX++) {
232
+
if (!getFirstCollision(layout, toLayoutItem(newItem, true))) {
233
+
foundPosition = true;
234
+
break;
235
+
}
236
+
}
237
+
if (!foundPosition) newItem.mobileY! += 1;
238
+
}
239
+
} else {
240
+
let foundPosition = false;
241
+
newItem.y = 0;
242
+
while (!foundPosition) {
243
+
for (newItem.x = 0; newItem.x <= COLUMNS - newItem.w; newItem.x++) {
244
+
if (!getFirstCollision(layout, toLayoutItem(newItem, false))) {
245
+
foundPosition = true;
246
+
break;
247
+
}
248
+
}
249
+
if (!foundPosition) newItem.y += 1;
250
+
}
251
+
}
252
+
}
+1
-2
src/lib/website/EditableWebsite.svelte
···
11
isTyping,
12
savePage,
13
scrollToItem,
14
-
setPositionOfNewItem,
15
validateLink,
16
getImage
17
} from '../helper';
···
39
import CardCommand from '$lib/components/card-command/CardCommand.svelte';
40
import { shouldMirror, mirrorLayout } from './layout-mirror';
41
import { SvelteMap } from 'svelte/reactivity';
42
-
import { fixCollisions, compactItems, fixAllCollisions } from '$lib/layout';
43
44
let {
45
data
···
11
isTyping,
12
savePage,
13
scrollToItem,
0
14
validateLink,
15
getImage
16
} from '../helper';
···
38
import CardCommand from '$lib/components/card-command/CardCommand.svelte';
39
import { shouldMirror, mirrorLayout } from './layout-mirror';
40
import { SvelteMap } from 'svelte/reactivity';
41
+
import { fixCollisions, compactItems, fixAllCollisions, setPositionOfNewItem } from '$lib/layout';
42
43
let {
44
data
+2
-2
src/lib/website/layout-mirror.ts
···
1
import { COLUMNS } from '$lib';
2
import { CardDefinitionsByType } from '$lib/cards';
3
-
import { clamp, findValidPosition } from '$lib/helper';
4
-
import { fixAllCollisions } from '$lib/layout';
5
import type { Item } from '$lib/types';
6
7
/**
···
1
import { COLUMNS } from '$lib';
2
import { CardDefinitionsByType } from '$lib/cards';
3
+
import { clamp } from '$lib/helper';
4
+
import { fixAllCollisions, findValidPosition } from '$lib/layout';
5
import type { Item } from '$lib/types';
6
7
/**