From bf5b787fb3554c2b3a9f154f486f8cbedb48c19f Mon Sep 17 00:00:00 2001 From: Roland Date: Sat, 28 Mar 2026 14:19:09 -0600 Subject: [PATCH] Unifying Files tab appearance for model pages. --- app/src/components/Archive.js | 80 +++++++++++++--------- app/src/components/Common.js | 9 +++ app/src/components/Docs.js | 39 +++++++---- app/src/components/VideoPlayer.js | 110 +++++++++++++++++++----------- wrolpi/files/models.py | 3 +- 5 files changed, 153 insertions(+), 88 deletions(-) diff --git a/app/src/components/Archive.js b/app/src/components/Archive.js index e70d4b88..8f4ff5cd 100644 --- a/app/src/components/Archive.js +++ b/app/src/components/Archive.js @@ -23,6 +23,7 @@ import { APIButton, BackButton, CardLink, + DirectoryLink, encodeMediaPath, ErrorMessage, ExternalCardLink, @@ -89,19 +90,6 @@ import {CollectionEditForm} from "./collections/CollectionEditForm"; import {RecurringDownloadsTable} from "./admin/Downloads"; import {DestinationForm} from "./Download"; -function archiveFileLink(path, directory = false) { - if (path) { - const href = directory ? - `/media/${encodeMediaPath(path)}/` - : `/media/${encodeMediaPath(path)}`; - return -
{path}
-
- } else { - return

Unknown

- } -} - function ArchivePage() { const navigate = useNavigate(); const {fileGroupId} = useParams(); @@ -258,37 +246,63 @@ function ArchivePage() { }; + // Helper to find file size from the files array by resolved path + const findFileSize = (resolvedPath) => { + if (!resolvedPath || !archiveFile.files) return null; + const file = archiveFile.files.find(i => String(i.path) === String(resolvedPath)); + return file && file.size + ? ({humanFileSize(file.size)}) + : null; + }; + // Helper to resolve and preview a data path const localPreviewPath = (dataPath, mimetype) => { const resolvedPath = resolveDataPath(dataPath, directory); if (resolvedPath) { - return {resolvedPath} + return <>{resolvedPath}{findFileSize(resolvedPath)} } else { return 'Unknown' } } + const tableStyle = {width: '100%', borderCollapse: 'separate', borderSpacing: '0 0.7em'}; + const labelStyle = {whiteSpace: 'nowrap', paddingRight: '1.5em', verticalAlign: 'top'}; const filesPane = { menuItem: 'Files', render: () => -
Singlefile File
- {localPreviewPath(data.singlefile_path, 'text/html')} - -
Readability File
- {localPreviewPath(data.readability_path, 'text/html')} - -
Readability Text File
- {localPreviewPath(data.readability_txt_path, 'text/plain')} - -
Readability JSON File
- {localPreviewPath(data.readability_json_path, 'application/json')} - -
Screenshot File
- {screenshotPath ? - archiveFileLink(screenshotPath) - : 'Unknown'} - -
Directory
- {archiveFileLink(archiveFile.directory, true)} + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Singlefile File{localPreviewPath(data.singlefile_path, 'text/html')}
Readability File{localPreviewPath(data.readability_path, 'text/html')}
Readability Text File{localPreviewPath(data.readability_txt_path, 'text/plain')}
Readability JSON File{localPreviewPath(data.readability_json_path, 'application/json')}
Screenshot File + {screenshotPath + ? <>{screenshotPath}{findFileSize(screenshotPath)} + : 'Unknown'} +
Directory
}; diff --git a/app/src/components/Common.js b/app/src/components/Common.js index 34e27d3f..efae402f 100644 --- a/app/src/components/Common.js +++ b/app/src/components/Common.js @@ -246,6 +246,15 @@ export function PreviewPath({path, mimetype, taggable = true, ...props}) { return } +export function DirectoryLink({path}) { + if (!path) return Unknown; + const href = `/media/${encodeMediaPath(path)}/`; + return + {path} + +} + export function RequiredAsterisk() { return * } diff --git a/app/src/components/Docs.js b/app/src/components/Docs.js index f8c949e3..d7d6886e 100644 --- a/app/src/components/Docs.js +++ b/app/src/components/Docs.js @@ -6,6 +6,7 @@ import {Media, ThemeContext} from "../contexts/contexts"; import { APIButton, BackButton, + DirectoryLink, encodeMediaPath, ErrorMessage, humanFileSize, @@ -288,21 +289,33 @@ function DocPage() { }; // Files pane. + const tableStyle = {width: '100%', borderCollapse: 'separate', borderSpacing: '0 0.7em'}; + const labelStyle = {whiteSpace: 'nowrap', paddingRight: '1.5em', verticalAlign: 'top'}; const filesPane = { menuItem: 'Files', render: () => - {docFile.files && docFile.files.map((file, idx) => -
- - {file.path} - - {file.size && - ({humanFileSize(file.size)}) - } -
- )} - -
Directory
-

{directory || 'Unknown'}

+ + + {docFile.files && docFile.files.map((file, idx) => + + + + + )} + + + + + +
+ {file.path.split('.').pop().toUpperCase()} + + + {file.path} + + {file.size && + ({humanFileSize(file.size)}) + } +
Directory
}; diff --git a/app/src/components/VideoPlayer.js b/app/src/components/VideoPlayer.js index eff72205..c4ba63ef 100644 --- a/app/src/components/VideoPlayer.js +++ b/app/src/components/VideoPlayer.js @@ -6,6 +6,7 @@ import _ from "lodash"; import { APIButton, BackButton, + DirectoryLink, encodeMediaPath, humanFileSize, humanNumber, @@ -29,19 +30,6 @@ import {Comment, CommentGroup, Input, Label} from "semantic-ui-react"; const MEDIA_PATH = '/media'; -function videoFileLink(path, directory = false) { - if (path) { - const href = directory ? - // Add / to end of directory. - `${MEDIA_PATH}/${encodeMediaPath(path)}/` : - `${MEDIA_PATH}/${encodeMediaPath(path)}`; - return -
{path}
-
- } else { - return 'Unknown' - } -} export function CaptionTrack({src, ...props}) { if (src.endsWith('.de.vtt')) { @@ -311,44 +299,84 @@ function VideoPage({videoFile, prevFile, nextFile, fetchVideo, ...props}) { }; const getFile = (suffix) => videoFile.files.find(i => i.path.toLowerCase().endsWith(suffix)); + const fileSize = (file) => file && file.size + ? ({humanFileSize(file.size)}) + : null; const {poster_file, info_json_file} = video; + const videoPathFile = videoFile.files.find(i => i.path === video['video_path']); const ffprobeJsonFile = getFile('.ffprobe.json'); + const tableStyle = {width: '100%', borderCollapse: 'separate', borderSpacing: '0 0.7em'}; + const labelStyle = {whiteSpace: 'nowrap', paddingRight: '1.5em', verticalAlign: 'top'}; const filesPane = { menuItem: 'Files', render: () => -

Video File

- {videoFileLink(video['video_path'])} + + + + + + -

Caption Files

- {caption_files ? - caption_files.map(i =>

- {i['path']} -

) : 'No caption files' - } + + + + -

Poster File

- {poster_file ? - - {poster_file['path']} - : 'No poster file' - } + + + + -

Info JSON File

- {info_json_file ? - - {info_json_file['path']} - : 'No yt-dlp info json file' - } + + + + -

ffprobe JSON File

- {ffprobeJsonFile ? - - {ffprobeJsonFile['path']} - : 'No ffprobe json file' - } + + + + -

Directory

- {videoFileLink(videoFile['directory'], true)} + + + + + +
Video File + {video['video_path'] + ? <> + {video['video_path']} + {fileSize(videoPathFile)} + : 'Unknown'} +
Caption Files + {caption_files + ? caption_files.map(i =>
+ {i['path']} + {fileSize(i)} +
) + : 'No caption files'} +
Poster File + {poster_file + ? <> + {poster_file['path']} + {fileSize(poster_file)} + : 'No poster file'} +
Info JSON File + {info_json_file + ? <> + {info_json_file['path']} + {fileSize(info_json_file)} + : 'No yt-dlp info json file'} +
ffprobe JSON File + {ffprobeJsonFile + ? <> + {ffprobeJsonFile['path']} + {fileSize(ffprobeJsonFile)} + : 'No ffprobe json file'} +
Directory
} diff --git a/wrolpi/files/models.py b/wrolpi/files/models.py index 169f08f9..badc9c9d 100644 --- a/wrolpi/files/models.py +++ b/wrolpi/files/models.py @@ -272,7 +272,8 @@ class FileGroup(ModelHelper, Base): new_files = list(self.files) if self.files else list() for path in paths: # Store just the filename, not the full path - new_files.append(dict(path=path.name, mimetype=get_mimetype(path))) + size = path.stat().st_size if path.exists() else None + new_files.append(dict(path=path.name, mimetype=get_mimetype(path), size=size)) self.files = unique_by_predicate(new_files, lambda i: i['path']) # Update modification_datetime to include the new files' mtimes if paths: