diff --git a/src/lib/layouts/Chatbar.svelte b/src/lib/layouts/Chatbar.svelte index 89d7183ab..9d0a40f73 100644 --- a/src/lib/layouts/Chatbar.svelte +++ b/src/lib/layouts/Chatbar.svelte @@ -95,7 +95,6 @@ UIStore.mutateChat(chat.id, c => { c.last_view_date = new Date() }) - ConversationStore.addPendingMessages(chat.id, res.message, txt) }) if (!isStickerOrGif) { chatMessages.update(messages => { diff --git a/src/lib/wasm/CommunitiesStore.ts b/src/lib/wasm/CommunitiesStore.ts index 9ee57069b..cd8cd920f 100644 --- a/src/lib/wasm/CommunitiesStore.ts +++ b/src/lib/wasm/CommunitiesStore.ts @@ -1,9 +1,14 @@ -import { get, writable, type Writable } from "svelte/store" +import { get, type Writable } from "svelte/store" import * as wasm from "warp-wasm" import { WarpStore } from "./WarpStore" -import { type Cancellable } from "$lib/utils/CancellablePromise" import { failure, success, type Result } from "$lib/utils/Result" import { handleErrors, WarpError } from "./HandleWarpErrors" +import { MessageOptions } from "warp-wasm" +import { convertWarpAttachment, createFileAttachHandler, createFileDownloadHandler, createFileDownloadHandlerRaw, type FetchMessageResponse, type FetchMessagesConfig, type FileAttachment, type SendMessageResult } from "./RaygunStore" +import { messageTypeFromTexts, type Message, type Reaction } from "$lib/types" +import { MultipassStoreInstance } from "./MultipassStore" +import { Store } from "$lib/state/Store" +import { Appearance } from "$lib/enums" class CommunitiesStore { private raygunWritable: Writable @@ -12,9 +17,6 @@ class CommunitiesStore { this.raygunWritable = raygun } - // async getCommunityStream(community_id: string): Promise { - - // } async createCommunity(name: string) { return this.get(r => r.create_community(name), `Error creating a community with name ${name}`) } @@ -136,28 +138,166 @@ class CommunitiesStore { return this.get(r => r.edit_community_channel_description(community_id, channel_id, description), `Error editing channel ${channel_id} description for community ${community_id}`) } - async grantCommunityChannelPermission(community_id: string, channel_id: string, permission: CommunityChannelPermission, role_id: string) - async revokeCommunityChannelPermission(community_id: string, channel_id: string, permission: CommunityChannelPermission, role_id: string) - async grantCommunityChannelPermissionForAll(community_id: string, channel_id: string, permission: CommunityChannelPermission) - async revokeCommunityChannelPermissionForAll(community_id: string, channel_id: string, permission: CommunityChannelPermission) - - async getCommunityChannelMessage(community_id: string, channel_id: string, message_id: string) - async getCommunityChannelMessages(community_id: string, channel_id: string, options: MessageOptions) - async getCommunityChannelMessageCount(community_id: string, channel_id: string) - async getCommunityChannelMessageReference(community_id: string, channel_id: string, message_id: string) - async getCommunityChannelMessageReferences(community_id: string, channel_id: string, options: MessageOptions) - - async communityChannelMessageStatus(community_id: string, channel_id: string, message_id: string) - async sendCommunityChannelMessage(community_id: string, channel_id: string, message: string[]) - async editCommunityChannelMessage(community_id: string, channel_id: string, message_id: string, message: string[]) - async replyToCommunityChannelMessage(community_id: string, channel_id: string, message_id: string, message: string[]) - async deleteCommunityChannelMessage(community_id: string, channel_id: string, message_id: string) - async pinCommunityChannelMessage(community_id: string, channel_id: string, message_id: string, state: PinState) - async reactToCommunityChannelMessage(community_id: string, channel_id: string, message_id: string, state: ReactionState, emoji: string) - async sendCommunityChannelMesssageEvent(community_id: string, channel_id: string, event: MessageEvent) - async cancelCommunityChannelMesssageEvent(community_id: string, channel_id: string, event: MessageEvent) - async attachToCommunityChannelMessage(community_id: string, channel_id: string, message_id: string | undefined, files: AttachmentFile[], message: string[]) - async downloadFromCommunityChannelMessage(community_id: string, channel_id: string, message_id: string, file: string) {} + async grantCommunityChannelPermission(community_id: string, channel_id: string, permission: CommunityChannelPermission, role_id: string) { + return this.get(r => r.grant_community_channel_permission(community_id, channel_id, permission, role_id), `Error granting channel permission for role ${role_id} in community ${community_id}`) + } + async revokeCommunityChannelPermission(community_id: string, channel_id: string, permission: CommunityChannelPermission, role_id: string) { + return this.get(r => r.revoke_community_channel_permission(community_id, channel_id, permission, role_id), `Error revoking channel permission for role ${role_id} in for community ${community_id}`) + } + async grantCommunityChannelPermissionForAll(community_id: string, channel_id: string, permission: CommunityChannelPermission) { + return this.get(r => r.grant_community_channel_permission_for_all(community_id, channel_id, permission), `Error granting channel permission for community ${community_id}`) + } + async revokeCommunityChannelPermissionForAll(community_id: string, channel_id: string, permission: CommunityChannelPermission) { + return this.get(r => r.revoke_community_channel_permission_for_all(community_id, channel_id, permission), `Error revoking channel permission for community ${community_id}`) + } + + async getCommunityChannelMessage(community_id: string, channel_id: string, message_id: string) { + return this.get(r => r.get_community_channel_message(community_id, channel_id, message_id), `Error revoking channel permission for community ${community_id}`) + } + + async fetchCommunityMessages(community_id: string, channel_id: string, config: FetchMessagesConfig): Promise> { + return this.get(async r => { + let message_options = new MessageOptions() + switch (config.type) { + case "Between": { + message_options.set_date_range(config.from, config.to) //TODO verify that js Date can be parsed to rust DateTime:: + break + } + case "MostRecent": { + let total_messages = await r.get_community_channel_message_count(community_id, channel_id) + message_options.set_range(Math.min(0, total_messages - config.amount), total_messages) + break + } + case "Earlier": { + message_options.set_date_range(new Date(), config.start_date) + message_options.set_reverse() + message_options.set_limit(config.limit) + break + } + case "Later": { + message_options.set_date_range(config.start_date, new Date()) + message_options.set_limit(config.limit) + break + } + } + + let messages = await this.getMessages(r, community_id, channel_id, message_options) + if (config.type === "Earlier") { + messages = messages.reverse() + } + let has_more = "limit" in config ? messages.length >= config.limit : false + + let opt = new MessageOptions() + opt.set_limit(1) + opt.set_last_message() + let most_recent = await this.getMessages(r, community_id, channel_id, opt) + return { + messages: messages, + has_more: has_more, + most_recent: most_recent[most_recent.length].id, + } + }, "Error fetching messages") + } + + private async getMessages(raygun: wasm.RayGunBox, community_id: string, channel_id: string, options: MessageOptions) { + let msgs = await raygun.get_community_channel_messages(community_id, channel_id, options) + let messages: Message[] = [] + if (msgs.variant() === wasm.MessagesEnum.List) { + let warpMsgs = msgs.messages()! + messages = (await Promise.all(warpMsgs.map(async msg => await this.convertCommunityMessage(community_id, channel_id, msg)))).filter((m: Message | null): m is Message => m !== null) + } + return messages + } + + async communityChannelMessageStatus(community_id: string, channel_id: string, message_id: string) { + return this.get(r => r.community_channel_message_status(community_id, channel_id, message_id), `Error fetching message status for message ${message_id} in community ${community_id}`) + } + async sendCommunityChannelMessage(community_id: string, channel_id: string, message: string[], attachments?: FileAttachment[]) { + return await this.get(async r => { + return await this.sendTo(r, community_id, channel_id, message, { attachments }) + }, `Error sending message in channel ${channel_id} for community ${community_id}`) + } + async editCommunityChannelMessage(community_id: string, channel_id: string, message_id: string, message: string[]) { + return this.get(r => r.edit_community_channel_message(community_id, channel_id, message_id, message), `Error revoking channel permission for community ${community_id}`) + } + async replyToCommunityChannelMessage(community_id: string, channel_id: string, message_id: string, message: string[], attachments?: FileAttachment[]) { + return await this.get(async r => { + return await this.sendTo(r, community_id, channel_id, message, { attachments, replyTo: message_id }) + }, `Error replying to message ${message_id} in channel ${channel_id} for community ${community_id}`) + } + async deleteCommunityChannelMessage(community_id: string, channel_id: string, message_id: string) { + return this.get(r => r.delete_community_channel_message(community_id, channel_id, message_id), `Error deleting message ${message_id} for channel ${channel_id} in community ${community_id}`) + } + async pinCommunityChannelMessage(community_id: string, channel_id: string, message_id: string, pin: boolean) { + return this.get(r => r.pin_community_channel_message(community_id, channel_id, message_id, pin ? wasm.PinState.Pin : wasm.PinState.Unpin), `Error pinning message for community ${community_id}`) + } + async reactToCommunityChannelMessage(community_id: string, channel_id: string, message_id: string, state: wasm.ReactionState, emoji: string) { + let result = await this.get(r => r.react_to_community_channel_message(community_id, channel_id, message_id, state, emoji), "Error reacting to community message") + return result.map(_ => { + // TODO: Requires Store changes + // ConversationStore.editReaction(get(Store.state.activeChat).id, message_id, emoji, state == wasm.ReactionState.Add) + }) + } + + async downloadFromCommunityChannelMessage(community_id: string, channel_id: string, message_id: string, file: string, size?: number) { + return await this.get(async r => { + let result = await r.download_stream_from_community_channel_message(community_id, channel_id, message_id, file) + return createFileDownloadHandler(file, result, size) + }, `Error downloading community attachment from community ${community_id} for message ${message_id}`) + } + + async getCommunityMessageAttachmentRaw(community_id: string, channel_id: string, message_id: string, file: string, options?: { size?: number; type?: string }) { + return await this.get(async r => { + let result = await r.download_stream_from_community_channel_message(community_id, channel_id, message_id, file) + return createFileDownloadHandlerRaw(file, result, options) + }, `Error downloading community attachment from community ${community_id} for message ${message_id}`) + } + + async sendCommunityChannelMesssageEvent(community_id: string, channel_id: string, event: wasm.MessageEvent) { + return await this.get(r => r.send_community_channel_messsage_event(community_id, channel_id, event), `Error sending event ${event}`) + } + async cancelCommunityChannelMesssageEvent(community_id: string, channel_id: string, event: wasm.MessageEvent) { + return await this.get(r => r.cancel_community_channel_messsage_event(community_id, channel_id, event), `Error sending event ${event}`) + } + + private async sendTo(raygun: wasm.RayGunBox, community_id: string, channel_id: string, message: string[], settings?: { attachments?: FileAttachment[]; replyTo?: string }): Promise { + if (settings?.attachments && settings?.attachments.length > 0) { + let result = await raygun + .attach_to_community_channel_message( + community_id, + channel_id, + settings?.replyTo, + settings?.attachments.map(f => new wasm.AttachmentFile(f.file, f.attachment ? new wasm.AttachmentStream(f.attachment[1], f.attachment[0]) : undefined)), + message + ) + .then(res => { + // message_sent event gets fired AFTER this returns + // TODO: Requires Store changes + // ConversationStore.addPendingMessages(conversation_id, res.get_message_id(), message) + createFileAttachHandler(res, (file, updater) => { + //TODO: Update file on store + }) + return res + }) + return { + message: result.get_message_id(), + progress: result, + } + } + return { + message: await (settings?.replyTo ? raygun.reply_to_community_channel_message(community_id, channel_id, settings.replyTo, message) : raygun.send_community_channel_message(community_id, channel_id, message)).then(messageId => { + // message_sent event gets fired BEFORE this returns + // So to + // 1. unify this system + // 2. keep it roughly the same as native (as on native due to some channel delays it handles message_sent after #send returns) + // We add the pending msg here and remove it in message_sent which has a short delay + // TODO: Requires Store changes + // ConversationStore.addPendingMessages(conversation_id, messageId, message) + return messageId + }), + } + } + /** * Convenient helper method to get data from raygun */ @@ -172,6 +312,40 @@ class CommunitiesStore { } return failure(WarpError.RAYGUN_NOT_FOUND) } + + private async convertCommunityMessage(community_id: string, channel_id: string, message: wasm.Message | undefined): Promise { + if (!message) return null + let user = get(Store.state.user) + let remote = message.sender() !== user.key + if (remote) { + let sender = await MultipassStoreInstance.identity_from_did(message.sender()) + if (sender) Store.updateUser(sender) + } + let attachments = message.attachments() + let reactions: { [key: string]: Reaction } = {} + message.reactions().forEach((dids, emoji) => { + reactions[emoji] = { + reactors: new Set(dids), + emoji: emoji, + highlight: Appearance.Default, //TODO + description: "", //TODO + } + }) + return { + id: message.id(), + details: { + at: message.date(), + origin: message.sender(), + remote: remote, + }, + text: message.lines(), + inReplyTo: message.replied() ? null : null, // fetch from ConversationStore + reactions: reactions, + attachments: attachments.map(f => convertWarpAttachment(f)), + pinned: message.pinned(), + type: messageTypeFromTexts(message.lines()), + } + } } export type CommunityPermission = wasm.CommunityPermission diff --git a/src/lib/wasm/RaygunStore.ts b/src/lib/wasm/RaygunStore.ts index d04a094d4..13e5337c9 100644 --- a/src/lib/wasm/RaygunStore.ts +++ b/src/lib/wasm/RaygunStore.ts @@ -6,7 +6,7 @@ import { UIStore } from "../state/ui" import { ConversationStore } from "../state/conversation" import { MessageOptions } from "warp-wasm" import { Appearance, ChatType, MessageAttachmentKind, Route } from "$lib/enums" -import { type User, type Chat, defaultChat, type Message, mentions_user, type Attachment, messageTypeFromTexts, type Reaction } from "$lib/types" +import { type User, type Chat, defaultChat, type Message, mentions_user, type Attachment, messageTypeFromTexts, type Reaction, type FileProgress } from "$lib/types" import { WarpError, handleErrors } from "./HandleWarpErrors" import { failure, success, type Result } from "$lib/utils/Result" import { create_cancellable_handler, type Cancellable } from "$lib/utils/CancellablePromise" @@ -306,7 +306,7 @@ class RaygunStore { async send(conversation_id: string, message: string[], attachments?: FileAttachment[]): Promise> { return await this.get(async r => { - return await this.sendTo(r, conversation_id, message, attachments) + return await this.sendTo(r, conversation_id, message, { attachments }) }, "Error sending message") } @@ -314,26 +314,26 @@ class RaygunStore { return await this.get(async r => { let sent = [] for (let conversation_id of conversation_ids) { - let res: MultiSendMessageResult = { chat: conversation_id, result: await this.sendTo(r, conversation_id, message, attachments) } + let res: MultiSendMessageResult = { chat: conversation_id, result: await this.sendTo(r, conversation_id, message, { attachments }) } sent.push(res) } return sent }, "Error sending message") } - private async sendTo(raygun: wasm.RayGunBox, conversation_id: string, message: string[], attachments?: FileAttachment[]): Promise { - if (attachments && attachments.length > 0) { + private async sendTo(raygun: wasm.RayGunBox, conversation_id: string, message: string[], settings?: { attachments?: FileAttachment[]; replyTo?: string }): Promise { + if (settings?.attachments && settings?.attachments.length > 0) { let result = await raygun .attach( conversation_id, - undefined, - attachments.map(f => new wasm.AttachmentFile(f.file, f.attachment ? new wasm.AttachmentStream(f.attachment[1], f.attachment[0]) : undefined)), + settings?.replyTo, + settings?.attachments.map(f => new wasm.AttachmentFile(f.file, f.attachment ? new wasm.AttachmentStream(f.attachment[1], f.attachment[0]) : undefined)), message ) .then(res => { // message_sent event gets fired AFTER this returns ConversationStore.addPendingMessages(conversation_id, res.get_message_id(), message) - this.createFileAttachHandler(conversation_id, res) + createFileAttachHandler(res, (file, updater) => ConversationStore.updatePendingMessages(conversation_id, res.get_message_id(), file, updater)) return res }) return { @@ -342,7 +342,7 @@ class RaygunStore { } } return { - message: await raygun.send(conversation_id, message).then(messageId => { + message: await (settings?.replyTo ? raygun.reply(conversation_id, settings.replyTo, message) : raygun.send(conversation_id, message)).then(messageId => { // message_sent event gets fired BEFORE this returns // So to // 1. unify this system @@ -361,14 +361,14 @@ class RaygunStore { async downloadAttachment(conversation_id: string, message_id: string, file: string, size?: number) { return await this.get(async r => { let result = await r.download_stream(conversation_id, message_id, file) - return this.createFileDownloadHandler(file, result, size) + return createFileDownloadHandler(file, result, size) }, `Error downloading attachment from ${conversation_id} for message ${message_id}`) } async getAttachmentRaw(conversation_id: string, message_id: string, file: string, options?: { size?: number; type?: string }) { return await this.get(async r => { let result = await r.download_stream(conversation_id, message_id, file) - return this.createFileDownloadHandlerRaw(file, result, options) + return createFileDownloadHandlerRaw(file, result, options) }, `Error downloading attachment from ${conversation_id} for message ${message_id}`) } @@ -385,28 +385,18 @@ class RaygunStore { async reply(conversation_id: string, message_id: string, message: string[], attachments?: FileAttachment[]): Promise> { return await this.get(async r => { - if (attachments && attachments.length > 0) { - let result = await r.attach( - conversation_id, - message_id, - attachments.map(f => new wasm.AttachmentFile(f.file, f.attachment ? new wasm.AttachmentStream(f.attachment[1], f.attachment[0]) : undefined)), - message - ) - return { - message: result.get_message_id(), - progress: result, - } - } - return { - message: await r.reply(conversation_id, message_id, message), - } - }, "Error replying to message") + return await this.sendTo(r, conversation_id, message, { attachments, replyTo: message_id }) + }, "Error sending message") } async sendEvent(conversation_id: string, event: wasm.MessageEvent) { return await this.get(r => r.send_event(conversation_id, event), `Error sending event ${event}`) } + async cancelEvent(conversation_id: string, event: wasm.MessageEvent) { + return await this.get(r => r.cancel_event(conversation_id, event), `Error cancelling event ${event}`) + } + private async handleRaygunEvent(raygun: wasm.RayGunBox) { let events: wasm.AsyncIterator | undefined while (!events) { @@ -730,7 +720,7 @@ class RaygunStore { }) } - protected async getMessages(raygun: wasm.RayGunBox, conversation_id: string, options: MessageOptions) { + private async getMessages(raygun: wasm.RayGunBox, conversation_id: string, options: MessageOptions) { let msgs = await raygun.get_messages(conversation_id, options) let messages: Message[] = [] if (msgs.variant() === wasm.MessagesEnum.List) { @@ -740,109 +730,6 @@ class RaygunStore { return messages } - protected async createFileDownloadHandlerRaw(name: string, it: wasm.AsyncIterator, options?: { size?: number; type?: string }): Promise { - let listener = { - [Symbol.asyncIterator]() { - return it - }, - } - let data: any[] = [] - try { - for await (const value of listener) { - data = [...data, ...value] - } - } catch (_) {} - return new File([new Uint8Array(data)], name, { type: options?.type }) - } - - protected async createFileDownloadHandler(name: string, it: wasm.AsyncIterator, size?: number) { - let blob = await this.createFileDownloadHandlerRaw(name, it, { size }) - const elem = window.document.createElement("a") - elem.href = window.URL.createObjectURL(blob) - elem.download = name - document.body.appendChild(elem) - elem.click() - document.body.removeChild(elem) - } - - /** - * Create a handler for attachment results that uploads the file to chat and updates pending message attachments - * TODO: verify it works as we dont have a way to upload files yet - */ - protected async createFileAttachHandler(conversationId: string, upload: wasm.AttachmentResult) { - let listener = { - [Symbol.asyncIterator]() { - return upload - }, - } - let cancelled = false - try { - for await (const value of listener) { - let event = parseJSValue(value) - log.info(`Handling file progress event: ${JSON.stringify(event)}`) - switch (event.type) { - case "AttachedProgress": { - let locationKind = parseJSValue(event.values[0]) - // Only streams need progress update - if (locationKind.type === "Stream") { - let progress = parseJSValue(event.values[1]) - let file = progress.values["name"] - ConversationStore.updatePendingMessages(conversationId, upload.get_message_id(), file, current => { - if (current) { - let copy = { ...current } - switch (progress.type) { - case "CurrentProgress": { - copy.size = progress.values["current"] - copy.total = progress.values["total"] - break - } - case "ProgressComplete": { - copy.size = progress.values["total"] - copy.total = progress.values["total"] - copy.done = true - break - } - case "ProgressFailed": { - copy.size = progress.values["last_size"] - copy.error = `Error: ${progress.values["error"]}` - break - } - } - return copy - } else if (progress.type === "CurrentProgress") { - return { - name: file, - size: progress.values["current"], - total: progress.values["total"], - cancellation: { - cancel: () => { - cancelled = true - }, - }, - } - } - return undefined - }) - } - break - } - case "Pending": { - if (Object.keys(event.values).length > 0) { - let res = parseJSValue(event.values) - if (res.type === "Err") { - log.error(`Error uploading file ${res.values}`) - } - } - break - } - } - if (cancelled) break - } - } catch (e) { - if (!`${e}`.includes(`Error: returned None`)) throw e - } - } - /** * Convenient helper method to get data from raygun */ @@ -861,7 +748,7 @@ class RaygunStore { /** * Converts warp message to ui message */ - protected async convertWarpMessage(conversation_id: string, message: wasm.Message | undefined): Promise { + private async convertWarpMessage(conversation_id: string, message: wasm.Message | undefined): Promise { if (!message) return null let user = get(Store.state.user) let remote = message.sender() !== user.key @@ -889,39 +776,16 @@ class RaygunStore { text: message.lines(), inReplyTo: message.replied() ? ConversationStore.getMessage(conversation_id, message.replied()!) : null, reactions: reactions, - attachments: attachments.map(f => this.convertWarpAttachment(f)), + attachments: attachments.map(f => convertWarpAttachment(f)), pinned: message.pinned(), type: messageTypeFromTexts(message.lines()), } } - protected convertWarpAttachment(attachment: wasm.File): Attachment { - let kind: MessageAttachmentKind = MessageAttachmentKind.File - let type = attachment.file_type() - let mime = "application/octet-stream" - if (type !== "Generic") { - mime = type.Mime - } - if (mime.startsWith("image")) { - kind = MessageAttachmentKind.Image - } else if (mime.startsWith("video")) { - kind = MessageAttachmentKind.Video - } - let thumbnail = attachment.thumbnail() - let location = thumbnail.length > 0 ? imageFromData(thumbnail, type) : "" - return { - kind: kind, - name: attachment.name(), - size: attachment.size(), - location: location, - mime: mime, - } - } - /** * Converts warp message to ui message */ - protected async convertWarpConversation(chat: wasm.Conversation, raygun: wasm.RayGunBox): Promise { + private async convertWarpConversation(chat: wasm.Conversation, raygun: wasm.RayGunBox): Promise { let direct = chat.conversation_type() === wasm.ConversationType.Direct let msg = await this.getMessages(raygun, chat.id(), new MessageOptions()) chat.recipients().forEach(async recipient => { @@ -954,6 +818,133 @@ class RaygunStore { } } +export async function createFileDownloadHandlerRaw(name: string, it: wasm.AsyncIterator, options?: { size?: number; type?: string }): Promise { + let listener = { + [Symbol.asyncIterator]() { + return it + }, + } + let data: any[] = [] + try { + for await (const value of listener) { + data = [...data, ...value] + } + } catch (_) {} + return new File([new Uint8Array(data)], name, { type: options?.type }) +} + +export async function createFileDownloadHandler(name: string, it: wasm.AsyncIterator, size?: number) { + let blob = await createFileDownloadHandlerRaw(name, it, { size }) + const elem = window.document.createElement("a") + elem.href = window.URL.createObjectURL(blob) + elem.download = name + document.body.appendChild(elem) + elem.click() + document.body.removeChild(elem) +} + +/** + * Create a handler for attachment results that uploads the file to chat and updates pending message attachments + * TODO: verify it works as we dont have a way to upload files yet + */ +export type ProgressHandler = (progress: FileProgress | undefined) => FileProgress | undefined +export async function createFileAttachHandler(upload: wasm.AttachmentResult, updater: (name: string, handler: ProgressHandler) => void) { + let listener = { + [Symbol.asyncIterator]() { + return upload + }, + } + let cancelled = false + try { + for await (const value of listener) { + let event = parseJSValue(value) + log.info(`Handling file progress event: ${JSON.stringify(event)}`) + switch (event.type) { + case "AttachedProgress": { + let locationKind = parseJSValue(event.values[0]) + // Only streams need progress update + if (locationKind.type === "Stream") { + let progress = parseJSValue(event.values[1]) + let file = progress.values["name"] + updater(file, current => { + if (current) { + let copy = { ...current } + switch (progress.type) { + case "CurrentProgress": { + copy.size = progress.values["current"] + copy.total = progress.values["total"] + break + } + case "ProgressComplete": { + copy.size = progress.values["total"] + copy.total = progress.values["total"] + copy.done = true + break + } + case "ProgressFailed": { + copy.size = progress.values["last_size"] + copy.error = `Error: ${progress.values["error"]}` + break + } + } + return copy + } else if (progress.type === "CurrentProgress") { + return { + name: file, + size: progress.values["current"], + total: progress.values["total"], + cancellation: { + cancel: () => { + cancelled = true + }, + }, + } + } + return undefined + }) + } + break + } + case "Pending": { + if (Object.keys(event.values).length > 0) { + let res = parseJSValue(event.values) + if (res.type === "Err") { + log.error(`Error uploading file ${res.values}`) + } + } + break + } + } + if (cancelled) break + } + } catch (e) { + if (!`${e}`.includes(`Error: returned None`)) throw e + } +} + +export function convertWarpAttachment(attachment: wasm.File): Attachment { + let kind: MessageAttachmentKind = MessageAttachmentKind.File + let type = attachment.file_type() + let mime = "application/octet-stream" + if (type !== "Generic") { + mime = type.Mime + } + if (mime.startsWith("image")) { + kind = MessageAttachmentKind.Image + } else if (mime.startsWith("video")) { + kind = MessageAttachmentKind.Video + } + let thumbnail = attachment.thumbnail() + let location = thumbnail.length > 0 ? imageFromData(thumbnail, type) : "" + return { + kind: kind, + name: attachment.name(), + size: attachment.size(), + location: location, + mime: mime, + } +} + export type GroupPermission = wasm.GroupPermission export const GroupPermission = wasm.GroupPermission export const RaygunStoreInstance = new RaygunStore(WarpStore.warp.raygun)