Initial Commit Rework

This commit is contained in:
Qstick 2017-09-03 22:20:56 -04:00
parent 74a4cc048c
commit 95051cbd63
2483 changed files with 101351 additions and 111396 deletions

View file

@ -0,0 +1,34 @@
import PropTypes from 'prop-types';
import React from 'react';
import Modal from 'Components/Modal/Modal';
import OrganizePreviewModalContentConnector from './OrganizePreviewModalContentConnector';
function OrganizePreviewModal(props) {
const {
isOpen,
onModalClose,
...otherProps
} = props;
return (
<Modal
isOpen={isOpen}
onModalClose={onModalClose}
>
{
isOpen &&
<OrganizePreviewModalContentConnector
{...otherProps}
onModalClose={onModalClose}
/>
}
</Modal>
);
}
OrganizePreviewModal.propTypes = {
isOpen: PropTypes.bool.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default OrganizePreviewModal;

View file

@ -0,0 +1,39 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { clearOrganizePreview } from 'Store/Actions/organizePreviewActions';
import OrganizePreviewModal from './OrganizePreviewModal';
const mapDispatchToProps = {
clearOrganizePreview
};
class OrganizePreviewModalConnector extends Component {
//
// Listeners
onModalClose = () => {
this.props.clearOrganizePreview();
this.props.onModalClose();
}
//
// Render
render() {
return (
<OrganizePreviewModal
{...this.props}
onModalClose={this.onModalClose}
/>
);
}
}
OrganizePreviewModalConnector.propTypes = {
clearOrganizePreview: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default connect(undefined, mapDispatchToProps)(OrganizePreviewModalConnector);

View file

@ -0,0 +1,24 @@
.path {
margin-left: 5px;
font-weight: bold;
}
.episodeFormat {
margin-left: 5px;
font-family: $monoSpaceFontFamily;
}
.previews {
margin-top: 10px;
}
.selectAllInputContainer {
margin-right: auto;
line-height: 30px;
}
.selectAllInput {
composes: input from 'Components/Form/CheckInput.css';
margin: 0;
}

View file

@ -0,0 +1,200 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import getSelectedIds from 'Utilities/Table/getSelectedIds';
import selectAll from 'Utilities/Table/selectAll';
import toggleSelected from 'Utilities/Table/toggleSelected';
import { kinds } from 'Helpers/Props';
import Alert from 'Components/Alert';
import Button from 'Components/Link/Button';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
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 CheckInput from 'Components/Form/CheckInput';
import OrganizePreviewRow from './OrganizePreviewRow';
import styles from './OrganizePreviewModalContent.css';
function getValue(allSelected, allUnselected) {
if (allSelected) {
return true;
} else if (allUnselected) {
return false;
}
return null;
}
class OrganizePreviewModalContent extends Component {
//
// Lifecycle
constructor(props, context) {
super(props, context);
this.state = {
allSelected: false,
allUnselected: false,
lastToggled: null,
selectedState: {}
};
}
//
// Control
getSelectedIds = () => {
return getSelectedIds(this.state.selectedState);
}
//
// Listeners
onSelectAllChange = ({ value }) => {
this.setState(selectAll(this.state.selectedState, value));
}
onSelectedChange = ({ id, value, shiftKey = false }) => {
this.setState((state) => {
return toggleSelected(state, this.props.items, id, value, shiftKey);
});
}
onOrganizePress = () => {
this.props.onOrganizePress(this.getSelectedIds());
}
//
// Render
render() {
const {
isFetching,
isPopulated,
error,
items,
renameEpisodes,
episodeFormat,
path,
onModalClose
} = this.props;
const {
allSelected,
allUnselected,
selectedState
} = this.state;
const selectAllValue = getValue(allSelected, allUnselected);
return (
<ModalContent onModalClose={onModalClose}>
<ModalHeader>
Organize & Rename
</ModalHeader>
<ModalBody>
{
isFetching &&
<LoadingIndicator />
}
{
!isFetching && error &&
<div>Error loading previews</div>
}
{
!isFetching && isPopulated && !items.length &&
<div>
{
renameEpisodes ?
<div>Success! My work is done, no files to rename.</div> :
<div>Renaming is disabled, nothing to rename</div>
}
</div>
}
{
!isFetching && isPopulated && !!items.length &&
<div>
<Alert>
<div>
All paths are relative to:
<span className={styles.path}>
{path}
</span>
</div>
<div>
Naming pattern:
<span className={styles.episodeFormat}>
{episodeFormat}
</span>
</div>
</Alert>
<div className={styles.previews}>
{
items.map((item) => {
return (
<OrganizePreviewRow
key={item.episodeFileId}
id={item.episodeFileId}
existingPath={item.existingPath}
newPath={item.newPath}
isSelected={selectedState[item.episodeFileId]}
onSelectedChange={this.onSelectedChange}
/>
);
})
}
</div>
</div>
}
</ModalBody>
<ModalFooter>
{
isPopulated && !!items.length &&
<CheckInput
className={styles.selectAllInput}
containerClassName={styles.selectAllInputContainer}
name="selectAll"
value={selectAllValue}
onChange={this.onSelectAllChange}
/>
}
<Button
onPress={onModalClose}
>
Cancel
</Button>
<Button
kind={kinds.PRIMARY}
onPress={this.onOrganizePress}
>
Organize
</Button>
</ModalFooter>
</ModalContent>
);
}
}
OrganizePreviewModalContent.propTypes = {
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
items: PropTypes.arrayOf(PropTypes.object).isRequired,
path: PropTypes.string.isRequired,
renameEpisodes: PropTypes.bool,
episodeFormat: PropTypes.string,
onOrganizePress: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default OrganizePreviewModalContent;

View file

@ -0,0 +1,91 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createArtistSelector from 'Store/Selectors/createArtistSelector';
import { fetchOrganizePreview } from 'Store/Actions/organizePreviewActions';
import { fetchNamingSettings } from 'Store/Actions/settingsActions';
import { executeCommand } from 'Store/Actions/commandActions';
import * as commandNames from 'Commands/commandNames';
import OrganizePreviewModalContent from './OrganizePreviewModalContent';
function createMapStateToProps() {
return createSelector(
(state) => state.organizePreview,
(state) => state.settings.naming,
createArtistSelector(),
(organizePreview, naming, series) => {
const props = { ...organizePreview };
props.isFetching = organizePreview.isFetching || naming.isFetching;
props.isPopulated = organizePreview.isPopulated && naming.isPopulated;
props.error = organizePreview.error || naming.error;
props.renameEpisodes = naming.item.renameEpisodes;
props.episodeFormat = naming.item[`${series.seriesType}EpisodeFormat`];
props.path = series.path;
return props;
}
);
}
const mapDispatchToProps = {
fetchOrganizePreview,
fetchNamingSettings,
executeCommand
};
class OrganizePreviewModalContentConnector extends Component {
//
// Lifecycle
componentDidMount() {
const {
seriesId,
seasonNumber
} = this.props;
this.props.fetchOrganizePreview({
seriesId,
seasonNumber
});
this.props.fetchNamingSettings();
}
//
// Listeners
onOrganizePress = (files) => {
this.props.executeCommand({
name: commandNames.RENAME_FILES,
seriesId: this.props.seriesId,
files
});
this.props.onModalClose();
}
//
// Render
render() {
return (
<OrganizePreviewModalContent
{...this.props}
onOrganizePress={this.onOrganizePress}
/>
);
}
}
OrganizePreviewModalContentConnector.propTypes = {
seriesId: PropTypes.number.isRequired,
seasonNumber: PropTypes.number,
fetchOrganizePreview: PropTypes.func.isRequired,
fetchNamingSettings: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired,
onModalClose: PropTypes.func.isRequired
};
export default connect(createMapStateToProps, mapDispatchToProps)(OrganizePreviewModalContentConnector);

View file

@ -0,0 +1,20 @@
.row {
display: flex;
margin-bottom: 5px;
padding: 5px 0;
border-bottom: 1px solid $borderColor;
&:last-of-type {
margin-bottom: 0;
padding-bottom: 0;
border-bottom: none;
}
}
.selectedContainer {
margin-right: 30px;
}
.path {
margin-left: 10px;
}

View file

@ -0,0 +1,90 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { icons, kinds } from 'Helpers/Props';
import Icon from 'Components/Icon';
import CheckInput from 'Components/Form/CheckInput';
import styles from './OrganizePreviewRow.css';
class OrganizePreviewRow extends Component {
//
// Lifecycle
componentDidMount() {
const {
id,
onSelectedChange
} = this.props;
onSelectedChange({ id, value: true });
}
//
// Listeners
onSelectedChange = ({ value, shiftKey }) => {
const {
id,
onSelectedChange
} = this.props;
onSelectedChange({ id, value, shiftKey });
}
//
// Render
render() {
const {
id,
existingPath,
newPath,
isSelected
} = this.props;
return (
<div className={styles.row}>
<CheckInput
containerClassName={styles.selectedContainer}
name={id.toString()}
value={isSelected}
onChange={this.onSelectedChange}
/>
<div>
<div>
<Icon
name={icons.SUBTRACT}
kind={kinds.DANGER}
/>
<span className={styles.path}>
{existingPath}
</span>
</div>
<div>
<Icon
name={icons.ADD}
kind={kinds.SUCCESS}
/>
<span className={styles.path}>
{newPath}
</span>
</div>
</div>
</div>
);
}
}
OrganizePreviewRow.propTypes = {
id: PropTypes.number.isRequired,
existingPath: PropTypes.string.isRequired,
newPath: PropTypes.string.isRequired,
isSelected: PropTypes.bool,
onSelectedChange: PropTypes.func.isRequired
};
export default OrganizePreviewRow;