feat(tailshare): download incoming taildrop files
This commit is contained in:
@@ -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<string, IPNWaitingFile>
|
||||
ipn: IPN
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<Title order={2}>Taildrop received files</Title>
|
||||
<Stack>
|
||||
{Object.values(waitingFiles).map((file) => (
|
||||
<Group key={file.name}>
|
||||
<Text>{file.name}</Text>
|
||||
<Space flex={1} />
|
||||
<Text>{fmtSize(file.size)}</Text>
|
||||
<ActionIcon
|
||||
onClick={() => {
|
||||
ipn.openWaitingFile(file.name).then((data) => {
|
||||
download(data, file.name)
|
||||
})
|
||||
}}
|
||||
>
|
||||
<DownloadIcon />
|
||||
</ActionIcon>
|
||||
<ActionIcon bg="red" onClick={() => ipn.deleteWaitingFile(file.name)}>
|
||||
<TrashIcon />
|
||||
</ActionIcon>
|
||||
</Group>
|
||||
))}
|
||||
</Stack>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default function TailscaleRoute() {
|
||||
const waitingFiles = useIpnSelector(getWaitingFiles)
|
||||
const ipn = use(IpnContext)
|
||||
|
||||
return (
|
||||
@@ -307,6 +349,12 @@ export default function TailscaleRoute() {
|
||||
{!ipn ? <ConnectTailscale /> : <TailscaleState />}
|
||||
<Divider m={"md"} />
|
||||
<TailscaleConfig />
|
||||
{!!Object.keys(waitingFiles).length && !!ipn && (
|
||||
<>
|
||||
<Divider m={"md"} />
|
||||
<TailscaleWaitingFiles waitingFiles={waitingFiles} ipn={ipn} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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<ArrayBuffer>])
|
||||
} else {
|
||||
const next = new Uint8Array<ArrayBuffer>(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)
|
||||
}
|
||||
Reference in New Issue
Block a user