From ed9cd5ea3e99a28f0be0a445533e472fb3afb2dc Mon Sep 17 00:00:00 2001 From: Codinget Date: Thu, 11 Jun 2026 00:04:18 +0000 Subject: [PATCH] feat(tailshare): download incoming taildrop files --- packages/tailshare/src/pages/tailscale.tsx | 50 +++++++++++++++++++++- packages/tailshare/src/utils/download.ts | 20 +++++++++ 2 files changed, 69 insertions(+), 1 deletion(-) create mode 100644 packages/tailshare/src/utils/download.ts diff --git a/packages/tailshare/src/pages/tailscale.tsx b/packages/tailshare/src/pages/tailscale.tsx index 1b95868..edcbae0 100644 --- a/packages/tailshare/src/pages/tailscale.tsx +++ b/packages/tailshare/src/pages/tailscale.tsx @@ -11,6 +11,8 @@ import { Loader, Modal, NativeSelect, + Space, + Stack, Text, TextInput, Title, @@ -35,14 +37,18 @@ import { getLoginUrl, getPeers, getWaitingFileCount, + getWaitingFiles, } from "@webnet/tsconnect-redux" import { IpnContext, useIpnSelector } from "../contexts/IpnContext" import { use } from "react" import styles from "./tailscale.scss" -import { CopyIcon } from "@phosphor-icons/react" +import { CopyIcon, DownloadIcon, TrashIcon } from "@phosphor-icons/react" import { useDisclosure } from "@mantine/hooks" import clsx from "clsx" import { useLocalStorage } from "../hooks/useLocalStorage" +import type { IPN, IPNWaitingFile } from "@webnet/tsconnect" +import { fmtSize } from "../utils/format" +import { download } from "../utils/download" function ConnectTailscale() { const willBuild = useTailshareSelector(getIpnPrepareWillBuild) @@ -289,7 +295,43 @@ function TailscaleSelfNodeStatus() { ) } +function TailscaleWaitingFiles({ + waitingFiles, + ipn, +}: { + waitingFiles: Record + ipn: IPN +}) { + return ( + <> + Taildrop received files + + {Object.values(waitingFiles).map((file) => ( + + {file.name} + + {fmtSize(file.size)} + { + ipn.openWaitingFile(file.name).then((data) => { + download(data, file.name) + }) + }} + > + + + ipn.deleteWaitingFile(file.name)}> + + + + ))} + + + ) +} + export default function TailscaleRoute() { + const waitingFiles = useIpnSelector(getWaitingFiles) const ipn = use(IpnContext) return ( @@ -307,6 +349,12 @@ export default function TailscaleRoute() { {!ipn ? : } + {!!Object.keys(waitingFiles).length && !!ipn && ( + <> + + + + )} ) } diff --git a/packages/tailshare/src/utils/download.ts b/packages/tailshare/src/utils/download.ts new file mode 100644 index 0000000..2b4eab6 --- /dev/null +++ b/packages/tailshare/src/utils/download.ts @@ -0,0 +1,20 @@ +export function download(data: Uint8Array | Blob, name: string) { + if (data instanceof Uint8Array) { + if (data.buffer instanceof ArrayBuffer) { + data = new Blob([data as Uint8Array]) + } else { + const next = new Uint8Array(new ArrayBuffer(data.length)) + next.set(data) + data = new Blob([next]) + } + } + const a = document.createElement("a") + const url = URL.createObjectURL(data) + a.href = url + a.download = name + a.hidden = true + document.body.append(a) + a.click() + a.remove() + URL.revokeObjectURL(url) +}