mirror of
https://github.com/Sonarr/Sonarr.git
synced 2026-04-21 22:05:38 -04:00
v3 UI
This commit is contained in:
committed by
Taloth Saldono
parent
99feff549d
commit
5894b4fd95
@@ -0,0 +1,18 @@
|
||||
.link {
|
||||
composes: link from 'Components/Link/Link.css';
|
||||
|
||||
display: block;
|
||||
}
|
||||
|
||||
.freeSpace,
|
||||
.unmappedFolders {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 150px;
|
||||
}
|
||||
|
||||
.actions {
|
||||
composes: cell from 'Components/Table/Cells/TableRowCell.css';
|
||||
|
||||
width: 45px;
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React from 'react';
|
||||
import formatBytes from 'Utilities/Number/formatBytes';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import Link from 'Components/Link/Link';
|
||||
import TableRow from 'Components/Table/TableRow';
|
||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||
import styles from './ImportSeriesRootFolderRow.css';
|
||||
|
||||
function ImportSeriesRootFolderRow(props) {
|
||||
const {
|
||||
id,
|
||||
path,
|
||||
freeSpace,
|
||||
unmappedFolders,
|
||||
onDeletePress
|
||||
} = props;
|
||||
|
||||
const unmappedFoldersCount = unmappedFolders.length || '-';
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<TableRowCell>
|
||||
<Link
|
||||
className={styles.link}
|
||||
to={`/add/import/${id}`}
|
||||
>
|
||||
{path}
|
||||
</Link>
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.freeSpace}>
|
||||
{formatBytes(freeSpace) || '-'}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.unmappedFolders}>
|
||||
{unmappedFoldersCount}
|
||||
</TableRowCell>
|
||||
|
||||
<TableRowCell className={styles.actions}>
|
||||
<IconButton
|
||||
title="Remove root folder"
|
||||
name={icons.REMOVE}
|
||||
onPress={onDeletePress}
|
||||
/>
|
||||
</TableRowCell>
|
||||
</TableRow>
|
||||
);
|
||||
}
|
||||
|
||||
ImportSeriesRootFolderRow.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
path: PropTypes.string.isRequired,
|
||||
freeSpace: PropTypes.number.isRequired,
|
||||
unmappedFolders: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onDeletePress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
ImportSeriesRootFolderRow.defaultProps = {
|
||||
freeSpace: 0,
|
||||
unmappedFolders: []
|
||||
};
|
||||
|
||||
export default ImportSeriesRootFolderRow;
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { deleteRootFolder } from 'Store/Actions/rootFolderActions';
|
||||
import ImportSeriesRootFolderRow from './ImportSeriesRootFolderRow';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
() => {
|
||||
return {
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
deleteRootFolder
|
||||
};
|
||||
|
||||
class ImportSeriesRootFolderRowConnector extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onDeletePress = () => {
|
||||
this.props.deleteRootFolder({ id: this.props.id });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ImportSeriesRootFolderRow
|
||||
{...this.props}
|
||||
onDeletePress={this.onDeletePress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ImportSeriesRootFolderRowConnector.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
deleteRootFolder: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(ImportSeriesRootFolderRowConnector);
|
||||
@@ -0,0 +1,32 @@
|
||||
.header {
|
||||
margin-bottom: 40px;
|
||||
text-align: center;
|
||||
font-weight: 300;
|
||||
font-size: 36px;
|
||||
}
|
||||
|
||||
.tips {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.tip {
|
||||
font-size: $defaultFontSize;
|
||||
}
|
||||
|
||||
.code {
|
||||
font-size: 12px;
|
||||
font-family: $monoSpaceFontFamily;
|
||||
}
|
||||
|
||||
.recentFolders {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.startImport {
|
||||
margin-top: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.importButtonIcon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { icons, kinds, sizes } from 'Helpers/Props';
|
||||
import Button from 'Components/Link/Button';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Icon from 'Components/Icon';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FileBrowserModal from 'Components/FileBrowser/FileBrowserModal';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
||||
import Table from 'Components/Table/Table';
|
||||
import TableBody from 'Components/Table/TableBody';
|
||||
import ImportSeriesRootFolderRowConnector from './ImportSeriesRootFolderRowConnector';
|
||||
import styles from './ImportSeriesSelectFolder.css';
|
||||
|
||||
const rootFolderColumns = [
|
||||
{
|
||||
name: 'path',
|
||||
label: 'Path',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'freeSpace',
|
||||
label: 'Free Space',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'unmappedFolders',
|
||||
label: 'Unmapped Folders',
|
||||
isVisible: true
|
||||
},
|
||||
{
|
||||
name: 'actions',
|
||||
isVisible: true
|
||||
}
|
||||
];
|
||||
|
||||
class ImportSeriesSelectFolder extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isAddNewRootFolderModalOpen: false
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
onAddNewRootFolderPress = () => {
|
||||
this.setState({ isAddNewRootFolderModalOpen: true });
|
||||
}
|
||||
|
||||
onNewRootFolderSelect = ({ value }) => {
|
||||
this.props.onNewRootFolderSelect(value);
|
||||
}
|
||||
|
||||
onAddRootFolderModalClose = () => {
|
||||
this.setState({ isAddNewRootFolderModalOpen: false });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
isWindows,
|
||||
isFetching,
|
||||
isPopulated,
|
||||
error,
|
||||
items
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<PageContent title="Import Series">
|
||||
<PageContentBodyConnector>
|
||||
{
|
||||
isFetching && !isPopulated &&
|
||||
<LoadingIndicator />
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && !!error &&
|
||||
<div>Unable to load root folders</div>
|
||||
}
|
||||
|
||||
{
|
||||
!error && isPopulated &&
|
||||
<div>
|
||||
<div className={styles.header}>
|
||||
Import series you already have
|
||||
</div>
|
||||
|
||||
<div className={styles.tips}>
|
||||
Some tips to ensure the import goes smoothly:
|
||||
<ul>
|
||||
<li className={styles.tip}>
|
||||
Make sure your files include the quality in the name. eg. <span className={styles.code}>episode.s02e15.bluray.mkv</span>
|
||||
</li>
|
||||
<li className={styles.tip}>
|
||||
Point Sonarr to the folder containing all of your tv shows not a specific one. eg. <span className={styles.code}>"{isWindows ? 'C:\\tv shows' : '/tv shows'}"</span> and not <span className={styles.code}>"{isWindows ? 'C:\\tv shows\\the simpsons' : '/tv shows/the simpsons'}"</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{
|
||||
items.length > 0 ?
|
||||
<div className={styles.recentFolders}>
|
||||
<FieldSet legend="Recent Folders">
|
||||
<Table
|
||||
columns={rootFolderColumns}
|
||||
>
|
||||
<TableBody>
|
||||
{
|
||||
items.map((rootFolder) => {
|
||||
return (
|
||||
<ImportSeriesRootFolderRowConnector
|
||||
key={rootFolder.id}
|
||||
id={rootFolder.id}
|
||||
path={rootFolder.path}
|
||||
freeSpace={rootFolder.freeSpace}
|
||||
unmappedFolders={rootFolder.unmappedFolders}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</FieldSet>
|
||||
|
||||
<Button
|
||||
kind={kinds.PRIMARY}
|
||||
size={sizes.LARGE}
|
||||
onPress={this.onAddNewRootFolderPress}
|
||||
>
|
||||
<Icon
|
||||
className={styles.importButtonIcon}
|
||||
name={icons.DRIVE}
|
||||
/>
|
||||
Choose another folder
|
||||
</Button>
|
||||
</div> :
|
||||
|
||||
<div className={styles.startImport}>
|
||||
<Button
|
||||
kind={kinds.PRIMARY}
|
||||
size={sizes.LARGE}
|
||||
onPress={this.onAddNewRootFolderPress}
|
||||
>
|
||||
<Icon
|
||||
className={styles.importButtonIcon}
|
||||
name={icons.DRIVE}
|
||||
/>
|
||||
Start Import
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
|
||||
<FileBrowserModal
|
||||
isOpen={this.state.isAddNewRootFolderModalOpen}
|
||||
name="rootFolderPath"
|
||||
value=""
|
||||
onChange={this.onNewRootFolderSelect}
|
||||
onModalClose={this.onAddRootFolderModalClose}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</PageContentBodyConnector>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ImportSeriesSelectFolder.propTypes = {
|
||||
isWindows: PropTypes.bool.isRequired,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
isPopulated: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
onNewRootFolderSelect: PropTypes.func.isRequired,
|
||||
onDeleteRootFolderPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default ImportSeriesSelectFolder;
|
||||
@@ -0,0 +1,91 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { push } from 'react-router-redux';
|
||||
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||
import { fetchRootFolders, addRootFolder, deleteRootFolder } from 'Store/Actions/rootFolderActions';
|
||||
import ImportSeriesSelectFolder from './ImportSeriesSelectFolder';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.rootFolders,
|
||||
createSystemStatusSelector(),
|
||||
(rootFolders, systemStatus) => {
|
||||
return {
|
||||
...rootFolders,
|
||||
isWindows: systemStatus.isWindows
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchRootFolders,
|
||||
addRootFolder,
|
||||
deleteRootFolder,
|
||||
push
|
||||
};
|
||||
|
||||
class ImportSeriesSelectFolderConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchRootFolders();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const {
|
||||
items,
|
||||
isSaving,
|
||||
saveError
|
||||
} = this.props;
|
||||
|
||||
if (prevProps.isSaving && !isSaving && !saveError) {
|
||||
const newRootFolders = _.differenceBy(items, prevProps.items, (item) => item.id);
|
||||
|
||||
if (newRootFolders.length === 1) {
|
||||
this.props.push(`${window.Sonarr.urlBase}/add/import/${newRootFolders[0].id}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onNewRootFolderSelect = (path) => {
|
||||
this.props.addRootFolder({ path });
|
||||
}
|
||||
|
||||
onDeleteRootFolderPress = (id) => {
|
||||
this.props.deleteRootFolder({ id });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<ImportSeriesSelectFolder
|
||||
{...this.props}
|
||||
onNewRootFolderSelect={this.onNewRootFolderSelect}
|
||||
onDeleteRootFolderPress={this.onDeleteRootFolderPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ImportSeriesSelectFolderConnector.propTypes = {
|
||||
isSaving: PropTypes.bool.isRequired,
|
||||
saveError: PropTypes.object,
|
||||
items: PropTypes.arrayOf(PropTypes.object).isRequired,
|
||||
fetchRootFolders: PropTypes.func.isRequired,
|
||||
addRootFolder: PropTypes.func.isRequired,
|
||||
deleteRootFolder: PropTypes.func.isRequired,
|
||||
push: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(ImportSeriesSelectFolderConnector);
|
||||
Reference in New Issue
Block a user