diff --git a/app/src/App.css b/app/src/App.css index 50bbb637..7dee8bee 100644 --- a/app/src/App.css +++ b/app/src/App.css @@ -1,3 +1,47 @@ +/* Nav bar: a hand-rolled flexbox bar (was a Semantic Menu). The user-chosen + background color and the `--btn-text` foreground are set inline per theme; + this stylesheet only supplies structure and hover affordances. */ +.wrolpi-navbar { + display: flex; + align-items: stretch; + /* + * Wraps rather than clipping. Semantic's menu wrapped, and with this many items the + * bar overflows a 1000px viewport -- `nowrap` cut "Admin" off the right edge, which + * is the one link a user needs to reach the settings that fix a broken WROLPi. + */ + flex-wrap: wrap; + min-height: 3.4em; + padding: 0 0.25em; +} + +.wrolpi-navbar-right { + display: flex; + align-items: center; + margin-left: auto; + gap: 0.2em; +} + +.wrolpi-navbar-link, +.wrolpi-navbar-link-button { + display: flex; + align-items: center; + /* Tightened from 1.1em: the bar has eleven links plus nine indicators. */ + padding: 0 0.8em; + color: inherit; + text-decoration: none; + white-space: nowrap; + background: none; + border: none; + font: inherit; + cursor: pointer; +} + +.wrolpi-navbar-link:hover, +.wrolpi-navbar-link-button:hover { + /* A translucent black works on every navbar colour, light or dark. */ + background: rgba(0, 0, 0, 0.12); +} + .channel-edit { border-radius: 3px; padding: 0.75em; diff --git a/app/src/App.js b/app/src/App.js index 68d69f8a..1e372a53 100644 --- a/app/src/App.js +++ b/app/src/App.js @@ -5,7 +5,6 @@ import {createBrowserRouter, createRoutesFromElements, Link, Outlet, Route, Rout import {VideosTabLayout, VideosPage, VideosSettingsPage, VideosStatistics, VideoWrapper} from "./components/Videos"; import AdminRoute from "./components/admin/AdminRoute"; import {Box, Header} from "./components/ui"; -import 'semantic-ui-offline/semantic.min.css'; import '@mantine/core/styles.css'; import '@mantine/notifications/styles.css'; // Theme tokens load last so they win over both component libraries. diff --git a/app/src/components/Archive.js b/app/src/components/Archive.js index 78470c7c..a8d97418 100644 --- a/app/src/components/Archive.js +++ b/app/src/components/Archive.js @@ -1,24 +1,5 @@ -import React, {useContext, useState} from "react"; +import React, {useState} from "react"; import {useHotkeys} from "react-hotkeys-hook"; -import { - CardContent, - CardDescription, - CardHeader, - CardMeta, - Container, - Form, - GridColumn, - GridRow, - Image, - Input, - PlaceholderHeader, - PlaceholderLine, - StatisticLabel, - StatisticValue, - TableCell, - TextArea, -} from "semantic-ui-react"; -import Icon from "semantic-ui-react/dist/commonjs/elements/Icon"; import { APIButton, BackButton, @@ -33,14 +14,14 @@ import { humanNumber, InfoHeader, isoDatetimeToAgoPopup, - mimetypeColor, PageContainer, PreviewPath, resolveDataPath, SearchInput, TabLinks, textEllipsis, - useTitle + mimetypeColor, + useTitle, } from "./Common"; import { deleteDomain, @@ -58,14 +39,13 @@ import { untagFileGroup, updateArchiveDownloaderConfig, } from "../api"; -import {InputForm, useForm} from "../hooks/useForm"; +import {useForm} from "../hooks/useForm"; import {TaggedDeleteConfirmModal} from "./TaggedDeleteConfirmModal"; import {CollectionTagModal} from "./collections/CollectionTagModal"; import {CollectionReorganizeModal} from "./collections/CollectionReorganizeModal"; import {BatchReorganizeModal} from "./collections/BatchReorganizeModal"; import {useReorganizationStatus} from "../contexts/FileWorkerStatusContext"; import {Link, Route, Routes, useLocation, useNavigate, useParams} from "react-router"; -import Message from "semantic-ui-react/dist/commonjs/collections/Message"; import { useArchive, useArchiveStatistics, @@ -78,14 +58,32 @@ import { useSearchOrder } from "../hooks/customHooks"; import {DeepSearchHint, FileCards, FileRowTagIcon, FilesView, SearchControlBar} from "./Files"; -import Grid from "semantic-ui-react/dist/commonjs/collections/Grid"; import _ from "lodash"; -import {ThemeContext} from "../contexts/contexts"; -import {Button, Card, darkTheme, Header, Loader, Placeholder, Popup, Segment, Statistic, Tab} from "./Theme"; +import { + ActionInput, + Button, + Card, + Header, + Icon, + IconButton, + Loader, + Loading, + Message, + Panel, + Placeholder, + Select, + Statistic, + StatisticGroup, + Table, + Tabs, + Textarea, + TextInput, + Tooltip, + toast, +} from "./ui"; import {BulkTagModal} from "./BulkTagModal"; -import {taggedImageLabel, TagsSelector} from "../Tags"; +import {TagsSelector} from "../Tags"; import {AddToPlaylistButton} from "./AddToPlaylist"; -import {toast} from "./ui"; import {API_ARCHIVE_UPLOAD_URI, Downloaders} from "./Vars"; import {CollectionTable} from "./collections/CollectionTable"; import {CollectionEditForm} from "./collections/CollectionEditForm"; @@ -96,14 +94,13 @@ function ArchivePage() { const navigate = useNavigate(); const {fileGroupId} = useParams(); const {archiveFile, history, fetchArchive} = useArchive(fileGroupId); - const {theme} = useContext(ThemeContext); const [taggedFileGroups, setTaggedFileGroups] = useState(null); let title = archiveFile ? archiveFile.title ? archiveFile.title : archiveFile.name : null; useTitle(title); if (archiveFile === null) { - return ; + return Loading archive...; } if (archiveFile === undefined) { return <> @@ -124,14 +121,15 @@ function ArchivePage() { const screenshotUrl = screenshotPath ? `/media/${encodeMediaPath(screenshotPath)}` : null; const singlefileButton = - ; const readButton = - const screenshot = screenshotUrl ? - : + Archive screenshot : null; const localDeleteArchive = async (force = false) => { @@ -171,7 +169,6 @@ function ArchivePage() { } const updateButton = ; const deleteButton = localDeleteArchive(false)} obeyWROLMode={true} > Delete ; const generateScreenshotButton = !screenshotUrl ? : null; - let historyList = ; + let historyList = ; if (history && history.length === 0) { historyList =

No history available

; } else if (history) { @@ -237,28 +232,12 @@ function ArchivePage() { ? isoDatetimeToAgoPopup(archiveFile.published_modified_datetime, true) : 'unknown'; - const aboutPane = { - menuItem: 'About', render: () => -
Domain
- {domainHeader} - -
Size
- {humanFileSize(size)} - -
URL
-

{archiveFile.url ? {archiveFile.url} : 'N/A'}

- -
Modified Date
-

{modifiedDatetimeString}

-
- }; - // 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)}) + ? ({humanFileSize(file.size)}) : null; }; @@ -275,51 +254,11 @@ function ArchivePage() { 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 - ? <>{screenshotPath}{findFileSize(screenshotPath)} - : 'Unknown'} -
Directory
-
- }; - - const tabPanes = [aboutPane, filesPane]; - const tabMenu = theme === darkTheme ? {inverted: true, attached: true} : {attached: true}; return <> - + {screenshot}
{textEllipsis(archiveFile.title || data.url)}
@@ -327,16 +266,14 @@ function ArchivePage() {
Author: {archiveFile.author ? archiveFile.author : 'unknown'}
- - - -
Published: {publishedDatetimeString}
-
- -
Downloaded: {downloadDatetimeString}
-
-
-
+
+
+
Published: {publishedDatetimeString}
+
+
+
Downloaded: {downloadDatetimeString}
+
+
{singlefileButton} {readButton} @@ -344,25 +281,78 @@ function ArchivePage() { {deleteButton} {generateScreenshotButton} -
+ - + - + - + + + About + Files + + +
Domain
+ {domainHeader} - +
Size
+ {humanFileSize(size)} + +
URL
+

{archiveFile.url ? {archiveFile.url} : 'N/A'}

+ +
Modified Date
+

{modifiedDatetimeString}

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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
+
+
+ + {historyList} - + ; - const imageLabel = file.tags && file.tags.length ? taggedImageLabel : null; + // Marks a tagged file, pinned to the poster's corner (same convention as CardPoster). + const cardTagIcon =
; + const imageLabel = !_.isEmpty(file.tags) ? cardTagIcon : null; + + let media; if (imageSrc) { - screenshot = ; + media = +
+ {imageLabel} + +
+
; + } else { + media = +
+ {imageLabel} + +
+
; } const domain = data ? data.domain : null; const domainUrl = `/archives?domain=${domain}`; const title = file.title || data.url; - const header = {title}; + const titleElm = + {title} + ; const dt = file.published_datetime || file.published_modified_datetime || file.modified; - return + + const meta = <> + {domain &&
+ {domain} +
}
- - {screenshot} - + {sortField === 'published_modified_datetime' + ? isoDatetimeToAgoPopup(file.published_modified_datetime, false) + : sortField === 'download_datetime' + ? isoDatetimeToAgoPopup(file.download_datetime, false) + : sortField === 'size' + ? humanFileSize(file.size) + : sortField === 'viewed' + ? isoDatetimeToAgoPopup(file.viewed, false) + : isoDatetimeToAgoPopup(dt, false) + } +
+ ; + + return +
+ + + +
- - - - - - - {domain && - - {domain} - } - - {sortField === 'published_modified_datetime' - ? isoDatetimeToAgoPopup(file.published_modified_datetime, false) - : sortField === 'download_datetime' - ? isoDatetimeToAgoPopup(file.download_datetime, false) - : sortField === 'size' - ? humanFileSize(file.size) - : sortField === 'viewed' - ? isoDatetimeToAgoPopup(file.viewed, false) - : isoDatetimeToAgoPopup(dt, false) - } - - - -