mirror of
https://github.com/Radarr/Radarr.git
synced 2026-04-24 22:35:49 -04:00
New: Project Aphrodite
This commit is contained in:
@@ -0,0 +1,393 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { inputTypes, sizes } from 'Helpers/Props';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import PageContent from 'Components/Page/PageContent';
|
||||
import PageContentBodyConnector from 'Components/Page/PageContentBodyConnector';
|
||||
import SettingsToolbarConnector from 'Settings/SettingsToolbarConnector';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import RootFoldersConnector from 'RootFolder/RootFoldersConnector';
|
||||
import NamingConnector from './Naming/NamingConnector';
|
||||
|
||||
const rescanAfterRefreshOptions = [
|
||||
{ key: 'always', value: 'Always' },
|
||||
{ key: 'afterManual', value: 'After Manual Refresh' },
|
||||
{ key: 'never', value: 'Never' }
|
||||
];
|
||||
|
||||
const fileDateOptions = [
|
||||
{ key: 'none', value: 'None' },
|
||||
{ key: 'cinemas', value: 'In Cinemas Date' },
|
||||
{ key: 'release', value: 'Physical Release Date' }
|
||||
];
|
||||
|
||||
class MediaManagement extends Component {
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
advancedSettings,
|
||||
isFetching,
|
||||
error,
|
||||
settings,
|
||||
hasSettings,
|
||||
isMono,
|
||||
onInputChange,
|
||||
onSavePress,
|
||||
...otherProps
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<PageContent title="Media Management Settings">
|
||||
<SettingsToolbarConnector
|
||||
advancedSettings={advancedSettings}
|
||||
{...otherProps}
|
||||
onSavePress={onSavePress}
|
||||
/>
|
||||
|
||||
<PageContentBodyConnector>
|
||||
<NamingConnector />
|
||||
|
||||
{
|
||||
isFetching &&
|
||||
<FieldSet legend="Naming Settings">
|
||||
<LoadingIndicator />
|
||||
</FieldSet>
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && error &&
|
||||
<FieldSet legend="Naming Settings">
|
||||
<div>Unable to load Media Management settings</div>
|
||||
</FieldSet>
|
||||
}
|
||||
|
||||
{
|
||||
hasSettings && !isFetching && !error &&
|
||||
<Form
|
||||
id="mediaManagementSettings"
|
||||
{...otherProps}
|
||||
>
|
||||
{
|
||||
advancedSettings &&
|
||||
<FieldSet legend="Folders">
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
<FormLabel>Create empty movie folders</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="createEmptyMovieFolders"
|
||||
helpText="Create missing movie folders during disk scan"
|
||||
onChange={onInputChange}
|
||||
{...settings.createEmptyMovieFolders}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
<FormLabel>Delete empty folders</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="deleteEmptyFolders"
|
||||
helpText="Delete empty movie folders during disk scan and when movie files are deleted"
|
||||
onChange={onInputChange}
|
||||
{...settings.deleteEmptyFolders}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FieldSet>
|
||||
}
|
||||
|
||||
{
|
||||
advancedSettings &&
|
||||
<FieldSet
|
||||
legend="Importing"
|
||||
>
|
||||
{
|
||||
isMono &&
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
<FormLabel>Skip Free Space Check</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="skipFreeSpaceCheckWhenImporting"
|
||||
helpText="Use when Radarr is unable to detect free space from your movie root folder"
|
||||
onChange={onInputChange}
|
||||
{...settings.skipFreeSpaceCheckWhenImporting}
|
||||
/>
|
||||
</FormGroup>
|
||||
}
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
<FormLabel>Use Hardlinks instead of Copy</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="copyUsingHardlinks"
|
||||
helpText="Use Hardlinks when trying to copy files from torrents that are still being seeded"
|
||||
helpTextWarning="Occasionally, file locks may prevent renaming files that are being seeded. You may temporarily disable seeding and use Radarr's rename function as a work around."
|
||||
onChange={onInputChange}
|
||||
{...settings.copyUsingHardlinks}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup size={sizes.MEDIUM}>
|
||||
<FormLabel>Import Extra Files</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="importExtraFiles"
|
||||
helpText="Import matching extra files (subtitles, nfo, etc) after importing an movie file"
|
||||
onChange={onInputChange}
|
||||
{...settings.importExtraFiles}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{
|
||||
settings.importExtraFiles.value &&
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
>
|
||||
<FormLabel>Import Extra Files</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="extraFileExtensions"
|
||||
helpText="Comma separated list of extra files to import, ie sub,nfo (.nfo will be imported as .nfo-orig)"
|
||||
onChange={onInputChange}
|
||||
{...settings.extraFileExtensions}
|
||||
/>
|
||||
</FormGroup>
|
||||
}
|
||||
</FieldSet>
|
||||
}
|
||||
|
||||
<FieldSet
|
||||
legend="File Management"
|
||||
>
|
||||
<FormGroup size={sizes.MEDIUM}>
|
||||
<FormLabel>Ignore Deleted Movies</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="autoUnmonitorPreviouslyDownloadedMovies"
|
||||
helpText="Movies deleted from disk are automatically unmonitored in Radarr"
|
||||
onChange={onInputChange}
|
||||
{...settings.autoUnmonitorPreviouslyDownloadedMovies}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
<FormLabel>Download Propers</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="autoDownloadPropers"
|
||||
helpText="Should Radarr automatically upgrade to propers when available?"
|
||||
onChange={onInputChange}
|
||||
{...settings.autoDownloadPropers}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
<FormLabel>Analyse video files</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="enableMediaInfo"
|
||||
helpText="Extract video information such as resolution, runtime and codec information from files. This requires Radarr to read parts of the file which may cause high disk or network activity during scans."
|
||||
onChange={onInputChange}
|
||||
{...settings.enableMediaInfo}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
>
|
||||
<FormLabel>Rescan Movie Folder after Refresh</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="rescanAfterRefresh"
|
||||
helpText="Rescan the movie folder after refreshing the movie"
|
||||
helpTextWarning="Radarr will not automatically detect changes to files when not set to 'Always'"
|
||||
values={rescanAfterRefreshOptions}
|
||||
onChange={onInputChange}
|
||||
{...settings.rescanAfterRefresh}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
>
|
||||
<FormLabel>Change File Date</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.SELECT}
|
||||
name="fileDate"
|
||||
helpText="Change file date on import/rescan"
|
||||
values={fileDateOptions}
|
||||
onChange={onInputChange}
|
||||
{...settings.fileDate}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
>
|
||||
<FormLabel>Recycling Bin</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.PATH}
|
||||
name="recycleBin"
|
||||
helpText="Movie files will go here when deleted instead of being permanently deleted"
|
||||
onChange={onInputChange}
|
||||
{...settings.recycleBin}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FieldSet>
|
||||
|
||||
{
|
||||
advancedSettings && isMono &&
|
||||
<FieldSet
|
||||
legend="Permissions"
|
||||
>
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
size={sizes.MEDIUM}
|
||||
>
|
||||
<FormLabel>Set Permissions</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="setPermissionsLinux"
|
||||
helpText="Should chmod/chown be run when files are imported/renamed?"
|
||||
helpTextWarning="If you're unsure what these settings do, do not alter them."
|
||||
onChange={onInputChange}
|
||||
{...settings.setPermissionsLinux}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
>
|
||||
<FormLabel>File chmod mode</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="fileChmod"
|
||||
helpText="Octal, applied to media files when imported/renamed by Radarr"
|
||||
onChange={onInputChange}
|
||||
{...settings.fileChmod}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
>
|
||||
<FormLabel>Folder chmod mode</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="folderChmod"
|
||||
helpText="Octal, applied to series/season folders created by Radarr"
|
||||
values={fileDateOptions}
|
||||
onChange={onInputChange}
|
||||
{...settings.folderChmod}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
>
|
||||
<FormLabel>chown User</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="chownUser"
|
||||
helpText="Username or uid. Use uid for remote file systems."
|
||||
values={fileDateOptions}
|
||||
onChange={onInputChange}
|
||||
{...settings.chownUser}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
>
|
||||
<FormLabel>chown Group</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.TEXT}
|
||||
name="chownGroup"
|
||||
helpText="Group name or gid. Use gid for remote file systems."
|
||||
values={fileDateOptions}
|
||||
onChange={onInputChange}
|
||||
{...settings.chownGroup}
|
||||
/>
|
||||
</FormGroup>
|
||||
</FieldSet>
|
||||
}
|
||||
</Form>
|
||||
}
|
||||
|
||||
<FieldSet legend="Root Folders">
|
||||
<RootFoldersConnector />
|
||||
</FieldSet>
|
||||
</PageContentBodyConnector>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
MediaManagement.propTypes = {
|
||||
advancedSettings: PropTypes.bool.isRequired,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
settings: PropTypes.object.isRequired,
|
||||
hasSettings: PropTypes.bool.isRequired,
|
||||
isMono: PropTypes.bool.isRequired,
|
||||
onSavePress: PropTypes.func.isRequired,
|
||||
onInputChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default MediaManagement;
|
||||
@@ -0,0 +1,86 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
||||
import createSystemStatusSelector from 'Store/Selectors/createSystemStatusSelector';
|
||||
import { fetchMediaManagementSettings, setMediaManagementSettingsValue, saveMediaManagementSettings, saveNamingSettings } from 'Store/Actions/settingsActions';
|
||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||
import MediaManagement from './MediaManagement';
|
||||
|
||||
const SECTION = 'mediaManagement';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.advancedSettings,
|
||||
(state) => state.settings.naming,
|
||||
createSettingsSectionSelector(SECTION),
|
||||
createSystemStatusSelector(),
|
||||
(advancedSettings, namingSettings, sectionSettings, systemStatus) => {
|
||||
return {
|
||||
advancedSettings,
|
||||
...sectionSettings,
|
||||
hasPendingChanges: !_.isEmpty(namingSettings.pendingChanges) || sectionSettings.hasPendingChanges,
|
||||
isMono: systemStatus.isMono
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchMediaManagementSettings,
|
||||
setMediaManagementSettingsValue,
|
||||
saveMediaManagementSettings,
|
||||
saveNamingSettings,
|
||||
clearPendingChanges
|
||||
};
|
||||
|
||||
class MediaManagementConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchMediaManagementSettings();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.clearPendingChanges({ section: `settings.${SECTION}` });
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onInputChange = ({ name, value }) => {
|
||||
this.props.setMediaManagementSettingsValue({ name, value });
|
||||
}
|
||||
|
||||
onSavePress = () => {
|
||||
this.props.saveMediaManagementSettings();
|
||||
this.props.saveNamingSettings();
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<MediaManagement
|
||||
onInputChange={this.onInputChange}
|
||||
onSavePress={this.onSavePress}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
MediaManagementConnector.propTypes = {
|
||||
fetchMediaManagementSettings: PropTypes.func.isRequired,
|
||||
setMediaManagementSettingsValue: PropTypes.func.isRequired,
|
||||
saveMediaManagementSettings: PropTypes.func.isRequired,
|
||||
saveNamingSettings: PropTypes.func.isRequired,
|
||||
clearPendingChanges: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(MediaManagementConnector);
|
||||
@@ -0,0 +1,5 @@
|
||||
.namingInput {
|
||||
composes: input from 'Components/Form/TextInput.css';
|
||||
|
||||
font-family: $monoSpaceFontFamily;
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { inputTypes, sizes } from 'Helpers/Props';
|
||||
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
|
||||
import FormInputButton from 'Components/Form/FormInputButton';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Form from 'Components/Form/Form';
|
||||
import FormGroup from 'Components/Form/FormGroup';
|
||||
import FormLabel from 'Components/Form/FormLabel';
|
||||
import FormInputGroup from 'Components/Form/FormInputGroup';
|
||||
import NamingModal from './NamingModal';
|
||||
import styles from './Naming.css';
|
||||
|
||||
class Naming extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this.state = {
|
||||
isNamingModalOpen: false,
|
||||
namingModalOptions: null
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onStandardNamingModalOpenClick = () => {
|
||||
this.setState({
|
||||
isNamingModalOpen: true,
|
||||
namingModalOptions: {
|
||||
name: 'standardMovieFormat',
|
||||
additional: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onMovieFolderNamingModalOpenClick = () => {
|
||||
this.setState({
|
||||
isNamingModalOpen: true,
|
||||
namingModalOptions: {
|
||||
name: 'movieFolderFormat'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onNamingModalClose = () => {
|
||||
this.setState({ isNamingModalOpen: false });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
advancedSettings,
|
||||
isFetching,
|
||||
error,
|
||||
settings,
|
||||
hasSettings,
|
||||
examples,
|
||||
examplesPopulated,
|
||||
onInputChange
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
isNamingModalOpen,
|
||||
namingModalOptions
|
||||
} = this.state;
|
||||
|
||||
const renameEpisodes = hasSettings && settings.renameEpisodes.value;
|
||||
|
||||
const standardMovieFormatHelpTexts = [];
|
||||
const standardMovieFormatErrors = [];
|
||||
const movieFolderFormatHelpTexts = [];
|
||||
const movieFolderFormatErrors = [];
|
||||
|
||||
if (examplesPopulated) {
|
||||
if (examples.movieExample) {
|
||||
standardMovieFormatHelpTexts.push(`Movie: ${examples.movieExample}`);
|
||||
} else {
|
||||
standardMovieFormatErrors.push({ message: 'Movie: Invalid Format' });
|
||||
}
|
||||
|
||||
if (examples.movieFolderExample) {
|
||||
movieFolderFormatHelpTexts.push(`Example: ${examples.movieFolderExample}`);
|
||||
} else {
|
||||
movieFolderFormatErrors.push({ message: 'Invalid Format' });
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<FieldSet legend="Movie Naming">
|
||||
{
|
||||
isFetching &&
|
||||
<LoadingIndicator />
|
||||
}
|
||||
|
||||
{
|
||||
!isFetching && error &&
|
||||
<div>Unable to load Naming settings</div>
|
||||
}
|
||||
|
||||
{
|
||||
hasSettings && !isFetching && !error &&
|
||||
<Form>
|
||||
<FormGroup size={sizes.MEDIUM}>
|
||||
<FormLabel>Rename Movies</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="renameEpisodes"
|
||||
helpText="Radarr will use the existing file name if renaming is disabled"
|
||||
onChange={onInputChange}
|
||||
{...settings.renameEpisodes}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup size={sizes.MEDIUM}>
|
||||
<FormLabel>Replace Illegal Characters</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
type={inputTypes.CHECK}
|
||||
name="replaceIllegalCharacters"
|
||||
helpText="Replace or Remove illegal characters"
|
||||
onChange={onInputChange}
|
||||
{...settings.replaceIllegalCharacters}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{
|
||||
renameEpisodes &&
|
||||
<div>
|
||||
<FormGroup size={sizes.LARGE}>
|
||||
<FormLabel>Standard Movie Format</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
inputClassName={styles.namingInput}
|
||||
type={inputTypes.TEXT}
|
||||
name="standardMovieFormat"
|
||||
buttons={<FormInputButton onPress={this.onStandardNamingModalOpenClick}>?</FormInputButton>}
|
||||
onChange={onInputChange}
|
||||
{...settings.standardMovieFormat}
|
||||
helpTexts={standardMovieFormatHelpTexts}
|
||||
errors={[...standardMovieFormatErrors, ...settings.standardMovieFormat.errors]}
|
||||
/>
|
||||
</FormGroup>
|
||||
</div>
|
||||
}
|
||||
|
||||
<FormGroup
|
||||
advancedSettings={advancedSettings}
|
||||
isAdvanced={true}
|
||||
>
|
||||
<FormLabel>Movie Folder Format</FormLabel>
|
||||
|
||||
<FormInputGroup
|
||||
inputClassName={styles.namingInput}
|
||||
type={inputTypes.TEXT}
|
||||
name="movieFolderFormat"
|
||||
buttons={<FormInputButton onPress={this.onMovieFolderNamingModalOpenClick}>?</FormInputButton>}
|
||||
onChange={onInputChange}
|
||||
{...settings.movieFolderFormat}
|
||||
helpTexts={['Used when adding a new movie or moving movies via the editor', ...movieFolderFormatHelpTexts]}
|
||||
errors={[...movieFolderFormatErrors, ...settings.movieFolderFormat.errors]}
|
||||
/>
|
||||
</FormGroup>
|
||||
|
||||
{
|
||||
namingModalOptions &&
|
||||
<NamingModal
|
||||
isOpen={isNamingModalOpen}
|
||||
advancedSettings={advancedSettings}
|
||||
{...namingModalOptions}
|
||||
value={settings[namingModalOptions.name].value}
|
||||
onInputChange={onInputChange}
|
||||
onModalClose={this.onNamingModalClose}
|
||||
/>
|
||||
}
|
||||
</Form>
|
||||
}
|
||||
</FieldSet>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Naming.propTypes = {
|
||||
advancedSettings: PropTypes.bool.isRequired,
|
||||
isFetching: PropTypes.bool.isRequired,
|
||||
error: PropTypes.object,
|
||||
settings: PropTypes.object.isRequired,
|
||||
hasSettings: PropTypes.bool.isRequired,
|
||||
examples: PropTypes.object.isRequired,
|
||||
examplesPopulated: PropTypes.bool.isRequired,
|
||||
onInputChange: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default Naming;
|
||||
@@ -0,0 +1,97 @@
|
||||
import _ from 'lodash';
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import createSettingsSectionSelector from 'Store/Selectors/createSettingsSectionSelector';
|
||||
import { fetchNamingSettings, setNamingSettingsValue, fetchNamingExamples } from 'Store/Actions/settingsActions';
|
||||
import { clearPendingChanges } from 'Store/Actions/baseActions';
|
||||
import Naming from './Naming';
|
||||
|
||||
const SECTION = 'naming';
|
||||
|
||||
function createMapStateToProps() {
|
||||
return createSelector(
|
||||
(state) => state.settings.advancedSettings,
|
||||
(state) => state.settings.namingExamples,
|
||||
createSettingsSectionSelector(SECTION),
|
||||
(advancedSettings, examples, sectionSettings) => {
|
||||
return {
|
||||
advancedSettings,
|
||||
examples: examples.item,
|
||||
examplesPopulated: !_.isEmpty(examples.item),
|
||||
...sectionSettings
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchNamingSettings,
|
||||
setNamingSettingsValue,
|
||||
fetchNamingExamples,
|
||||
clearPendingChanges
|
||||
};
|
||||
|
||||
class NamingConnector extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this._namingExampleTimeout = null;
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.props.fetchNamingSettings();
|
||||
this.props.fetchNamingExamples();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.props.clearPendingChanges({ section: SECTION });
|
||||
}
|
||||
|
||||
//
|
||||
// Control
|
||||
|
||||
_fetchNamingExamples = () => {
|
||||
this.props.fetchNamingExamples();
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onInputChange = ({ name, value }) => {
|
||||
this.props.setNamingSettingsValue({ name, value });
|
||||
|
||||
if (this._namingExampleTimeout) {
|
||||
clearTimeout(this._namingExampleTimeout);
|
||||
}
|
||||
|
||||
this._namingExampleTimeout = setTimeout(this._fetchNamingExamples, 1000);
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Naming
|
||||
onInputChange={this.onInputChange}
|
||||
onSavePress={this.onSavePress}
|
||||
{...this.props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NamingConnector.propTypes = {
|
||||
fetchNamingSettings: PropTypes.func.isRequired,
|
||||
setNamingSettingsValue: PropTypes.func.isRequired,
|
||||
fetchNamingExamples: PropTypes.func.isRequired,
|
||||
clearPendingChanges: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
export default connect(createMapStateToProps, mapDispatchToProps)(NamingConnector);
|
||||
@@ -0,0 +1,18 @@
|
||||
.groups {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.namingSelectContainer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.namingSelect {
|
||||
composes: select from 'Components/Form/SelectInput.css';
|
||||
|
||||
margin-left: 10px;
|
||||
width: 200px;
|
||||
}
|
||||
@@ -0,0 +1,549 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import FieldSet from 'Components/FieldSet';
|
||||
import Button from 'Components/Link/Button';
|
||||
import SelectInput from 'Components/Form/SelectInput';
|
||||
import TextInput from 'Components/Form/TextInput';
|
||||
import Modal from 'Components/Modal/Modal';
|
||||
import ModalContent from 'Components/Modal/ModalContent';
|
||||
import ModalHeader from 'Components/Modal/ModalHeader';
|
||||
import ModalBody from 'Components/Modal/ModalBody';
|
||||
import ModalFooter from 'Components/Modal/ModalFooter';
|
||||
import NamingOption from './NamingOption';
|
||||
import styles from './NamingModal.css';
|
||||
|
||||
class NamingModal extends Component {
|
||||
|
||||
//
|
||||
// Lifecycle
|
||||
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
|
||||
this._selectionStart = null;
|
||||
this._selectionEnd = null;
|
||||
|
||||
this.state = {
|
||||
separator: ' ',
|
||||
case: 'title'
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onTokenSeparatorChange = (event) => {
|
||||
this.setState({ separator: event.value });
|
||||
}
|
||||
|
||||
onTokenCaseChange = (event) => {
|
||||
this.setState({ case: event.value });
|
||||
}
|
||||
|
||||
onInputSelectionChange = (selectionStart, selectionEnd) => {
|
||||
this._selectionStart = selectionStart;
|
||||
this._selectionEnd = selectionEnd;
|
||||
}
|
||||
|
||||
onOptionPress = ({ isFullFilename, tokenValue }) => {
|
||||
const {
|
||||
name,
|
||||
value,
|
||||
onInputChange
|
||||
} = this.props;
|
||||
|
||||
const selectionStart = this._selectionStart;
|
||||
const selectionEnd = this._selectionEnd;
|
||||
|
||||
if (isFullFilename) {
|
||||
onInputChange({ name, value: tokenValue });
|
||||
} else if (selectionStart == null) {
|
||||
onInputChange({
|
||||
name,
|
||||
value: `${value}${tokenValue}`
|
||||
});
|
||||
} else {
|
||||
const start = value.substring(0, selectionStart);
|
||||
const end = value.substring(selectionEnd);
|
||||
const newValue = `${start}${tokenValue}${end}`;
|
||||
|
||||
onInputChange({ name, value: newValue });
|
||||
this._selectionStart = newValue.length - 1;
|
||||
this._selectionEnd = newValue.length - 1;
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
|
||||
render() {
|
||||
const {
|
||||
name,
|
||||
value,
|
||||
isOpen,
|
||||
advancedSettings,
|
||||
season,
|
||||
episode,
|
||||
daily,
|
||||
anime,
|
||||
additional,
|
||||
onInputChange,
|
||||
onModalClose
|
||||
} = this.props;
|
||||
|
||||
const {
|
||||
separator: tokenSeparator,
|
||||
case: tokenCase
|
||||
} = this.state;
|
||||
|
||||
const separatorOptions = [
|
||||
{ key: ' ', value: 'Space ( )' },
|
||||
{ key: '.', value: 'Period (.)' },
|
||||
{ key: '_', value: 'Underscore (_)' },
|
||||
{ key: '-', value: 'Dash (-)' }
|
||||
];
|
||||
|
||||
const caseOptions = [
|
||||
{ key: 'title', value: 'Default Case' },
|
||||
{ key: 'lower', value: 'Lower Case' },
|
||||
{ key: 'upper', value: 'Upper Case' }
|
||||
];
|
||||
|
||||
const fileNameTokens = [
|
||||
{
|
||||
token: '{Series Title} - S{season:00}E{episode:00} - {Episode Title} {Quality Full}',
|
||||
example: 'Series Title (2010) - S01E01 - Episode Title HDTV-720p Proper'
|
||||
},
|
||||
{
|
||||
token: '{Series Title} - {season:0}x{episode:00} - {Episode Title} {Quality Full}',
|
||||
example: 'Series Title (2010) - 1x01 - Episode Title HDTV-720p Proper'
|
||||
},
|
||||
{
|
||||
token: '{Series.Title}.S{season:00}E{episode:00}.{EpisodeClean.Title}.{Quality.Full}',
|
||||
example: 'Series.Title.(2010).S01E01.Episode.Title.HDTV-720p'
|
||||
}
|
||||
];
|
||||
|
||||
const seriesTokens = [
|
||||
{ token: '{Series Title}', example: 'Series Title!' },
|
||||
{ token: '{Series CleanTitle}', example: 'Series Title' },
|
||||
{ token: '{Series CleanTitleYear}', example: 'Series Title 2010' },
|
||||
{ token: '{Series TitleThe}', example: 'Series Title, The' },
|
||||
{ token: '{Series TitleTheYear}', example: 'Series Title, The (2010)' },
|
||||
{ token: '{Series TitleYear}', example: 'Series Title (2010)' }
|
||||
];
|
||||
|
||||
const seriesIdTokens = [
|
||||
{ token: '{ImdbId}', example: 'tt12345' },
|
||||
{ token: '{TvdbId}', example: '12345' },
|
||||
{ token: '{TvMazeId}', example: '54321' }
|
||||
];
|
||||
|
||||
const seasonTokens = [
|
||||
{ token: '{season:0}', example: '1' },
|
||||
{ token: '{season:00}', example: '01' }
|
||||
];
|
||||
|
||||
const episodeTokens = [
|
||||
{ token: '{episode:0}', example: '1' },
|
||||
{ token: '{episode:00}', example: '01' }
|
||||
];
|
||||
|
||||
const airDateTokens = [
|
||||
{ token: '{Air-Date}', example: '2016-03-20' },
|
||||
{ token: '{Air Date}', example: '2016 03 20' }
|
||||
];
|
||||
|
||||
const absoluteTokens = [
|
||||
{ token: '{absolute:0}', example: '1' },
|
||||
{ token: '{absolute:00}', example: '01' },
|
||||
{ token: '{absolute:000}', example: '001' }
|
||||
];
|
||||
|
||||
const episodeTitleTokens = [
|
||||
{ token: '{Episode Title}', example: 'Episode Title' },
|
||||
{ token: '{Episode CleanTitle}', example: 'Episode Title' }
|
||||
];
|
||||
|
||||
const qualityTokens = [
|
||||
{ token: '{Quality Full}', example: 'HDTV 720p Proper' },
|
||||
{ token: '{Quality Title}', example: 'HDTV 720p' }
|
||||
];
|
||||
|
||||
const mediaInfoTokens = [
|
||||
{ token: '{MediaInfo Simple}', example: 'x264 DTS' },
|
||||
{ token: '{MediaInfo Full}', example: 'x264 DTS [EN+DE]' },
|
||||
{ token: '{MediaInfo VideoCodec}', example: 'x264' },
|
||||
{ token: '{MediaInfo AudioFormat}', example: 'DTS' },
|
||||
{ token: '{MediaInfo AudioChannels}', example: '5.1' }
|
||||
];
|
||||
|
||||
const releaseGroupTokens = [
|
||||
{ token: '{Release Group}', example: 'Rls Grp' }
|
||||
];
|
||||
|
||||
const originalTokens = [
|
||||
{ token: '{Original Title}', example: 'Series.Title.S01E01.HDTV.x264-EVOLVE' },
|
||||
{ token: '{Original Filename}', example: 'series.title.s01e01.hdtv.x264-EVOLVE' }
|
||||
];
|
||||
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isOpen}
|
||||
onModalClose={onModalClose}
|
||||
>
|
||||
<ModalContent onModalClose={onModalClose}>
|
||||
<ModalHeader>
|
||||
File Name Tokens
|
||||
</ModalHeader>
|
||||
|
||||
<ModalBody>
|
||||
<div className={styles.namingSelectContainer}>
|
||||
<SelectInput
|
||||
className={styles.namingSelect}
|
||||
name="separator"
|
||||
value={tokenSeparator}
|
||||
values={separatorOptions}
|
||||
onChange={this.onTokenSeparatorChange}
|
||||
/>
|
||||
|
||||
<SelectInput
|
||||
className={styles.namingSelect}
|
||||
name="case"
|
||||
value={tokenCase}
|
||||
values={caseOptions}
|
||||
onChange={this.onTokenCaseChange}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{
|
||||
!advancedSettings &&
|
||||
<FieldSet legend="File Names">
|
||||
<div className={styles.groups}>
|
||||
{
|
||||
fileNameTokens.map(({ token, example }) => {
|
||||
return (
|
||||
<NamingOption
|
||||
key={token}
|
||||
name={name}
|
||||
value={value}
|
||||
token={token}
|
||||
example={example}
|
||||
isFullFilename={true}
|
||||
tokenSeparator={tokenSeparator}
|
||||
tokenCase={tokenCase}
|
||||
size={sizes.LARGE}
|
||||
onPress={this.onOptionPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</FieldSet>
|
||||
}
|
||||
|
||||
<FieldSet legend="Series">
|
||||
<div className={styles.groups}>
|
||||
{
|
||||
seriesTokens.map(({ token, example }) => {
|
||||
return (
|
||||
<NamingOption
|
||||
key={token}
|
||||
name={name}
|
||||
value={value}
|
||||
token={token}
|
||||
example={example}
|
||||
tokenSeparator={tokenSeparator}
|
||||
tokenCase={tokenCase}
|
||||
onPress={this.onOptionPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</FieldSet>
|
||||
|
||||
<FieldSet legend="Series ID">
|
||||
<div className={styles.groups}>
|
||||
{
|
||||
seriesIdTokens.map(({ token, example }) => {
|
||||
return (
|
||||
<NamingOption
|
||||
key={token}
|
||||
name={name}
|
||||
value={value}
|
||||
token={token}
|
||||
example={example}
|
||||
tokenSeparator={tokenSeparator}
|
||||
tokenCase={tokenCase}
|
||||
onPress={this.onOptionPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</FieldSet>
|
||||
|
||||
{
|
||||
season &&
|
||||
<FieldSet legend="Season">
|
||||
<div className={styles.groups}>
|
||||
{
|
||||
seasonTokens.map(({ token, example }) => {
|
||||
return (
|
||||
<NamingOption
|
||||
key={token}
|
||||
name={name}
|
||||
value={value}
|
||||
token={token}
|
||||
example={example}
|
||||
tokenSeparator={tokenSeparator}
|
||||
tokenCase={tokenCase}
|
||||
onPress={this.onOptionPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</FieldSet>
|
||||
}
|
||||
|
||||
{
|
||||
episode &&
|
||||
<div>
|
||||
<FieldSet legend="Episode">
|
||||
<div className={styles.groups}>
|
||||
{
|
||||
episodeTokens.map(({ token, example }) => {
|
||||
return (
|
||||
<NamingOption
|
||||
key={token}
|
||||
name={name}
|
||||
value={value}
|
||||
token={token}
|
||||
example={example}
|
||||
tokenSeparator={tokenSeparator}
|
||||
tokenCase={tokenCase}
|
||||
onPress={this.onOptionPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</FieldSet>
|
||||
|
||||
{
|
||||
daily &&
|
||||
<FieldSet legend="Air-Date">
|
||||
<div className={styles.groups}>
|
||||
{
|
||||
airDateTokens.map(({ token, example }) => {
|
||||
return (
|
||||
<NamingOption
|
||||
key={token}
|
||||
name={name}
|
||||
value={value}
|
||||
token={token}
|
||||
example={example}
|
||||
tokenSeparator={tokenSeparator}
|
||||
tokenCase={tokenCase}
|
||||
onPress={this.onOptionPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</FieldSet>
|
||||
}
|
||||
|
||||
{
|
||||
anime &&
|
||||
<FieldSet legend="Absolute Episode Number">
|
||||
<div className={styles.groups}>
|
||||
{
|
||||
absoluteTokens.map(({ token, example }) => {
|
||||
return (
|
||||
<NamingOption
|
||||
key={token}
|
||||
name={name}
|
||||
value={value}
|
||||
token={token}
|
||||
example={example}
|
||||
tokenSeparator={tokenSeparator}
|
||||
tokenCase={tokenCase}
|
||||
onPress={this.onOptionPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</FieldSet>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
{
|
||||
additional &&
|
||||
<div>
|
||||
<FieldSet legend="Episode Title">
|
||||
<div className={styles.groups}>
|
||||
{
|
||||
episodeTitleTokens.map(({ token, example }) => {
|
||||
return (
|
||||
<NamingOption
|
||||
key={token}
|
||||
name={name}
|
||||
value={value}
|
||||
token={token}
|
||||
example={example}
|
||||
tokenSeparator={tokenSeparator}
|
||||
tokenCase={tokenCase}
|
||||
onPress={this.onOptionPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</FieldSet>
|
||||
|
||||
<FieldSet legend="Quality">
|
||||
<div className={styles.groups}>
|
||||
{
|
||||
qualityTokens.map(({ token, example }) => {
|
||||
return (
|
||||
<NamingOption
|
||||
key={token}
|
||||
name={name}
|
||||
value={value}
|
||||
token={token}
|
||||
example={example}
|
||||
tokenSeparator={tokenSeparator}
|
||||
tokenCase={tokenCase}
|
||||
onPress={this.onOptionPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</FieldSet>
|
||||
|
||||
<FieldSet legend="Media Info">
|
||||
<div className={styles.groups}>
|
||||
{
|
||||
mediaInfoTokens.map(({ token, example }) => {
|
||||
return (
|
||||
<NamingOption
|
||||
key={token}
|
||||
name={name}
|
||||
value={value}
|
||||
token={token}
|
||||
example={example}
|
||||
tokenSeparator={tokenSeparator}
|
||||
tokenCase={tokenCase}
|
||||
onPress={this.onOptionPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</FieldSet>
|
||||
|
||||
<FieldSet legend="Release Group">
|
||||
<div className={styles.groups}>
|
||||
{
|
||||
releaseGroupTokens.map(({ token, example }) => {
|
||||
return (
|
||||
<NamingOption
|
||||
key={token}
|
||||
name={name}
|
||||
value={value}
|
||||
token={token}
|
||||
example={example}
|
||||
tokenSeparator={tokenSeparator}
|
||||
tokenCase={tokenCase}
|
||||
onPress={this.onOptionPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</FieldSet>
|
||||
|
||||
<FieldSet legend="Original">
|
||||
<div className={styles.groups}>
|
||||
{
|
||||
originalTokens.map(({ token, example }) => {
|
||||
return (
|
||||
<NamingOption
|
||||
key={token}
|
||||
name={name}
|
||||
value={value}
|
||||
token={token}
|
||||
example={example}
|
||||
tokenSeparator={tokenSeparator}
|
||||
tokenCase={tokenCase}
|
||||
size={sizes.LARGE}
|
||||
onPress={this.onOptionPress}
|
||||
/>
|
||||
);
|
||||
}
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</FieldSet>
|
||||
</div>
|
||||
}
|
||||
</ModalBody>
|
||||
|
||||
<ModalFooter>
|
||||
<TextInput
|
||||
name={name}
|
||||
value={value}
|
||||
onChange={onInputChange}
|
||||
onSelectionChange={this.onInputSelectionChange}
|
||||
/>
|
||||
<Button onPress={onModalClose}>
|
||||
Close
|
||||
</Button>
|
||||
</ModalFooter>
|
||||
</ModalContent>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NamingModal.propTypes = {
|
||||
name: PropTypes.string.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
isOpen: PropTypes.bool.isRequired,
|
||||
advancedSettings: PropTypes.bool.isRequired,
|
||||
season: PropTypes.bool.isRequired,
|
||||
episode: PropTypes.bool.isRequired,
|
||||
daily: PropTypes.bool.isRequired,
|
||||
anime: PropTypes.bool.isRequired,
|
||||
additional: PropTypes.bool.isRequired,
|
||||
onInputChange: PropTypes.func.isRequired,
|
||||
onModalClose: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
NamingModal.defaultProps = {
|
||||
season: false,
|
||||
episode: false,
|
||||
daily: false,
|
||||
anime: false,
|
||||
additional: false
|
||||
};
|
||||
|
||||
export default NamingModal;
|
||||
@@ -0,0 +1,66 @@
|
||||
.option {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
margin: 3px;
|
||||
border: 1px solid $borderColor;
|
||||
|
||||
&:hover {
|
||||
.token {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
.example {
|
||||
background-color: #ccc;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.small {
|
||||
width: 420px;
|
||||
}
|
||||
|
||||
.large {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.token {
|
||||
flex: 0 0 50%;
|
||||
padding: 6px 16px;
|
||||
background-color: #eee;
|
||||
font-family: $monoSpaceFontFamily;
|
||||
}
|
||||
|
||||
.example {
|
||||
flex: 0 0 50%;
|
||||
padding: 6px 16px;
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
.lower {
|
||||
text-transform: lowercase;
|
||||
}
|
||||
|
||||
.upper {
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.isFullFilename {
|
||||
.token,
|
||||
.example {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointSmall) {
|
||||
.option.small {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: $breakpointExtraSmall) {
|
||||
.token,
|
||||
.example {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import React, { Component } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { sizes } from 'Helpers/Props';
|
||||
import Link from 'Components/Link/Link';
|
||||
import styles from './NamingOption.css';
|
||||
|
||||
class NamingOption extends Component {
|
||||
|
||||
//
|
||||
// Listeners
|
||||
|
||||
onPress = () => {
|
||||
const {
|
||||
token,
|
||||
tokenSeparator,
|
||||
tokenCase,
|
||||
isFullFilename,
|
||||
onPress
|
||||
} = this.props;
|
||||
|
||||
let tokenValue = token;
|
||||
|
||||
tokenValue = tokenValue.replace(/ /g, tokenSeparator);
|
||||
|
||||
if (tokenCase === 'lower') {
|
||||
tokenValue = token.toLowerCase();
|
||||
} else if (tokenCase === 'upper') {
|
||||
tokenValue = token.toUpperCase();
|
||||
}
|
||||
|
||||
onPress({ isFullFilename, tokenValue });
|
||||
}
|
||||
|
||||
//
|
||||
// Render
|
||||
render() {
|
||||
const {
|
||||
token,
|
||||
tokenSeparator,
|
||||
example,
|
||||
tokenCase,
|
||||
isFullFilename,
|
||||
size
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<Link
|
||||
className={classNames(
|
||||
styles.option,
|
||||
styles[size],
|
||||
styles[tokenCase],
|
||||
isFullFilename && styles.isFullFilename
|
||||
)}
|
||||
onPress={this.onPress}
|
||||
>
|
||||
<div className={styles.token}>
|
||||
{token.replace(/ /g, tokenSeparator)}
|
||||
</div>
|
||||
|
||||
<div className={styles.example}>
|
||||
{example.replace(/ /g, tokenSeparator)}
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
NamingOption.propTypes = {
|
||||
token: PropTypes.string.isRequired,
|
||||
example: PropTypes.string.isRequired,
|
||||
tokenSeparator: PropTypes.string.isRequired,
|
||||
tokenCase: PropTypes.string.isRequired,
|
||||
isFullFilename: PropTypes.bool.isRequired,
|
||||
size: PropTypes.oneOf([sizes.SMALL, sizes.LARGE]),
|
||||
onPress: PropTypes.func.isRequired
|
||||
};
|
||||
|
||||
NamingOption.defaultProps = {
|
||||
size: sizes.SMALL,
|
||||
isFullFilename: false
|
||||
};
|
||||
|
||||
export default NamingOption;
|
||||
Reference in New Issue
Block a user