your personal website on atproto - mirror blento.app

add plyrfm track embed

Florian 6d5e9596 d426f2df

+131 -1
+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 + 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 - LastFMProfileCardDefinition 100 + LastFMProfileCardDefinition, 101 + PlyrFMCardDefinition 100 102 ] as const; 101 103 102 104 export const CardDefinitionsByType = AllCardDefinitions.reduce(
+50
src/lib/cards/media/PlyrFMCard/CreatePlyrFMCardModal.svelte
··· 1 + <script lang="ts"> 2 + import { Alert, Button, Input, Modal, Subheading } from '@foxui/core'; 3 + import type { CreationModalComponentProps } from '../../types'; 4 + import { toPlyrFMEmbedUrl } from './index'; 5 + 6 + let { item = $bindable(), oncreate, oncancel }: CreationModalComponentProps = $props(); 7 + 8 + let errorMessage = $state(''); 9 + 10 + function checkUrl() { 11 + errorMessage = ''; 12 + 13 + const embedUrl = toPlyrFMEmbedUrl(item.cardData.href); 14 + 15 + if (!embedUrl) { 16 + errorMessage = 'Please enter a valid plyr.fm track URL'; 17 + return false; 18 + } 19 + 20 + item.cardData.href = embedUrl; 21 + 22 + return true; 23 + } 24 + </script> 25 + 26 + <Modal open={true} closeButton={false}> 27 + <Subheading>Enter a Plyr.fm track URL</Subheading> 28 + <Input 29 + bind:value={item.cardData.href} 30 + placeholder="https://plyr.fm/track/..." 31 + onkeydown={(e) => { 32 + if (e.key === 'Enter' && checkUrl()) oncreate(); 33 + }} 34 + /> 35 + 36 + {#if errorMessage} 37 + <Alert type="error" title="Invalid URL"><span>{errorMessage}</span></Alert> 38 + {/if} 39 + 40 + <div class="mt-4 flex justify-end gap-2"> 41 + <Button onclick={oncancel} variant="ghost">Cancel</Button> 42 + <Button 43 + onclick={() => { 44 + if (checkUrl()) oncreate(); 45 + }} 46 + > 47 + Create 48 + </Button> 49 + </div> 50 + </Modal>
+22
src/lib/cards/media/PlyrFMCard/PlyrFMCard.svelte
··· 1 + <script lang="ts"> 2 + import type { ContentComponentProps } from '../../types'; 3 + 4 + let { item, isEditing }: ContentComponentProps = $props(); 5 + </script> 6 + 7 + {#if item.cardData?.href} 8 + <div class="absolute inset-0 p-2"> 9 + <iframe 10 + class={['h-full w-full rounded-2xl', isEditing && 'pointer-events-none']} 11 + src={item.cardData.href} 12 + frameborder="0" 13 + allow="autoplay; encrypted-media" 14 + loading="lazy" 15 + title="Plyr.fm track" 16 + ></iframe> 17 + </div> 18 + {:else} 19 + <div class="flex h-full items-center justify-center p-4 text-center opacity-50"> 20 + Missing Plyr.fm URL 21 + </div> 22 + {/if}
+56
src/lib/cards/media/PlyrFMCard/index.ts
··· 1 + import type { CardDefinition } from '../../types'; 2 + import CreatePlyrFMCardModal from './CreatePlyrFMCardModal.svelte'; 3 + import PlyrFMCard from './PlyrFMCard.svelte'; 4 + 5 + const cardType = 'plyr-fm'; 6 + 7 + export const PlyrFMCardDefinition = { 8 + type: cardType, 9 + contentComponent: PlyrFMCard, 10 + creationModalComponent: CreatePlyrFMCardModal, 11 + createNew: (item) => { 12 + item.cardType = cardType; 13 + item.cardData = {}; 14 + item.w = 4; 15 + item.mobileW = 8; 16 + item.h = 2; 17 + item.mobileH = 4; 18 + }, 19 + 20 + onUrlHandler: (url, item) => { 21 + const embedUrl = toPlyrFMEmbedUrl(url); 22 + if (!embedUrl) return null; 23 + 24 + item.cardData.href = embedUrl; 25 + 26 + item.w = 4; 27 + item.mobileW = 8; 28 + item.h = 2; 29 + item.mobileH = 4; 30 + 31 + return item; 32 + }, 33 + 34 + urlHandlerPriority: 2, 35 + 36 + name: 'Plyr.fm Song', 37 + canResize: true, 38 + minW: 2, 39 + minH: 2, 40 + 41 + keywords: ['music', 'song', 'plyr', 'plyrfm', 'audio'], 42 + groups: ['Media'], 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 + } as CardDefinition & { type: typeof cardType }; 45 + 46 + // Match plyr.fm track URLs (both embed and regular) 47 + // https://plyr.fm/embed/track/56 48 + // https://plyr.fm/track/595 49 + export function toPlyrFMEmbedUrl(url: string | undefined): string | null { 50 + if (!url) return null; 51 + 52 + const match = url.match(/plyr\.fm\/(embed\/)?track\/(\d+)/); 53 + if (!match) return null; 54 + 55 + return `https://plyr.fm/embed/track/${match[2]}`; 56 + }