tangled
alpha
login
or
join now
flo-bit.dev
/
blento
your personal website on atproto - mirror
blento.app
16
fork
atom
overview
issues
pulls
pipelines
v0.1
jycouet
1 week ago
bc24bc8f
ce18dffd
+50
-90
5 changed files
expand all
collapse all
unified
split
src
lib
cards
BaseCard
BaseEditingCard.svelte
components
qr
QRCodeDisplay.svelte
QRCodeModal.svelte
qrOverlay.svelte.ts
website
Profile.svelte
+4
-4
src/lib/cards/BaseCard/BaseEditingCard.svelte
···
57
57
58
58
const cardDef = $derived(CardDefinitionsByType[item.cardType]);
59
59
60
60
-
const minW = $derived(cardDef?.minW ?? (isMobile() ? 2 : 2));
61
61
-
const minH = $derived(cardDef?.minH ?? (isMobile() ? 2 : 2));
60
60
+
const minW = $derived(cardDef.minW ?? (isMobile() ? 2 : 2));
61
61
+
const minH = $derived(cardDef.minH ?? (isMobile() ? 2 : 2));
62
62
63
63
-
const maxW = $derived(cardDef?.maxW ?? COLUMNS);
64
64
-
const maxH = $derived(cardDef?.maxH ?? (isMobile() ? 12 : 6));
63
63
+
const maxW = $derived(cardDef.maxW ?? COLUMNS);
64
64
+
const maxH = $derived(cardDef.maxH ?? (isMobile() ? 12 : 6));
65
65
66
66
// Resize handle state
67
67
let isResizing = $state(false);
+24
-38
src/lib/components/qr/QRCodeDisplay.svelte
···
1
1
<script lang="ts">
2
2
-
import { browser } from '$app/environment';
2
2
+
import { onMount } from 'svelte';
3
3
4
4
-
let { url, size = 280, logo }: { url: string; size?: number; logo?: string } = $props();
4
4
+
let { url, size = 280 }: { url: string; size?: number } = $props();
5
5
6
6
let container: HTMLDivElement | undefined = $state();
7
7
8
8
-
$effect(() => {
9
9
-
if (!browser || !container) return;
10
10
-
11
11
-
const render = async () => {
12
12
-
const QRCodeStylingModule = await import('qr-code-styling');
13
13
-
const QRCodeStyling = QRCodeStylingModule.default;
14
14
-
15
15
-
container!.innerHTML = '';
8
8
+
onMount(async () => {
9
9
+
if (!container) return;
16
10
17
17
-
const options: ConstructorParameters<typeof QRCodeStyling>[0] = {
18
18
-
width: size,
19
19
-
height: size,
20
20
-
data: url,
21
21
-
dotsOptions: {
22
22
-
color: '#000',
23
23
-
type: 'rounded'
24
24
-
},
25
25
-
backgroundOptions: {
26
26
-
color: '#fff'
27
27
-
},
28
28
-
cornersSquareOptions: {
29
29
-
type: 'extra-rounded'
30
30
-
},
31
31
-
cornersDotOptions: {
32
32
-
type: 'dot'
33
33
-
}
34
34
-
};
11
11
+
const module = await import('qr-code-styling');
12
12
+
const QRCodeStyling = module.default;
35
13
36
36
-
if (logo) {
37
37
-
options.image = logo;
38
38
-
options.imageOptions = {
39
39
-
crossOrigin: 'anonymous',
40
40
-
margin: 4
41
41
-
};
14
14
+
const options: ConstructorParameters<typeof QRCodeStyling>[0] = {
15
15
+
width: size,
16
16
+
height: size,
17
17
+
data: url,
18
18
+
dotsOptions: {
19
19
+
color: '#000',
20
20
+
type: 'rounded'
21
21
+
},
22
22
+
backgroundOptions: {
23
23
+
color: '#fff'
24
24
+
},
25
25
+
cornersSquareOptions: {
26
26
+
type: 'extra-rounded'
27
27
+
},
28
28
+
cornersDotOptions: {
29
29
+
type: 'dot'
42
30
}
43
43
-
44
44
-
const qrCode = new QRCodeStyling(options);
45
45
-
qrCode.append(container!);
46
31
};
47
32
48
48
-
render();
33
33
+
const qrCode = new QRCodeStyling(options);
34
34
+
qrCode.append(container);
49
35
});
50
36
</script>
51
37
+1
-3
src/lib/components/qr/QRCodeModal.svelte
···
28
28
toast.error('Failed to copy');
29
29
}
30
30
}
31
31
-
32
32
-
const logoUrl = $derived(context.avatar || context.favicon);
33
31
</script>
34
32
35
33
<Modal bind:open closeButton={true} class="max-w-sm">
···
54
52
{/if}
55
53
56
54
<div class="overflow-hidden rounded-2xl">
57
57
-
<QRCodeDisplay url={href} size={280} logo={logoUrl} />
55
55
+
<QRCodeDisplay url={href} size={280} />
58
56
</div>
59
57
60
58
<div class="flex w-full items-center gap-2">
+9
-5
src/lib/components/qr/qrOverlay.svelte.ts
···
12
12
}
13
13
14
14
export function qrOverlay(
15
15
-
node: HTMLAnchorElement,
16
16
-
params: { context?: QRContext; disabled?: boolean } = {}
15
15
+
node: HTMLElement,
16
16
+
params: { href?: string; context?: QRContext; disabled?: boolean } = {}
17
17
) {
18
18
const LONG_PRESS_DURATION = 500;
19
19
let longPressTimer: ReturnType<typeof setTimeout> | null = null;
20
20
let isLongPress = false;
21
21
22
22
+
function getHref() {
23
23
+
return params.href || (node as HTMLAnchorElement).href || '';
24
24
+
}
25
25
+
22
26
function startLongPress() {
23
27
if (params.disabled) return;
24
28
isLongPress = false;
25
29
longPressTimer = setTimeout(() => {
26
30
isLongPress = true;
27
27
-
openModal?.(node.href, params.context ?? {});
31
31
+
openModal?.(getHref(), params.context ?? {});
28
32
}, LONG_PRESS_DURATION);
29
33
}
30
34
···
45
49
function handleContextMenu(e: MouseEvent) {
46
50
if (params.disabled) return;
47
51
e.preventDefault();
48
48
-
openModal?.(node.href, params.context ?? {});
52
52
+
openModal?.(getHref(), params.context ?? {});
49
53
}
50
54
51
55
node.addEventListener('pointerdown', startLongPress);
···
56
60
node.addEventListener('contextmenu', handleContextMenu);
57
61
58
62
return {
59
59
-
update(newParams: { context?: QRContext; disabled?: boolean }) {
63
63
+
update(newParams: { href?: string; context?: QRContext; disabled?: boolean }) {
60
64
params = newParams;
61
65
},
62
66
destroy() {
+12
-40
src/lib/website/Profile.svelte
···
8
8
import { getDescription, getName } from '$lib/helper';
9
9
import { page } from '$app/state';
10
10
import type { ActorIdentifier } from '@atcute/lexicons';
11
11
-
import QRCodeModal from '$lib/components/qr/QRCodeModal.svelte';
11
11
+
import { qrOverlay } from '$lib/components/qr/qrOverlay.svelte';
12
12
13
13
let {
14
14
data,
···
22
22
renderer.link = ({ href, title, text }) =>
23
23
`<a target="_blank" href="${href}" title="${title}">${text}</a>`;
24
24
25
25
-
let qrOpen = $state(false);
26
26
-
let longPressTimer: ReturnType<typeof setTimeout> | null = null;
27
27
-
28
28
-
const profileUrl = $derived(`${page.url}/${data.handle}`);
29
29
-
30
30
-
function startLongPress() {
31
31
-
longPressTimer = setTimeout(() => {
32
32
-
qrOpen = true;
33
33
-
}, 500);
34
34
-
}
35
35
-
36
36
-
function cancelLongPress() {
37
37
-
if (longPressTimer) {
38
38
-
clearTimeout(longPressTimer);
39
39
-
longPressTimer = null;
40
40
-
}
41
41
-
}
42
42
-
43
43
-
function handleContextMenu(e: MouseEvent) {
44
44
-
e.preventDefault();
45
45
-
qrOpen = true;
46
46
-
}
25
25
+
const profileUrl = $derived(`${page.url.origin}/${data.handle}`);
47
26
</script>
48
27
49
28
<!-- lg:fixed lg:h-screen lg:w-1/4 lg:max-w-none lg:px-12 lg:pt-24 xl:w-1/3 -->
···
51
30
class="mx-auto flex max-w-lg flex-col justify-between px-8 @5xl/wrapper:fixed @5xl/wrapper:h-screen @5xl/wrapper:w-1/4 @5xl/wrapper:max-w-none @5xl/wrapper:px-12"
52
31
>
53
32
<div class="flex flex-col gap-4 pt-16 pb-8 @5xl/wrapper:h-screen @5xl/wrapper:pt-24">
54
54
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
55
55
-
<div
56
56
-
class="w-fit cursor-pointer"
57
57
-
onpointerdown={startLongPress}
58
58
-
onpointerup={cancelLongPress}
59
59
-
onpointercancel={cancelLongPress}
60
60
-
onpointerleave={cancelLongPress}
61
61
-
oncontextmenu={handleContextMenu}
33
33
+
<a
34
34
+
href={profileUrl}
35
35
+
class="w-fit"
36
36
+
use:qrOverlay={{
37
37
+
context: {
38
38
+
title: getName(data),
39
39
+
avatar: data.profile.avatar
40
40
+
}
41
41
+
}}
62
42
>
63
43
{#if data.profile.avatar}
64
44
<img
···
69
49
{:else}
70
50
<div class="bg-base-300 dark:bg-base-700 size-32 rounded-full @5xl/wrapper:size-44"></div>
71
51
{/if}
72
72
-
</div>
73
73
-
<QRCodeModal
74
74
-
bind:open={qrOpen}
75
75
-
href={profileUrl}
76
76
-
context={{
77
77
-
title: getName(data),
78
78
-
avatar: data.profile.avatar
79
79
-
}}
80
80
-
/>
52
52
+
</a>
81
53
82
54
<div class="text-4xl font-bold wrap-anywhere">
83
55
{getName(data)}