From d17ad50d56857f759dd8017deb565a1d8c459cf1 Mon Sep 17 00:00:00 2001 From: Roland Date: Sat, 29 Jan 2022 21:24:39 -0700 Subject: [PATCH 1/4] Archive tabs are handled by TabRouter. --- app/src/components/Archive.js | 110 +++++++++++++--------------------- app/src/components/Common.js | 61 ++++++++++++++++++- app/src/components/Nav.js | 4 +- modules/files/api.py | 1 + modules/files/lib.py | 4 +- 5 files changed, 107 insertions(+), 73 deletions(-) diff --git a/app/src/components/Archive.js b/app/src/components/Archive.js index 2adc29eb..f6cc4be5 100644 --- a/app/src/components/Archive.js +++ b/app/src/components/Archive.js @@ -1,6 +1,14 @@ import React, {useState} from "react"; import {Card, Confirm, Container, Icon, Image, Placeholder, Tab} from "semantic-ui-react"; -import Paginator, {ClearButton, ExternalLink, SearchInput, textEllipsis, uploadDate, WROLModeMessage} from "./Common"; +import Paginator, { + ClearButton, + ExternalLink, + SearchInput, + TabRouter, + textEllipsis, + uploadDate, + WROLModeMessage +} from "./Common"; import {deleteArchive, postDownload, refreshArchives} from "../api"; import Button from "semantic-ui-react/dist/commonjs/elements/Button"; import {Link, NavLink} from "react-router-dom"; @@ -289,73 +297,41 @@ function Domains() { } -export class ArchiveRoute extends React.Component { - - constructor(props) { - super(props); - - this.panesTos = ['/archive', '/archive/domains', '/archive/manage']; - this.state = { - activeIndex: this.matchPaneTo(), - } - - this.archivePanes = [ - { - menuItem: { - as: NavLink, - content: 'Archives', - id: 'archive', - to: '/archive', - exact: true, - key: 'home', - }, - render: () => +export function ArchiveRoute(props) { + const panes = [ + { + menuItem: { + as: NavLink, + content: 'Archives', + id: 'archive', + to: '/archive', + exact: true, + key: 'home', }, - { - menuItem: { - as: NavLink, - content: 'Domains', - id: 'domains', - to: '/archive/domains', - exact: true, - key: 'domains', - }, - render: () => + render: () => + }, + { + menuItem: { + as: NavLink, + content: 'Domains', + id: 'domains', + to: '/archive/domains', + exact: true, + key: 'domains', }, - { - menuItem: { - as: NavLink, - content: 'Manage', - id: 'manage', - to: '/archive/manage', - exact: true, - key: 'manage', - }, - render: () => + render: () => + }, + { + menuItem: { + as: NavLink, + content: 'Manage', + id: 'manage', + to: '/archive/manage', + exact: true, + key: 'manage', }, - ]; - - } - - componentDidUpdate(prevProps, prevState, snapshot) { - if (prevProps.location.pathname !== this.props.location.pathname) { - this.setState({activeIndex: this.matchPaneTo()}); - } - } - - matchPaneTo = () => { - return this.panesTos.indexOf(this.props.location.pathname); - } - - render() { - return ( - - - - ) - } + render: () => + }, + ]; + return } diff --git a/app/src/components/Common.js b/app/src/components/Common.js index e50fae2f..82425355 100644 --- a/app/src/components/Common.js +++ b/app/src/components/Common.js @@ -1,5 +1,5 @@ import React, {useEffect, useState} from "react"; -import {Card, Container, Form, Icon, Image, Input, Pagination, Progress} from 'semantic-ui-react'; +import {Card, Container, Form, Icon, Image, Input, Pagination, Progress, Tab} from 'semantic-ui-react'; import {Link} from "react-router-dom"; import Button from "semantic-ui-react/dist/commonjs/elements/Button"; import Grid from "semantic-ui-react/dist/commonjs/collections/Grid"; @@ -635,3 +635,62 @@ export function textEllipsis(str, maxLength, {side = "end", ellipsis = "..."} = } return str; } + +export class TabRouter extends React.Component { + constructor(props) { + super(props); + + const panes = props.panes; + let paneTos = []; + for (let i = 0; i < panes.length; i++) { + const pane = panes[i]; + paneTos = paneTos.concat([pane['menuItem']['to']]); + } + + this.state = { + activeIndex: this.matchPaneTo(paneTos), + paneTos: paneTos, + }; + } + + matchPaneTo = (paneTos) => { + const pathname = this.props.location.pathname; + // Attempt to find the exact match first. + let activeIndex = paneTos.indexOf(pathname); + if (activeIndex !== -1) { + return activeIndex; + } + + // Use the first tab if we can't find one. + activeIndex = 0; + // Try to find the most-exact match. + for (let i = 0; i < paneTos.length; i++) { + let to = paneTos[i]; + if (pathname.startsWith(to) && to.length > paneTos[activeIndex].length) { + // "to" is a better match because it is longer than the last one we found. + activeIndex = i; + } + } + return activeIndex; + } + + componentDidUpdate(prevProps, prevState, snapshot) { + if (prevProps.location.pathname !== this.props.location.pathname) { + this.setState({activeIndex: this.matchPaneTo(this.state.paneTos)}); + } + } + + render() { + const {panes, renderActiveOnly} = this.props; + return ( + <> + + + ) + } + +} diff --git a/app/src/components/Nav.js b/app/src/components/Nav.js index c93d8877..eb5a043f 100644 --- a/app/src/components/Nav.js +++ b/app/src/components/Nav.js @@ -14,8 +14,8 @@ const links = [ ] }, {text: 'Inventory', to: '/inventory', exact: true}, - {text: 'Archive', to: '/archive', exact: true}, - {text: 'Files', to: '/files', exact: true}, + {text: 'Archive', to: '/archive'}, + {text: 'Files', to: '/files'}, { text: 'Apps', links: [ {to: '/apps/otp', text: 'One Time Pad', exact: true}, diff --git a/modules/files/api.py b/modules/files/api.py index 72cee406..2d8a8cb6 100644 --- a/modules/files/api.py +++ b/modules/files/api.py @@ -14,6 +14,7 @@ bp = get_blueprint('Files', '/api/files') def paths_to_files(paths: List[pathlib.Path]): + """Convert Paths to what the React UI expects.""" media_directory = get_media_directory() new_files = [] for path in paths: diff --git a/modules/files/lib.py b/modules/files/lib.py index 438f17c6..6224be4e 100644 --- a/modules/files/lib.py +++ b/modules/files/lib.py @@ -51,9 +51,7 @@ def list_files(directories: List[str]) -> List[Path]: @wrol_mode_check def delete_file(file: str): - """ - Delete a file in the media directory. - """ + """Delete a file in the media directory.""" file = get_media_directory() / file if file.is_dir() or not file.is_file(): raise InvalidFile(f'Invalid file {file}') From 8da7e984e2e6f8d458658bdea1697c9f67fc67ab Mon Sep 17 00:00:00 2001 From: Roland Date: Sat, 29 Jan 2022 22:25:35 -0700 Subject: [PATCH 2/4] Using NavLinks on all pages. --- app/src/components/Admin.js | 33 ++++---- app/src/components/Archive.js | 51 ++++--------- app/src/components/Common.js | 75 ++++--------------- app/src/components/Files.js | 10 +-- app/src/components/Inventory.js | 40 ++++------ app/src/components/Nav.js | 22 ++---- app/src/components/Videos.js | 129 ++++++++++++++++++-------------- 7 files changed, 144 insertions(+), 216 deletions(-) diff --git a/app/src/components/Admin.js b/app/src/components/Admin.js index d6235786..04fe60dd 100644 --- a/app/src/components/Admin.js +++ b/app/src/components/Admin.js @@ -23,7 +23,8 @@ import { startDownloads } from "../api"; import TimezoneSelect from 'react-timezone-select'; -import {secondsToDate, secondsToFrequency, textEllipsis, truncateLong, WROLModeMessage} from "./Common"; +import {secondsToDate, secondsToFrequency, TabLinks, textEllipsis, WROLModeMessage} from "./Common"; +import {Route} from "react-router-dom"; class Settings extends React.Component { @@ -440,23 +441,21 @@ class Downloads extends React.Component { } } -class Admin extends React.Component { +export default function Admin(props) { - render() { + const links = [ + {text: 'Downloads', to: '/admin', exact: true}, + {text: 'Settings', to: '/admin/settings'}, + {text: 'WROL Mode', to: '/admin/wrol'}, + ]; - const panes = [ - {menuItem: 'Downloads', render: () => }, - {menuItem: 'Settings', render: () => }, - {menuItem: 'WROL Mode', render: () => }, - ]; - - return ( - - - - ) - } + return ( + <> + + + + + + ) } - -export default Admin; diff --git a/app/src/components/Archive.js b/app/src/components/Archive.js index f6cc4be5..cc2c3492 100644 --- a/app/src/components/Archive.js +++ b/app/src/components/Archive.js @@ -1,17 +1,17 @@ import React, {useState} from "react"; -import {Card, Confirm, Container, Icon, Image, Placeholder, Tab} from "semantic-ui-react"; +import {Card, Confirm, Container, Icon, Image, Placeholder} from "semantic-ui-react"; import Paginator, { ClearButton, ExternalLink, SearchInput, - TabRouter, + TabLinks, textEllipsis, uploadDate, WROLModeMessage } from "./Common"; import {deleteArchive, postDownload, refreshArchives} from "../api"; import Button from "semantic-ui-react/dist/commonjs/elements/Button"; -import {Link, NavLink} from "react-router-dom"; +import {Link, Route} from "react-router-dom"; import {ArchivePlaceholder} from "./Placeholder"; import Table from "semantic-ui-react/dist/commonjs/collections/Table"; import {useArchives} from "../hooks/useArchives"; @@ -298,40 +298,15 @@ function Domains() { export function ArchiveRoute(props) { - const panes = [ - { - menuItem: { - as: NavLink, - content: 'Archives', - id: 'archive', - to: '/archive', - exact: true, - key: 'home', - }, - render: () => - }, - { - menuItem: { - as: NavLink, - content: 'Domains', - id: 'domains', - to: '/archive/domains', - exact: true, - key: 'domains', - }, - render: () => - }, - { - menuItem: { - as: NavLink, - content: 'Manage', - id: 'manage', - to: '/archive/manage', - exact: true, - key: 'manage', - }, - render: () => - }, + const links = [ + {text: 'Archives', to: '/archive', exact: true}, + {text: 'Domains', to: '/archive/domains'}, + {text: 'Manage', to: '/archive/manage'}, ]; - return + return <> + + + + + } diff --git a/app/src/components/Common.js b/app/src/components/Common.js index 82425355..a1daea02 100644 --- a/app/src/components/Common.js +++ b/app/src/components/Common.js @@ -1,6 +1,6 @@ import React, {useEffect, useState} from "react"; -import {Card, Container, Form, Icon, Image, Input, Pagination, Progress, Tab} from 'semantic-ui-react'; -import {Link} from "react-router-dom"; +import {Card, Container, Form, Icon, Image, Input, Menu, Pagination, Progress} from 'semantic-ui-react'; +import {Link, NavLink} from "react-router-dom"; import Button from "semantic-ui-react/dist/commonjs/elements/Button"; import Grid from "semantic-ui-react/dist/commonjs/collections/Grid"; import Message from "semantic-ui-react/dist/commonjs/collections/Message"; @@ -636,61 +636,18 @@ export function textEllipsis(str, maxLength, {side = "end", ellipsis = "..."} = return str; } -export class TabRouter extends React.Component { - constructor(props) { - super(props); - - const panes = props.panes; - let paneTos = []; - for (let i = 0; i < panes.length; i++) { - const pane = panes[i]; - paneTos = paneTos.concat([pane['menuItem']['to']]); - } - - this.state = { - activeIndex: this.matchPaneTo(paneTos), - paneTos: paneTos, - }; - } - - matchPaneTo = (paneTos) => { - const pathname = this.props.location.pathname; - // Attempt to find the exact match first. - let activeIndex = paneTos.indexOf(pathname); - if (activeIndex !== -1) { - return activeIndex; - } - - // Use the first tab if we can't find one. - activeIndex = 0; - // Try to find the most-exact match. - for (let i = 0; i < paneTos.length; i++) { - let to = paneTos[i]; - if (pathname.startsWith(to) && to.length > paneTos[activeIndex].length) { - // "to" is a better match because it is longer than the last one we found. - activeIndex = i; - } - } - return activeIndex; - } - - componentDidUpdate(prevProps, prevState, snapshot) { - if (prevProps.location.pathname !== this.props.location.pathname) { - this.setState({activeIndex: this.matchPaneTo(this.state.paneTos)}); - } - } - - render() { - const {panes, renderActiveOnly} = this.props; - return ( - <> - - - ) - } - +export function TabLinks({links}) { + return ( + + {links.map((link) => + + {link.text} + + )} + + ) } diff --git a/app/src/components/Files.js b/app/src/components/Files.js index d39226f6..9a2345bf 100644 --- a/app/src/components/Files.js +++ b/app/src/components/Files.js @@ -110,10 +110,8 @@ class Files extends React.Component { } } -export class FilesRoute extends React.Component { - render() { - return ( - - ); - } +export function FilesRoute(props) { + return ( + + ); } diff --git a/app/src/components/Inventory.js b/app/src/components/Inventory.js index b7d44d88..6ded9ded 100644 --- a/app/src/components/Inventory.js +++ b/app/src/components/Inventory.js @@ -13,11 +13,10 @@ import { updateInventory, updateItem } from "../api"; -import {Button, Checkbox, Dropdown, Form, Grid, Header, Portal, Segment, Tab, Table} from "semantic-ui-react"; +import {Button, Checkbox, Dropdown, Form, Grid, Header, Portal, Segment, Table} from "semantic-ui-react"; import {Route} from "react-router-dom"; -import Container from "semantic-ui-react/dist/commonjs/elements/Container"; import {toast} from 'react-semantic-toasts'; -import {arraysEqual, enumerate, replaceNullValues} from './Common'; +import {arraysEqual, enumerate, replaceNullValues, TabLinks} from './Common'; import _ from 'lodash'; class InventorySummary extends React.Component { @@ -1089,27 +1088,16 @@ class InventoryAddList extends React.Component { } } -class InventoryTab extends React.Component { - - render() { - let panes = [ - {menuItem: 'List', render: () => }, - {menuItem: 'Summary', render: () => }, - ]; - return ( - - ) - } -} - -export class InventoryRoute extends React.Component { - render() { - return ( - <> - - - - - ) - } +export function InventoryRoute(props) { + const links = [ + {text: 'List', to: '/inventory', exact: true}, + {text: 'Summary', to: '/inventory/summary'}, + ]; + return ( + <> + + + + + ) } diff --git a/app/src/components/Nav.js b/app/src/components/Nav.js index eb5a043f..770605c1 100644 --- a/app/src/components/Nav.js +++ b/app/src/components/Nav.js @@ -5,14 +5,7 @@ import {Dropdown, Menu, Responsive} from "semantic-ui-react"; const responsiveWidth = 500; const links = [ - { - text: 'Videos', links: [ - {to: '/videos', text: 'Newest Videos', exact: true}, - {to: '/videos/favorites', text: 'Favorites', exact: true}, - {to: '/videos/channel', text: 'Channels', exact: true}, - {to: '/videos/manage', text: 'Manage', exact: true}, - ] - }, + {text: 'Videos', to: '/videos'}, {text: 'Inventory', to: '/inventory', exact: true}, {text: 'Archive', to: '/archive'}, {text: 'Files', to: '/files'}, @@ -33,7 +26,7 @@ function DropdownLinks(props) { {props.link.links.map((l) => { return ( - + ) })} @@ -41,7 +34,8 @@ function DropdownLinks(props) { ) } -function Link(props) { +function MenuLink(props) { + // Wrapper around NavLink to handle Navlink/Dropdown change. let classes = 'item'; if (props.link.header) { classes = `${classes} header`; @@ -68,17 +62,17 @@ export function NavBar() { return ( {/*Always show WROLPi home button*/} - + {/*Show the links in a menu when on desktop*/} {links.map((link) => { return ( - + ) })} {rightLinks.map((link) => { - return () + return () })} @@ -87,7 +81,7 @@ export function NavBar() { {collapsedLinks.map((link) => { - return () + return () })} diff --git a/app/src/components/Videos.js b/app/src/components/Videos.js index 756536a0..bac210ed 100644 --- a/app/src/components/Videos.js +++ b/app/src/components/Videos.js @@ -1,5 +1,5 @@ import React from 'react'; -import {Link, Route} from "react-router-dom"; +import {NavLink, Route} from "react-router-dom"; import Paginator, { changePageHistory, DEFAULT_LIMIT, @@ -10,8 +10,10 @@ import Paginator, { scrollToTop, searchOrders, secondsToString, + TabLinks, VideoCards, - videoOrders, WROLModeMessage + videoOrders, + WROLModeMessage } from "./Common" import VideoPage from "./VideoPlayer"; import {download, getChannel, getStatistics, getVideo, refresh, searchVideos} from "../api"; @@ -27,8 +29,7 @@ import { Modal, Radio, Segment, - Statistic, - Tab + Statistic } from "semantic-ui-react"; import * as QueryString from 'query-string'; import Container from "semantic-ui-react/dist/commonjs/elements/Container"; @@ -65,7 +66,6 @@ class ManageVideos extends React.Component { return ( -
Manage Videos