your personal website on atproto - mirror
blento.app
1<script lang="ts">
2 import { Button, Input, Label, Modal, Subheading } from '@foxui/core';
3 import type { CreationModalComponentProps } from '../../types';
4 import { onMount } from 'svelte';
5 import { getDidContext } from '$lib/website/context';
6 import { getAuthorFeed } from '$lib/atproto/methods';
7
8 let { item = $bindable(), oncreate, oncancel }: CreationModalComponentProps = $props();
9
10 let did = getDidContext();
11
12 let mediaList: { fullsize: string; isVideo?: boolean; playlist?: string; thumbnail?: string }[] =
13 $state([]);
14
15 let isLoading = $state(true);
16
17 onMount(async () => {
18 const authorFeed = await getAuthorFeed({ did });
19
20 for (let post of authorFeed?.feed ?? []) {
21 let images =
22 post.post.embed?.$type === 'app.bsky.embed.images#view' ? post.post.embed : undefined;
23
24 for (let image of images?.images ?? []) {
25 mediaList.push(image);
26 }
27
28 if (
29 post.post.embed?.$type === 'app.bsky.embed.video#view' &&
30 post.post.embed.thumbnail &&
31 post.post.embed.playlist
32 ) {
33 mediaList.push({
34 ...post.post.embed,
35 isVideo: true,
36 fullsize: ''
37 });
38 }
39 }
40
41 isLoading = false;
42 });
43
44 let selected = $state();
45</script>
46
47<Modal
48 bind:open={
49 () => true,
50 (change) => {
51 if (!change) oncancel();
52 }
53 }
54 closeButton={false}
55 class="flex max-h-screen flex-col"
56>
57 <Subheading>Select an image or video</Subheading>
58
59 <div
60 class="bg-base-100 dark:bg-base-950 grid h-[50dvh] grid-cols-2 gap-4 overflow-y-scroll rounded-2xl p-4 lg:grid-cols-3"
61 >
62 {#each mediaList as media (media.thumbnail || media.playlist)}
63 <button
64 onclick={() => {
65 selected = media;
66 if (media.isVideo) {
67 item.cardData = {
68 video: media
69 };
70 } else item.cardData.image = media;
71 }}
72 class="relative cursor-pointer"
73 >
74 <img
75 src={media.fullsize || media.thumbnail}
76 alt=""
77 class={[
78 'h-32 w-full rounded-xl object-cover',
79 selected === media
80 ? 'outline-accent-500 opacity-100 outline-2 -outline-offset-2'
81 : 'opacity-80'
82 ]}
83 />
84 {#if media.isVideo}
85 <div class="absolute inset-0 inline-flex items-center justify-center">
86 <svg
87 xmlns="http://www.w3.org/2000/svg"
88 viewBox="0 0 24 24"
89 fill="currentColor"
90 class="text-accent-500 size-6"
91 >
92 <path
93 d="M4.5 4.5a3 3 0 0 0-3 3v9a3 3 0 0 0 3 3h8.25a3 3 0 0 0 3-3v-9a3 3 0 0 0-3-3H4.5ZM19.94 18.75l-2.69-2.69V7.94l2.69-2.69c.944-.945 2.56-.276 2.56 1.06v11.38c0 1.336-1.616 2.005-2.56 1.06Z"
94 />
95 </svg>
96 </div>
97 {/if}
98 </button>
99 {/each}
100 {#if isLoading}
101 <span class="col-span-full p-4 text-lg italic">Loading your media...</span>
102 {:else if mediaList.length === 0}
103 <span class="col-span-full p-4 text-lg italic"
104 >No media found, upload an image or video to bluesky to see it here.</span
105 >
106 {/if}
107 </div>
108
109 <Label class="mt-4">Link (optional):</Label>
110 <Input bind:value={item.cardData.href} />
111
112 <div class="mt-4 flex justify-end gap-2">
113 <Button
114 onclick={() => {
115 oncancel();
116 }}
117 variant="ghost">Cancel</Button
118 >
119 <Button
120 disabled={!selected}
121 onclick={async () => {
122 oncreate();
123 }}>Create</Button
124 >
125 </div>
126</Modal>