diff --git a/frontend/src/Store/Actions/bookFileActions.js b/frontend/src/Store/Actions/bookFileActions.js
index 29b3059b7..66c4b40d1 100644
--- a/frontend/src/Store/Actions/bookFileActions.js
+++ b/frontend/src/Store/Actions/bookFileActions.js
@@ -41,6 +41,14 @@ export const defaultState = {
},
columns: [
+ {
+ name: 'select',
+ columnLabel: 'Select',
+ isSortable: false,
+ isVisible: true,
+ isModifiable: false,
+ isHidden: true
+ },
{
name: 'path',
label: 'Path',
diff --git a/frontend/src/UnmappedFiles/UnmappedFilesTable.js b/frontend/src/UnmappedFiles/UnmappedFilesTable.js
index b7999fd8a..ee966b487 100644
--- a/frontend/src/UnmappedFiles/UnmappedFilesTable.js
+++ b/frontend/src/UnmappedFiles/UnmappedFilesTable.js
@@ -1,5 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
+import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContent from 'Components/Page/PageContent';
import PageContentBody from 'Components/Page/PageContentBody';
@@ -9,8 +10,12 @@ import PageToolbarSection from 'Components/Page/Toolbar/PageToolbarSection';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import VirtualTable from 'Components/Table/VirtualTable';
import VirtualTableRow from 'Components/Table/VirtualTableRow';
-import { align, icons, sortDirections } from 'Helpers/Props';
+import { align, icons, kinds, sortDirections } from 'Helpers/Props';
+import hasDifferentItemsOrOrder from 'Utilities/Object/hasDifferentItemsOrOrder';
import translate from 'Utilities/String/translate';
+import getSelectedIds from 'Utilities/Table/getSelectedIds';
+import selectAll from 'Utilities/Table/selectAll';
+import toggleSelected from 'Utilities/Table/toggleSelected';
import UnmappedFilesTableHeader from './UnmappedFilesTableHeader';
import UnmappedFilesTableRow from './UnmappedFilesTableRow';
@@ -23,10 +28,43 @@ class UnmappedFilesTable extends Component {
super(props, context);
this.state = {
- scroller: null
+ scroller: null,
+ allSelected: false,
+ allUnselected: false,
+ lastToggled: null,
+ selectedState: {}
};
}
+ componentDidMount() {
+ this.setSelectedState();
+ }
+
+ componentDidUpdate(prevProps) {
+ const {
+ items,
+ sortKey,
+ sortDirection,
+ isDeleting,
+ deleteError
+ } = this.props;
+
+ if (sortKey !== prevProps.sortKey ||
+ sortDirection !== prevProps.sortDirection ||
+ hasDifferentItemsOrOrder(prevProps.items, items)
+ ) {
+ this.setSelectedState();
+ }
+
+ const hasFinishedDeleting = prevProps.isDeleting &&
+ !isDeleting &&
+ !deleteError;
+
+ if (hasFinishedDeleting) {
+ this.onSelectAllChange({ value: false });
+ }
+ }
+
//
// Control
@@ -34,6 +72,68 @@ class UnmappedFilesTable extends Component {
this.setState({ scroller: ref });
};
+ getSelectedIds = () => {
+ if (this.state.allUnselected) {
+ return [];
+ }
+ return getSelectedIds(this.state.selectedState);
+ };
+
+ setSelectedState() {
+ const {
+ items
+ } = this.props;
+
+ const {
+ selectedState
+ } = this.state;
+
+ const newSelectedState = {};
+
+ items.forEach((file) => {
+ const isItemSelected = selectedState[file.id];
+
+ if (isItemSelected) {
+ newSelectedState[file.id] = isItemSelected;
+ } else {
+ newSelectedState[file.id] = false;
+ }
+ });
+
+ const selectedCount = getSelectedIds(newSelectedState).length;
+ const newStateCount = Object.keys(newSelectedState).length;
+ let isAllSelected = false;
+ let isAllUnselected = false;
+
+ if (selectedCount === 0) {
+ isAllUnselected = true;
+ } else if (selectedCount === newStateCount) {
+ isAllSelected = true;
+ }
+
+ this.setState({ selectedState: newSelectedState, allSelected: isAllSelected, allUnselected: isAllUnselected });
+ }
+
+ onSelectAllChange = ({ value }) => {
+ this.setState(selectAll(this.state.selectedState, value));
+ };
+
+ onSelectAllPress = () => {
+ this.onSelectAllChange({ value: !this.state.allSelected });
+ };
+
+ onSelectedChange = ({ id, value, shiftKey = false }) => {
+ this.setState((state) => {
+ return toggleSelected(state, this.props.items, id, value, shiftKey);
+ });
+ };
+
+ onDeleteUnmappedFilesPress = () => {
+ const selectedIds = this.getSelectedIds();
+
+ this.props.deleteUnmappedFiles(selectedIds);
+ };
+
rowRenderer = ({ key, rowIndex, style }) => {
const {
items,
@@ -41,6 +141,10 @@ class UnmappedFilesTable extends Component {
deleteUnmappedFile
} = this.props;
+ const {
+ selectedState
+ } = this.state;
+
const item = items[rowIndex];
return (
@@ -51,6 +155,8 @@ class UnmappedFilesTable extends Component {
@@ -63,6 +169,7 @@ class UnmappedFilesTable extends Component {
const {
isFetching,
isPopulated,
+ isDeleting,
error,
items,
columns,
@@ -72,13 +179,19 @@ class UnmappedFilesTable extends Component {
onSortPress,
isScanningFolders,
onAddMissingAuthorsPress,
+ deleteUnmappedFiles,
...otherProps
} = this.props;
const {
- scroller
+ scroller,
+ allSelected,
+ allUnselected,
+ selectedState
} = this.state;
+ const selectedTrackFileIds = this.getSelectedIds();
+
return (
@@ -90,6 +203,13 @@ class UnmappedFilesTable extends Component {
isSpinning={isScanningFolders}
onPress={onAddMissingAuthorsPress}
/>
+
@@ -117,9 +237,9 @@ class UnmappedFilesTable extends Component {
{
isPopulated && !error && !items.length &&
-
+
Success! My work is done, all files on disk are matched to known books.
-
+
}
{
@@ -138,8 +258,12 @@ class UnmappedFilesTable extends Component {
sortDirection={sortDirection}
onTableOptionChange={onTableOptionChange}
onSortPress={onSortPress}
+ allSelected={allSelected}
+ allUnselected={allUnselected}
+ onSelectAllChange={this.onSelectAllChange}
/>
}
+ selectedState={selectedState}
sortKey={sortKey}
sortDirection={sortDirection}
/>
@@ -153,6 +277,8 @@ class UnmappedFilesTable extends Component {
UnmappedFilesTable.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
+ isDeleting: PropTypes.bool.isRequired,
+ deleteError: PropTypes.object,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
columns: PropTypes.arrayOf(PropTypes.object).isRequired,
@@ -161,6 +287,7 @@ UnmappedFilesTable.propTypes = {
onTableOptionChange: PropTypes.func.isRequired,
onSortPress: PropTypes.func.isRequired,
deleteUnmappedFile: PropTypes.func.isRequired,
+ deleteUnmappedFiles: PropTypes.func.isRequired,
isScanningFolders: PropTypes.bool.isRequired,
onAddMissingAuthorsPress: PropTypes.func.isRequired
};
diff --git a/frontend/src/UnmappedFiles/UnmappedFilesTableConnector.js b/frontend/src/UnmappedFiles/UnmappedFilesTableConnector.js
index 5d6df606f..1a8e15eb9 100644
--- a/frontend/src/UnmappedFiles/UnmappedFilesTableConnector.js
+++ b/frontend/src/UnmappedFiles/UnmappedFilesTableConnector.js
@@ -5,7 +5,7 @@ import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import * as commandNames from 'Commands/commandNames';
import withCurrentPage from 'Components/withCurrentPage';
-import { deleteBookFile, fetchBookFiles, setBookFilesSort, setBookFilesTableOption } from 'Store/Actions/bookFileActions';
+import { deleteBookFile, deleteBookFiles, fetchBookFiles, setBookFilesSort, setBookFilesTableOption } from 'Store/Actions/bookFileActions';
import { executeCommand } from 'Store/Actions/commandActions';
import createClientSideCollectionSelector from 'Store/Selectors/createClientSideCollectionSelector';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
@@ -28,7 +28,9 @@ function createMapStateToProps() {
items,
...otherProps
} = bookFiles;
+
const unmappedFiles = _.filter(items, { bookId: 0 });
+
return {
items: unmappedFiles,
...otherProps,
@@ -57,6 +59,10 @@ function createMapDispatchToProps(dispatch, props) {
dispatch(deleteBookFile({ id }));
},
+ deleteUnmappedFiles(bookFileIds) {
+ dispatch(deleteBookFiles({ bookFileIds }));
+ },
+
onAddMissingAuthorsPress() {
dispatch(executeCommand({
name: commandNames.RESCAN_FOLDERS,
@@ -106,7 +112,8 @@ UnmappedFilesTableConnector.propTypes = {
onSortPress: PropTypes.func.isRequired,
onTableOptionChange: PropTypes.func.isRequired,
fetchUnmappedFiles: PropTypes.func.isRequired,
- deleteUnmappedFile: PropTypes.func.isRequired
+ deleteUnmappedFile: PropTypes.func.isRequired,
+ deleteUnmappedFiles: PropTypes.func.isRequired
};
export default withCurrentPage(
diff --git a/frontend/src/UnmappedFiles/UnmappedFilesTableHeader.js b/frontend/src/UnmappedFiles/UnmappedFilesTableHeader.js
index 7ac0a4e44..5b4b22933 100644
--- a/frontend/src/UnmappedFiles/UnmappedFilesTableHeader.js
+++ b/frontend/src/UnmappedFiles/UnmappedFilesTableHeader.js
@@ -4,6 +4,7 @@ import IconButton from 'Components/Link/IconButton';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import VirtualTableHeader from 'Components/Table/VirtualTableHeader';
import VirtualTableHeaderCell from 'Components/Table/VirtualTableHeaderCell';
+import VirtualTableSelectAllHeaderCell from 'Components/Table/VirtualTableSelectAllHeaderCell';
import { icons } from 'Helpers/Props';
// import hasGrowableColumns from './hasGrowableColumns';
import styles from './UnmappedFilesTableHeader.css';
@@ -12,6 +13,9 @@ function UnmappedFilesTableHeader(props) {
const {
columns,
onTableOptionChange,
+ allSelected,
+ allUnselected,
+ onSelectAllChange,
...otherProps
} = props;
@@ -30,6 +34,17 @@ function UnmappedFilesTableHeader(props) {
return null;
}
+ if (name === 'select') {
+ return (
+
+ );
+ }
+
if (name === 'actions') {
return (
+ );
+ }
+
if (name === 'path') {
return (
0 && bookFile.Author != null && bookFile.Author.Value != null)
+ {
+ _mediaFileDeletionService.DeleteTrackFile(bookFile.Author.Value, bookFile);
+ }
+ else
+ {
+ _mediaFileDeletionService.DeleteTrackFile(bookFile, "Unmapped_Files");
+ }
}
- return Ok();
+ return new { };
}
[NonAction]