Initial commit

This commit is contained in:
Scott E. Graves
2018-09-25 12:30:15 -05:00
commit a778b6dd25
52 changed files with 3450 additions and 0 deletions

11
src/App.css Normal file
View File

@@ -0,0 +1,11 @@
.App {
display: flex;
flex-direction: column;
margin: 0;
padding: 10px;
box-sizing: border-box;
height: 100vh;
width: 100vw;
background-image: url('./assets/images/background.jpg');
background-size: cover;
}

564
src/App.js Normal file
View File

@@ -0,0 +1,564 @@
import React, {Component} from 'react';
import CSSModules from 'react-css-modules';
import styles from './App.css';
import Box from './components/UI/Box/Box';
import DropDown from './components/UI/DropDown/DropDown';
import * as Constants from './constants';
import axios from 'axios';
import MountItems from './containers/MountItems/MountItems';
import DependencyList from './components/DependencyList/DependencyList';
import Button from './components/UI/Button/Button';
import Modal from './components/UI/Modal/Modal';
import DownloadProgress from './components/DownloadProgress/DownloadProgress';
import UpgradeUI from './components/UpgradeUI/UpgradeUI';
import UpgradeIcon from './components/UpgradeIcon/UpgradeIcon';
const Scheduler = require('node-schedule');
let ipcRenderer = null;
if (!process.versions.hasOwnProperty('electron')) {
ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null);
}
class App extends Component {
constructor(props) {
super(props);
if (ipcRenderer) {
ipcRenderer.on('get_platform_reply', (event, arg) => {
this.setState({
Platform: arg.data,
});
ipcRenderer.send('get_state', Constants.DATA_LOCATIONS[arg.data]);
});
ipcRenderer.on('get_state_reply', (event, arg) => {
if (arg.data) {
if (arg.data.Hyperspace.AutoMount === undefined) {
arg.data.Hyperspace['AutoMount'] = false;
}
if (arg.data.Sia.AutoMount === undefined) {
arg.data.Sia['AutoMount'] = false;
}
this.setState({
Hyperspace: arg.data.Hyperspace,
Release: arg.data.Release,
Sia: arg.data.Sia,
Version: arg.data.Version,
});
}
this.grabReleases();
});
ipcRenderer.on('grab_releases_reply', ()=> {
axios.get(Constants.RELEASES_URL)
.then(response => {
const versionLookup = {
Alpha: response.data.Versions.Alpha[this.state.Platform],
Beta: response.data.Versions.Beta[this.state.Platform],
RC: response.data.Versions.RC[this.state.Platform],
Release: response.data.Versions.Release[this.state.Platform],
};
const locationsLookup = {
...response.data.Locations[this.state.Platform],
};
this.setState({
AllowOptions: true,
LocationsLookup: locationsLookup,
VersionLookup: versionLookup,
});
this.checkVersionInstalled(this.state.Release, this.state.Version, versionLookup);
}).catch(error => {
console.log(error);
});
});
ipcRenderer.on('grab_ui_releases_reply', ()=> {
axios.get(Constants.UI_RELEASES_URL)
.then(response => {
const data = response.data;
if (data.Versions &&
data.Versions[this.state.Platform] &&
(data.Versions[this.state.Platform].length > 0) &&
(data.Versions[this.state.Platform][0] !== this.props.version)) {
this.setState({
UpgradeAvailable: true,
UpgradeDismissed: false,
UpgradeData: data.Locations[this.state.Platform][data.Versions[this.state.Platform][0]],
});
}
}).catch(error => {
console.log(error);
});
});
ipcRenderer.on('download_file_progress', (event, arg) => {
this.setState({
DownloadProgress: arg.data.Progress,
});
});
ipcRenderer.on('download_file_complete', (event, arg) => {
if (this.state.DownloadingRelease) {
if (arg.data.Success) {
const selectedVersion = this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]][this.state.Version];
ipcRenderer.send('extract_release', {
Directory: Constants.DATA_LOCATIONS[this.state.Platform],
Source: arg.data.Destination,
Version: selectedVersion,
});
}
this.setState({
DownloadActive: false,
DownloadProgress: 0.0,
DownloadingRelease: false,
ExtractActive: arg.data.Success,
DownloadName: '',
});
} else if (this.state.DownloadingDependency) {
if (arg.data.Success) {
ipcRenderer.send('install_dependency', {
Source: arg.data.Destination,
});
}
this.setState({
DownloadActive: false,
DownloadProgress: 0.0,
DownloadingDependency: arg.data.Success,
DownloadName: '',
});
} else if (this.state.DownloadingUpgrade) {
if (arg.data.Success) {
ipcRenderer.send('install_upgrade', {
Source: arg.data.Destination,
});
} else {
this.setState({
DownloadActive: false,
DownloadProgress: 0.0,
DownloadingUpgrade: false,
DownloadName: '',
});
}
} else {
this.setState({
DownloadActive: false,
DownloadProgress: 0.0,
DownloadName: '',
});
}
});
ipcRenderer.on('extract_release_complete', (event, arg) => {
ipcRenderer.send('delete_file', {
FilePath: arg.data.Source,
});
this.setState({
ExtractActive: false,
});
this.checkVersionInstalled(this.state.Release, this.state.Version);
});
ipcRenderer.on('check_installed_reply', (event, arg) => {
this.setState({
AllowDownload: true,
DownloadingDependency: false,
MissingDependencies: arg.data.Dependencies,
RepertoryVersion: arg.data.Success && arg.data.Exists ? arg.data.Version : 'none',
});
});
ipcRenderer.on('install_dependency_reply', (event, arg) => {
ipcRenderer.send('delete_file', {
FilePath: arg.data.Source,
});
this.checkVersionInstalled(this.state.Release, this.state.Version);
});
ipcRenderer.on('install_upgrade_reply', (event, arg) => {
ipcRenderer.sendSync('delete_file', {
FilePath: arg.data.Source,
});
this.setState({
DownloadActive: false,
DownloadProgress: 0.0,
DownloadName: '',
});
});
ipcRenderer.send('get_platform');
Scheduler.scheduleJob('23 11 * * *', this.updateCheckScheduledJob);
}
}
state = {
AllowOptions: false,
AllowDownload: false,
AutoMountChecked: false,
DownloadActive: false,
DownloadProgress: 0.0,
DownloadingDependency: false,
DownloadName: '',
DownloadingRelease: false,
DownloadingUpgrade: false,
ExtractActive: false,
Hyperspace: {
AutoMount: false,
MountLocation: '',
},
LocationsLookup: {},
MissingDependencies: [],
Platform: 'unknown',
Release: 3,
ReleaseTypes: [
'Release',
'RC',
'Beta',
'Alpha',
],
RepertoryVersion: 'none',
Sia: {
AutoMount: false,
MountLocation: '',
},
UpgradeAvailable: false,
UpgradeData: {},
UpgradeDismissed: false,
Version: 0,
VersionLookup: {
Alpha: [
'unavailable'
],
Beta: [
'unavailable'
],
RC: [
'unavailable'
],
Release: [
'unavailable'
],
}
};
checkVersionInstalled = (release, version, versionLookup) => {
if (!versionLookup) {
versionLookup = this.state.VersionLookup;
}
const selectedVersion = versionLookup[this.state.ReleaseTypes[release]][version];
this.setState({
AllowDownload: false,
});
if (ipcRenderer) {
let dependencies = [];
if (this.state.LocationsLookup[selectedVersion] && this.state.LocationsLookup[selectedVersion].dependencies) {
dependencies = this.state.LocationsLookup[selectedVersion].dependencies;
}
ipcRenderer.send('check_installed', {
Dependencies: dependencies,
Directory: Constants.DATA_LOCATIONS[this.state.Platform],
Version: selectedVersion,
});
}
};
grabReleases = () => {
if (this.state.Platform !== 'unknown') {
if (ipcRenderer) {
ipcRenderer.send('grab_releases');
ipcRenderer.send('grab_ui_releases');
}
}
};
handleAutoMountChanged = (storageType, e) => {
let sia = {
...this.state.Sia
};
let hyperspace = {
...this.state.Hyperspace
};
if (storageType === 'Hyperspace') {
hyperspace.AutoMount = e.target.checked;
this.setState({
Hyperspace: hyperspace,
});
} else if (storageType === 'Sia') {
sia.AutoMount = e.target.checked;
this.setState({
Sia: sia,
});
}
this.saveState(this.state.Release, this.state.Version, sia, hyperspace);
};
handleDependencyDownload = (url) => {
if (ipcRenderer) {
const items = url.split('/');
const fileName = items[items.length - 1];
this.setState({
DownloadActive: true,
DownloadingDependency: true,
DownloadName: fileName,
});
ipcRenderer.send('download_file', {
Directory: Constants.DATA_LOCATIONS[this.state.Platform],
Filename: fileName,
URL: url,
});
}
};
handleMountLocationChanged = (storageType, location) => {
const state = {
...this.state[storageType],
MountLocation: location,
};
this.setState({
[storageType]: state,
});
const hyperspace = (storageType === 'Hyperspace') ? state : {
...this.state.Hyperspace,
};
const sia = storageType === 'Sia' ? state : {
...this.state.Sia,
};
this.saveState(this.state.Release, this.state.Version, sia, hyperspace);
};
handleReleaseChanged = (e) => {
const val = parseInt(e.target.value, 10);
this.setState({
Release: val,
Version: 0
});
this.saveState(val, 0, this.state.Sia, this.state.Hyperspace);
this.checkVersionInstalled(val, 0);
};
handleReleaseDownload = () => {
const selectedVersion = this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]][this.state.Version];
const fileName = selectedVersion + '.zip';
if (ipcRenderer) {
this.setState({
DownloadActive: true,
DownloadingRelease: true,
DownloadName: fileName,
});
ipcRenderer.send('download_file', {
Directory: Constants.DATA_LOCATIONS[this.state.Platform],
Filename: fileName,
URL: this.state.LocationsLookup[selectedVersion].urls[0],
});
}
};
handleUIDownload = () => {
if (ipcRenderer) {
this.setState({
DownloadActive: true,
DownloadingUpgrade: true,
DownloadName: 'UI Upgrade',
});
ipcRenderer.send('download_file', {
Directory: Constants.DATA_LOCATIONS[this.state.Platform],
Filename: this.state.Platform === 'win32' ? 'upgrade.exe' : 'upgrade',
URL: this.state.UpgradeData.urls[0],
});
} else {
this.setState({UpgradeDismissed: true});
}
};
handleVersionChanged = (e) => {
const val = parseInt(e.target.value, 10);
this.setState({
Version: val
});
this.saveState(this.state.Release, val, this.state.Sia, this.state.Hyperspace);
this.checkVersionInstalled(this.state.Release, val);
};
notifyAutoMountProcessed = () => {
this.setState({AutoMountChecked: true});
};
saveState = (release, version, sia, hyperspace)=> {
if (ipcRenderer) {
ipcRenderer.send('save_state', {
Directory: Constants.DATA_LOCATIONS[this.state.Platform],
State: {
Hyperspace: hyperspace,
Release: release,
Sia: sia,
Version: version,
}
});
}
};
updateCheckScheduledJob = () => {
if (this.state.Platform !== 'unknown') {
if (ipcRenderer) {
ipcRenderer.send('grab_ui_releases');
}
}
};
render() {
const selectedVersion = this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]][this.state.Version];
const downloadEnabled = this.state.AllowDownload &&
!this.state.DownloadActive &&
(((selectedVersion !== 'unavailable') && (selectedVersion !== this.state.RepertoryVersion)));
const allowMount = this.state.RepertoryVersion !== 'none';
const missingDependencies = (this.state.MissingDependencies.length > 0);
let mountDisplay = null;
if (allowMount) {
mountDisplay = <MountItems platform={this.state.Platform}
sia={this.state.Sia}
hyperspace={this.state.Hyperspace}
changed={this.handleMountLocationChanged}
processAutoMount={!this.state.AutoMountChecked}
autoMountProcessed={this.notifyAutoMountProcessed}
autoMountChanged={this.handleAutoMountChanged}
version={this.state.RepertoryVersion}
directory={Constants.DATA_LOCATIONS[this.state.Platform]}
disabled={!allowMount}/>;
}
let dependencyDisplay = null;
if (missingDependencies && !this.state.DownloadActive) {
dependencyDisplay = (
<Modal>
<DependencyList allowDownload={!this.state.DownloadingDependency}
dependencies={this.state.MissingDependencies}
onDownload={this.handleDependencyDownload}/>
}
</Modal>
)
}
let downloadDisplay = null;
if (this.state.DownloadActive) {
downloadDisplay = (
<Modal>
<DownloadProgress progress={this.state.DownloadProgress}
display={this.state.DownloadName}/>
</Modal>);
}
let releaseDisplay = null;
if (this.state.ExtractActive) {
releaseDisplay = <h3 style={{textAlign: 'center'}}>{'Activating <' + selectedVersion + '>'}</h3>
} else {
releaseDisplay = <Button disabled={!downloadEnabled}
clicked={this.handleReleaseDownload}>Install</Button>;
}
let upgradeDisplay = null;
if (!missingDependencies &&
!this.state.DownloadActive &&
this.state.UpgradeAvailable &&
!this.state.UpgradeDismissed) {
upgradeDisplay = (
<Modal>
<UpgradeUI upgrade={this.handleUIDownload}
cancel={()=>this.setState({UpgradeDismissed: true})}/>
</Modal>
);
}
let options = null;
if (this.state.AllowOptions) {
options = (
<table width='100%' cellPadding='2'>
<tbody>
<tr>
<td width='33%'>
<h2>Release</h2>
</td>
<td width='33%'>
<h2>Version</h2>
</td>
<td width='33%'>
<h2>Installed</h2>
</td>
</tr>
<tr>
<td>
<DropDown disabled={this.state.DownloadActive || this.state.ExtractActive}
items={this.state.ReleaseTypes}
selected={this.state.Release}
changed={this.handleReleaseChanged}/>
</td>
<td>
<DropDown disabled={this.state.DownloadActive || this.state.ExtractActive}
items={this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]]}
selected={this.state.Version}
changed={this.handleVersionChanged}/>
</td>
<td>
{this.state.RepertoryVersion}
</td>
</tr>
<tr>
<td colSpan={3}>
{releaseDisplay}
</td>
</tr>
<tr>
<td colSpan={3}>
{mountDisplay}
</td>
</tr>
</tbody>
</table>);
}
return (
<div styleName='App'>
{dependencyDisplay}
{upgradeDisplay}
{downloadDisplay}
<Box dxDark dxStyle={{'height': 'auto', 'padding': '2px'}}>
<table cellPadding={0} cellSpacing={0} style={{margin: 0, padding: 0}}>
<tbody style={{margin: 0, padding: 0}}>
<tr style={{margin: 0, padding: 0}}>
<td width='33%' style={{margin: 0, padding: 0}}/>
<td width='33%' style={{margin: 0, padding: 0}}>
<h1 style={{'textAlign': 'center'}}>{'Repertory UI v' + this.props.version}</h1>
</td>
<td width='33%' style={{margin: 0, padding: 0}} align='right' valign='middle'>
<UpgradeIcon
available={this.state.UpgradeAvailable}
clicked={()=>this.setState({UpgradeDismissed: false})}/>
</td>
</tr>
</tbody>
</table>
</Box>
<Box dxStyle={{'padding': '4px', 'marginTop': '10px'}}>
{options}
</Box>
</div>
);
}
}
export default CSSModules(App, styles, {allowMultiple: true});

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

View File

@@ -0,0 +1,9 @@
.Dependency {
margin: 0;
padding: 0;
width: 100%;
}
.Link {
cursor: pointer;
}

View File

@@ -0,0 +1,25 @@
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './Dependency.css';
export default CSSModules((props) => {
return (
<div styleName='Dependency'>
<table width="100%">
<tbody>
<tr>
<td width="35%">
<h3>{props.name}</h3>
</td>
<td>
{props.allowDownload ?
<a styleName='Link' href={void(0)} onClick={()=>{props.onDownload(props.download); return false;}}><u>Install</u></a> :
'Installing...'}
</td>
</tr>
</tbody>
</table>
</div>
);
}, styles, {allowMultiple: true});

View File

@@ -0,0 +1,3 @@
.DependencyList {
}

View File

@@ -0,0 +1,27 @@
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './DependencyList.css';
import Dependency from './Dependency/Dependency';
import Box from '../UI/Box/Box';
export default CSSModules((props) => {
const items = props.dependencies.map((k, i)=> {
return (
<Dependency allowDownload={props.allowDownload}
key={i}
name={k.display}
download={k.download}
onDownload={props.onDownload}/>
);
});
return (
<Box dxDark dxStyle={{width: '300px', height: 'auto', padding: '5px'}}>
<div style={{width: '100%', height: 'auto', paddingBottom: '5px', boxSizing: 'border-box'}}>
<h1 style={{width: '100%', textAlign: 'center'}}>Missing Dependencies</h1>
</div>
{items}
</Box>
);
}, styles, {allowMultiple: true});

View File

@@ -0,0 +1,17 @@
import Box from '../UI/Box/Box';
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './DownloadProgress.css';
export default CSSModules((props) => {
return (
<Box dxDark dxStyle={{width: '380px', height: 'auto', padding: '5px'}}>
<div style={{width: '100%', height: 'auto'}}>
<h1 style={{width: '100%', textAlign: 'center'}}>{'Downloading ' + props.display}</h1>
</div>
<progress max={100.0} id={'download_progress'}
style={{width: '100%'}}
value={props.progress}/>
</Box>);
}, styles, {allowMultiple: true});

View File

@@ -0,0 +1,4 @@
.MountItem {
width: 100%;
}

View File

@@ -0,0 +1,52 @@
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './MountItem.css';
import DropDown from '../UI/DropDown/DropDown';
import Button from '../UI/Button/Button';
import Loader from 'react-loader-spinner';
export default CSSModules((props) => {
let inputControl = null;
let mountWidth = '70%';
if (props.platform === 'win32') {
inputControl = <DropDown disabled={!props.allowMount || props.mounted}
items={props.items}
selected={props.items.indexOf(props.location)}
changed={props.changed}/>;
mountWidth = '18%';
} else {
inputControl = <input disabled={!props.allowMount || props.mounted}
type={'text'}
value={props.location}
onChange={props.changed}/>;
}
let actionDisplay = null;
if (props.allowMount) {
actionDisplay = <Button buttonStyles={{width: '100%'}}
clicked={()=>props.clicked(props.title, !props.mounted, props.location, props.pid)}>{props.mounted ? 'Unmount' : 'Mount'}</Button>;
} else {
actionDisplay = <Loader color={'var(--heading_text_color)'}
height='24px'
width='24px'
type='Circles'/>;
}
return (
<div styleName='MountItem'>
<h2>{props.title}</h2>
<table width='100%' cellPadding='2'>
<tbody>
<tr>
<td width={mountWidth} height='30px'>{inputControl}</td>
<td width='25%' align='center' valign='middle'>
{actionDisplay}
</td>
<td>
<input type='checkbox' checked={props.autoMount} onChange={props.autoMountChanged}/>Auto-mount
</td>
</tr>
</tbody>
</table>
</div>
);
}, styles, {allowMultiple: true});

View File

@@ -0,0 +1,41 @@
.Box {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
background-color: var(--control_transparent_background);
box-sizing: border-box;
border: var(--control_border);
border-radius: var(--border_radius);
box-shadow: var(--control_box_shadow);
}
.Box.Darker {
background-color: var(--control_dark_transparent_background);
}
.Box.SlideOut {
animation: slide-out 0.5s forwards;
transform: translateX(-100%);
}
.Box.SlideOutTop {
animation: slide-out-top 0.5s forwards;
transform: translateY(-100%);
}
.Box.FadeIn {
transition: 0.3s;
}
@keyframes slide-out {
100% {
transform: translateX(0%);
}
}
@keyframes slide-out-top {
100% {
transform: translateY(0%);
}
}

View File

@@ -0,0 +1,28 @@
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './Box.css';
export default CSSModules((props) => {
const styleList = ['Box'];
if (props.dxDark) {
styleList.push('Darker');
}
if (props.dxSlideOut) {
styleList.push('SlideOut');
} else if (props.dxFadeIn) {
styleList.push('FadeIn');
} else if (props.dxSlideOutTop) {
styleList.push('SlideOutTop');
}
return (
<div
onClick={props.clicked}
styleName={styleList.join(' ')}
style={{...props.dxStyle}}>
{props.children}
</div>
);
}, styles, {allowMultiple: true});

View File

@@ -0,0 +1,30 @@
.Button {
display: block;
text-align: center;
margin: 0;
padding: 4px;
outline: 0;
color: var(--text_color);
border-radius: var(--border_radius);
background-color: var(--control_background);
border: none;
text-decoration: none;
text-outline: none;
width: 70px;
}
.Button:hover:enabled {
background: var(--control_background_hover);
color: var(--text_color_hover);
cursor: pointer;
}
.Button:hover:disabled {
cursor: default;
}
.Button:active,
.Button.active {
box-shadow: none;
cursor: pointer;
}

View File

@@ -0,0 +1,13 @@
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './Button.css';
export default CSSModules((props) => {
return (
<button disabled={props.disabled}
styleName={'Button'}
style={props.buttonStyles}
onClick={props.clicked}>{props.children}</button>
);
}, styles, {allowMultiple: true});

View File

@@ -0,0 +1,24 @@
.DropDown {
width: 100%;
margin: 0;
padding: 0;
}
.Select {
width: 100%;
height: 100%;
display: block;
margin: 0;
padding: 2px;
border-radius: var(--border_radius);
background: rgba(10, 10, 20, 0.3);
border-color: rgba(10, 10, 20, 0.9);
color: var(--text_color);
box-sizing: border-box;
}
.Option {
background: rgba(10, 10, 15, 0.8);
border-color: rgba(10, 10, 20, 0.9);
color: var(--text_color);
}

View File

@@ -0,0 +1,20 @@
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './DropDown.css';
export default CSSModules((props) => {
const options = props.items.map((s, i) => {
return (
<option styleName='Option' key={i} value={i}>{s}</option>
);
});
return (
<div styleName='DropDown'>
<select styleName='Select' disabled={props.disabled} onChange={props.changed} value={props.selected}>
{options}
</select>
</div>
);
}, styles, {allowMultiple: true});

View File

@@ -0,0 +1,19 @@
.Modal {
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.5);
z-index: 2000;
}
.Content {
position: fixed;
width: auto;
height: auto;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 2001;
}

View File

@@ -0,0 +1,14 @@
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './Modal.css'
export default CSSModules((props) => {
return (
<div
styleName='Modal'
onClick={props.clicked}>
<div styleName='Content'>
{props.children}
</div>
</div>);
}, styles, {allowMultiple: true});

View File

@@ -0,0 +1,14 @@
.UpgradeIcon {
display: block;
margin: 0;
padding: 0;
width: 20px;
height: 20px;
border: 0;
box-sizing: border-box;
cursor: pointer;
}
div.UpgradeIcon {
cursor: default;
}

View File

@@ -0,0 +1,10 @@
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './UpgradeIcon.css';
import availableImage from '../../assets/images/upgrade_available.png';
export default CSSModules((props) => {
return props.available ?
<img alt='' styleName='UpgradeIcon' src={availableImage} onClick={props.clicked}/> :
<div styleName='UpgradeIcon'/> ;
}, styles, {allowMultiple: true});

View File

View File

@@ -0,0 +1,28 @@
import Button from '../UI/Button/Button';
import Box from '../UI/Box/Box';
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './UpgradeUI.css';
export default CSSModules((props) => {
return (
<Box dxDark dxStyle={{width: '180px', height: 'auto', padding: '5px'}}>
<div style={{width: '100%', height: 'auto'}}>
<h1 style={{width: '100%', textAlign: 'center'}}>UI Upgrade Available</h1>
</div>
<table cellSpacing={5} width="100%">
<tbody>
<tr>
<td width="50%">
<Button buttonStyles={{width: '100%'}}
clicked={props.upgrade}>Install</Button>
</td>
<td width="50%">
<Button buttonStyles={{width: '100%'}}
clicked={props.cancel}>Cancel</Button>
</td>
</tr>
</tbody>
</table>
</Box>);
}, styles, {allowMultiple: true});

7
src/constants.js Normal file
View File

@@ -0,0 +1,7 @@
export const RELEASES_URL = 'https://bitbucket.org/blockstorage/repertory/raw/master/releases.json';
export const DATA_LOCATIONS = {
linux: '~/.local/repertory/ui',
darwin: '~/Library/Application Support/repertory/ui',
win32: '%LOCALAPPDATA%\\repertory\\ui',
};
export const UI_RELEASES_URL = 'https://bitbucket.org/blockstorage/repertory-ui/raw/master/releases.json';

View File

@@ -0,0 +1,4 @@
.MountItems {
margin-top: 10px;
width: 100%;
}

View File

@@ -0,0 +1,184 @@
import React from 'react';
import {Component} from 'react';
import CSSModules from 'react-css-modules';
import styles from './MountItems.css';
import MountItem from '../../components/MountItem/MountItem';
let ipcRenderer = null;
if (!process.versions.hasOwnProperty('electron')) {
ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null);
}
class MountItems extends Component {
constructor(props) {
super(props);
if (ipcRenderer) {
ipcRenderer.on('detect_mounts_reply', (event, arg) => {
if (arg.data.Success) {
const sia = {
...this.state.Sia,
AllowMount: true,
DriveLetters: (arg.data.DriveLetters.Sia),
Mounted: (arg.data.Locations.Sia.length > 0),
PID: arg.data.PIDS.Sia,
};
const hs = {
...this.state.Hyperspace,
AllowMount: true,
DriveLetters: (arg.data.DriveLetters.Hyperspace),
Mounted: (arg.data.Locations.Hyperspace.length > 0),
PID: arg.data.PIDS.Hyperspace,
};
this.setState({
Hyperspace: hs,
Sia: sia,
});
let hsLocation = arg.data.Locations.Hyperspace;
if ((hsLocation.length === 0) && (this.props.platform === 'win32')) {
hsLocation = this.props.hyperspace.MountLocation || arg.data.DriveLetters.Hyperspace[0];
}
if (hsLocation !== this.props.hyperspace.MountLocation) {
this.props.changed('Hyperspace', hsLocation);
}
let siaLocation = arg.data.Locations.Sia;
if ((siaLocation.length === 0) && (this.props.platform === 'win32')) {
siaLocation = this.props.sia.MountLocation || arg.data.DriveLetters.Sia[0];
}
if (siaLocation !== this.props.sia.MountLocation) {
this.props.changed('Sia', siaLocation);
}
this.performAutoMount();
}
});
ipcRenderer.on('mount_drive_reply', (event, arg) => {
const state = {
...this.state[arg.data.StorageType],
PID: arg.data.PID,
Mounted: arg.data.Success,
};
this.setState({
[arg.data.StorageType]: state,
});
this.detectMounts();
});
ipcRenderer.on('unmount_drive_reply', (event, arg) => {
this.detectMounts();
});
this.detectMounts();
}
}
state = {
Hyperspace: {
AllowMount: false,
DriveLetters: [],
Mounted: false,
PID: -1,
},
Sia: {
AllowMount: false,
DriveLetters: [],
Mounted: false,
PID: -1,
},
};
detectMounts = ()=> {
ipcRenderer.send('detect_mounts', {
Directory: this.props.directory,
Version: this.props.version,
});
};
handleMountLocationChanged = (systemType, value) => {
if (this.props.platform === 'win32') {
this.props.changed(systemType, this.state[systemType].DriveLetters[value]);
}
else {
this.props.changed(systemType, value);
}
};
handleMountUnMount = (storageType, mount, location, pid) => {
if (ipcRenderer) {
const state = {
...this.state[storageType],
AllowMount: false,
};
this.setState({
[storageType]: state,
});
if (mount) {
ipcRenderer.send('mount_drive', {
Directory: this.props.directory,
Location: location,
StorageType: storageType,
Version: this.props.version,
});
} else {
ipcRenderer.send('unmount_drive', {
Directory: this.props.directory,
Location: location,
PID: pid,
StorageType: storageType,
Version: this.props.version,
});
}
}
};
performAutoMount = ()=> {
if (this.props.processAutoMount) {
this.props.autoMountProcessed();
if (this.props.hyperspace.AutoMount &&
!this.state.Hyperspace.Mounted &&
(this.props.hyperspace.MountLocation.length > 0)) {
this.handleMountUnMount('Hyperspace', true, this.props.hyperspace.MountLocation);
}
if (this.props.sia.AutoMount &&
!this.state.Sia.Mounted &&
(this.props.sia.MountLocation.length > 0)) {
this.handleMountUnMount('Sia', true, this.props.sia.MountLocation);
}
}
};
render() {
return (
<div styleName='MountItems'>
<MountItem allowMount={this.state.Hyperspace.AllowMount}
autoMount={this.props.hyperspace.AutoMount}
autoMountChanged={(e)=>this.props.autoMountChanged('Hyperspace', e)}
mounted={this.state.Hyperspace.Mounted}
items={this.state.Hyperspace.DriveLetters}
platform={this.props.platform}
title={'Hyperspace'}
location={this.props.hyperspace.MountLocation}
changed={(e) => this.handleMountLocationChanged('Hyperspace', e.target.value)}
clicked={this.handleMountUnMount}
pid={this.state.Hyperspace.PID}/>
<MountItem allowMount={this.state.Sia.AllowMount}
autoMount={this.props.sia.AutoMount}
autoMountChanged={(e)=>this.props.autoMountChanged('Sia', e)}
mounted={this.state.Sia.Mounted}
items={this.state.Sia.DriveLetters}
platform={this.props.platform}
title={'Sia'}
location={this.props.sia.MountLocation}
changed={(e) => this.handleMountLocationChanged('Sia', e.target.value)}
clicked={this.handleMountUnMount}
pid={this.state.Sia.PID}/>
</div>);
}
}
export default CSSModules(MountItems, styles, {allowMultiple: true});

63
src/index.css Normal file
View File

@@ -0,0 +1,63 @@
:root {
--border_radius: 4px;
--control_background: rgba(150, 150, 190, .15);
--control_background_hover: rgba(150, 150, 190, .3);
--control_border: 1px solid rgba(70, 70, 70, 0.9);
--control_box_shadow: 1px 1px 1px black;
--control_transparent_background: rgba(60, 60, 70, 0.4);
--control_dark_transparent_background: rgba(60, 60, 70, 0.4);
--text_color: rgba(200, 205, 220, 0.7);
--text_color_hover: rgba(200, 205, 220, 0.7);
--heading_text_color: rgba(194, 217, 255, 0.6);
--heading_other_text_color: rgba(200, 205, 220, 0.7);
--text_color_transition: color 0.3s;
}
* {
font-family: 'Nunito', sans-serif;
font-size: 15px;
}
*::-moz-focus-inner {
border: 0;
}
a {
outline: 0;
}
html, body {
height: 100%;
width: 100%;
margin: 0;
padding: 0;
color: var(--text_color);
}
p {
padding: 0;
margin: 0;
color: var(--text_color);
font-size: medium;
font-weight: bold;
text-align: center;
}
h1, h2, h3 {
padding: 0;
margin: 0;
font-weight: bold;
text-decoration: none;
white-space: pre;
text-align: left;
}
h1 {
color: var(--heading_text_color);
}
h2, h3 {
color: var(--heading_other_text_color);
}

10
src/index.js Normal file
View File

@@ -0,0 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';
import packageJson from '../package.json';
ReactDOM.render(<App version={packageJson.version}/>, document.getElementById('root'));
registerServiceWorker();

View File

@@ -0,0 +1,117 @@
// In production, we register a service worker to serve assets from local cache.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on the "N+1" visit to a page, since previously
// cached resources are updated in the background.
// To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
// This link also includes instructions on opting out of this behavior.
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.1/8 is considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export default function register() {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Lets check if a service worker still exists or not.
checkValidServiceWorker(swUrl);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://goo.gl/SC7cgQ'
);
});
} else {
// Is not local host. Just register service worker
registerValidSW(swUrl);
}
});
}
}
function registerValidSW(swUrl) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the old content will have been purged and
// the fresh content will have been added to the cache.
// It's the perfect time to display a "New content is
// available; please refresh." message in your web app.
console.log('New content is available; please refresh.');
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl)
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
if (
response.status === 404 ||
response.headers.get('content-type').indexOf('javascript') === -1
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
registration.unregister();
});
}
}