Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>pull/8414/head
parent
ee5fed8522
commit
e85c010bf2
@ -0,0 +1,3 @@
|
||||
.icon {
|
||||
margin-right: 8px;
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'icon': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
@ -0,0 +1,41 @@
|
||||
import { IconDefinition } from '@fortawesome/fontawesome-common-types';
|
||||
import React from 'react';
|
||||
import MenuItem from 'Components/Menu/MenuItem';
|
||||
import SpinnerIcon from 'Components/SpinnerIcon';
|
||||
import styles from './PageToolbarOverflowMenuItem.css';
|
||||
|
||||
interface PageToolbarOverflowMenuItemProps {
|
||||
iconName: IconDefinition;
|
||||
spinningName?: IconDefinition;
|
||||
isDisabled?: boolean;
|
||||
isSpinning?: boolean;
|
||||
showIndicator?: boolean;
|
||||
label: string;
|
||||
text?: string;
|
||||
onPress: () => void;
|
||||
}
|
||||
|
||||
function PageToolbarOverflowMenuItem(props: PageToolbarOverflowMenuItemProps) {
|
||||
const {
|
||||
iconName,
|
||||
spinningName,
|
||||
label,
|
||||
isDisabled,
|
||||
isSpinning = false,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
return (
|
||||
<MenuItem key={label} isDisabled={isDisabled || isSpinning} {...otherProps}>
|
||||
<SpinnerIcon
|
||||
className={styles.icon}
|
||||
name={iconName}
|
||||
spinningName={spinningName}
|
||||
isSpinning={isSpinning}
|
||||
/>
|
||||
{label}
|
||||
</MenuItem>
|
||||
);
|
||||
}
|
||||
|
||||
export default PageToolbarOverflowMenuItem;
|
@ -0,0 +1,8 @@
|
||||
enum TooltipPosition {
|
||||
Top = 'top',
|
||||
Right = 'right',
|
||||
Bottom = 'bottom',
|
||||
Left = 'left',
|
||||
}
|
||||
|
||||
export default TooltipPosition;
|
@ -0,0 +1,72 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useSelect } from 'App/SelectContext';
|
||||
import { REFRESH_MOVIE } from 'Commands/commandNames';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import createMovieClientSideCollectionItemsSelector from 'Store/Selectors/createMovieClientSideCollectionItemsSelector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
|
||||
interface MovieIndexRefreshMovieButtonProps {
|
||||
isSelectMode: boolean;
|
||||
selectedFilterKey: string;
|
||||
}
|
||||
|
||||
function MovieIndexRefreshMovieButton(
|
||||
props: MovieIndexRefreshMovieButtonProps
|
||||
) {
|
||||
const isRefreshing = useSelector(
|
||||
createCommandExecutingSelector(REFRESH_MOVIE)
|
||||
);
|
||||
const { items, totalItems } = useSelector(
|
||||
createMovieClientSideCollectionItemsSelector('movieIndex')
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { isSelectMode, selectedFilterKey } = props;
|
||||
const [selectState] = useSelect();
|
||||
const { selectedState } = selectState;
|
||||
|
||||
const selectedMovieIds = useMemo(() => {
|
||||
return getSelectedIds(selectedState);
|
||||
}, [selectedState]);
|
||||
|
||||
const moviesToRefresh =
|
||||
isSelectMode && selectedMovieIds.length > 0
|
||||
? selectedMovieIds
|
||||
: items.map((m) => m.id);
|
||||
|
||||
const refreshIndexLabel =
|
||||
selectedFilterKey === 'all'
|
||||
? translate('UpdateAll')
|
||||
: translate('UpdateFiltered');
|
||||
|
||||
const refreshSelectLabel =
|
||||
selectedMovieIds.length > 0
|
||||
? translate('UpdateSelected')
|
||||
: translate('UpdateAll');
|
||||
|
||||
const onPress = useCallback(() => {
|
||||
dispatch(
|
||||
executeCommand({
|
||||
name: REFRESH_MOVIE,
|
||||
movieIds: moviesToRefresh,
|
||||
})
|
||||
);
|
||||
}, [dispatch, moviesToRefresh]);
|
||||
|
||||
return (
|
||||
<PageToolbarButton
|
||||
label={isSelectMode ? refreshSelectLabel : refreshIndexLabel}
|
||||
isSpinning={isRefreshing}
|
||||
isDisabled={!totalItems}
|
||||
iconName={icons.REFRESH}
|
||||
onPress={onPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default MovieIndexRefreshMovieButton;
|
@ -0,0 +1,68 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useSelect } from 'App/SelectContext';
|
||||
import { MOVIE_SEARCH } from 'Commands/commandNames';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import createMovieClientSideCollectionItemsSelector from 'Store/Selectors/createMovieClientSideCollectionItemsSelector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
|
||||
interface MovieIndexSearchButtonProps {
|
||||
isSelectMode: boolean;
|
||||
selectedFilterKey: string;
|
||||
}
|
||||
|
||||
function MovieIndexSearchButton(props: MovieIndexSearchButtonProps) {
|
||||
const isSearching = useSelector(createCommandExecutingSelector(MOVIE_SEARCH));
|
||||
const { items, totalItems } = useSelector(
|
||||
createMovieClientSideCollectionItemsSelector('movieIndex')
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
const { isSelectMode, selectedFilterKey } = props;
|
||||
const [selectState] = useSelect();
|
||||
const { selectedState } = selectState;
|
||||
|
||||
const selectedMovieIds = useMemo(() => {
|
||||
return getSelectedIds(selectedState);
|
||||
}, [selectedState]);
|
||||
|
||||
const moviesToSearch =
|
||||
isSelectMode && selectedMovieIds.length > 0
|
||||
? selectedMovieIds
|
||||
: items.map((m) => m.id);
|
||||
|
||||
const searchIndexLabel =
|
||||
selectedFilterKey === 'all'
|
||||
? translate('SearchAll')
|
||||
: translate('SearchFiltered');
|
||||
|
||||
const searchSelectLabel =
|
||||
selectedMovieIds.length > 0
|
||||
? translate('SearchSelected')
|
||||
: translate('SearchAll');
|
||||
|
||||
const onPress = useCallback(() => {
|
||||
dispatch(
|
||||
executeCommand({
|
||||
name: MOVIE_SEARCH,
|
||||
movieIds: moviesToSearch,
|
||||
})
|
||||
);
|
||||
}, [dispatch, moviesToSearch]);
|
||||
|
||||
return (
|
||||
<PageToolbarButton
|
||||
label={isSelectMode ? searchSelectLabel : searchIndexLabel}
|
||||
isSpinning={isSearching}
|
||||
isDisabled={!totalItems}
|
||||
iconName={icons.SEARCH}
|
||||
onPress={onPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default MovieIndexSearchButton;
|
@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import DeleteMovieModalContent from './DeleteMovieModalContent';
|
||||
|
||||
interface DeleteMovieModalProps {
|
||||
isOpen: boolean;
|
||||
movieIds: number[];
|
||||
onModalClose(): void;
|
||||
}
|
||||
|
||||
function DeleteMovieModal(props: DeleteMovieModalProps) {
|
||||
const { isOpen, movieIds, onModalClose } = props;
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onModalClose={onModalClose}>
|
||||
<DeleteMovieModalContent
|
||||
movieIds={movieIds}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default DeleteMovieModal;
|
@ -0,0 +1,13 @@
|
||||
.message {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.pathContainer {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.path {
|
||||
margin-left: 5px;
|
||||
color: var(--dangerColor);
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'message': string;
|
||||
'path': string;
|
||||
'pathContainer': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
@ -0,0 +1,155 @@
|
||||
import { orderBy } from 'lodash';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import Button from 'Components/Link/Button';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds } from 'Helpers/Props';
|
||||
import { bulkDeleteMovie, setDeleteOption } from 'Store/Actions/movieActions';
|
||||
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './DeleteMovieModalContent.css';
|
||||
|
||||
interface DeleteMovieModalContentProps {
|
||||
movieIds: number[];
|
||||
onModalClose(): void;
|
||||
}
|
||||
|
||||
const selectDeleteOptions = createSelector(
|
||||
(state) => state.movie.deleteOptions,
|
||||
(deleteOptions) => deleteOptions
|
||||
);
|
||||
|
||||
function DeleteMovieModalContent(props: DeleteMovieModalContentProps) {
|
||||
const { movieIds, onModalClose } = props;
|
||||
|
||||
const { addImportListExclusion } = useSelector(selectDeleteOptions);
|
||||
const allMovies = useSelector(createAllMoviesSelector());
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [deleteFiles, setDeleteFiles] = useState(false);
|
||||
|
||||
const movies = useMemo(() => {
|
||||
const movies = movieIds.map((id) => {
|
||||
return allMovies.find((s) => s.id === id);
|
||||
});
|
||||
|
||||
return orderBy(movies, ['sortTitle']);
|
||||
}, [movieIds, allMovies]);
|
||||
|
||||
const onDeleteFilesChange = useCallback(
|
||||
({ value }) => {
|
||||
setDeleteFiles(value);
|
||||
},
|
||||
[setDeleteFiles]
|
||||
);
|
||||
|
||||
const onDeleteOptionChange = useCallback(
|
||||
({ name, value }) => {
|
||||
dispatch(
|
||||
setDeleteOption({
|
||||
[name]: value,
|
||||
})
|
||||
);
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const onDeleteMoviesConfirmed = useCallback(() => {
|
||||
setDeleteFiles(false);
|
||||
|
||||
dispatch(
|
||||
bulkDeleteMovie({
|
||||
movieIds,
|
||||
deleteFiles,
|
||||
addImportListExclusion,
|
||||
})
|
||||
);
|
||||
|
||||
onModalClose();
|
||||
}, [
|
||||
movieIds,
|
||||
deleteFiles,
|
||||
addImportListExclusion,
|
||||
setDeleteFiles,
|
||||
dispatch,
|
||||
onModalClose,
|
||||
]);
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>{translate('DeleteSelectedMovie')}</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('AddListExclusion')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="addImportListExclusion"
|
||||
value={addImportListExclusion}
|
||||
helpText={translate('AddImportExclusionHelpText')}
|
||||
onChange={onDeleteOptionChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{`Delete Movie Folder${
|
||||
movies.length > 1 ? 's' : ''
|
||||
}`}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="deleteFiles"
|
||||
value={deleteFiles}
|
||||
helpText={`Delete Movie Folder${
|
||||
movies.length > 1 ? 's' : ''
|
||||
} and all contents`}
|
||||
kind={kinds.DANGER}
|
||||
onChange={onDeleteFilesChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
|
||||
<div className={styles.message}>
|
||||
{`Are you sure you want to delete ${movies.length} selected movie(s)${
|
||||
deleteFiles ? ' and all contents' : ''
|
||||
}?`}
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
{movies.map((s) => {
|
||||
return (
|
||||
<li key={s.title}>
|
||||
<span>{s.title}</span>
|
||||
|
||||
{deleteFiles && (
|
||||
<span className={styles.pathContainer}>
|
||||
-<span className={styles.path}>{s.path}</span>
|
||||
</span>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
|
||||
|
||||
<Button kind={kinds.DANGER} onPress={onDeleteMoviesConfirmed}>
|
||||
{translate('Delete')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
|
||||
export default DeleteMovieModalContent;
|
@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import EditMoviesModalContent from './EditMoviesModalContent';
|
||||
|
||||
interface EditMoviesModalProps {
|
||||
isOpen: boolean;
|
||||
movieIds: number[];
|
||||
onSavePress(payload: object): void;
|
||||
onModalClose(): void;
|
||||
}
|
||||
|
||||
function EditMoviesModal(props: EditMoviesModalProps) {
|
||||
const { isOpen, movieIds, onSavePress, onModalClose } = props;
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onModalClose={onModalClose}>
|
||||
<EditMoviesModalContent
|
||||
movieIds={movieIds}
|
||||
onSavePress={onSavePress}
|
||||
onModalClose={onModalClose}
|
||||
/>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditMoviesModal;
|
@ -0,0 +1,16 @@
|
||||
.modalFooter {
|
||||
composes: modalFooter from '~Components/Modal/ModalFooter.css';
|
||||
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.selected {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointExtraSmall) {
|
||||
.modalFooter {
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'modalFooter': string;
|
||||
'selected': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
@ -0,0 +1,187 @@
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import Button from 'Components/Link/Button';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes } from 'Helpers/Props';
|
||||
import MoveMovieModal from 'Movie/MoveMovie/MoveMovieModal';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './EditMoviesModalContent.css';
|
||||
|
||||
interface SavePayload {
|
||||
monitored?: boolean;
|
||||
qualityProfileId?: number;
|
||||
rootFolderPath?: string;
|
||||
moveFiles?: boolean;
|
||||
}
|
||||
|
||||
interface EditMoviesModalContentProps {
|
||||
movieIds: number[];
|
||||
onSavePress(payload: object): void;
|
||||
onModalClose(): void;
|
||||
}
|
||||
|
||||
const NO_CHANGE = 'noChange';
|
||||
|
||||
const monitoredOptions = [
|
||||
{ key: NO_CHANGE, value: 'No Change', disabled: true },
|
||||
{ key: 'monitored', value: 'Monitored' },
|
||||
{ key: 'unmonitored', value: 'Unmonitored' },
|
||||
];
|
||||
|
||||
function EditMoviesModalContent(props: EditMoviesModalContentProps) {
|
||||
const { movieIds, onSavePress, onModalClose } = props;
|
||||
|
||||
const [monitored, setMonitored] = useState(NO_CHANGE);
|
||||
const [qualityProfileId, setQualityProfileId] = useState<string | number>(
|
||||
NO_CHANGE
|
||||
);
|
||||
const [rootFolderPath, setRootFolderPath] = useState(NO_CHANGE);
|
||||
const [isConfirmMoveModalOpen, setIsConfirmMoveModalOpen] = useState(false);
|
||||
|
||||
const save = useCallback(
|
||||
(moveFiles) => {
|
||||
let hasChanges = false;
|
||||
const payload: SavePayload = {};
|
||||
|
||||
if (monitored !== NO_CHANGE) {
|
||||
hasChanges = true;
|
||||
payload.monitored = monitored === 'monitored';
|
||||
}
|
||||
|
||||
if (qualityProfileId !== NO_CHANGE) {
|
||||
hasChanges = true;
|
||||
payload.qualityProfileId = qualityProfileId as number;
|
||||
}
|
||||
|
||||
if (rootFolderPath !== NO_CHANGE) {
|
||||
hasChanges = true;
|
||||
payload.rootFolderPath = rootFolderPath;
|
||||
payload.moveFiles = moveFiles;
|
||||
}
|
||||
|
||||
if (hasChanges) {
|
||||
onSavePress(payload);
|
||||
}
|
||||
|
||||
onModalClose();
|
||||
},
|
||||
[monitored, qualityProfileId, rootFolderPath, onSavePress, onModalClose]
|
||||
);
|
||||
|
||||
const onInputChange = useCallback(
|
||||
({ name, value }) => {
|
||||
switch (name) {
|
||||
case 'monitored':
|
||||
setMonitored(value);
|
||||
break;
|
||||
case 'qualityProfileId':
|
||||
setQualityProfileId(value);
|
||||
break;
|
||||
case 'rootFolderPath':
|
||||
setRootFolderPath(value);
|
||||
break;
|
||||
default:
|
||||
console.warn('EditMoviesModalContent Unknown Input');
|
||||
}
|
||||
},
|
||||
[setMonitored]
|
||||
);
|
||||
|
||||
const onSavePressWrapper = useCallback(() => {
|
||||
if (rootFolderPath === NO_CHANGE) {
|
||||
save(false);
|
||||
} else {
|
||||
setIsConfirmMoveModalOpen(true);
|
||||
}
|
||||
}, [rootFolderPath, save]);
|
||||
|
||||
const onDoNotMoveMoviePress = useCallback(() => {
|
||||
setIsConfirmMoveModalOpen(false);
|
||||
save(false);
|
||||
}, [setIsConfirmMoveModalOpen, save]);
|
||||
|
||||
const onMoveMoviePress = useCallback(() => {
|
||||
setIsConfirmMoveModalOpen(false);
|
||||
save(true);
|
||||
}, [setIsConfirmMoveModalOpen, save]);
|
||||
|
||||
const selectedCount = movieIds.length;
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>{translate('EditSelectedMovies')}</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Monitored')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="monitored"
|
||||
value={monitored}
|
||||
values={monitoredOptions}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Quality Profile')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.QUALITY_PROFILE_SELECT}
|
||||
name="qualityProfileId"
|
||||
value={qualityProfileId}
|
||||
includeNoChange={true}
|
||||
includeNoChangeDisabled={false}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Root Folder')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.ROOT_FOLDER_SELECT}
|
||||
name="rootFolderPath"
|
||||
value={rootFolderPath}
|
||||
includeNoChange={true}
|
||||
includeNoChangeDisabled={false}
|
||||
selectedValueOptions={{ includeFreeSpace: false }}
|
||||
helpText={
|
||||
'Moving movies to the same root folder can be used to rename movie folders to match updated title or naming format'
|
||||
}
|
||||
onChange={onInputChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter className={styles.modalFooter}>
|
||||
<div className={styles.selected}>
|
||||
{translate('MoviesSelectedInterp', selectedCount.toString())}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
|
||||
|
||||
<Button onPress={onSavePressWrapper}>
|
||||
{translate('Apply Changes')}
|
||||
</Button>
|
||||
</div>
|
||||
</ModalFooter>
|
||||
|
||||
<MoveMovieModal
|
||||
isOpen={isConfirmMoveModalOpen}
|
||||
destinationRootFolder={rootFolderPath}
|
||||
onSavePress={onDoNotMoveMoviePress}
|
||||
onMoveMoviePress={onMoveMoviePress}
|
||||
/>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
|
||||
export default EditMoviesModalContent;
|
@ -0,0 +1,41 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import { SelectActionType, useSelect } from 'App/SelectContext';
|
||||
import PageToolbarOverflowMenuItem from 'Components/Page/Toolbar/PageToolbarOverflowMenuItem';
|
||||
import { icons } from 'Helpers/Props';
|
||||
|
||||
interface MovieIndexSelectAllMenuItemProps {
|
||||
label: string;
|
||||
isSelectMode: boolean;
|
||||
}
|
||||
|
||||
function MovieIndexSelectAllMenuItem(props: MovieIndexSelectAllMenuItemProps) {
|
||||
const { isSelectMode } = props;
|
||||
const [selectState, selectDispatch] = useSelect();
|
||||
const { allSelected, allUnselected } = selectState;
|
||||
|
||||
let iconName = icons.SQUARE_MINUS;
|
||||
|
||||
if (allSelected) {
|
||||
iconName = icons.CHECK_SQUARE;
|
||||
} else if (allUnselected) {
|
||||
iconName = icons.SQUARE;
|
||||
}
|
||||
|
||||
const onPressWrapper = useCallback(() => {
|
||||
selectDispatch({
|
||||
type: allSelected
|
||||
? SelectActionType.UnselectAll
|
||||
: SelectActionType.SelectAll,
|
||||
});
|
||||
}, [allSelected, selectDispatch]);
|
||||
|
||||
return isSelectMode ? (
|
||||
<PageToolbarOverflowMenuItem
|
||||
label={allSelected ? 'Unselect All' : 'Select All'}
|
||||
iconName={iconName}
|
||||
onPress={onPressWrapper}
|
||||
/>
|
||||
) : null;
|
||||
}
|
||||
|
||||
export default MovieIndexSelectAllMenuItem;
|
@ -0,0 +1,72 @@
|
||||
.footer {
|
||||
composes: contentFooter from '~Components/Page/PageContentFooter.css';
|
||||
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.actionButtons,
|
||||
.deleteButtons {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.deleteButtons {
|
||||
margin-left: 50px;
|
||||
}
|
||||
|
||||
.selected {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
flex-grow: 1;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointMedium) {
|
||||
.buttons {
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.selected {
|
||||
justify-content: center;
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
order: -1;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointSmall) {
|
||||
.footer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.buttons {
|
||||
flex-direction: column;
|
||||
margin-top: 20px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.actionButtons {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.actionButtons,
|
||||
.deleteButtons {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.deleteButtons {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.selected {
|
||||
justify-content: center;
|
||||
order: -1;
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'actionButtons': string;
|
||||
'buttons': string;
|
||||
'deleteButtons': string;
|
||||
'footer': string;
|
||||
'selected': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
@ -0,0 +1,216 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { SelectActionType, useSelect } from 'App/SelectContext';
|
||||
import { RENAME_MOVIE } from 'Commands/commandNames';
|
||||
import SpinnerButton from 'Components/Link/SpinnerButton';
|
||||
import PageContentFooter from 'Components/Page/PageContentFooter';
|
||||
import { kinds } from 'Helpers/Props';
|
||||
import { saveMovieEditor } from 'Store/Actions/movieActions';
|
||||
import { fetchRootFolders } from 'Store/Actions/rootFolderActions';
|
||||
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import getSelectedIds from 'Utilities/Table/getSelectedIds';
|
||||
import DeleteMovieModal from './Delete/DeleteMovieModal';
|
||||
import EditMoviesModal from './Edit/EditMoviesModal';
|
||||
import OrganizeMoviesModal from './Organize/OrganizeMoviesModal';
|
||||
import TagsModal from './Tags/TagsModal';
|
||||
import styles from './MovieIndexSelectFooter.css';
|
||||
|
||||
const movieEditorSelector = createSelector(
|
||||
(state) => state.movies,
|
||||
(movies) => {
|
||||
const { isSaving, isDeleting, deleteError } = movies;
|
||||
|
||||
return {
|
||||
isSaving,
|
||||
isDeleting,
|
||||
deleteError,
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
function MovieIndexSelectFooter() {
|
||||
const { isSaving, isDeleting, deleteError } =
|
||||
useSelector(movieEditorSelector);
|
||||
|
||||
const isOrganizingMovies = useSelector(
|
||||
createCommandExecutingSelector(RENAME_MOVIE)
|
||||
);
|
||||
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [isEditModalOpen, setIsEditModalOpen] = useState(false);
|
||||
const [isOrganizeModalOpen, setIsOrganizeModalOpen] = useState(false);
|
||||
const [isTagsModalOpen, setIsTagsModalOpen] = useState(false);
|
||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
||||
const [isSavingMovies, setIsSavingMovies] = useState(false);
|
||||
const [isSavingTags, setIsSavingTags] = useState(false);
|
||||
|
||||
const [selectState, selectDispatch] = useSelect();
|
||||
const { selectedState } = selectState;
|
||||
|
||||
const movieIds = useMemo(() => {
|
||||
return getSelectedIds(selectedState);
|
||||
}, [selectedState]);
|
||||
|
||||
const selectedCount = movieIds.length ? movieIds.length : 0;
|
||||
|
||||
const onEditPress = useCallback(() => {
|
||||
setIsEditModalOpen(true);
|
||||
}, [setIsEditModalOpen]);
|
||||
|
||||
const onEditModalClose = useCallback(() => {
|
||||
setIsEditModalOpen(false);
|
||||
}, [setIsEditModalOpen]);
|
||||
|
||||
const onSavePress = useCallback(
|
||||
(payload) => {
|
||||
setIsSavingMovies(true);
|
||||
setIsEditModalOpen(false);
|
||||
|
||||
dispatch(
|
||||
saveMovieEditor({
|
||||
...payload,
|
||||
movieIds,
|
||||
})
|
||||
);
|
||||
},
|
||||
[movieIds, dispatch]
|
||||
);
|
||||
|
||||
const onOrganizePress = useCallback(() => {
|
||||
setIsOrganizeModalOpen(true);
|
||||
}, [setIsOrganizeModalOpen]);
|
||||
|
||||
const onOrganizeModalClose = useCallback(() => {
|
||||
setIsOrganizeModalOpen(false);
|
||||
}, [setIsOrganizeModalOpen]);
|
||||
|
||||
const onTagsPress = useCallback(() => {
|
||||
setIsTagsModalOpen(true);
|
||||
}, [setIsTagsModalOpen]);
|
||||
|
||||
const onTagsModalClose = useCallback(() => {
|
||||
setIsTagsModalOpen(false);
|
||||
}, [setIsTagsModalOpen]);
|
||||
|
||||
const onApplyTagsPress = useCallback(
|
||||
(tags, applyTags) => {
|
||||
setIsSavingTags(true);
|
||||
setIsTagsModalOpen(false);
|
||||
|
||||
dispatch(
|
||||
saveMovieEditor({
|
||||
movieIds,
|
||||
tags,
|
||||
applyTags,
|
||||
})
|
||||
);
|
||||
},
|
||||
[movieIds, dispatch]
|
||||
);
|
||||
|
||||
const onDeletePress = useCallback(() => {
|
||||
setIsDeleteModalOpen(true);
|
||||
}, [setIsDeleteModalOpen]);
|
||||
|
||||
const onDeleteModalClose = useCallback(() => {
|
||||
setIsDeleteModalOpen(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSaving) {
|
||||
setIsSavingMovies(false);
|
||||
setIsSavingTags(false);
|
||||
}
|
||||
}, [isSaving]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDeleting && !deleteError) {
|
||||
selectDispatch({ type: SelectActionType.UnselectAll });
|
||||
}
|
||||
}, [isDeleting, deleteError, selectDispatch]);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchRootFolders());
|
||||
}, [dispatch]);
|
||||
|
||||
const anySelected = selectedCount > 0;
|
||||
|
||||
return (
|
||||
<PageContentFooter className={styles.footer}>
|
||||
<div className={styles.buttons}>
|
||||
<div className={styles.actionButtons}>
|
||||
<SpinnerButton
|
||||
isSpinning={isSaving && isSavingMovies}
|
||||
isDisabled={!anySelected || isOrganizingMovies}
|
||||
onPress={onEditPress}
|
||||
>
|
||||
{translate('Edit')}
|
||||
</SpinnerButton>
|
||||
|
||||
<SpinnerButton
|
||||
kind={kinds.WARNING}
|
||||
isSpinning={isOrganizingMovies}
|
||||
isDisabled={!anySelected || isOrganizingMovies}
|
||||
onPress={onOrganizePress}
|
||||
>
|
||||
{translate('Rename Files')}
|
||||
</SpinnerButton>
|
||||
|
||||
<SpinnerButton
|
||||
isSpinning={isSaving && isSavingTags}
|
||||
isDisabled={!anySelected || isOrganizingMovies}
|
||||
onPress={onTagsPress}
|
||||
>
|
||||
{translate('SetTags')}
|
||||
</SpinnerButton>
|
||||
</div>
|
||||
|
||||
<div className={styles.deleteButtons}>
|
||||
<SpinnerButton
|
||||
kind={kinds.DANGER}
|
||||
isSpinning={isDeleting}
|
||||
isDisabled={!anySelected || isDeleting}
|
||||
onPress={onDeletePress}
|
||||
>
|
||||
{translate('Delete')}
|
||||
</SpinnerButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.selected}>
|
||||
{translate('MoviesSelectedInterp', selectedCount.toString())}
|
||||
</div>
|
||||
|
||||
<EditMoviesModal
|
||||
isOpen={isEditModalOpen}
|
||||
movieIds={movieIds}
|
||||
onSavePress={onSavePress}
|
||||
onModalClose={onEditModalClose}
|
||||
/>
|
||||
|
||||
<TagsModal
|
||||
isOpen={isTagsModalOpen}
|
||||
movieIds={movieIds}
|
||||
onApplyTagsPress={onApplyTagsPress}
|
||||
onModalClose={onTagsModalClose}
|
||||
/>
|
||||
|
||||
<OrganizeMoviesModal
|
||||
isOpen={isOrganizeModalOpen}
|
||||
movieIds={movieIds}
|
||||
onModalClose={onOrganizeModalClose}
|
||||
/>
|
||||
|
||||
<DeleteMovieModal
|
||||
isOpen={isDeleteModalOpen}
|
||||
movieIds={movieIds}
|
||||
onModalClose={onDeleteModalClose}
|
||||
/>
|
||||
</PageContentFooter>
|
||||
);
|
||||
}
|
||||
|
||||
export default MovieIndexSelectFooter;
|
@ -0,0 +1,37 @@
|
||||
import { IconDefinition } from '@fortawesome/fontawesome-common-types';
|
||||
import React, { useCallback } from 'react';
|
||||
import { SelectActionType, useSelect } from 'App/SelectContext';
|
||||
import PageToolbarButton from 'Components/Page/Toolbar/PageToolbarButton';
|
||||
|
||||
interface MovieIndexSelectModeButtonProps {
|
||||
label: string;
|
||||
iconName: IconDefinition;
|
||||
isSelectMode: boolean;
|
||||
overflowComponent: React.FunctionComponent;
|
||||
onPress: () => void;
|
||||
}
|
||||
|
||||
function MovieIndexSelectModeButton(props: MovieIndexSelectModeButtonProps) {
|
||||
const { label, iconName, isSelectMode, onPress } = props;
|
||||
const [, selectDispatch] = useSelect();
|
||||
|
||||
const onPressWrapper = useCallback(() => {
|
||||
if (isSelectMode) {
|
||||
selectDispatch({
|
||||
type: SelectActionType.Reset,
|
||||
});
|
||||
}
|
||||
|
||||
onPress();
|
||||
}, [isSelectMode, onPress, selectDispatch]);
|
||||
|
||||
return (
|
||||
<PageToolbarButton
|
||||
label={label}
|
||||
iconName={iconName}
|
||||
onPress={onPressWrapper}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default MovieIndexSelectModeButton;
|
@ -0,0 +1,38 @@
|
||||
import { IconDefinition } from '@fortawesome/fontawesome-common-types';
|
||||
import React, { useCallback } from 'react';
|
||||
import { SelectActionType, useSelect } from 'App/SelectContext';
|
||||
import PageToolbarOverflowMenuItem from 'Components/Page/Toolbar/PageToolbarOverflowMenuItem';
|
||||
|
||||
interface MovieIndexSelectModeMenuItemProps {
|
||||
label: string;
|
||||
iconName: IconDefinition;
|
||||
isSelectMode: boolean;
|
||||
onPress: () => void;
|
||||
}
|
||||
|
||||
function MovieIndexSelectModeMenuItem(
|
||||
props: MovieIndexSelectModeMenuItemProps
|
||||
) {
|
||||
const { label, iconName, isSelectMode, onPress } = props;
|
||||
const [, selectDispatch] = useSelect();
|
||||
|
||||
const onPressWrapper = useCallback(() => {
|
||||
if (isSelectMode) {
|
||||
selectDispatch({
|
||||
type: SelectActionType.Reset,
|
||||
});
|
||||
}
|
||||
|
||||
onPress();
|
||||
}, [isSelectMode, onPress, selectDispatch]);
|
||||
|
||||
return (
|
||||
<PageToolbarOverflowMenuItem
|
||||
label={label}
|
||||
iconName={iconName}
|
||||
onPress={onPressWrapper}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default MovieIndexSelectModeMenuItem;
|
@ -0,0 +1,21 @@
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import OrganizeMoviesModalContent from './OrganizeMoviesModalContent';
|
||||
|
||||
interface OrganizeMoviesModalProps {
|
||||
isOpen: boolean;
|
||||
movieIds: number[];
|
||||
onModalClose: () => void;
|
||||
}
|
||||
|
||||
function OrganizeMoviesModal(props: OrganizeMoviesModalProps) {
|
||||
const { isOpen, onModalClose, ...otherProps } = props;
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onModalClose={onModalClose}>
|
||||
<OrganizeMoviesModalContent {...otherProps} onModalClose={onModalClose} />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default OrganizeMoviesModal;
|
@ -0,0 +1,8 @@
|
||||
.renameIcon {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'message': string;
|
||||
'renameIcon': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
@ -0,0 +1,82 @@
|
||||
import { orderBy } from 'lodash';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { RENAME_MOVIE } from 'Commands/commandNames';
|
||||
import Alert from 'Components/Alert';
|
||||
import Icon from 'Components/Icon';
|
||||
import Button from 'Components/Link/Button';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { icons, kinds } from 'Helpers/Props';
|
||||
import { executeCommand } from 'Store/Actions/commandActions';
|
||||
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './OrganizeMoviesModalContent.css';
|
||||
|
||||
interface OrganizeMoviesModalContentProps {
|
||||
movieIds: number[];
|
||||
onModalClose: () => void;
|
||||
}
|
||||
|
||||
function OrganizeMoviesModalContent(props: OrganizeMoviesModalContentProps) {
|
||||
const { movieIds, onModalClose } = props;
|
||||
|
||||
const allMovies = useSelector(createAllMoviesSelector());
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const movieTitles = useMemo(() => {
|
||||
const movies = movieIds.map((id) => {
|
||||
return allMovies.find((s) => s.id === id);
|
||||
});
|
||||
|
||||
const sorted = orderBy(movies, ['sortTitle']);
|
||||
|
||||
return sorted.map((s) => s.title);
|
||||
}, [movieIds, allMovies]);
|
||||
|
||||
const onOrganizePress = useCallback(() => {
|
||||
dispatch(
|
||||
executeCommand({
|
||||
name: RENAME_MOVIE,
|
||||
movieIds,
|
||||
})
|
||||
);
|
||||
|
||||
onModalClose();
|
||||
}, [movieIds, onModalClose, dispatch]);
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>{translate('OrganizeSelectedMovies')}</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<Alert>
|
||||
{translate('PreviewRenameHelpText')}
|
||||
<Icon className={styles.renameIcon} name={icons.ORGANIZE} />
|
||||
</Alert>
|
||||
|
||||
<div className={styles.message}>
|
||||
{translate('OrganizeConfirm', movieTitles.length)}
|
||||
</div>
|
||||
|
||||
<ul>
|
||||
{movieTitles.map((title) => {
|
||||
return <li key={title}>{title}</li>;
|
||||
})}
|
||||
</ul>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
|
||||
|
||||
<Button kind={kinds.DANGER} onPress={onOrganizePress}>
|
||||
{translate('Organize')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
|
||||
export default OrganizeMoviesModalContent;
|
@ -0,0 +1,22 @@
|
||||
import React from 'react';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import TagsModalContent from './TagsModalContent';
|
||||
|
||||
interface TagsModalProps {
|
||||
isOpen: boolean;
|
||||
movieIds: number[];
|
||||
onApplyTagsPress: (tags: number[], applyTags: string) => void;
|
||||
onModalClose: () => void;
|
||||
}
|
||||
|
||||
function TagsModal(props: TagsModalProps) {
|
||||
const { isOpen, onModalClose, ...otherProps } = props;
|
||||
|
||||
return (
|
||||
<Modal isOpen={isOpen} onModalClose={onModalClose}>
|
||||
<TagsModalContent {...otherProps} onModalClose={onModalClose} />
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
|
||||
export default TagsModal;
|
@ -0,0 +1,12 @@
|
||||
.renameIcon {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.message {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.result {
|
||||
padding-top: 4px;
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
// This file is automatically generated.
|
||||
// Please do not change this file!
|
||||
interface CssExports {
|
||||
'message': string;
|
||||
'renameIcon': string;
|
||||
'result': string;
|
||||
}
|
||||
export const cssExports: CssExports;
|
||||
export default cssExports;
|
@ -0,0 +1,172 @@
|
||||
import { concat, uniq } from 'lodash';
|
||||
import React, { useCallback, useMemo, useState } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import Label from 'Components/Label';
|
||||
import Button from 'Components/Link/Button';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import { inputTypes, kinds, sizes } from 'Helpers/Props';
|
||||
import createAllMoviesSelector from 'Store/Selectors/createAllMoviesSelector';
|
||||
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import styles from './TagsModalContent.css';
|
||||
|
||||
interface TagsModalContentProps {
|
||||
movieIds: number[];
|
||||
onApplyTagsPress: (tags: number[], applyTags: string) => void;
|
||||
onModalClose: () => void;
|
||||
}
|
||||
|
||||
function TagsModalContent(props: TagsModalContentProps) {
|
||||
const { movieIds, onModalClose, onApplyTagsPress } = props;
|
||||
|
||||
const allMovies = useSelector(createAllMoviesSelector());
|
||||
const tagList = useSelector(createTagsSelector());
|
||||
|
||||
const [tags, setTags] = useState<number[]>([]);
|
||||
const [applyTags, setApplyTags] = useState('add');
|
||||
|
||||
const movieTags = useMemo(() => {
|
||||
const movies = movieIds.map((id) => {
|
||||
return allMovies.find((s) => s.id === id);
|
||||
});
|
||||
|
||||
return uniq(concat(...movies.map((s) => s.tags)));
|
||||
}, [movieIds, allMovies]);
|
||||
|
||||
const onTagsChange = useCallback(
|
||||
({ value }) => {
|
||||
setTags(value);
|
||||
},
|
||||
[setTags]
|
||||
);
|
||||
|
||||
const onApplyTagsChange = useCallback(
|
||||
({ value }) => {
|
||||
setApplyTags(value);
|
||||
},
|
||||
[setApplyTags]
|
||||
);
|
||||
|
||||
const onApplyPress = useCallback(() => {
|
||||
onApplyTagsPress(tags, applyTags);
|
||||
}, [tags, applyTags, onApplyTagsPress]);
|
||||
|
||||
const applyTagsOptions = [
|
||||
{ key: 'add', value: translate('Add') },
|
||||
{ key: 'remove', value: translate('Remove') },
|
||||
{ key: 'replace', value: translate('Replace') },
|
||||
];
|
||||
|
||||
return (
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>{translate('Tags')}</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<Form>
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Tags')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TAG}
|
||||
name="tags"
|
||||
value={tags}
|
||||
onChange={onTagsChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('ApplyTags')}</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="applyTags"
|
||||
value={applyTags}
|
||||
values={applyTagsOptions}
|
||||
helpTexts={[
|
||||
translate('ApplyTagsHelpTexts1'),
|
||||
translate('ApplyTagsHelpTexts2'),
|
||||
translate('ApplyTagsHelpTexts3'),
|
||||
translate('ApplyTagsHelpTexts4'),
|
||||
]}
|
||||
onChange={onApplyTagsChange}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<FormLabel>{translate('Result')}</FormLabel>
|
||||
|
||||
<div className={styles.result}>
|
||||
{movieTags.map((id) => {
|
||||
const tag = tagList.find((t) => t.id === id);
|
||||
|
||||
if (!tag) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const removeTag =
|
||||
(applyTags === 'remove' && tags.indexOf(id) > -1) ||
|
||||
(applyTags === 'replace' && tags.indexOf(id) === -1);
|
||||
|
||||
return (
|
||||
<Label
|
||||
key={tag.id}
|
||||
title={
|
||||
removeTag
|
||||
? translate('RemovingTag')
|
||||
: translate('ExistingTag')
|
||||
}
|
||||
kind={removeTag ? kinds.INVERSE : kinds.INFO}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
{tag.label}
|
||||
</Label>
|
||||
);
|
||||
})}
|
||||
|
||||
{(applyTags === 'add' || applyTags === 'replace') &&
|
||||
tags.map((id) => {
|
||||
const tag = tagList.find((t) => t.id === id);
|
||||
|
||||
if (!tag) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (movieTags.indexOf(id) > -1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Label
|
||||
key={tag.id}
|
||||
title={translate('AddingTag')}
|
||||
kind={kinds.SUCCESS}
|
||||
size={sizes.LARGE}
|
||||
>
|
||||
{tag.label}
|
||||
</Label>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</FormGroup>
|
||||
</Form>
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<Button onPress={onModalClose}>{translate('Cancel')}</Button>
|
||||
|
||||
<Button kind={kinds.PRIMARY} onPress={onApplyPress}>
|
||||
{translate('Apply')}
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
);
|
||||
}
|
||||
|
||||
export default TagsModalContent;
|
Loading…
Reference in new issue