diff --git a/frontend/src/components/DownloadCard.tsx b/frontend/src/components/DownloadCard.tsx index 723fa922..e9bae93b 100644 --- a/frontend/src/components/DownloadCard.tsx +++ b/frontend/src/components/DownloadCard.tsx @@ -24,6 +24,12 @@ import StopCircleIcon from '@mui/icons-material/StopCircle' import OpenInBrowserIcon from '@mui/icons-material/OpenInBrowser' import SaveAltIcon from '@mui/icons-material/SaveAlt' +import ShareIcon from '@mui/icons-material/Share' +import ContentCopyIcon from '@mui/icons-material/ContentCopy' +import { useState } from 'react' +import { Dialog, DialogTitle, DialogContent, DialogContentText, DialogActions } from '@mui/material' + + type Props = { download: RPCResult onStop: () => void @@ -55,97 +61,150 @@ const DownloadCard: React.FC = ({ download, onStop, onCopy }) => { window.open(`${serverAddr}/filebrowser/d/${encoded}?token=${localStorage.getItem('token')}`) } + const handleShare = () => { + const encoded = base64URLEncode(download.output.savedFilePath) + const link = `${serverAddr}/public/${encoded}` + setShareLink(link) + setShareOpen(true) + } + + const [shareOpen, setShareOpen] = useState(false) + const [shareLink, setShareLink] = useState('') + + return ( - - { - navigator.clipboard.writeText(download.info.url) - onCopy() - }}> - {download.info.thumbnail !== '' ? - : - - } - {download.progress.percentage ? - : - null - } - - {download.info.title !== '' ? - - {ellipsis(download.info.title, 100)} - : - + <> + + { + navigator.clipboard.writeText(download.info.url) + onCopy() + }}> + {download.info.thumbnail !== '' ? + : + } - - - - {!isCompleted() ? download.progress.percentage : ''} - - -   - {!isCompleted() ? formatSpeedMiB(download.progress.speed) : ''} - - - {formatSize(download.info.filesize_approx ?? 0)} - - - - - - - {isCompleted() ? - - - - - - : - - - - - - } - {isCompleted() && - <> - + {download.progress.percentage ? + : + null + } + + {download.info.title !== '' ? + + {ellipsis(download.info.title, 100)} + : + + } + + + + {!isCompleted() ? download.progress.percentage : ''} + + +   + {!isCompleted() ? formatSpeedMiB(download.progress.speed) : ''} + + + {formatSize(download.info.filesize_approx ?? 0)} + + + + + + + {isCompleted() ? + downloadFile(download.output.savedFilePath)} + onClick={onStop} > - + - + : + viewFile(download.output.savedFilePath)} + onClick={onStop} > - + - - } - - + } + {isCompleted() && + <> + + downloadFile(download.output.savedFilePath)} + > + + + + + viewFile(download.output.savedFilePath)} + > + + + + + + + + + + } + + + setShareOpen(false)} + > + Share this file + + + Copy the link below and share it. + + + + {shareLink} + + + + + + + + + ) } diff --git a/frontend/src/views/Filebrowser.tsx b/frontend/src/views/Filebrowser.tsx index 7ab6d569..fdc7687f 100644 --- a/frontend/src/views/Filebrowser.tsx +++ b/frontend/src/views/Filebrowser.tsx @@ -43,6 +43,10 @@ import { DirectoryEntry } from '../types' import { base64URLEncode, formatSize } from '../utils' import { useAtomValue } from 'jotai' +import ShareIcon from '@mui/icons-material/Share' +import ContentCopyIcon from '@mui/icons-material/ContentCopy' + + export default function Downloaded() { const [menuPos, setMenuPos] = useState({ x: 0, y: 0 }) const [showMenu, setShowMenu] = useState(false) @@ -178,6 +182,9 @@ export default function Downloaded() { fetcherSubfolder(path) }) + const [shareOpen, setShareOpen] = useState(false) + const [shareLink, setShareLink] = useState('') + return ( { + if (currentFile) { + const encoded = base64URLEncode(currentFile.path) + const link = `${serverAddr}/public/${encoded}` + setShareLink(link) + setShareOpen(true) + setShowMenu(false) + } + }} /> theme.zIndex.drawer + 1 }} @@ -316,8 +332,46 @@ export default function Downloaded() { + setShareOpen(false)} + > + Share this file + + + Copy the link below and share it. + + + + + + + + + + ) + } const IconMenu: React.FC<{ @@ -326,7 +380,8 @@ const IconMenu: React.FC<{ hide: boolean onDownload: () => void onDelete: () => void -}> = ({ posX, posY, hide, onDelete, onDownload }) => { + onShare: () => void +}> = ({ posX, posY, hide, onDelete, onDownload, onShare }) => { return ( - - Download - + Download - - Delete - + Delete + + + + + + Share ) -} \ No newline at end of file +} diff --git a/server/filebrowser/handlers.go b/server/filebrowser/handlers.go index 1e1621fd..17a0234e 100644 --- a/server/filebrowser/handlers.go +++ b/server/filebrowser/handlers.go @@ -190,20 +190,21 @@ func DownloadFile(w http.ResponseWriter, r *http.Request) { root := config.Instance().DownloadPath - if strings.Contains(filepath.Dir(filepath.Clean(filename)), filepath.Clean(root)) { - w.Header().Add("Content-Disposition", "inline; filename=\""+filepath.Base(filename)+"\"") - w.Header().Set("Content-Type", "application/octet-stream") - - fd, err := os.Open(filename) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } +if strings.HasPrefix(filename, root) { + w.Header().Add("Content-Disposition", "inline; filename=\""+filepath.Base(filename)+"\"") + w.Header().Set("Content-Type", "application/octet-stream") - io.Copy(w, fd) + fd, err := os.Open(filename) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) return } + defer fd.Close() + io.Copy(w, fd) + return +} + w.WriteHeader(http.StatusUnauthorized) } diff --git a/server/server.go b/server/server.go index 429046ea..d7a318c4 100644 --- a/server/server.go +++ b/server/server.go @@ -201,6 +201,12 @@ func newServer(c serverConfig) *http.Server { r.Get("/bulk", filebrowser.BulkDownload(c.mdb)) }) + + // Filebrowser routes + r.Route("/public", func(r chi.Router) { + r.Get("/{id}", filebrowser.SendFile) + }) + // Archive routes r.Route("/archive", archive.ApplyRouter(c.db))