Merged 1.0.1_branch into master

This commit is contained in:
Scott Graves
2018-10-04 15:17:44 -05:00
56 changed files with 2045 additions and 627 deletions

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ node_modules/
build/
chrome_data/
dist/
/.cache

12
CHANGELOG.md Normal file
View File

@@ -0,0 +1,12 @@
# Changelog #
## 1.0.1 ##
* Added configuration settings for Repertory 1.0.0-alpha.2 and above
* Fixed memory leak on component unmount
* Added error display
* Lighter tray icon on Windows
* Tray icon indicates mount status on Windows
* Various fixes/layout changes
## 1.0.0 ##
* Initial release
* Windows support

8
LICENSE.md Normal file
View File

@@ -0,0 +1,8 @@
# Repertory UI MIT License #
### Copyright <2018> <scott.e.graves@gmail.com> ###
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,6 +1,5 @@
# Repertory UI
![alt text](https://image.ibb.co/dpncxU/repertory_windows.png)
![alt text](https://image.ibb.co/mnhA1z/repertory_1_0_1.png)
### GUI for [Repertory](https://bitbucket.org/blockstorage/repertory) ###
Repertory allows you to mount Hyperspace or Sia blockchain storage solutions via FUSE on Linux/OS X or via WinFSP on Windows.
# Downloads #

View File

@@ -2,7 +2,7 @@
const {app, BrowserWindow, Tray, nativeImage, Menu} = require('electron');
const {ipcMain} = require('electron');
const Constants = require('./src/constants');
const path = require('path');
const url = require('url');
require('electron-debug')();
@@ -14,17 +14,19 @@ const AutoLaunch = require('auto-launch');
// Keep a global reference of the window object, if you don't, the window will
// be closed automatically when the JavaScript object is garbage collected.
let mainContextWindow;
let mainWindow;
let mainWindowTray;
let mountedPIDs = [];
function createWindow() {
// Create the browser window.
const height = process.env.ELECTRON_START_URL ? 340 : 320;
const height = process.env.ELECTRON_START_URL ? 324 : 304;
mainWindow = new BrowserWindow({
width: 425,
height: height,
resizable: false,
title: 'Repertory UI',
webPreferences: {
webSecurity: !process.env.ELECTRON_START_URL
}
@@ -46,14 +48,18 @@ function createWindow() {
mainWindow = null
});
if (os.platform() === 'win32') {
if ((os.platform() === 'win32') || (os.platform() === 'linux')) {
const appPath = (os.platform() === 'win32') ?
path.resolve(path.join(app.getAppPath(), '..\\..\\repertory-ui.exe')) :
process.env.APPIMAGE;
const autoLauncher = new AutoLaunch({
name: 'Repertory UI',
path: path.resolve(path.join(app.getAppPath(), '..\\..\\repertory-ui.exe')),
path: appPath,
});
const image = nativeImage.createFromPath(path.join(__dirname, '/build/icon.ico'));
const contextMenu = Menu.buildFromTemplate([
const image = nativeImage.createFromPath(path.join(__dirname, '/build/logo.png'));
mainContextWindow = Menu.buildFromTemplate([
{
label: 'Visible', type: 'checkbox', click(item) {
if (item.checked) {
@@ -75,21 +81,30 @@ function createWindow() {
autoLauncher.disable();
}
}
},
{
type: 'separator'
},
{
label: 'Exit', click(item) {
app.quit();
}
}
]);
contextMenu.items[0].checked = true;
autoLauncher.isEnabled()
.then((enabled) => {
contextMenu.items[1].checked = enabled;
mainContextWindow.items[0].checked = true;
autoLauncher
.isEnabled()
.then((enabled) => {
mainContextWindow.items[1].checked = enabled;
mainWindowTray = new Tray(image);
mainWindowTray.setToolTip('Repertory UI');
mainWindowTray.setContextMenu(contextMenu)
})
.catch(() => {
app.quit();
});
mainWindowTray = new Tray(image);
mainWindowTray.setToolTip('Repertory UI');
mainWindowTray.setContextMenu(mainContextWindow)
})
.catch(() => {
app.quit();
});
}
}
@@ -99,6 +114,10 @@ if (!instanceLock) {
} else {
app.on('second-instance', () => {
if (mainWindow) {
mainWindow.show();
if (mainContextWindow) {
mainContextWindow.items[0].checked = true;
}
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
@@ -125,7 +144,17 @@ if (!instanceLock) {
});
}
ipcMain.on('check_installed', (event, data) => {
const standardIPCReply = (event, channel, data, error) => {
event.sender.send(channel, {
data: {
...data,
Error: error,
Success: !error,
}
});
};
ipcMain.on(Constants.IPC_Check_Installed, (event, data) => {
const dataDirectory = helpers.resolvePath(data.Directory);
const destination = path.join(dataDirectory, data.Version);
helpers
@@ -136,27 +165,20 @@ ipcMain.on('check_installed', (event, data) => {
exists = fs.existsSync(destination) && fs.lstatSync(destination).isDirectory();
} catch (e) {
}
event.sender.send('check_installed_reply', {
data: {
Dependencies: dependencies,
Exists: exists,
Success: true,
Version: data.Version,
}
});
}).catch((e) => {
event.sender.send('check_installed_reply', {
data: {
Dependencies: [],
Error: e,
Success: false,
Version: data.Version,
}
standardIPCReply(event, Constants.IPC_Check_Installed_Reply, {
Dependencies: dependencies,
Exists: exists,
Version: data.Version,
});
}).catch(error => {
standardIPCReply(event, Constants.IPC_Check_Installed_Reply, {
Dependencies: [],
Version: data.Version,
}, error);
});
});
ipcMain.on('delete_file', (event, data) => {
ipcMain.on(Constants.IPC_Delete_File, (event, data) => {
try {
if (fs.existsSync(data.FilePath)) {
fs.unlinkSync(data.FilePath);
@@ -165,7 +187,7 @@ ipcMain.on('delete_file', (event, data) => {
}
});
ipcMain.on('detect_mounts', (event, data) => {
ipcMain.on(Constants.IPC_Detect_Mounts, (event, data) => {
let driveLetters = {
Hyperspace: [],
Sia: [],
@@ -202,6 +224,23 @@ ipcMain.on('detect_mounts', (event, data) => {
}
};
const setImage = (hsLocation, siaLocation) => {
if (os.platform() === 'win32') {
let image;
if ((siaLocation.length > 0) && (hsLocation.length > 0)) {
image = nativeImage.createFromPath(path.join(__dirname, '/build/logo_both.png'));
} else if (hsLocation.length > 0) {
image = nativeImage.createFromPath(path.join(__dirname, '/build/logo_hs.png'));
} else if (siaLocation.length > 0) {
image = nativeImage.createFromPath(path.join(__dirname, '/build/logo_sia.png'));
} else {
image = nativeImage.createFromPath(path.join(__dirname, '/build/logo.png'));
}
mainWindowTray.setImage(image);
}
};
const dataDirectory = helpers.resolvePath(data.Directory);
helpers
.detectRepertoryMounts(dataDirectory, data.Version)
@@ -213,204 +252,225 @@ ipcMain.on('detect_mounts', (event, data) => {
siaLocation = siaLocation.toUpperCase();
grabDriveLetters(hsLocation, siaLocation);
}
event.sender.send('detect_mounts_reply', {
data: {
DriveLetters: driveLetters,
Locations: {
Hyperspace: hsLocation,
Sia: siaLocation,
},
Success: true,
PIDS: {
Hyperspace: results.Hyperspace.PID,
Sia: results.Sia.PID,
}
setImage(hsLocation, siaLocation);
standardIPCReply(event, Constants.IPC_Detect_Mounts_Reply, {
DriveLetters: driveLetters,
Locations: {
Hyperspace: hsLocation,
Sia: siaLocation,
},
PIDS: {
Hyperspace: results.Hyperspace.PID,
Sia: results.Sia.PID,
}
});
})
.catch((err) => {
.catch(error => {
grabDriveLetters('', '');
event.sender.send('detect_mounts_reply', {
data: {
DriveLetters: driveLetters,
Error: err,
Success: false,
}
});
setImage('', '');
standardIPCReply(event, Constants.IPC_Detect_Mounts_Reply, {
DriveLetters: driveLetters,
}, error);
});
});
ipcMain.on('download_file', (event, data) => {
ipcMain.on(Constants.IPC_Download_File, (event, data) => {
const dataDirectory = helpers.resolvePath(data.Directory);
const destination = path.join(dataDirectory, data.Filename);
helpers.downloadFile(data.URL, destination, (progress) => {
event.sender.send('download_file_progress', {
data: {
Destination: destination,
Progress: progress,
URL: data.URL,
}
});
}, (success, err) => {
event.sender.send('download_file_complete', {
data: {
Destination: destination,
Error: err,
Success: success,
URL: data.URL,
}
standardIPCReply(event, Constants.IPC_Download_File_Progress, {
Destination: destination,
Progress: progress,
URL: data.URL,
});
}, error => {
standardIPCReply(event, Constants.IPC_Download_File_Complete, {
Destination: destination,
URL: data.URL,
}, error);
});
});
ipcMain.on('extract_release', (event, data) => {
ipcMain.on(Constants.IPC_Extract_Release, (event, data) => {
const dataDirectory = helpers.resolvePath(data.Directory);
const destination = path.join(dataDirectory, data.Version);
helpers.mkDirByPathSync(destination);
const stream = fs.createReadStream(data.Source);
stream.pipe(unzip.Extract({ path: destination }))
.on('error', (e) => {
stream
.pipe(unzip.Extract({ path: destination }))
.on('error', error => {
try {
helpers.removeDirectoryRecursively(destination);
} catch (e) {
}
stream.close();
event.sender.send('extract_release_complete', {
data: {
Error: e,
Source: data.Source,
Success: false,
}
});
standardIPCReply(event, Constants.IPC_Extract_Release_Complete, {
Source: data.Source,
}, error);
})
.on('finish', () => {
stream.close();
event.sender.send('extract_release_complete', {
data: {
Source: data.Source,
Success: true,
}
standardIPCReply(event, Constants.IPC_Extract_Release_Complete, {
Source: data.Source,
});
});
});
ipcMain.on('get_platform', (event) => {
event.sender.send('get_platform_reply', {
ipcMain.on(Constants.IPC_Get_Config, (event, data) => {
const dataDirectory = helpers.resolvePath(data.Directory);
helpers
.getConfig(dataDirectory, data.Version, data.StorageType)
.then((data) => {
if (data.Code === 0) {
standardIPCReply(event, Constants.IPC_Get_Config_Reply, {
Config: data.Data,
});
} else {
standardIPCReply(event, Constants.IPC_Get_Config_Reply, {}, data.Code);
}
})
.catch(error => {
standardIPCReply(event, Constants.IPC_Get_Config_Reply, {}, error);
});
});
ipcMain.on(Constants.IPC_Get_Config_Template, (event, data) => {
const dataDirectory = helpers.resolvePath(data.Directory);
helpers
.getConfigTemplate(dataDirectory, data.Version, data.StorageType)
.then((data) => {
standardIPCReply(event, Constants.IPC_Get_Config_Template_Reply, {
Template: data,
});
})
.catch(error => {
standardIPCReply(event, Constants.IPC_Get_Config_Template_Reply, {}, error);
});
});
ipcMain.on(Constants.IPC_Get_Platform, (event) => {
event.sender.send(Constants.IPC_Get_Platform_Reply, {
data: os.platform()
});
});
ipcMain.on('get_state', (event, data) => {
ipcMain.on(Constants.IPC_Get_State, (event, data) => {
const dataDirectory = helpers.resolvePath(data);
helpers.mkDirByPathSync(dataDirectory);
const configFile = path.join(dataDirectory, 'settings.json');
if (fs.existsSync(configFile)) {
event.sender.send('get_state_reply', {
event.sender.send(Constants.IPC_Get_State_Reply, {
data: JSON.parse(fs.readFileSync(configFile, 'utf8')),
});
} else {
event.sender.send('get_state_reply', {
event.sender.send(Constants.IPC_Get_State_Reply, {
data: null,
});
}
});
ipcMain.on('grab_releases', (event) => {
event.sender.send('grab_releases_reply');
ipcMain.on(Constants.IPC_Grab_Releases, (event) => {
standardIPCReply(event, Constants.IPC_Grab_Releases_Reply);
});
ipcMain.on('grab_ui_releases', (event) => {
event.sender.send('grab_ui_releases_reply');
ipcMain.on(Constants.IPC_Grab_UI_Releases, (event) => {
standardIPCReply(event, Constants.IPC_Grab_UI_Releases_Reply);
});
ipcMain.on('install_dependency', (event, data) => {
ipcMain.on(Constants.IPC_Install_Dependency, (event, data) => {
helpers
.executeAndWait(data.Source)
.then(()=> {
event.sender.send('install_dependency_reply', {
data: {
Source: data.Source,
Success: true,
}
standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, {
Source: data.Source,
});
})
.catch((e)=> {
event.sender.send('install_dependency_reply', {
data: {
Error: e,
Source: data.Source,
Success: false,
}
});
.catch(error => {
standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, {
Source: data.Source,
}, error);
});
});
ipcMain.on('install_upgrade', (event, data) => {
ipcMain.on(Constants.IPC_Install_Upgrade, (event, data) => {
helpers
.executeAsync(data.Source)
.then(()=> {
mainWindow.close();
})
.catch((e)=> {
event.sender.send('install_upgrade_reply', {
data: {
Error: e,
Source: data.Source,
Success: false,
}
});
.catch(error => {
standardIPCReply(event, Constants.IPC_Install_Upgrade_Reply, {
Source: data.Source,
}, error);
});
});
ipcMain.on('mount_drive', (event, data) => {
ipcMain.on(Constants.IPC_Mount_Drive, (event, data) => {
const dataDirectory = helpers.resolvePath(data.Directory);
const errorHandler = (pid) => {
const errorHandler = (pid, error) => {
mountedPIDs.splice(mountedPIDs.indexOf(pid), 1);
event.sender.send('unmount_drive_reply', {
data: {
PID: -1,
StorageType: data.StorageType,
Success: false,
}
});
standardIPCReply(event, Constants.IPC_Unmount_Drive_Reply, {
PID: -1,
StorageType: data.StorageType,
}, error || Error(data.StorageType + ' Unmounted'));
};
helpers.executeMount(dataDirectory, data.Version, data.StorageType, data.Location, (_, pid)=> {
errorHandler(pid);
helpers.executeMount(dataDirectory, data.Version, data.StorageType, data.Location, (error, pid) => {
errorHandler(pid, error);
})
.then(pid=> {
.then(pid => {
if (pid !== -1) {
mountedPIDs.push(pid);
}
event.sender.send('mount_drive_reply', {
data: {
PID: pid,
StorageType: data.StorageType,
Success: true,
}
standardIPCReply(event, Constants.IPC_Mount_Drive_Reply, {
PID: pid,
StorageType: data.StorageType,
});
})
.catch((_, pid) => {
errorHandler(pid);
.catch(error => {
errorHandler(-1, error);
});
});
ipcMain.on('save_state', (event, data) => {
ipcMain.on(Constants.IPC_Save_State, (event, data) => {
const dataDirectory = helpers.resolvePath(data.Directory);
helpers.mkDirByPathSync(dataDirectory);
const configFile = path.join(dataDirectory, 'settings.json');
fs.writeFileSync(configFile, JSON.stringify(data.State), 'utf8');
});
ipcMain.on('unmount_drive', (event, data) => {
helpers.stopProcessByPID(data.PID)
.then((pid)=> {
if (mountedPIDs.indexOf(pid) === -1) {
event.sender.send('unmount_drive_reply');
ipcMain.on(Constants.IPC_Set_Config_Values, (event, data) => {
const dataDirectory = helpers.resolvePath(data.Directory);
const setConfigValue = (i) => {
if (i < data.Items.length) {
helpers
.setConfigValue(data.Items[i].Name, data.Items[i].Value, dataDirectory, data.StorageType, data.Version)
.then(() => {
setConfigValue(++i);
})
.catch(() => {
setConfigValue(++i);
});
} else {
standardIPCReply(event, Constants.IPC_Set_Config_Values_Reply, {});
}
})
.catch((e) => {
console.log(e);
});
};
setConfigValue(0);
});
ipcMain.on(Constants.IPC_Shutdown, () => {
app.quit();
});
ipcMain.on(Constants.IPC_Unmount_Drive, (event, data) => {
helpers
.stopProcessByPID(data.PID)
.then((pid)=> {
if (mountedPIDs.indexOf(pid) === -1) {
event.sender.send(Constants.IPC_Unmount_Drive_Reply);
}
})
.catch((e) => {
console.log(e);
});
});

View File

@@ -80,18 +80,18 @@ module.exports.downloadFile = (url, destination, progressCallback, completeCallb
response.data.on('end', () => {
stream.end(() => {
completeCallback(true);
completeCallback();
});
});
response.data.on('error', (e) => {
stream.end(() => {
completeCallback(false, e);
completeCallback(e);
});
});
})
.catch((e)=> {
completeCallback(false, e);
completeCallback(e);
});
};
@@ -195,6 +195,85 @@ module.exports.executeMount = (directory, version, storageType, location, exitCa
});
};
module.exports.getConfig = (directory, version, storageType) => {
return new Promise((resolve, reject) => {
const processOptions = {
detached: true,
shell: false,
windowsHide: true,
};
const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory');
const args = [];
args.push('-dc');
if (storageType.toLowerCase() === 'hyperspace') {
args.push('-hs');
}
const process = new spawn(command, args, processOptions);
let result = '';
process.on('error', (err) => {
reject(err);
});
process.stdout.on('data', (d)=> {
result += d;
});
process.on('exit', () => {
const lines = result
.replace(/\r\n/g, '\n')
.split('\n');
const code = parseInt(lines[0], 10);
if (code === 0) {
lines.shift();
resolve({
Code: code,
Data: JSON.parse(lines.join('\n')),
});
} else {
resolve(code);
}
});
process.unref();
});
};
module.exports.getConfigTemplate = (directory, version, storageType) => {
return new Promise((resolve, reject) => {
const processOptions = {
detached: true,
shell: false,
windowsHide: true,
};
const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory');
const args = [];
args.push('-gt');
if (storageType.toLowerCase() === 'hyperspace') {
args.push('-hs');
}
const process = new spawn(command, args, processOptions);
let result = '';
process.on('error', (err) => {
reject(err);
});
process.stdout.on('data', (d)=> {
result += d;
});
process.on('exit', () => {
resolve(JSON.parse(result));
});
process.unref();
});
};
module.exports.getMissingDependencies = dependencies => {
return new Promise((resolve, reject) => {
if (!dependencies || (dependencies.length === 0)) {
@@ -323,6 +402,37 @@ module.exports.resolvePath = str => {
}
};
module.exports.setConfigValue = (name, value, directory, storageType, version) => {
return new Promise((resolve, reject) => {
const processOptions = {
detached: true,
shell: false,
windowsHide: true,
};
const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory');
const args = [];
args.push('-set');
args.push(name);
args.push(value);
if (storageType.toLowerCase() === 'hyperspace') {
args.push('-hs');
}
const process = new spawn(command, args, processOptions);
process.on('error', (err) => {
reject(err);
});
process.on('exit', () => {
resolve();
});
process.unref();
});
};
module.exports.stopProcessByPID = pid => {
return new Promise((resolve, reject) => {
const processOptions = {

View File

@@ -1,6 +1,6 @@
{
"name": "repertory-ui",
"version": "1.0.0",
"version": "1.0.1",
"private": true,
"dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.1",
@@ -47,6 +47,7 @@
"react-dev-utils": "^5.0.1",
"react-dom": "^16.4.1",
"react-loader-spinner": "^2.0.6",
"react-tooltip": "^3.8.4",
"resolve": "1.6.0",
"style-loader": "0.19.0",
"sw-precache-webpack-plugin": "0.11.4",
@@ -68,7 +69,7 @@
},
"devDependencies": {
"cross-env": "^5.2.0",
"electron": "3.0.0",
"electron": "^3.0.2",
"electron-builder": "^20.28.4",
"extract-text-webpack-plugin": "^3.0.2",
"webpack-browser-plugin": "^1.0.20"
@@ -121,6 +122,7 @@
"appId": "repertory-ui",
"files": [
"./electron.js",
"./src/constants.js",
"build/**/*",
"node_modules/**/*",
"./helpers.js"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

After

Width:  |  Height:  |  Size: 361 KiB

BIN
public/favicon_old.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

After

Width:  |  Height:  |  Size: 361 KiB

BIN
public/icon_old.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 361 KiB

BIN
public/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
public/logo_both.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
public/logo_hs.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
public/logo_sia.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -1,6 +1,12 @@
{
"Locations": {
"win32": {
"1.0.1": {
"hash": "",
"urls": [
"https://sia.pixeldrain.com/api/file/Alo1IF1u/download"
]
},
"1.0.0": {
"hash": "",
"urls": [
@@ -11,6 +17,7 @@
},
"Versions": {
"win32": [
"1.0.1",
"1.0.0"
]
}

View File

@@ -9,3 +9,22 @@
background-image: url('./assets/images/background.jpg');
background-size: cover;
}
.Container {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
box-sizing: border-box;
}
.Header {
height: 28px;
margin-bottom: 8px;
box-sizing: border-box;
}
.Content {
flex: 1;
box-sizing: border-box;
}

View File

@@ -1,18 +1,22 @@
import React, {Component} from 'react';
import CSSModules from 'react-css-modules';
import axios from 'axios';
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 Configuration from './containers/Configuration/Configuration';
import CSSModules from 'react-css-modules';
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 ErrorDetails from './components/ErrorDetails/ErrorDetails';
import Grid from './components/UI/Grid/Grid';
import Loading from './components/UI/Loading/Loading';
import Modal from './components/UI/Modal/Modal';
import MountItems from './containers/MountItems/MountItems';
import ReleaseVersionDisplay from './components/ReleaseVersionDisplay/ReleaseVersionDisplay';
import Text from './components/UI/Text/Text';
import UpgradeIcon from './components/UpgradeIcon/UpgradeIcon';
import UpgradeUI from './components/UpgradeUI/UpgradeUI';
const Constants = require('./constants');
const Scheduler = require('node-schedule');
let ipcRenderer = null;
@@ -25,180 +29,30 @@ class App extends Component {
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(Constants.IPC_Check_Installed_Reply, this.onCheckInstalledReply);
ipcRenderer.on(Constants.IPC_Download_File_Complete, this.onDownloadFileComplete);
ipcRenderer.on(Constants.IPC_Download_File_Progress, this.onDownloadFileProgress);
ipcRenderer.on(Constants.IPC_Extract_Release_Complete, this.onExtractReleaseComplete);
ipcRenderer.on(Constants.IPC_Get_State_Reply, this.onGetStateReply);
ipcRenderer.on(Constants.IPC_Grab_Releases_Reply, this.onGrabReleasesReply);
ipcRenderer.on(Constants.IPC_Grab_UI_Releases_Reply, this.onGrabUiReleasesReply);
ipcRenderer.on(Constants.IPC_Install_Dependency_Reply, this.onInstallDependencyReply);
ipcRenderer.on(Constants.IPC_Install_Upgrade_Reply, this.onInstallUpgradeReply);
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');
ipcRenderer.send(Constants.IPC_Get_State, Constants.DATA_LOCATIONS[this.props.platform]);
Scheduler.scheduleJob('23 11 * * *', this.updateCheckScheduledJob);
}
}
state = {
AllowOptions: false,
AllowDownload: false,
AutoMountChecked: false,
AutoMountProcessed: false,
ConfigStorageType: null,
DisplayError: false,
DisplayMainContent: false,
Error: null,
ErrorAction: null,
ErrorCritical: false,
DownloadActive: false,
DownloadProgress: 0.0,
DownloadingDependency: false,
@@ -212,6 +66,7 @@ class App extends Component {
},
LocationsLookup: {},
MissingDependencies: [],
MountsBusy: false,
Platform: 'unknown',
Release: 3,
ReleaseTypes: [
@@ -220,7 +75,7 @@ class App extends Component {
'Beta',
'Alpha',
],
RepertoryVersion: 'none',
InstalledVersion: 'none',
Sia: {
AutoMount: false,
MountLocation: '',
@@ -228,7 +83,8 @@ class App extends Component {
UpgradeAvailable: false,
UpgradeData: {},
UpgradeDismissed: false,
Version: 0,
Version: -1,
VersionAvailable: false,
VersionLookup: {
Alpha: [
'unavailable'
@@ -255,25 +111,58 @@ class App extends Component {
AllowDownload: false,
});
if (ipcRenderer) {
let dependencies = [];
if (this.state.LocationsLookup[selectedVersion] && this.state.LocationsLookup[selectedVersion].dependencies) {
dependencies = this.state.LocationsLookup[selectedVersion].dependencies;
}
if (selectedVersion !== 'unavailable') {
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,
ipcRenderer.send(Constants.IPC_Check_Installed, {
Dependencies: dependencies,
Directory: Constants.DATA_LOCATIONS[this.props.platform],
Version: selectedVersion,
});
}
}
};
closeErrorDisplay = () => {
if (this.state.ErrorAction) {
this.state.ErrorAction();
}
if (this.state.ErrorCritical) {
if (ipcRenderer) {
ipcRenderer.send(Constants.IPC_Shutdown);
}
} else {
this.setState({
DisplayError: false,
Error: null,
});
}
};
componentWillUnmount = () => {
if (ipcRenderer) {
ipcRenderer.removeListener(Constants.IPC_Check_Installed_Reply, this.onCheckInstalledReply);
ipcRenderer.removeListener(Constants.IPC_Download_File_Complete, this.onDownloadFileComplete);
ipcRenderer.removeListener(Constants.IPC_Download_File_Progress, this.onDownloadFileProgress);
ipcRenderer.removeListener(Constants.IPC_Extract_Release_Complete, this.onExtractReleaseComplete);
ipcRenderer.removeListener(Constants.IPC_Get_State_Reply, this.onGetStateReply);
ipcRenderer.removeListener(Constants.IPC_Grab_Releases_Reply, this.onGrabReleasesReply);
ipcRenderer.removeListener(Constants.IPC_Grab_UI_Releases_Reply, this.onGrabUiReleasesReply);
ipcRenderer.removeListener(Constants.IPC_Install_Dependency_Reply, this.onInstallDependencyReply);
ipcRenderer.removeListener(Constants.IPC_Install_Upgrade_Reply, this.onInstallUpgradeReply);
}
};
grabReleases = () => {
if (this.state.Platform !== 'unknown') {
if (this.props.platform !== 'unknown') {
if (ipcRenderer) {
ipcRenderer.send('grab_releases');
ipcRenderer.send('grab_ui_releases');
ipcRenderer.send(Constants.IPC_Grab_Releases);
ipcRenderer.send(Constants.IPC_Grab_UI_Releases);
}
}
};
@@ -302,6 +191,18 @@ class App extends Component {
this.saveState(this.state.Release, this.state.Version, sia, hyperspace);
};
handleConfigClicked = (storageType) => {
this.setState({
ConfigStorageType: storageType,
})
};
handleConfigClosed = () => {
this.setState({
ConfigStorageType: null,
});
};
handleDependencyDownload = (url) => {
if (ipcRenderer) {
const items = url.split('/');
@@ -313,8 +214,8 @@ class App extends Component {
DownloadName: fileName,
});
ipcRenderer.send('download_file', {
Directory: Constants.DATA_LOCATIONS[this.state.Platform],
ipcRenderer.send(Constants.IPC_Download_File, {
Directory: Constants.DATA_LOCATIONS[this.props.platform],
Filename: fileName,
URL: url,
});
@@ -341,12 +242,13 @@ class App extends Component {
handleReleaseChanged = (e) => {
const val = parseInt(e.target.value, 10);
const versionIndex = this.state.VersionLookup[this.state.ReleaseTypes[val]].length - 1;
this.setState({
Release: val,
Version: 0
Version: versionIndex
});
this.saveState(val, 0, this.state.Sia, this.state.Hyperspace);
this.checkVersionInstalled(val, 0);
this.saveState(val, versionIndex, this.state.Sia, this.state.Hyperspace);
this.checkVersionInstalled(val, versionIndex);
};
handleReleaseDownload = () => {
@@ -359,8 +261,8 @@ class App extends Component {
DownloadName: fileName,
});
ipcRenderer.send('download_file', {
Directory: Constants.DATA_LOCATIONS[this.state.Platform],
ipcRenderer.send(Constants.IPC_Download_File, {
Directory: Constants.DATA_LOCATIONS[this.props.platform],
Filename: fileName,
URL: this.state.LocationsLookup[selectedVersion].urls[0],
});
@@ -375,9 +277,9 @@ class App extends Component {
DownloadName: 'UI Upgrade',
});
ipcRenderer.send('download_file', {
Directory: Constants.DATA_LOCATIONS[this.state.Platform],
Filename: this.state.Platform === 'win32' ? 'upgrade.exe' : 'upgrade',
ipcRenderer.send(Constants.IPC_Download_File, {
Directory: Constants.DATA_LOCATIONS[this.props.platform],
Filename: this.props.platform === 'win32' ? 'upgrade.exe' : 'upgrade',
URL: this.state.UpgradeData.urls[0],
});
} else {
@@ -395,13 +297,228 @@ class App extends Component {
};
notifyAutoMountProcessed = () => {
this.setState({AutoMountChecked: true});
this.setState({AutoMountProcessed: true});
};
notifyMountsBusy = (busy) => {
this.setState({MountsBusy: busy})
};
onCheckInstalledReply = (event, arg) => {
const action = () => {
const installedVersion = arg.data.Success && arg.data.Exists ? arg.data.Version : 'none';
let versionAvailable = false;
if (installedVersion !== 'none') {
const latestVersion = this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]].length - 1;
let version = this.state.Version;
if (version === -1) {
version = latestVersion;
}
versionAvailable = version !== latestVersion;
}
this.setState({
AllowDownload: true,
DownloadingDependency: false,
MissingDependencies: arg.data.Dependencies,
InstalledVersion: installedVersion,
VersionAvailable: versionAvailable,
});
};
if (arg.data.Success) {
action();
} else {
this.setErrorState(arg.data.Error, action);
}
};
onDownloadFileComplete = (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(Constants.IPC_Extract_Release, {
Directory: Constants.DATA_LOCATIONS[this.props.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(Constants.IPC_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(Constants.IPC_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: '',
});
}
};
onDownloadFileProgress = (event, arg) => {
this.setState({
DownloadProgress: arg.data.Progress,
});
};
onExtractReleaseComplete = (event, arg) => {
ipcRenderer.send(Constants.IPC_Delete_File, {
FilePath: arg.data.Source,
});
this.setState({
ExtractActive: false,
});
this.checkVersionInstalled(this.state.Release, this.state.Version);
};
onGetStateReply = (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();
};
onGrabReleasesReply = ()=> {
const doUpdate = (locationsLookup, versionLookup) => {
const latestVersion = versionLookup[this.state.ReleaseTypes[this.state.Release]].length - 1;
let version = this.state.Version;
if ((version === -1) || !versionLookup[this.state.ReleaseTypes[this.state.Release]][version]) {
version = latestVersion;
this.saveState(this.state.Release, version, this.state.Sia, this.state.Hyperspace);
}
this.setState({
DisplayMainContent: true,
LocationsLookup: locationsLookup,
Version: version,
VersionAvailable: version !== latestVersion,
VersionLookup: versionLookup,
});
this.checkVersionInstalled(this.state.Release, version, versionLookup);
};
axios.get(Constants.RELEASES_URL)
.then(response => {
const versionLookup = {
Alpha: response.data.Versions.Alpha[this.props.platform],
Beta: response.data.Versions.Beta[this.props.platform],
RC: response.data.Versions.RC[this.props.platform],
Release: response.data.Versions.Release[this.props.platform],
};
const locationsLookup = {
...response.data.Locations[this.props.platform],
};
window.localStorage.setItem('releases', JSON.stringify({
LocationsLookup: locationsLookup,
VersionLookup: versionLookup
}));
doUpdate(locationsLookup, versionLookup);
}).catch(error => {
const releases = window.localStorage.getItem('releases');
if (releases && (releases.length > 0)) {
const obj = JSON.parse(releases);
const locationsLookup = obj.LocationsLookup;
const versionLookup = obj.VersionLookup;
doUpdate(locationsLookup, versionLookup);
} else {
this.setErrorState(error, null, true);
}
});
};
onGrabUiReleasesReply = ()=> {
axios.get(Constants.UI_RELEASES_URL)
.then(response => {
const data = response.data;
if (data.Versions &&
data.Versions[this.props.platform] &&
(data.Versions[this.props.platform].length > 0) &&
(data.Versions[this.props.platform][0] !== this.props.version)) {
this.setState({
UpgradeAvailable: true,
UpgradeDismissed: false,
UpgradeData: data.Locations[this.props.platform][data.Versions[this.props.platform][0]],
});
}
}).catch(() => {
this.setState({
UpgradeAvailable: false,
UpgradeData: {},
});
});
};
onInstallDependencyReply = (event, arg) => {
ipcRenderer.send(Constants.IPC_Delete_File, {
FilePath: arg.data.Source,
});
this.checkVersionInstalled(this.state.Release, this.state.Version);
};
onInstallUpgradeReply = (event, arg) => {
ipcRenderer.sendSync(Constants.IPC_Delete_File, {
FilePath: arg.data.Source,
});
this.setState({
DownloadActive: false,
DownloadProgress: 0.0,
DownloadName: '',
});
};
saveState = (release, version, sia, hyperspace)=> {
if (ipcRenderer) {
ipcRenderer.send('save_state', {
Directory: Constants.DATA_LOCATIONS[this.state.Platform],
ipcRenderer.send(Constants.IPC_Save_State, {
Directory: Constants.DATA_LOCATIONS[this.props.platform],
State: {
Hyperspace: hyperspace,
Release: release,
@@ -412,38 +529,77 @@ class App extends Component {
}
};
setErrorState = (error, action, critical) => {
this.setState({
DisplayError: true,
Error: error,
ErrorAction: action,
ErrorCritical: critical,
});
};
updateCheckScheduledJob = () => {
if (this.state.Platform !== 'unknown') {
if (ipcRenderer) {
ipcRenderer.send('grab_ui_releases');
}
if (this.props.platform !== 'unknown') {
this.grabReleases();
}
};
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);
const selectedVersion = (this.state.Version === -1) ?
'unavailable' :
this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]][this.state.Version];
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}/>;
const downloadEnabled = this.state.AllowDownload &&
!this.state.MountsBusy &&
!this.state.DownloadActive &&
(selectedVersion !== 'unavailable') &&
(selectedVersion !== this.state.InstalledVersion);
const allowMount = this.state.InstalledVersion !== 'none';
const missingDependencies = (this.state.MissingDependencies.length > 0);
const allowConfig = this.state.LocationsLookup[selectedVersion] &&
this.state.LocationsLookup[selectedVersion].config_support;
const showDependencies = missingDependencies &&
!this.state.DownloadActive;
const showConfig = !missingDependencies &&
this.state.ConfigStorageType &&
allowConfig;
const showUpgrade = this.state.UpgradeAvailable &&
!this.state.DisplayError &&
!showConfig &&
!missingDependencies &&
!this.state.DownloadActive &&
!this.state.UpgradeDismissed;
let errorDisplay = null;
if (this.state.DisplayError) {
errorDisplay = (
<Modal critical>
<ErrorDetails closed={this.closeErrorDisplay}
critical={this.state.ErrorCritical}
error={this.state.Error}/>
</Modal>
);
}
let configDisplay = null;
if (showConfig) {
configDisplay = (
<Modal>
<Configuration closed={this.handleConfigClosed}
directory={Constants.DATA_LOCATIONS[this.props.platform]}
errorHandler={this.setErrorState}
storageType={this.state.ConfigStorageType}
version={selectedVersion} />
</Modal>
);
}
let dependencyDisplay = null;
if (missingDependencies && !this.state.DownloadActive) {
if (showDependencies) {
dependencyDisplay = (
<Modal>
<DependencyList allowDownload={!this.state.DownloadingDependency}
@@ -458,104 +614,100 @@ class App extends Component {
if (this.state.DownloadActive) {
downloadDisplay = (
<Modal>
<DownloadProgress progress={this.state.DownloadProgress}
display={this.state.DownloadName}/>
<DownloadProgress display={this.state.DownloadName}
progress={this.state.DownloadProgress}/>
</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) {
if (showUpgrade) {
upgradeDisplay = (
<Modal>
<UpgradeUI upgrade={this.handleUIDownload}
cancel={()=>this.setState({UpgradeDismissed: true})}/>
<UpgradeUI cancel={()=>this.setState({UpgradeDismissed: true})}
upgrade={this.handleUIDownload}/>
</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>);
let mainContent = [];
if (this.state.DisplayMainContent) {
let key = 0;
mainContent.push((
<div key={'rvd_' + key++}
style={{height: '44%'}}>
<ReleaseVersionDisplay disabled={this.state.DownloadActive || this.state.ExtractActive || this.state.MountsBusy}
downloadClicked={this.handleReleaseDownload}
downloadDisabled={!downloadEnabled}
installedVersion={this.state.InstalledVersion}
release={this.state.Release}
releaseChanged={this.handleReleaseChanged}
releaseExtracting={this.state.ExtractActive}
releaseTypes={this.state.ReleaseTypes}
version={this.state.Version}
versionAvailable={this.state.VersionAvailable}
versionChanged={this.handleVersionChanged}
versions={this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]]}/>
</div>
));
if (allowMount) {
mainContent.push((
<div key={'md_' + key++}
style={{height: '56%'}}>
<MountItems allowConfig={allowConfig}
autoMountChanged={this.handleAutoMountChanged}
autoMountProcessed={this.notifyAutoMountProcessed}
changed={this.handleMountLocationChanged}
configClicked={this.handleConfigClicked}
directory={Constants.DATA_LOCATIONS[this.props.platform]}
errorHandler={this.setErrorState}
hyperspace={this.state.Hyperspace}
mountsBusy={this.notifyMountsBusy}
platform={this.props.platform}
processAutoMount={!this.state.AutoMountProcessed}
sia={this.state.Sia}
version={this.state.InstalledVersion}/>
</div>
));
}
} else {
mainContent = <Loading/>
}
return (
<div styleName='App'>
{errorDisplay}
{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>
{configDisplay}
<div styleName='Container'>
<div styleName='Header'>
<Box>
<Grid>
<Text col={0}
colSpan={'remain'}
row={0}
rowSpan={'remain'}
text={'Repertory UI v' + this.props.version}
textAlign={'center'}
type={'Heading1'}/>
<UpgradeIcon
available={this.state.UpgradeAvailable}
clicked={()=>this.setState({UpgradeDismissed: false})}
col={dimensions => dimensions.columns - 6}
colSpan={5}
row={1}
rowSpan={remain=>remain - 2}/>
</Grid>
</Box>
</div>
<div styleName='Content'>
<Box dxStyle={{padding: '8px'}}>
{mainContent}
</Box>
</div>
</div>
</div>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 175 KiB

View File

@@ -0,0 +1,36 @@
.ConfigurationItem {
margin: 0;
padding: 0;
}
input.Input {
display: block;
margin: 0;
padding: 2px;
border-radius: var(--border_radius);
background: rgba(160, 160, 160, 0.1);
border: none;
box-shadow: none;
outline: none;
color: var(--text_color);
box-sizing: border-box;
}
.Select {
display: block;
margin: 0;
padding: 2px;
border-radius: var(--border_radius);
background: rgba(160, 160, 160, 0.1);
border: none;
box-shadow: none;
outline: none;
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,107 @@
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './ConfigurationItem.css';
export default CSSModules((props) => {
const handleChanged = (e) => {
const target = e.target;
if (target.type === 'checkbox') {
target.value = e.target.checked ? "true" : "false";
}
props.changed(target);
};
let data;
switch (props.template.type) {
case "bool":
data = <input checked={JSON.parse(props.value)}
onChange={e=>handleChanged(e)}
type={'checkbox'}/>;
break;
case "double":
data = <input min={0.0}
onChange={e=>handleChanged(e)}
step={"0.01"}
styleName='Input'
type={'number'}
value={parseFloat(props.value).toFixed(2)}/>;
break;
case "list":
const options = props.items.map((s, i) => {
return (
<option styleName='Option' key={i} value={s}>{s}</option>
);
});
data = (
<select onChange={e=>handleChanged(e)}
styleName='Select'
value={props.value}>
{options}
</select>
);
break;
case "string":
data = <input onChange={e=>handleChanged(e)}
styleName='Input'
type={'text'}
value={props.value}/>;
break;
case "uint8":
data = <input max={255}
min={0}
onChange={e=>handleChanged(e)}
styleName='Input'
type={'number'}
value={props.value}/>;
break;
case "uint16":
data = <input max={65535}
min={0}
onChange={e=>handleChanged(e)}
styleName='Input'
type={'number'}
value={props.value}/>;
break;
case "uint32":
data = <input max={4294967295}
min={0}
onChange={e=>handleChanged(e)}
styleName='Input'
type={'number'}
value={props.value}/>;
break;
case "uint64":
data = <input max={18446744073709551615}
min={0}
onChange={e=>handleChanged(e)}
styleName='Input'
type={'number'}
value={props.value}/>;
break;
default:
data = <div>{props.value}</div>;
}
return (
<div styleName='ConfigurationItem'>
<table cellPadding='2'
width='100%'>
<tbody>
<tr>
<td width='100%'>{props.label}</td>
<td>{data}</td>
</tr>
</tbody>
</table>
</div>
);
}, styles, {allowMultiple: true});

View File

@@ -8,15 +8,15 @@ export default CSSModules((props) => {
const items = props.dependencies.map((k, i)=> {
return (
<Dependency allowDownload={props.allowDownload}
download={k.download}
key={i}
name={k.display}
download={k.download}
onDownload={props.onDownload}/>
);
});
return (
<Box dxDark dxStyle={{width: '300px', height: 'auto', padding: '5px'}}>
<Box 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>

View File

@@ -6,7 +6,7 @@ import styles from './DownloadProgress.css';
export default CSSModules((props) => {
return (
<Box dxDark dxStyle={{width: '380px', height: 'auto', padding: '5px'}}>
<Box dxStyle={{width: '380px', height: 'auto', padding: '5px'}}>
<div style={{width: '100%', height: 'auto'}}>
<h1 style={{width: '100%', textAlign: 'center'}}>{'Downloading ' + props.display}</h1>
</div>

View File

@@ -0,0 +1,11 @@
.Heading {
color: var(--text_color_error);
text-align: center;
margin-bottom: 4px;
}
.Content {
max-height: 60vh;
overflow-y: auto;
margin-bottom: 8px;
}

View File

@@ -0,0 +1,18 @@
import React from 'react';
import Box from '../UI/Box/Box';
import Button from '../UI/Button/Button';
import CSSModules from 'react-css-modules';
import styles from './ErrorDetails.css';
export default CSSModules((props) => {
return (
<Box dxStyle={{padding: '8px'}}>
<h1 styleName='Heading'>Application Error</h1>
<div styleName='Content'>
<p>{props.error.toString()}</p>
</div>
<Button clicked={props.closed}>Dismiss</Button>
</Box>
);
}, styles, {allowMultiple: true});

View File

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

View File

@@ -4,49 +4,92 @@ import styles from './MountItem.css';
import DropDown from '../UI/DropDown/DropDown';
import Button from '../UI/Button/Button';
import Loader from 'react-loader-spinner';
import Text from '../UI/Text/Text';
import Grid from '../UI/Grid/Grid';
import configureImage from '../../assets/images/configure.png';
import RootElem from '../../hoc/RootElem/RootElem';
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 configButton = null;
let secondRow = 6;
if (props.allowConfig) {
configButton = (
<RootElem colSpan={4}
rowSpan={6}>
<img alt=''
height={'16px'}
onClick={props.configClicked}
src={configureImage}
style={{padding: 0, border: 0, margin: 0, cursor: 'pointer'}}
width={'16px'}/>
</RootElem>
);
}
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>;
let inputColumnSpan;
let inputControl = null;
if (props.platform === 'win32') {
inputColumnSpan = 20;
inputControl = <DropDown changed={props.changed}
colSpan={inputColumnSpan}
disabled={!props.allowMount || props.mounted}
items={props.items}
row={secondRow}
rowSpan={7}
selected={props.items.indexOf(props.location)}/>;
} else {
actionDisplay = <Loader color={'var(--heading_text_color)'}
height='24px'
width='24px'
type='Circles'/>;
inputColumnSpan = 60;
inputControl = (
<RootElem colSpan={inputColumnSpan}
row={secondRow}
rowSpan={7}>
<input disabled={!props.allowMount || props.mounted}
onChange={props.changed}
type={'text'}
value={props.location}/>
</RootElem>);
}
const buttonDisplay = props.allowMount ?
(props.mounted ? 'Unmount' : 'Mount') :
<Loader color={'var(--heading_text_color)'}
height='19px'
type='Circles'
width='19px'/>;
const actionsDisplay = (
<Button clicked={()=>props.clicked(props.title, !props.mounted, props.location, props.pid)}
col={inputColumnSpan + 2}
colSpan={20}
disabled={!props.allowMount}
row={secondRow}
rowSpan={7}>
{buttonDisplay}
</Button>);
const autoMountControl = (
<RootElem col={inputColumnSpan + 23}
colSpan={26}
row={secondRow}
rowSpan={7}>
<input checked={props.autoMount}
onChange={props.autoMountChanged}
type='checkbox'/>Auto-mount
</RootElem>
);
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>
<Grid>
{configButton}
<Text
col={configButton ? 6 : 0}
rowSpan={5}
text={props.title}
type={'Heading1'}/>
{inputControl}
{actionsDisplay}
{autoMountControl}
</Grid>
);
}, styles, {allowMultiple: true});

View File

@@ -0,0 +1,74 @@
import React from 'react';
import styles from './ReleaseVersionDisplay.css';
import CSSModules from 'react-css-modules';
import DropDown from '../UI/DropDown/DropDown';
import Grid from '../UI/Grid/Grid';
import Text from '../UI/Text/Text';
import Button from '../UI/Button/Button';
import UpgradeIcon from '../UpgradeIcon/UpgradeIcon';
export default CSSModules((props) => {
let optionsDisplay = null;
if (props.releaseExtracting) {
optionsDisplay = <Text align='center'
row={13}
rowSpan={7}
colSpan={'remain'}
text={'Activating <' + props.installedVersion + '>'}/>
} else {
optionsDisplay = <Button clicked={props.downloadClicked}
colSpan={20}
disabled={props.downloadDisabled}
row={13}
rowSpan={7}>Install</Button>;
}
return (
<Grid>
<Text colSpan={columns=>columns / 3}
rowSpan={4}
text={'Release'}
textAlign={'left'}
type={'Heading2'}/>
<DropDown changed={props.releaseChanged}
colSpan={remain=>remain / 3 - 1}
disabled={props.disabled}
items={props.releaseTypes}
row={5}
rowSpan={7}
selected={props.release}/>
<Text col={dimensions => dimensions.columns / 3}
colSpan={remain=>remain / 2}
rowSpan={4}
text={'Version'}
textAlign={'left'}
type={'Heading2'}/>
<UpgradeIcon available={props.versionAvailable}
col={dimensions => ((dimensions.columns / 3) * 2) - 6}
colSpan={4}
release
rowSpan={4}/>
<DropDown changed={props.versionChanged}
col={dimensions => dimensions.columns / 3}
colSpan={remain=>remain / 2 - 1}
disabled={props.disabled}
items={props.versions}
row={5}
rowSpan={7}
selected={props.version}/>
<Text col={dimensions => (dimensions.columns / 3) * 2}
colSpan={'remain'}
rowSpan={4}
text={'Installed'}
textAlign={'left'}
type={'Heading2'}/>
<Text col={dimensions => (dimensions.columns / 3) * 2}
colSpan={'remain'}
row={5}
rowSpan={7}
text={props.installedVersion}
textAlign={'left'}/>
{optionsDisplay}
</Grid>
);
}, styles, {allowMultiple: true});

View File

@@ -3,7 +3,8 @@ import CSSModules from 'react-css-modules';
import styles from './Box.css';
export default CSSModules((props) => {
const styleList = ['Box'];
const styleList = [];
styleList.push('Box');
if (props.dxDark) {
styleList.push('Darker');
}

View File

@@ -1,7 +1,6 @@
.Button {
display: block;
text-align: center;
margin: 0;
padding: 4px;
outline: 0;
color: var(--text_color);
@@ -10,7 +9,10 @@
border: none;
text-decoration: none;
text-outline: none;
width: 70px;
vertical-align: center;
height: 100%;
width: 100%;
overflow: hidden;
}
.Button:hover:enabled {
@@ -20,7 +22,7 @@
}
.Button:hover:disabled {
cursor: default;
cursor: no-drop;
}
.Button:active,

View File

@@ -1,5 +1,7 @@
.DropDown {
display: block;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
@@ -22,3 +24,16 @@
border-color: rgba(10, 10, 20, 0.9);
color: var(--text_color);
}
.Select:hover:enabled {
cursor: pointer;
}
.Select:hover:disabled {
cursor: no-drop;
}
.Select:active,
.Select.active {
cursor: pointer;
}

View File

@@ -1,6 +1,6 @@
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './DropDown.css';
import CSSModules from 'react-css-modules';
export default CSSModules((props) => {
const options = props.items.map((s, i) => {

View File

@@ -0,0 +1,18 @@
.Grid {
margin: 0;
padding: 0;
display: grid;
box-sizing: border-box;
height: 100%;
width: 100%;
overflow-y: auto;
overflow-x: auto;
}
.GridOwner {
padding: 0;
margin: 0;
width: 100%;
height: 100%;
box-sizing: border-box;
}

View File

@@ -0,0 +1,126 @@
import React, {Component} from 'react';
import CSSModules from 'react-css-modules';
import styles from './Grid.css';
import GridComponent from './GridComponent/GridComponent';
export default CSSModules(class extends Component {
constructor(props) {
super(props);
window.addEventListener("resize", this.updateSizeAsync);
}
state = {
calculated: false,
dimensions: {
columns: 0,
rows: 0
}
};
calculateDimensions = (size) => {
return {
columns: Math.floor(size.width / 4),
rows: Math.floor(size.height / 4)
};
};
getSize = () => {
const elem = this.refs.GridOwner;
return {
height: elem ? elem.clientHeight : 0,
width: elem ? elem.clientWidth : 0
};
};
updateSize = () => {
const state = {
...this.state
};
const size = this.getSize();
const dimensions = this.calculateDimensions(size);
if (state.dimensions !== dimensions) {
this.setState({
calculated: true,
dimensions: dimensions
})
}
};
updateSizeAsync = () => {
return new Promise((done) => {
this.updateSize();
done();
});
};
componentDidMount = () => {
this.updateSizeAsync();
};
componentWillUnmount = () => {
window.removeEventListener("resize", this.updateSizeAsync);
};
render() {
let children = null;
const dimensions = this.state.dimensions;
if (this.state.calculated) {
children = React.Children.map(this.props.children, (child, i) => {
if (child) {
let row = child.props.row || 0;
if (typeof(row) === 'function') {
row = row(dimensions);
}
let col = child.props.col || 0;
if (typeof(col) === 'function') {
col = col(dimensions);
}
let rowSpan = child.props.rowSpan;
if (typeof(rowSpan) === 'function') {
rowSpan = rowSpan(dimensions.rows - row, dimensions.rows);
}
let colSpan = child.props.colSpan;
if (typeof(colSpan) === 'function') {
colSpan = colSpan(dimensions.columns - col, dimensions.columns);
}
rowSpan = rowSpan ? (rowSpan === 'remain' ? (dimensions.rows - row) : rowSpan) : null;
colSpan = colSpan ? (colSpan === 'remain' ? dimensions.columns - col : colSpan) : null;
return <GridComponent
row={row}
col={col}
rowSpan={rowSpan}
colSpan={colSpan}
key={'gc_' + i}>{child}</GridComponent>;
} else {
return null;
}
})
.filter(i => i !== null);
}
const style = {
style: {
gridTemplateColumns: '4px '.repeat(dimensions.columns).trim(),
gridTemplateRows: '4px '.repeat(dimensions.rows).trim(),
gridAutoColumns: '4px',
gridAutoRows: '4px'
}
};
return (
<div
ref='GridOwner'
styleName='GridOwner'>
<div styleName='Grid' {...style}>
{children}
</div>
</div>
)
};
}, styles, {allowMultiple: true});

View File

@@ -0,0 +1,7 @@
.GridComponent {
padding: 0;
margin: 0;
display: flex;
justify-content: center;
align-items: center;
}

View File

@@ -0,0 +1,21 @@
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './GridComponent.css';
export default CSSModules((props) => {
const style = {
style: {
gridRowStart: Math.floor(props.row + 1),
gridRowEnd: 'span ' + Math.floor(props.rowSpan || 1),
gridColumnStart: Math.floor(props.col + 1),
gridColumnEnd: 'span ' + Math.floor(props.colSpan || 1)
}
};
return (
<div styleName='GridComponent' {...style}>
{props.children}
</div>
);
}, styles, {allowMultiple: true});

View File

@@ -0,0 +1,18 @@
.Loading {
margin: 0;
padding: 0;
height: inherit;
width: 100%;
box-sizing: border-box;
}
.Content {
margin: 0;
padding: 0;
position: relative;
top: 50%; left: 50%;
transform: translate(-50%,-50%);
width: 28px;
height: 28px;
box-sizing: border-box;
}

View File

@@ -0,0 +1,17 @@
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './Loading.css'
import Loader from 'react-loader-spinner';
export default CSSModules((props) => {
return (
<div
styleName='Loading'>
<div styleName='Content'>
<Loader color={'var(--heading_text_color)'}
height='28px'
width='28px'
type='ThreeDots'/>
</div>
</div>);
}, styles, {allowMultiple: true});

View File

@@ -17,3 +17,11 @@
transform: translate(-50%, -50%);
z-index: 2001;
}
.Modal.Critical {
z-index: 2100;
}
.Content.Critical {
z-index: 2101;
}

View File

@@ -3,11 +3,20 @@ import CSSModules from 'react-css-modules';
import styles from './Modal.css'
export default CSSModules((props) => {
let modalStyles = [];
let contentStyles = [];
modalStyles.push('Modal');
contentStyles.push('Content');
if (props.critical) {
modalStyles.push('Critical');
contentStyles.push('Critical');
}
return (
<div
styleName='Modal'
styleName={modalStyles.join(' ')}
onClick={props.clicked}>
<div styleName='Content'>
<div styleName={contentStyles.join(' ')}>
{props.children}
</div>
</div>);

View File

@@ -0,0 +1,39 @@
.TextOwner {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-content: center;
flex-direction: column;
}
.Text {
display: inline-block;
padding: 0;
margin: 0;
text-decoration: none;
text-align: center;
vertical-align: center;
color: var(--text_color);
}
.Heading1 {
font-weight: bold;
color: var(--heading_text_color);
}
.Heading2 {
font-weight: bold;
color: var(--heading_other_text_color);
}
.Heading3 {
font-weight: bold;
color: var(--heading_other_text_color);
}
.AltTextColor {
color: var(--heading_other_text_color);
}

View File

@@ -0,0 +1,28 @@
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './Text.css';
export default CSSModules((props) => {
const styleList = [];
styleList.push('Text');
if (props.type) {
styleList.push(props.type);
}
let style = {...props.style};
if (props.textAlign) {
style['textAlign'] = props.textAlign.toLowerCase();
}
const text = (
<div
styleName={styleList.join(' ')}
style={style}>{props.text}
</div>);
return props.noOwner ? text : (
<div styleName={'TextOwner'}>
{text}
</div>);
}, styles, {allowMultiple: true});

View File

@@ -1,14 +1,21 @@
.UpgradeIcon {
display: block;
margin: 0;
.Owner {
padding: 0;
width: 20px;
height: 20px;
margin: 0;
width: 100%;
height: 100%;
border: 0;
box-sizing: border-box;
cursor: pointer;
}
div.UpgradeIcon {
cursor: default;
}
.UpgradeIcon {
display: block;
margin: 0;
padding: 0;
object-fit: contain;
border: 0;
max-width: 100%;
max-height: 100%;
box-sizing: border-box;
opacity: 0.65;
}

View File

@@ -1,10 +1,29 @@
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './UpgradeIcon.css';
import availableImage from '../../assets/images/upgrade_available.png';
import availableImage from '../../assets/images/release_available.png';
import ReactTooltip from 'react-tooltip';
export default CSSModules((props) => {
return props.available ?
<img alt='' styleName='UpgradeIcon' src={availableImage} onClick={props.clicked}/> :
<div styleName='UpgradeIcon'/> ;
let placement = 'left';
let toolTipText = 'UI Upgrade Available';
if (props.release) {
placement='bottom';
toolTipText = 'New Release Available';
}
return props
.available ?
(
<div styleName='Owner'>
<p data-tip='' data-for={placement}>
<img alt=''
onClick={props.clicked}
src={availableImage}
styleName='UpgradeIcon'/>
</p>
<ReactTooltip id={placement} place={placement}>{toolTipText}</ReactTooltip>
</div>
)
: null;
}, styles, {allowMultiple: true});

View File

@@ -6,22 +6,22 @@ import styles from './UpgradeUI.css';
export default CSSModules((props) => {
return (
<Box dxDark dxStyle={{width: '180px', height: 'auto', padding: '5px'}}>
<Box 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>
<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>);

View File

@@ -1,7 +1,62 @@
export const RELEASES_URL = 'https://bitbucket.org/blockstorage/repertory/raw/master/releases.json';
export const DATA_LOCATIONS = {
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.RELEASES_URL = 'https://bitbucket.org/blockstorage/repertory/raw/master/releases.json';
exports.DATA_LOCATIONS = {
linux: '~/.local/repertory/ui',
darwin: '~/Library/Application Support/repertory/ui',
win32: '%LOCALAPPDATA%\\repertory\\ui',
win32: '%LOCALAPPDATA%\\repertory\\ui'
};
export const UI_RELEASES_URL = 'https://bitbucket.org/blockstorage/repertory-ui/raw/master/releases.json';
exports.UI_RELEASES_URL = 'https://bitbucket.org/blockstorage/repertory-ui/raw/master/releases.json';
exports.IPC_Check_Installed = 'check_installed';
exports.IPC_Check_Installed_Reply = 'check_installed_reply';
exports.IPC_Delete_File = 'delete_file';
exports.IPC_Detect_Mounts = 'detect_mounts';
exports.IPC_Detect_Mounts_Reply = 'detect_mounts_reply';
exports.IPC_Download_File = 'download_file';
exports.IPC_Download_File_Complete = 'download_file_complete';
exports.IPC_Download_File_Progress = 'download_file_progress';
exports.IPC_Extract_Release = 'extract_release';
exports.IPC_Extract_Release_Complete = 'extract_release_complete';
exports.IPC_Get_Config = 'get_config';
exports.IPC_Get_Config_Reply = 'get_config_reply';
exports.IPC_Get_Config_Template = 'get_config_template';
exports.IPC_Get_Config_Template_Reply = 'get_config_template_reply';
exports.IPC_Get_Platform = 'get_platform';
exports.IPC_Get_Platform_Reply = 'get_platform_reply';
exports.IPC_Get_State = 'get_state';
exports.IPC_Get_State_Reply = 'get_state_reply';
exports.IPC_Grab_Releases = 'grab_releases';
exports.IPC_Grab_Releases_Reply = 'grab_releases_reply';
exports.IPC_Grab_UI_Releases = 'grab_ui_releases';
exports.IPC_Grab_UI_Releases_Reply = 'grab_ui_releases_reply';
exports.IPC_Install_Dependency = 'install_dependency';
exports.IPC_Install_Dependency_Reply = 'install_dependency_reply';
exports.IPC_Install_Upgrade = 'install_upgrade';
exports.IPC_Install_Upgrade_Reply = 'install_upgrade_reply';
exports.IPC_Mount_Drive = 'mount_drive';
exports.IPC_Mount_Drive_Reply = 'mount_drive_reply';
exports.IPC_Save_State = 'save_state';
exports.IPC_Set_Config_Values = 'set_config_values';
exports.IPC_Set_Config_Values_Reply = 'set_config_values_reply';
exports.IPC_Shutdown = 'shutdown';
exports.IPC_Unmount_Drive = 'unmount_drive';
exports.IPC_Unmount_Drive_Reply = 'unmount_drive_reply';

View File

@@ -0,0 +1,6 @@
.Configuration {
width: 90vw;
height: 90vh;
padding: 4px;
margin: 0;
}

View File

@@ -0,0 +1,295 @@
import React, {Component} from 'react';
import styles from './Configuration.css';
import Box from '../../components/UI/Box/Box';
import Button from '../../components/UI/Button/Button';
import ConfigurationItem from '../../components/ConfigurationItem/ConfigurationItem';
import CSSModules from 'react-css-modules';
import Modal from '../../components/UI/Modal/Modal';
const Constants = require('../../constants');
let ipcRenderer = null;
if (!process.versions.hasOwnProperty('electron')) {
ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null);
}
class Configuration extends Component {
constructor(props) {
super(props);
if (ipcRenderer) {
ipcRenderer.on(Constants.IPC_Get_Config_Template_Reply, this.onGetConfigTemplateReply);
ipcRenderer.on(Constants.IPC_Get_Config_Reply, this.onGetConfigReply);
ipcRenderer.on(Constants.IPC_Set_Config_Values_Reply, this.onSetConfigValuesReply);
ipcRenderer.send(Constants.IPC_Get_Config_Template, {
Directory: this.props.directory,
StorageType: this.props.storageType,
Version: this.props.version,
});
}
}
state = {
ChangedItems: [],
ChangedObjectLookup: null,
ObjectLookup: {},
OriginalItemList: [],
OriginalObjectLookup: {},
ItemList: [],
Saving: false,
ShowAdvanced: false,
Template: {}
};
checkSaveRequired = () => {
const changedItems = [];
let i = 0;
for (const item of this.state.ItemList) {
if (this.state.OriginalItemList[i++].value !== item.value) {
changedItems.push(item);
}
}
let changedObjectLookup = null;
for (const key of Object.keys(this.state.ObjectLookup)) {
const changedObjectItems = [];
let j = 0;
for (const item of this.state.ObjectLookup[key]) {
if (this.state.OriginalObjectLookup[key][j++].value !== item.value) {
changedObjectItems.push(item);
}
}
if (changedObjectItems.length > 0) {
if (changedObjectLookup === null) {
changedObjectLookup = {};
}
changedObjectLookup[key] = changedObjectItems;
}
}
if ((changedItems.length > 0) || changedObjectLookup) {
this.setState({
ChangedItems: changedItems,
ChangedObjectLookup: changedObjectLookup,
});
} else {
this.props.closed();
}
};
componentWillUnmount = () => {
if (ipcRenderer) {
ipcRenderer.removeListener(Constants.IPC_Get_Config_Reply, this.onGetConfigReply);
ipcRenderer.removeListener(Constants.IPC_Get_Config_Template_Reply, this.onGetConfigTemplateReply);
ipcRenderer.removeListener(Constants.IPC_Set_Config_Values_Reply, this.onSetConfigValuesReply);
}
};
createItemList = (config, template) => {
const objectList = [];
const itemList = Object
.keys(config)
.map(key => {
return {
advanced: template[key] ? template[key].advanced : false,
label: key,
value: config[key],
};
})
.filter(i=> {
let ret = template[i.label];
if (ret && (template[i.label].type === 'object')) {
objectList.push(i);
ret = false;
}
return ret;
});
return {
ObjectList: objectList,
ItemList: itemList,
}
};
handleItemChanged = (target, idx) => {
const itemList = [
...this.state.ItemList
];
itemList[idx].value = target.value.toString();
this.setState({
ItemList: itemList
});
};
handleObjectItemChanged = (target, name, idx) => {
const itemList = [
...this.state.ObjectLookup[name]
];
const objectLookup = {
...this.state.ObjectLookup,
};
itemList[idx].value = target.value.toString();
objectLookup[name] = itemList;
this.setState({
ObjectLookup: objectLookup,
});
};
onGetConfigReply = (event, arg) => {
if (arg.data.Success) {
const list = this.createItemList(arg.data.Config, this.state.Template);
const itemListCopy = JSON.parse(JSON.stringify(list.ItemList));
let objectLookup = {};
for (const obj of list.ObjectList) {
const list2 = this.createItemList(obj.value, this.state.Template[obj.label].template);
objectLookup[obj.label] = list2.ItemList;
}
const objectLookupCopy = JSON.parse(JSON.stringify(objectLookup));
this.setState({
ItemList: list.ItemList,
ObjectLookup: objectLookup,
OriginalItemList: itemListCopy,
OriginalObjectLookup: objectLookupCopy,
});
} else {
this.props.errorHandler(arg.data.Error);
}
};
onGetConfigTemplateReply = (event, arg) => {
if (arg.data.Success) {
this.setState({
Template: arg.data.Template,
});
ipcRenderer.send(Constants.IPC_Get_Config, {
Directory: this.props.directory,
StorageType: this.props.storageType,
Version: this.props.version,
});
} else {
this.props.errorHandler(arg.data.Error, () => {
this.props.closed();
});
}
};
onSetConfigValuesReply = () => {
this.props.closed();
};
saveAndClose = () => {
if (ipcRenderer) {
this.setState({
Saving: true,
});
const changedItems = [];
for (const item of this.state.ChangedItems) {
changedItems.push({
Name: item.label,
Value: item.value,
});
}
if (this.state.ChangedObjectLookup) {
for (const key of Object.keys(this.state.ChangedObjectLookup)) {
for (const item of this.state.ChangedObjectLookup[key]) {
changedItems.push({
Name: key + '.' + item.label,
Value: item.value,
});
}
}
}
ipcRenderer.send(Constants.IPC_Set_Config_Values, {
Directory: this.props.directory,
Items: changedItems,
StorageType: this.props.storageType,
Version: this.props.version,
});
}
};
render() {
let confirmSave = null;
if ((this.state.ChangedItems.length > 0) || this.state.ChangedObjectLookup) {
confirmSave = (
<Modal>
<Box dxStyle={{width: '40vw', padding: '4px'}}>
<h1 style={{width: '100%', textAlign: 'center'}}>Save Changes?</h1>
<table width='100%'><tbody>
<tr>
<td align='center' width='50%'><Button clicked={this.saveAndClose} disabled={this.state.Saving}>Yes</Button></td>
<td align='center' width='50%'><Button clicked={this.props.closed} disabled={this.state.Saving}>No</Button></td>
</tr>
</tbody></table>
</Box>
</Modal>
);
}
const configurationItems = this.state.ItemList
.map((k, i) => {
return (
((this.state.ShowAdvanced && k.advanced) || !k.advanced) ?
<ConfigurationItem advanced={k.advanced}
changed={e=>this.handleItemChanged(e, i)}
items={this.state.Template[k.label].items}
key={i}
label={k.label}
template={this.state.Template[k.label]}
value={k.value}/> :
null)
});
let objectItems = [];
for (const key of Object.keys(this.state.ObjectLookup)) {
objectItems.push((
<div key={key}>
<h1>{key}</h1>
<div>
{
this.state.ObjectLookup[key].map((k, i) => {
return (
((this.state.ShowAdvanced && k.advanced) || !k.advanced) ?
<ConfigurationItem advanced={k.advanced}
changed={e=>this.handleObjectItemChanged(e, key, i)}
items={this.state.Template[key].template[k.label].items}
key={i}
label={k.label}
template={this.state.Template[key].template[k.label]}
value={k.value}/> :
null)
})
}
</div>
</div>
));
}
return (
<div styleName='Configuration'>
{confirmSave}
<Box dxDark dxStyle={{padding: '8px'}}>
<div style={{float: 'right', margin: 0, padding: 0, marginTop: '-4px', boxSizing: 'border-box', display: 'block'}}>
<b style={{cursor: 'pointer'}}
onClick={this.checkSaveRequired}>X</b>
</div>
<h1 style={{width: '100%', textAlign: 'center'}}>{this.props.storageType + ' Configuration'}</h1>
<div style={{overflowY: 'auto', height: '90%'}}>
{objectItems}
{(configurationItems.length > 0) ? <h1>Settings</h1> : null}
{configurationItems}
</div>
</Box>
</div>
);
}
}
export default CSSModules(Configuration, styles, {allowMultiple: true});

View File

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

View File

@@ -4,6 +4,8 @@ import CSSModules from 'react-css-modules';
import styles from './MountItems.css';
import MountItem from '../../components/MountItem/MountItem';
const Constants = require('../../constants');
let ipcRenderer = null;
if (!process.versions.hasOwnProperty('electron')) {
ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null);
@@ -13,64 +15,9 @@ 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();
});
ipcRenderer.on(Constants.IPC_Detect_Mounts_Reply, this.onDetectMountsReply);
ipcRenderer.on(Constants.IPC_Mount_Drive_Reply, this.onMountDriveReply);
ipcRenderer.on(Constants.IPC_Unmount_Drive_Reply, this.onUnmountDriveReply);
this.detectMounts();
}
@@ -91,8 +38,17 @@ class MountItems extends Component {
},
};
componentWillUnmount = () => {
if (ipcRenderer) {
ipcRenderer.removeListener(Constants.IPC_Detect_Mounts_Reply, this.onDetectMountsReply);
ipcRenderer.removeListener(Constants.IPC_Mount_Drive_Reply, this.onMountDriveReply);
ipcRenderer.removeListener(Constants.IPC_Unmount_Drive_Reply, this.onUnmountDriveReply);
}
};
detectMounts = ()=> {
ipcRenderer.send('detect_mounts', {
this.props.mountsBusy(true);
ipcRenderer.send(Constants.IPC_Detect_Mounts, {
Directory: this.props.directory,
Version: this.props.version,
});
@@ -117,15 +73,17 @@ class MountItems extends Component {
[storageType]: state,
});
this.props.mountsBusy(true);
if (mount) {
ipcRenderer.send('mount_drive', {
ipcRenderer.send(Constants.IPC_Mount_Drive, {
Directory: this.props.directory,
Location: location,
StorageType: storageType,
Version: this.props.version,
});
} else {
ipcRenderer.send('unmount_drive', {
ipcRenderer.send(Constants.IPC_Unmount_Drive, {
Directory: this.props.directory,
Location: location,
PID: pid,
@@ -136,6 +94,69 @@ class MountItems extends Component {
}
};
onDetectMountsReply = (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,
});
this.props.mountsBusy(hs.Mounted || sia.Mounted);
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();
} else {
this.props.errorHandler(arg.data.Error);
}
};
onMountDriveReply = (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();
};
onUnmountDriveReply = (event, arg) => {
this.detectMounts();
};
performAutoMount = ()=> {
if (this.props.processAutoMount) {
this.props.autoMountProcessed();
@@ -155,28 +176,33 @@ class MountItems extends Component {
render() {
return (
<div styleName='MountItems'>
<MountItem allowMount={this.state.Hyperspace.AllowMount}
<MountItem allowConfig={this.props.allowConfig}
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}
configClicked={()=>this.props.configClicked('Hyperspace')}
items={this.state.Hyperspace.DriveLetters}
location={this.props.hyperspace.MountLocation}
mounted={this.state.Hyperspace.Mounted}
pid={this.state.Hyperspace.PID}
platform={this.props.platform}
title={'Hyperspace'}/>
<div style={{paddingTop: '8px'}}/>
<MountItem allowConfig={this.props.allowConfig}
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}/>
configClicked={()=>this.props.configClicked('Sia')}
items={this.state.Sia.DriveLetters}
location={this.props.sia.MountLocation}
mounted={this.state.Sia.Mounted}
pid={this.state.Sia.PID}
platform={this.props.platform}
title={'Sia'}/>
</div>);
}
}

View File

@@ -0,0 +1,9 @@
import React from 'react';
export default (props) => {
return (
<div style={{margin: 0, padding: 0}} {...props}>
{props.children}
</div>
)
};

View File

@@ -2,22 +2,23 @@
--border_radius: 4px;
--control_background: rgba(150, 150, 190, .15);
--control_background_hover: rgba(150, 150, 190, .3);
--control_background_hover: rgba(150, 150, 190, .35);
--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);
--control_transparent_background: rgba(40, 40, 55, 0.45);
--control_dark_transparent_background: rgba(15, 15, 15, 0.8);
--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_error: rgba(203, 120, 120, 0.8);
--heading_text_color: rgba(161, 190, 219, 0.7);
--heading_other_text_color: var(--heading_text_color);
--text_color_transition: color 0.3s;
}
* {
font-family: 'Nunito', sans-serif;
font-size: 15px;
font-size: 5vh;
}
*::-moz-focus-inner {
@@ -61,3 +62,27 @@ h1 {
h2, h3 {
color: var(--heading_other_text_color);
}
p {
margin: 0;
padding: 0;
font-weight: normal;
color: var(--text_color);
}
.scrollable-content {
overflow-x: hidden;
overflow-y: scroll;
}
.scrollable-content, ::-webkit-scrollbar {
width: 10px;
}
.scrollable-content, ::-webkit-scrollbar * {
background: transparent;
}
.scrollable-content, ::-webkit-scrollbar-thumb {
background: rgba(90, 90, 90, 0.6) !important;
}

View File

@@ -1,10 +1,20 @@
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';
import registerServiceWorker from './registerServiceWorker';
const Constants = require('./constants');
if (!process.versions.hasOwnProperty('electron')) {
const ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null);
if (ipcRenderer) {
ipcRenderer.on(Constants.IPC_Get_Platform_Reply, (event, arg) => {
ReactDOM.render(<App platform={arg.data} version={packageJson.version}/>, document.getElementById('root'));
registerServiceWorker();
});
ipcRenderer.send(Constants.IPC_Get_Platform);
}
}
ReactDOM.render(<App version={packageJson.version}/>, document.getElementById('root'));
registerServiceWorker();