frontend for xcvr appview
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

big refactor

+566 -462
+22
src/lib/colors.ts
··· 3 3 return rgbToHex(rgb) 4 4 } 5 5 6 + export type ColorSet = { 7 + theme: string 8 + themetransparent: string 9 + themecontrast: string 10 + themecontrasttransparent: string 11 + } 12 + 13 + export function colorSetFromTheme(theme: number): ColorSet { 14 + const color = numToHex(theme); 15 + const cpartial = hexToTransparent(color); 16 + const contrast = hexToContrast(color); 17 + const partial = hexToTransparent(contrast); 18 + 19 + return { 20 + theme: color, 21 + themetransparent: cpartial, 22 + themecontrast: contrast, 23 + themecontrasttransparent: partial, 24 + } 25 + } 26 + 27 + 6 28 export function numToHex(num: number) { 7 29 const int = Math.max(Math.min(16777215, Math.floor(num)), 0) 8 30 return "#" + int.toString(16).padStart(6, '0')
+8
src/lib/components/EnbyTransmission.svelte
··· 1 + <script lang="ts"> 2 + import type { Enby } from "$lib/types"; 3 + 4 + interface Props { 5 + enby: Enby; 6 + } 7 + let { enby }: Props = $props(); 8 + </script>
+5 -2
src/lib/components/History.svelte
··· 1 1 <script lang="ts"> 2 - import Transmission from "$lib/components/Transmission.svelte"; 2 + import MessageTransmission from "$lib/components/MessageTransmission.svelte"; 3 3 import type { SignedMessageView } from "$lib/types"; 4 4 import { calculateMarginTop, signedMessageViewToMessage } from "$lib/utils"; 5 5 interface Props { ··· 10 10 11 11 <div id="receiver"> 12 12 {#each [...messages].reverse() as message, i} 13 - <Transmission message={signedMessageViewToMessage(message)} margin={0} /> 13 + <MessageTransmission 14 + message={signedMessageViewToMessage(message)} 15 + margin={0} 16 + /> 14 17 {/each} 15 18 </div> 16 19
+84 -38
src/lib/components/ImageTransmission.svelte
··· 1 1 <script lang="ts"> 2 2 import type { Image } from "$lib/types"; 3 3 import { computePosition, flip, shift, offset } from "@floating-ui/dom"; 4 - import { hexToContrast, hexToTransparent, numToHex } from "$lib/colors"; 4 + import { colorSetFromTheme, type ColorSet } from "$lib/colors"; 5 5 import { smartAbsoluteTimestamp, dumbAbsoluteTimestamp } from "$lib/utils"; 6 6 import ProfileCard from "./ProfileCard.svelte"; 7 7 interface Props { ··· 12 12 fs?: string; 13 13 } 14 14 let { image, margin, onmute, onunmute, fs }: Props = $props(); 15 - let color: string = numToHex(image.color ?? 16777215); 16 - let cpartial: string = hexToTransparent(color); 17 - let contrast: string = hexToContrast(color); 18 - let partial: string = hexToTransparent(contrast); 15 + let protocol: string = $state(image.mediaView ? "atp" : "lrc"); 16 + let canatp: boolean = $derived(image.mediaView !== undefined); 17 + let canlrc: boolean = $derived( 18 + image.lrcdata.init !== undefined || image.lrcdata.pub !== undefined, 19 + ); 20 + let nick: string | undefined = $derived( 21 + protocol === "atp" ? image.mediaView?.nick : image.lrcdata.init?.nick, 22 + ); 23 + let handle: string | undefined = $derived( 24 + protocol === "atp" 25 + ? image.mediaView?.author.handle 26 + : image.lrcdata.init?.handle, 27 + ); 28 + let color: number | undefined = $derived( 29 + protocol === "atp" 30 + ? (image.mediaView?.color ?? image.mediaView?.author.color) 31 + : image.lrcdata.init?.color, 32 + ); 33 + let colorSet: ColorSet = $derived(colorSetFromTheme(color ?? 8421504)); 34 + let src: string | undefined = $derived( 35 + protocol === "atp" 36 + ? image.mediaView?.imageView?.src 37 + : image.lrcdata.pub?.contentAddress, 38 + ); 39 + let alt: string | undefined = $derived( 40 + protocol === "atp" 41 + ? image.mediaView?.imageView?.alt 42 + : image.lrcdata.pub?.alt, 43 + ); 19 44 let triggerEl: HTMLElement | undefined = $state(); 20 45 let profileEl: HTMLElement | undefined = $state(); 21 46 let showProfile = $state(false); ··· 38 63 let pinned = $state(false); 39 64 </script> 40 65 41 - {#if image.muted === false} 66 + {#if image.lrcdata.muted === false} 42 67 <div 43 - style:--theme={color} 44 - style:--themep={cpartial} 45 - style:--tcontrast={contrast} 46 - style:--tpartial={partial} 68 + style:--theme={colorSet.theme} 69 + style:--themep={colorSet.themetransparent} 70 + style:--tcontrast={colorSet.themecontrast} 71 + style:--tpartial={colorSet.themecontrasttransparent} 47 72 style:--margin={margin + "rem"} 48 73 style:--size={fs ?? "1rem"} 49 - class="{image.active ? 'active' : ''} 50 - {image.profileView ? 'signed' : ''} 74 + class="{image.lrcdata.pub || image.mediaView ? '' : 'active'} 75 + {image.mediaView ? 'signed' : ''} 51 76 {pinned ? 'pinned' : ''} 52 - {image.nick ? '' : 'late'} 77 + {image.lrcdata.init ? '' : 'late'} 53 78 imageTransmission" 54 79 > 55 80 <div class="header"> 56 - <span class="nick">{image.nick ?? "???"}</span 57 - >{#if image.handle}{#if !image.profileView}<span class="handle" 58 - >@{image.handle}</span 81 + <span class="nick">{nick ?? ""}</span>{#if handle !== undefined} 82 + {#if !image.mediaView?.author}<span class="handle" 83 + >@{handle}</span 59 84 >{:else}<div 60 85 role="button" 61 86 tabindex="0" ··· 66 91 <a 67 92 bind:this={triggerEl} 68 93 class="handle" 69 - href={`/p/${image.handle}`}>@{image.handle}</a 94 + href={`/p/${handle}`}>@{handle}</a 70 95 > 71 96 {#if showProfile} 72 97 <div 73 98 class="profile-container" 74 99 bind:this={profileEl} 75 100 > 76 - <ProfileCard profile={image.profileView} /> 101 + <ProfileCard profile={image.mediaView.author} /> 77 102 </div> 78 103 {/if} 79 104 </div> 80 105 {/if} 81 - <span 82 - class="time clickable" 83 - title={dumbAbsoluteTimestamp(image.startedAt)} 84 - > 85 - {smartAbsoluteTimestamp(image.startedAt)} 86 - </span> 106 + {#if protocol === "atp"} 107 + {#if canlrc} 108 + <button 109 + class="clickable" 110 + onclick={() => { 111 + protocol = "lrc"; 112 + }} 113 + > 114 + atproto, switch to lrc 115 + </button> 116 + {:else} 117 + <span class="clickable">atproto</span> 118 + {/if} 119 + {:else if canatp} 120 + <button 121 + class="clickable" 122 + onclick={() => (protocol = "atp")} 123 + > 124 + lrc, switch to atproto 125 + </button> 126 + {:else} 127 + <span class="clickable">lrc</span> 128 + {/if} 129 + {#if image.signetView?.startedAt} 130 + <span 131 + class="time clickable" 132 + title={dumbAbsoluteTimestamp( 133 + Date.parse(image.signetView.startedAt), 134 + )} 135 + > 136 + {smartAbsoluteTimestamp( 137 + Date.parse(image.signetView.startedAt), 138 + )} 139 + </span> 140 + {/if} 87 141 <button 88 142 class="clickable" 89 143 onclick={() => { ··· 92 146 > 93 147 {pinned ? "unpin" : "pin"} 94 148 </button> 95 - {#if image.mine !== true} 149 + {#if image.lrcdata.mine !== true} 96 150 <button 97 151 class="mute clickable" 98 152 onclick={() => { 99 - image.muted = true; 153 + image.lrcdata.mine = true; 100 154 onmute?.(image.id); 101 155 }} 102 156 > ··· 105 159 {/if} 106 160 {/if} 107 161 </div> 108 - {#if image.src || image.msrc} 162 + {#if src} 109 163 <div class="image-wrapper"> 110 - <img 111 - class="bg-img" 112 - src={image.msrc ? image.msrc : image.src} 113 - alt={image.alt} 114 - /> 115 - <img 116 - class="fg-img" 117 - src={image.msrc ? image.msrc : image.src} 118 - alt={image.alt} 119 - /> 164 + <img class="bg-img" {src} {alt} /> 165 + <img class="fg-img" {src} {alt} /> 120 166 </div> 121 167 {:else} 122 168 i don't have an image yet ··· 127 173 <button 128 174 class="unmute" 129 175 onclick={() => { 130 - image.muted = false; 176 + image.lrcdata.muted = false; 131 177 onunmute?.(image.id); 132 178 }} 133 179 >
+12 -20
src/lib/components/Receiever.svelte
··· 1 1 <script lang="ts"> 2 - import Transmission from "$lib/components/Transmission.svelte"; 2 + import EnbyTransmission from "$lib/components/EnbyTransmission.svelte"; 3 + import MessageTransmission from "$lib/components/MessageTransmission.svelte"; 3 4 import ImageTransmission from "$lib/components/ImageTransmission.svelte"; 4 - import type { Message, Image, Item } from "$lib/types"; 5 - import { isMessage, isImage } from "$lib/types"; 6 - import type { Action } from "svelte/action"; 5 + import type { Item } from "$lib/types"; 6 + import { isMessage, isImage, isEnby } from "$lib/types"; 7 7 interface Props { 8 8 items: Array<Item>; 9 9 mylocaltext?: string; ··· 14 14 let length = $derived(items.length); 15 15 let innerWidth = $state(0); 16 16 let isDesktop = $derived(innerWidth > 1000); 17 - const attachImage: Action<HTMLDivElement, HTMLImageElement | undefined> = ( 18 - node, 19 - img, 20 - ) => { 21 - $effect(() => { 22 - if (img != null) node.appendChild(img); 23 - node.innerText = "bebebe"; 24 - return () => { 25 - if (img != null) node.removeChild(img); 26 - }; 27 - }); 28 - }; 29 17 </script> 30 18 31 19 <svelte:window bind:innerWidth /> 32 20 <div id="receiver"> 33 - {#each items as item, index} 21 + {#each items as item, index (item.id)} 34 22 {@const last = length - 1} 35 23 {@const diff = last - index} 36 24 {@const guess = 2 + (Math.atan((diff - 19) * 0.25) / -2.8 - 0.45)} 37 25 {@const res = Math.min(Math.max(guess, 1), 2)} 38 - {#if isMessage(item)} 39 - <Transmission 26 + {#if isEnby(item)} 27 + <EnbyTransmission enby={item} /> 28 + {:else if isMessage(item)} 29 + <MessageTransmission 40 30 message={item} 41 - mylocaltext={item.active && item.mine ? mylocaltext : undefined} 31 + mylocaltext={item.lrcdata.mine && !item.lrcdata.pub 32 + ? mylocaltext 33 + : undefined} 42 34 margin={0} 43 35 {onmute} 44 36 {onunmute}
+82 -44
src/lib/components/Transmission.svelte src/lib/components/MessageTransmission.svelte
··· 2 2 import type { Message } from "$lib/types"; 3 3 import * as linkify from "linkifyjs"; 4 4 import { computePosition, flip, shift, offset } from "@floating-ui/dom"; 5 - import { hexToContrast, hexToTransparent, numToHex } from "$lib/colors"; 5 + import { colorSetFromTheme } from "$lib/colors"; 6 + import type { ColorSet } from "$lib/colors"; 6 7 import { smartAbsoluteTimestamp, dumbAbsoluteTimestamp } from "$lib/utils"; 7 8 import ProfileCard from "./ProfileCard.svelte"; 8 9 import diff from "fast-diff"; ··· 16 17 } 17 18 let { message, margin, mylocaltext, onmute, onunmute, fs }: Props = 18 19 $props(); 19 - let color: string = numToHex(message.color ?? 16777215); 20 - let cpartial: string = hexToTransparent(color); 21 - let contrast: string = hexToContrast(color); 22 - let partial: string = hexToTransparent(contrast); 20 + let protocol: string = $state(message.messageView ? "atp" : "lrc"); 21 + let canatp: boolean = $derived(message.messageView !== undefined); 22 + let canlrc: boolean = $derived( 23 + message.lrcdata.init !== undefined || message.lrcdata.pub !== undefined, 24 + ); 25 + let nick: string | undefined = $derived( 26 + protocol === "atp" 27 + ? message.messageView?.nick 28 + : message.lrcdata.init?.nick, 29 + ); 30 + let handle: string | undefined = $derived( 31 + protocol === "atp" 32 + ? message.messageView?.author.handle 33 + : message.lrcdata.init?.handle, 34 + ); 35 + let color: number | undefined = $derived( 36 + protocol === "atp" 37 + ? (message.messageView?.color ?? message.messageView?.author.color) 38 + : message.lrcdata.init?.color, 39 + ); 40 + let colorSet: ColorSet = $derived(colorSetFromTheme(color ?? 8421504)); 23 41 let triggerEl: HTMLElement | undefined = $state(); 24 42 let profileEl: HTMLElement | undefined = $state(); 25 43 let showProfile = $state(false); ··· 44 62 div.textContent = text; 45 63 return div.innerHTML; 46 64 }; 47 - let canshownotlrc = $derived( 48 - message.mbody !== undefined && message.mbody !== message.body, 49 - ); 50 - let showinglrc = $state(message.mbody !== undefined); 51 65 const convertLinksToMessageFrags = (body: string) => { 52 66 const ebody = escapeHTML(body); 53 67 const links = linkify.find(body, "url"); ··· 88 102 }; 89 103 let mfrags = $derived( 90 104 convertLinksToMessageFrags( 91 - showinglrc ? message.body : (message.mbody ?? message.body), 105 + protocol === "lrc" 106 + ? message.lrcdata.body 107 + : (message.messageView?.body ?? 108 + "i'm showing atproto data when i shouldn't be!"), 92 109 ), 93 110 ); 94 111 let diffs = $derived( 95 - message.active && message.mine && mylocaltext 96 - ? diff(message.body, mylocaltext) 112 + message.lrcdata.pub && message.lrcdata.mine && mylocaltext 113 + ? diff(message.lrcdata.body, mylocaltext) 97 114 : null, 98 115 ); 99 116 let pinned = $state(false); 100 117 </script> 101 118 102 - {#if message.muted === false} 119 + {#if message.lrcdata.muted === false} 103 120 <div 104 - style:--theme={color} 105 - style:--themep={cpartial} 106 - style:--tcontrast={contrast} 107 - style:--tpartial={partial} 121 + style:--theme={colorSet.theme} 122 + style:--themep={colorSet.themetransparent} 123 + style:--tcontrast={colorSet.themecontrast} 124 + style:--tpartial={colorSet.themecontrasttransparent} 108 125 style:--margin={margin + "rem"} 109 126 style:--size={fs ?? "1rem"} 110 - class="{message.active ? 'active' : ''} 111 - {message.profileView ? 'signed' : ''} 127 + class="{message.lrcdata.pub || message.messageView ? '' : 'active'} 128 + {message.messageView?.author ? 'signed' : ''} 112 129 {pinned ? 'pinned' : ''} 113 - {message.nick ? '' : 'late'} 130 + {message.lrcdata.init ? '' : 'late'} 114 131 transmission" 115 132 > 116 133 <div class="header"> 117 - <span class="nick">{message.nick ?? "???"}</span 118 - >{#if message.handle}{#if !message.profileView}<span class="handle" 119 - >@{message.handle}</span 134 + <span class="nick">{nick ?? ""}</span>{#if handle !== undefined} 135 + {#if !message.messageView?.author}<span class="handle" 136 + >@{handle}</span 120 137 >{:else}<div 121 138 role="button" 122 139 tabindex="0" ··· 127 144 <a 128 145 bind:this={triggerEl} 129 146 class="handle" 130 - href={`/p/${message.handle}`}>@{message.handle}</a 147 + href={`/p/${handle}`}>@{handle}</a 131 148 > 132 149 {#if showProfile} 133 150 <div 134 151 class="profile-container" 135 152 bind:this={profileEl} 136 153 > 137 - <ProfileCard profile={message.profileView} /> 154 + <ProfileCard 155 + profile={message.messageView.author} 156 + /> 138 157 </div> 139 158 {/if} 140 159 </div> 141 160 {/if} 142 - <span 143 - class="time clickable" 144 - title={dumbAbsoluteTimestamp(message.startedAt)} 145 - > 146 - {smartAbsoluteTimestamp(message.startedAt)} 147 - </span> 161 + {#if protocol === "atp"} 162 + {#if canlrc} 163 + <button 164 + class="clickable" 165 + onclick={() => { 166 + protocol = "lrc"; 167 + }} 168 + > 169 + atproto, switch to lrc 170 + </button> 171 + {:else} 172 + <span class="clickable">atproto</span> 173 + {/if} 174 + {:else if canatp} 175 + <button 176 + class="clickable" 177 + onclick={() => (protocol = "atp")} 178 + > 179 + lrc, switch to atproto 180 + </button> 181 + {:else} 182 + <span class="clickable">lrc</span> 183 + {/if} 184 + {#if message.signetView?.startedAt} 185 + <span 186 + class="time clickable" 187 + title={dumbAbsoluteTimestamp( 188 + Date.parse(message.signetView.startedAt), 189 + )} 190 + > 191 + {smartAbsoluteTimestamp( 192 + Date.parse(message.signetView.startedAt), 193 + )} 194 + </span> 195 + {/if} 148 196 <button 149 197 class="clickable" 150 198 onclick={() => { ··· 153 201 > 154 202 {pinned ? "unpin" : "pin"} 155 203 </button> 156 - {#if message.mine !== true} 204 + {#if message.lrcdata.mine !== true} 157 205 <button 158 206 class="mute clickable" 159 207 onclick={() => { 160 - message.muted = true; 208 + message.lrcdata.muted = true; 161 209 onmute?.(message.id); 162 210 }} 163 211 > 164 212 mute 165 213 </button> 166 214 {/if} 167 - {#if canshownotlrc}<span class="clickable" 168 - ><button 169 - onclick={() => { 170 - showinglrc = !showinglrc; 171 - }} 172 - >{showinglrc 173 - ? "go back to atproto" 174 - : "I WAS THERE!"}</button 175 - > (difference between atproto + lrc detected)</span 176 - >{/if} 177 215 {/if} 178 216 </div> 179 217 <div class="body"> ··· 205 243 <button 206 244 class="unmute" 207 245 onclick={() => { 208 - message.muted = false; 246 + message.lrcdata.muted = false; 209 247 onunmute?.(message.id); 210 248 }} 211 249 >
+4 -4
src/lib/components/Transmitter.svelte
··· 73 73 const el = event.target as HTMLInputElement; 74 74 switch (event.inputType) { 75 75 case "insertLineBreak": { 76 - if (ctx.myID === undefined) { 76 + if (ctx.myMessage === undefined) { 77 77 event.preventDefault(); 78 78 return; 79 79 } ··· 94 94 if (imageURL) { 95 95 URL.revokeObjectURL(imageURL); 96 96 } 97 - ctx.atpblob = undefined; 98 - ctx.pubImage("", undefined, undefined); 97 + 98 + ctx.cancelImage(); 99 99 imageAlt = ""; 100 100 imageURL = undefined; 101 101 }; ··· 165 165 fs={isDesktop ? "2rem" : "1rem"} 166 166 /> 167 167 <button onclick={cancelimagepost}> cancel </button> 168 - {#if ctx.atpblob !== undefined} 168 + {#if ctx.myMedia?.atpblob !== undefined} 169 169 <button onclick={uploadimage}> confirm </button> 170 170 {:else} 171 171 uploading...
+81 -25
src/lib/types.ts
··· 1 - import * as lrc from '@rachel-mp4/lrcproto' 2 1 export type Channel = { 3 2 title: string 4 3 host: string ··· 26 25 avatar?: string 27 26 } 28 27 29 - type BaseItem = { 30 - uri?: string 28 + export type Item = Message | Media | Enby 29 + 30 + export type Enby = { 31 + type: 'enby' 32 + id: number 33 + lrcdata: LrcBaseItem 34 + signetView?: SignetView 35 + } 36 + 37 + export type Message = { 38 + type: 'message' 31 39 id: number 32 - active: boolean 40 + lrcdata: LrcMessage 41 + messageView?: MessageView 42 + signetView?: SignetView 43 + } 44 + 45 + export type Media = Image 46 + 47 + export type Image = { 48 + type: 'image' 49 + id: number 50 + lrcdata: LrcMedia 51 + mediaView?: MediaView 52 + signetView?: SignetView 53 + atpblob?: AtpBlob 54 + } 55 + 56 + export type LrcMessage = LrcBaseItem & { 57 + body: string 58 + pub?: LrcMessagePub 59 + } 60 + 61 + export type LrcMedia = LrcBaseItem & { 62 + pub?: LrcMediaPub 63 + } 64 + 65 + export type LrcBaseItem = { 33 66 mine: boolean 34 67 muted: boolean 68 + init?: LrcInit 69 + } 70 + 71 + export type LrcInit = { 35 72 color?: number 73 + nick?: string 36 74 handle?: string 37 - profileView?: ProfileView 38 - signetView?: SignetView 39 - nick?: string 40 - startedAt: number 75 + nonce?: Uint8Array 41 76 } 42 77 78 + export type LrcMediaPub = { 79 + alt: string 80 + contentAddress?: string 81 + } 82 + 83 + export type LrcMessagePub = boolean 84 + 43 85 export type AspectRatio = { 44 86 width: number 45 87 height: number 46 88 } 47 89 48 - export type Image = BaseItem & { 49 - type: 'image' 50 - alt?: string 51 - malt?: string 52 - src?: string 53 - msrc?: string 54 - aspectRatio?: AspectRatio 55 - maspectRatio?: AspectRatio 90 + export function isEnby(item: Item): item is Enby { 91 + return item.type === "enby" 92 + } 93 + 94 + export function isMessage(item: Item): item is Message { 95 + return item.type === 'message' || item.type === 'enby' 96 + } 97 + 98 + export function isImage(item: Item): item is Image { 99 + return item.type === 'image' || item.type === 'enby' 100 + } 101 + 102 + export function isMedia(item: Item): item is Media { 103 + return isImage(item) 56 104 } 57 105 58 - export type Message = BaseItem & { 59 - type: 'message' 60 - body: string 61 - mbody?: string 106 + export type AtpBlob = { 107 + $type: string 108 + ref: { 109 + $link: string 110 + } 111 + mimeType: string 112 + size: number 62 113 } 63 - export type Item = Message | Image 64 114 65 - export function isMessage(item: Item): item is Message { 66 - return item.type === 'message' 115 + export type AtpImage = { 116 + $type: string 117 + alt: string 118 + aspectRatio?: ATPAspectRatio 119 + blob?: AtpBlob 67 120 } 68 121 69 - export function isImage(item: Item): item is Image { 70 - return item.type === 'image' 122 + export type ATPAspectRatio = { 123 + width: number 124 + height: number 71 125 } 72 126 73 127 export type LogItem = { ··· 108 162 color?: number 109 163 signetURI: string 110 164 } 165 + 166 + export type ItemView = MessageView | MediaView 111 167 112 168 export type ImageView = { 113 169 $type?: string
+15 -10
src/lib/utils.ts
··· 62 62 export function signedMessageViewToMessage(sm: SignedMessageView): Message { 63 63 return { 64 64 type: 'message', 65 - uri: sm.uri, 66 - body: sm.body, 67 65 id: sm.signet.lrcId, 68 - active: false, 69 - mine: false, 70 - muted: false, 71 - ...(sm.color && { color: sm.color }), 72 - handle: sm.author.handle, 73 - profileView: sm.author, 66 + lrcdata: { 67 + body: "", 68 + muted: false, 69 + mine: false 70 + }, 74 71 signetView: sm.signet, 75 - ...(sm.nick && { nick: sm.nick }), 76 - startedAt: Date.parse(sm.signet.startedAt), 72 + messageView: { 73 + $type: sm.$type, 74 + uri: sm.uri, 75 + author: sm.author, 76 + body: sm.body, 77 + ...(sm.nick && { nick: sm.nick }), 78 + ...(sm.color && { color: sm.color }), 79 + signetURI: sm.signet.uri, 80 + postedAt: sm.postedAt 81 + } 77 82 } 78 83 } 79 84 export function sanitizeHandle(input: string) {
+253 -319
src/lib/wscontext.svelte.ts
··· 1 - import type { AspectRatio, Item, Image, Message, LogItem, SignetView, MessageView, MediaView, ImageView } from "./types" 2 - import { isMessage, isImage } from "./types" 1 + import type * as xcvr from "./types" 2 + import { isMessage, isImage, isMedia } from "./types" 3 3 import * as lrc from '@rachel-mp4/lrcproto/gen/ts/lrc' 4 4 5 - // so the thing with the current message is that i require a signet to post 6 - // which is not ideal because you might be in an lrc server that is working 7 - // but the atproto integration isn't, and i want it to still be somewhat ok 8 - // in that case. additionally, it means that in the first like round trip + 9 - // however long it takes for atproto to propogate, you can't submit your 10 - // message either. 11 - // so i want to make that side of things better 12 - type ATPBlob = { 13 - $type: string 14 - ref: { 15 - $link: string 16 - } 17 - mimeType: string 18 - size: number 19 - } 20 - 21 - type ATPImage = { 22 - $type: string 23 - alt: string 24 - aspectRatio?: ATPAspectRatio 25 - blob?: ATPBlob 26 - } 27 - 28 - type ATPAspectRatio = { 29 - width: number 30 - height: number 31 - } 32 - 33 5 export class WSContext { 34 - items: Array<Item> = $state(new Array()) 35 - orphanedSignets: Map<string, SignetView> = new Map() 36 - orphanedMessages: Map<string, MessageView> = new Map() 37 - orphanedMedias: Map<string, MediaView> = new Map() 38 - log: Array<LogItem> = $state(new Array()) 6 + existingindices: Map<number, boolean> = new Map() 7 + existinguris: Map<string, string> = new Map() 8 + items: Array<xcvr.Item> = $state(new Array()) 9 + log: Array<xcvr.LogItem> = $state(new Array()) 39 10 topic: string = $state("") 40 11 connected: boolean = $state(false) 41 12 conncount = $state(0) ··· 44 15 color: number = $state(Math.floor(Math.random() * 16777216)) 45 16 46 17 channelUri: string 47 - active: boolean = false 48 - mediaactive: boolean = false 49 18 nick: string = "wanderer" 50 19 handle: string = "" 51 - 52 - myID: undefined | number 53 - myNonce: undefined | Uint8Array 54 20 curMsg: string = $state("") 55 - mySignet: undefined | SignetView 56 - 57 - myMediaID: undefined | number 58 - myMediaNonce: undefined | Uint8Array 59 - atpblob: ATPBlob | undefined = $state() 60 - myMediaSignet: undefined | SignetView 21 + myMessage: xcvr.Message | undefined 22 + messageactive: boolean = false 23 + myMedia: xcvr.Media | undefined 24 + mediaactive: boolean = false 61 25 62 26 audio: HTMLAudioElement = new Audio('/notif.wav') 63 27 shortaudio: HTMLAudioElement = new Audio('/shortnotif.wav') ··· 89 53 this.ls?.close() 90 54 connectTo(url, this) 91 55 this.items = [] 92 - this.orphanedMessages = new Map() 93 - this.orphanedMedias = new Map() 94 - this.orphanedSignets = new Map() 95 - this.mySignet = undefined 96 - this.myID = undefined 97 - this.myNonce = undefined 98 56 } 99 57 100 58 disconnect = () => { ··· 103 61 this.ls?.close() 104 62 this.ls = null 105 63 this.items = [] 106 - this.orphanedMessages = new Map() 107 - this.orphanedMedias = new Map() 108 - this.orphanedSignets = new Map() 109 - this.mySignet = undefined 110 - this.myID = undefined 111 - this.myNonce = undefined 112 64 } 113 65 114 66 starttransmit = () => { ··· 128 80 } 129 81 130 82 insertLineBreak = () => { 131 - if (this.active) { 83 + if (this.myMessage) { 132 84 this.starttransmit() 133 85 pubMessage(this) 134 86 const api = import.meta.env.VITE_API_URL ··· 145 97 } 146 98 console.log(body) 147 99 const record = { 148 - ...(this.mySignet && { signetURI: this.mySignet.uri }), 100 + ...(this.myMessage.signetView && { signetURI: this.myMessage.signetView.uri }), 149 101 ...(this.channelUri && { channelURI: this.channelUri }), 150 - ...(this.myID && { messageID: this.myID }), 151 - ...(this.myNonce && { nonce: b64encodebytearray(this.myNonce) }), 102 + messageID: this.myMessage.id, 103 + ...(this.myMessage.lrcdata?.init?.nonce && { nonce: b64encodebytearray(this.myMessage.lrcdata.init.nonce) }), 152 104 body: body, 153 105 ...(this.nick && { nick: this.nick }), 154 106 ...(this.color && { color: this.color }), ··· 180 132 }, 2000) 181 133 }) 182 134 } 183 - this.active = false 135 + this.myMessage = undefined 136 + this.messageactive = false 184 137 this.curMsg = "" 185 - this.mySignet = undefined 186 - this.myID = undefined 138 + } else if (this.messageactive) { 139 + this.starttransmit() 140 + pubMessage(this) 141 + this.messageactive = false 142 + this.curMsg = "" 187 143 } 188 144 } 189 145 190 146 pubImage = (alt: string, width: number | undefined, height: number | undefined) => { 191 - if (this.atpblob) { 192 - let aspectRatio: AspectRatio | undefined 147 + if (this.myMedia) { 148 + let aspectRatio: xcvr.AspectRatio | undefined 193 149 if (width && height) { 194 150 aspectRatio = { 195 151 width: width, 196 152 height: height 197 153 } 198 154 } 199 - const image: ATPImage = { 155 + const image: xcvr.AtpImage = { 200 156 $type: "org.xcvr.lrc.image", 201 157 alt: alt, 202 - blob: this.atpblob, 158 + ...(this.myMedia.atpblob && { blob: this.myMedia.atpblob }), 203 159 ...(aspectRatio && { aspectRatio: aspectRatio }) 204 160 } 205 161 const record = { 206 - ...(this.myMediaSignet && { signetURI: this.myMediaSignet.uri }), 162 + ...(this.myMedia.signetView && { signetURI: this.myMedia.signetView.uri }), 207 163 ...(this.channelUri && { channelURI: this.channelUri }), 208 - ...(this.myMediaID && { messageID: this.myMediaID }), 209 - ...(this.myNonce && { nonce: b64encodebytearray(this.myNonce) }), 164 + messageID: this.myMedia.id, 165 + ...(this.myMedia.lrcdata?.init?.nonce && { nonce: b64encodebytearray(this.myMedia.lrcdata.init.nonce) }), 210 166 image: image, 211 167 ...(this.nick && { nick: this.nick }), 212 168 ...(this.color && { color: this.color }), ··· 238 194 }).then((val) => console.log(val), (val) => console.log(val)) 239 195 }, 2000) 240 196 }) 241 - const contentAddress = `${api}/xrpc/org.xcvr.lrc.getImage?handle=${this.handle}&cid=${this.atpblob.ref["$link"]}` 242 - if (this.mediaactive) { 197 + if (this.myMedia.atpblob) { 198 + const contentAddress = `${api}/xrpc/org.xcvr.lrc.getImage?handle=${this.handle}&cid=${this.myMedia.atpblob.ref["$link"]}` 243 199 pubImage(alt, contentAddress, this) 200 + } else { 201 + pubImage(alt, undefined, this) 244 202 } 203 + this.myMedia = undefined 245 204 this.mediaactive = false 246 - this.atpblob = undefined 247 - this.myMediaSignet = undefined 248 - this.myMediaID = undefined 249 - } else { 205 + } else if (this.mediaactive) { 250 206 pubImage(alt, undefined, this) 207 + this.mediaactive = false 251 208 } 252 209 } 210 + cancelImage = () => { 211 + if (this.mediaactive) { 212 + pubImage(undefined, undefined, this) 213 + this.myMedia = undefined 214 + this.mediaactive = false 215 + } 216 + 217 + } 253 218 254 219 initImage = (blob: File) => { 255 - if (!this.mediaactive) { 220 + if (!this.myMedia) { 256 221 initImage(this) 257 - this.mediaactive = true 258 222 const api = import.meta.env.VITE_API_URL 259 223 const endpoint = `${api}/lrc/image` 260 224 const formData = new FormData() ··· 265 229 }).then((response) => { 266 230 if (response.ok) { 267 231 response.json().then((atpblob) => { 268 - this.atpblob = atpblob 232 + if (this.myMedia) { 233 + this.myMedia.atpblob = atpblob 234 + } 269 235 } 270 236 ) 271 237 } else { ··· 277 243 278 244 279 245 insert = (idx: number, s: string) => { 280 - if (!this.active) { 246 + if (!this.messageactive) { 281 247 initMessage(this) 282 - this.active = true 248 + this.messageactive = true 283 249 } 284 250 insertMessage(idx, s, this) 285 251 this.curMsg = insertSIntoAStringAtIdx(s, this.curMsg, idx) 286 252 } 287 253 288 254 delete = (idx: number, idx2: number) => { 289 - if (!this.active) { 255 + if (!this.messageactive) { 290 256 return 291 257 } 292 258 deleteMessage(idx, idx2, this) ··· 319 285 this.conncount = cc 320 286 } 321 287 322 - // theoretically this could occur _after we have an orphaned signet or an orphanedmessage or both! so, 323 - // TODO: make it work in that case 324 - pushItem = (item: Item) => { 288 + pushItem = (item: xcvr.Item) => { 289 + if (this.existingindices.get(item.id)) { 290 + console.log("you tried to push an item who exists!") 291 + return 292 + } 325 293 if (document.hidden || !document.hasFocus()) { 326 294 this.audio.currentTime = 0 327 295 this.audio.play() 328 - } else if (!item.mine) { 296 + } else if (!item.lrcdata.mine) { 329 297 this.shortaudio.currentTime = 0 330 298 this.shortaudio.play() 331 299 } 332 - if (this.items.length > 200) { 333 - this.items = [...this.items.slice(this.items.length - 199), item] 334 - } else { 335 - this.items.push(item) 300 + this.items.push(item) 301 + if (item.lrcdata.mine) { 302 + if (isMessage(item)) { 303 + this.myMessage = item 304 + } else if (isMedia(item)) { 305 + this.myMedia = item 306 + } 336 307 } 308 + this.existingindices.set(item.id, true) 337 309 } 338 310 339 - replaceItem = (id: number, newItem: Item) => { 340 - this.items = this.items.map((item: Item) => { 341 - return item.id === id ? newItem : item 342 - }) 311 + initMessage = (id: number, init: xcvr.LrcInit, mine: boolean) => { 312 + if (this.existingindices.get(id)) { 313 + this.items = this.items.map((item: xcvr.Item) => { 314 + return item.id === id && isMessage(item) 315 + ? { ...item, type: "message", lrcdata: { ...item.lrcdata, init: init } } 316 + : item 317 + }) 318 + } else { 319 + this.pushItem({ 320 + type: 'message', 321 + id: id, 322 + lrcdata: { 323 + body: '', 324 + mine: mine, 325 + muted: false, 326 + init: init, 327 + }, 328 + }) 329 + } 343 330 } 344 331 345 - pubItem = (id: number) => { 346 - this.items = this.items.map((item: Item) => { 347 - return isMessage(item) && item.id === id ? { ...item, active: false } : item 348 - }) 332 + initMedia = (id: number, init: xcvr.LrcInit, mine: boolean) => { 333 + if (this.existingindices.get(id)) { 334 + this.items = this.items.map((item: xcvr.Item) => { 335 + return item.id === id && isImage(item) 336 + ? { ...item, type: "image", lrcdata: { ...item.lrcdata, init: init } } 337 + : item 338 + }) 339 + } else { 340 + this.pushItem({ 341 + type: 'image', 342 + id: id, 343 + lrcdata: { 344 + mine: mine, 345 + muted: false, 346 + init: init, 347 + }, 348 + }) 349 + } 349 350 } 350 351 351 - mediapubItem = (id: number, alt: string | undefined, contentAddress: string | undefined) => { 352 - this.items = this.items.map((item: Item) => { 353 - return isImage(item) && item.id === id ? { ...item, active: false, alt: alt, src: contentAddress } : item 354 - }) 352 + initMute = (id: number) => { 353 + if (this.existingindices.get(id)) { 354 + this.items = this.items.map((item: xcvr.Item) => { 355 + return item.id === id 356 + ? { ...item, lrcdata: { ...item.lrcdata, muted: true } } as typeof item 357 + : item 358 + }) 359 + } else { 360 + this.pushItem({ 361 + type: 'enby', 362 + id: id, 363 + lrcdata: { 364 + mine: false, 365 + muted: true, 366 + } 367 + }) 368 + } 355 369 } 356 370 357 - insertMessage = (id: number, idx: number, s: string) => { 358 - this.ensureExistenceOfMessage(id) 359 - this.items = this.items.map((item: Item) => { 360 - return isMessage(item) && item.id === id ? { ...item, body: insertSIntoAStringAtIdx(s, item.body, idx) } : item 361 - }) 371 + pubMessage = (id: number) => { 372 + if (this.existingindices.get(id)) { 373 + this.items = this.items.map((item: xcvr.Item) => { 374 + return item.id === id && isMessage(item) 375 + ? { ...item, type: "message", lrcdata: { ...item.lrcdata, pub: true } } 376 + : item 377 + }) 378 + } else { 379 + this.pushItem({ 380 + type: "message", 381 + id: id, 382 + lrcdata: { 383 + mine: false, 384 + muted: false, 385 + body: "", 386 + }, 387 + }) 388 + } 362 389 } 363 390 364 - deleteMessage = (id: number, idx1: number, idx2: number) => { 365 - this.ensureExistenceOfMessage(id) 366 - this.items = this.items.map((item: Item) => { 367 - return isMessage(item) && item.id === id ? { ...item, body: deleteFromAStringBetweenIdxs(item.body, idx1, idx2) } : item 368 - }) 391 + pubMedia = (id: number, pub: xcvr.LrcMediaPub) => { 392 + if (this.existingindices.get(id)) { 393 + this.items = this.items.map((item: xcvr.Item) => { 394 + return item.id === id && isMedia(item) 395 + ? { 396 + ...item, type: "image", 397 + lrcdata: { 398 + ...item.lrcdata, 399 + pub: pub 400 + } 401 + } 402 + : item 403 + }) 404 + } else { 405 + this.pushItem({ 406 + type: "image", 407 + id: id, 408 + lrcdata: { 409 + mine: false, 410 + muted: false, 411 + pub: pub, 412 + }, 413 + }) 414 + } 369 415 } 370 416 371 - ensureExistenceOfMessage = (id: number) => { 372 - const idx = this.items.findIndex((item) => { return item.id === id }) 373 - if (idx === -1) { 417 + insertMessage = (id: number, idx: number, s: string) => { 418 + if (this.existingindices.get(id)) { 419 + this.items = this.items.map((item: xcvr.Item) => { 420 + return item.id === id && isMessage(item) 421 + ? { ...item, type: "message", lrcdata: { ...item.lrcdata, body: insertSIntoAStringAtIdx(s, item.lrcdata.body, idx) } } 422 + : item 423 + }) 424 + } else { 374 425 this.pushItem({ 375 - type: 'message', 376 - body: "", 426 + type: "message", 377 427 id: id, 378 - active: true, 379 - mine: false, 380 - muted: false, 381 - startedAt: Date.now(), 428 + lrcdata: { 429 + mine: false, 430 + muted: false, 431 + body: insertSIntoAStringAtIdx(s, "", idx), 432 + pub: false 433 + }, 382 434 }) 383 435 } 384 436 } 385 437 386 - addSignet = (signet: SignetView) => { 387 - if (signet.lrcId === this.myID) { 388 - this.mySignet = signet 389 - } 390 - if (signet.lrcId === this.myMediaID) { 391 - this.myMediaSignet = signet 392 - } 393 - console.log("now we are signing") 394 - const arrayIdx = this.items.findIndex(item => item.id === signet.lrcId) 395 - if (arrayIdx !== -1) { 396 - console.log("found appropriate signet c:") 397 - this.items = this.items.map((item: Item) => { 398 - return item.id === signet.lrcId ? { ...item, signetView: signet } : item 438 + deleteMessage = (id: number, idx1: number, idx2: number) => { 439 + if (this.existingindices.get(id)) { 440 + this.items = this.items.map((item: xcvr.Item) => { 441 + return item.id === id && isMessage(item) 442 + ? { ...item, type: "message", lrcdata: { ...item.lrcdata, body: deleteFromAStringBetweenIdxs(item.lrcdata.body, idx1, idx2) } } 443 + : item 399 444 }) 400 445 } else { 401 - console.log("couldn't find appropriate signet :c") 402 - const om = this.orphanedMessages.get(signet.uri) 403 - if (om !== undefined) { 404 - console.log("some orphan logic") 405 - const message = makeMessageFromSignetAndMessageViews(om, signet) 406 - const idx = this.items.findIndex(item => item.id > signet.lrcId) 407 - if (idx === -1) { 408 - this.items.push(message) 409 - } else { 410 - this.items = [...this.items.slice(0, idx), message, ...this.items.slice(idx)] 411 - } 412 - this.orphanedMessages.delete(signet.uri) 413 - return 414 - } 415 - const oi = this.orphanedMedias.get(signet.uri) 416 - if (oi !== undefined) { 417 - console.log("comse orphan logic 2") 418 - const image = makeImageFromSignetAndImageMediaViews(oi, signet) 419 - const idx = this.items.findIndex(item => item.id > signet.lrcId) 420 - if (idx === -1) { 421 - this.items.push(image) 422 - } else { 423 - this.items = [...this.items.slice(0, idx), image, ...this.items.slice(idx)] 424 - } 425 - this.orphanedMedias.delete(signet.uri) 426 - return 427 - } 428 - this.orphanedSignets.set(signet.uri, signet) 446 + this.pushItem({ 447 + type: "message", 448 + id: id, 449 + lrcdata: { 450 + mine: false, 451 + muted: false, 452 + body: deleteFromAStringBetweenIdxs("", idx1, idx2), 453 + pub: false 454 + }, 455 + }) 429 456 } 430 457 } 431 458 432 - verifyMessage = (message: MessageView) => { 433 - console.log("now we are verifying!") 434 - console.log(message.signetURI) 435 - const arrayIdx = this.items.findIndex(item => item.signetView?.uri === message.signetURI && item.signetView?.authorHandle === message.author.handle) 436 - if (arrayIdx !== -1) { 437 - console.log("found appropriate message c:") 438 - this.items = this.items.map((item: Item) => { 439 - return item.signetView?.uri === message.signetURI && isMessage(item) ? 440 - { ...makeMessageFromSignetAndMessageViews(message, item.signetView), body: item.body, mine: item.mine } : item 459 + addSignet = (signet: xcvr.SignetView) => { 460 + if (this.existingindices.get(signet.lrcId)) { 461 + this.items = this.items.map((item: xcvr.Item) => { 462 + return item.id === signet.lrcId 463 + ? { ...item, signetView: signet } 464 + : item 465 + }) 466 + } else { 467 + this.pushItem({ 468 + type: "enby", 469 + id: signet.lrcId, 470 + lrcdata: { mine: false, muted: false }, 471 + signetView: signet 441 472 }) 442 473 } 443 - else { 444 - console.log("couldn't find appropriate message :c") 445 - const os = this.orphanedSignets.get(message.signetURI) 446 - if (os !== undefined) { 447 - console.log("some orphan logic") 448 - const m = makeMessageFromSignetAndMessageViews(message, os) 449 - const idx = this.items.findIndex(item => item.id > os.lrcId) 450 - if (idx === -1) { 451 - this.items.push(m) 452 - } else { 453 - this.items = [...this.items.slice(0, idx), m, ...this.items.slice(idx)] 454 - } 455 - this.orphanedSignets.delete(os.uri) 456 - } else { 457 - this.orphanedMessages.set(message.signetURI, message) 458 - } 459 - } 474 + this.existinguris.set(signet.uri, signet.authorHandle) 460 475 } 461 476 462 - verifyImageMediaView = (media: MediaView) => { 463 - console.log("now we are verifying!") 464 - console.log(media.signetURI) 465 - const arrayIdx = this.items.findIndex(item => item.signetView?.uri === media.signetURI && item.signetView?.authorHandle === media.author.handle) 466 - if (arrayIdx !== -1) { 467 - console.log("found appropriate message c:") 468 - this.items = this.items.map((item: Item) => { 469 - return item.signetView?.uri === media.signetURI && isMessage(item) ? 470 - { ...makeImageFromSignetAndImageMediaViews(media, item.signetView), body: item.body, mine: item.mine } : item 477 + addMessageView = (message: xcvr.MessageView) => { 478 + if (this.existinguris.get(message.signetURI) === message.author.handle) { 479 + this.items = this.items.map((item: xcvr.Item) => { 480 + return item.signetView?.uri === message.signetURI && isMessage(item) 481 + ? { ...item, type: "message", messageView: message } 482 + : item 471 483 }) 484 + this.existinguris.delete(message.signetURI) 485 + } else { 486 + console.error("recieved a messageview who doesn't have a matching signet, rejecting: ", message) 472 487 } 473 - else { 474 - console.log("couldn't find appropriate message :c") 475 - const os = this.orphanedSignets.get(media.signetURI) 476 - if (os !== undefined) { 477 - console.log("some orphan logic") 478 - const m = makeImageFromSignetAndImageMediaViews(media, os) 479 - const idx = this.items.findIndex(item => item.id > os.lrcId) 480 - if (idx === -1) { 481 - this.items.push(m) 482 - } else { 483 - this.items = [...this.items.slice(0, idx), m, ...this.items.slice(idx)] 484 - } 485 - this.orphanedSignets.delete(os.uri) 486 - } else { 487 - this.orphanedMedias.set(media.signetURI, media) 488 - } 488 + } 489 + 490 + addImageView = (media: xcvr.MediaView) => { 491 + if (!media.imageView) { 492 + console.log("called add imageview when i don't have an imageview") 493 + return 494 + } 495 + if (this.existinguris.get(media.signetURI)) { 496 + this.items = this.items.map((item: xcvr.Item) => { 497 + return item.signetView?.uri === media.signetURI && isImage(item) ? 498 + { ...item, type: "image", mediaView: media } : item 499 + }) 500 + this.existinguris.delete(media.signetURI) 501 + } else { 502 + console.error("recieved a mediaview who doesn't have a matching signet, rejecting: ", media) 489 503 } 490 504 } 491 505 ··· 499 513 500 514 const b64encodebytearray = (u8: Uint8Array): string => { 501 515 return btoa(String.fromCharCode(...u8)) 502 - } 503 - 504 - const makeMessageFromSignetAndMessageViews = (m: MessageView, s: SignetView): Message => { 505 - return { 506 - type: 'message', 507 - uri: m.uri, 508 - body: "i didn't catch the lrc message body :c", 509 - mbody: m.body, 510 - id: s.lrcId, 511 - active: false, 512 - mine: false, 513 - muted: false, 514 - ...(m.color && { color: m.color }), 515 - handle: m.author.handle, 516 - profileView: m.author, 517 - signetView: s, 518 - ...(m.nick && { nick: m.nick }), 519 - startedAt: Date.parse(s.startedAt) 520 - } 521 - } 522 - 523 - const makeImageFromSignetAndImageMediaViews = (i: MediaView, s: SignetView): Image => { 524 - return { 525 - type: 'image', 526 - uri: i.uri, 527 - id: s.lrcId, 528 - active: false, 529 - mine: false, 530 - muted: false, 531 - malt: i.imageView?.alt, 532 - ...(i.imageView?.src && { src: i.imageView.src }), 533 - ...(i.imageView?.aspectRatio && { maspectRatio: i.imageView.aspectRatio }), 534 - ...(i.nick && { nick: i.nick }), 535 - ...(i.color && { color: i.color }), 536 - handle: i.author.handle, 537 - profileView: i.author, 538 - signetView: s, 539 - startedAt: Date.parse(s.startedAt), 540 - } 541 516 } 542 517 543 518 const insertSIntoAStringAtIdx = (s: string, a: string, idx: number) => { ··· 644 619 const color = lex.color 645 620 const signetURI = lex.signetURI 646 621 const postedAt = lex.postedAt 647 - ctx.verifyMessage({ 622 + ctx.addMessageView({ 648 623 uri: uri, 649 624 author: author, 650 625 body: body, ··· 666 641 ...(lex.author.color && { color: lex.author.color }), 667 642 ...(lex.author.avatar && { avatar: lex.author.avatar }), 668 643 } 669 - var imageView: ImageView | undefined 644 + var imageView: xcvr.ImageView | undefined 670 645 if (lex.imageView) { 671 646 console.log("has an image!") 672 647 imageView = { ··· 679 654 const color = lex.color 680 655 const signetURI = lex.signetURI 681 656 const postedAt = lex.postedAt 682 - ctx.verifyImageMediaView({ 657 + ctx.addImageView({ 683 658 uri: uri, 684 659 author: author, 685 660 ...(imageView && { imageView: imageView }), ··· 898 873 } 899 874 900 875 case "init": { 901 - console.log(event.msg.init) 902 876 const id = event.msg.init.id ?? 0 903 877 if (id === 0) return false 904 - const echoed = event.msg.init.echoed ?? false 905 - if (echoed) { 906 - ctx.myID = id 907 - ctx.myNonce = event.msg.init.nonce 908 - // return false 909 - } 878 + const color = event.msg.init.color 910 879 const nick = event.msg.init.nick 911 880 const handle = event.msg.init.externalID 912 - const color = event.msg.init.color 913 - const body = "" 914 - const active = true 915 - const mine = echoed 916 - const muted = false 917 - const startedAt = Date.now() 918 - const msg: Message = { 919 - type: 'message', 920 - body: body, 921 - id: id, 922 - active: active, 923 - mine: mine, 924 - muted: muted, 881 + const nonce = event.msg.init.nonce 882 + const mine = event.msg.init.echoed ?? false 883 + const init: xcvr.LrcInit = { 925 884 ...(color && { color: color }), 885 + ...(nick && { nick: nick }), 926 886 ...(handle && { handle: handle }), 927 - ...(nick && { nick: nick }), 928 - startedAt: startedAt 887 + ...(nonce && { nonce: nonce }), 929 888 } 930 - ctx.pushItem(msg) 889 + ctx.initMessage(id, init, mine) 931 890 ctx.pushToLog(id, byteArray, "init") 932 891 return true 933 892 } 934 893 935 894 case "mediainit": { 936 - console.log("media init!!!!") 937 895 const id = event.msg.mediainit.id ?? 0 938 896 if (id === 0) return false 939 - const echoed = event.msg.mediainit.echoed ?? false 940 - if (echoed) { 941 - ctx.myMediaID = id 942 - ctx.myMediaNonce = event.msg.mediainit.nonce 943 - // return false 944 - } 897 + const color = event.msg.mediainit.color 945 898 const nick = event.msg.mediainit.nick 946 899 const handle = event.msg.mediainit.externalID 947 - const color = event.msg.mediainit.color 948 - const active = true 949 - const mine = echoed 950 - const muted = false 951 - const startedAt = Date.now() 952 - const msg: Image = { 953 - type: 'image', 954 - id: id, 955 - active: active, 956 - mine: mine, 957 - muted: muted, 900 + const nonce = event.msg.mediainit.nonce 901 + const mine = event.msg.mediainit.echoed ?? false 902 + const init: xcvr.LrcInit = { 958 903 ...(color && { color: color }), 904 + ...(nick && { nick: nick }), 959 905 ...(handle && { handle: handle }), 960 - ...(nick && { nick: nick }), 961 - startedAt: startedAt 906 + ...(nonce && { nonce: nonce }), 962 907 } 963 - ctx.pushItem(msg) 908 + ctx.initMedia(id, init, mine) 964 909 ctx.pushToLog(id, byteArray, "init") 965 910 return true 966 911 } ··· 968 913 case "pub": { 969 914 const id = event.msg.pub.id ?? 0 970 915 if (id === 0) return false 971 - ctx.pubItem(id) 916 + ctx.pubMessage(id) 972 917 ctx.pushToLog(id, byteArray, "pub") 973 918 return false 974 919 } ··· 976 921 case "mediapub": { 977 922 const id = event.msg.mediapub.id ?? 0 978 923 if (id === 0) return false 979 - ctx.mediapubItem(id, event.msg.mediapub.alt, event.msg.mediapub.contentAddress) 924 + const pub: xcvr.LrcMediaPub = { 925 + alt: event.msg.mediapub.alt ?? "", 926 + contentAddress: event.msg.mediapub.contentAddress 927 + } 928 + ctx.pubMedia(id, pub) 980 929 ctx.pushToLog(id, byteArray, "pub") 981 930 return false 982 931 } ··· 1001 950 case "mute": { 1002 951 const id = event.msg.mute.id ?? 0 1003 952 if (id === 0) return false 1004 - const muted = true 1005 - const active = true 1006 - const mine = false 1007 - const body = "" 1008 - const startedAt = Date.now() 1009 - const msg: Message = { 1010 - type: "message", 1011 - id: id, 1012 - body: body, 1013 - muted: muted, 1014 - active: active, 1015 - mine: mine, 1016 - startedAt: startedAt 1017 - } 1018 - 1019 - ctx.pushItem(msg) 953 + ctx.initMute(id) 1020 954 return false 1021 955 } 1022 956