Merge remote-tracking branch 'origin/1.1.x_branch' into 1.2.x_branch

This commit is contained in:
2020-02-20 21:24:40 -06:00
21 changed files with 266 additions and 72 deletions

View File

@@ -2,6 +2,10 @@
## 1.2.0 ## 1.2.0
* Goobox S3 support * Goobox S3 support
## 1.1.5
* \#38: Enhance new repertory release available notification
* Added `FocusTrap` to modals
## 1.1.4 ## 1.1.4
* \#39: Cleanup old releases and UI upgrades * \#39: Cleanup old releases and UI upgrades

View File

@@ -1,5 +1,5 @@
# Repertory UI # Repertory UI
![alt text](https://i.ibb.co/vmZDFK6/repertory-ui-1-1-2.png) ![alt text](https://i.ibb.co/y4bwwhV/repertory-1-1-4.png)
## GUI for [Repertory](https://bitbucket.org/blockstorage/repertory) ## GUI for [Repertory](https://bitbucket.org/blockstorage/repertory)
Repertory allows you to mount Sia and/or ScPrime blockchain storage solutions via FUSE on Linux/OS X or via WinFSP on Windows. Repertory allows you to mount Sia and/or ScPrime blockchain storage solutions via FUSE on Linux/OS X or via WinFSP on Windows.
@@ -9,10 +9,10 @@ Repertory allows you to mount Sia and/or ScPrime blockchain storage solutions vi
* ScPrime >=1.4.1.2 * ScPrime >=1.4.1.2
## Downloads ## Downloads
* **Repertory UI v1.1.2 Linux 64-bit** [<Primary\>](https://pixeldrain.com/u/5i1mA1gb) [<Alternate\>](https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.2_linux_x86_64.AppImage) * **Repertory UI v1.1.4 Linux 64-bit** [<Primary\>](https://pixeldrain.com/u/KfWzWfxp) [<Alternate\>](https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_linux_x86_64.AppImage)
* NOTE: Linux distributions require `fuse` and `libfuse` to be installed. * NOTE: Linux distributions require `fuse` and `libfuse` to be installed.
* **Repertory UI v1.1.2 OS X 64-bit** [<Primary\>](https://pixeldrain.com/u/jEWmNDRX) [<Alternate\>](https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.2_mac.dmg) * **Repertory UI v1.1.4 OS X 64-bit** [<Primary\>](https://pixeldrain.com/u/68WJfKsz) [<Alternate\>](https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_mac.dmg)
* **Repertory UI v1.1.3 Windows 64-bit** [<Primary\>](https://pixeldrain.com/u/xyfCGfcM) [<Alternate\>](https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.3_win.exe) * **Repertory UI v1.1.4 Windows 64-bit** [<Primary\>](https://pixeldrain.com/u/h5HoAqxF) [<Alternate\>](https://bitbucket.org/blockstorage/repertory-ui/downloads/repertory-ui_1.1.4_win.exe)
## Supported Platforms ## Supported Platforms
* OS X 64-bit * OS X 64-bit

View File

@@ -5,14 +5,16 @@
"author": "scott.e.graves@protonmail.com", "author": "scott.e.graves@protonmail.com",
"description": "GUI for Repertory - Repertory allows you to mount Sia, Goobox S3 and/or ScPrime blockchain storage solutions via FUSE on Linux/OS X or via WinFSP on Windows.", "description": "GUI for Repertory - Repertory allows you to mount Sia, Goobox S3 and/or ScPrime blockchain storage solutions via FUSE on Linux/OS X or via WinFSP on Windows.",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.26", "@fortawesome/fontawesome-svg-core": "^1.2.27",
"@fortawesome/free-solid-svg-icons": "^5.12.0", "@fortawesome/free-solid-svg-icons": "^5.12.1",
"@fortawesome/react-fontawesome": "^0.1.8", "@fortawesome/react-fontawesome": "^0.1.8",
"@reduxjs/toolkit": "^1.2.3", "@reduxjs/toolkit": "^1.2.4",
"auto-launch": "^5.0.5", "auto-launch": "^5.0.5",
"axios": "^0.19.2", "axios": "^0.19.2",
"devtron": "^1.4.0", "devtron": "^1.4.0",
"electron-debug": "^3.0.1", "electron-debug": "^3.0.1",
"electron-log": "^4.0.6",
"focus-trap-react": "^6.0.0",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"node-schedule": "^1.3.2", "node-schedule": "^1.3.2",
"randomstring": "^1.1.5", "randomstring": "^1.1.5",
@@ -21,10 +23,10 @@
"react-loader-spinner": "^3.1.5", "react-loader-spinner": "^3.1.5",
"react-redux": "^7.1.3", "react-redux": "^7.1.3",
"react-scripts": "3.3.1", "react-scripts": "3.3.1",
"react-tooltip": "^3.11.4", "react-tooltip": "^4.0.3",
"redux": "^4.0.5", "redux": "^4.0.5",
"redux-thunk": "^2.3.0", "redux-thunk": "^2.3.0",
"unzipper": "^0.10.7", "unzipper": "^0.10.8",
"winreg": "^1.2.4" "winreg": "^1.2.4"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -11,6 +11,7 @@ import InfoDetails from './components/InfoDetails/InfoDetails';
import IPCContainer from './containers/IPCContainer/IPCContainer'; import IPCContainer from './containers/IPCContainer/IPCContainer';
import Loading from './components/UI/Loading/Loading'; import Loading from './components/UI/Loading/Loading';
import MountItems from './containers/MountItems/MountItems'; import MountItems from './containers/MountItems/MountItems';
import NewReleases from './components/NewReleases/NewReleases';
import {notifyError} from './redux/actions/error_actions'; import {notifyError} from './redux/actions/error_actions';
import Reboot from './components/Reboot/Reboot'; import Reboot from './components/Reboot/Reboot';
import ReleaseVersionDisplay from './components/ReleaseVersionDisplay/ReleaseVersionDisplay'; import ReleaseVersionDisplay from './components/ReleaseVersionDisplay/ReleaseVersionDisplay';
@@ -111,12 +112,26 @@ class App extends IPCContainer {
!this.props.DismissDependencies && !this.props.DismissDependencies &&
this.props.AllowMount; this.props.AllowMount;
const showNewReleases = !showConfig &&
!this.props.DisplayConfirmYesNo &&
!showDependencies &&
!this.props.DownloadActive &&
!this.props.DisplayError &&
!this.props.DisplayInfo &&
!this.props.InstallActive &&
!this.props.RebootRequired &&
!this.props.DisplaySelectAppPlatform &&
!showUpgrade &&
!this.props.DismissNewReleasesAvailable &&
(this.props.NewReleasesAvailable.length > 0);
const configDisplay = createModalConditionally(showConfig, <Configuration version={selectedVersion} remoteSupported={remoteSupported} />); const configDisplay = createModalConditionally(showConfig, <Configuration version={selectedVersion} remoteSupported={remoteSupported} />);
const confirmDisplay = createModalConditionally(this.props.DisplayConfirmYesNo, <YesNo/>); const confirmDisplay = createModalConditionally(this.props.DisplayConfirmYesNo, <YesNo/>);
const dependencyDisplay = createModalConditionally(showDependencies, <DependencyList/>); const dependencyDisplay = createModalConditionally(showDependencies, <DependencyList/>);
const downloadDisplay = createModalConditionally(this.props.DownloadActive, <DownloadProgress/>); const downloadDisplay = createModalConditionally(this.props.DownloadActive, <DownloadProgress />, false, true);
const errorDisplay = createModalConditionally(this.props.DisplayError, <ErrorDetails/>, true); const errorDisplay = createModalConditionally(this.props.DisplayError, <ErrorDetails/>, true);
const infoDisplay = createModalConditionally(this.props.DisplayInfo, <InfoDetails/>, true); const infoDisplay = createModalConditionally(this.props.DisplayInfo, <InfoDetails/>, true);
const newReleasesDisplay = createModalConditionally(showNewReleases, <NewReleases/>);
const rebootDisplay = createModalConditionally(this.props.RebootRequired, <Reboot />); const rebootDisplay = createModalConditionally(this.props.RebootRequired, <Reboot />);
const selectAppPlatformDisplay = createModalConditionally(this.props.DisplaySelectAppPlatform, <SelectAppPlatform/>); const selectAppPlatformDisplay = createModalConditionally(this.props.DisplaySelectAppPlatform, <SelectAppPlatform/>);
const upgradeDisplay = createModalConditionally(showUpgrade, <UpgradeUI/>); const upgradeDisplay = createModalConditionally(showUpgrade, <UpgradeUI/>);
@@ -175,6 +190,7 @@ class App extends IPCContainer {
{mainContent} {mainContent}
</div> </div>
</div> </div>
{newReleasesDisplay}
{selectAppPlatformDisplay} {selectAppPlatformDisplay}
{dependencyDisplay} {dependencyDisplay}
{upgradeDisplay} {upgradeDisplay}
@@ -201,12 +217,14 @@ const mapStateToProps = state => {
DisplayError: state.error.DisplayError, DisplayError: state.error.DisplayError,
DisplayInfo: state.error.DisplayInfo, DisplayInfo: state.error.DisplayInfo,
DisplaySelectAppPlatform: state.common.DisplaySelectAppPlatform, DisplaySelectAppPlatform: state.common.DisplaySelectAppPlatform,
DismissNewReleasesAvailable: state.relver.DismissNewReleasesAvailable,
DownloadActive: state.download.DownloadActive, DownloadActive: state.download.DownloadActive,
InstallActive: state.install.InstallActive, InstallActive: state.install.InstallActive,
InstalledVersion: state.relver.InstalledVersion, InstalledVersion: state.relver.InstalledVersion,
LocationsLookup: state.relver.LocationsLookup, LocationsLookup: state.relver.LocationsLookup,
MissingDependencies: state.install.MissingDependencies, MissingDependencies: state.install.MissingDependencies,
MountsBusy: state.mounts.MountsBusy, MountsBusy: state.mounts.MountsBusy,
NewReleasesAvailable: state.relver.NewReleasesAvailable,
Platform: state.common.Platform, Platform: state.common.Platform,
ProviderState: state.mounts.ProviderState, ProviderState: state.mounts.ProviderState,
RebootRequired: state.common.RebootRequired, RebootRequired: state.common.RebootRequired,

View File

@@ -22,9 +22,9 @@ export default connect(mapStateToProps)(props => {
</td> </td>
<td> <td>
{props.AllowDownload ? {props.AllowDownload ?
<a href={void(0)} <a href={'#'}
className={'DependencyLink'} className={'DependencyLink'}
onClick={()=>{props.onDownload(); return false;}}><u>Install</u></a> : onClick={()=>{props.onDownload(); return false;}}><u>Install</u></a> :
'Installing...'} 'Installing...'}
</td> </td>
</tr> </tr>

View File

@@ -0,0 +1,28 @@
import React from 'react';
import * as Constants from '../../../constants';
import Button from '../../UI/Button/Button';
export default ({release, lastItem}) => {
return (
<div>
<h2>{'[' + Constants.RELEASE_TYPES[release.Release] + '] ' + release.Display }</h2>
<table cellSpacing={0} cellPadding={0} width="97%">
<tbody>
<tr style={{height: '4px'}}/>
<tr>
<td width="50%">
<Button buttonStyles={{width: '100%'}}>Changes</Button>
</td>
<td>
<div style={{width: 'var(--default_spacing)'}}/>
</td>
<td width="50%">
<Button buttonStyles={{width: '100%'}}>Install</Button>
</td>
</tr>
{lastItem ? null : <tr style={{height: 'var(--default_spacing)'}}/>}
</tbody>
</table>
</div>
);
};

View File

@@ -0,0 +1,11 @@
.NewReleasesHeading {
text-align: center;
margin-bottom: 4px;
}
.NewReleasesContent {
max-height: 60vh;
min-width: 50vw;
overflow-y: auto;
margin-bottom: var(--default_spacing);
}

View File

@@ -0,0 +1,37 @@
import React from 'react';
import {connect} from 'react-redux';
import Box from '../UI/Box/Box';
import Button from '../UI/Button/Button';
import NewRelease from './NewRelease/NewRelease';
import './NewReleases.css';
import {setDismissNewReleasesAvailable} from '../../redux/actions/release_version_actions';
const mapStateToProps = state => {
return {
NewReleasesAvailable: state.relver.NewReleasesAvailable,
};
};
const mapDispatchToProps = dispatch => {
return {
dismissNewReleasesAvailable: () => dispatch(setDismissNewReleasesAvailable(true)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(props => {
const newReleases = props.NewReleasesAvailable.map((i, idx) => {
return <NewRelease key={'new_release_' + i.Release + '_' + i.Version}
lastItem={idx === (props.NewReleasesAvailable.length - 1)}
release={i} />;
});
return (
<Box dxDark dxStyle={{padding: 'var(--default_spacing)'}}>
<h1 className={'NewReleasesHeading'}>New Repertory Versions Available</h1>
<div className={'NewReleasesContent'}>
{newReleases}
</div>
<Button clicked={props.dismissNewReleasesAvailable}>Dismiss</Button>
</Box>
);
});

View File

@@ -4,6 +4,7 @@ import './Button.css';
export default props => { export default props => {
return ( return (
<button disabled={props.disabled} <button disabled={props.disabled}
autoFocus={props.autoFocus}
className={'Button'} className={'Button'}
style={props.buttonStyles} style={props.buttonStyles}
onClick={props.clicked}>{props.children}</button> onClick={props.clicked}>{props.children}</button>

View File

@@ -6,6 +6,7 @@ export default props => {
<div className={'CheckBoxOwner'}> <div className={'CheckBoxOwner'}>
<label className='CheckBoxLabel'>{props.label} <label className='CheckBoxLabel'>{props.label}
<input checked={JSON.parse(props.checked)} <input checked={JSON.parse(props.checked)}
autoFocus={props.autoFocus}
disabled={props.disabled} disabled={props.disabled}
onChange={props.changed} onChange={props.changed}
type='checkbox'/> type='checkbox'/>

View File

@@ -11,6 +11,7 @@ export default props => {
return ( return (
<div className={'DropDown'}> <div className={'DropDown'}>
<select className={'DropDownSelect' + (props.auto ? ' Auto ' : '') + (props.alt ? ' Alt ' : '') } <select className={'DropDownSelect' + (props.auto ? ' Auto ' : '') + (props.alt ? ' Alt ' : '') }
autoFocus={props.autoFocus}
disabled={props.disabled} disabled={props.disabled}
onChange={props.changed} onChange={props.changed}
value={props.selected}> value={props.selected}>

View File

@@ -1,5 +1,7 @@
import React from 'react'; import React from 'react';
import './Modal.css' import './Modal.css'
import FocusTrap from 'focus-trap-react';
export default props => { export default props => {
let modalStyles = []; let modalStyles = [];
@@ -12,11 +14,14 @@ export default props => {
} }
return ( return (
<div <FocusTrap active={!props.disableFocusTrap}>
className={modalStyles.join(' ')} <div
onClick={props.clicked}> className={modalStyles.join(' ')}
<div className={contentStyles.join(' ')}> onClick={props.clicked}>
{props.children} <div className={contentStyles.join(' ')}>
{props.children}
</div>
</div> </div>
</div>); </FocusTrap>
);
}; };

View File

@@ -65,7 +65,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(class extends Compon
<h1 style={{color: 'var(--text_color_error)', textAlign: 'center', paddingBottom: 'var(--default_spacing)'}}>Add Remote Mount</h1> <h1 style={{color: 'var(--text_color_error)', textAlign: 'center', paddingBottom: 'var(--default_spacing)'}}>Add Remote Mount</h1>
<Text text={'Hostname or IP'} <Text text={'Hostname or IP'}
textAlign={'left'} textAlign={'left'}
type={'Heading1'}/> type={'Heading2'}/>
<input onChange={e => this.setState({HostNameOrIp: e.target.value.trim()})} <input onChange={e => this.setState({HostNameOrIp: e.target.value.trim()})}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
type={'text'} type={'text'}
@@ -73,7 +73,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(class extends Compon
<div style={{paddingTop: 'var(--default_spacing)'}}/> <div style={{paddingTop: 'var(--default_spacing)'}}/>
<Text text={'Port'} <Text text={'Port'}
textAlign={'left'} textAlign={'left'}
type={'Heading1'}/> type={'Heading2'}/>
<input max={65535} <input max={65535}
min={1025} min={1025}
onChange={e => this.setState({Port: e.target.value})} onChange={e => this.setState({Port: e.target.value})}
@@ -83,7 +83,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(class extends Compon
<div style={{paddingTop: 'var(--default_spacing)'}}/> <div style={{paddingTop: 'var(--default_spacing)'}}/>
<Text text={'Remote Token'} <Text text={'Remote Token'}
textAlign={'left'} textAlign={'left'}
type={'Heading1'}/> type={'Heading2'}/>
<input onChange={e => this.setState({Token: e.target.value})} <input onChange={e => this.setState({Token: e.target.value})}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
type={'text'} type={'text'}

View File

@@ -163,6 +163,8 @@ class Configuration extends IPCContainer {
ObjectLookup: objectLookup, ObjectLookup: objectLookup,
OriginalItemList: itemListCopy, OriginalItemList: itemListCopy,
OriginalObjectLookup: objectLookupCopy, OriginalObjectLookup: objectLookupCopy,
}, () => {
}); });
} else { } else {
this.props.notifyError(arg.data.Error); this.props.notifyError(arg.data.Error);
@@ -262,32 +264,22 @@ class Configuration extends IPCContainer {
); );
} }
const configurationItems = this.state.ItemList let autoFocus = true;
.map((k, i) => {
return (
((!this.state.IsRemoteMount || !k.hide_remote) && (!k.advanced || (this.state.ShowAdvanced && k.advanced))) ?
<ConfigurationItem advanced={k.advanced}
changed={e=>this.handleItemChanged(e, i)}
grouping={'Settings'}
items={this.state.Template[k.label].items}
key={i}
label={k.label}
template={this.state.Template[k.label]}
value={k.value}/> :
null)
});
let objectItems = []; let objectItems = [];
for (const key of Object.keys(this.state.ObjectLookup)) { for (const key of Object.keys(this.state.ObjectLookup)) {
objectItems.push(( objectItems.push((
<div key={key}> <div key={key}>
<h1>{key}</h1> <h2>{key}</h2>
<div> <div>
{ {
this.state.ObjectLookup[key].map((k, i) => { this.state.ObjectLookup[key].map((k, i) => {
const shouldFocus = autoFocus;
autoFocus = false;
return ( return (
(!k.advanced || (this.state.ShowAdvanced && k.advanced && !k.remote) || this.showRemoteConfigItem(k, this.state.ObjectLookup[key])) ? (!k.advanced || (this.state.ShowAdvanced && k.advanced && !k.remote) || this.showRemoteConfigItem(k, this.state.ObjectLookup[key])) ?
<ConfigurationItem advanced={k.advanced} <ConfigurationItem advanced={k.advanced}
autoFocus={shouldFocus}
changed={e=>this.handleObjectItemChanged(e, key, i)} changed={e=>this.handleObjectItemChanged(e, key, i)}
grouping={key} grouping={key}
items={this.state.Template[key].template[k.label].items} items={this.state.Template[key].template[k.label].items}
@@ -304,13 +296,33 @@ class Configuration extends IPCContainer {
)); ));
} }
const configurationItems = this.state.ItemList
.map((k, i) => {
const shouldFocus = autoFocus;
autoFocus = false;
return (
((!this.state.IsRemoteMount || !k.hide_remote) && (!k.advanced || (this.state.ShowAdvanced && k.advanced))) ?
<ConfigurationItem advanced={k.advanced}
autoFocus={shouldFocus}
changed={e=>this.handleItemChanged(e, i)}
grouping={'Settings'}
items={this.state.Template[k.label].items}
key={i}
label={k.label}
template={this.state.Template[k.label]}
value={k.value}/> :
null
);
});
return ( return (
<div className={'Configuration'}> <div className={'Configuration'}>
{confirmSave} {confirmSave}
<Box dxDark dxStyle={{padding: 'var(--default_spacing)'}}> <Box dxDark dxStyle={{padding: 'var(--default_spacing)'}}>
<div style={{float: 'right', margin: 0, padding: 0, marginTop: '-4px', boxSizing: 'border-box', display: 'block'}}> <div style={{float: 'right', margin: 0, padding: 0, marginTop: '-4px', boxSizing: 'border-box', display: 'block'}}>
<b style={{cursor: 'pointer'}} <a href={'#'}
onClick={this.checkSaveRequired}>X</b> onClick={this.checkSaveRequired}
style={{cursor: 'pointer'}}>X</a>
</div> </div>
<h1 style={{width: '100%', textAlign: 'center'}}>{( <h1 style={{width: '100%', textAlign: 'center'}}>{(
this.props.DisplayRemoteConfiguration ? this.props.DisplayRemoteConfiguration ?
@@ -318,7 +330,7 @@ class Configuration extends IPCContainer {
this.props.DisplayConfiguration) + ' Configuration'}</h1> this.props.DisplayConfiguration) + ' Configuration'}</h1>
<div style={{overflowY: 'auto', height: '90%'}}> <div style={{overflowY: 'auto', height: '90%'}}>
{objectItems} {objectItems}
{(configurationItems.length > 0) ? <h1>Settings</h1> : null} {(configurationItems.length > 0) ? <h2>Settings</h2> : null}
{configurationItems} {configurationItems}
</div> </div>
</Box> </Box>

View File

@@ -18,7 +18,7 @@ export default connect(null, mapDispatchToProps)(props => {
const handleChanged = (e) => { const handleChanged = (e) => {
const target = e.target; const target = e.target;
if (target.type === 'checkbox') { if (target.type === 'checkbox') {
target.value = e.target.checked ? "true" : "false"; target.value = e.target.checked ? 'true' : 'false';
} }
props.changed(target); props.changed(target);
}; };
@@ -30,49 +30,54 @@ export default connect(null, mapDispatchToProps)(props => {
props.notifyInfo(props.label, description); props.notifyInfo(props.label, description);
}; };
infoDisplay = <a href={void(0)} infoDisplay = <a href={'#'}
className={'ConfigurationInfo'} className={'ConfigurationInfo'}
onClick={()=>{displayInfo(); return false;}}><FontAwesomeIcon icon={faInfoCircle}/></a>; onClick={()=>{displayInfo(); return false;}}><FontAwesomeIcon icon={faInfoCircle}/></a>;
} }
let data; let data;
switch (props.template.type) { switch (props.template.type) {
case "bool": case 'bool':
data = <CheckBox changed={handleChanged} data = <CheckBox changed={handleChanged}
checked={props.value} checked={props.value}
disabled={props.readOnly}/>; disabled={props.readOnly}
autoFocus={props.autoFocus}/>;
break; break;
case "double": case 'double':
data = <input min={0.0} data = <input min={0.0}
autoFocus={props.autoFocus}
disabled={props.readOnly} disabled={props.readOnly}
onChange={e=>handleChanged(e)} onChange={e=>handleChanged(e)}
step={"0.01"} step={'0.01'}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
type={'number'} type={'number'}
value={parseFloat(props.value).toFixed(2)}/>; value={parseFloat(props.value).toFixed(2)}/>;
break; break;
case "list": case 'list':
data = <DropDown alt data = <DropDown alt
auto auto
autoFocus={props.autoFocus}
changed={handleChanged} changed={handleChanged}
disabled={props.readOnly} disabled={props.readOnly}
items={props.items} items={props.items}
selected={props.value} />; selected={props.value} />;
break; break;
case "string": case 'string':
data = <input onChange={e=>handleChanged(e)} data = <input onChange={e=>handleChanged(e)}
autoFocus={props.autoFocus}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
disabled={props.readOnly} disabled={props.readOnly}
type={'text'} type={'text'}
value={props.value}/>; value={props.value}/>;
break; break;
case "uint8": case 'uint8':
data = <input max={255} data = <input max={255}
min={0} min={0}
autoFocus={props.autoFocus}
disabled={props.readOnly} disabled={props.readOnly}
onChange={e=>handleChanged(e)} onChange={e=>handleChanged(e)}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
@@ -80,9 +85,10 @@ export default connect(null, mapDispatchToProps)(props => {
value={props.value}/>; value={props.value}/>;
break; break;
case "uint16": case 'uint16':
data = <input max={65535} data = <input max={65535}
min={0} min={0}
autoFocus={props.autoFocus}
disabled={props.readOnly} disabled={props.readOnly}
onChange={e=>handleChanged(e)} onChange={e=>handleChanged(e)}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
@@ -90,9 +96,10 @@ export default connect(null, mapDispatchToProps)(props => {
value={props.value}/>; value={props.value}/>;
break; break;
case "uint32": case 'uint32':
data = <input max={4294967295} data = <input max={4294967295}
min={0} min={0}
autoFocus={props.autoFocus}
disabled={props.readOnly} disabled={props.readOnly}
onChange={e=>handleChanged(e)} onChange={e=>handleChanged(e)}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}
@@ -100,9 +107,10 @@ export default connect(null, mapDispatchToProps)(props => {
value={props.value}/>; value={props.value}/>;
break; break;
case "uint64": case 'uint64':
data = <input max={18446744073709551615} data = <input max={18446744073709551615}
min={0} min={0}
autoFocus={props.autoFocus}
disabled={props.readOnly} disabled={props.readOnly}
onChange={e=>handleChanged(e)} onChange={e=>handleChanged(e)}
className={'ConfigurationItemInput'} className={'ConfigurationItemInput'}

View File

@@ -152,8 +152,8 @@ export default connect(mapStateToProps, mapDispatchToProps)(props => {
} }
}; };
removeControl = ( removeControl = (
<a col={dimensions=>dimensions.columns - 6} <a href={'#'}
href={void(0)} col={dimensions=>dimensions.columns - 6}
onClick={handleRemoveMount} onClick={handleRemoveMount}
row={secondRow + 3} row={secondRow + 3}
style={removeStyle}> style={removeStyle}>
@@ -169,7 +169,7 @@ export default connect(mapStateToProps, mapDispatchToProps)(props => {
col={configButton ? 6 : 0} col={configButton ? 6 : 0}
rowSpan={5} rowSpan={5}
text={props.remote ? props.provider.substr(6) : props.provider} text={props.remote ? props.provider.substr(6) : props.provider}
type={'Heading1'}/> type={'Heading2'}/>
{inputControls} {inputControls}
{actionsDisplay} {actionsDisplay}
{autoMountControl} {autoMountControl}

View File

@@ -8,11 +8,11 @@
--control_transparent_background: rgba(10, 10, 16, 0.5); --control_transparent_background: rgba(10, 10, 16, 0.5);
--control_dark_transparent_background: rgba(10, 10, 16, 0.7); --control_dark_transparent_background: rgba(10, 10, 16, 0.7);
--text_color: rgba(200, 200, 240, 0.7); --text_color: rgba(200, 200, 240, 0.65);
--text_color_hover: rgba(200, 200, 225, 0.7); --text_color_hover: rgba(200, 200, 225, 0.65);
--text_color_error: rgba(203, 120, 120, 0.7); --text_color_error: rgba(203, 120, 120, 0.8);
--heading_text_color: rgba(132, 160, 230, 0.7); --heading_text_color: rgba(132, 160, 230, 0.8);
--heading_other_text_color: var(--heading_text_color); --heading_other_text_color: rgba(132, 160, 230, 0.65);
--text_color_transition: color 0.3s; --text_color_transition: color 0.3s;
--default_font_size: 14px; --default_font_size: 14px;
@@ -30,6 +30,9 @@
a { a {
outline: 0; outline: 0;
color: var(--text_color);
text-decoration: none;
font-weight: bold;
} }
html, body { html, body {

View File

@@ -13,7 +13,10 @@ import {
setDismissDependencies setDismissDependencies
} from './install_actions'; } from './install_actions';
import {unmountAll} from './mount_actions'; import {unmountAll} from './mount_actions';
import {getIPCRenderer} from '../../utils'; import {
getIPCRenderer,
getNewReleases
} from '../../utils';
export const CLEAR_UI_UPGRADE = 'relver/clearUIUpgrade'; export const CLEAR_UI_UPGRADE = 'relver/clearUIUpgrade';
export const clearUIUpgrade = () => { export const clearUIUpgrade = () => {
@@ -123,11 +126,23 @@ export const loadReleases = () => {
...response.data.Locations[appPlatform], ...response.data.Locations[appPlatform],
}; };
const storedReleases = localStorage.getItem('releases');
let newReleases = [];
if (storedReleases && (storedReleases.length > 0)) {
newReleases = getNewReleases(JSON.parse(storedReleases).VersionLookup, versionLookup);
}
localStorage.setItem('releases', JSON.stringify({ localStorage.setItem('releases', JSON.stringify({
LocationsLookup: locationsLookup, LocationsLookup: locationsLookup,
VersionLookup: versionLookup VersionLookup: versionLookup
})); }));
dispatchActions(locationsLookup, versionLookup); dispatchActions(locationsLookup, versionLookup);
dispatch(setNewReleasesAvailable(newReleases));
if (getState().relver.NewReleasesAvailable.length > 0) {
localStorage.setItem('previous_releases', storedReleases);
dispatch(showWindow());
}
}).catch(error => { }).catch(error => {
const releases = localStorage.getItem('releases'); const releases = localStorage.getItem('releases');
if (releases && (releases.length > 0)) { if (releases && (releases.length > 0)) {
@@ -174,8 +189,10 @@ export const setActiveRelease = (release, version) => {
}; };
export const setAllowDismissDependencies = createAction('relver/setAllowDismissDependencies'); export const setAllowDismissDependencies = createAction('relver/setAllowDismissDependencies');
export const setDismissNewReleasesAvailable = createAction('relver/setDismissNewReleasesAvailable');
export const setDismissUIUpgrade = createAction('relver/setDismissUIUpgrade'); export const setDismissUIUpgrade = createAction('relver/setDismissUIUpgrade');
export const setInstalledVersion = createAction('relver/setInstalledVersion'); export const setInstalledVersion = createAction('relver/setInstalledVersion');
export const setNewReleasesAvailable = createAction('relver/setNewReleasesAvailable');
export const SET_RELEASE_DATA = 'relver/setReleaseData'; export const SET_RELEASE_DATA = 'relver/setReleaseData';
export const setReleaseData = (locationsLookup, versionLookup)=> { export const setReleaseData = (locationsLookup, versionLookup)=> {

View File

@@ -15,8 +15,10 @@ const versionLookup = Constants.RELEASE_TYPES.map(k=> {
export const releaseVersionReducer = createReducer({ export const releaseVersionReducer = createReducer({
AllowDismissDependencies: false, AllowDismissDependencies: false,
DismissNewReleasesAvailable: true,
InstalledVersion: 'none', InstalledVersion: 'none',
LocationsLookup: {}, LocationsLookup: {},
NewReleasesAvailable: [],
Release: 0, Release: 0,
ReleaseDefault: 0, ReleaseDefault: 0,
ReleaseUpgradeAvailable: false, ReleaseUpgradeAvailable: false,
@@ -49,6 +51,12 @@ export const releaseVersionReducer = createReducer({
AllowDismissDependencies: action.payload, AllowDismissDependencies: action.payload,
}; };
}, },
[Actions.setDismissNewReleasesAvailable]: (state, action) => {
return {
...state,
DismissNewReleasesAvailable: action.payload,
};
},
[Actions.setDismissUIUpgrade]: (state, action) => { [Actions.setDismissUIUpgrade]: (state, action) => {
return { return {
...state, ...state,
@@ -61,6 +69,13 @@ export const releaseVersionReducer = createReducer({
InstalledVersion: action.payload, InstalledVersion: action.payload,
} }
}, },
[Actions.setNewReleasesAvailable]: (state, action) => {
return {
...state,
DismissNewReleasesAvailable: false,
NewReleasesAvailable: action.payload,
};
},
[Actions.SET_RELEASE_DATA]: (state, action) => { [Actions.SET_RELEASE_DATA]: (state, action) => {
return { return {
...state, ...state,

View File

@@ -6,8 +6,8 @@ const ipcRenderer = (!process.versions.hasOwnProperty('electron') && window && w
window.require('electron').ipcRenderer : window.require('electron').ipcRenderer :
null; null;
export const createModalConditionally = (condition, jsx, critical) => { export const createModalConditionally = (condition, jsx, critical, disableFocusTrap) => {
const modalProps = {critical: critical}; const modalProps = {critical: critical, disableFocusTrap: disableFocusTrap};
return condition ? (<Modal {...modalProps}>{jsx}</Modal>) : null; return condition ? (<Modal {...modalProps}>{jsx}</Modal>) : null;
}; };
@@ -20,6 +20,37 @@ export const getIPCRenderer = () => {
return ipcRenderer; return ipcRenderer;
}; };
export const getNewReleases = (existingReleases, newReleases) => {
const ret = [];
/*existingReleases = Constants.RELEASE_TYPES.reduce((map, release) => {
map[release] = [];
return map;
}, {});*/
if (existingReleases && newReleases) {
Constants.RELEASE_TYPES.forEach(release => {
newReleases[release]
.filter(version => !existingReleases[release].includes(version) && (version !== 'unavailable'))
.forEach(version => {
ret.splice(0, 0, {
Display: version,
Release: Constants.RELEASE_TYPES.indexOf(release),
Version: newReleases[release].indexOf(version),
});
});
});
}
ret.push({
Display: '1.1.1',
Release: 1,
Version: 2,
});
return ret;
};
export const getSelectedVersionFromState = state => { export const getSelectedVersionFromState = state => {
return (state.relver.Version === -1) ? return (state.relver.Version === -1) ?
'unavailable' : 'unavailable' :