Various UI Fixes and Updates

Closes #188
Closes #185
Closes #187
This commit is contained in:
Qstick 2018-01-25 22:01:53 -05:00
parent 3beac03c00
commit 54e9f88648
89 changed files with 2354 additions and 995 deletions

View file

@ -14,3 +14,8 @@
.scroller {
margin-top: 20px;
}
.loading {
display: inline-block;
margin-right: auto;
}

View file

@ -3,11 +3,12 @@ import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import { scrollDirections } from 'Helpers/Props';
import Button from 'Components/Link/Button';
import Scroller from 'Components/Scroller/Scroller';
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 Scroller from 'Components/Scroller/Scroller';
import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import PathInput from 'Components/Form/PathInput';
@ -43,12 +44,15 @@ class FileBrowserModalContent extends Component {
};
}
componentDidUpdate(prevProps) {
componentDidUpdate(prevProps, prevState) {
const {
currentPath
} = this.props;
if (currentPath !== this.state.currentPath) {
if (
currentPath !== this.state.currentPath &&
currentPath !== prevState.currentPath
) {
this.setState({ currentPath });
this._scrollerNode.scrollTop = 0;
}
@ -91,6 +95,9 @@ class FileBrowserModalContent extends Component {
render() {
const {
isFetching,
isPopulated,
error,
parent,
directories,
files,
@ -125,61 +132,77 @@ class FileBrowserModalContent extends Component {
ref={this.setScrollerRef}
className={styles.scroller}
>
<Table columns={columns}>
<TableBody>
{
emptyParent &&
<FileBrowserRow
type="computer"
name="My Computer"
path={parent}
onPress={this.onRowPress}
/>
}
{
!!error &&
<div>Error loading contents</div>
}
{
!emptyParent && parent &&
<FileBrowserRow
type="parent"
name="..."
path={parent}
onPress={this.onRowPress}
/>
}
{
directories.map((directory) => {
return (
{
isPopulated && !error &&
<Table columns={columns}>
<TableBody>
{
emptyParent &&
<FileBrowserRow
key={directory.path}
type={directory.type}
name={directory.name}
path={directory.path}
type="computer"
name="My Computer"
path={parent}
onPress={this.onRowPress}
/>
);
})
}
}
{
files.map((file) => {
return (
{
!emptyParent && parent &&
<FileBrowserRow
key={file.path}
type={file.type}
name={file.name}
path={file.path}
type="parent"
name="..."
path={parent}
onPress={this.onRowPress}
/>
);
})
}
</TableBody>
</Table>
}
{
directories.map((directory) => {
return (
<FileBrowserRow
key={directory.path}
type={directory.type}
name={directory.name}
path={directory.path}
onPress={this.onRowPress}
/>
);
})
}
{
files.map((file) => {
return (
<FileBrowserRow
key={file.path}
type={file.type}
name={file.name}
path={file.path}
onPress={this.onRowPress}
/>
);
})
}
</TableBody>
</Table>
}
</Scroller>
</ModalBody>
<ModalFooter>
{
isFetching &&
<LoadingIndicator
className={styles.loading}
size={20}
/>
}
<Button
onPress={onModalClose}
>
@ -200,6 +223,9 @@ class FileBrowserModalContent extends Component {
FileBrowserModalContent.propTypes = {
name: PropTypes.string.isRequired,
value: PropTypes.string.isRequired,
isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object,
parent: PropTypes.string,
currentPath: PropTypes.string.isRequired,
directories: PropTypes.arrayOf(PropTypes.object).isRequired,

View file

@ -11,6 +11,9 @@ function createMapStateToProps() {
(state) => state.paths,
(paths) => {
const {
isFetching,
isPopulated,
error,
parent,
currentPath,
directories,
@ -22,6 +25,9 @@ function createMapStateToProps() {
});
return {
isFetching,
isPopulated,
error,
parent,
currentPath,
directories,

View file

@ -8,12 +8,23 @@ class NumberInput extends Component {
// Listeners
onChange = ({ name, value }) => {
const {
min,
max
} = this.props;
let newValue = null;
if (value) {
newValue = this.props.isFloat ? parseFloat(value) : parseInt(value);
}
if (min != null && newValue < min) {
newValue = min;
} else if (max != null && newValue > max) {
newValue = max;
}
this.props.onChange({
name,
value: newValue
@ -40,6 +51,8 @@ class NumberInput extends Component {
NumberInput.propTypes = {
value: PropTypes.number,
min: PropTypes.number,
max: PropTypes.number,
isFloat: PropTypes.bool.isRequired,
onChange: PropTypes.func.isRequired
};

View file

@ -98,7 +98,7 @@ class ClipboardButton extends Component {
className={styles.button}
{...otherProps}
>
<span className={showStateIcon && styles.showStateIcon}>
<span className={showStateIcon ? styles.showStateIcon : undefined}>
{
showSuccess &&
<span className={styles.stateIconContainer}>

View file

@ -41,7 +41,8 @@ IconButton.propTypes = {
};
IconButton.defaultProps = {
className: styles.button
className: styles.button,
size: 12
};
export default IconButton;

View file

@ -47,13 +47,13 @@ class Link extends Component {
el = 'a';
linkProps.href = to;
linkProps.target = target || '_self';
} else if (to.startsWith(window.Sonarr.urlBase)) {
} else if (to.startsWith(window.Lidarr.urlBase)) {
el = RouterLink;
linkProps.to = to;
linkProps.target = target;
} else {
el = RouterLink;
linkProps.to = `${window.Sonarr.urlBase}/${to.replace(/^\//, '')}`;
linkProps.to = `${window.Lidarr.urlBase}/${to.replace(/^\//, '')}`;
linkProps.target = target;
}
}

View file

@ -32,6 +32,6 @@
.label {
left: 100%;
opacity: 0;
visibility: hidden;
}
}

View file

@ -129,7 +129,7 @@ class SpinnerErrorButton extends Component {
isSpinning={isSpinning}
{...otherProps}
>
<span className={showIcon && styles.showIcon}>
<span className={showIcon ? styles.showIcon : undefined}>
{
showIcon &&
<span className={styles.iconContainer}>

View file

@ -16,6 +16,7 @@ function SpinnerIconButton(props) {
<IconButton
name={isSpinning ? (spinningName || name) : name}
isDisabled={isDisabled || isSpinning}
isSpinning={isSpinning}
{...otherProps}
/>
);

View file

@ -1,7 +1,6 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Portal from 'react-portal';
import classNames from 'classnames';
import elementClass from 'element-class';
import getUniqueElememtId from 'Utilities/getUniqueElementId';
@ -27,6 +26,8 @@ class Modal extends Component {
constructor(props, context) {
super(props, context);
this._node = document.getElementById('modal-root');
this._backgroundRef = null;
this._modalId = getUniqueElememtId();
}
@ -57,6 +58,10 @@ class Modal extends Component {
//
// Control
_setBackgroundRef = (ref) => {
this._backgroundRef = ref;
}
_openModal() {
openModals.push(this._modalId);
window.addEventListener('keydown', this.onKeyDown);
@ -79,9 +84,9 @@ class Modal extends Component {
const targetElement = this._findEventTarget(event);
if (targetElement) {
const modalElement = ReactDOM.findDOMNode(this.refs.modal);
const backgroundElement = ReactDOM.findDOMNode(this._backgroundRef);
return !modalElement || !modalElement.contains(targetElement);
return backgroundElement.isEqualNode(targetElement);
}
return false;
@ -138,10 +143,6 @@ class Modal extends Component {
}
}
onClosePress = (event) => {
this.props.onModalClose();
}
//
// Render
@ -155,36 +156,32 @@ class Modal extends Component {
isOpen
} = this.props;
return (
<Portal
isOpened={isOpen}
if (!isOpen) {
return null;
}
return ReactDOM.createPortal(
<div
className={styles.modalContainer}
>
<div>
{
isOpen &&
<div
className={styles.modalContainer}
>
<div
className={backdropClassName}
onMouseDown={this.onBackdropBeginPress}
onMouseUp={this.onBackdropEndPress}
>
<div
ref="modal"
className={classNames(
className,
styles[size]
)}
style={style}
>
{children}
</div>
</div>
</div>
}
<div
ref={this._setBackgroundRef}
className={backdropClassName}
onMouseDown={this.onBackdropBeginPress}
onMouseUp={this.onBackdropEndPress}
>
<div
className={classNames(
className,
styles[size]
)}
style={style}
>
{children}
</div>
</div>
</Portal>
</div>,
this._node
);
}
}

View file

@ -13,7 +13,7 @@ function NotFound({ message }) {
<img
className={styles.image}
src={`${window.Sonarr.urlBase}/Content/Images/404.png`}
src={`${window.Lidarr.urlBase}/Content/Images/404.png`}
/>
</div>
</PageContent>

View file

@ -19,11 +19,11 @@ function createMapStateToProps() {
function createMapDispatchToProps(dispatch, props) {
return {
onGoToArtist(foreignArtistId) {
dispatch(push(`${window.Sonarr.urlBase}/artist/${foreignArtistId}`));
dispatch(push(`${window.Lidarr.urlBase}/artist/${foreignArtistId}`));
},
onGoToAddNewArtist(query) {
dispatch(push(`${window.Sonarr.urlBase}/add/new?term=${encodeURIComponent(query)}`));
dispatch(push(`${window.Lidarr.urlBase}/add/new?term=${encodeURIComponent(query)}`));
}
};
}

View file

@ -51,10 +51,10 @@ class PageHeader extends Component {
return (
<div className={styles.header}>
<div className={styles.logoContainer}>
<Link to={`${window.Sonarr.urlBase}/`}>
<Link to={`${window.Lidarr.urlBase}/`}>
<img
className={styles.logo}
src={`${window.Sonarr.urlBase}/Content/Images/logo.svg`}
src={`${window.Lidarr.urlBase}/Content/Images/logo.svg`}
/>
</Link>
</div>
@ -74,6 +74,7 @@ class PageHeader extends Component {
className={styles.donate}
name={icons.HEART}
to="https://www.paypal.me/Lidarr"
size={14}
/>
<PageHeaderActionsMenuConnector
onKeyboardShortcutsPress={this.onOpenKeyboardShortcutsModal}

View file

@ -61,7 +61,7 @@ function PageHeaderActionsMenu(props) {
{
formsAuth &&
<MenuItem
to={`${window.Sonarr.urlBase}/logout`}
to={`${window.Lidarr.urlBase}/logout`}
noRouter={true}
>
<Icon

View file

@ -15,7 +15,7 @@ import LoadingPage from './LoadingPage';
import Page from './Page';
function testLocalStorage() {
const key = 'sonarrTest';
const key = 'lidarrTest';
try {
localStorage.setItem(key, key);
@ -64,7 +64,7 @@ function createMapStateToProps() {
function createMapDispatchToProps(dispatch, props) {
return {
dispatchFetchSeries() {
dispatchFetchArtist() {
dispatch(fetchArtist());
},
dispatchFetchTags() {
@ -109,7 +109,7 @@ class PageConnector extends Component {
componentDidMount() {
if (!this.props.isPopulated) {
this.props.dispatchFetchSeries();
this.props.dispatchFetchArtist();
this.props.dispatchFetchTags();
this.props.dispatchFetchQualityProfiles();
this.props.dispatchFetchLanguageProfiles();
@ -133,7 +133,7 @@ class PageConnector extends Component {
const {
isPopulated,
hasError,
dispatchFetchSeries,
dispatchFetchArtist,
dispatchFetchTags,
dispatchFetchQualityProfiles,
dispatchFetchLanguageProfiles,
@ -171,7 +171,7 @@ PageConnector.propTypes = {
isPopulated: PropTypes.bool.isRequired,
hasError: PropTypes.bool.isRequired,
isSidebarVisible: PropTypes.bool.isRequired,
dispatchFetchSeries: PropTypes.func.isRequired,
dispatchFetchArtist: PropTypes.func.isRequired,
dispatchFetchTags: PropTypes.func.isRequired,
dispatchFetchQualityProfiles: PropTypes.func.isRequired,
dispatchFetchLanguageProfiles: PropTypes.func.isRequired,

View file

@ -415,7 +415,7 @@ class PageSidebar extends Component {
transform
} = this.state;
const urlBase = window.Sonarr.urlBase;
const urlBase = window.Lidarr.urlBase;
const pathname = urlBase ? location.pathname.substr(urlBase.length) || '/' : location.pathname;
const activeParent = getActiveParent(pathname);

View file

@ -80,7 +80,7 @@ class SignalRConnector extends Component {
componentDidMount() {
console.log('Starting signalR');
this.signalRconnection = $.connection('/signalr', { apiKey: window.Sonarr.apiKey });
this.signalRconnection = $.connection('/signalr', { apiKey: window.Lidarr.apiKey });
this.signalRconnection.stateChanged(this.onStateChanged);
this.signalRconnection.received(this.onReceived);
@ -232,11 +232,12 @@ class SignalRConnector extends Component {
}
handleTrackFile = (body) => {
const section = 'trackFiles';
if (body.action === 'updated') {
this.props.updateItem({
section: 'trackFiles',
...body.resource
});
this.props.updateItem({ section, ...body.resource });
} else if (body.action === 'deleted') {
this.props.removeItem({ section, id: body.resource.id });
}
}
@ -335,7 +336,7 @@ class SignalRConnector extends Component {
}
onReconnecting = () => {
if (window.Sonarr.unloading) {
if (window.Lidarr.unloading) {
return;
}
@ -349,7 +350,7 @@ class SignalRConnector extends Component {
}
onDisconnected = () => {
if (window.Sonarr.unloading) {
if (window.Lidarr.unloading) {
return;
}

View file

@ -14,14 +14,15 @@ function SpinnerIcon(props) {
return (
<Icon
name={isSpinning ? (spinningName || name) : name}
isSpinning={isSpinning}
{...otherProps}
/>
);
}
SpinnerIcon.propTypes = {
name: PropTypes.string.isRequired,
spinningName: PropTypes.string.isRequired,
name: PropTypes.object.isRequired,
spinningName: PropTypes.object.isRequired,
isSpinning: PropTypes.bool.isRequired
};

View file

@ -18,7 +18,7 @@ function TableOptionsColumn(props) {
} = props;
return (
<div className={!isModifiable && styles.notDragable}>
<div className={isModifiable ? undefined : styles.notDragable}>
<div
className={classNames(
styles.column,