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