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/ build/
chrome_data/ chrome_data/
dist/ 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 # 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) ### ### 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. Repertory allows you to mount Hyperspace or Sia blockchain storage solutions via FUSE on Linux/OS X or via WinFSP on Windows.
# Downloads # # Downloads #

View File

@@ -2,7 +2,7 @@
const {app, BrowserWindow, Tray, nativeImage, Menu} = require('electron'); const {app, BrowserWindow, Tray, nativeImage, Menu} = require('electron');
const {ipcMain} = require('electron'); const {ipcMain} = require('electron');
const Constants = require('./src/constants');
const path = require('path'); const path = require('path');
const url = require('url'); const url = require('url');
require('electron-debug')(); 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 // 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. // be closed automatically when the JavaScript object is garbage collected.
let mainContextWindow;
let mainWindow; let mainWindow;
let mainWindowTray; let mainWindowTray;
let mountedPIDs = []; let mountedPIDs = [];
function createWindow() { function createWindow() {
// Create the browser window. // 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({ mainWindow = new BrowserWindow({
width: 425, width: 425,
height: height, height: height,
resizable: false, resizable: false,
title: 'Repertory UI',
webPreferences: { webPreferences: {
webSecurity: !process.env.ELECTRON_START_URL webSecurity: !process.env.ELECTRON_START_URL
} }
@@ -46,14 +48,18 @@ function createWindow() {
mainWindow = null 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({ const autoLauncher = new AutoLaunch({
name: 'Repertory UI', 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 image = nativeImage.createFromPath(path.join(__dirname, '/build/logo.png'));
const contextMenu = Menu.buildFromTemplate([ mainContextWindow = Menu.buildFromTemplate([
{ {
label: 'Visible', type: 'checkbox', click(item) { label: 'Visible', type: 'checkbox', click(item) {
if (item.checked) { if (item.checked) {
@@ -75,17 +81,26 @@ function createWindow() {
autoLauncher.disable(); autoLauncher.disable();
} }
} }
},
{
type: 'separator'
},
{
label: 'Exit', click(item) {
app.quit();
}
} }
]); ]);
contextMenu.items[0].checked = true; mainContextWindow.items[0].checked = true;
autoLauncher.isEnabled() autoLauncher
.isEnabled()
.then((enabled) => { .then((enabled) => {
contextMenu.items[1].checked = enabled; mainContextWindow.items[1].checked = enabled;
mainWindowTray = new Tray(image); mainWindowTray = new Tray(image);
mainWindowTray.setToolTip('Repertory UI'); mainWindowTray.setToolTip('Repertory UI');
mainWindowTray.setContextMenu(contextMenu) mainWindowTray.setContextMenu(mainContextWindow)
}) })
.catch(() => { .catch(() => {
app.quit(); app.quit();
@@ -99,6 +114,10 @@ if (!instanceLock) {
} else { } else {
app.on('second-instance', () => { app.on('second-instance', () => {
if (mainWindow) { if (mainWindow) {
mainWindow.show();
if (mainContextWindow) {
mainContextWindow.items[0].checked = true;
}
if (mainWindow.isMinimized()) { if (mainWindow.isMinimized()) {
mainWindow.restore(); 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 dataDirectory = helpers.resolvePath(data.Directory);
const destination = path.join(dataDirectory, data.Version); const destination = path.join(dataDirectory, data.Version);
helpers helpers
@@ -136,27 +165,20 @@ ipcMain.on('check_installed', (event, data) => {
exists = fs.existsSync(destination) && fs.lstatSync(destination).isDirectory(); exists = fs.existsSync(destination) && fs.lstatSync(destination).isDirectory();
} catch (e) { } catch (e) {
} }
event.sender.send('check_installed_reply', { standardIPCReply(event, Constants.IPC_Check_Installed_Reply, {
data: {
Dependencies: dependencies, Dependencies: dependencies,
Exists: exists, Exists: exists,
Success: true,
Version: data.Version, Version: data.Version,
}
}); });
}).catch((e) => { }).catch(error => {
event.sender.send('check_installed_reply', { standardIPCReply(event, Constants.IPC_Check_Installed_Reply, {
data: {
Dependencies: [], Dependencies: [],
Error: e,
Success: false,
Version: data.Version, Version: data.Version,
} }, error);
});
}); });
}); });
ipcMain.on('delete_file', (event, data) => { ipcMain.on(Constants.IPC_Delete_File, (event, data) => {
try { try {
if (fs.existsSync(data.FilePath)) { if (fs.existsSync(data.FilePath)) {
fs.unlinkSync(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 = { let driveLetters = {
Hyperspace: [], Hyperspace: [],
Sia: [], 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); const dataDirectory = helpers.resolvePath(data.Directory);
helpers helpers
.detectRepertoryMounts(dataDirectory, data.Version) .detectRepertoryMounts(dataDirectory, data.Version)
@@ -213,201 +252,222 @@ ipcMain.on('detect_mounts', (event, data) => {
siaLocation = siaLocation.toUpperCase(); siaLocation = siaLocation.toUpperCase();
grabDriveLetters(hsLocation, siaLocation); grabDriveLetters(hsLocation, siaLocation);
} }
event.sender.send('detect_mounts_reply', { setImage(hsLocation, siaLocation);
data: { standardIPCReply(event, Constants.IPC_Detect_Mounts_Reply, {
DriveLetters: driveLetters, DriveLetters: driveLetters,
Locations: { Locations: {
Hyperspace: hsLocation, Hyperspace: hsLocation,
Sia: siaLocation, Sia: siaLocation,
}, },
Success: true,
PIDS: { PIDS: {
Hyperspace: results.Hyperspace.PID, Hyperspace: results.Hyperspace.PID,
Sia: results.Sia.PID, Sia: results.Sia.PID,
} }
}
}); });
}) })
.catch((err) => { .catch(error => {
grabDriveLetters('', ''); grabDriveLetters('', '');
setImage('', '');
event.sender.send('detect_mounts_reply', { standardIPCReply(event, Constants.IPC_Detect_Mounts_Reply, {
data: {
DriveLetters: driveLetters, DriveLetters: driveLetters,
Error: err, }, error);
Success: false,
}
});
}); });
}); });
ipcMain.on('download_file', (event, data) => { ipcMain.on(Constants.IPC_Download_File, (event, data) => {
const dataDirectory = helpers.resolvePath(data.Directory); const dataDirectory = helpers.resolvePath(data.Directory);
const destination = path.join(dataDirectory, data.Filename); const destination = path.join(dataDirectory, data.Filename);
helpers.downloadFile(data.URL, destination, (progress) => { helpers.downloadFile(data.URL, destination, (progress) => {
event.sender.send('download_file_progress', { standardIPCReply(event, Constants.IPC_Download_File_Progress, {
data: {
Destination: destination, Destination: destination,
Progress: progress, Progress: progress,
URL: data.URL, URL: data.URL,
}
}); });
}, (success, err) => { }, error => {
event.sender.send('download_file_complete', { standardIPCReply(event, Constants.IPC_Download_File_Complete, {
data: {
Destination: destination, Destination: destination,
Error: err,
Success: success,
URL: data.URL, 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 dataDirectory = helpers.resolvePath(data.Directory);
const destination = path.join(dataDirectory, data.Version); const destination = path.join(dataDirectory, data.Version);
helpers.mkDirByPathSync(destination); helpers.mkDirByPathSync(destination);
const stream = fs.createReadStream(data.Source); const stream = fs.createReadStream(data.Source);
stream.pipe(unzip.Extract({ path: destination })) stream
.on('error', (e) => { .pipe(unzip.Extract({ path: destination }))
.on('error', error => {
try { try {
helpers.removeDirectoryRecursively(destination); helpers.removeDirectoryRecursively(destination);
} catch (e) { } catch (e) {
} }
stream.close(); stream.close();
event.sender.send('extract_release_complete', { standardIPCReply(event, Constants.IPC_Extract_Release_Complete, {
data: {
Error: e,
Source: data.Source, Source: data.Source,
Success: false, }, error);
}
});
}) })
.on('finish', () => { .on('finish', () => {
stream.close(); stream.close();
event.sender.send('extract_release_complete', { standardIPCReply(event, Constants.IPC_Extract_Release_Complete, {
data: {
Source: data.Source, Source: data.Source,
Success: true,
}
}); });
}); });
}); });
ipcMain.on('get_platform', (event) => { ipcMain.on(Constants.IPC_Get_Config, (event, data) => {
event.sender.send('get_platform_reply', { 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() data: os.platform()
}); });
}); });
ipcMain.on('get_state', (event, data) => { ipcMain.on(Constants.IPC_Get_State, (event, data) => {
const dataDirectory = helpers.resolvePath(data); const dataDirectory = helpers.resolvePath(data);
helpers.mkDirByPathSync(dataDirectory); helpers.mkDirByPathSync(dataDirectory);
const configFile = path.join(dataDirectory, 'settings.json'); const configFile = path.join(dataDirectory, 'settings.json');
if (fs.existsSync(configFile)) { 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')), data: JSON.parse(fs.readFileSync(configFile, 'utf8')),
}); });
} else { } else {
event.sender.send('get_state_reply', { event.sender.send(Constants.IPC_Get_State_Reply, {
data: null, data: null,
}); });
} }
}); });
ipcMain.on('grab_releases', (event) => { ipcMain.on(Constants.IPC_Grab_Releases, (event) => {
event.sender.send('grab_releases_reply'); standardIPCReply(event, Constants.IPC_Grab_Releases_Reply);
}); });
ipcMain.on('grab_ui_releases', (event) => { ipcMain.on(Constants.IPC_Grab_UI_Releases, (event) => {
event.sender.send('grab_ui_releases_reply'); standardIPCReply(event, Constants.IPC_Grab_UI_Releases_Reply);
}); });
ipcMain.on('install_dependency', (event, data) => { ipcMain.on(Constants.IPC_Install_Dependency, (event, data) => {
helpers helpers
.executeAndWait(data.Source) .executeAndWait(data.Source)
.then(()=> { .then(()=> {
event.sender.send('install_dependency_reply', { standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, {
data: {
Source: data.Source, Source: data.Source,
Success: true,
}
}); });
}) })
.catch((e)=> { .catch(error => {
event.sender.send('install_dependency_reply', { standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, {
data: {
Error: e,
Source: data.Source, Source: data.Source,
Success: false, }, error);
}
});
}); });
}); });
ipcMain.on('install_upgrade', (event, data) => { ipcMain.on(Constants.IPC_Install_Upgrade, (event, data) => {
helpers helpers
.executeAsync(data.Source) .executeAsync(data.Source)
.then(()=> { .then(()=> {
mainWindow.close(); mainWindow.close();
}) })
.catch((e)=> { .catch(error => {
event.sender.send('install_upgrade_reply', { standardIPCReply(event, Constants.IPC_Install_Upgrade_Reply, {
data: {
Error: e,
Source: data.Source, Source: data.Source,
Success: false, }, error);
}
});
}); });
}); });
ipcMain.on('mount_drive', (event, data) => { ipcMain.on(Constants.IPC_Mount_Drive, (event, data) => {
const dataDirectory = helpers.resolvePath(data.Directory); const dataDirectory = helpers.resolvePath(data.Directory);
const errorHandler = (pid) => { const errorHandler = (pid, error) => {
mountedPIDs.splice(mountedPIDs.indexOf(pid), 1); mountedPIDs.splice(mountedPIDs.indexOf(pid), 1);
event.sender.send('unmount_drive_reply', { standardIPCReply(event, Constants.IPC_Unmount_Drive_Reply, {
data: {
PID: -1, PID: -1,
StorageType: data.StorageType, StorageType: data.StorageType,
Success: false, }, error || Error(data.StorageType + ' Unmounted'));
}
});
}; };
helpers.executeMount(dataDirectory, data.Version, data.StorageType, data.Location, (_, pid)=> { helpers.executeMount(dataDirectory, data.Version, data.StorageType, data.Location, (error, pid) => {
errorHandler(pid); errorHandler(pid, error);
}) })
.then(pid=> { .then(pid => {
if (pid !== -1) { if (pid !== -1) {
mountedPIDs.push(pid); mountedPIDs.push(pid);
} }
event.sender.send('mount_drive_reply', { standardIPCReply(event, Constants.IPC_Mount_Drive_Reply, {
data: {
PID: pid, PID: pid,
StorageType: data.StorageType, StorageType: data.StorageType,
Success: true,
}
}); });
}) })
.catch((_, pid) => { .catch(error => {
errorHandler(pid); errorHandler(-1, error);
}); });
}); });
ipcMain.on('save_state', (event, data) => { ipcMain.on(Constants.IPC_Save_State, (event, data) => {
const dataDirectory = helpers.resolvePath(data.Directory); const dataDirectory = helpers.resolvePath(data.Directory);
helpers.mkDirByPathSync(dataDirectory); helpers.mkDirByPathSync(dataDirectory);
const configFile = path.join(dataDirectory, 'settings.json'); const configFile = path.join(dataDirectory, 'settings.json');
fs.writeFileSync(configFile, JSON.stringify(data.State), 'utf8'); fs.writeFileSync(configFile, JSON.stringify(data.State), 'utf8');
}); });
ipcMain.on('unmount_drive', (event, data) => { ipcMain.on(Constants.IPC_Set_Config_Values, (event, data) => {
helpers.stopProcessByPID(data.PID) 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, {});
}
};
setConfigValue(0);
});
ipcMain.on(Constants.IPC_Shutdown, () => {
app.quit();
});
ipcMain.on(Constants.IPC_Unmount_Drive, (event, data) => {
helpers
.stopProcessByPID(data.PID)
.then((pid)=> { .then((pid)=> {
if (mountedPIDs.indexOf(pid) === -1) { if (mountedPIDs.indexOf(pid) === -1) {
event.sender.send('unmount_drive_reply'); event.sender.send(Constants.IPC_Unmount_Drive_Reply);
} }
}) })
.catch((e) => { .catch((e) => {

View File

@@ -80,18 +80,18 @@ module.exports.downloadFile = (url, destination, progressCallback, completeCallb
response.data.on('end', () => { response.data.on('end', () => {
stream.end(() => { stream.end(() => {
completeCallback(true); completeCallback();
}); });
}); });
response.data.on('error', (e) => { response.data.on('error', (e) => {
stream.end(() => { stream.end(() => {
completeCallback(false, e); completeCallback(e);
}); });
}); });
}) })
.catch((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 => { module.exports.getMissingDependencies = dependencies => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (!dependencies || (dependencies.length === 0)) { 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 => { module.exports.stopProcessByPID = pid => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const processOptions = { const processOptions = {

View File

@@ -1,6 +1,6 @@
{ {
"name": "repertory-ui", "name": "repertory-ui",
"version": "1.0.0", "version": "1.0.1",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.1", "@fortawesome/fontawesome-svg-core": "^1.2.1",
@@ -47,6 +47,7 @@
"react-dev-utils": "^5.0.1", "react-dev-utils": "^5.0.1",
"react-dom": "^16.4.1", "react-dom": "^16.4.1",
"react-loader-spinner": "^2.0.6", "react-loader-spinner": "^2.0.6",
"react-tooltip": "^3.8.4",
"resolve": "1.6.0", "resolve": "1.6.0",
"style-loader": "0.19.0", "style-loader": "0.19.0",
"sw-precache-webpack-plugin": "0.11.4", "sw-precache-webpack-plugin": "0.11.4",
@@ -68,7 +69,7 @@
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"electron": "3.0.0", "electron": "^3.0.2",
"electron-builder": "^20.28.4", "electron-builder": "^20.28.4",
"extract-text-webpack-plugin": "^3.0.2", "extract-text-webpack-plugin": "^3.0.2",
"webpack-browser-plugin": "^1.0.20" "webpack-browser-plugin": "^1.0.20"
@@ -121,6 +122,7 @@
"appId": "repertory-ui", "appId": "repertory-ui",
"files": [ "files": [
"./electron.js", "./electron.js",
"./src/constants.js",
"build/**/*", "build/**/*",
"node_modules/**/*", "node_modules/**/*",
"./helpers.js" "./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": { "Locations": {
"win32": { "win32": {
"1.0.1": {
"hash": "",
"urls": [
"https://sia.pixeldrain.com/api/file/Alo1IF1u/download"
]
},
"1.0.0": { "1.0.0": {
"hash": "", "hash": "",
"urls": [ "urls": [
@@ -11,6 +17,7 @@
}, },
"Versions": { "Versions": {
"win32": [ "win32": [
"1.0.1",
"1.0.0" "1.0.0"
] ]
} }

View File

@@ -9,3 +9,22 @@
background-image: url('./assets/images/background.jpg'); background-image: url('./assets/images/background.jpg');
background-size: cover; 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 React, {Component} from 'react';
import CSSModules from 'react-css-modules'; import axios from 'axios';
import styles from './App.css'; import styles from './App.css';
import Box from './components/UI/Box/Box'; import Box from './components/UI/Box/Box';
import DropDown from './components/UI/DropDown/DropDown'; import Configuration from './containers/Configuration/Configuration';
import * as Constants from './constants'; import CSSModules from 'react-css-modules';
import axios from 'axios';
import MountItems from './containers/MountItems/MountItems';
import DependencyList from './components/DependencyList/DependencyList'; 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 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 UpgradeIcon from './components/UpgradeIcon/UpgradeIcon';
import UpgradeUI from './components/UpgradeUI/UpgradeUI';
const Constants = require('./constants');
const Scheduler = require('node-schedule'); const Scheduler = require('node-schedule');
let ipcRenderer = null; let ipcRenderer = null;
@@ -25,180 +29,30 @@ class App extends Component {
super(props); super(props);
if (ipcRenderer) { if (ipcRenderer) {
ipcRenderer.on('get_platform_reply', (event, arg) => { ipcRenderer.on(Constants.IPC_Check_Installed_Reply, this.onCheckInstalledReply);
this.setState({ ipcRenderer.on(Constants.IPC_Download_File_Complete, this.onDownloadFileComplete);
Platform: arg.data, ipcRenderer.on(Constants.IPC_Download_File_Progress, this.onDownloadFileProgress);
}); ipcRenderer.on(Constants.IPC_Extract_Release_Complete, this.onExtractReleaseComplete);
ipcRenderer.send('get_state', Constants.DATA_LOCATIONS[arg.data]); 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) => { ipcRenderer.send(Constants.IPC_Get_State, Constants.DATA_LOCATIONS[this.props.platform]);
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); Scheduler.scheduleJob('23 11 * * *', this.updateCheckScheduledJob);
} }
} }
state = { state = {
AllowOptions: false,
AllowDownload: false, AllowDownload: false,
AutoMountChecked: false, AutoMountProcessed: false,
ConfigStorageType: null,
DisplayError: false,
DisplayMainContent: false,
Error: null,
ErrorAction: null,
ErrorCritical: false,
DownloadActive: false, DownloadActive: false,
DownloadProgress: 0.0, DownloadProgress: 0.0,
DownloadingDependency: false, DownloadingDependency: false,
@@ -212,6 +66,7 @@ class App extends Component {
}, },
LocationsLookup: {}, LocationsLookup: {},
MissingDependencies: [], MissingDependencies: [],
MountsBusy: false,
Platform: 'unknown', Platform: 'unknown',
Release: 3, Release: 3,
ReleaseTypes: [ ReleaseTypes: [
@@ -220,7 +75,7 @@ class App extends Component {
'Beta', 'Beta',
'Alpha', 'Alpha',
], ],
RepertoryVersion: 'none', InstalledVersion: 'none',
Sia: { Sia: {
AutoMount: false, AutoMount: false,
MountLocation: '', MountLocation: '',
@@ -228,7 +83,8 @@ class App extends Component {
UpgradeAvailable: false, UpgradeAvailable: false,
UpgradeData: {}, UpgradeData: {},
UpgradeDismissed: false, UpgradeDismissed: false,
Version: 0, Version: -1,
VersionAvailable: false,
VersionLookup: { VersionLookup: {
Alpha: [ Alpha: [
'unavailable' 'unavailable'
@@ -255,25 +111,58 @@ class App extends Component {
AllowDownload: false, AllowDownload: false,
}); });
if (selectedVersion !== 'unavailable') {
if (ipcRenderer) { if (ipcRenderer) {
let dependencies = []; let dependencies = [];
if (this.state.LocationsLookup[selectedVersion] && this.state.LocationsLookup[selectedVersion].dependencies) { if (this.state.LocationsLookup[selectedVersion] && this.state.LocationsLookup[selectedVersion].dependencies) {
dependencies = this.state.LocationsLookup[selectedVersion].dependencies; dependencies = this.state.LocationsLookup[selectedVersion].dependencies;
} }
ipcRenderer.send('check_installed', { ipcRenderer.send(Constants.IPC_Check_Installed, {
Dependencies: dependencies, Dependencies: dependencies,
Directory: Constants.DATA_LOCATIONS[this.state.Platform], Directory: Constants.DATA_LOCATIONS[this.props.platform],
Version: selectedVersion, 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,
});
}
}; };
grabReleases = () => { componentWillUnmount = () => {
if (this.state.Platform !== 'unknown') {
if (ipcRenderer) { if (ipcRenderer) {
ipcRenderer.send('grab_releases'); ipcRenderer.removeListener(Constants.IPC_Check_Installed_Reply, this.onCheckInstalledReply);
ipcRenderer.send('grab_ui_releases'); 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.props.platform !== 'unknown') {
if (ipcRenderer) {
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); this.saveState(this.state.Release, this.state.Version, sia, hyperspace);
}; };
handleConfigClicked = (storageType) => {
this.setState({
ConfigStorageType: storageType,
})
};
handleConfigClosed = () => {
this.setState({
ConfigStorageType: null,
});
};
handleDependencyDownload = (url) => { handleDependencyDownload = (url) => {
if (ipcRenderer) { if (ipcRenderer) {
const items = url.split('/'); const items = url.split('/');
@@ -313,8 +214,8 @@ class App extends Component {
DownloadName: fileName, DownloadName: fileName,
}); });
ipcRenderer.send('download_file', { ipcRenderer.send(Constants.IPC_Download_File, {
Directory: Constants.DATA_LOCATIONS[this.state.Platform], Directory: Constants.DATA_LOCATIONS[this.props.platform],
Filename: fileName, Filename: fileName,
URL: url, URL: url,
}); });
@@ -341,12 +242,13 @@ class App extends Component {
handleReleaseChanged = (e) => { handleReleaseChanged = (e) => {
const val = parseInt(e.target.value, 10); const val = parseInt(e.target.value, 10);
const versionIndex = this.state.VersionLookup[this.state.ReleaseTypes[val]].length - 1;
this.setState({ this.setState({
Release: val, Release: val,
Version: 0 Version: versionIndex
}); });
this.saveState(val, 0, this.state.Sia, this.state.Hyperspace); this.saveState(val, versionIndex, this.state.Sia, this.state.Hyperspace);
this.checkVersionInstalled(val, 0); this.checkVersionInstalled(val, versionIndex);
}; };
handleReleaseDownload = () => { handleReleaseDownload = () => {
@@ -359,8 +261,8 @@ class App extends Component {
DownloadName: fileName, DownloadName: fileName,
}); });
ipcRenderer.send('download_file', { ipcRenderer.send(Constants.IPC_Download_File, {
Directory: Constants.DATA_LOCATIONS[this.state.Platform], Directory: Constants.DATA_LOCATIONS[this.props.platform],
Filename: fileName, Filename: fileName,
URL: this.state.LocationsLookup[selectedVersion].urls[0], URL: this.state.LocationsLookup[selectedVersion].urls[0],
}); });
@@ -375,9 +277,9 @@ class App extends Component {
DownloadName: 'UI Upgrade', DownloadName: 'UI Upgrade',
}); });
ipcRenderer.send('download_file', { ipcRenderer.send(Constants.IPC_Download_File, {
Directory: Constants.DATA_LOCATIONS[this.state.Platform], Directory: Constants.DATA_LOCATIONS[this.props.platform],
Filename: this.state.Platform === 'win32' ? 'upgrade.exe' : 'upgrade', Filename: this.props.platform === 'win32' ? 'upgrade.exe' : 'upgrade',
URL: this.state.UpgradeData.urls[0], URL: this.state.UpgradeData.urls[0],
}); });
} else { } else {
@@ -395,13 +297,228 @@ class App extends Component {
}; };
notifyAutoMountProcessed = () => { 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)=> { saveState = (release, version, sia, hyperspace)=> {
if (ipcRenderer) { if (ipcRenderer) {
ipcRenderer.send('save_state', { ipcRenderer.send(Constants.IPC_Save_State, {
Directory: Constants.DATA_LOCATIONS[this.state.Platform], Directory: Constants.DATA_LOCATIONS[this.props.platform],
State: { State: {
Hyperspace: hyperspace, Hyperspace: hyperspace,
Release: release, 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 = () => { updateCheckScheduledJob = () => {
if (this.state.Platform !== 'unknown') { if (this.props.platform !== 'unknown') {
if (ipcRenderer) { this.grabReleases();
ipcRenderer.send('grab_ui_releases');
}
} }
}; };
render() { render() {
const selectedVersion = this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]][this.state.Version]; const selectedVersion = (this.state.Version === -1) ?
const downloadEnabled = this.state.AllowDownload && 'unavailable' :
!this.state.DownloadActive && this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]][this.state.Version];
(((selectedVersion !== 'unavailable') && (selectedVersion !== this.state.RepertoryVersion)));
const allowMount = this.state.RepertoryVersion !== 'none';
const missingDependencies = (this.state.MissingDependencies.length > 0);
let mountDisplay = null; const downloadEnabled = this.state.AllowDownload &&
if (allowMount) { !this.state.MountsBusy &&
mountDisplay = <MountItems platform={this.state.Platform} !this.state.DownloadActive &&
sia={this.state.Sia} (selectedVersion !== 'unavailable') &&
hyperspace={this.state.Hyperspace} (selectedVersion !== this.state.InstalledVersion);
changed={this.handleMountLocationChanged}
processAutoMount={!this.state.AutoMountChecked} const allowMount = this.state.InstalledVersion !== 'none';
autoMountProcessed={this.notifyAutoMountProcessed} const missingDependencies = (this.state.MissingDependencies.length > 0);
autoMountChanged={this.handleAutoMountChanged} const allowConfig = this.state.LocationsLookup[selectedVersion] &&
version={this.state.RepertoryVersion} this.state.LocationsLookup[selectedVersion].config_support;
directory={Constants.DATA_LOCATIONS[this.state.Platform]}
disabled={!allowMount}/>; 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; let dependencyDisplay = null;
if (missingDependencies && !this.state.DownloadActive) { if (showDependencies) {
dependencyDisplay = ( dependencyDisplay = (
<Modal> <Modal>
<DependencyList allowDownload={!this.state.DownloadingDependency} <DependencyList allowDownload={!this.state.DownloadingDependency}
@@ -458,105 +614,101 @@ class App extends Component {
if (this.state.DownloadActive) { if (this.state.DownloadActive) {
downloadDisplay = ( downloadDisplay = (
<Modal> <Modal>
<DownloadProgress progress={this.state.DownloadProgress} <DownloadProgress display={this.state.DownloadName}
display={this.state.DownloadName}/> progress={this.state.DownloadProgress}/>
</Modal>); </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; let upgradeDisplay = null;
if (!missingDependencies && if (showUpgrade) {
!this.state.DownloadActive &&
this.state.UpgradeAvailable &&
!this.state.UpgradeDismissed) {
upgradeDisplay = ( upgradeDisplay = (
<Modal> <Modal>
<UpgradeUI upgrade={this.handleUIDownload} <UpgradeUI cancel={()=>this.setState({UpgradeDismissed: true})}
cancel={()=>this.setState({UpgradeDismissed: true})}/> upgrade={this.handleUIDownload}/>
</Modal> </Modal>
); );
} }
let options = null; let mainContent = [];
if (this.state.AllowOptions) { if (this.state.DisplayMainContent) {
options = ( let key = 0;
<table width='100%' cellPadding='2'> mainContent.push((
<tbody> <div key={'rvd_' + key++}
<tr> style={{height: '44%'}}>
<td width='33%'> <ReleaseVersionDisplay disabled={this.state.DownloadActive || this.state.ExtractActive || this.state.MountsBusy}
<h2>Release</h2> downloadClicked={this.handleReleaseDownload}
</td> downloadDisabled={!downloadEnabled}
<td width='33%'> installedVersion={this.state.InstalledVersion}
<h2>Version</h2> release={this.state.Release}
</td> releaseChanged={this.handleReleaseChanged}
<td width='33%'> releaseExtracting={this.state.ExtractActive}
<h2>Installed</h2> releaseTypes={this.state.ReleaseTypes}
</td> version={this.state.Version}
</tr> versionAvailable={this.state.VersionAvailable}
<tr> versionChanged={this.handleVersionChanged}
<td> versions={this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]]}/>
<DropDown disabled={this.state.DownloadActive || this.state.ExtractActive} </div>
items={this.state.ReleaseTypes} ));
selected={this.state.Release}
changed={this.handleReleaseChanged}/> if (allowMount) {
</td> mainContent.push((
<td> <div key={'md_' + key++}
<DropDown disabled={this.state.DownloadActive || this.state.ExtractActive} style={{height: '56%'}}>
items={this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]]} <MountItems allowConfig={allowConfig}
selected={this.state.Version} autoMountChanged={this.handleAutoMountChanged}
changed={this.handleVersionChanged}/> autoMountProcessed={this.notifyAutoMountProcessed}
</td> changed={this.handleMountLocationChanged}
<td> configClicked={this.handleConfigClicked}
{this.state.RepertoryVersion} directory={Constants.DATA_LOCATIONS[this.props.platform]}
</td> errorHandler={this.setErrorState}
</tr> hyperspace={this.state.Hyperspace}
<tr> mountsBusy={this.notifyMountsBusy}
<td colSpan={3}> platform={this.props.platform}
{releaseDisplay} processAutoMount={!this.state.AutoMountProcessed}
</td> sia={this.state.Sia}
</tr> version={this.state.InstalledVersion}/>
<tr> </div>
<td colSpan={3}> ));
{mountDisplay} }
</td> } else {
</tr> mainContent = <Loading/>
</tbody>
</table>);
} }
return ( return (
<div styleName='App'> <div styleName='App'>
{errorDisplay}
{dependencyDisplay} {dependencyDisplay}
{upgradeDisplay} {upgradeDisplay}
{downloadDisplay} {downloadDisplay}
<Box dxDark dxStyle={{'height': 'auto', 'padding': '2px'}}> {configDisplay}
<table cellPadding={0} cellSpacing={0} style={{margin: 0, padding: 0}}> <div styleName='Container'>
<tbody style={{margin: 0, padding: 0}}> <div styleName='Header'>
<tr style={{margin: 0, padding: 0}}> <Box>
<td width='33%' style={{margin: 0, padding: 0}}/> <Grid>
<td width='33%' style={{margin: 0, padding: 0}}> <Text col={0}
<h1 style={{'textAlign': 'center'}}>{'Repertory UI v' + this.props.version}</h1> colSpan={'remain'}
</td> row={0}
<td width='33%' style={{margin: 0, padding: 0}} align='right' valign='middle'> rowSpan={'remain'}
text={'Repertory UI v' + this.props.version}
textAlign={'center'}
type={'Heading1'}/>
<UpgradeIcon <UpgradeIcon
available={this.state.UpgradeAvailable} available={this.state.UpgradeAvailable}
clicked={()=>this.setState({UpgradeDismissed: false})}/> clicked={()=>this.setState({UpgradeDismissed: false})}
</td> col={dimensions => dimensions.columns - 6}
</tr> colSpan={5}
</tbody> row={1}
</table> rowSpan={remain=>remain - 2}/>
</Grid>
</Box> </Box>
<Box dxStyle={{'padding': '4px', 'marginTop': '10px'}}> </div>
{options} <div styleName='Content'>
<Box dxStyle={{padding: '8px'}}>
{mainContent}
</Box> </Box>
</div> </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)=> { const items = props.dependencies.map((k, i)=> {
return ( return (
<Dependency allowDownload={props.allowDownload} <Dependency allowDownload={props.allowDownload}
download={k.download}
key={i} key={i}
name={k.display} name={k.display}
download={k.download}
onDownload={props.onDownload}/> onDownload={props.onDownload}/>
); );
}); });
return ( 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'}}> <div style={{width: '100%', height: 'auto', paddingBottom: '5px', boxSizing: 'border-box'}}>
<h1 style={{width: '100%', textAlign: 'center'}}>Missing Dependencies</h1> <h1 style={{width: '100%', textAlign: 'center'}}>Missing Dependencies</h1>
</div> </div>

View File

@@ -6,7 +6,7 @@ import styles from './DownloadProgress.css';
export default CSSModules((props) => { export default CSSModules((props) => {
return ( return (
<Box dxDark dxStyle={{width: '380px', height: 'auto', padding: '5px'}}> <Box dxStyle={{width: '380px', height: 'auto', padding: '5px'}}>
<div style={{width: '100%', height: 'auto'}}> <div style={{width: '100%', height: 'auto'}}>
<h1 style={{width: '100%', textAlign: 'center'}}>{'Downloading ' + props.display}</h1> <h1 style={{width: '100%', textAlign: 'center'}}>{'Downloading ' + props.display}</h1>
</div> </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 DropDown from '../UI/DropDown/DropDown';
import Button from '../UI/Button/Button'; import Button from '../UI/Button/Button';
import Loader from 'react-loader-spinner'; 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) => { export default CSSModules((props) => {
let inputControl = null; let configButton = null;
let mountWidth = '70%'; let secondRow = 6;
if (props.platform === 'win32') { if (props.allowConfig) {
inputControl = <DropDown disabled={!props.allowMount || props.mounted} configButton = (
items={props.items} <RootElem colSpan={4}
selected={props.items.indexOf(props.location)} rowSpan={6}>
changed={props.changed}/>; <img alt=''
mountWidth = '18%'; height={'16px'}
} else { onClick={props.configClicked}
inputControl = <input disabled={!props.allowMount || props.mounted} src={configureImage}
type={'text'} style={{padding: 0, border: 0, margin: 0, cursor: 'pointer'}}
value={props.location} width={'16px'}/>
onChange={props.changed}/>; </RootElem>
);
} }
let actionDisplay = null; let inputColumnSpan;
if (props.allowMount) { let inputControl = null;
actionDisplay = <Button buttonStyles={{width: '100%'}} if (props.platform === 'win32') {
clicked={()=>props.clicked(props.title, !props.mounted, props.location, props.pid)}>{props.mounted ? 'Unmount' : 'Mount'}</Button>; 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 { } else {
actionDisplay = <Loader color={'var(--heading_text_color)'} inputColumnSpan = 60;
height='24px' inputControl = (
width='24px' <RootElem colSpan={inputColumnSpan}
type='Circles'/>; 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 ( return (
<div styleName='MountItem'> <Grid>
<h2>{props.title}</h2> {configButton}
<table width='100%' cellPadding='2'> <Text
<tbody> col={configButton ? 6 : 0}
<tr> rowSpan={5}
<td width={mountWidth} height='30px'>{inputControl}</td> text={props.title}
<td width='25%' align='center' valign='middle'> type={'Heading1'}/>
{actionDisplay} {inputControl}
</td> {actionsDisplay}
<td> {autoMountControl}
<input type='checkbox' checked={props.autoMount} onChange={props.autoMountChanged}/>Auto-mount </Grid>
</td>
</tr>
</tbody>
</table>
</div>
); );
}, styles, {allowMultiple: true}); }, 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'; import styles from './Box.css';
export default CSSModules((props) => { export default CSSModules((props) => {
const styleList = ['Box']; const styleList = [];
styleList.push('Box');
if (props.dxDark) { if (props.dxDark) {
styleList.push('Darker'); styleList.push('Darker');
} }

View File

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

View File

@@ -1,5 +1,7 @@
.DropDown { .DropDown {
display: block;
width: 100%; width: 100%;
height: 100%;
margin: 0; margin: 0;
padding: 0; padding: 0;
} }
@@ -22,3 +24,16 @@
border-color: rgba(10, 10, 20, 0.9); border-color: rgba(10, 10, 20, 0.9);
color: var(--text_color); 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 React from 'react';
import CSSModules from 'react-css-modules';
import styles from './DropDown.css'; import styles from './DropDown.css';
import CSSModules from 'react-css-modules';
export default CSSModules((props) => { export default CSSModules((props) => {
const options = props.items.map((s, i) => { 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%); transform: translate(-50%, -50%);
z-index: 2001; 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' import styles from './Modal.css'
export default CSSModules((props) => { export default CSSModules((props) => {
let modalStyles = [];
let contentStyles = [];
modalStyles.push('Modal');
contentStyles.push('Content');
if (props.critical) {
modalStyles.push('Critical');
contentStyles.push('Critical');
}
return ( return (
<div <div
styleName='Modal' styleName={modalStyles.join(' ')}
onClick={props.clicked}> onClick={props.clicked}>
<div styleName='Content'> <div styleName={contentStyles.join(' ')}>
{props.children} {props.children}
</div> </div>
</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 { .Owner {
display: block;
margin: 0;
padding: 0; padding: 0;
width: 20px; margin: 0;
height: 20px; width: 100%;
height: 100%;
border: 0; border: 0;
box-sizing: border-box; box-sizing: border-box;
cursor: pointer; cursor: pointer;
} }
div.UpgradeIcon { .UpgradeIcon {
cursor: default; 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 React from 'react';
import CSSModules from 'react-css-modules'; import CSSModules from 'react-css-modules';
import styles from './UpgradeIcon.css'; 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) => { export default CSSModules((props) => {
return props.available ? let placement = 'left';
<img alt='' styleName='UpgradeIcon' src={availableImage} onClick={props.clicked}/> : let toolTipText = 'UI Upgrade Available';
<div styleName='UpgradeIcon'/> ; 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}); }, styles, {allowMultiple: true});

View File

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

View File

@@ -1,7 +1,62 @@
export const RELEASES_URL = 'https://bitbucket.org/blockstorage/repertory/raw/master/releases.json'; Object.defineProperty(exports, "__esModule", {
export const DATA_LOCATIONS = { value: true
});
exports.RELEASES_URL = 'https://bitbucket.org/blockstorage/repertory/raw/master/releases.json';
exports.DATA_LOCATIONS = {
linux: '~/.local/repertory/ui', linux: '~/.local/repertory/ui',
darwin: '~/Library/Application Support/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 { .MountItems {
margin-top: 10px; padding: 0;
margin: 0;
width: 100%; width: 100%;
box-sizing: border-box;
} }

View File

@@ -4,6 +4,8 @@ import CSSModules from 'react-css-modules';
import styles from './MountItems.css'; import styles from './MountItems.css';
import MountItem from '../../components/MountItem/MountItem'; import MountItem from '../../components/MountItem/MountItem';
const Constants = require('../../constants');
let ipcRenderer = null; let ipcRenderer = null;
if (!process.versions.hasOwnProperty('electron')) { if (!process.versions.hasOwnProperty('electron')) {
ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null); ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null);
@@ -13,64 +15,9 @@ class MountItems extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
if (ipcRenderer) { if (ipcRenderer) {
ipcRenderer.on('detect_mounts_reply', (event, arg) => { ipcRenderer.on(Constants.IPC_Detect_Mounts_Reply, this.onDetectMountsReply);
if (arg.data.Success) { ipcRenderer.on(Constants.IPC_Mount_Drive_Reply, this.onMountDriveReply);
const sia = { ipcRenderer.on(Constants.IPC_Unmount_Drive_Reply, this.onUnmountDriveReply);
...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(); 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 = ()=> { detectMounts = ()=> {
ipcRenderer.send('detect_mounts', { this.props.mountsBusy(true);
ipcRenderer.send(Constants.IPC_Detect_Mounts, {
Directory: this.props.directory, Directory: this.props.directory,
Version: this.props.version, Version: this.props.version,
}); });
@@ -117,15 +73,17 @@ class MountItems extends Component {
[storageType]: state, [storageType]: state,
}); });
this.props.mountsBusy(true);
if (mount) { if (mount) {
ipcRenderer.send('mount_drive', { ipcRenderer.send(Constants.IPC_Mount_Drive, {
Directory: this.props.directory, Directory: this.props.directory,
Location: location, Location: location,
StorageType: storageType, StorageType: storageType,
Version: this.props.version, Version: this.props.version,
}); });
} else { } else {
ipcRenderer.send('unmount_drive', { ipcRenderer.send(Constants.IPC_Unmount_Drive, {
Directory: this.props.directory, Directory: this.props.directory,
Location: location, Location: location,
PID: pid, 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 = ()=> { performAutoMount = ()=> {
if (this.props.processAutoMount) { if (this.props.processAutoMount) {
this.props.autoMountProcessed(); this.props.autoMountProcessed();
@@ -155,28 +176,33 @@ class MountItems extends Component {
render() { render() {
return ( return (
<div styleName='MountItems'> <div styleName='MountItems'>
<MountItem allowMount={this.state.Hyperspace.AllowMount} <MountItem allowConfig={this.props.allowConfig}
allowMount={this.state.Hyperspace.AllowMount}
autoMount={this.props.hyperspace.AutoMount} autoMount={this.props.hyperspace.AutoMount}
autoMountChanged={(e)=>this.props.autoMountChanged('Hyperspace', e)} 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)} changed={(e) => this.handleMountLocationChanged('Hyperspace', e.target.value)}
clicked={this.handleMountUnMount} clicked={this.handleMountUnMount}
pid={this.state.Hyperspace.PID}/> configClicked={()=>this.props.configClicked('Hyperspace')}
<MountItem allowMount={this.state.Sia.AllowMount} 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} autoMount={this.props.sia.AutoMount}
autoMountChanged={(e)=>this.props.autoMountChanged('Sia', e)} 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)} changed={(e) => this.handleMountLocationChanged('Sia', e.target.value)}
clicked={this.handleMountUnMount} 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>); </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; --border_radius: 4px;
--control_background: rgba(150, 150, 190, .15); --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_border: 1px solid rgba(70, 70, 70, 0.9);
--control_box_shadow: 1px 1px 1px black; --control_box_shadow: 1px 1px 1px black;
--control_transparent_background: rgba(60, 60, 70, 0.4); --control_transparent_background: rgba(40, 40, 55, 0.45);
--control_dark_transparent_background: rgba(60, 60, 70, 0.4); --control_dark_transparent_background: rgba(15, 15, 15, 0.8);
--text_color: rgba(200, 205, 220, 0.7); --text_color: rgba(200, 205, 220, 0.7);
--text_color_hover: rgba(200, 205, 220, 0.7); --text_color_hover: rgba(200, 205, 220, 0.7);
--heading_text_color: rgba(194, 217, 255, 0.6); --text_color_error: rgba(203, 120, 120, 0.8);
--heading_other_text_color: rgba(200, 205, 220, 0.7); --heading_text_color: rgba(161, 190, 219, 0.7);
--heading_other_text_color: var(--heading_text_color);
--text_color_transition: color 0.3s; --text_color_transition: color 0.3s;
} }
* { * {
font-family: 'Nunito', sans-serif; font-family: 'Nunito', sans-serif;
font-size: 15px; font-size: 5vh;
} }
*::-moz-focus-inner { *::-moz-focus-inner {
@@ -61,3 +62,27 @@ h1 {
h2, h3 { h2, h3 {
color: var(--heading_other_text_color); 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 React from 'react';
import ReactDOM from 'react-dom'; import ReactDOM from 'react-dom';
import './index.css'; import './index.css';
import App from './App'; import App from './App';
import registerServiceWorker from './registerServiceWorker';
import packageJson from '../package.json'; 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();