New: UI Updates, Tag manager, More custom filters (#437)

* New: UI Updates, Tag manager, More custom filters

* fixup! Fix ScanFixture Unit Tests

* Fixed: Sentry Errors from UI don't have release, branch, environment

* Changed: Bump Mobile Detect for New Device Detection

* Fixed: Build on changes to package.json

* fixup! Add MetadataProfile filter option

* fixup! Tag Note, Blacklist, Manual Import

* fixup: Remove connectSection

* fixup: root folder comment
This commit is contained in:
Qstick 2018-08-07 20:57:15 -04:00 committed by GitHub
parent afa78b1d20
commit 6581b3a2c5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
198 changed files with 3057 additions and 888 deletions

View file

@ -1,4 +1,4 @@
.descriptionList {
margin-top: 0;
margin-bottom: 20px;
margin-bottom: 0;
}

View file

@ -9,11 +9,12 @@ class DescriptionList extends Component {
render() {
const {
className,
children
} = this.props;
return (
<dl className={styles.descriptionList}>
<dl className={className}>
{children}
</dl>
);
@ -21,7 +22,12 @@ class DescriptionList extends Component {
}
DescriptionList.propTypes = {
className: PropTypes.string.isRequired,
children: PropTypes.node
};
DescriptionList.defaultProps = {
className: styles.descriptionList
};
export default DescriptionList;

View file

@ -0,0 +1,18 @@
import React from 'react';
import FilterBuilderRowValue from './FilterBuilderRowValue';
const protocols = [
{ id: 'continuing', name: 'Continuing' },
{ id: 'ended', name: 'Ended' }
];
function ArtistStatusFilterBuilderRowValue(props) {
return (
<FilterBuilderRowValue
tagList={protocols}
{...props}
/>
);
}
export default ArtistStatusFilterBuilderRowValue;

View file

@ -0,0 +1,18 @@
import React from 'react';
import FilterBuilderRowValue from './FilterBuilderRowValue';
const protocols = [
{ id: true, name: 'true' },
{ id: false, name: 'false' }
];
function BoolFilterBuilderRowValue(props) {
return (
<FilterBuilderRowValue
tagList={protocols}
{...props}
/>
);
}
export default BoolFilterBuilderRowValue;

View file

@ -0,0 +1,15 @@
.container {
display: flex;
}
.numberInput {
composes: text from 'Components/Form/TextInput.css';
margin-right: 3px;
}
.selectInput {
composes: select from 'Components/Form/SelectInput.css';
margin-left: 3px;
}

View file

@ -0,0 +1,171 @@
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import isString from 'Utilities/String/isString';
import { IN_LAST, IN_NEXT } from 'Helpers/Props/filterTypes';
import NumberInput from 'Components/Form/NumberInput';
import SelectInput from 'Components/Form/SelectInput';
import TextInput from 'Components/Form/TextInput';
import { NAME } from './FilterBuilderRowValue';
import styles from './DateFilterBuilderRowValue.css';
const timeOptions = [
{ key: 'seconds', value: 'seconds' },
{ key: 'minutes', value: 'minutes' },
{ key: 'hours', value: 'hours' },
{ key: 'days', value: 'days' },
{ key: 'weeks', value: 'weeks' },
{ key: 'months', value: 'months' }
];
function isInFilter(filterType) {
return filterType === IN_LAST || filterType === IN_NEXT;
}
class DateFilterBuilderRowValue extends Component {
//
// Lifecycle
componentDidMount() {
const {
filterType,
filterValue,
onChange
} = this.props;
if (isInFilter(filterType) && isString(filterValue)) {
onChange({
name: NAME,
value: {
time: timeOptions[0].key,
value: null
}
});
}
}
componentDidUpdate(prevProps) {
const {
filterType,
filterValue,
onChange
} = this.props;
if (prevProps.filterType === filterType) {
return;
}
if (isInFilter(filterType) && isString(filterValue)) {
onChange({
name: NAME,
value: {
time: timeOptions[0].key,
value: null
}
});
return;
}
if (!isInFilter(filterType) && !isString(filterValue)) {
onChange({
name: NAME,
value: ''
});
}
}
//
// Listeners
onValueChange = ({ value }) => {
const {
filterValue,
onChange
} = this.props;
let newValue = value;
if (!isString(value)) {
newValue = {
time: filterValue.time,
value
};
}
onChange({
name: NAME,
value: newValue
});
}
onTimeChange = ({ value }) => {
const {
filterValue,
onChange
} = this.props;
onChange({
name: NAME,
value: {
time: value,
value: filterValue.value
}
});
}
//
// Render
render() {
const {
filterType,
filterValue
} = this.props;
if (
(isInFilter(filterType) && isString(filterValue)) ||
(!isInFilter(filterType) && !isString(filterValue))
) {
return null;
}
if (isInFilter(filterType)) {
return (
<div className={styles.container}>
<NumberInput
className={styles.numberInput}
name={NAME}
value={filterValue.value}
onChange={this.onValueChange}
/>
<SelectInput
className={styles.selectInput}
name={NAME}
value={filterValue.time}
values={timeOptions}
onChange={this.onTimeChange}
/>
</div>
);
}
return (
<TextInput
name={NAME}
value={filterValue}
placeholder="yyyy-mm-dd"
onChange={this.onValueChange}
/>
);
}
}
DateFilterBuilderRowValue.propTypes = {
filterType: PropTypes.string,
filterValue: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired,
onChange: PropTypes.func.isRequired
};
export default DateFilterBuilderRowValue;

View file

@ -3,10 +3,17 @@ import React, { Component } from 'react';
import { filterBuilderTypes, filterBuilderValueTypes, icons } from 'Helpers/Props';
import SelectInput from 'Components/Form/SelectInput';
import IconButton from 'Components/Link/IconButton';
import BoolFilterBuilderRowValue from './BoolFilterBuilderRowValue';
import DateFilterBuilderRowValue from './DateFilterBuilderRowValue';
import FilterBuilderRowValueConnector from './FilterBuilderRowValueConnector';
import IndexerFilterBuilderRowValueConnector from './IndexerFilterBuilderRowValueConnector';
import LanguageProfileFilterBuilderRowValueConnector from './LanguageProfileFilterBuilderRowValueConnector';
import MetadataProfileFilterBuilderRowValueConnector from './MetadataProfileFilterBuilderRowValueConnector';
import ProtocolFilterBuilderRowValue from './ProtocolFilterBuilderRowValue';
import QualityFilterBuilderRowValueConnector from './QualityFilterBuilderRowValueConnector';
import QualityProfileFilterBuilderRowValueConnector from './QualityProfileFilterBuilderRowValueConnector';
import ArtistStatusFilterBuilderRowValue from './ArtistStatusFilterBuilderRowValue';
import TagFilterBuilderRowValueConnector from './TagFilterBuilderRowValueConnector';
import styles from './FilterBuilderRow.css';
function getselectedFilterBuilderProp(filterBuilderProps, name) {
@ -29,6 +36,14 @@ function getDefaultFilterType(selectedFilterBuilderProp) {
return filterBuilderTypes.possibleFilterTypes[selectedFilterBuilderProp.type][0].key;
}
function getDefaultFilterValue(selectedFilterBuilderProp) {
if (selectedFilterBuilderProp.type === filterBuilderTypes.DATE) {
return '';
}
return [];
}
function getRowValueConnector(selectedFilterBuilderProp) {
if (!selectedFilterBuilderProp) {
return FilterBuilderRowValueConnector;
@ -37,15 +52,36 @@ function getRowValueConnector(selectedFilterBuilderProp) {
const valueType = selectedFilterBuilderProp.valueType;
switch (valueType) {
case filterBuilderValueTypes.BOOL:
return BoolFilterBuilderRowValue;
case filterBuilderValueTypes.DATE:
return DateFilterBuilderRowValue;
case filterBuilderValueTypes.INDEXER:
return IndexerFilterBuilderRowValueConnector;
case filterBuilderValueTypes.LANGUAGE_PROFILE:
return LanguageProfileFilterBuilderRowValueConnector;
case filterBuilderValueTypes.METADATA_PROFILE:
return MetadataProfileFilterBuilderRowValueConnector;
case filterBuilderValueTypes.PROTOCOL:
return ProtocolFilterBuilderRowValue;
case filterBuilderValueTypes.QUALITY:
return QualityFilterBuilderRowValueConnector;
case filterBuilderValueTypes.QUALITY_PROFILE:
return QualityProfileFilterBuilderRowValueConnector;
case filterBuilderValueTypes.ARTIST_STATUS:
return ArtistStatusFilterBuilderRowValue;
case filterBuilderValueTypes.TAG:
return TagFilterBuilderRowValueConnector;
default:
return FilterBuilderRowValueConnector;
}
@ -59,9 +95,15 @@ class FilterBuilderRow extends Component {
constructor(props, context) {
super(props, context);
this.state = {
selectedFilterBuilderProp: null
};
const {
filterKey,
filterBuilderProps
} = props;
if (filterKey) {
const selectedFilterBuilderProp = filterBuilderProps.find((a) => a.name === filterKey);
this.selectedFilterBuilderProp = selectedFilterBuilderProp;
}
}
componentDidMount() {
@ -74,7 +116,7 @@ class FilterBuilderRow extends Component {
if (filterKey) {
const selectedFilterBuilderProp = filterBuilderProps.find((a) => a.name === filterKey);
this.setState({ selectedFilterBuilderProp });
this.selectedFilterBuilderProp = selectedFilterBuilderProp;
return;
}
@ -83,13 +125,12 @@ class FilterBuilderRow extends Component {
const filter = {
key: selectedFilterBuilderProp.name,
value: [],
value: getDefaultFilterValue(selectedFilterBuilderProp),
type: getDefaultFilterType(selectedFilterBuilderProp)
};
this.setState({ selectedFilterBuilderProp }, () => {
onFilterChange(index, filter);
});
this.selectedFilterBuilderProp = selectedFilterBuilderProp;
onFilterChange(index, filter);
}
//
@ -107,13 +148,12 @@ class FilterBuilderRow extends Component {
const filter = {
key,
value: [],
value: getDefaultFilterValue(selectedFilterBuilderProp),
type
};
this.setState({ selectedFilterBuilderProp }, () => {
onFilterChange(index, filter);
});
this.selectedFilterBuilderProp = selectedFilterBuilderProp;
onFilterChange(index, filter);
}
onFilterChange = ({ name, value }) => {
@ -163,12 +203,11 @@ class FilterBuilderRow extends Component {
filterType,
filterValue,
filterCount,
filterBuilderProps
filterBuilderProps,
sectionItems
} = this.props;
const {
selectedFilterBuilderProp
} = this.state;
const selectedFilterBuilderProp = this.selectedFilterBuilderProp;
const keyOptions = filterBuilderProps.map((availablePropFilter) => {
return {
@ -209,8 +248,10 @@ class FilterBuilderRow extends Component {
{
filterValue != null && !!selectedFilterBuilderProp &&
<ValueComponent
filterType={filterType}
filterValue={filterValue}
selectedFilterBuilderProp={selectedFilterBuilderProp}
sectionItems={sectionItems}
onChange={this.onFilterChange}
/>
}
@ -236,10 +277,11 @@ class FilterBuilderRow extends Component {
FilterBuilderRow.propTypes = {
index: PropTypes.number.isRequired,
filterKey: PropTypes.string,
filterValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array]),
filterValue: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.array, PropTypes.object]),
filterType: PropTypes.string,
filterCount: PropTypes.number.isRequired,
filterBuilderProps: PropTypes.arrayOf(PropTypes.object).isRequired,
sectionItems: PropTypes.arrayOf(PropTypes.object).isRequired,
onFilterChange: PropTypes.func.isRequired,
onAddPress: PropTypes.func.isRequired,
onRemovePress: PropTypes.func.isRequired

View file

@ -4,7 +4,7 @@ import { kinds, filterBuilderTypes } from 'Helpers/Props';
import TagInput, { tagShape } from 'Components/Form/TagInput';
import FilterBuilderRowValueTag from './FilterBuilderRowValueTag';
const NAME = 'value';
export const NAME = 'value';
class FilterBuilderRowValue extends Component {
@ -91,7 +91,7 @@ class FilterBuilderRowValue extends Component {
}
FilterBuilderRowValue.propTypes = {
filterValue: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.string, PropTypes.number])).isRequired,
filterValue: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.bool, PropTypes.string, PropTypes.number])).isRequired,
selectedFilterBuilderProp: PropTypes.object.isRequired,
tagList: PropTypes.arrayOf(PropTypes.shape(tagShape)).isRequired,
onChange: PropTypes.func.isRequired

View file

@ -1,12 +1,13 @@
import _ from 'lodash';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import sortByName from 'Utilities/Array/sortByName';
import { filterBuilderTypes } from 'Helpers/Props';
import FilterBuilderRowValue from './FilterBuilderRowValue';
function createTagListSelector() {
return createSelector(
(state, { sectionItems }) => _.get(state, sectionItems),
(state, { sectionItems }) => sectionItems,
(state, { selectedFilterBuilderProp }) => selectedFilterBuilderProp,
(sectionItems, selectedFilterBuilderProp) => {
if (
@ -19,16 +20,20 @@ function createTagListSelector() {
let items = [];
if (selectedFilterBuilderProp.optionsSelector) {
items = sectionItems.map(selectedFilterBuilderProp.optionsSelector);
items = selectedFilterBuilderProp.optionsSelector(sectionItems);
} else {
items = sectionItems.map((item) => {
items = sectionItems.reduce((acc, item) => {
const name = item[selectedFilterBuilderProp.name];
return {
id: name,
name
};
});
if (name) {
acc.push({
id: name,
name
});
}
return acc;
}, []).sort(sortByName);
}
return _.uniqBy(items, 'id');

View file

@ -0,0 +1,28 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import FilterBuilderRowValue from './FilterBuilderRowValue';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.languageProfiles,
(languageProfiles) => {
const tagList = languageProfiles.items.map((languageProfile) => {
const {
id,
name
} = languageProfile;
return {
id,
name
};
});
return {
tagList
};
}
);
}
export default connect(createMapStateToProps)(FilterBuilderRowValue);

View file

@ -0,0 +1,28 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import FilterBuilderRowValue from './FilterBuilderRowValue';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.metadataProfiles,
(metadataProfiles) => {
const tagList = metadataProfiles.items.map((metadataProfile) => {
const {
id,
name
} = metadataProfile;
return {
id,
name
};
});
return {
tagList
};
}
);
}
export default connect(createMapStateToProps)(FilterBuilderRowValue);

View file

@ -0,0 +1,28 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import FilterBuilderRowValue from './FilterBuilderRowValue';
function createMapStateToProps() {
return createSelector(
(state) => state.settings.qualityProfiles,
(qualityProfiles) => {
const tagList = qualityProfiles.items.map((qualityProfile) => {
const {
id,
name
} = qualityProfile;
return {
id,
name
};
});
return {
tagList
};
}
);
}
export default connect(createMapStateToProps)(FilterBuilderRowValue);

View file

@ -0,0 +1,27 @@
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import createTagsSelector from 'Store/Selectors/createTagsSelector';
import FilterBuilderRowValue from './FilterBuilderRowValue';
function createMapStateToProps() {
return createSelector(
createTagsSelector(),
(tagList) => {
return {
tagList: tagList.map((tag) => {
const {
id,
label: name
} = tag;
return {
id,
name
};
})
};
}
);
}
export default connect(createMapStateToProps)(FilterBuilderRowValue);

View file

@ -9,9 +9,28 @@
}
.inputContainer {
position: relative;
flex: 1 1 auto;
}
.inputUnit {
position: absolute;
top: 0;
right: 20px;
margin-top: 7px;
width: 75px;
color: #c6c6c6;
text-align: right;
pointer-events: none;
user-select: none;
}
.inputUnitNumber {
composes: inputUnit;
right: 40px;
}
.pendingChangesContainer {
display: flex;
justify-content: flex-end;

View file

@ -83,6 +83,7 @@ function FormInputGroup(props) {
containerClassName,
inputClassName,
type,
unit,
buttons,
helpText,
helpTexts,
@ -115,6 +116,19 @@ function FormInputGroup(props) {
hasButton={hasButton}
{...otherProps}
/>
{
unit &&
<div
className={
type === inputTypes.NUMBER ?
styles.inputUnitNumber :
styles.inputUnit
}
>
{unit}
</div>
}
</div>
{
@ -219,6 +233,7 @@ FormInputGroup.propTypes = {
containerClassName: PropTypes.string.isRequired,
inputClassName: PropTypes.string,
type: PropTypes.string.isRequired,
unit: PropTypes.string,
buttons: PropTypes.oneOfType([PropTypes.node, PropTypes.arrayOf(PropTypes.node)]),
helpText: PropTypes.string,
helpTexts: PropTypes.arrayOf(PropTypes.string),

View file

@ -48,12 +48,14 @@ class NumberInput extends Component {
render() {
const {
value,
...otherProps
} = this.props;
return (
<TextInput
type="number"
value={value == null ? '' : value}
{...otherProps}
onChange={this.onChange}
onBlur={this.onBlur}

View file

@ -1,30 +1,39 @@
import PropTypes from 'prop-types';
import React from 'react';
import { kinds } from 'Helpers/Props';
import SpinnerButton from 'Components/Link/SpinnerButton';
import SpinnerErrorButton from 'Components/Link/SpinnerErrorButton';
function OAuthInput(props) {
const {
label,
authorizing,
error,
onPress
} = props;
return (
<div>
<SpinnerButton
<SpinnerErrorButton
kind={kinds.PRIMARY}
isSpinning={authorizing}
error={error}
onPress={onPress}
>
Start OAuth
</SpinnerButton>
{label}
</SpinnerErrorButton>
</div>
);
}
OAuthInput.propTypes = {
label: PropTypes.string.isRequired,
authorizing: PropTypes.bool.isRequired,
error: PropTypes.object,
onPress: PropTypes.func.isRequired
};
OAuthInput.defaultProps = {
label: 'Start OAuth'
};
export default OAuthInput;

View file

@ -26,18 +26,17 @@ class OAuthInputConnector extends Component {
componentDidUpdate(prevProps) {
const {
accessToken,
accessTokenSecret,
result,
onChange
} = this.props;
if (accessToken &&
accessToken !== prevProps.accessToken &&
accessTokenSecret &&
accessTokenSecret !== prevProps.accessTokenSecret) {
onChange({ name: 'AccessToken', value: accessToken });
onChange({ name: 'AccessTokenSecret', value: accessTokenSecret });
if (!result || result === prevProps.result) {
return;
}
Object.keys(result).forEach((key) => {
onChange({ name: key, value: result[key] });
});
}
componentWillUnmount = () => {
@ -70,8 +69,7 @@ class OAuthInputConnector extends Component {
}
OAuthInputConnector.propTypes = {
accessToken: PropTypes.string,
accessTokenSecret: PropTypes.string,
result: PropTypes.object,
provider: PropTypes.string.isRequired,
providerData: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,

View file

@ -7,19 +7,6 @@ import FormLabel from 'Components/Form/FormLabel';
import FormInputGroup from 'Components/Form/FormInputGroup';
function getType(type) {
// Textbox,
// Password,
// Checkbox,
// Select,
// Path,
// FilePath,
// Hidden,
// Tag,
// Action,
// Url,
// Captcha
// OAuth
switch (type) {
case 'captcha':
return inputTypes.CAPTCHA;
@ -27,6 +14,8 @@ function getType(type) {
return inputTypes.CHECK;
case 'password':
return inputTypes.PASSWORD;
case 'number':
return inputTypes.NUMBER;
case 'path':
return inputTypes.PATH;
case 'select':
@ -83,6 +72,7 @@ function ProviderFieldFormGroup(props) {
<FormInputGroup
type={getType(type)}
name={name}
label={label}
helpText={helpText}
helpLink={helpLink}
value={value}

View file

@ -32,7 +32,7 @@ function RootFolderSelectInputSelectedValue(props) {
}
RootFolderSelectInputSelectedValue.propTypes = {
value: PropTypes.string.isRequired,
value: PropTypes.string,
freeSpace: PropTypes.number,
includeFreeSpace: PropTypes.bool.isRequired
};

View file

@ -32,6 +32,7 @@
min-width: 20%;
max-width: 100%;
width: 0%;
height: 21px;
border: none;
}

View file

@ -58,20 +58,20 @@ class TagInput extends Component {
return name;
}
//
// Listeners
onInputContainerPress = () => {
this._autosuggestRef.input.focus();
}
onTagAdd(tag) {
addTag = _.debounce((tag) => {
this.props.onTagAdd(tag);
this.setState({
value: '',
suggestions: []
});
}, 250, { leading: true, trailing: false })
//
// Listeners
onInputContainerPress = () => {
this._autosuggestRef.input.focus();
}
onInputChange = (event, { newValue, method }) => {
@ -116,10 +116,9 @@ class TagInput extends Component {
const tag = getTag(value, selectedIndex, suggestions, allowNew);
if (tag) {
this.onTagAdd(tag);
this.addTag(tag);
event.preventDefault();
}
event.preventDefault();
}
}
@ -147,7 +146,7 @@ class TagInput extends Component {
const tag = getTag(value, selectedIndex, suggestions, allowNew);
if (tag) {
this.onTagAdd(tag);
this.addTag(tag);
}
}
@ -174,7 +173,7 @@ class TagInput extends Component {
}
onSuggestionSelected = (event, { suggestion }) => {
this.onTagAdd(suggestion);
this.addTag(suggestion);
}
//
@ -262,7 +261,7 @@ class TagInput extends Component {
}
export const tagShape = {
id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired,
id: PropTypes.oneOfType([PropTypes.bool, PropTypes.number, PropTypes.string]).isRequired,
name: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired
};

View file

@ -6,7 +6,6 @@
color: $white;
text-align: center;
white-space: nowrap;
font-weight: bold;
line-height: 1;
cursor: default;
}
@ -92,6 +91,7 @@
.large {
padding: 3px 7px;
font-weight: bold;
font-size: 14px;
}

View file

@ -48,6 +48,10 @@ class Menu extends Component {
this.setMaxHeight();
}
componentWillUnmount() {
this._removeListener();
}
//
// Control

View file

@ -54,7 +54,7 @@
.extraLarge {
composes: modal;
width: 1440px;
width: 1280px;
}
@media only screen and (max-width: $breakpointExtraLarge) {

View file

@ -123,6 +123,10 @@ const links = [
title: 'Metadata',
to: '/settings/metadata'
},
{
title: 'Tags',
to: '/settings/tags'
},
{
title: 'General',
to: '/settings/general'

View file

@ -5,6 +5,7 @@ import { Component } from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';
import { repopulatePage } from 'Utilities/pagePopulator';
import titleCase from 'Utilities/String/titleCase';
import { updateCommand, finishCommand } from 'Store/Actions/commandActions';
import { setAppValue, setVersion } from 'Store/Actions/appActions';
import { update, updateItem, removeItem } from 'Store/Actions/baseActions';
@ -34,6 +35,13 @@ function isAppDisconnected(disconnectedTime) {
return Math.floor(new Date().getTime() / 1000) - disconnectedTime > 180;
}
function getHandlerName(name) {
name = titleCase(name);
name = name.replace('/', '');
return `handle${name}`;
}
function createMapStateToProps() {
return createSelector(
(state) => state.app.isReconnecting,
@ -91,6 +99,10 @@ class SignalRConnector extends Component {
}
componentWillUnmount() {
if (this.retryTimeoutId) {
this.retryTimeoutId = clearTimeout(this.retryTimeoutId);
}
this.signalRconnection.stop();
this.signalRconnection = null;
}
@ -106,6 +118,11 @@ class SignalRConnector extends Component {
}
this.retryTimeoutId = setTimeout(() => {
if (!this.signalRconnection) {
console.error('signalR: Connection was disposed');
return;
}
this.signalRconnection.start(this.signalRconnectionOptions);
this.retryInterval = Math.min(this.retryInterval + 1, 10);
}, this.retryInterval * 1000);
@ -117,70 +134,14 @@ class SignalRConnector extends Component {
body
} = message;
if (name === 'calendar') {
this.handleCalendar(body);
const handler = this[getHandlerName(name)];
if (handler) {
handler(body);
return;
}
if (name === 'command') {
this.handleCommand(body);
return;
}
if (name === 'album') {
this.handleAlbum(body);
return;
}
if (name === 'track') {
this.handleTrack(body);
return;
}
if (name === 'trackfile') {
this.handleTrackFile(body);
return;
}
if (name === 'health') {
this.handleHealth(body);
return;
}
if (name === 'artist') {
this.handleArtist(body);
return;
}
if (name === 'queue') {
this.handleQueue(body);
return;
}
if (name === 'queue/details') {
this.handleQueueDetails(body);
return;
}
if (name === 'queue/status') {
this.handleQueueStatus(body);
return;
}
if (name === 'version') {
this.handleVersion(body);
return;
}
if (name === 'wanted/cutoff') {
this.handleWantedCutoff(body);
return;
}
if (name === 'wanted/missing') {
this.handleWantedMissing(body);
return;
}
console.error(`signalR: Unable to find handler for ${name}`);
}
handleCalendar = (body) => {
@ -237,7 +198,7 @@ class SignalRConnector extends Component {
}
}
handleHealth = (body) => {
handleHealth = () => {
this.props.fetchHealth();
}
@ -252,13 +213,13 @@ class SignalRConnector extends Component {
}
}
handleQueue = (body) => {
handleQueue = () => {
if (this.props.isQueuePopulated) {
this.props.fetchQueue();
}
}
handleQueueDetails = (body) => {
handleQueueDetails = () => {
this.props.fetchQueueDetails();
}
@ -292,12 +253,16 @@ class SignalRConnector extends Component {
}
}
handleSystemTask = () => {
// No-op for now, we may want this later
}
//
// Listeners
onStateChanged = (change) => {
const state = getState(change.newState);
console.log(`SignalR: ${state}`);
console.log(`signalR: ${state}`);
if (state === 'connected') {
// Clear disconnected time
@ -326,7 +291,7 @@ class SignalRConnector extends Component {
}
onReceived = (message) => {
console.debug('SignalR: received', message.name, message.body);
console.debug('signalR: received', message.name, message.body);
this.handleMessage(message);
}

View file

@ -6,6 +6,7 @@ function TableRow(props) {
const {
className,
children,
overlayContent,
...otherProps
} = props;
@ -21,7 +22,8 @@ function TableRow(props) {
TableRow.propTypes = {
className: PropTypes.string.isRequired,
children: PropTypes.node
children: PropTypes.node,
overlayContent: PropTypes.bool
};
TableRow.defaultProps = {

View file

@ -100,5 +100,5 @@
}
.body {
padding: 20px;
padding: 10px;
}

View file

@ -89,6 +89,7 @@ class Popover extends Component {
render() {
const {
className,
anchor,
title,
body,
@ -103,6 +104,7 @@ class Popover extends Component {
{...tetherOptions[position]}
>
<span
className={className}
// onClick={this.onClick}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}
@ -141,6 +143,7 @@ class Popover extends Component {
}
Popover.propTypes = {
className: PropTypes.string,
anchor: PropTypes.node.isRequired,
title: PropTypes.string.isRequired,
body: PropTypes.oneOfType([PropTypes.string, PropTypes.node]).isRequired,