From dad276d6ecdb347f306dffd4522ac3feef1ae85d Mon Sep 17 00:00:00 2001 From: Roland Date: Fri, 4 Nov 2022 09:58:38 -0600 Subject: [PATCH] Creating isEmpty function to check if dict/array have some contents. --- app/src/components/Archive.js | 43 +++++++-- app/src/components/Channels.js | 4 +- app/src/components/Common.js | 130 +++----------------------- app/src/components/Files.js | 17 ++-- app/src/components/Inventory.js | 14 +-- app/src/components/Map.js | 10 +- app/src/components/Theme.js | 3 +- app/src/components/VideoPlayer.js | 3 +- app/src/components/Videos.js | 118 +++++++++++++++++++++-- app/src/components/admin/Downloads.js | 6 +- 10 files changed, 189 insertions(+), 159 deletions(-) diff --git a/app/src/components/Archive.js b/app/src/components/Archive.js index ed7c8b08..7500e03e 100644 --- a/app/src/components/Archive.js +++ b/app/src/components/Archive.js @@ -19,6 +19,7 @@ import { ExternalCardLink, FileIcon, HelpHeader, + isEmpty, mimetypeColor, PageContainer, SearchInput, @@ -113,9 +114,9 @@ function ArchivePage() { ); let alternativesList = ; - if (alternatives && alternatives.length > 0) { + if (!isEmpty(alternatives)) { alternativesList = ; - } else if (alternatives && alternatives.length === 0) { + } else if (isEmpty(alternatives)) { alternativesList =

No alternatives available

; } @@ -235,7 +236,7 @@ export function Domains() { ) - } else if (!domains || domains.length === 0) { + } else if (isEmpty(domains)) { return No domains yet. Archive some webpages! @@ -289,7 +290,7 @@ function Archives() { useTitle('Archives'); let filterOptions = []; - if (domains && domains.length > 0) { + if (!isEmpty(domains)) { domains.forEach(i => { filterOptions = [...filterOptions, {text: i['domain'], key: i['domain'], value: i['domain']}] }); @@ -379,7 +380,7 @@ function Archives() { const selectElm =
invertSelection()} - disabled={!archives || archives.length === 0} + disabled={isEmpty(archives)} > Invert @@ -428,6 +429,34 @@ function Archives() { /> } +export function ArchiveRowCells({file}) { + const {archive} = file; + + const archiveUrl = `/archive/${archive.id}`; + const posterUrl = archive.screenshot_path ? `/media/${encodeURIComponent(archive.screenshot_path)}` : null; + + let poster; + if (posterUrl) { + poster = + + ; + } else { + poster = ; + } + + // Fragment for SelectableRow + return ( + +
{poster}
+
+ + + {textEllipsis(archive.title || archive.stem, 100)} + + +
) +} + export function ArchiveRoute() { const links = [ {text: 'Archives', to: '/archive', end: true}, diff --git a/app/src/components/Channels.js b/app/src/components/Channels.js index 91d61820..60fd256f 100644 --- a/app/src/components/Channels.js +++ b/app/src/components/Channels.js @@ -17,7 +17,7 @@ import {createChannel, deleteChannel, downloadChannel, refreshChannel, updateCha import Container from "semantic-ui-react/dist/commonjs/elements/Container"; import { frequencyOptions, - humanFileSize, + humanFileSize, isEmpty, RequiredAsterisk, secondsToDate, secondsToFrequency, @@ -574,7 +574,7 @@ export function Channels() { - } else if (channels.length === 0) { + } else if (isEmpty(channels)) { return <> {header} diff --git a/app/src/components/Common.js b/app/src/components/Common.js index 690ce0d9..2f82fed3 100644 --- a/app/src/components/Common.js +++ b/app/src/components/Common.js @@ -1,10 +1,10 @@ import React, {useContext, useEffect, useState} from "react"; -import {Card, Container, IconGroup, Image, Input, Modal, Pagination, Responsive, TableCell} from 'semantic-ui-react'; +import {Card, Container, IconGroup, Input, Modal, Pagination, Responsive} from 'semantic-ui-react'; import {Link, NavLink} from "react-router-dom"; import Message from "semantic-ui-react/dist/commonjs/collections/Message"; import {useDownloaders, useHotspot, useThrottle} from "../hooks/customHooks"; import {StatusContext, ThemeContext} from "../contexts/contexts"; -import {Button, CardIcon, darkTheme, Form, Header, Icon, lightTheme, Menu, Popup, Statistic} from "./Theme"; +import {Button, darkTheme, Form, Header, Icon, lightTheme, Menu, Popup, Statistic} from "./Theme"; export const API_URI = `http://${window.location.host}/api`; export const VIDEOS_API = `${API_URI}/videos`; @@ -133,122 +133,6 @@ export function ExternalCardLink({to, ...props}) { } -export function VideoCard({file}) { - const {video} = file; - const {s} = useContext(ThemeContext); - - let video_url = `/videos/video/${video.id}`; - const upload_date = uploadDate(video.upload_date); - // A video may not have a channel. - const channel = video.channel ? video.channel : null; - let channel_url = null; - if (channel) { - channel_url = `/videos/channel/${channel.id}/video`; - video_url = `/videos/channel/${channel.id}/video/${video.id}`; - } - const poster_url = video.poster_path ? `/media/${encodeURIComponent(video.poster_path)}` : null; - - let imageLabel = null; - if (video.favorite) { - imageLabel = {corner: 'left', icon: 'heart', color: 'green'}; - } - let poster = ; - if (poster_url) { - poster = ; - } - - return ( - - {poster} - - - - - - -

{textEllipsis(video.title || video.stem || video.video_path, 100)}

- -
-
- - - {channel && - {channel.name} - } -

{upload_date}

-
-
-
-
) -} - -export function VideoRowCells({file}) { - const {video} = file; - - let video_url = `/videos/video/${video.id}`; - const poster_url = video.poster_path ? `/media/${encodeURIComponent(video.poster_path)}` : null; - - let poster; - let imageLabel = video.favorite ? {corner: 'left', icon: 'heart', color: 'green'} : null; - if (poster_url) { - poster = - - - } else { - poster = ; - } - - // Fragment for SelectableRow - return - -
- {poster} -
-
- - - {textEllipsis(video.title || video.stem || video.video_path, 100)} - - -
-} - -export function ArchiveRowCells({file}) { - const {archive} = file; - - const archiveUrl = `/archive/${archive.id}`; - const posterUrl = archive.screenshot_path ? `/media/${encodeURIComponent(archive.screenshot_path)}` : null; - - let poster; - if (posterUrl) { - poster = - - ; - } else { - poster = ; - } - - // Fragment for SelectableRow - return ( - -
{poster}
-
- - - {textEllipsis(archive.title || archive.stem, 100)} - - -
) -} - export function RequiredAsterisk() { return * } @@ -410,6 +294,16 @@ export function arraysEqual(a, b) { return true; } +export function isEmpty(i) { + const str = Object.prototype.toString.call(i); + if (str === '[object Object]') { + return Object.keys(i).length === 0; + } else if (str === '[object Array]') { + return i.length === 0; + } + return !i; +} + export function scrollToTop() { window.scrollTo({ top: 0, behavior: "auto" diff --git a/app/src/components/Files.js b/app/src/components/Files.js index cecf7cf1..e6641374 100644 --- a/app/src/components/Files.js +++ b/app/src/components/Files.js @@ -4,28 +4,27 @@ import {Card, Confirm, Divider, Dropdown, Image, PlaceholderLine, TableCell, Tab import 'react-keyed-file-browser/dist/react-keyed-file-browser.css'; import {deleteFile, refreshDirectoryFiles, refreshFiles} from "../api"; import { - ArchiveRowCells, CardGroupCentered, ExternalCardLink, FileIcon, humanFileSize, + isEmpty, mimetypeColor, PageContainer, Paginator, textEllipsis, uploadDate, - useTitle, - VideoCard, - VideoRowCells + useTitle } from "./Common"; import {useBrowseFiles, useQuery, useSearchFiles} from "../hooks/customHooks"; import {Route, Routes} from "react-router-dom"; import {CardPlacholder} from "./Placeholder"; -import {ArchiveCard} from "./Archive"; +import {ArchiveCard, ArchiveRowCells} from "./Archive"; import Grid from "semantic-ui-react/dist/commonjs/collections/Grid"; import {ThemeContext} from "../contexts/contexts"; import {Button, CardIcon, Header, Icon, Placeholder, Segment} from "./Theme"; import {SelectableTable} from "./Tables"; +import {VideoCard, VideoRowCells} from "./Videos"; const icons = { File: , @@ -199,11 +198,11 @@ function FileCard({file}) { } export function FileCards({files}) { - if (files && files.length > 0) { + if (!isEmpty(files)) { return {files.map((i) => )} - } else if (files && files.length === 0) { + } else if (isEmpty(files)) { return
No files found.
} else { return @@ -256,7 +255,7 @@ function FileRow({file}) { } export function FileTable({files, selectOn, onSelect, footer, selectedKeys}) { - if (files && files.length > 0) { + if (!isEmpty(files)) { const headerContents = ['Poster', 'Title']; const rows = files.map(i => ); return ; - } else if (files && files.length === 0) { + } else if (isEmpty(files)) { return

No results!

} else { return diff --git a/app/src/components/Inventory.js b/app/src/components/Inventory.js index 55c5bfc3..b23b7375 100644 --- a/app/src/components/Inventory.js +++ b/app/src/components/Inventory.js @@ -26,7 +26,7 @@ import { } from "semantic-ui-react"; import {Route, Routes} from "react-router-dom"; import {toast} from 'react-semantic-toasts'; -import {arraysEqual, enumerate, PageContainer, replaceNullValues, TabLinks, useTitle} from './Common'; +import {arraysEqual, enumerate, isEmpty, PageContainer, replaceNullValues, TabLinks, useTitle} from './Common'; import _ from 'lodash'; import {ThemeContext} from "../contexts/contexts"; import {useInventory} from "../hooks/customHooks"; @@ -50,7 +50,7 @@ function InventorySummary() { } - if (byCategory && byCategory.length > 0) { + if (!isEmpty(byCategory)) { return @@ -77,7 +77,7 @@ function InventorySummary() { } - if (bySubcategory && bySubcategory.length > 0) { + if (!isEmpty(bySubcategory )) { return
@@ -106,7 +106,7 @@ function InventorySummary() { } - if (byName && byName.length > 0) { + if (!isEmpty(byName)) { return
@@ -309,7 +309,7 @@ class InventoryList extends React.Component { } render() { - if (this.props.items.length === 0) { + if (isEmpty(this.props.items)) { return {({t}) => (

Add some items using the form above!

@@ -386,7 +386,7 @@ class InventoryList extends React.Component { diff --git a/app/src/components/Map.js b/app/src/components/Map.js index 5cf76b5f..9001dea1 100644 --- a/app/src/components/Map.js +++ b/app/src/components/Map.js @@ -1,5 +1,5 @@ import React, {useContext} from "react"; -import {HelpPopup, humanFileSize, PageContainer, TabLinks, useTitle, WROLModeMessage} from "./Common"; +import {HelpPopup, humanFileSize, isEmpty, PageContainer, TabLinks, useTitle, WROLModeMessage} from "./Common"; import {Route, Routes} from "react-router-dom"; import {getMapImportStatus, importMapFiles} from "../api"; import { @@ -131,7 +131,7 @@ class ManageMap extends React.Component { this.setState({'ready': false}); const {files, selectedPaths} = this.state; - if (!selectedPaths || selectedPaths.length === 0) { + if (isEmpty(selectedPaths)) { // No files are selected, import all. let paths = []; for (let i = 0; i < files.length; i++) { @@ -192,13 +192,13 @@ class ManageMap extends React.Component { pending.length > 1 ? 'Importing...' : `Importing ${pending[0]}` : null; - let disabled = dockerized || !files || files.length === 0 || import_running; + let disabled = dockerized || isEmpty(files) || import_running; let importButton = ; let rows; @@ -213,7 +213,7 @@ class ManageMap extends React.Component { ; - } else if (files.length === 0) { + } else if (isEmpty(files)) { rows = No PBF map files were found in map/pbf ; diff --git a/app/src/components/Theme.js b/app/src/components/Theme.js index c05494a3..7b31ea4a 100644 --- a/app/src/components/Theme.js +++ b/app/src/components/Theme.js @@ -22,6 +22,7 @@ import { TabPane as TAB_PANE, TextArea as TEXTAREA } from "semantic-ui-react"; +import {isEmpty} from "./Common"; export const darkTheme = 'dark'; export const lightTheme = 'light'; @@ -30,7 +31,7 @@ export const systemTheme = 'system'; export const themeSessionKey = 'color-scheme'; export function ThemeWrapper({children, ...props}) { - if (Object.keys(props).length > 0) { + if (!isEmpty(props)) { console.log(props); console.error('ThemeWrapper does not support props!'); } diff --git a/app/src/components/VideoPlayer.js b/app/src/components/VideoPlayer.js index 6492fa13..b49eddf1 100644 --- a/app/src/components/VideoPlayer.js +++ b/app/src/components/VideoPlayer.js @@ -1,7 +1,7 @@ import React, {useContext, useState} from 'react'; import {deleteVideos} from "../api"; import {Link, useNavigate, useParams} from "react-router-dom"; -import {humanFileSize, humanNumber, PageContainer, secondsToTimestamp, uploadDate, useTitle, VideoCard} from "./Common"; +import {humanFileSize, humanNumber, PageContainer, secondsToTimestamp, uploadDate, useTitle} from "./Common"; import {Confirm} from "semantic-ui-react"; import Grid from "semantic-ui-react/dist/commonjs/collections/Grid"; import Container from "semantic-ui-react/dist/commonjs/elements/Container"; @@ -9,6 +9,7 @@ import {VideoPlaceholder} from "./Placeholder"; import {useChannel} from "../hooks/customHooks"; import {ThemeContext} from "../contexts/contexts"; import {Button, darkTheme, Header, Icon, Segment, Tab, TabPane} from "./Theme"; +import {VideoCard} from "./Videos"; const MEDIA_PATH = '/media'; diff --git a/app/src/components/Videos.js b/app/src/components/Videos.js index 10789f63..1e508a9d 100644 --- a/app/src/components/Videos.js +++ b/app/src/components/Videos.js @@ -1,23 +1,42 @@ -import React, {useEffect, useState} from 'react'; +import React, {useContext, useEffect, useState} from 'react'; import {Link, Route, Routes, useParams} from "react-router-dom"; import { + CardLink, defaultSearchOrder, defaultVideoOrder, + Duration, + FileIcon, + isEmpty, + mimetypeColor, PageContainer, scrollToTop, SearchInput, TabLinks, + textEllipsis, + uploadDate, useTitle } from "./Common" import VideoPage from "./VideoPlayer"; -import {Confirm, Dropdown, PlaceholderHeader, PlaceholderLine, StatisticLabel, StatisticValue} from "semantic-ui-react"; +import { + Card, + Confirm, + Container, + Dropdown, + Image, + PlaceholderHeader, + PlaceholderLine, + StatisticLabel, + StatisticValue, + TableCell +} from "semantic-ui-react"; import {Channels, EditChannel, NewChannel} from "./Channels"; import {useChannel, useQuery, useSearchVideos, useVideo, useVideoStatistics} from "../hooks/customHooks"; import {FilesView} from "./Files"; import Grid from "semantic-ui-react/dist/commonjs/collections/Grid"; import Icon from "semantic-ui-react/dist/commonjs/elements/Icon"; -import {Button, Header, Loader, Placeholder, Segment, Statistic, StatisticGroup} from "./Theme"; +import {Button, CardIcon, Header, Loader, Placeholder, Segment, Statistic, StatisticGroup} from "./Theme"; import {deleteVideos} from "../api"; +import {ThemeContext} from "../contexts/contexts"; export function VideoWrapper() { const {videoId} = useParams(); @@ -149,7 +168,7 @@ function Videos({filter}) { const selectElm =
invertSelection()} - disabled={!videos || videos.length === 0} + disabled={isEmpty(videos)} > Invert @@ -279,3 +298,90 @@ export function VideosRoute(props) { ) } +export function VideoCard({file}) { + const {video} = file; + const {s} = useContext(ThemeContext); + + let video_url = `/videos/video/${video.id}`; + const upload_date = uploadDate(video.upload_date); + // A video may not have a channel. + const channel = video.channel ? video.channel : null; + let channel_url = null; + if (channel) { + channel_url = `/videos/channel/${channel.id}/video`; + video_url = `/videos/channel/${channel.id}/video/${video.id}`; + } + const poster_url = video.poster_path ? `/media/${encodeURIComponent(video.poster_path)}` : null; + + let imageLabel = null; + if (video.favorite) { + imageLabel = {corner: 'left', icon: 'heart', color: 'green'}; + } + let poster = ; + if (poster_url) { + poster = ; + } + + return ( + + {poster} + + + + + + +

{textEllipsis(video.title || video.stem || video.video_path, 100)}

+ +
+
+ + + {channel && + {channel.name} + } +

{upload_date}

+
+
+
+
) +} + +export function VideoRowCells({file}) { + const {video} = file; + + let video_url = `/videos/video/${video.id}`; + const poster_url = video.poster_path ? `/media/${encodeURIComponent(video.poster_path)}` : null; + + let poster; + let imageLabel = video.favorite ? {corner: 'left', icon: 'heart', color: 'green'} : null; + if (poster_url) { + poster = + + + } else { + poster = ; + } + + // Fragment for SelectableRow + return + +
+ {poster} +
+
+ + + {textEllipsis(video.title || video.stem || video.video_path, 100)} + + +
+} diff --git a/app/src/components/admin/Downloads.js b/app/src/components/admin/Downloads.js index 3272d644..3372bd89 100644 --- a/app/src/components/admin/Downloads.js +++ b/app/src/components/admin/Downloads.js @@ -2,7 +2,7 @@ import React, {useContext} from "react"; import {clearCompletedDownloads, clearFailedDownloads, killDownload, postDownload} from "../../api"; import {Link} from "react-router-dom"; import { - DisableDownloadsToggle, + DisableDownloadsToggle, isEmpty, secondsToElapsedPopup, secondsToFrequency, textEllipsis, @@ -301,7 +301,7 @@ export function Downloads() { ); let onceTable = tablePlaceholder; - if (onceDownloads && onceDownloads.length === 0) { + if (isEmpty(onceDownloads)) { onceTable =

No downloads are scheduled.

} else if (onceDownloads) { onceTable = ( @@ -320,7 +320,7 @@ export function Downloads() { } let recurringTable = tablePlaceholder; - if (recurringDownloads && recurringDownloads.length === 0) { + if (isEmpty(recurringDownloads)) { recurringTable =

No recurring downloads are scheduled.

} else if (recurringDownloads) { recurringTable =