Initial commit
This commit is contained in:
11
src/App.css
Normal file
11
src/App.css
Normal 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
564
src/App.js
Normal 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});
|
||||
BIN
src/assets/images/background.jpg
Normal file
BIN
src/assets/images/background.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
BIN
src/assets/images/upgrade_available.png
Normal file
BIN
src/assets/images/upgrade_available.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 175 KiB |
9
src/components/DependencyList/Dependency/Dependency.css
Normal file
9
src/components/DependencyList/Dependency/Dependency.css
Normal file
@@ -0,0 +1,9 @@
|
||||
.Dependency {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.Link {
|
||||
cursor: pointer;
|
||||
}
|
||||
25
src/components/DependencyList/Dependency/Dependency.js
Normal file
25
src/components/DependencyList/Dependency/Dependency.js
Normal 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});
|
||||
3
src/components/DependencyList/DependencyList.css
Normal file
3
src/components/DependencyList/DependencyList.css
Normal file
@@ -0,0 +1,3 @@
|
||||
.DependencyList {
|
||||
|
||||
}
|
||||
27
src/components/DependencyList/DependencyList.js
Normal file
27
src/components/DependencyList/DependencyList.js
Normal 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});
|
||||
17
src/components/DownloadProgress/DownloadProgress.js
Normal file
17
src/components/DownloadProgress/DownloadProgress.js
Normal 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});
|
||||
4
src/components/MountItem/MountItem.css
Normal file
4
src/components/MountItem/MountItem.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.MountItem {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
52
src/components/MountItem/MountItem.js
Normal file
52
src/components/MountItem/MountItem.js
Normal 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});
|
||||
41
src/components/UI/Box/Box.css
Normal file
41
src/components/UI/Box/Box.css
Normal 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%);
|
||||
}
|
||||
}
|
||||
28
src/components/UI/Box/Box.js
Normal file
28
src/components/UI/Box/Box.js
Normal 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});
|
||||
30
src/components/UI/Button/Button.css
Normal file
30
src/components/UI/Button/Button.css
Normal 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;
|
||||
}
|
||||
13
src/components/UI/Button/Button.js
Normal file
13
src/components/UI/Button/Button.js
Normal 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});
|
||||
|
||||
24
src/components/UI/DropDown/DropDown.css
Normal file
24
src/components/UI/DropDown/DropDown.css
Normal 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);
|
||||
}
|
||||
20
src/components/UI/DropDown/DropDown.js
Normal file
20
src/components/UI/DropDown/DropDown.js
Normal 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});
|
||||
19
src/components/UI/Modal/Modal.css
Normal file
19
src/components/UI/Modal/Modal.css
Normal 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;
|
||||
}
|
||||
14
src/components/UI/Modal/Modal.js
Normal file
14
src/components/UI/Modal/Modal.js
Normal 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});
|
||||
14
src/components/UpgradeIcon/UpgradeIcon.css
Normal file
14
src/components/UpgradeIcon/UpgradeIcon.css
Normal 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;
|
||||
}
|
||||
10
src/components/UpgradeIcon/UpgradeIcon.js
Normal file
10
src/components/UpgradeIcon/UpgradeIcon.js
Normal 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});
|
||||
0
src/components/UpgradeUI/UpgradeUI.css
Normal file
0
src/components/UpgradeUI/UpgradeUI.css
Normal file
28
src/components/UpgradeUI/UpgradeUI.js
Normal file
28
src/components/UpgradeUI/UpgradeUI.js
Normal 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
7
src/constants.js
Normal 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';
|
||||
4
src/containers/MountItems/MountItems.css
Normal file
4
src/containers/MountItems/MountItems.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.MountItems {
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
}
|
||||
184
src/containers/MountItems/MountItems.js
Normal file
184
src/containers/MountItems/MountItems.js
Normal 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
63
src/index.css
Normal 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
10
src/index.js
Normal 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();
|
||||
117
src/registerServiceWorker.js
Normal file
117
src/registerServiceWorker.js
Normal 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();
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user