frontend for xcvr appview

maybe this will allow for reading media + message history combined

+80 -22
+18 -9
src/lib/components/History.svelte
··· 1 1 <script lang="ts"> 2 2 import MessageTransmission from "$lib/components/MessageTransmission.svelte"; 3 - import type { SignedMessageView } from "$lib/types"; 4 - import { calculateMarginTop, signedMessageViewToMessage } from "$lib/utils"; 3 + import type { SignedItemView } from "$lib/types"; 4 + import { 5 + signedImageViewToImage, 6 + signedMessageViewToMessage, 7 + } from "$lib/utils"; 8 + import ImageTransmission from "./ImageTransmission.svelte"; 5 9 interface Props { 6 - messages: Array<SignedMessageView>; 10 + items: Array<SignedItemView>; 7 11 } 8 - let { messages }: Props = $props(); 12 + let { items }: Props = $props(); 9 13 </script> 10 14 11 15 <div id="receiver"> 12 - {#each [...messages].reverse() as message, i} 13 - <MessageTransmission 14 - message={signedMessageViewToMessage(message)} 15 - margin={0} 16 - /> 16 + {#each [...items].reverse() as item, i (`${item.signet.lrcId}-${item.type}`)} 17 + {#if item.type === "message"} 18 + <MessageTransmission 19 + message={signedMessageViewToMessage(item)} 20 + margin={0} 21 + /> 22 + {/if} 23 + {#if item.type === "image"} 24 + <ImageTransmission image={signedImageViewToImage(item)} margin={0} /> 25 + {/if} 17 26 {/each} 18 27 </div> 19 28
+18
src/lib/types.ts
··· 161 161 nick?: string 162 162 color?: number 163 163 signetURI: string 164 + postedAt: string 164 165 } 165 166 166 167 export type ItemView = MessageView | MediaView ··· 172 173 aspectRatio?: AspectRatio 173 174 } 174 175 176 + export type SignedItemView = SignedMessageView | SignedMediaView 177 + 175 178 export type SignedMessageView = { 179 + type: 'message' 176 180 $type?: string 177 181 uri: string 178 182 author: ProfileView ··· 182 186 signet: SignetView 183 187 postedAt: string 184 188 } 189 + 190 + export type SignedMediaView = SignedImageView 191 + 192 + export type SignedImageView = { 193 + type: 'image' 194 + $type?: string 195 + uri: string 196 + author: ProfileView 197 + image: ImageView 198 + nick?: string 199 + color?: number 200 + signet: SignetView 201 + postedAt: string 202 + }
+23 -1
src/lib/utils.ts
··· 1 1 import type { ChannelView } from "$lib/types.ts" 2 - import type { SignedMessageView, Message } from "$lib/types.ts" 2 + import type { SignedImageView, SignedMessageView, Message, Image } from "$lib/types.ts" 3 3 export function getChannelWS(c: ChannelView): string | null { 4 4 const host = c.host 5 5 const uri = c.uri ··· 57 57 const elapsedMs = currentTime - previousTime; 58 58 const elapsedMinutes = elapsedMs / (1000 * 60); 59 59 return Math.log(elapsedMinutes + 1); 60 + } 61 + 62 + export function signedImageViewToImage(sm: SignedImageView): Image { 63 + return { 64 + type: 'image', 65 + id: sm.signet.lrcId, 66 + lrcdata: { 67 + muted: false, 68 + mine: false 69 + }, 70 + signetView: sm.signet, 71 + mediaView: { 72 + $type: sm.$type, 73 + uri: sm.uri, 74 + author: sm.author, 75 + imageView: sm.image, 76 + ...(sm.nick && { nick: sm.nick }), 77 + ...(sm.color && { color: sm.color }), 78 + signetURI: sm.signet.uri, 79 + postedAt: sm.postedAt 80 + } 81 + } 60 82 } 61 83 62 84 export function signedMessageViewToMessage(sm: SignedMessageView): Message {
+17 -8
src/routes/c/[handle]/[rkey]/history/+page.svelte
··· 1 1 <script lang="ts"> 2 2 import type { PageProps } from "./$types"; 3 3 import History from "$lib/components/History.svelte"; 4 + import type { SignedItemView } from "$lib/types"; 5 + const nicify = (arr: Array<any>): Array<SignedItemView> => { 6 + return arr.map((element) => { 7 + if (element["$type"] === "org.xcvr.lrc.defs#signedMessageView") { 8 + return { ...element, type: "message" }; 9 + } else if (element["$type"] === "org.xcvr.lrc.defs#signedMediaView") 10 + return { ...element, type: "image" }; 11 + }); 12 + }; 4 13 let { data }: PageProps = $props(); 5 - let messages = $state(data.messages); 14 + let items = $state(nicify(data.items)); 6 15 let nextCursor = $state(data.cursor); 7 16 let hasMore = $derived(!!nextCursor); 8 17 let loading = $state(false); 9 18 const base = import.meta.env.VITE_API_URL; 10 - const endpoint = "/xrpc/org.xcvr.lrc.getMessages"; 19 + const endpoint = "/xrpc/org.xcvr.lrc.getHistory"; 11 20 let query = $derived(`?channelURI=${data.uri}&cursor=${nextCursor}`); 12 21 $effect(() => console.log(`${base}${endpoint}${query}`)); 13 - $effect(() => console.log(messages)); 22 + $effect(() => console.log(items)); 14 23 let scrollContainer: HTMLElement; 15 24 let shouldScrollToBottom = $state(true); 16 25 17 26 $effect(() => { 18 - if (shouldScrollToBottom && scrollContainer && messages.length > 0) { 27 + if (shouldScrollToBottom && scrollContainer && items.length > 0) { 19 28 scrollContainer.scrollTop = scrollContainer.scrollHeight; 20 29 shouldScrollToBottom = false; 21 30 } ··· 27 36 const oldScrollTop = scrollContainer.scrollTop; 28 37 try { 29 38 const base = import.meta.env.VITE_API_URL; 30 - const endpoint = "/xrpc/org.xcvr.lrc.getMessages"; 39 + const endpoint = "/xrpc/org.xcvr.lrc.getHistory"; 31 40 const query = `?channelURI=${data.uri}&cursor=${nextCursor}`; 32 41 const res = await fetch(`${base}${endpoint}${query}`); 33 42 const newData = await res.json(); 34 - messages = [...messages, ...newData.messages]; 43 + items = [...items, ...nicify(newData.messages)]; 35 44 nextCursor = newData.cursor; 36 45 requestAnimationFrame(() => { 37 46 const newScrollHeight = scrollContainer.scrollHeight; ··· 54 63 <span> loading... </span> 55 64 {/if} 56 65 {/if} 57 - {#if messages && messages.length !== 0} 58 - <History {messages} /> 66 + {#if items && items.length !== 0} 67 + <History {items} /> 59 68 {:else} 60 69 <h1>NO HISTORY</h1> 61 70 {/if}
+4 -4
src/routes/c/[handle]/[rkey]/history/+page.ts
··· 17 17 if (!uri) throw error(500, 'Invalid channel response') 18 18 19 19 // Get messages 20 - const messagesRes = await fetch(`${base}/xrpc/org.xcvr.lrc.getMessages?channelURI=${uri}`) 21 - if (!messagesRes.ok) throw error(messagesRes.status, 'Failed to load messages') 20 + const historyRes = await fetch(`${base}/xrpc/org.xcvr.lrc.getHistory?channelURI=${uri}`) 21 + if (!historyRes.ok) throw error(historyRes.status, 'Failed to load messages') 22 22 23 - const { messages = [], cursor } = await messagesRes.json() 23 + const { items = [], cursor } = await historyRes.json() 24 24 25 - return { messages, cursor, uri } 25 + return { items, cursor, uri } 26 26 27 27 } catch (err) { 28 28 throw error(500, 'Unexpected error')