tangled
alpha
login
or
join now
moth11.net
/
xcvr
2
fork
atom
frontend for xcvr appview
2
fork
atom
overview
issues
pulls
pipelines
just gonna try this and see what breaks
moth11.net
5 months ago
57f7747c
c442e411
+217
-17
4 changed files
expand all
collapse all
unified
split
package-lock.json
src
lib
components
AutoGrowTextArea.svelte
Transmitter.svelte
wscontext.svelte.ts
+3
-3
package-lock.json
···
558
558
"license": "(Apache-2.0 AND BSD-3-Clause)"
559
559
},
560
560
"node_modules/@rachel-mp4/lrcproto": {
561
561
-
"version": "1.0.4",
562
562
-
"resolved": "https://registry.npmjs.org/@rachel-mp4/lrcproto/-/lrcproto-1.0.4.tgz",
563
563
-
"integrity": "sha512-C5wtSj1oa8YhKb7U0v4YgNx4OMfrtJmhCDlZ0BMmCrBA93IX153pbjro8WtXDNfZXyyUgOLkjzd1VrsMgUA3KA==",
561
561
+
"version": "1.2.0",
562
562
+
"resolved": "https://registry.npmjs.org/@rachel-mp4/lrcproto/-/lrcproto-1.2.0.tgz",
563
563
+
"integrity": "sha512-DSbEf5wxS0ArjgYkh6ZVLqpB3qvGlGB8Co9AzDeK9fnYZIfwmVgSQemT/Um2e2Oaq/mioNAx8gph6T01wQz1dQ==",
564
564
"license": "MIT",
565
565
"dependencies": {
566
566
"typescript": "^3.9.10"
+1
src/lib/components/AutoGrowTextArea.svelte
···
230
230
position: absolute;
231
231
top: 0;
232
232
left: 0;
233
233
+
max-height: 16rem;
233
234
}
234
235
.selected.emoji-result {
235
236
background: var(--fg);
+40
-13
src/lib/components/Transmitter.svelte
···
14
14
let nick = $state(defaultNick ?? "wanderer");
15
15
let innerWidth = $state(0);
16
16
let isDesktop = $derived(innerWidth > 1000);
17
17
+
let imageURL: string | undefined = $state();
18
18
+
let imageAlt: string = $state("");
17
19
$effect(() => {
18
20
if (ctx) {
19
21
ctx.setNick(nick);
···
75
77
};
76
78
const convertFileToImageItem = (blob: File) => {
77
79
const blobUrl = URL.createObjectURL(blob);
78
78
-
const img = document.createElement("img");
79
79
-
img.src = blobUrl;
80
80
-
const image: Image = {
81
81
-
type: "image",
82
82
-
image: img,
83
83
-
id: 0,
84
84
-
active: false,
85
85
-
mine: false,
86
86
-
muted: false,
87
87
-
startedAt: Date.now(),
88
88
-
};
89
89
-
ctx.pushItem(image);
90
90
-
console.log("pushed image item!");
80
80
+
ctx.initImage(blob);
81
81
+
imageURL = blobUrl;
82
82
+
};
83
83
+
const cancelimagepost = () => {
84
84
+
if (imageURL) {
85
85
+
URL.revokeObjectURL(imageURL);
86
86
+
}
87
87
+
ctx.atpblob = undefined;
88
88
+
ctx.pubImage("");
89
89
+
imageAlt = "";
90
90
+
imageURL = undefined;
91
91
+
};
92
92
+
const uploadimage = () => {
93
93
+
ctx.pubImage(imageAlt);
94
94
+
if (imageURL) {
95
95
+
URL.revokeObjectURL(imageURL);
96
96
+
}
97
97
+
imageAlt = "";
98
98
+
imageURL = undefined;
91
99
};
92
100
</script>
93
101
···
136
144
maxlength={65535}
137
145
fs={isDesktop ? "2rem" : "1rem"}
138
146
/>
147
147
+
{#if imageURL !== undefined}
148
148
+
<div>
149
149
+
<img src={imageURL} alt={imageAlt} />
150
150
+
<AutoGrowInput
151
151
+
bind:value={imageAlt}
152
152
+
placeholder="alt text"
153
153
+
size={10}
154
154
+
bold={false}
155
155
+
fs={isDesktop ? "2rem" : "1rem"}
156
156
+
/>
157
157
+
<button onclick={cancelimagepost}> cancel </button>
158
158
+
{#if ctx.atpblob}
159
159
+
confirm
160
160
+
{:else}
161
161
+
uploading...
162
162
+
{/if}
163
163
+
<button onclick={uploadimage}> confirm</button>
164
164
+
</div>
165
165
+
{/if}
139
166
</div>
140
167
141
168
<style>
+173
-1
src/lib/wscontext.svelte.ts
···
9
9
// however long it takes for atproto to propogate, you can't submit your
10
10
// message either.
11
11
// so i want to make that side of things better
12
12
+
type ATPBlob = {
13
13
+
$type: string
14
14
+
ref: string
15
15
+
mimeType: string
16
16
+
size: number
17
17
+
}
18
18
+
19
19
+
type ATPImage = {
20
20
+
$type: string
21
21
+
alt: string
22
22
+
aspectRatio?: ATPAspectRatio
23
23
+
blob?: ATPBlob
24
24
+
}
25
25
+
26
26
+
type ATPAspectRatio = {
27
27
+
width: number
28
28
+
height: number
29
29
+
}
12
30
13
31
export class WSContext {
14
32
items: Array<Item> = $state(new Array())
···
25
43
26
44
channelUri: string
27
45
active: boolean = false
46
46
+
mediaactive: boolean = false
28
47
nick: string = "wanderer"
29
48
handle: string = ""
30
49
31
50
myID: undefined | number
32
51
myNonce: undefined | Uint8Array
33
52
curMsg: string = $state("")
53
53
+
mySignet: undefined | SignetView
34
54
35
35
-
mySignet: undefined | SignetView
55
55
+
myMediaID: undefined | number
56
56
+
myMediaNonce: undefined | Uint8Array
57
57
+
atpblob: ATPBlob | undefined
58
58
+
myMediaSignet: undefined | SignetView
36
59
37
60
audio: HTMLAudioElement = new Audio('/notif.wav')
38
61
shortaudio: HTMLAudioElement = new Audio('/shortnotif.wav')
···
162
185
}
163
186
}
164
187
188
188
+
pubImage = (alt: string) => {
189
189
+
if (this.atpblob) {
190
190
+
const image: ATPImage = { $type: "string", alt: alt, blob: this.atpblob }
191
191
+
const record = {
192
192
+
...(this.mySignet && { signetURI: this.mySignet.uri }),
193
193
+
...(this.channelUri && { channelURI: this.channelUri }),
194
194
+
...(this.myID && { messageID: this.myID }),
195
195
+
...(this.myNonce && { nonce: b64encodebytearray(this.myNonce) }),
196
196
+
image: image,
197
197
+
...(this.nick && { nick: this.nick }),
198
198
+
...(this.color && { color: this.color }),
199
199
+
}
200
200
+
const api = import.meta.env.VITE_API_URL
201
201
+
const recordstrungified = JSON.stringify(record)
202
202
+
const endpoint = `${api}/lrc/media`
203
203
+
fetch(endpoint, {
204
204
+
method: "POST",
205
205
+
headers: {
206
206
+
"Content-Type": "application/json",
207
207
+
},
208
208
+
body: recordstrungified,
209
209
+
}).then((response) => {
210
210
+
if (response.ok) {
211
211
+
console.log(response)
212
212
+
} else {
213
213
+
throw new Error(`HTTP ${response.status}`)
214
214
+
}
215
215
+
}).catch(() => {
216
216
+
setTimeout(() => {
217
217
+
fetch(endpoint, {
218
218
+
method: "POST",
219
219
+
headers: {
220
220
+
"Content-Type": "application/json",
221
221
+
},
222
222
+
body: recordstrungified,
223
223
+
}).then((val) => console.log(val), (val) => console.log(val))
224
224
+
}, 2000)
225
225
+
})
226
226
+
const uri = "beep"
227
227
+
const contentAddress = `${api}/lrc/getImage?uri=${uri}`
228
228
+
if (this.mediaactive) {
229
229
+
pubImage(alt, contentAddress, this)
230
230
+
}
231
231
+
} else {
232
232
+
pubImage(alt, undefined, this)
233
233
+
}
234
234
+
}
235
235
+
236
236
+
initImage = (blob: File) => {
237
237
+
if (!this.mediaactive) {
238
238
+
initImage(this)
239
239
+
this.mediaactive = true
240
240
+
const api = import.meta.env.VITE_API_URL
241
241
+
const endpoint = `${api}/lrc/image`
242
242
+
const formData = new FormData()
243
243
+
formData.append("image", blob)
244
244
+
fetch(endpoint, {
245
245
+
method: "POST",
246
246
+
body: formData,
247
247
+
}).then((response) => {
248
248
+
if (response.ok) {
249
249
+
response.json().then((atpblob) =>
250
250
+
this.atpblob = atpblob)
251
251
+
} else {
252
252
+
throw new Error(`HTTP ${response.status}`)
253
253
+
}
254
254
+
}).catch((err) => { console.log(err) })
255
255
+
}
256
256
+
}
257
257
+
258
258
+
165
259
insert = (idx: number, s: string) => {
166
260
if (!this.active) {
167
261
initMessage(this)
···
234
328
})
235
329
}
236
330
331
331
+
mediapubItem = (id: number, alt: string | undefined, contentAddress: string | undefined) => {
332
332
+
this.items = this.items.map((item: Item) => {
333
333
+
return isImage(item) && item.id === id ? { ...item, active: false, alt: alt, contentAddress: contentAddress } : item
334
334
+
})
335
335
+
}
336
336
+
237
337
insertMessage = (id: number, idx: number, s: string) => {
238
338
this.ensureExistenceOfMessage(id)
239
339
this.items = this.items.map((item: Item) => {
···
266
366
addSignet = (signet: SignetView) => {
267
367
if (signet.lrcId === this.myID) {
268
368
this.mySignet = signet
369
369
+
}
370
370
+
if (signet.lrcId === this.myMediaID) {
371
371
+
this.myMediaSignet = signet
269
372
}
270
373
console.log("now we are signing")
271
374
const arrayIdx = this.items.findIndex(item => item.id === signet.lrcId)
···
518
621
ctx.ws?.send(byteArray)
519
622
}
520
623
624
624
+
export const initImage = (ctx: WSContext) => {
625
625
+
const evt: lrc.Event = {
626
626
+
msg: {
627
627
+
oneofKind: "mediainit",
628
628
+
mediainit: {
629
629
+
nick: ctx.nick,
630
630
+
color: ctx.color,
631
631
+
externalID: ctx.handle
632
632
+
}
633
633
+
}
634
634
+
}
635
635
+
const byteArray = lrc.Event.toBinary(evt)
636
636
+
ctx.ws?.send(byteArray)
637
637
+
}
638
638
+
639
639
+
export const pubImage = (alt: string | undefined, contentAddress: string | undefined, ctx: WSContext) => {
640
640
+
const evt: lrc.Event = {
641
641
+
msg: {
642
642
+
oneofKind: "mediapub",
643
643
+
mediapub: {
644
644
+
alt: alt,
645
645
+
contentAddress: contentAddress,
646
646
+
}
647
647
+
}
648
648
+
}
649
649
+
const byteArray = lrc.Event.toBinary(evt)
650
650
+
ctx.ws?.send(byteArray)
651
651
+
}
652
652
+
521
653
export const insertMessage = (idx: number, s: string, ctx: WSContext) => {
522
654
if (ctx.shouldTransmit) {
523
655
const evt: lrc.Event = {
···
712
844
return true
713
845
}
714
846
847
847
+
case "mediainit": {
848
848
+
const id = event.msg.mediainit.id ?? 0
849
849
+
if (id === 0) return false
850
850
+
const echoed = event.msg.mediainit.echoed ?? false
851
851
+
if (echoed) {
852
852
+
ctx.myMediaID = id
853
853
+
ctx.myMediaNonce = event.msg.mediainit.nonce
854
854
+
// return false
855
855
+
}
856
856
+
const nick = event.msg.mediainit.nick
857
857
+
const handle = event.msg.mediainit.externalID
858
858
+
const color = event.msg.mediainit.color
859
859
+
const active = true
860
860
+
const mine = echoed
861
861
+
const muted = false
862
862
+
const startedAt = Date.now()
863
863
+
const msg: Image = {
864
864
+
type: 'image',
865
865
+
id: id,
866
866
+
active: active,
867
867
+
mine: mine,
868
868
+
muted: muted,
869
869
+
...(color && { color: color }),
870
870
+
...(handle && { handle: handle }),
871
871
+
...(nick && { nick: nick }),
872
872
+
startedAt: startedAt
873
873
+
}
874
874
+
ctx.pushItem(msg)
875
875
+
ctx.pushToLog(id, byteArray, "init")
876
876
+
return true
877
877
+
}
878
878
+
715
879
case "pub": {
716
880
const id = event.msg.pub.id ?? 0
717
881
if (id === 0) return false
718
882
ctx.pubItem(id)
883
883
+
ctx.pushToLog(id, byteArray, "pub")
884
884
+
return false
885
885
+
}
886
886
+
887
887
+
case "mediapub": {
888
888
+
const id = event.msg.mediapub.id ?? 0
889
889
+
if (id === 0) return false
890
890
+
ctx.mediapubItem(id, event.msg.mediapub.alt, event.msg.mediapub.contentAddress)
719
891
ctx.pushToLog(id, byteArray, "pub")
720
892
return false
721
893
}