Reformatting Channel page to display buttons better on mobile. Creating BackButton to consolidate functionality. Expanding search input on mobile.

This commit is contained in:
Roland 2022-11-30 19:31:18 -07:00
parent 759560dc6f
commit 9f52f41d56
No known key found for this signature in database
GPG key ID: 9D6DBD3ACF11AB93
6 changed files with 235 additions and 225 deletions

View file

@ -9,6 +9,7 @@ import {Link} from "react-router-dom";
import {BandwidthProgressCombined, CPUUsageProgress} from "./components/admin/Status";
import {ProgressPlaceholder} from "./components/Placeholder";
import {Divider} from "semantic-ui-react";
import Grid from "semantic-ui-react/dist/commonjs/collections/Grid";
export function Dashboard() {
useTitle('Dashboard');
@ -35,14 +36,20 @@ export function Dashboard() {
}
return (<PageContainer>
<SearchInput clearable
searchStr={searchStr}
onSubmit={setSearchStr}
size='large'
placeholder='Search Everywhere...'
actionIcon='search'
style={{marginBottom: '2em'}}
/>
<Grid>
<Grid.Row>
<Grid.Column mobile={16} computer={8}>
<SearchInput clearable
searchStr={searchStr}
onSubmit={setSearchStr}
size='large'
placeholder='Search Everywhere...'
actionIcon='search'
style={{marginBottom: '2em'}}
/>
</Grid.Column>
</Grid.Row>
</Grid>
{body}
</PageContainer>)
}

View file

@ -14,6 +14,7 @@ import {
TableRow
} from "semantic-ui-react";
import {
BackButton,
CardLink,
defaultSearchOrder,
ExternalCardLink,
@ -140,7 +141,7 @@ function ArchivePage() {
return (
<>
<Button icon='arrow left' content='Back' onClick={() => navigate(-1)}/>
<BackButton/>
<Segment>
{screenshot}

View file

@ -16,6 +16,7 @@ import {
import {createChannel, deleteChannel, downloadChannel, refreshChannel, updateChannel, validateRegex} from "../api";
import Container from "semantic-ui-react/dist/commonjs/elements/Container";
import {
BackButton,
DirectoryInput,
frequencyOptions,
humanFileSize,
@ -146,10 +147,12 @@ function ChannelPage({create, header}) {
download_frequency: channel.download_frequency,
match_regex: channel.match_regex,
};
setDisabled(true);
setLoading(true);
let response = null;
try {
setLoading(true);
if (create !== undefined) {
response = await createChannel(body);
} else {
@ -163,7 +166,9 @@ function ChannelPage({create, header}) {
description: 'Could not save channel',
time: 5000,
});
return;
} finally {
setDisabled(false);
setLoading(false);
}
@ -244,185 +249,184 @@ function ChannelPage({create, header}) {
setDeleteOpen(true);
}
const handleCancel = (e) => {
e.preventDefault();
navigate(-1);
}
return <Container fluid>
<Segment>
<Header as="h1">{header}</Header>
<WROLModeMessage content='Channel page is disabled while WROL Mode is enabled.'/>
<Form
id="editChannel"
onSubmit={handleSubmit}
error={error}
success={success}
autoComplete="off"
>
<FormGroup>
<FormField width={8}>
<FormInput required
label="Channel Name"
name="name"
type="text"
placeholder="Short Channel Name"
disabled={disabled}
error={errors.name}
value={channel.name}
onChange={(e, {value}) => changeValue('name', value)}
/>
</FormField>
<FormField width={8}>
<label>
Directory <RequiredAsterisk/>
</label>
<DirectoryInput required
disabled={create === undefined}
value={channel.directory}
setInput={value => changeValue('directory', value)}
placeholder='videos/channel directory'
/>
</FormField>
</FormGroup>
{
create !== undefined &&
<FormGroup>
<FormField width={8}/>{/* Empty field*/}
<FormField width={8}>
<FormField>
<Toggle
toggle
label="Create this directory, if it doesn't exist."
name="mkdir"
disabled={disabled}
error={errors.mkdir}
checked={channel.mkdir}
onChange={i => {
handleCheckbox(null, {name: 'mkdir', checked: i})
}}
/>
</FormField>
</FormField>
</FormGroup>
}
<FormGroup>
<FormField width={16}>
<FormInput
label="URL"
name="url"
type="url"
disabled={disabled}
placeholder='https://example.com/channel/videos'
error={errors.url}
value={channel.url}
onChange={handleInputChange}
/>
</FormField>
</FormGroup>
<BackButton/>
{!create &&
<Link to={`/videos/channel/${channel.id}/video`}>
<Button>Videos</Button>
</Link>}
<FormGroup>
<FormField>
<label>Download Frequency</label>
<Dropdown selection
name='download_frequency'
placeholder='Frequency'
error={errors.download_frequency}
value={channel.download_frequency}
disabled={disabled || !channel.url}
options={frequencyOptions}
onChange={handleInputChange}
/>
</FormField>
</FormGroup>
<Accordion style={{marginBottom: '1em'}}>
<AccordionTitle
onClick={handleAdvancedClick}
index={0}
active={activeIndex === 0}
>
<Icon name='dropdown'/>
Advanced Settings
</AccordionTitle>
<AccordionContent active={activeIndex === 0}>
<Segment secondary>
<Header as="h4">
The following settings are encouraged by default, modify them at your own risk.
</Header>
<FormField>
<FormInput
label="Title Match Regex"
name="match_regex"
type="text"
disabled={disabled}
error={!validRegex}
placeholder='.*([Nn]ame Matching).*'
value={channel.match_regex}
onChange={checkRegex}
/>
</FormField>
</Segment>
</AccordionContent>
</Accordion>
<Container>
<Message error
header={messageHeader}
content={messageContent}
<Header as="h1">{header}</Header>
<WROLModeMessage content='Channel page is disabled while WROL Mode is enabled.'/>
<Form
id="editChannel"
onSubmit={handleSubmit}
error={error}
success={success}
autoComplete="off"
>
<FormGroup>
<FormField width={8}>
<FormInput required
label="Channel Name"
name="name"
type="text"
placeholder="Short Channel Name"
disabled={disabled}
error={errors.name}
value={channel.name}
onChange={(e, {value}) => changeValue('name', value)}
/>
<Message success
header={messageHeader}
content={messageContent}
</FormField>
<FormField width={8}>
<label>
Directory <RequiredAsterisk/>
</label>
<DirectoryInput required
disabled={create === undefined}
value={channel.directory}
setInput={value => changeValue('directory', value)}
placeholder='videos/channel directory'
/>
<Button
color="green"
type="submit"
floated='right'
>
{disabled ? <Loader active inline/> : 'Save'}
</Button>
<Button
secondary
floated='right'
onClick={handleCancel}
>
Cancel
</Button>
{!create &&
<>
<Button color='red' onClick={handleDeleteButton}>
Delete
</Button>
<Confirm
open={deleteOpen}
content='Are you sure you want to delete this channel? No video files will be deleted.'
confirmButton='Delete'
onCancel={() => setDeleteOpen(false)}
onConfirm={handleDeleteConfirm}
</FormField>
</FormGroup>
{
create !== undefined &&
<FormGroup>
<FormField width={8}/>{/* Empty field*/}
<FormField width={8}>
<FormField>
<Toggle
toggle
label="Create this directory, if it doesn't exist."
name="mkdir"
disabled={disabled}
error={errors.mkdir}
checked={channel.mkdir}
onChange={i => {
handleCheckbox(null, {name: 'mkdir', checked: i})
}}
/>
<Button
color='violet'
onClick={handleDownloadChannel}
disabled={!channel.url || !channel.download_frequency}
>
Download
</Button>
<Button color='blue' onClick={handleRefreshChannel}>
Refresh
</Button>
<Link to={`/videos/channel/${channel.id}/video`}>
<Button>Videos</Button>
</Link>
</>
}
</Container>
</Form>
</Segment>
{channel.statistics && <ChannelStatistics statistics={channel.statistics}/>}
</FormField>
</FormField>
</FormGroup>
}
<FormGroup>
<FormField width={16}>
<FormInput
label="URL"
name="url"
type="url"
disabled={disabled}
placeholder='https://example.com/channel/videos'
error={errors.url}
value={channel.url}
onChange={handleInputChange}
/>
</FormField>
</FormGroup>
<FormGroup>
<FormField>
<label>Download Frequency</label>
<Dropdown selection
name='download_frequency'
placeholder='Frequency'
error={errors.download_frequency}
value={channel.download_frequency}
disabled={disabled || !channel.url}
options={frequencyOptions}
onChange={handleInputChange}
/>
</FormField>
</FormGroup>
<Accordion style={{marginBottom: '1em'}}>
<AccordionTitle
onClick={handleAdvancedClick}
index={0}
active={activeIndex === 0}
>
<Icon name='dropdown'/>
Advanced Settings
</AccordionTitle>
<AccordionContent active={activeIndex === 0}>
<Segment secondary>
<Header as="h4">
The following settings are encouraged by default, modify them at your own risk.
</Header>
<FormField>
<FormInput
label="Title Match Regex"
name="match_regex"
type="text"
disabled={disabled}
error={!validRegex}
placeholder='.*([Nn]ame Matching).*'
value={channel.match_regex}
onChange={checkRegex}
/>
</FormField>
</Segment>
</AccordionContent>
</Accordion>
<Message error
header={messageHeader}
content={messageContent}
/>
<Message success
header={messageHeader}
content={messageContent}
/>
<Grid stackable columns={2}>
<Grid.Row>
<Grid.Column>
{!create &&
<>
<Button color='red' onClick={handleDeleteButton} size='small'>
Delete
</Button>
<Confirm
open={deleteOpen}
content='Are you sure you want to delete this channel? No video files will be deleted.'
confirmButton='Delete'
onCancel={() => setDeleteOpen(false)}
onConfirm={handleDeleteConfirm}
/>
<Button
color='violet'
size='small'
onClick={handleDownloadChannel}
disabled={!channel.url || !channel.download_frequency}
>
Download
</Button>
<Button color='blue' size='small' onClick={handleRefreshChannel}>
Refresh
</Button>
</>
}
</Grid.Column>
<Grid.Column>
<Button
color="green"
size='big'
type="submit"
floated='right'
>
{disabled ? <Loader active inline/> : 'Save'}
</Button>
</Grid.Column>
</Grid.Row>
</Grid>
</Form>
<div style={{marginTop: '2em'}}>
{channel.statistics && <ChannelStatistics statistics={channel.statistics}/>}
</div>
</Container>
}
@ -494,28 +498,27 @@ export function Channels() {
setSearchStr(value);
}
let header = <Grid columns={2} style={{marginBottom: '1em'}}>
<Grid.Column>
<Header as='h1'>Channels</Header>
</Grid.Column>
<Grid.Column textAlign='right'>
<Input
icon='search'
placeholder='Name filter...'
size="large"
name="filterStr"
value={searchStr}
onChange={handleInputChange}/>
</Grid.Column>
<Grid.Row>
<Grid.Column/>
<Grid.Column textAlign='right'>
<Link to='/videos/channel/new'>
<Button secondary>New Channel</Button>
</Link>
</Grid.Column>
</Grid.Row>;
</Grid>;
let header = <div style={{marginBottom: '1em'}}>
<Header as='h1'>Channels</Header>
<Grid stackable columns={2}>
<Grid.Row>
<Grid.Column>
<Input fluid
icon='search'
placeholder='Name filter...'
size="large"
name="filterStr"
value={searchStr}
onChange={handleInputChange}/>
</Grid.Column>
<Grid.Column textAlign='right'>
<Link to='/videos/channel/new'>
<Button secondary>New Channel</Button>
</Link>
</Grid.Column>
</Grid.Row>
</Grid>
</div>;
let tableHeader = <TableHeader>
<TableRow>

View file

@ -1,6 +1,6 @@
import React, {useContext, useEffect, useState} from "react";
import {Card, Container, IconGroup, Image, Input, Modal, Pagination, Responsive} from 'semantic-ui-react';
import {Link, NavLink} from "react-router-dom";
import {Link, NavLink, useNavigate} from "react-router-dom";
import Message from "semantic-ui-react/dist/commonjs/collections/Message";
import {useDirectories, useDownloaders, useHotspot, useSettings, useThrottle} from "../hooks/customHooks";
import {StatusContext, ThemeContext} from "../contexts/contexts";
@ -344,7 +344,7 @@ export function SearchInput({
}
return <Form onSubmit={localOnSubmit} {...props}>
<Input
<Input fluid
placeholder={placeholder}
type='text'
onChange={(e) => setValue(e.target.value)}
@ -740,3 +740,8 @@ export function DirectoryInput({disabled, error, placeholder, setInput, value, r
</datalist>
</div>);
}
export const BackButton = () => {
const navigate = useNavigate();
return <Button icon='arrow left' content='Back' onClick={() => navigate(-1)}/>;
}

View file

@ -2,7 +2,7 @@ import React, {useContext, useState} from 'react';
import {deleteVideos} from "../api";
import {Link, useNavigate, useParams} from "react-router-dom";
import _ from "lodash";
import {humanFileSize, humanNumber, PageContainer, uploadDate, useTitle} from "./Common";
import {BackButton, humanFileSize, humanNumber, PageContainer, 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";
@ -153,14 +153,8 @@ function VideoPage({videoFile, prevFile, nextFile, setFavorite, ...props}) {
const tabMenu = theme === darkTheme ? {inverted: true, attached: true} : {attached: true};
return (<>
<Container>
<Button
style={{marginTop: '1em', marginBottom: '1em'}}
onClick={() => navigate(-1)}
>
<Icon name='left arrow'/>
Back
</Button>
<Container style={{margin: '1em'}}>
<BackButton/>
</Container>
<video controls

View file

@ -1,4 +1,4 @@
import React, {useContext, useEffect, useState} from 'react';
import React, {Fragment, useContext, useEffect, useState} from 'react';
import {Link, Route, Routes, useParams} from "react-router-dom";
import {
CardLink,
@ -50,7 +50,7 @@ export function VideoWrapper() {
setFavorite={setFavorite} autoplay={true}/>
}
function Videos({filter}) {
function VideosPage({filter}) {
let title = 'Videos';
if (filter && filter.indexOf('favorite') >= 0) {
title = 'Favorite Videos';
@ -105,7 +105,7 @@ function Videos({filter}) {
videoOrders = [{key: 'rank', value: 'rank', text: 'Search Rank'}, ...videoOrders];
}
const menuColumns = <>
const menuColumns = <Fragment>
<Grid.Column mobile={8} computer={5}>
<SearchInput clearable searchStr={searchStr} onSubmit={setSearchStr} actionIcon='search'/>
</Grid.Column>
@ -118,7 +118,7 @@ function Videos({filter}) {
onChange={(e, {value}) => setOrderBy(value)}
/>
</Grid.Column>
</>;
</Fragment>;
let header;
if (channel && channel.name) {
@ -287,13 +287,13 @@ export function VideosRoute(props) {
<PageContainer>
<TabLinks links={links}/>
<Routes>
<Route path='/' exact element={<Videos/>}/>
<Route path='favorites' exact element={<Videos filter='favorite' header='Favorite Videos'/>}/>
<Route path='/' exact element={<VideosPage/>}/>
<Route path='favorites' exact element={<VideosPage filter='favorite' header='Favorite Videos'/>}/>
<Route path='channel' exact element={<Channels/>}/>
<Route path='statistics' exact element={<Statistics/>}/>
<Route path='channel/new' exact element={<NewChannel/>}/>
<Route path='channel/:channelId/edit' exact element={<EditChannel/>}/>
<Route path='channel/:channelId/video' exact element={<Videos/>}/>
<Route path='channel/:channelId/video' exact element={<VideosPage/>}/>
</Routes>
</PageContainer>
)