Creating isEmpty function to check if dict/array have some contents.

This commit is contained in:
Roland 2022-11-04 09:58:38 -06:00
parent 92bd1de8a0
commit dad276d6ec
No known key found for this signature in database
GPG key ID: 9D6DBD3ACF11AB93
10 changed files with 189 additions and 159 deletions

View file

@ -19,6 +19,7 @@ import {
ExternalCardLink,
FileIcon,
HelpHeader,
isEmpty,
mimetypeColor,
PageContainer,
SearchInput,
@ -113,9 +114,9 @@ function ArchivePage() {
</>);
let alternativesList = <Loader active/>;
if (alternatives && alternatives.length > 0) {
if (!isEmpty(alternatives)) {
alternativesList = <FileCards files={alternatives}/>;
} else if (alternatives && alternatives.length === 0) {
} else if (isEmpty(alternatives)) {
alternativesList = <p>No alternatives available</p>;
}
@ -235,7 +236,7 @@ export function Domains() {
</PlaceholderHeader>
</Placeholder>
</>)
} else if (!domains || domains.length === 0) {
} else if (isEmpty(domains)) {
return <Message>
<Message.Header>No domains yet.</Message.Header>
<Message.Content>Archive some webpages!</Message.Content>
@ -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 = <div style={{marginTop: '0.5em'}}>
<Button
color='red'
disabled={!selectedArchives || selectedArchives.length === 0}
disabled={isEmpty(selectedArchives)}
onClick={() => setDeleteOpen(true)}
>Delete</Button>
<Confirm
@ -392,14 +393,14 @@ function Archives() {
<Button
color='grey'
onClick={() => invertSelection()}
disabled={!archives || archives.length === 0}
disabled={isEmpty(archives)}
>
Invert
</Button>
<Button
color='yellow'
onClick={() => clearSelection()}
disabled={(archives && archives.length === 0) || selectedArchives.length === 0}
disabled={isEmpty(archives) || isEmpty(selectedArchives)}
>
Clear
</Button>
@ -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 = <CardLink to={archiveUrl}>
<Image wrapped src={posterUrl} width='50px'/>
</CardLink>;
} else {
poster = <FileIcon file={file} size='large'/>;
}
// Fragment for SelectableRow
return (<React.Fragment>
<TableCell>
<center>{poster}</center>
</TableCell>
<TableCell>
<CardLink to={archiveUrl}>
{textEllipsis(archive.title || archive.stem, 100)}
</CardLink>
</TableCell>
</React.Fragment>)
}
export function ArchiveRoute() {
const links = [
{text: 'Archives', to: '/archive', end: true},

View file

@ -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() {
</TableBody>
</Table>
</>
} else if (channels.length === 0) {
} else if (isEmpty(channels)) {
return <>
{header}
<Message>

View file

@ -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}) {
</a>
}
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 = <CardIcon><FileIcon file={file}/></CardIcon>;
if (poster_url) {
poster = <Image wrapped
src={poster_url}
label={imageLabel}
style={{position: 'relative', width: '100%'}}
/>;
}
return (<Card color={mimetypeColor(file.mimetype)}>
<Link to={video_url}>
{poster}
</Link>
<Duration video={video}/>
<Card.Content {...s}>
<Card.Header>
<Container textAlign='left'>
<Link to={video_url} className="no-link-underscore card-link">
<p {...s}>{textEllipsis(video.title || video.stem || video.video_path, 100)}</p>
</Link>
</Container>
</Card.Header>
<Card.Description>
<Container textAlign='left'>
{channel && <Link to={channel_url} className="no-link-underscore card-link">
<b {...s}>{channel.name}</b>
</Link>}
<p {...s}>{upload_date}</p>
</Container>
</Card.Description>
</Card.Content>
</Card>)
}
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 = <CardLink to={video_url}>
<Image wrapped
src={poster_url}
label={imageLabel}
width='50px'
/>
</CardLink>
} else {
poster = <FileIcon file={file} size='large'/>;
}
// Fragment for SelectableRow
return <React.Fragment>
<TableCell>
<center>
{poster}
</center>
</TableCell>
<TableCell>
<CardLink to={video_url}>
{textEllipsis(video.title || video.stem || video.video_path, 100)}
</CardLink>
</TableCell>
</React.Fragment>
}
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 = <CardLink to={archiveUrl}>
<Image wrapped src={posterUrl} width='50px'/>
</CardLink>;
} else {
poster = <FileIcon file={file} size='large'/>;
}
// Fragment for SelectableRow
return (<React.Fragment>
<TableCell>
<center>{poster}</center>
</TableCell>
<TableCell>
<CardLink to={archiveUrl}>
{textEllipsis(archive.title || archive.stem, 100)}
</CardLink>
</TableCell>
</React.Fragment>)
}
export function RequiredAsterisk() {
return <span style={{color: '#db2828'}}> *</span>
}
@ -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"

View file

@ -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: <Icon name='file'/>,
@ -199,11 +198,11 @@ function FileCard({file}) {
}
export function FileCards({files}) {
if (files && files.length > 0) {
if (!isEmpty(files)) {
return <CardGroupCentered>
{files.map((i) => <FileCard key={i['path']} file={i}/>)}
</CardGroupCentered>
} else if (files && files.length === 0) {
} else if (isEmpty(files)) {
return <Header as='h4'>No files found.</Header>
} else {
return <CardGroupCentered><CardPlacholder/></CardGroupCentered>
@ -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 => <FileRow key={i['key']} file={i}/>);
return <SelectableTable
@ -267,7 +266,7 @@ export function FileTable({files, selectOn, onSelect, footer, selectedKeys}) {
footer={footer}
rows={rows}
/>;
} else if (files && files.length === 0) {
} else if (isEmpty(files)) {
return <Segment><p>No results!</p></Segment>
} else {
return <Placeholder>

View file

@ -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() {
</TableRow>
}
if (byCategory && byCategory.length > 0) {
if (!isEmpty(byCategory)) {
return <Table>
<TableHeader>
<TableRow>
@ -77,7 +77,7 @@ function InventorySummary() {
</TableRow>
}
if (bySubcategory && bySubcategory.length > 0) {
if (!isEmpty(bySubcategory )) {
return <Table>
<TableHeader>
<TableRow>
@ -106,7 +106,7 @@ function InventorySummary() {
</TableRow>
}
if (byName && byName.length > 0) {
if (!isEmpty(byName)) {
return <Table>
<TableHeader>
@ -309,7 +309,7 @@ class InventoryList extends React.Component {
}
render() {
if (this.props.items.length === 0) {
if (isEmpty(this.props.items)) {
return <ThemeContext.Consumer>
{({t}) => (
<p {...t}>Add some items using the form above!</p>
@ -386,7 +386,7 @@ class InventoryList extends React.Component {
<Button
color='red'
onClick={this.handleRemove}
disabled={this.state.checkboxes.length === 0}
disabled={isEmpty(this.state.checkboxes)}
floated='right'
>
Remove
@ -394,7 +394,7 @@ class InventoryList extends React.Component {
<Button
secondary
onClick={this.handleSave}
disabled={this.state.checkboxes.length === 0}
disabled={isEmpty(this.state.checkboxes)}
>
Save
</Button>

View file

@ -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 = <Button
primary
disabled={disabled}
onClick={this.import}
>
{selectedPaths.length > 0 ? 'Import Selected' : 'Import All'}
{!isEmpty(selectedPaths) ? 'Import Selected' : 'Import All'}
</Button>;
let rows;
@ -213,7 +213,7 @@ class ManageMap extends React.Component {
</Placeholder>
</TableCell>
</TableRow>;
} else if (files.length === 0) {
} else if (isEmpty(files)) {
rows = <TableRow>
<TableCell/><TableCell colSpan={5}>No PBF map files were found in <b>map/pbf</b></TableCell>
</TableRow>;

View file

@ -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!');
}

View file

@ -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';

View file

@ -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 = <div style={{marginTop: '0.5em'}}>
<Button
color='red'
disabled={!selectedVideos || selectedVideos.length === 0}
disabled={isEmpty(selectedVideos)}
onClick={() => setDeleteOpen(true)}
>Delete</Button>
<Confirm
@ -162,14 +181,14 @@ function Videos({filter}) {
<Button
color='grey'
onClick={() => invertSelection()}
disabled={!videos || videos.length === 0}
disabled={isEmpty(videos)}
>
Invert
</Button>
<Button
color='yellow'
onClick={() => clearSelection()}
disabled={(videos && videos.length === 0) || selectedVideos.length === 0}
disabled={isEmpty(videos) || isEmpty(selectedVideos)}
>
Clear
</Button>
@ -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 = <CardIcon><FileIcon file={file}/></CardIcon>;
if (poster_url) {
poster = <Image wrapped
src={poster_url}
label={imageLabel}
style={{position: 'relative', width: '100%'}}
/>;
}
return (<Card color={mimetypeColor(file.mimetype)}>
<Link to={video_url}>
{poster}
</Link>
<Duration video={video}/>
<Card.Content {...s}>
<Card.Header>
<Container textAlign='left'>
<Link to={video_url} className="no-link-underscore card-link">
<p {...s}>{textEllipsis(video.title || video.stem || video.video_path, 100)}</p>
</Link>
</Container>
</Card.Header>
<Card.Description>
<Container textAlign='left'>
{channel && <Link to={channel_url} className="no-link-underscore card-link">
<b {...s}>{channel.name}</b>
</Link>}
<p {...s}>{upload_date}</p>
</Container>
</Card.Description>
</Card.Content>
</Card>)
}
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 = <CardLink to={video_url}>
<Image wrapped
src={poster_url}
label={imageLabel}
width='50px'
/>
</CardLink>
} else {
poster = <FileIcon file={file} size='large'/>;
}
// Fragment for SelectableRow
return <React.Fragment>
<TableCell>
<center>
{poster}
</center>
</TableCell>
<TableCell>
<CardLink to={video_url}>
{textEllipsis(video.title || video.stem || video.video_path, 100)}
</CardLink>
</TableCell>
</React.Fragment>
}

View file

@ -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 = <p {...t}>No downloads are scheduled.</p>
} else if (onceDownloads) {
onceTable = (
@ -320,7 +320,7 @@ export function Downloads() {
}
let recurringTable = tablePlaceholder;
if (recurringDownloads && recurringDownloads.length === 0) {
if (isEmpty(recurringDownloads)) {
recurringTable = <p {...t}>No recurring downloads are scheduled.</p>
} else if (recurringDownloads) {
recurringTable = <Table>