From 51699d7d158cdb964c8f11b486dfeb5645b8b93d Mon Sep 17 00:00:00 2001 From: an anme Date: Fri, 20 Jun 2025 13:35:31 +0200 Subject: [PATCH 1/4] public share feature --- frontend/src/components/DownloadCard.tsx | 221 ++++++++++++++--------- frontend/src/views/Filebrowser.tsx | 73 +++++++- server/filebrowser/handlers.go | 21 ++- server/server.go | 6 + 4 files changed, 222 insertions(+), 99 deletions(-) diff --git a/frontend/src/components/DownloadCard.tsx b/frontend/src/components/DownloadCard.tsx index 723fa922..bdc8b911 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)} + > + + + + {/* ← Das hier neu */} + + + + + + } + + + 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)) From 2d3e84f0f6c112812f7335ba108cf4416de7e6a1 Mon Sep 17 00:00:00 2001 From: an anme Date: Fri, 20 Jun 2025 21:39:50 +0200 Subject: [PATCH 2/4] added a player for /public videos --- frontend/src/Layout.tsx | 250 ++++++++++------------- frontend/src/components/DownloadCard.tsx | 3 +- frontend/src/router.tsx | 14 ++ frontend/src/utils.ts | 10 + frontend/src/views/Filebrowser.tsx | 2 +- frontend/src/views/Player.tsx | 70 +++++++ 6 files changed, 200 insertions(+), 149 deletions(-) create mode 100644 frontend/src/views/Player.tsx diff --git a/frontend/src/Layout.tsx b/frontend/src/Layout.tsx index 0d136bc3..c47743a0 100644 --- a/frontend/src/Layout.tsx +++ b/frontend/src/Layout.tsx @@ -20,7 +20,7 @@ import Typography from '@mui/material/Typography' import { grey } from '@mui/material/colors' import { useAtomValue } from 'jotai' import { useMemo, useState } from 'react' -import { Link, Outlet } from 'react-router-dom' +import { Link, Outlet, useLocation } from 'react-router-dom' import { settingsState } from './atoms/settings' import AppBar from './components/AppBar' import Drawer from './components/Drawer' @@ -34,10 +34,12 @@ import { getAccentValue } from './utils' export default function Layout() { const [open, setOpen] = useState(false) + const location = useLocation() + const isPlayerRoute = location.pathname.startsWith('/public/') const settings = useAtomValue(settingsState) - const mode = settings.theme + const theme = useMemo(() => createTheme({ palette: { @@ -53,164 +55,118 @@ export default function Layout() { ) const toggleDrawer = () => setOpen(state => !state) - const { i18n } = useI18n() return ( {settings.appTitle} - - - - - + + {isPlayerRoute ? ( + // ❯ Minimal layout for /public/:encoded + + + + ) : ( + // ❯ Full layout for all other pages + + + + + + + + {settings.appTitle} + + + + + - - - - {settings.appTitle} - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - {/* - - - - - - - */} - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + - -