mirror of
https://github.com/lrnselfreliance/wrolpi.git
synced 2026-10-26 19:01:39 -04:00
Reformatting Channel page to display buttons better on mobile. Creating BackButton to consolidate functionality. Expanding search input on mobile.
This commit is contained in:
parent
759560dc6f
commit
9f52f41d56
6 changed files with 235 additions and 225 deletions
|
|
@ -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>)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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)}/>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue