Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(chat): implement file sharing and downloading functionality for files in chats #959

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/lib/components/messaging/embeds/FileEmbed.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import prettyBytes from "pretty-bytes"
import { createEventDispatcher } from "svelte"
import { _ } from "svelte-i18n"
import { Store } from "$lib/state/Store"
import { ToastMessage } from "$lib/state/ui/toast"

export let altBackgroundColor: boolean = false

Expand Down
45 changes: 45 additions & 0 deletions src/lib/utils/Functions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { Color, Format } from "$lib/enums"
import { Store } from "$lib/state/Store"
import { ToastMessage } from "$lib/state/ui/toast"
import { Filesystem, Encoding } from "@capacitor/filesystem"
import { Directory as LocalDirectory } from "@capacitor/filesystem"
import { Share } from "@capacitor/share"
import TimeAgo from "javascript-time-ago"
import { log } from "./Logger"
import { _ } from "svelte-i18n"
import { get } from "svelte/store"

export const debounce = (fn: Function, ms = 300) => {
let timeoutId: ReturnType<typeof setTimeout>
Expand Down Expand Up @@ -66,3 +74,40 @@ export function formatStyledText(text: string): string {

return formattedText
}

export async function shareFile(fileName: string, combinedArray: Buffer) {
try {
const base64Data = combinedArray.toString("base64")

const filePath = await Filesystem.writeFile({
path: fileName,
data: base64Data!,
directory: LocalDirectory.Cache,
})

await Share.share({
text: fileName,
url: filePath.uri,
})

log.info(`File shared: ${fileName} successfully`)
} catch (error) {
let errorMessage = `${error}`
log.error("Error when to share file:", fileName, "Error:", errorMessage)
if (errorMessage.includes("Share canceled")) {
Store.addToastNotification(new ToastMessage("", get(_)("files.shareFileCanceled"), 2))
return
}
}
}

export async function downloadFileFromWeb(data: any[], size: number, name: string) {
let options: { size?: number; type?: string } = { size }
let blob = new File([new Uint8Array(data)], name, { type: options?.type })
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)
}
25 changes: 13 additions & 12 deletions src/lib/wasm/RaygunStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ 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"
import { isAndroidOriOS } from "$lib/utils/Mobile"
import { downloadFileFromWeb, shareFile } from "$lib/utils/Functions"

const MAX_PINNED_MESSAGES = 100
export type FetchMessagesConfig =
Expand Down Expand Up @@ -363,7 +364,12 @@ 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 createFileDownloadHandler(file, result, size)
let data = await createFileDownloadHandler(file, result, size)
if (isAndroidOriOS()) {
await shareFile(file, Buffer.from(data))
} else {
await downloadFileFromWeb(data, size || 0, file)
}
}, `Error downloading attachment from ${conversation_id} for message ${message_id}`)
}

Expand Down Expand Up @@ -820,7 +826,7 @@ class RaygunStore {
}
}

export async function createFileDownloadHandlerRaw(name: string, it: wasm.AsyncIterator, options?: { size?: number; type?: string }): Promise<Blob> {
export async function createFileDownloadHandlerRaw(name: string, it: wasm.AsyncIterator, options?: { size?: number; type?: string }): Promise<any[]> {
let listener = {
[Symbol.asyncIterator]() {
return it
Expand All @@ -832,17 +838,12 @@ export async function createFileDownloadHandlerRaw(name: string, it: wasm.AsyncI
data = [...data, ...value]
}
} catch (_) {}
return new File([new Uint8Array(data)], name, { type: options?.type })
return data
}

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)
export async function createFileDownloadHandler(name: string, it: wasm.AsyncIterator, size?: number): Promise<any[]> {
let data = await createFileDownloadHandlerRaw(name, it, { size })
return data
}

/**
Expand Down
34 changes: 3 additions & 31 deletions src/routes/files/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@
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"
import { writable, readable } from "svelte/store"
import type { FileInfo } from "$lib/types"
import { writable } from "svelte/store"
import { UIStore } from "$lib/state/ui"
import FolderItem from "./FolderItem.svelte"
import { v4 as uuidv4 } from "uuid"
Expand All @@ -25,10 +25,8 @@
import { Store } from "$lib/state/Store"
import path from "path"
import { MultipassStoreInstance } from "$lib/wasm/MultipassStore"
import { Share } from "@capacitor/share"
import { isAndroidOriOS } from "$lib/utils/Mobile"
import { Filesystem, Directory, Encoding } from "@capacitor/filesystem"
import { log } from "$lib/utils/Logger"
import { shareFile } from "$lib/utils/Functions"

export let browseFilesForChatMode: boolean = false

Expand Down Expand Up @@ -478,32 +476,6 @@
)
}

async function shareFile(fileName: string, combinedArray: Buffer) {
try {
const base64Data = combinedArray.toString("base64")

const filePath = await Filesystem.writeFile({
path: fileName,
data: base64Data!,
directory: Directory.Cache,
})

await Share.share({
text: fileName,
url: filePath.uri,
})

log.info(`File shared: ${fileName} successfully`)
} catch (error) {
let errorMessage = `${error}`
log.error("Error when to share file:", fileName, "Error:", errorMessage)
if (errorMessage.includes("Share canceled")) {
Store.addToastNotification(new ToastMessage("", $_("files.shareFileCanceled"), 2))
return
}
}
}

$: chats = UIStore.state.chats
$: activeChat = Store.state.activeChat

Expand Down
Loading