tangled
alpha
login
or
join now
moth11.net
/
xcvr
2
fork
atom
frontend for xcvr appview
2
fork
atom
overview
issues
pulls
pipelines
initial image stuff
moth11.net
6 months ago
35b979e6
706a0713
+159
-61
6 changed files
expand all
collapse all
unified
split
src
lib
components
AutoGrowTextArea.svelte
Receiever.svelte
types.ts
utils.ts
wscontext.svelte.ts
routes
c
[handle]
[rkey]
+page.svelte
+18
src/lib/components/AutoGrowTextArea.svelte
···
7
interface Props {
8
onBeforeInput?: (event: InputEvent) => void;
9
onInputEl?: (el: HTMLTextAreaElement) => void;
0
10
placeholder?: string;
11
value?: string;
12
maxlength?: number;
···
20
placeholder,
21
value = $bindable(""),
22
onInputEl,
0
23
maxlength,
24
bold = false,
25
color,
···
173
});
174
}
175
});
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
176
</script>
177
178
<div class="autogrowwrapper">
···
183
{maxlength}
184
oninput={adjust}
185
onkeydown={emojifier}
0
186
onbeforeinput={bi}
187
style:font-weight={bold ? "700" : "inherit"}
188
style:--theme={color}
···
7
interface Props {
8
onBeforeInput?: (event: InputEvent) => void;
9
onInputEl?: (el: HTMLTextAreaElement) => void;
10
+
imageHandler?: (image: File) => void;
11
placeholder?: string;
12
value?: string;
13
maxlength?: number;
···
21
placeholder,
22
value = $bindable(""),
23
onInputEl,
24
+
imageHandler,
25
maxlength,
26
bold = false,
27
color,
···
175
});
176
}
177
});
178
+
const pastifier = (event: ClipboardEvent) => {
179
+
const items = event.clipboardData?.items;
180
+
if (items === undefined) {
181
+
return;
182
+
}
183
+
for (const item of items) {
184
+
if (item.type.startsWith("image/")) {
185
+
const blob = item.getAsFile();
186
+
if (blob === null) {
187
+
return;
188
+
}
189
+
imageHandler?.(blob);
190
+
}
191
+
}
192
+
};
193
</script>
194
195
<div class="autogrowwrapper">
···
200
{maxlength}
201
oninput={adjust}
202
onkeydown={emojifier}
203
+
onpaste={pastifier}
204
onbeforeinput={bi}
205
style:font-weight={bold ? "700" : "inherit"}
206
style:--theme={color}
+16
-13
src/lib/components/Receiever.svelte
···
1
<script lang="ts">
2
import Transmission from "$lib/components/Transmission.svelte";
3
-
import type { Message } from "$lib/types";
0
4
interface Props {
5
-
messages: Array<Message>;
6
mylocaltext?: string;
7
onmute?: (id: number) => void;
8
onunmute?: (id: number) => void;
9
}
10
-
let { messages, mylocaltext, onmute, onunmute }: Props = $props();
11
-
let length = $derived(messages.length);
12
let innerWidth = $state(0);
13
let isDesktop = $derived(innerWidth > 1000);
14
</script>
15
16
<svelte:window bind:innerWidth />
17
<div id="receiver">
18
-
{#each messages as message, index}
19
{@const last = length - 1}
20
{@const diff = last - index}
21
{@const guess = 2 + (Math.atan((diff - 19) * 0.25) / -2.8 - 0.45)}
22
{@const res = Math.min(Math.max(guess, 1), 2)}
23
-
<Transmission
24
-
{message}
25
-
mylocaltext={message.active && message.mine ? mylocaltext : undefined}
26
-
margin={0}
27
-
{onmute}
28
-
{onunmute}
29
-
fs={isDesktop ? `${res}rem` : "1rem"}
30
-
/>
0
0
31
{/each}
32
</div>
33
···
1
<script lang="ts">
2
import Transmission from "$lib/components/Transmission.svelte";
3
+
import type { Message, Image, Item } from "$lib/types";
4
+
import { isMessage, isImage } from "$lib/types";
5
interface Props {
6
+
items: Array<Item>;
7
mylocaltext?: string;
8
onmute?: (id: number) => void;
9
onunmute?: (id: number) => void;
10
}
11
+
let { items, mylocaltext, onmute, onunmute }: Props = $props();
12
+
let length = $derived(items.length);
13
let innerWidth = $state(0);
14
let isDesktop = $derived(innerWidth > 1000);
15
</script>
16
17
<svelte:window bind:innerWidth />
18
<div id="receiver">
19
+
{#each items as item, index}
20
{@const last = length - 1}
21
{@const diff = last - index}
22
{@const guess = 2 + (Math.atan((diff - 19) * 0.25) / -2.8 - 0.45)}
23
{@const res = Math.min(Math.max(guess, 1), 2)}
24
+
{#if isMessage(item)}
25
+
<Transmission
26
+
message={item}
27
+
mylocaltext={item.active && item.mine ? mylocaltext : undefined}
28
+
margin={0}
29
+
{onmute}
30
+
{onunmute}
31
+
fs={isDesktop ? `${res}rem` : "1rem"}
32
+
/>
33
+
{/if}
34
{/each}
35
</div>
36
+30
-3
src/lib/types.ts
···
26
avatar?: string
27
}
28
29
-
export type Message = {
30
uri?: string
31
-
body: string
32
-
mbody?: string
33
id: number
34
active: boolean
35
mine: boolean
···
41
nick?: string
42
startedAt: number
43
}
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
44
45
export type LogItem = {
46
id: number
···
69
color?: number
70
signetURI: string
71
postedAt: string
0
0
0
0
0
0
0
0
0
0
72
}
73
74
export type SignedMessageView = {
···
26
avatar?: string
27
}
28
29
+
type BaseItem = {
30
uri?: string
0
0
31
id: number
32
active: boolean
33
mine: boolean
···
39
nick?: string
40
startedAt: number
41
}
42
+
export type Image = BaseItem & {
43
+
type: 'image'
44
+
image?: HTMLImageElement
45
+
}
46
+
47
+
export type Message = BaseItem & {
48
+
type: 'message'
49
+
body: string
50
+
mbody?: string
51
+
}
52
+
export type Item = Message | Image
53
+
54
+
export function isMessage(item: Item): item is Message {
55
+
return item.type === 'message'
56
+
}
57
+
58
+
export function isImage(item: Item): item is Image {
59
+
return item.type === 'image'
60
+
}
61
62
export type LogItem = {
63
id: number
···
86
color?: number
87
signetURI: string
88
postedAt: string
89
+
}
90
+
91
+
export type ImageView = {
92
+
$type?: string
93
+
uri: string
94
+
author: ProfileView
95
+
imageUri: string
96
+
nick?: string
97
+
color?: number
98
+
signetURI: string
99
}
100
101
export type SignedMessageView = {
+1
src/lib/utils.ts
···
61
62
export function signedMessageViewToMessage(sm: SignedMessageView): Message {
63
return {
0
64
uri: sm.uri,
65
body: sm.body,
66
id: sm.signet.lrcId,
···
61
62
export function signedMessageViewToMessage(sm: SignedMessageView): Message {
63
return {
64
+
type: 'message',
65
uri: sm.uri,
66
body: sm.body,
67
id: sm.signet.lrcId,
+92
-43
src/lib/wscontext.svelte.ts
···
1
-
import type { Message, LogItem, SignetView, MessageView } from "./types"
0
2
import * as lrc from '@rachel-mp4/lrcproto/gen/ts/lrc'
3
4
// so the thing with the current message is that i require a signet to post
···
10
// so i want to make that side of things better
11
12
export class WSContext {
13
-
messages: Array<Message> = $state(new Array())
14
orphanedSignets: Map<string, SignetView> = new Map()
15
orphanedMessages: Map<string, MessageView> = new Map()
0
16
log: Array<LogItem> = $state(new Array())
17
topic: string = $state("")
18
connected: boolean = $state(false)
···
61
this.ws?.close()
62
this.ls?.close()
63
connectTo(url, this)
64
-
this.messages = []
65
this.orphanedMessages = new Map()
0
66
this.orphanedSignets = new Map()
67
this.mySignet = undefined
68
this.myID = undefined
···
74
this.ws = null
75
this.ls?.close()
76
this.ls = null
77
-
this.messages = []
78
this.orphanedMessages = new Map()
0
79
this.orphanedSignets = new Map()
80
this.mySignet = undefined
81
this.myID = undefined
···
203
204
// theoretically this could occur _after we have an orphaned signet or an orphanedmessage or both! so,
205
// TODO: make it work in that case
206
-
pushMessage = (message: Message) => {
207
if (document.hidden || !document.hasFocus()) {
208
this.audio.currentTime = 0
209
this.audio.play()
210
-
} else if (!message.mine) {
211
this.shortaudio.currentTime = 0
212
this.shortaudio.play()
213
}
214
-
if (this.messages.length > 200) {
215
-
this.messages = [...this.messages.slice(this.messages.length - 199), message]
216
} else {
217
-
this.messages.push(message)
218
}
219
}
220
221
-
editMessage = (id: number, newMessage: Message) => {
222
-
this.messages = this.messages.map((msg: Message) => {
223
-
return msg.id === id ? newMessage : msg
224
})
225
}
226
227
-
pubMessage = (id: number) => {
228
-
this.messages = this.messages.map((msg: Message) => {
229
-
return msg.id === id ? { ...msg, active: false } : msg
230
})
231
}
232
233
insertMessage = (id: number, idx: number, s: string) => {
234
-
this.ensureExistenceOf(id)
235
-
this.messages = this.messages.map((msg: Message) => {
236
-
return msg.id === id ? { ...msg, body: insertSIntoAStringAtIdx(s, msg.body, idx) } : msg
237
})
238
}
239
240
deleteMessage = (id: number, idx1: number, idx2: number) => {
241
-
this.ensureExistenceOf(id)
242
-
this.messages = this.messages.map((msg: Message) => {
243
-
return msg.id === id ? { ...msg, body: deleteFromAStringBetweenIdxs(msg.body, idx1, idx2) } : msg
244
})
245
}
246
247
-
ensureExistenceOf = (id: number) => {
248
-
const idx = this.messages.findIndex((msg) => { return msg.id === id })
249
if (idx === -1) {
250
-
this.pushMessage({
0
251
body: "",
252
id: id,
253
active: true,
···
263
this.mySignet = signet
264
}
265
console.log("now we are signing")
266
-
const arrayIdx = this.messages.findIndex(msg => msg.id === signet.lrcId)
267
if (arrayIdx !== -1) {
268
console.log("found appropriate signet c:")
269
-
this.messages = this.messages.map((msg: Message) => {
270
-
return msg.id === signet.lrcId ? { ...msg, signetView: signet } : msg
271
})
272
} else {
273
console.log("couldn't find appropriate signet :c")
···
275
if (om !== undefined) {
276
console.log("some orphan logic")
277
const message = makeMessageFromSignetAndMessageViews(om, signet)
278
-
const idx = this.messages.findIndex(msg => msg.id > signet.lrcId)
279
if (idx === -1) {
280
-
this.messages.push(message)
281
} else {
282
-
this.messages = [...this.messages.slice(0, idx), message, ...this.messages.slice(idx)]
283
}
284
this.orphanedMessages.delete(signet.uri)
285
-
} else {
286
-
this.orphanedSignets.set(signet.uri, signet)
0
0
0
0
0
0
0
0
0
0
0
0
287
}
0
288
}
289
}
290
291
verifyMessage = (message: MessageView) => {
292
console.log("now we are verifying!")
293
console.log(message.signetURI)
294
-
const arrayIdx = this.messages.findIndex(msg => msg.signetView?.uri === message.signetURI && msg.signetView?.authorHandle === message.author.handle)
295
if (arrayIdx !== -1) {
296
console.log("found appropriate message c:")
297
-
this.messages = this.messages.map((msg: Message) => {
298
-
return msg.signetView?.uri === message.signetURI ?
299
-
{ ...makeMessageFromSignetAndMessageViews(message, msg.signetView), body: msg.body, mine: msg.mine } : msg
300
})
301
}
302
else {
···
305
if (os !== undefined) {
306
console.log("some orphan logic")
307
const m = makeMessageFromSignetAndMessageViews(message, os)
308
-
const idx = this.messages.findIndex(msg => msg.id > os.lrcId)
309
if (idx === -1) {
310
-
this.messages.push(m)
311
} else {
312
-
this.messages = [...this.messages.slice(0, idx), m, ...this.messages.slice(idx)]
313
}
314
this.orphanedSignets.delete(os.uri)
315
} else {
···
317
}
318
}
319
}
0
320
pushToLog = (id: number, ba: Uint8Array, type: string) => {
321
const bstring = Array.from(ba).map(byte => byte.toString(16).padStart(2, "0")).join('')
322
const time = Date.now()
···
331
332
const makeMessageFromSignetAndMessageViews = (m: MessageView, s: SignetView): Message => {
333
return {
0
334
uri: m.uri,
335
body: "i didn't catch the lrc message body :c",
336
mbody: m.body,
···
344
signetView: s,
345
...(m.nick && { nick: m.nick }),
346
startedAt: Date.parse(s.startedAt)
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
0
347
}
348
}
349
···
657
const mine = echoed
658
const muted = false
659
const startedAt = Date.now()
660
-
const msg = {
0
661
body: body,
662
id: id,
663
active: active,
···
668
...(nick && { nick: nick }),
669
startedAt: startedAt
670
}
671
-
ctx.pushMessage(msg)
672
ctx.pushToLog(id, byteArray, "init")
673
return true
674
}
···
676
case "pub": {
677
const id = event.msg.pub.id ?? 0
678
if (id === 0) return false
679
-
ctx.pubMessage(id)
680
ctx.pushToLog(id, byteArray, "pub")
681
return false
682
}
···
706
const mine = false
707
const body = ""
708
const startedAt = Date.now()
709
-
ctx.pushMessage({ id, body, muted, active, mine, startedAt })
0
0
0
0
0
0
0
0
0
0
710
return false
711
}
712
···
1
+
import type { Item, Image, Message, LogItem, SignetView, MessageView, ImageView } from "./types"
2
+
import { isMessage, isImage } from "./types"
3
import * as lrc from '@rachel-mp4/lrcproto/gen/ts/lrc'
4
5
// so the thing with the current message is that i require a signet to post
···
11
// so i want to make that side of things better
12
13
export class WSContext {
14
+
items: Array<Item> = $state(new Array())
15
orphanedSignets: Map<string, SignetView> = new Map()
16
orphanedMessages: Map<string, MessageView> = new Map()
17
+
orphanedImages: Map<string, ImageView> = new Map()
18
log: Array<LogItem> = $state(new Array())
19
topic: string = $state("")
20
connected: boolean = $state(false)
···
63
this.ws?.close()
64
this.ls?.close()
65
connectTo(url, this)
66
+
this.items = []
67
this.orphanedMessages = new Map()
68
+
this.orphanedImages = new Map()
69
this.orphanedSignets = new Map()
70
this.mySignet = undefined
71
this.myID = undefined
···
77
this.ws = null
78
this.ls?.close()
79
this.ls = null
80
+
this.items = []
81
this.orphanedMessages = new Map()
82
+
this.orphanedImages = new Map()
83
this.orphanedSignets = new Map()
84
this.mySignet = undefined
85
this.myID = undefined
···
207
208
// theoretically this could occur _after we have an orphaned signet or an orphanedmessage or both! so,
209
// TODO: make it work in that case
210
+
pushItem = (item: Item) => {
211
if (document.hidden || !document.hasFocus()) {
212
this.audio.currentTime = 0
213
this.audio.play()
214
+
} else if (!item.mine) {
215
this.shortaudio.currentTime = 0
216
this.shortaudio.play()
217
}
218
+
if (this.items.length > 200) {
219
+
this.items = [...this.items.slice(this.items.length - 199), item]
220
} else {
221
+
this.items.push(item)
222
}
223
}
224
225
+
replaceItem = (id: number, newItem: Item) => {
226
+
this.items = this.items.map((item: Item) => {
227
+
return item.id === id ? newItem : item
228
})
229
}
230
231
+
pubItem = (id: number) => {
232
+
this.items = this.items.map((item: Item) => {
233
+
return isMessage(item) && item.id === id ? { ...item, active: false } : item
234
})
235
}
236
237
insertMessage = (id: number, idx: number, s: string) => {
238
+
this.ensureExistenceOfMessage(id)
239
+
this.items = this.items.map((item: Item) => {
240
+
return isMessage(item) && item.id === id ? { ...item, body: insertSIntoAStringAtIdx(s, item.body, idx) } : item
241
})
242
}
243
244
deleteMessage = (id: number, idx1: number, idx2: number) => {
245
+
this.ensureExistenceOfMessage(id)
246
+
this.items = this.items.map((item: Item) => {
247
+
return isMessage(item) && item.id === id ? { ...item, body: deleteFromAStringBetweenIdxs(item.body, idx1, idx2) } : item
248
})
249
}
250
251
+
ensureExistenceOfMessage = (id: number) => {
252
+
const idx = this.items.findIndex((item) => { return item.id === id })
253
if (idx === -1) {
254
+
this.pushItem({
255
+
type: 'message',
256
body: "",
257
id: id,
258
active: true,
···
268
this.mySignet = signet
269
}
270
console.log("now we are signing")
271
+
const arrayIdx = this.items.findIndex(item => item.id === signet.lrcId)
272
if (arrayIdx !== -1) {
273
console.log("found appropriate signet c:")
274
+
this.items = this.items.map((item: Item) => {
275
+
return item.id === signet.lrcId ? { ...item, signetView: signet } : item
276
})
277
} else {
278
console.log("couldn't find appropriate signet :c")
···
280
if (om !== undefined) {
281
console.log("some orphan logic")
282
const message = makeMessageFromSignetAndMessageViews(om, signet)
283
+
const idx = this.items.findIndex(item => item.id > signet.lrcId)
284
if (idx === -1) {
285
+
this.items.push(message)
286
} else {
287
+
this.items = [...this.items.slice(0, idx), message, ...this.items.slice(idx)]
288
}
289
this.orphanedMessages.delete(signet.uri)
290
+
return
291
+
}
292
+
const oi = this.orphanedImages.get(signet.uri)
293
+
if (oi !== undefined) {
294
+
console.log("comse orphan logic 2")
295
+
const image = makeImageFromSignetAndImageViews(oi, signet)
296
+
const idx = this.items.findIndex(item => item.id > signet.lrcId)
297
+
if (idx === -1) {
298
+
this.items.push(image)
299
+
} else {
300
+
this.items = [...this.items.slice(0, idx), image, ...this.items.slice(idx)]
301
+
}
302
+
this.orphanedImages.delete(signet.uri)
303
+
return
304
}
305
+
this.orphanedSignets.set(signet.uri, signet)
306
}
307
}
308
309
verifyMessage = (message: MessageView) => {
310
console.log("now we are verifying!")
311
console.log(message.signetURI)
312
+
const arrayIdx = this.items.findIndex(item => item.signetView?.uri === message.signetURI && item.signetView?.authorHandle === message.author.handle)
313
if (arrayIdx !== -1) {
314
console.log("found appropriate message c:")
315
+
this.items = this.items.map((item: Item) => {
316
+
return item.signetView?.uri === message.signetURI && isMessage(item) ?
317
+
{ ...makeMessageFromSignetAndMessageViews(message, item.signetView), body: item.body, mine: item.mine } : item
318
})
319
}
320
else {
···
323
if (os !== undefined) {
324
console.log("some orphan logic")
325
const m = makeMessageFromSignetAndMessageViews(message, os)
326
+
const idx = this.items.findIndex(item => item.id > os.lrcId)
327
if (idx === -1) {
328
+
this.items.push(m)
329
} else {
330
+
this.items = [...this.items.slice(0, idx), m, ...this.items.slice(idx)]
331
}
332
this.orphanedSignets.delete(os.uri)
333
} else {
···
335
}
336
}
337
}
338
+
339
pushToLog = (id: number, ba: Uint8Array, type: string) => {
340
const bstring = Array.from(ba).map(byte => byte.toString(16).padStart(2, "0")).join('')
341
const time = Date.now()
···
350
351
const makeMessageFromSignetAndMessageViews = (m: MessageView, s: SignetView): Message => {
352
return {
353
+
type: 'message',
354
uri: m.uri,
355
body: "i didn't catch the lrc message body :c",
356
mbody: m.body,
···
364
signetView: s,
365
...(m.nick && { nick: m.nick }),
366
startedAt: Date.parse(s.startedAt)
367
+
}
368
+
}
369
+
370
+
const makeImageFromSignetAndImageViews = (i: ImageView, s: SignetView): Image => {
371
+
return {
372
+
type: 'image',
373
+
uri: i.uri,
374
+
id: s.lrcId,
375
+
active: false,
376
+
mine: false,
377
+
muted: false,
378
+
//image: fetch(i.imageUri)
379
+
...(i.nick && { nick: i.nick }),
380
+
...(i.color && { color: i.color }),
381
+
handle: i.author.handle,
382
+
profileView: i.author,
383
+
signetView: s,
384
+
startedAt: Date.parse(s.startedAt),
385
}
386
}
387
···
695
const mine = echoed
696
const muted = false
697
const startedAt = Date.now()
698
+
const msg: Message = {
699
+
type: 'message',
700
body: body,
701
id: id,
702
active: active,
···
707
...(nick && { nick: nick }),
708
startedAt: startedAt
709
}
710
+
ctx.pushItem(msg)
711
ctx.pushToLog(id, byteArray, "init")
712
return true
713
}
···
715
case "pub": {
716
const id = event.msg.pub.id ?? 0
717
if (id === 0) return false
718
+
ctx.pubItem(id)
719
ctx.pushToLog(id, byteArray, "pub")
720
return false
721
}
···
745
const mine = false
746
const body = ""
747
const startedAt = Date.now()
748
+
const msg: Message = {
749
+
type: "message",
750
+
id: id,
751
+
body: body,
752
+
muted: muted,
753
+
active: active,
754
+
mine: mine,
755
+
startedAt: startedAt
756
+
}
757
+
758
+
ctx.pushItem(msg)
759
return false
760
}
761
+2
-2
src/routes/c/[handle]/[rkey]/+page.svelte
···
49
</div>
50
{/if}
51
{#if ctx}
52
-
{#if ctx.messages.length === 0 && ctx.connected}
53
<div>connecting...</div>
54
<div>and you're connected.</div>
55
<div>messages will go here, start typing down below</div>
···
94
</div>
95
{/if}
96
<Receiever
97
-
messages={ctx.messages}
98
mylocaltext={ctx.curMsg}
99
onmute={ctx.mute}
100
onunmute={ctx.unmute}
···
49
</div>
50
{/if}
51
{#if ctx}
52
+
{#if ctx.items.length === 0 && ctx.connected}
53
<div>connecting...</div>
54
<div>and you're connected.</div>
55
<div>messages will go here, start typing down below</div>
···
94
</div>
95
{/if}
96
<Receiever
97
+
items={ctx.items}
98
mylocaltext={ctx.curMsg}
99
onmute={ctx.mute}
100
onunmute={ctx.unmute}