mirror of https://github.com/Radarr/Radarr
Convert Root Folders to Typescript
This commit is contained in:
parent
835a539275
commit
abad6a9f18
|
@ -9,6 +9,7 @@ import ImportList from 'typings/ImportList';
|
|||
import Indexer from 'typings/Indexer';
|
||||
import Notification from 'typings/Notification';
|
||||
import QualityProfile from 'typings/QualityProfile';
|
||||
import RootFolder from 'typings/RootFolder';
|
||||
import { UiSettings } from 'typings/UiSettings';
|
||||
|
||||
export interface DownloadClientAppState
|
||||
|
@ -34,6 +35,11 @@ export interface QualityProfilesAppState
|
|||
extends AppSectionState<QualityProfile>,
|
||||
AppSectionSchemaState<QualityProfile> {}
|
||||
|
||||
export interface RootFolderAppState
|
||||
extends AppSectionState<RootFolder>,
|
||||
AppSectionDeleteState,
|
||||
AppSectionSaveState {}
|
||||
|
||||
export type LanguageSettingsAppState = AppSectionState<Language>;
|
||||
export type UiSettingsAppState = AppSectionState<UiSettings>;
|
||||
|
||||
|
@ -41,10 +47,11 @@ interface SettingsAppState {
|
|||
downloadClients: DownloadClientAppState;
|
||||
importLists: ImportListAppState;
|
||||
indexers: IndexerAppState;
|
||||
notifications: NotificationAppState;
|
||||
language: LanguageSettingsAppState;
|
||||
uiSettings: UiSettingsAppState;
|
||||
notifications: NotificationAppState;
|
||||
qualityProfiles: QualityProfilesAppState;
|
||||
rootFolders: RootFolderAppState;
|
||||
uiSettings: UiSettingsAppState;
|
||||
}
|
||||
|
||||
export default SettingsAppState;
|
||||
|
|
|
@ -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);
|
|
@ -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,11 +10,11 @@ import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
|||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBody from 'Components/Page/PageContentBody';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import RootFoldersConnector from 'RootFolder/RootFoldersConnector';
|
||||
import RootFolders from 'RootFolder/RootFolders';
|
||||
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import NamingConnector from './Naming/NamingConnector';
|
||||
import AddRootFolderConnector from './RootFolder/AddRootFolderConnector';
|
||||
import AddRootFolder from './RootFolder/AddRootFolder';
|
||||
|
||||
const rescanAfterRefreshOptions = [
|
||||
{
|
||||
|
@ -479,8 +479,8 @@ class MediaManagement extends Component {
|
|||
}
|
||||
|
||||
<FieldSet legend={translate('RootFolders')}>
|
||||
<RootFoldersConnector />
|
||||
<AddRootFolderConnector />
|
||||
<RootFolders />
|
||||
<AddRootFolder />
|
||||
</FieldSet>
|
||||
</PageContentBody>
|
||||
</PageContent>
|
||||
|
|
|
@ -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;
|
|
@ -245,6 +245,8 @@
|
|||
"DeleteRemotePathMappingMessageText": "Are you sure you want to delete this remote path mapping?",
|
||||
"DeleteRestriction": "Delete Restriction",
|
||||
"DeleteRestrictionHelpText": "Are you sure you want to delete this restriction?",
|
||||
"DeleteRootFolder": "Delete Root Folder",
|
||||
"DeleteRootFolderMessageText": "Are you sure you want to delete the root folder '{0}'?",
|
||||
"DeleteSelectedDownloadClients": "Delete Download Client(s)",
|
||||
"DeleteSelectedDownloadClientsMessageText": "Are you sure you want to delete {0} selected download client(s)?",
|
||||
"DeleteSelectedImportLists": "Delete Import List(s)",
|
||||
|
@ -940,6 +942,7 @@
|
|||
"RootFolder": "Root Folder",
|
||||
"RootFolderCheckMultipleMessage": "Multiple root folders are missing: {0}",
|
||||
"RootFolderCheckSingleMessage": "Missing root folder: {0}",
|
||||
"RootFolderPath": "Root Folder Path",
|
||||
"RootFolders": "Root Folders",
|
||||
"RottenTomatoesRating": "Tomato Rating",
|
||||
"RssSyncHelpText": "Interval in minutes. Set to zero to disable (this will stop all automatic release grabbing)",
|
||||
|
|
Loading…
Reference in New Issue