tangled
alpha
login
or
join now
flo-bit.dev
/
blento
21
fork
atom
your personal website on atproto - mirror
blento.app
21
fork
atom
overview
issues
pulls
pipelines
add plyrfm track embed
Florian
4 weeks ago
6d5e9596
d426f2df
+131
-1
4 changed files
expand all
collapse all
unified
split
src
lib
cards
index.ts
media
PlyrFMCard
CreatePlyrFMCardModal.svelte
PlyrFMCard.svelte
index.ts
+3
-1
src/lib/cards/index.ts
···
46
46
import { LastFMTopTracksCardDefinition } from './media/LastFMCard/LastFMTopTracksCard';
47
47
import { LastFMTopAlbumsCardDefinition } from './media/LastFMCard/LastFMTopAlbumsCard';
48
48
import { LastFMProfileCardDefinition } from './media/LastFMCard/LastFMProfileCard';
49
49
+
import { PlyrFMCardDefinition } from './media/PlyrFMCard';
49
50
// import { Model3DCardDefinition } from './visual/Model3DCard';
50
51
51
52
export const AllCardDefinitions = [
···
96
97
LastFMRecentTracksCardDefinition,
97
98
LastFMTopTracksCardDefinition,
98
99
LastFMTopAlbumsCardDefinition,
99
99
-
LastFMProfileCardDefinition
100
100
+
LastFMProfileCardDefinition,
101
101
+
PlyrFMCardDefinition
100
102
] as const;
101
103
102
104
export const CardDefinitionsByType = AllCardDefinitions.reduce(
+50
src/lib/cards/media/PlyrFMCard/CreatePlyrFMCardModal.svelte
···
1
1
+
<script lang="ts">
2
2
+
import { Alert, Button, Input, Modal, Subheading } from '@foxui/core';
3
3
+
import type { CreationModalComponentProps } from '../../types';
4
4
+
import { toPlyrFMEmbedUrl } from './index';
5
5
+
6
6
+
let { item = $bindable(), oncreate, oncancel }: CreationModalComponentProps = $props();
7
7
+
8
8
+
let errorMessage = $state('');
9
9
+
10
10
+
function checkUrl() {
11
11
+
errorMessage = '';
12
12
+
13
13
+
const embedUrl = toPlyrFMEmbedUrl(item.cardData.href);
14
14
+
15
15
+
if (!embedUrl) {
16
16
+
errorMessage = 'Please enter a valid plyr.fm track URL';
17
17
+
return false;
18
18
+
}
19
19
+
20
20
+
item.cardData.href = embedUrl;
21
21
+
22
22
+
return true;
23
23
+
}
24
24
+
</script>
25
25
+
26
26
+
<Modal open={true} closeButton={false}>
27
27
+
<Subheading>Enter a Plyr.fm track URL</Subheading>
28
28
+
<Input
29
29
+
bind:value={item.cardData.href}
30
30
+
placeholder="https://plyr.fm/track/..."
31
31
+
onkeydown={(e) => {
32
32
+
if (e.key === 'Enter' && checkUrl()) oncreate();
33
33
+
}}
34
34
+
/>
35
35
+
36
36
+
{#if errorMessage}
37
37
+
<Alert type="error" title="Invalid URL"><span>{errorMessage}</span></Alert>
38
38
+
{/if}
39
39
+
40
40
+
<div class="mt-4 flex justify-end gap-2">
41
41
+
<Button onclick={oncancel} variant="ghost">Cancel</Button>
42
42
+
<Button
43
43
+
onclick={() => {
44
44
+
if (checkUrl()) oncreate();
45
45
+
}}
46
46
+
>
47
47
+
Create
48
48
+
</Button>
49
49
+
</div>
50
50
+
</Modal>
+22
src/lib/cards/media/PlyrFMCard/PlyrFMCard.svelte
···
1
1
+
<script lang="ts">
2
2
+
import type { ContentComponentProps } from '../../types';
3
3
+
4
4
+
let { item, isEditing }: ContentComponentProps = $props();
5
5
+
</script>
6
6
+
7
7
+
{#if item.cardData?.href}
8
8
+
<div class="absolute inset-0 p-2">
9
9
+
<iframe
10
10
+
class={['h-full w-full rounded-2xl', isEditing && 'pointer-events-none']}
11
11
+
src={item.cardData.href}
12
12
+
frameborder="0"
13
13
+
allow="autoplay; encrypted-media"
14
14
+
loading="lazy"
15
15
+
title="Plyr.fm track"
16
16
+
></iframe>
17
17
+
</div>
18
18
+
{:else}
19
19
+
<div class="flex h-full items-center justify-center p-4 text-center opacity-50">
20
20
+
Missing Plyr.fm URL
21
21
+
</div>
22
22
+
{/if}
+56
src/lib/cards/media/PlyrFMCard/index.ts
···
1
1
+
import type { CardDefinition } from '../../types';
2
2
+
import CreatePlyrFMCardModal from './CreatePlyrFMCardModal.svelte';
3
3
+
import PlyrFMCard from './PlyrFMCard.svelte';
4
4
+
5
5
+
const cardType = 'plyr-fm';
6
6
+
7
7
+
export const PlyrFMCardDefinition = {
8
8
+
type: cardType,
9
9
+
contentComponent: PlyrFMCard,
10
10
+
creationModalComponent: CreatePlyrFMCardModal,
11
11
+
createNew: (item) => {
12
12
+
item.cardType = cardType;
13
13
+
item.cardData = {};
14
14
+
item.w = 4;
15
15
+
item.mobileW = 8;
16
16
+
item.h = 2;
17
17
+
item.mobileH = 4;
18
18
+
},
19
19
+
20
20
+
onUrlHandler: (url, item) => {
21
21
+
const embedUrl = toPlyrFMEmbedUrl(url);
22
22
+
if (!embedUrl) return null;
23
23
+
24
24
+
item.cardData.href = embedUrl;
25
25
+
26
26
+
item.w = 4;
27
27
+
item.mobileW = 8;
28
28
+
item.h = 2;
29
29
+
item.mobileH = 4;
30
30
+
31
31
+
return item;
32
32
+
},
33
33
+
34
34
+
urlHandlerPriority: 2,
35
35
+
36
36
+
name: 'Plyr.fm Song',
37
37
+
canResize: true,
38
38
+
minW: 2,
39
39
+
minH: 2,
40
40
+
41
41
+
keywords: ['music', 'song', 'plyr', 'plyrfm', 'audio'],
42
42
+
groups: ['Media'],
43
43
+
icon: `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" class="size-4"><path stroke-linecap="round" stroke-linejoin="round" d="m9 9 10.5-3m0 6.553v3.75a2.25 2.25 0 0 1-1.632 2.163l-1.32.377a1.803 1.803 0 1 1-.99-3.467l2.31-.66a2.25 2.25 0 0 0 1.632-2.163Zm0 0V4.125A2.25 2.25 0 0 0 17.378 1.9l-7.056 2.018A2.25 2.25 0 0 0 8.69 6.08v7.353m0 0v2.929a2.25 2.25 0 0 1-1.244 2.013L6.07 19.21a1.803 1.803 0 1 1-1.758-3.14l1.88-1.003A2.25 2.25 0 0 0 7.378 13.1v-.065Z" /></svg>`
44
44
+
} as CardDefinition & { type: typeof cardType };
45
45
+
46
46
+
// Match plyr.fm track URLs (both embed and regular)
47
47
+
// https://plyr.fm/embed/track/56
48
48
+
// https://plyr.fm/track/595
49
49
+
export function toPlyrFMEmbedUrl(url: string | undefined): string | null {
50
50
+
if (!url) return null;
51
51
+
52
52
+
const match = url.match(/plyr\.fm\/(embed\/)?track\/(\d+)/);
53
53
+
if (!match) return null;
54
54
+
55
55
+
return `https://plyr.fm/embed/track/${match[2]}`;
56
56
+
}