Update UI

Squash into v3 UI
This commit is contained in:
Mark McDowall 2018-11-25 14:40:25 -08:00 committed by Taloth Saldono
parent dbdb8e4436
commit aafa9cb6be
6 changed files with 122 additions and 76 deletions

View File

@ -99,6 +99,7 @@ class GeneralSettings extends Component {
isMono, isMono,
isWindows, isWindows,
mode, mode,
packageUpdateMechanism,
onInputChange, onInputChange,
onConfirmResetApiKey, onConfirmResetApiKey,
...otherProps ...otherProps
@ -161,6 +162,7 @@ class GeneralSettings extends Component {
advancedSettings={advancedSettings} advancedSettings={advancedSettings}
settings={settings} settings={settings}
isMono={isMono} isMono={isMono}
packageUpdateMechanism={packageUpdateMechanism}
onInputChange={onInputChange} onInputChange={onInputChange}
/> />
@ -202,6 +204,7 @@ GeneralSettings.propTypes = {
isMono: PropTypes.bool.isRequired, isMono: PropTypes.bool.isRequired,
isWindows: PropTypes.bool.isRequired, isWindows: PropTypes.bool.isRequired,
mode: PropTypes.string.isRequired, mode: PropTypes.string.isRequired,
packageUpdateMechanism: PropTypes.string.isRequired,
onInputChange: PropTypes.func.isRequired, onInputChange: PropTypes.func.isRequired,
onConfirmResetApiKey: PropTypes.func.isRequired, onConfirmResetApiKey: PropTypes.func.isRequired,
onConfirmRestart: PropTypes.func.isRequired onConfirmRestart: PropTypes.func.isRequired

View File

@ -27,6 +27,7 @@ function createMapStateToProps() {
isMono: systemStatus.isMono, isMono: systemStatus.isMono,
isWindows: systemStatus.isWindows, isWindows: systemStatus.isWindows,
mode: systemStatus.mode, mode: systemStatus.mode,
packageUpdateMechanism: systemStatus.packageUpdateMechanism,
...sectionSettings ...sectionSettings
}; };
} }

View File

@ -1,5 +1,6 @@
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React from 'react'; import React from 'react';
import titleCase from 'Utilities/String/titleCase';
import { inputTypes, sizes } from 'Helpers/Props'; import { inputTypes, sizes } from 'Helpers/Props';
import FieldSet from 'Components/FieldSet'; import FieldSet from 'Components/FieldSet';
import FormGroup from 'Components/Form/FormGroup'; import FormGroup from 'Components/Form/FormGroup';
@ -11,6 +12,7 @@ function UpdateSettings(props) {
advancedSettings, advancedSettings,
settings, settings,
isMono, isMono,
packageUpdateMechanism,
onInputChange onInputChange
} = props; } = props;
@ -30,6 +32,13 @@ function UpdateSettings(props) {
{ key: 'script', value: 'Script' } { key: 'script', value: 'Script' }
]; ];
if (packageUpdateMechanism !== 'builtIn') {
updateOptions.push({
key: packageUpdateMechanism,
value: titleCase(packageUpdateMechanism)
});
}
return ( return (
<FieldSet legend="Updates"> <FieldSet legend="Updates">
<FormGroup <FormGroup
@ -50,58 +59,58 @@ function UpdateSettings(props) {
{ {
isMono && isMono &&
<div> <div>
<FormGroup <FormGroup
advancedSettings={advancedSettings} advancedSettings={advancedSettings}
isAdvanced={true} isAdvanced={true}
size={sizes.MEDIUM} size={sizes.MEDIUM}
> >
<FormLabel>Automatic</FormLabel> <FormLabel>Automatic</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.CHECK} type={inputTypes.CHECK}
name="updateAutomatically" name="updateAutomatically"
helpText="Automatically download and install updates. You will still be able to install from System: Updates" helpText="Automatically download and install updates. You will still be able to install from System: Updates"
onChange={onInputChange} onChange={onInputChange}
{...updateAutomatically} {...updateAutomatically}
/> />
</FormGroup> </FormGroup>
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Mechanism</FormLabel>
<FormInputGroup
type={inputTypes.SELECT}
name="updateMechanism"
values={updateOptions}
helpText="Use Sonarr's built-in updater or a script"
helpLink="https://github.com/Sonarr/Sonarr/wiki/Updating"
onChange={onInputChange}
{...updateMechanism}
/>
</FormGroup>
{
updateMechanism.value === 'script' &&
<FormGroup <FormGroup
advancedSettings={advancedSettings} advancedSettings={advancedSettings}
isAdvanced={true} isAdvanced={true}
> >
<FormLabel>Script Path</FormLabel> <FormLabel>Mechanism</FormLabel>
<FormInputGroup <FormInputGroup
type={inputTypes.TEXT} type={inputTypes.SELECT}
name="updateScriptPath" name="updateMechanism"
helpText="Path to a custom script that takes an extracted update package and handle the remainder of the update process" values={updateOptions}
helpText="Use Sonarr's built-in updater or a script"
helpLink="https://github.com/Sonarr/Sonarr/wiki/Updating"
onChange={onInputChange} onChange={onInputChange}
{...updateScriptPath} {...updateMechanism}
/> />
</FormGroup> </FormGroup>
}
</div> {
updateMechanism.value === 'script' &&
<FormGroup
advancedSettings={advancedSettings}
isAdvanced={true}
>
<FormLabel>Script Path</FormLabel>
<FormInputGroup
type={inputTypes.TEXT}
name="updateScriptPath"
helpText="Path to a custom script that takes an extracted update package and handle the remainder of the update process"
onChange={onInputChange}
{...updateScriptPath}
/>
</FormGroup>
}
</div>
} }
</FieldSet> </FieldSet>
); );
@ -111,6 +120,7 @@ UpdateSettings.propTypes = {
advancedSettings: PropTypes.bool.isRequired, advancedSettings: PropTypes.bool.isRequired,
settings: PropTypes.object.isRequired, settings: PropTypes.object.isRequired,
isMono: PropTypes.bool.isRequired, isMono: PropTypes.bool.isRequired,
packageUpdateMechanism: PropTypes.string.isRequired,
onInputChange: PropTypes.func.isRequired onInputChange: PropTypes.func.isRequired
}; };

View File

@ -1,8 +1,4 @@
.updateAvailable { .messageContainer {
display: flex;
}
.upToDate {
display: flex; display: flex;
margin-bottom: 20px; margin-bottom: 20px;
} }
@ -12,7 +8,7 @@
font-size: 30px; font-size: 30px;
} }
.upToDateMessage { .message {
padding-left: 5px; padding-left: 5px;
font-size: 18px; font-size: 18px;
line-height: 30px; line-height: 30px;

View File

@ -1,6 +1,6 @@
import _ from 'lodash'; import _ from 'lodash';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component, Fragment } from 'react';
import { icons, kinds } from 'Helpers/Props'; import { icons, kinds } from 'Helpers/Props';
import formatDate from 'Utilities/Date/formatDate'; import formatDate from 'Utilities/Date/formatDate';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
@ -21,15 +21,18 @@ class Updates extends Component {
const { const {
isFetching, isFetching,
isPopulated, isPopulated,
error, updatesError,
generalSettingsError,
items, items,
isInstallingUpdate, isInstallingUpdate,
updateMechanism,
shortDateFormat, shortDateFormat,
onInstallLatestPress onInstallLatestPress
} = this.props; } = this.props;
const hasUpdates = isPopulated && !error && items.length > 0; const hasError = !!(updatesError || generalSettingsError);
const noUpdates = isPopulated && !error && !items.length; const hasUpdates = isPopulated && !hasError && items.length > 0;
const noUpdates = isPopulated && !hasError && !items.length;
const hasUpdateToInstall = hasUpdates && _.some(items, { installable: true, latest: true }); const hasUpdateToInstall = hasUpdates && _.some(items, { installable: true, latest: true });
const noUpdateToInstall = hasUpdates && !hasUpdateToInstall; const noUpdateToInstall = hasUpdates && !hasUpdateToInstall;
@ -37,7 +40,7 @@ class Updates extends Component {
<PageContent title="Updates"> <PageContent title="Updates">
<PageContentBodyConnector> <PageContentBodyConnector>
{ {
!isPopulated && !error && !isPopulated && !hasError &&
<LoadingIndicator /> <LoadingIndicator />
} }
@ -48,15 +51,30 @@ class Updates extends Component {
{ {
hasUpdateToInstall && hasUpdateToInstall &&
<div className={styles.updateAvailable}> <div className={styles.messageContainer}>
<SpinnerButton {
className={styles.updateAvailable} updateMechanism === 'builtIn' || updateMechanism === 'script' ?
kind={kinds.PRIMARY} <SpinnerButton
isSpinning={isInstallingUpdate} className={styles.updateAvailable}
onPress={onInstallLatestPress} kind={kinds.PRIMARY}
> isSpinning={isInstallingUpdate}
Install Latest onPress={onInstallLatestPress}
</SpinnerButton> >
Install Latest
</SpinnerButton> :
<Fragment>
<Icon
name={icons.WARNING}
kind={kinds.WARNING}
size={30}
/>
<div className={styles.message}>
Unable to update Sonarr. Sonarr is configured to use an external update mechanism
</div>
</Fragment>
}
{ {
isFetching && isFetching &&
@ -70,13 +88,14 @@ class Updates extends Component {
{ {
noUpdateToInstall && noUpdateToInstall &&
<div className={styles.upToDate}> <div className={styles.messageContainer}>
<Icon <Icon
className={styles.upToDateIcon} className={styles.upToDateIcon}
name={icons.CHECK_CIRCLE} name={icons.CHECK_CIRCLE}
size={30} size={30}
/> />
<div className={styles.upToDateMessage}>
<div className={styles.message}>
The latest version of Sonarr is already installed The latest version of Sonarr is already installed
</div> </div>
@ -144,11 +163,18 @@ class Updates extends Component {
} }
{ {
!!error && !!updatesError &&
<div> <div>
Failed to fetch updates Failed to fetch updates
</div> </div>
} }
{
!!generalSettingsError &&
<div>
Failed to update settings
</div>
}
</PageContentBodyConnector> </PageContentBodyConnector>
</PageContent> </PageContent>
); );
@ -159,9 +185,11 @@ class Updates extends Component {
Updates.propTypes = { Updates.propTypes = {
isFetching: PropTypes.bool.isRequired, isFetching: PropTypes.bool.isRequired,
isPopulated: PropTypes.bool.isRequired, isPopulated: PropTypes.bool.isRequired,
error: PropTypes.object, updatesError: PropTypes.object,
generalSettingsError: PropTypes.object,
items: PropTypes.array.isRequired, items: PropTypes.array.isRequired,
isInstallingUpdate: PropTypes.bool.isRequired, isInstallingUpdate: PropTypes.bool.isRequired,
updateMechanism: PropTypes.string.isRequired,
shortDateFormat: PropTypes.string.isRequired, shortDateFormat: PropTypes.string.isRequired,
onInstallLatestPress: PropTypes.func.isRequired onInstallLatestPress: PropTypes.func.isRequired
}; };

View File

@ -2,6 +2,7 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { fetchGeneralSettings } from 'Store/Actions/settingsActions';
import { fetchUpdates } from 'Store/Actions/systemActions'; import { fetchUpdates } from 'Store/Actions/systemActions';
import { executeCommand } from 'Store/Actions/commandActions'; import { executeCommand } from 'Store/Actions/commandActions';
import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector'; import createCommandExecutingSelector from 'Store/Selectors/createCommandExecutingSelector';
@ -12,22 +13,26 @@ import Updates from './Updates';
function createMapStateToProps() { function createMapStateToProps() {
return createSelector( return createSelector(
(state) => state.system.updates, (state) => state.system.updates,
(state) => state.settings.general,
createUISettingsSelector(), createUISettingsSelector(),
createCommandExecutingSelector(commandNames.APPLICATION_UPDATE), createCommandExecutingSelector(commandNames.APPLICATION_UPDATE),
(updates, uiSettings, isInstallingUpdate) => { (updates, generalSettings, uiSettings, isInstallingUpdate) => {
const { const {
isFetching, error: updatesError,
isPopulated,
error,
items items
} = updates; } = updates;
const isFetching = updates.isFetching || generalSettings.isFetching;
const isPopulated = updates.isPopulated && generalSettings.isPopulated;
return { return {
isFetching, isFetching,
isPopulated, isPopulated,
error, updatesError,
generalSettingsError: generalSettings.error,
items, items,
isInstallingUpdate, isInstallingUpdate,
updateMechanism: generalSettings.item.updateMechanism,
shortDateFormat: uiSettings.shortDateFormat shortDateFormat: uiSettings.shortDateFormat
}; };
} }
@ -35,8 +40,9 @@ function createMapStateToProps() {
} }
const mapDispatchToProps = { const mapDispatchToProps = {
fetchUpdates, dispatchFetchUpdates: fetchUpdates,
executeCommand dispatchFetchGeneralSettings: fetchGeneralSettings,
dispatchExecuteCommand: executeCommand
}; };
class UpdatesConnector extends Component { class UpdatesConnector extends Component {
@ -45,14 +51,15 @@ class UpdatesConnector extends Component {
// Lifecycle // Lifecycle
componentDidMount() { componentDidMount() {
this.props.fetchUpdates(); this.props.dispatchFetchUpdates();
this.props.dispatchFetchGeneralSettings();
} }
// //
// Listeners // Listeners
onInstallLatestPress = () => { onInstallLatestPress = () => {
this.props.executeCommand({ name: commandNames.APPLICATION_UPDATE }); this.props.dispatchExecuteCommand({ name: commandNames.APPLICATION_UPDATE });
} }
// //
@ -69,8 +76,9 @@ class UpdatesConnector extends Component {
} }
UpdatesConnector.propTypes = { UpdatesConnector.propTypes = {
fetchUpdates: PropTypes.func.isRequired, dispatchFetchUpdates: PropTypes.func.isRequired,
executeCommand: PropTypes.func.isRequired dispatchFetchGeneralSettings: PropTypes.func.isRequired,
dispatchExecuteCommand: PropTypes.func.isRequired
}; };
export default connect(createMapStateToProps, mapDispatchToProps)(UpdatesConnector); export default connect(createMapStateToProps, mapDispatchToProps)(UpdatesConnector);