mirror of
https://github.com/lidarr/lidarr.git
synced 2025-07-15 01:23:53 -07:00
parent
3beac03c00
commit
54e9f88648
89 changed files with 2354 additions and 995 deletions
|
@ -14,3 +14,8 @@
|
|||
.scroller {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.loading {
|
||||
display: inline-block;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -41,7 +41,8 @@ IconButton.propTypes = {
|
|||
};
|
||||
|
||||
IconButton.defaultProps = {
|
||||
className: styles.button
|
||||
className: styles.button,
|
||||
size: 12
|
||||
};
|
||||
|
||||
export default IconButton;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,6 @@
|
|||
|
||||
.label {
|
||||
left: 100%;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -16,6 +16,7 @@ function SpinnerIconButton(props) {
|
|||
<IconButton
|
||||
name={isSpinning ? (spinningName || name) : name}
|
||||
isDisabled={isDisabled || isSpinning}
|
||||
isSpinning={isSpinning}
|
||||
{...otherProps}
|
||||
/>
|
||||
);
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)}`));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -61,7 +61,7 @@ function PageHeaderActionsMenu(props) {
|
|||
{
|
||||
formsAuth &&
|
||||
<MenuItem
|
||||
to={`${window.Sonarr.urlBase}/logout`}
|
||||
to={`${window.Lidarr.urlBase}/logout`}
|
||||
noRouter={true}
|
||||
>
|
||||
<Icon
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue