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