Convert Root Folders to Typescript
This commit is contained in:
parent
8bd91bd86b
commit
a5aab810d7
|
@ -10,6 +10,7 @@ import ImportList from 'typings/ImportList';
|
||||||
import Indexer from 'typings/Indexer';
|
import Indexer from 'typings/Indexer';
|
||||||
import Notification from 'typings/Notification';
|
import Notification from 'typings/Notification';
|
||||||
import QualityProfile from 'typings/QualityProfile';
|
import QualityProfile from 'typings/QualityProfile';
|
||||||
|
import RootFolder from 'typings/RootFolder';
|
||||||
import { UiSettings } from 'typings/UiSettings';
|
import { UiSettings } from 'typings/UiSettings';
|
||||||
|
|
||||||
export interface DownloadClientAppState
|
export interface DownloadClientAppState
|
||||||
|
@ -35,6 +36,11 @@ export interface QualityProfilesAppState
|
||||||
extends AppSectionState<QualityProfile>,
|
extends AppSectionState<QualityProfile>,
|
||||||
AppSectionSchemaState<QualityProfile> {}
|
AppSectionSchemaState<QualityProfile> {}
|
||||||
|
|
||||||
|
export interface RootFolderAppState
|
||||||
|
extends AppSectionState<RootFolder>,
|
||||||
|
AppSectionDeleteState,
|
||||||
|
AppSectionSaveState {}
|
||||||
|
|
||||||
export type LanguageSettingsAppState = AppSectionState<Language>;
|
export type LanguageSettingsAppState = AppSectionState<Language>;
|
||||||
export type UiSettingsAppState = AppSectionItemState<UiSettings>;
|
export type UiSettingsAppState = AppSectionItemState<UiSettings>;
|
||||||
|
|
||||||
|
@ -45,6 +51,7 @@ interface SettingsAppState {
|
||||||
languages: LanguageSettingsAppState;
|
languages: LanguageSettingsAppState;
|
||||||
notifications: NotificationAppState;
|
notifications: NotificationAppState;
|
||||||
qualityProfiles: QualityProfilesAppState;
|
qualityProfiles: QualityProfilesAppState;
|
||||||
|
rootFolders: RootFolderAppState;
|
||||||
ui: UiSettingsAppState;
|
ui: UiSettingsAppState;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,81 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import Label from 'Components/Label';
|
|
||||||
import IconButton from 'Components/Link/IconButton';
|
|
||||||
import Link from 'Components/Link/Link';
|
|
||||||
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
|
||||||
import TableRow from 'Components/Table/TableRow';
|
|
||||||
import { icons, kinds } from 'Helpers/Props';
|
|
||||||
import formatBytes from 'Utilities/Number/formatBytes';
|
|
||||||
import styles from './RootFolderRow.css';
|
|
||||||
|
|
||||||
function RootFolderRow(props) {
|
|
||||||
const {
|
|
||||||
id,
|
|
||||||
path,
|
|
||||||
accessible,
|
|
||||||
freeSpace,
|
|
||||||
unmappedFolders,
|
|
||||||
onDeletePress
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
const isUnavailable = !accessible;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TableRow>
|
|
||||||
<TableRowCell>
|
|
||||||
{
|
|
||||||
isUnavailable ?
|
|
||||||
<div className={styles.unavailablePath}>
|
|
||||||
{path}
|
|
||||||
|
|
||||||
<Label
|
|
||||||
className={styles.unavailableLabel}
|
|
||||||
kind={kinds.DANGER}
|
|
||||||
>
|
|
||||||
Unavailable
|
|
||||||
</Label>
|
|
||||||
</div> :
|
|
||||||
|
|
||||||
<Link
|
|
||||||
className={styles.link}
|
|
||||||
to={`/add/import/${id}`}
|
|
||||||
>
|
|
||||||
{path}
|
|
||||||
</Link>
|
|
||||||
}
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell className={styles.freeSpace}>
|
|
||||||
{(isUnavailable || isNaN(freeSpace)) ? '-' : formatBytes(freeSpace)}
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell className={styles.unmappedFolders}>
|
|
||||||
{isUnavailable ? '-' : unmappedFolders.length}
|
|
||||||
</TableRowCell>
|
|
||||||
|
|
||||||
<TableRowCell className={styles.actions}>
|
|
||||||
<IconButton
|
|
||||||
title="Remove root folder"
|
|
||||||
name={icons.REMOVE}
|
|
||||||
onPress={onDeletePress}
|
|
||||||
/>
|
|
||||||
</TableRowCell>
|
|
||||||
</TableRow>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
RootFolderRow.propTypes = {
|
|
||||||
id: PropTypes.number.isRequired,
|
|
||||||
path: PropTypes.string.isRequired,
|
|
||||||
accessible: PropTypes.bool.isRequired,
|
|
||||||
freeSpace: PropTypes.number,
|
|
||||||
unmappedFolders: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
||||||
onDeletePress: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
RootFolderRow.defaultProps = {
|
|
||||||
unmappedFolders: []
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RootFolderRow;
|
|
|
@ -0,0 +1,95 @@
|
||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import Label from 'Components/Label';
|
||||||
|
import IconButton from 'Components/Link/IconButton';
|
||||||
|
import Link from 'Components/Link/Link';
|
||||||
|
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||||
|
import TableRowCell from 'Components/Table/Cells/TableRowCell';
|
||||||
|
import TableRow from 'Components/Table/TableRow';
|
||||||
|
import { icons, kinds } from 'Helpers/Props';
|
||||||
|
import { deleteRootFolder } from 'Store/Actions/rootFolderActions';
|
||||||
|
import formatBytes from 'Utilities/Number/formatBytes';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import styles from './RootFolderRow.css';
|
||||||
|
|
||||||
|
interface RootFolderRowProps {
|
||||||
|
id: number;
|
||||||
|
path: string;
|
||||||
|
accessible: boolean;
|
||||||
|
freeSpace?: number;
|
||||||
|
unmappedFolders: object[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function RootFolderRow(props: RootFolderRowProps) {
|
||||||
|
const { id, path, accessible, freeSpace, unmappedFolders = [] } = props;
|
||||||
|
|
||||||
|
const isUnavailable = !accessible;
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||||
|
|
||||||
|
const onDeletePress = useCallback(() => {
|
||||||
|
setIsDeleteModalOpen(true);
|
||||||
|
}, [setIsDeleteModalOpen]);
|
||||||
|
|
||||||
|
const onDeleteModalClose = useCallback(() => {
|
||||||
|
setIsDeleteModalOpen(false);
|
||||||
|
}, [setIsDeleteModalOpen]);
|
||||||
|
|
||||||
|
const onConfirmDelete = useCallback(() => {
|
||||||
|
dispatch(deleteRootFolder({ id }));
|
||||||
|
|
||||||
|
setIsDeleteModalOpen(false);
|
||||||
|
}, [dispatch, id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow>
|
||||||
|
<TableRowCell>
|
||||||
|
{isUnavailable ? (
|
||||||
|
<div className={styles.unavailablePath}>
|
||||||
|
{path}
|
||||||
|
|
||||||
|
<Label className={styles.unavailableLabel} kind={kinds.DANGER}>
|
||||||
|
{translate('Unavailable')}
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<Link className={styles.link} to={`/add/import/${id}`}>
|
||||||
|
{path}
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell className={styles.freeSpace}>
|
||||||
|
{isUnavailable || isNaN(Number(freeSpace))
|
||||||
|
? '-'
|
||||||
|
: formatBytes(freeSpace)}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell className={styles.unmappedFolders}>
|
||||||
|
{isUnavailable ? '-' : unmappedFolders.length}
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<TableRowCell className={styles.actions}>
|
||||||
|
<IconButton
|
||||||
|
title={translate('RemoveRootFolder')}
|
||||||
|
name={icons.REMOVE}
|
||||||
|
onPress={onDeletePress}
|
||||||
|
/>
|
||||||
|
</TableRowCell>
|
||||||
|
|
||||||
|
<ConfirmModal
|
||||||
|
isOpen={isDeleteModalOpen}
|
||||||
|
kind={kinds.DANGER}
|
||||||
|
title={translate('DeleteRootFolder')}
|
||||||
|
message={translate('DeleteRootFolderMessageText', { path })}
|
||||||
|
confirmLabel={translate('Delete')}
|
||||||
|
onConfirm={onConfirmDelete}
|
||||||
|
onCancel={onDeleteModalClose}
|
||||||
|
/>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RootFolderRow;
|
|
@ -1,13 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { deleteRootFolder } from 'Store/Actions/rootFolderActions';
|
|
||||||
import RootFolderRow from './RootFolderRow';
|
|
||||||
|
|
||||||
function createMapDispatchToProps(dispatch, props) {
|
|
||||||
return {
|
|
||||||
onDeletePress() {
|
|
||||||
dispatch(deleteRootFolder({ id: props.id }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(null, createMapDispatchToProps)(RootFolderRow);
|
|
|
@ -1,83 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React from 'react';
|
|
||||||
import Alert from 'Components/Alert';
|
|
||||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
|
||||||
import Table from 'Components/Table/Table';
|
|
||||||
import TableBody from 'Components/Table/TableBody';
|
|
||||||
import { kinds } from 'Helpers/Props';
|
|
||||||
import RootFolderRowConnector from './RootFolderRowConnector';
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
function RootFolders(props) {
|
|
||||||
const {
|
|
||||||
isFetching,
|
|
||||||
isPopulated,
|
|
||||||
error,
|
|
||||||
items
|
|
||||||
} = props;
|
|
||||||
|
|
||||||
if (isFetching && !isPopulated) {
|
|
||||||
return (
|
|
||||||
<LoadingIndicator />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isFetching && !!error) {
|
|
||||||
return (
|
|
||||||
<Alert kind={kinds.DANGER}>Unable to load root folders</Alert>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Table
|
|
||||||
columns={rootFolderColumns}
|
|
||||||
>
|
|
||||||
<TableBody>
|
|
||||||
{
|
|
||||||
items.map((rootFolder) => {
|
|
||||||
return (
|
|
||||||
<RootFolderRowConnector
|
|
||||||
key={rootFolder.id}
|
|
||||||
id={rootFolder.id}
|
|
||||||
path={rootFolder.path}
|
|
||||||
accessible={rootFolder.accessible}
|
|
||||||
freeSpace={rootFolder.freeSpace}
|
|
||||||
unmappedFolders={rootFolder.unmappedFolders}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
}
|
|
||||||
</TableBody>
|
|
||||||
</Table>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
RootFolders.propTypes = {
|
|
||||||
isFetching: PropTypes.bool.isRequired,
|
|
||||||
isPopulated: PropTypes.bool.isRequired,
|
|
||||||
error: PropTypes.object,
|
|
||||||
items: PropTypes.arrayOf(PropTypes.object).isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default RootFolders;
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
import React, { useEffect } from 'react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import Alert from 'Components/Alert';
|
||||||
|
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
|
import Table from 'Components/Table/Table';
|
||||||
|
import TableBody from 'Components/Table/TableBody';
|
||||||
|
import { kinds } from 'Helpers/Props';
|
||||||
|
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||||
|
import createRootFoldersSelector from 'Store/Selectors/createRootFoldersSelector';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import RootFolderRow from './RootFolderRow';
|
||||||
|
|
||||||
|
const rootFolderColumns = [
|
||||||
|
{
|
||||||
|
name: 'path',
|
||||||
|
get label() {
|
||||||
|
return translate('Path');
|
||||||
|
},
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'freeSpace',
|
||||||
|
get label() {
|
||||||
|
return translate('FreeSpace');
|
||||||
|
},
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'unmappedFolders',
|
||||||
|
get label() {
|
||||||
|
return translate('UnmappedFolders');
|
||||||
|
},
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'actions',
|
||||||
|
isVisible: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
function RootFolders() {
|
||||||
|
const { isFetching, isPopulated, error, items } = useSelector(
|
||||||
|
createRootFoldersSelector()
|
||||||
|
);
|
||||||
|
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(fetchRootFolders());
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
if (isFetching && !isPopulated) {
|
||||||
|
return <LoadingIndicator />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isFetching && !!error) {
|
||||||
|
return (
|
||||||
|
<Alert kind={kinds.DANGER}>{translate('UnableToLoadRootFolders')}</Alert>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Table columns={rootFolderColumns}>
|
||||||
|
<TableBody>
|
||||||
|
{items.map((rootFolder) => {
|
||||||
|
return (
|
||||||
|
<RootFolderRow
|
||||||
|
key={rootFolder.id}
|
||||||
|
id={rootFolder.id}
|
||||||
|
path={rootFolder.path}
|
||||||
|
accessible={rootFolder.accessible}
|
||||||
|
freeSpace={rootFolder.freeSpace}
|
||||||
|
unmappedFolders={rootFolder.unmappedFolders}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RootFolders;
|
|
@ -1,46 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
|
||||||
import RootFolders from './RootFolders';
|
|
||||||
|
|
||||||
function createMapStateToProps() {
|
|
||||||
return createSelector(
|
|
||||||
(state) => state.rootFolders,
|
|
||||||
(rootFolders) => {
|
|
||||||
return rootFolders;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
dispatchFetchRootFolders: fetchRootFolders
|
|
||||||
};
|
|
||||||
|
|
||||||
class RootFoldersConnector extends Component {
|
|
||||||
|
|
||||||
//
|
|
||||||
// Lifecycle
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.dispatchFetchRootFolders();
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// Render
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<RootFolders
|
|
||||||
{...this.props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
RootFoldersConnector.propTypes = {
|
|
||||||
dispatchFetchRootFolders: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default connect(createMapStateToProps, mapDispatchToProps)(RootFoldersConnector);
|
|
|
@ -10,10 +10,11 @@ import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||||
import PageContent from 'Components/Page/PageContent';
|
import PageContent from 'Components/Page/PageContent';
|
||||||
import PageContentBody from 'Components/Page/PageContentBody';
|
import PageContentBody from 'Components/Page/PageContentBody';
|
||||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||||
import RootFoldersConnector from 'RootFolder/RootFoldersConnector';
|
import RootFolders from 'RootFolder/RootFolders';
|
||||||
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
|
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
import NamingConnector from './Naming/NamingConnector';
|
import NamingConnector from './Naming/NamingConnector';
|
||||||
import AddRootFolderConnector from './RootFolder/AddRootFolderConnector';
|
import AddRootFolder from './RootFolder/AddRootFolder';
|
||||||
|
|
||||||
const episodeTitleRequiredOptions = [
|
const episodeTitleRequiredOptions = [
|
||||||
{ key: 'always', value: 'Always' },
|
{ key: 'always', value: 'Always' },
|
||||||
|
@ -452,9 +453,9 @@ class MediaManagement extends Component {
|
||||||
</Form> : null
|
</Form> : null
|
||||||
}
|
}
|
||||||
|
|
||||||
<FieldSet legend="Root Folders">
|
<FieldSet legend={translate('RootFolders')}>
|
||||||
<RootFoldersConnector />
|
<RootFolders />
|
||||||
<AddRootFolderConnector />
|
<AddRootFolder />
|
||||||
</FieldSet>
|
</FieldSet>
|
||||||
</PageContentBody>
|
</PageContentBody>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
|
|
|
@ -1,71 +0,0 @@
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import React, { Component } from 'react';
|
|
||||||
import FileBrowserModal from 'Components/FileBrowser/FileBrowserModal';
|
|
||||||
import Icon from 'Components/Icon';
|
|
||||||
import Button from 'Components/Link/Button';
|
|
||||||
import { icons, kinds, sizes } from 'Helpers/Props';
|
|
||||||
import styles from './AddRootFolder.css';
|
|
||||||
|
|
||||||
class AddRootFolder 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() {
|
|
||||||
return (
|
|
||||||
<div className={styles.addRootFolderButtonContainer}>
|
|
||||||
<Button
|
|
||||||
kind={kinds.PRIMARY}
|
|
||||||
size={sizes.LARGE}
|
|
||||||
onPress={this.onAddNewRootFolderPress}
|
|
||||||
>
|
|
||||||
<Icon
|
|
||||||
className={styles.importButtonIcon}
|
|
||||||
name={icons.DRIVE}
|
|
||||||
/>
|
|
||||||
Add Root Folder
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
<FileBrowserModal
|
|
||||||
isOpen={this.state.isAddNewRootFolderModalOpen}
|
|
||||||
name="rootFolderPath"
|
|
||||||
value=""
|
|
||||||
onChange={this.onNewRootFolderSelect}
|
|
||||||
onModalClose={this.onAddRootFolderModalClose}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AddRootFolder.propTypes = {
|
|
||||||
onNewRootFolderSelect: PropTypes.func.isRequired
|
|
||||||
};
|
|
||||||
|
|
||||||
export default AddRootFolder;
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
import React, { useCallback, useState } from 'react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import FileBrowserModal from 'Components/FileBrowser/FileBrowserModal';
|
||||||
|
import Icon from 'Components/Icon';
|
||||||
|
import Button from 'Components/Link/Button';
|
||||||
|
import { icons, kinds, sizes } from 'Helpers/Props';
|
||||||
|
import { addRootFolder } from 'Store/Actions/rootFolderActions';
|
||||||
|
import translate from 'Utilities/String/translate';
|
||||||
|
import styles from './AddRootFolder.css';
|
||||||
|
|
||||||
|
function AddRootFolder() {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
|
const [isAddNewRootFolderModalOpen, setIsAddNewRootFolderModalOpen] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
|
const onAddNewRootFolderPress = useCallback(() => {
|
||||||
|
setIsAddNewRootFolderModalOpen(true);
|
||||||
|
}, [setIsAddNewRootFolderModalOpen]);
|
||||||
|
|
||||||
|
const onNewRootFolderSelect = useCallback(
|
||||||
|
({ value }: { value: string }) => {
|
||||||
|
dispatch(addRootFolder({ path: value }));
|
||||||
|
},
|
||||||
|
[dispatch]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onAddRootFolderModalClose = useCallback(() => {
|
||||||
|
setIsAddNewRootFolderModalOpen(false);
|
||||||
|
}, [setIsAddNewRootFolderModalOpen]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.addRootFolderButtonContainer}>
|
||||||
|
<Button
|
||||||
|
kind={kinds.PRIMARY}
|
||||||
|
size={sizes.LARGE}
|
||||||
|
onPress={onAddNewRootFolderPress}
|
||||||
|
>
|
||||||
|
<Icon className={styles.importButtonIcon} name={icons.DRIVE} />
|
||||||
|
{translate('AddRootFolder')}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<FileBrowserModal
|
||||||
|
isOpen={isAddNewRootFolderModalOpen}
|
||||||
|
name="rootFolderPath"
|
||||||
|
value=""
|
||||||
|
onChange={onNewRootFolderSelect}
|
||||||
|
onModalClose={onAddRootFolderModalClose}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AddRootFolder;
|
|
@ -1,13 +0,0 @@
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { addRootFolder } from 'Store/Actions/rootFolderActions';
|
|
||||||
import AddRootFolder from './AddRootFolder';
|
|
||||||
|
|
||||||
function createMapDispatchToProps(dispatch) {
|
|
||||||
return {
|
|
||||||
onNewRootFolderSelect(path) {
|
|
||||||
dispatch(addRootFolder({ path }));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default connect(null, createMapDispatchToProps)(AddRootFolder);
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { RootFolderAppState } from 'App/State/SettingsAppState';
|
||||||
|
import createSortedSectionSelector from 'Store/Selectors/createSortedSectionSelector';
|
||||||
|
import RootFolder from 'typings/RootFolder';
|
||||||
|
|
||||||
|
export default function createRootFoldersSelector() {
|
||||||
|
return createSelector(
|
||||||
|
createSortedSectionSelector('rootFolders', (a: RootFolder, b: RootFolder) =>
|
||||||
|
a.path.localeCompare(b.path)
|
||||||
|
),
|
||||||
|
(rootFolders: RootFolderAppState) => rootFolders
|
||||||
|
);
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
import ModelBase from 'App/ModelBase';
|
||||||
|
|
||||||
|
interface RootFolder extends ModelBase {
|
||||||
|
id: number;
|
||||||
|
path: string;
|
||||||
|
accessible: boolean;
|
||||||
|
freeSpace?: number;
|
||||||
|
unmappedFolders: object[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RootFolder;
|
|
@ -7,6 +7,7 @@
|
||||||
"AddAutoTag": "Add Auto Tag",
|
"AddAutoTag": "Add Auto Tag",
|
||||||
"AddCondition": "Add Condition",
|
"AddCondition": "Add Condition",
|
||||||
"AddNew": "Add New",
|
"AddNew": "Add New",
|
||||||
|
"AddRootFolder": "Add Root Folder",
|
||||||
"Added": "Added",
|
"Added": "Added",
|
||||||
"AddingTag": "Adding tag",
|
"AddingTag": "Adding tag",
|
||||||
"Age": "Age",
|
"Age": "Age",
|
||||||
|
@ -76,6 +77,8 @@
|
||||||
"DeleteConditionMessageText": "Are you sure you want to delete the condition '{0}'?",
|
"DeleteConditionMessageText": "Are you sure you want to delete the condition '{0}'?",
|
||||||
"DeleteCustomFormat": "Delete Custom Format",
|
"DeleteCustomFormat": "Delete Custom Format",
|
||||||
"DeleteCustomFormatMessageText": "Are you sure you want to delete the custom format '{0}'?",
|
"DeleteCustomFormatMessageText": "Are you sure you want to delete the custom format '{0}'?",
|
||||||
|
"DeleteRootFolder": "Delete Root Folder",
|
||||||
|
"DeleteRootFolderMessageText": "Are you sure you want to delete the root folder '{path}'?",
|
||||||
"DeleteSelectedDownloadClients": "Delete Download Client(s)",
|
"DeleteSelectedDownloadClients": "Delete Download Client(s)",
|
||||||
"DeleteSelectedDownloadClientsMessageText": "Are you sure you want to delete {count} selected download client(s)?",
|
"DeleteSelectedDownloadClientsMessageText": "Are you sure you want to delete {count} selected download client(s)?",
|
||||||
"DeleteSelectedImportLists": "Delete Import List(s)",
|
"DeleteSelectedImportLists": "Delete Import List(s)",
|
||||||
|
@ -295,6 +298,7 @@
|
||||||
"RemoveFailedDownloads": "Remove Failed Downloads",
|
"RemoveFailedDownloads": "Remove Failed Downloads",
|
||||||
"RemoveFromDownloadClient": "Remove From Download Client",
|
"RemoveFromDownloadClient": "Remove From Download Client",
|
||||||
"RemoveFromDownloadClientHelpTextWarning": "Removing will remove the download and the file(s) from the download client.",
|
"RemoveFromDownloadClientHelpTextWarning": "Removing will remove the download and the file(s) from the download client.",
|
||||||
|
"RemoveRootFolder": "Remove root folder",
|
||||||
"RemoveSelectedItem": "Remove Selected Item",
|
"RemoveSelectedItem": "Remove Selected Item",
|
||||||
"RemoveSelectedItemQueueMessageText": "Are you sure you want to remove 1 item from the queue?",
|
"RemoveSelectedItemQueueMessageText": "Are you sure you want to remove 1 item from the queue?",
|
||||||
"RemoveSelectedItems": "Remove Selected Items",
|
"RemoveSelectedItems": "Remove Selected Items",
|
||||||
|
@ -323,6 +327,7 @@
|
||||||
"RootFolderMissingHealthCheckMessage": "Missing root folder: {0}",
|
"RootFolderMissingHealthCheckMessage": "Missing root folder: {0}",
|
||||||
"RootFolderMultipleMissingHealthCheckMessage": "Multiple root folders are missing: {0}",
|
"RootFolderMultipleMissingHealthCheckMessage": "Multiple root folders are missing: {0}",
|
||||||
"RootFolderPath": "Root Folder Path",
|
"RootFolderPath": "Root Folder Path",
|
||||||
|
"RootFolders": "Root Folders",
|
||||||
"Runtime": "Runtime",
|
"Runtime": "Runtime",
|
||||||
"Save": "Save",
|
"Save": "Save",
|
||||||
"SceneNumbering": "Scene Numbering",
|
"SceneNumbering": "Scene Numbering",
|
||||||
|
@ -370,7 +375,10 @@
|
||||||
"UI Language": "UI Language",
|
"UI Language": "UI Language",
|
||||||
"UnableToLoadAutoTagging": "Unable to load auto tagging",
|
"UnableToLoadAutoTagging": "Unable to load auto tagging",
|
||||||
"UnableToLoadBackups": "Unable to load backups",
|
"UnableToLoadBackups": "Unable to load backups",
|
||||||
|
"UnableToLoadRootFolders": "Unable to load root folders",
|
||||||
"UnableToUpdateSonarrDirectly": "Unable to update Sonarr directly,",
|
"UnableToUpdateSonarrDirectly": "Unable to update Sonarr directly,",
|
||||||
|
"Unavailable": "Unavailable",
|
||||||
|
"UnmappedFolders": "Unmapped Folders",
|
||||||
"Unmonitored": "Unmonitored",
|
"Unmonitored": "Unmonitored",
|
||||||
"UnmonitoredOnly": "Unmonitored Only",
|
"UnmonitoredOnly": "Unmonitored Only",
|
||||||
"UpdateAvailableHealthCheckMessage": "New update is available",
|
"UpdateAvailableHealthCheckMessage": "New update is available",
|
||||||
|
|
Loading…
Reference in New Issue