select_chat(chat, undefined)}>
- {#if chat.users.length === 2}
-
0}
- image={$userCache[chat.users[0]].profile.photo.image}
- status={$userCache[chat.users[0]].profile.status}
- size={Size.Medium}
- loading={loading}
- id={$userCache[chat.users[0]].key}
- frame={$userCache[chat.users[0]].profile.photo.frame} />
- {:else}
- $userCache[u])} />
- {/if}
+
{#if name.startsWith(filter.toLocaleLowerCase())}
diff --git a/src/lib/components/ui/ChatIcon.svelte b/src/lib/components/ui/ChatIcon.svelte
new file mode 100644
index 000000000..dc89537ad
--- /dev/null
+++ b/src/lib/components/ui/ChatIcon.svelte
@@ -0,0 +1,32 @@
+
+
+{#if chat.icon}
+ 0} image={chat.icon} size={Size.Medium} loading={loading} />
+{:else if chat.kind === ChatType.DirectMessage}
+ 0}
+ id={$users[chat.users[1]]?.key}
+ image={$users[chat.users[1]]?.profile.photo.image}
+ frame={$users[chat.users[1]]?.profile.photo.frame}
+ status={$users[chat.users[1]]?.profile.status}
+ size={size}
+ noIndicator={noIndicator}
+ loading={loading} />
+{:else}
+
+{/if}
diff --git a/src/lib/lang/en.json b/src/lib/lang/en.json
index f99a4c769..0ce862b24 100644
--- a/src/lib/lang/en.json
+++ b/src/lib/lang/en.json
@@ -110,6 +110,8 @@
"description.placeholder": "Group description goes here",
"add": "Add Members",
"add.description": "Allows user to add members to the group",
+ "remove": "Remove Members",
+ "remove.description": "Allows user to add remove from the group",
"edit": "Edit Members",
"details": "Change Details",
"details.description": "Allows user to change the name and MOTD of the group",
diff --git a/src/lib/layouts/Slimbar.svelte b/src/lib/layouts/Slimbar.svelte
index 1243d9ad5..d545f51c3 100644
--- a/src/lib/layouts/Slimbar.svelte
+++ b/src/lib/layouts/Slimbar.svelte
@@ -8,7 +8,7 @@
import { slide } from "svelte/transition"
import { animationDuration } from "$lib/globals/animations"
import { Store } from "$lib/state/Store"
- import { ProfilePicture, ProfilePictureMany } from "$lib/components"
+ import { ChatIcon, ProfilePicture, ProfilePictureMany } from "$lib/components"
import { Label } from "$lib/elements"
import { goto } from "$app/navigation"
import CommunityIcon from "$lib/components/community/icon/CommunityIcon.svelte"
@@ -66,11 +66,7 @@
}
goto(Route.Chat)
}}>
- {#if favorite.kind === ChatType.DirectMessage}
- 0} image={resolved[1]?.profile.photo.image} status={resolved[1].profile.status} size={Size.Medium} />
- {:else}
-
- {/if}
+
{/each}
diff --git a/src/lib/types/index.ts b/src/lib/types/index.ts
index 70eb1b44f..59a3de59c 100644
--- a/src/lib/types/index.ts
+++ b/src/lib/types/index.ts
@@ -176,6 +176,8 @@ export type Chat = {
last_message_id: string
last_message_at: Date
last_message_preview: string
+ icon?: string
+ banner?: string
}
const typingDuration = 5
diff --git a/src/lib/wasm/ConstellationStore.ts b/src/lib/wasm/ConstellationStore.ts
index ef726db25..5862c969e 100644
--- a/src/lib/wasm/ConstellationStore.ts
+++ b/src/lib/wasm/ConstellationStore.ts
@@ -319,9 +319,17 @@ class ConstellationStore {
}
}
-export function imageFromData(data: any[], prefix: string, kind: string) {
- let hrefData = btoa(new Uint8Array(data).reduce((data, byte) => data + String.fromCharCode(byte), ""))
- return `data:${prefix}/${kind};base64, ${hrefData}`
+export function imageFromData(data: Uint8Array, file_type: wasm.FileType) {
+ let mime = "application/octet-stream"
+ if (file_type !== "Generic") {
+ mime = file_type.Mime
+ }
+ return imageFromMime(data, mime)
+}
+
+export function imageFromMime(data: Uint8Array, mime: string) {
+ let hrefData = btoa(data.reduce((data, byte) => data + String.fromCharCode(byte), ""))
+ return `data:${mime};base64, ${hrefData}`
}
export const ConstellationStoreInstance = new ConstellationStore(WarpStore.warp.constellation)
diff --git a/src/lib/wasm/MultipassStore.ts b/src/lib/wasm/MultipassStore.ts
index c78399da1..3e5567060 100644
--- a/src/lib/wasm/MultipassStore.ts
+++ b/src/lib/wasm/MultipassStore.ts
@@ -190,7 +190,7 @@ class MultipassStore {
if (multipass) {
try {
- let outgoingFriendRequests: Array
= await multipass.list_outgoing_request()
+ let outgoingFriendRequests = await multipass.list_outgoing_request()
let outgoingFriendRequestsUsers: Array = []
for (let i = 0; i < outgoingFriendRequests.length; i++) {
let friendUser = await this.identity_from_did(outgoingFriendRequests[i].identity)
@@ -239,12 +239,12 @@ class MultipassStore {
) {
return failure(handleErrors("Invalid identity"))
}
- let identity: any[] = await multipass.get_identity(wasm.Identifier.Username, friend) // This is empty if it was never resolved
+ let identity: wasm.Identity[] = await multipass.get_identity({ Username: friend }) // This is empty if it was never resolved
// It should only find 1 matching identity
if (identity.length != 1) {
return failure(handleErrors("Invalid identity"))
}
- did = identity[0].did_key
+ did = identity[0].did_key()
}
return success(await multipass.send_request(did))
} catch (error) {
@@ -321,7 +321,7 @@ class MultipassStore {
if (multipass) {
try {
- let incomingFriendRequests: Array = await multipass.list_incoming_request()
+ let incomingFriendRequests = await multipass.list_incoming_request()
let incomingFriendRequestsUsers: Array = []
for (let i = 0; i < incomingFriendRequests.length; i++) {
let friendUser = await this.identity_from_did(incomingFriendRequests[i].identity)
@@ -358,7 +358,7 @@ class MultipassStore {
if (multipass) {
try {
- let blockedUsersAny: Array = await multipass.block_list()
+ let blockedUsersAny = await multipass.block_list()
let blockedUsers: Array = []
for (let i = 0; i < blockedUsersAny.length; i++) {
let friendUser = await this.identity_from_did(blockedUsersAny[i])
@@ -386,10 +386,10 @@ class MultipassStore {
if (multipass) {
try {
- let friendsAny: Array = await multipass.list_friends()
+ let friends = await multipass.list_friends()
let friendsUsers: Array = []
- for (let i = 0; i < friendsAny.length; i++) {
- let friendUser = await this.identity_from_did(friendsAny[i])
+ for (let i = 0; i < friends.length; i++) {
+ let friendUser = await this.identity_from_did(friends[i])
if (friendUser) {
friendsUsers.push(friendUser.key)
Store.updateUser(friendUser)
@@ -514,7 +514,7 @@ class MultipassStore {
const multipass = get(this.multipassWritable)
if (multipass) {
- await multipass.update_identity(wasm.IdentityUpdate.Username, new_username)
+ await multipass.update_identity({ Username: new_username })
await this._updateIdentity()
}
}
@@ -527,7 +527,7 @@ class MultipassStore {
const multipass = get(this.multipassWritable)
if (multipass) {
- await multipass.update_identity(wasm.IdentityUpdate.StatusMessage, newStatusMessage)
+ await multipass.update_identity({ StatusMessage: newStatusMessage })
await this._updateIdentity()
}
}
@@ -541,7 +541,7 @@ class MultipassStore {
const multipass = get(this.multipassWritable)
if (multipass) {
- await multipass.update_identity(wasm.IdentityUpdate.AddMetadataKey, [key, value])
+ await multipass.update_identity({ AddMetadataKey: { key, value } })
await this._updateIdentity()
}
}
@@ -554,7 +554,7 @@ class MultipassStore {
const multipass = get(this.multipassWritable)
if (multipass) {
- await multipass.update_identity(wasm.IdentityUpdate.RemoveMetadataKey, key)
+ await multipass.update_identity({ RemoveMetadataKey: key })
await this._updateIdentity()
}
}
@@ -568,8 +568,7 @@ class MultipassStore {
if (multipass) {
const buffer = Buffer.from(newPictureBase64, "base64")
- let pictureAsBytes = new Uint8Array(buffer)
- await multipass.update_identity(wasm.IdentityUpdate.Picture, pictureAsBytes)
+ await multipass.update_identity({ Picture: Array.from(buffer) })
await this._updateIdentity()
}
}
@@ -583,8 +582,7 @@ class MultipassStore {
if (multipass) {
const buffer = Buffer.from(newPictureBase64, "base64")
- let pictureAsBytes = new Uint8Array(buffer)
- await multipass.update_identity(wasm.IdentityUpdate.Banner, pictureAsBytes)
+ await multipass.update_identity({ Banner: Array.from(buffer) })
await this._updateIdentity()
}
}
@@ -628,14 +626,14 @@ class MultipassStore {
if (multipass) {
for (let tries = 0; tries < maxRetries; tries++) {
try {
- let identity = (await multipass.get_identity(wasm.Identifier.DID, id))[0]
+ let identity = (await multipass.get_identity({ DID: id }))[0]
let profilePicture = await this.getUserProfilePicture(id)
let bannerPicture = await this.getUserBannerPicture(id)
let status = await this.getUserStatus(id)
return {
...defaultUser,
- key: identity === undefined ? id : identity.did_key,
- name: identity === undefined ? id : identity.username,
+ key: identity === undefined ? id : identity.did_key(),
+ name: identity === undefined ? id : identity.username(),
profile: {
...defaultProfileData,
photo: {
@@ -650,9 +648,9 @@ class MultipassStore {
overlay: "",
},
status: status,
- status_message: identity === undefined ? "" : (identity.status_message ?? ""),
+ status_message: identity === undefined ? "" : (identity.status_message() ?? ""),
},
- integrations: identity === undefined ? new Map() : identity.metadata,
+ integrations: identity === undefined ? new Map() : identity.metadata(),
media: {
is_playing_audio: false,
is_streaming_video: false,
diff --git a/src/lib/wasm/RaygunStore.ts b/src/lib/wasm/RaygunStore.ts
index 506c45a92..895cb0a09 100644
--- a/src/lib/wasm/RaygunStore.ts
+++ b/src/lib/wasm/RaygunStore.ts
@@ -5,20 +5,21 @@ import { Store } from "../state/Store"
import { UIStore } from "../state/ui"
import { ConversationStore } from "../state/conversation"
import { MessageOptions } from "warp-wasm"
-import { ChatType, MessageAttachmentKind, Route } from "$lib/enums"
-import { type User, type Chat, defaultChat, type Message, mentions_user, type Attachment, messageTypeFromTexts } from "$lib/types"
+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 { WarpError, handleErrors } from "./HandleWarpErrors"
import { failure, success, type Result } from "$lib/utils/Result"
import { create_cancellable_handler, type Cancellable } from "$lib/utils/CancellablePromise"
import { parseJSValue } from "./EnumParser"
import { MultipassStoreInstance } from "./MultipassStore"
import { log } from "$lib/utils/Logger"
-import { imageFromData } from "./ConstellationStore"
+import { imageFromData, imageFromMime } from "./ConstellationStore"
import { Sounds } from "$lib/components/utils/SoundHandler"
import { SettingsStore } from "$lib/state"
import { ToastMessage } from "$lib/state/ui/toast"
import { page } from "$app/stores"
import { goto } from "$app/navigation"
+import { Readable } from "stream"
const MAX_PINNED_MESSAGES = 100
export type FetchMessagesConfig =
@@ -84,11 +85,6 @@ export type FileAttachment = {
attachment?: [ReadableStream, number]
}
-type Range = {
- start: any
- end: any
-}
-
class RaygunStore {
private raygunWritable: Writable
// A map of message listeners
@@ -204,6 +200,22 @@ class RaygunStore {
return await this.get(r => r.set_conversation_description(conversation_id, description), "Error updating conversation settings")
}
+ async updateConversationPicture(conversation_id: string, picture: string, banner?: boolean) {
+ return await this.get(r => {
+ const data = picture.startsWith("data:") ? picture.split(",")[1] : picture
+ const buffer = Buffer.from(data, "base64")
+ const len = buffer.length
+ const stream = new ReadableStream({
+ start(controller) {
+ controller.enqueue(buffer)
+ controller.close()
+ },
+ })
+ let file = new wasm.AttachmentFile("", new wasm.AttachmentStream(len, stream))
+ banner ? r.update_conversation_banner(conversation_id, file) : r.update_conversation_icon(conversation_id, file)
+ }, "Error updating conversation icon")
+ }
+
/**
* Deletes a message for the given chat. If no message id provided will delete the chat
*/
@@ -237,41 +249,29 @@ class RaygunStore {
}, `Error fetching conversations`)
}
- async fetchMessages(conversation_id: string, config: FetchMessagesConfig & { [type: string]: any }): Promise> {
+ async fetchMessages(conversation_id: string, config: FetchMessagesConfig): Promise> {
return this.get(async r => {
let message_options = new MessageOptions()
switch (config.type) {
case "Between": {
- let range: Range = {
- start: config.from,
- end: config.to,
- }
- message_options.set_date_range(range) //TODO verify that js Date can be parsed to rust DateTime::
+ 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_message_count(conversation_id)
- let range: Range = {
- start: Math.min(0, total_messages - config.amount),
- end: total_messages,
- }
- message_options.set_range(range)
+ message_options.set_range(Math.min(0, total_messages - config.amount), total_messages)
+ break
}
case "Earlier": {
- let range: Range = {
- start: new Date(),
- end: config.start_date,
- }
- message_options.set_date_range(range)
+ message_options.set_date_range(new Date(), config.start_date)
message_options.set_reverse()
message_options.set_limit(config.limit)
+ break
}
case "Later": {
- let range: Range = {
- start: config.start_date,
- end: new Date(),
- }
- message_options.set_date_range(range)
+ message_options.set_date_range(config.start_date, new Date())
message_options.set_limit(config.limit)
+ break
}
}
@@ -279,7 +279,7 @@ class RaygunStore {
if (config.type === "Earlier") {
messages = messages.reverse()
}
- let has_more = messages.length >= config.get_limit()
+ let has_more = "limit" in config ? messages.length >= config.limit : false
let opt = new MessageOptions()
opt.set_limit(1)
@@ -664,6 +664,23 @@ class RaygunStore {
})
break
}
+ case "conversation_updated_icon": {
+ let conversation_id: string = event.values["conversation_id"]
+ let icon = await raygun.conversation_icon(conversation_id)
+ console.log("type ", icon.image_type(), " data ", btoa(icon.data().reduce((data, byte) => data + String.fromCharCode(byte), "")))
+ UIStore.mutateChat(conversation_id, c => {
+ c.icon = imageFromMime(icon.data(), "image/png")
+ })
+ break
+ }
+ case "conversation_updated_banner": {
+ let conversation_id: string = event.values["conversation_id"]
+ let icon = await raygun.conversation_banner(conversation_id)
+ UIStore.mutateChat(conversation_id, c => {
+ c.banner = imageFromData(icon.data(), icon.image_type())
+ })
+ break
+ }
default: {
log.error(`Unhandled message event: ${JSON.stringify(event)}`)
break
@@ -677,7 +694,7 @@ class RaygunStore {
let msgs = await raygun.get_messages(conversation_id, options)
let messages: Message[] = []
if (msgs.variant() === wasm.MessagesEnum.List) {
- let warpMsgs = (msgs.value() as any[]).map(v => wasm.message_from({ ...Object.fromEntries(v) }))
+ let warpMsgs = msgs.messages()!
messages = (await Promise.all(warpMsgs.map(async msg => await this.convertWarpMessage(conversation_id, msg)))).filter((m: Message | null): m is Message => m !== null)
}
return messages
@@ -812,7 +829,16 @@ class RaygunStore {
let sender = await MultipassStoreInstance.identity_from_did(message.sender())
if (sender) Store.updateUser(sender)
}
- let attachments: any[] = message.attachments()
+ 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: {
@@ -822,31 +848,31 @@ class RaygunStore {
},
text: message.lines(),
inReplyTo: message.replied() ? ConversationStore.getMessage(conversation_id, message.replied()!) : null,
- reactions: message.reactions(),
+ reactions: reactions,
attachments: attachments.map(f => this.convertWarpAttachment(f)),
pinned: message.pinned(),
type: messageTypeFromTexts(message.lines()),
}
}
- private convertWarpAttachment(attachment: any): Attachment {
+ private convertWarpAttachment(attachment: wasm.File): Attachment {
let kind: MessageAttachmentKind = MessageAttachmentKind.File
- let type = parseJSValue(attachment.file_type)
+ let type = attachment.file_type()
let mime = "application/octet-stream"
- if (type.type === "mime") {
- mime = type.values as any as string
+ 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(attachment.thumbnail, "image", mime) : ""
+ let thumbnail = attachment.thumbnail()
+ let location = thumbnail.length > 0 ? imageFromData(thumbnail, type) : ""
return {
kind: kind,
- name: attachment.name,
- size: attachment.size,
+ name: attachment.name(),
+ size: attachment.size(),
location: location,
mime: mime,
}
diff --git a/src/routes/chat/+page.svelte b/src/routes/chat/+page.svelte
index 25928f755..5fe39842a 100644
--- a/src/routes/chat/+page.svelte
+++ b/src/routes/chat/+page.svelte
@@ -5,7 +5,22 @@
import { animationDuration } from "$lib/globals/animations"
import { slide } from "svelte/transition"
import { Chatbar, Sidebar, Topbar, Profile } from "$lib/layouts"
- import { ImageEmbed, ChatPreview, Conversation, Message as MessageComponent, MessageGroup, MessageReactions, MessageReplyContainer, ProfilePicture, Modal, ProfilePictureMany, ChatFilter, ContextMenu, EmojiGroup } from "$lib/components"
+ import {
+ ImageEmbed,
+ ChatPreview,
+ Conversation,
+ Message as MessageComponent,
+ MessageGroup,
+ MessageReactions,
+ MessageReplyContainer,
+ ProfilePicture,
+ Modal,
+ ProfilePictureMany,
+ ChatFilter,
+ ContextMenu,
+ EmojiGroup,
+ ChatIcon,
+ } from "$lib/components"
import CreateTransaction from "$lib/components/wallet/CreateTransaction.svelte"
import { Button, FileInput, Icon, Label, Text } from "$lib/elements"
import CallScreen from "$lib/components/calling/CallScreen.svelte"
@@ -637,19 +652,7 @@
{#if $activeChat.users.length > 0}
- {#if $activeChat.kind === ChatType.DirectMessage}
-
0}
- id={$users[$activeChat.users[1]]?.key}
- image={$users[$activeChat.users[1]]?.profile.photo.image}
- frame={$users[$activeChat.users[1]]?.profile.photo.frame}
- status={$users[$activeChat.users[1]]?.profile.status}
- size={Size.Medium}
- loading={loading} />
- {:else}
- (showUsers = true)} />
- {/if}
+ (showUsers = true)} />
{/if}
diff --git a/src/routes/files/+page.svelte b/src/routes/files/+page.svelte
index bc64e5e17..1ac9eb908 100644
--- a/src/routes/files/+page.svelte
+++ b/src/routes/files/+page.svelte
@@ -8,7 +8,7 @@
import Text from "$lib/elements/Text.svelte"
import Label from "$lib/elements/Label.svelte"
import prettyBytes from "pretty-bytes"
- import { ChatPreview, ImageEmbed, ImageFile, Modal, FileFolder, ProgressButton, ContextMenu, ChatFilter, ProfilePicture, ProfilePictureMany } from "$lib/components"
+ import { ChatPreview, ImageEmbed, ImageFile, Modal, FileFolder, ProgressButton, ContextMenu, ChatFilter, ProfilePicture, ProfilePictureMany, ChatIcon } from "$lib/components"
import Controls from "$lib/layouts/Controls.svelte"
import { onMount } from "svelte"
import type { FileInfo, User } from "$lib/types"
@@ -710,21 +710,13 @@
{:else if item.type === "folder"}
{#if item.chat}
- {#if item.chat.kind === ChatType.DirectMessage}
+ {#if item.chat.kind === ChatType.DirectMessage || item.chat.icon}
-
0}
- id={$users[item.chat.users[1]]?.key}
- image={$users[item.chat.users[1]]?.profile.photo.image}
- frame={$users[item.chat.users[1]]?.profile.photo.frame}
- size={Size.Smaller}
- noIndicator={true}
- loading={loading} />
+
{:else}
{/if}
{/if}
diff --git a/src/routes/files/BrowseFiles.svelte b/src/routes/files/BrowseFiles.svelte
index 37e59262f..77db2557a 100644
--- a/src/routes/files/BrowseFiles.svelte
+++ b/src/routes/files/BrowseFiles.svelte
@@ -4,7 +4,7 @@
import { Controls } from "$lib/layouts"
import { _ } from "svelte-i18n"
- import { ImageFile, FileFolder, ProfilePicture, ProfilePictureMany } from "$lib/components"
+ import { ImageFile, FileFolder, ChatIcon } from "$lib/components"
import { createEventDispatcher, onMount } from "svelte"
import type { FileInfo } from "$lib/types"
import { get, writable } from "svelte/store"
@@ -260,21 +260,13 @@
{:else if item.type === "folder"}
{#if item.chat}
- {#if item.chat.kind === ChatType.DirectMessage}
+ {#if item.chat.kind === ChatType.DirectMessage || item.chat.icon}
-
0}
- id={$users[item.chat.users[1]]?.key}
- image={$users[item.chat.users[1]]?.profile.photo.image}
- frame={$users[item.chat.users[1]]?.profile.photo.frame}
- size={Size.Smaller}
- noIndicator={true}
- loading={loading} />
+
{:else}
{/if}
{/if}