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 console.log(media);
66 selected = media;
67 if (media.isVideo) {
68 item.cardData = {
69 video: media
70 };
71 } else item.cardData.image = media;
72 }}
73 class="relative cursor-pointer"
74 >
75 <img
76 src={media.fullsize || media.thumbnail}
77 alt=""
78 class={[
79 'h-32 w-full rounded-xl object-cover',
80 selected === media
81 ? 'outline-accent-500 opacity-100 outline-2 -outline-offset-2'
82 : 'opacity-80'
83 ]}
84 />
85 {#if media.isVideo}
86 <div class="absolute inset-0 inline-flex items-center justify-center">
87 <svg
88 xmlns="http://www.w3.org/2000/svg"
89 viewBox="0 0 24 24"
90 fill="currentColor"
91 class="text-accent-500 size-6"
92 >
93 <path
94 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"
95 />
96 </svg>
97 </div>
98 {/if}
99 </button>
100 {/each}
101 {#if isLoading}
102 <span class="col-span-full p-4 text-lg italic">Loading your media...</span>
103 {:else if mediaList.length === 0}
104 <span class="col-span-full p-4 text-lg italic"
105 >No media found, upload an image or video to bluesky to see it here.</span
106 >
107 {/if}
108 </div>
109
110 <Label class="mt-4">Link (optional):</Label>
111 <Input bind:value={item.cardData.href} />
112
113 <div class="mt-4 flex justify-end gap-2">
114 <Button
115 onclick={() => {
116 oncancel();
117 }}
118 variant="ghost">Cancel</Button
119 >
120 <Button
121 disabled={!selected}
122 onclick={async () => {
123 oncreate();
124 }}>Create</Button
125 >
126 </div>
127</Modal>