Merged 1.0.2_branch into master

This commit is contained in:
Scott Graves
2019-04-06 23:17:07 -05:00
25 changed files with 1339 additions and 745 deletions

View File

@@ -1,4 +1,15 @@
# Changelog # # Changelog #
## 1.0.2 ##
* Option to launch application hidden (notification icon only)
* Close window to notification area
* Unmount on application exit
* Ability to cancel mount retry on unexpected failure
* OS X support
* SiaPrime support
* Partial Linux support
* Electron to v4
* Prevent mount if dependencies are missing
## 1.0.1 ## ## 1.0.1 ##
* Added configuration settings for Repertory 1.0.0-alpha.2 and above * Added configuration settings for Repertory 1.0.0-alpha.2 and above
* Fixed memory leak on component unmount * Fixed memory leak on component unmount

View File

@@ -1,11 +1,12 @@
# Repertory UI # Repertory UI
![alt text](https://image.ibb.co/mnhA1z/repertory_1_0_1.png) ![alt text](https://image.ibb.co/edOg5A/repertory-ui-1-0-2.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 Sia, SiaPrime and/or Hyperspace blockchain storage solutions via FUSE on Linux/OS X or via WinFSP on Windows.
# Downloads # # Downloads #
* [Repertory UI v1.0.1 Windows 64-bit](https://sia.pixeldrain.com/api/file/Alo1IF1u) * [Repertory UI v1.0.2 OS X](https://pixeldrain.com/u/OtxPlbOI)
* [Repertory UI v1.0.2 Windows 64-bit](https://pixeldrain.com/u/4oJeVntd)
# Supported Platforms # # Supported Platforms #
* OS X
* Windows 64-bit * Windows 64-bit
# Future Platforms # # Future Platforms #
* OS X
* Linux 64-bit * Linux 64-bit

View File

@@ -1,6 +1,6 @@
// Modules to control application life and create native browser window // Modules to control application life and create native browser window
const {app, BrowserWindow, Tray, nativeImage, Menu} = require('electron'); const {app, BrowserWindow, Tray, nativeImage, Menu, dialog} = require('electron');
const {ipcMain} = require('electron'); const {ipcMain} = require('electron');
const Constants = require('./src/constants'); const Constants = require('./src/constants');
const path = require('path'); const path = require('path');
@@ -14,43 +14,106 @@ 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 trayContextMenu;
let mainWindow; let mainWindow;
let mainWindowTray; let mainWindowTray;
let mountedPIDs = []; let mountedData = {};
let mountedLocations = [];
let expectedUnmount = {};
let launchHidden = false;
let firstMountCheck = true;
let manualMountDetection = {};
let isQuiting = false;
app.on('before-quit', function () {
isQuiting = true;
});
function closeApplication() {
app.quit();
}
function setWindowVisibility(show) {
if (show) {
mainWindow.show();
if (os.platform() === 'darwin') {
app.dock.show();
}
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
mainWindow.focus();
} else {
mainWindow.hide();
if (os.platform() === 'darwin') {
app.dock.hide();
}
}
if (trayContextMenu && mainWindowTray) {
trayContextMenu.items[0].checked = show;
mainWindowTray.setContextMenu(trayContextMenu)
}
}
function createWindow() { function createWindow() {
loadUiSettings();
// Create the browser window. // Create the browser window.
const height = process.env.ELECTRON_START_URL ? 324 : 304; const height = (process.env.ELECTRON_START_URL || (os.platform() === 'darwin') ? 364 : 344) - ((os.platform() === 'win32') ? 0 : 20);
mainWindow = new BrowserWindow({ mainWindow = new BrowserWindow({
width: 425, width: 428 + ((os.platform() === 'win32') ? 0 : (os.platform() === 'darwin') ? 150 : 160),
height: height, height: height,
fullscreen: false,
resizable: false, resizable: false,
show: !launchHidden,
title: 'Repertory UI', title: 'Repertory UI',
webPreferences: { webPreferences: {
webSecurity: !process.env.ELECTRON_START_URL webSecurity: !process.env.ELECTRON_START_URL
} }
}); });
if ((os.platform() === 'darwin') && launchHidden) {
app.dock.hide();
}
// and load the index.html of the app. // and load the index.html of the app.
const startUrl = process.env.ELECTRON_START_URL || url.format({ const startUrl = process.env.ELECTRON_START_URL || url.format({
pathname: path.join(__dirname, '/build/index.html'), pathname: path.join(__dirname, '/build/index.html'),
protocol: 'file:', protocol: 'file:',
slashes: true slashes: true
}); });
mainWindow.loadURL(startUrl);
// Emitted when the window is closed. mainWindow.on('close', function (event) {
if (!isQuiting) {
event.preventDefault();
if (mainWindow.isVisible()) {
setWindowVisibility(false);
}
event.returnValue = false;
}
});
mainWindow.on('closed', function () { mainWindow.on('closed', function () {
// Dereference the window object, usually you would store windows // Dereference the window object, usually you would store windows
// in an array if your app supports multi windows, this is the time // in an array if your app supports multi windows, this is the time
// when you should delete the corresponding element. // when you should delete the corresponding element.
mainWindow = null mainWindow = null;
// Unmount all items
for (const i in mountedLocations) {
const data = mountedData[mountedLocations[i]];
helpers.stopMountProcessSync(data.DataDirectory, data.Version, data.StorageType);
}
mountedLocations = [];
mountedData = {};
}); });
if ((os.platform() === 'win32') || (os.platform() === 'linux')) { const appPath = (os.platform() === 'win32') ? path.resolve(path.join(app.getAppPath(), '..\\..\\repertory-ui.exe')) :
const appPath = (os.platform() === 'win32') ? (os.platform() === 'darwin') ? path.resolve(path.join(path.dirname(app.getAppPath()), '../MacOS/repertory-ui')) :
path.resolve(path.join(app.getAppPath(), '..\\..\\repertory-ui.exe')) :
process.env.APPIMAGE; process.env.APPIMAGE;
const autoLauncher = new AutoLaunch({ const autoLauncher = new AutoLaunch({
@@ -58,20 +121,12 @@ function createWindow() {
path: appPath, path: appPath,
}); });
const image = nativeImage.createFromPath(path.join(__dirname, '/build/logo.png')); trayContextMenu = Menu.buildFromTemplate([
mainContextWindow = Menu.buildFromTemplate([
{ {
label: 'Visible', type: 'checkbox', click(item) { label: 'Visible', type: 'checkbox', click(item) {
if (item.checked) { setWindowVisibility(item.checked);
mainWindow.show(); },
if (mainWindow.isMinimized()) { checked: !launchHidden,
mainWindow.restore();
}
mainWindow.focus()
} else {
mainWindow.hide();
}
}
}, },
{ {
label: 'Auto-start', type: 'checkbox', click(item) { label: 'Auto-start', type: 'checkbox', click(item) {
@@ -86,74 +141,172 @@ function createWindow() {
type: 'separator' type: 'separator'
}, },
{ {
label: 'Exit', click(item) { label: 'Launch Hidden', type: 'checkbox', click(item) {
app.quit(); launchHidden = !!item.checked;
saveUiSettings();
},
checked: launchHidden,
},
{
type: 'separator'
},
{
label: 'Exit and Unmount', click() {
closeApplication();
} }
} }
]); ]);
mainContextWindow.items[0].checked = true; const image = nativeImage.createFromPath(path.join(__dirname, (os.platform() === 'darwin') ? '/build/logo_mac.png' : '/build/logo.png'));
mainWindowTray = new Tray(image);
autoLauncher autoLauncher
.isEnabled() .isEnabled()
.then((enabled) => { .then((enabled) => {
mainContextWindow.items[1].checked = enabled; trayContextMenu.items[1].checked = enabled;
mainWindowTray = new Tray(image);
mainWindowTray.setToolTip('Repertory UI'); mainWindowTray.setToolTip('Repertory UI');
mainWindowTray.setContextMenu(mainContextWindow) mainWindowTray.setContextMenu(trayContextMenu)
}) })
.catch(() => { .catch(() => {
app.quit(); closeApplication();
}); });
}
mainWindow.loadURL(startUrl);
} }
const instanceLock = app.requestSingleInstanceLock(); const instanceLock = app.requestSingleInstanceLock();
if (!instanceLock) { if (!instanceLock) {
app.quit() closeApplication();
} else { } else {
app.on('second-instance', () => { app.on('second-instance', () => {
if (mainWindow) { if (mainWindow) {
mainWindow.show(); setWindowVisibility(true);
if (mainContextWindow) {
mainContextWindow.items[0].checked = true;
}
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
mainWindow.focus()
} }
}); });
app.on('ready', createWindow); app.on('ready', createWindow);
app.on('window-all-closed', () => { app.on('window-all-closed', () => {
// On OS X it is common for applications and their menu bar closeApplication();
// to stay active until the user quits explicitly with Cmd + Q
if (process.platform !== 'darwin') {
app.quit()
}
}); });
}
app.on('activate', () => { const clearManualMountDetection = (storageType) => {
// On OS X it's common to re-create a window in the app when the if (manualMountDetection[storageType]) {
// dock icon is clicked and there are no other windows open. clearInterval(manualMountDetection[storageType]);
if (mainWindow === null) { delete manualMountDetection[storageType];
createWindow() }
};
const loadUiSettings = () => {
const settingFile = path.join(helpers.resolvePath(Constants.DATA_LOCATIONS[os.platform()]), 'ui.json');
try {
if (fs.statSync(settingFile).isFile()) {
const settings = JSON.parse(fs.readFileSync(settingFile, 'utf8'));
launchHidden = settings.launch_hidden;
}
} catch (e) {
}
};
const monitorMount = (sender, storageType, dataDirectory, version, pid, location) => {
manualMountDetection[storageType] = setInterval(() => {
helpers
.detectRepertoryMounts(dataDirectory, version)
.then(result => {
if (result[storageType].PID !== pid) {
if (result[storageType].PID === -1) {
clearManualMountDetection(storageType);
sender.send(Constants.IPC_Unmount_Drive_Reply, {
data: {
Expected: expectedUnmount[storageType],
Location: location,
StorageType: storageType,
Error: Error(storageType + ' Unmounted').toString(),
Success: false,
} }
}); });
} else {
pid = result[storageType].PID;
} }
}
})
.catch(e => {
console.log(e);
});
},6000);
};
const saveUiSettings = () => {
const settingFile = path.join(helpers.resolvePath(Constants.DATA_LOCATIONS[os.platform()]), 'ui.json');
try {
fs.writeFileSync(settingFile, JSON.stringify({
launch_hidden: launchHidden,
}), 'utf-8');
} catch (e) {
}
};
const standardIPCReply = (event, channel, data, error) => { const standardIPCReply = (event, channel, data, error) => {
if (mainWindow) {
event.sender.send(channel, { event.sender.send(channel, {
data: { data: {
...data, ...data,
Error: error, Error: error instanceof Error ? error.toString() : error,
Success: !error, Success: !error,
} }
}); });
}
}; };
ipcMain.on(Constants.IPC_Browse_Directory + '_sync', (event, data) => {
dialog.showOpenDialog(mainWindow, {
defaultPath: data.Location,
properties: ['openDirectory'],
title: data.Title,
}, (filePaths) => {
if (filePaths && (filePaths.length > 0)) {
event.returnValue = filePaths[0];
} else {
event.returnValue = '';
}
});
});
ipcMain.on(Constants.IPC_Check_Dependency_Installed, (event, data) => {
try {
const exists = fs.lstatSync(data.File).isFile();
standardIPCReply(event, Constants.IPC_Check_Dependency_Installed_Reply, {
data: {
Exists: exists,
},
});
} catch (e) {
standardIPCReply(event, Constants.IPC_Check_Dependency_Installed_Reply, {
data : {
Exists: false,
},
});
}
});
ipcMain.on(Constants.IPC_Check_Dependency_Installed + '_sync', (event, data) => {
try {
const ls = fs.lstatSync(data.File);
event.returnValue = {
data: {
Exists: ls.isFile() || ls.isSymbolicLink(),
},
};
} catch (e) {
event.returnValue = {
data: {
Exists: false
},
};
}
});
ipcMain.on(Constants.IPC_Check_Installed, (event, data) => { 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);
@@ -178,6 +331,29 @@ ipcMain.on(Constants.IPC_Check_Installed, (event, data) => {
}); });
}); });
ipcMain.on(Constants.IPC_Check_Mount_Location + '_sync', (event, data) => {
let response = {
Success: true,
Error: ''
};
try {
if (fs.existsSync(data.Location) && fs.statSync(data.Location).isDirectory()) {
if (fs.readdirSync(data.Location).length !== 0) {
response.Success = false;
response.Error = 'Directory not empty: ' + data.Location;
}
} else {
response.Success = false;
response.Error = 'Directory not found: ' + data.Location;
}
} catch (e) {
response.Success = false;
response.Error = e.toString();
}
event.returnValue = response;
});
ipcMain.on(Constants.IPC_Delete_File, (event, data) => { ipcMain.on(Constants.IPC_Delete_File, (event, data) => {
try { try {
if (fs.existsSync(data.FilePath)) { if (fs.existsSync(data.FilePath)) {
@@ -187,87 +363,123 @@ ipcMain.on(Constants.IPC_Delete_File, (event, data) => {
} }
}); });
ipcMain.on(Constants.IPC_Detect_Mounts, (event, data) => { ipcMain.on(Constants.IPC_Delete_File + '_sync', (event, data) => {
let driveLetters = { try {
Hyperspace: [], if (fs.existsSync(data.FilePath)) {
Sia: [], fs.unlinkSync(data.FilePath);
}
event.returnValue = {
data: true,
}; };
} catch (e) {
event.returnValue = {
data: false,
};
}
});
const grabDriveLetters = (hsLocation, siaLocation) => { ipcMain.on(Constants.IPC_Detect_Mounts, (event, data) => {
let driveLetters = {};
for (const provider of Constants.PROVIDER_LIST) {
driveLetters[provider] = [];
}
const grabDriveLetters = (locations) => {
for (let i = 'c'.charCodeAt(0); i <= 'z'.charCodeAt(0); i++) { for (let i = 'c'.charCodeAt(0); i <= 'z'.charCodeAt(0); i++) {
const drive = (String.fromCharCode(i) + ':').toUpperCase(); const drive = (String.fromCharCode(i) + ':').toUpperCase();
if (!(hsLocation.startsWith(drive) || siaLocation.startsWith(drive))) { let driveInUse;
if (Object.keys(locations).length > 0) {
for (const provider of Constants.PROVIDER_LIST) {
driveInUse = locations[provider].startsWith(drive);
if (driveInUse)
break;
}
}
if (!driveInUse) {
try { try {
if (!fs.existsSync(drive)) { if (!fs.existsSync(drive)) {
driveLetters.Hyperspace.push(drive); for (const provider of Constants.PROVIDER_LIST) {
driveLetters.Sia.push(drive); driveLetters[provider].push(drive);
}
} }
} catch (e) { } catch (e) {
} }
} }
} }
if (hsLocation.length > 0) { if (Object.keys(locations).length > 0) {
if (!driveLetters.Hyperspace.find((driveLetter) => { for (const provider of Constants.PROVIDER_LIST) {
return driveLetter === hsLocation; if (locations[provider].length > 0) {
if (!driveLetters[provider].find((driveLetter) => {
return driveLetter === locations[provider];
})) { })) {
driveLetters.Hyperspace.push(hsLocation); driveLetters[provider].push(locations[provider]);
} }
} }
if (siaLocation.length > 0) {
if (!driveLetters.Sia.find((driveLetter) => {
return driveLetter === siaLocation;
})) {
driveLetters.Sia.push(siaLocation);
} }
} }
}; };
const setImage = (hsLocation, siaLocation) => { const setImage = (locations) => {
if (os.platform() === 'win32') { let driveInUse;
if (Object.keys(locations).length > 0) {
for (const provider of Constants.PROVIDER_LIST) {
driveInUse = locations[provider].length > 0;
if (driveInUse)
break;
}
}
let image; let image;
if ((siaLocation.length > 0) && (hsLocation.length > 0)) { if (driveInUse) {
image = nativeImage.createFromPath(path.join(__dirname, '/build/logo_both.png')); image = nativeImage.createFromPath(path.join(__dirname, os.platform() === 'darwin' ? '/build/logo_both_mac.png' : '/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 { } else {
image = nativeImage.createFromPath(path.join(__dirname, '/build/logo.png')); image = nativeImage.createFromPath(path.join(__dirname, os.platform() === 'darwin' ? '/build/logo_mac.png' : '/build/logo.png'));
} }
mainWindowTray.setImage(image); 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)
.then((results) => { .then((results) => {
let hsLocation = results.Hyperspace.Location; let storageData = {};
let siaLocation = results.Sia.Location; let locations = {};
if (os.platform() === 'win32') { for (const provider of Constants.PROVIDER_LIST) {
hsLocation = hsLocation.toUpperCase(); storageData[provider] = results[provider] ? results[provider] : {
siaLocation = siaLocation.toUpperCase(); Active: false,
grabDriveLetters(hsLocation, siaLocation); Location: '',
PID: -1,
};
locations[provider] = storageData[provider].Location;
if (storageData[provider].PID !== -1) {
expectedUnmount[provider] = false;
if (firstMountCheck) {
monitorMount(event.sender, provider, dataDirectory, data.Version, storageData[provider].PID, storageData[provider].Location);
}
}
}
if (os.platform() === 'win32') {
grabDriveLetters(locations);
}
setImage(locations);
if (firstMountCheck) {
firstMountCheck = false;
} }
setImage(hsLocation, siaLocation);
standardIPCReply(event, Constants.IPC_Detect_Mounts_Reply, { standardIPCReply(event, Constants.IPC_Detect_Mounts_Reply, {
DriveLetters: driveLetters, DriveLetters: driveLetters,
Locations: { Locations: locations,
Hyperspace: hsLocation,
Sia: siaLocation,
},
PIDS: {
Hyperspace: results.Hyperspace.PID,
Sia: results.Sia.PID,
}
}); });
}) })
.catch(error => { .catch(error => {
grabDriveLetters('', ''); if (os.platform() === 'win32') {
setImage('', ''); grabDriveLetters({});
}
setImage({});
standardIPCReply(event, Constants.IPC_Detect_Mounts_Reply, { standardIPCReply(event, Constants.IPC_Detect_Mounts_Reply, {
DriveLetters: driveLetters, DriveLetters: driveLetters,
}, error); }, error);
@@ -379,57 +591,119 @@ ipcMain.on(Constants.IPC_Grab_UI_Releases, (event) => {
}); });
ipcMain.on(Constants.IPC_Install_Dependency, (event, data) => { ipcMain.on(Constants.IPC_Install_Dependency, (event, data) => {
if (data.Source.toLowerCase().endsWith('.dmg')) {
helpers helpers
.executeAndWait(data.Source) .executeAsync('open', ['-a', 'Finder', '-W', data.Source])
.then(() => { .then(() => {
standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, { standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, {
Source: data.Source, Source: data.Source,
URL: data.URL,
}); });
}) })
.catch(error=> { .catch(error=> {
standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, { standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, {
Source: data.Source, Source: data.Source,
URL: data.URL,
}, error); }, error);
}); });
} else {
helpers
.executeAndWait(data.Source)
.then(() => {
standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, {
Source: data.Source,
URL: data.URL,
});
})
.catch(error => {
standardIPCReply(event, Constants.IPC_Install_Dependency_Reply, {
Source: data.Source,
URL: data.URL,
}, error);
});
}
}); });
ipcMain.on(Constants.IPC_Install_Upgrade, (event, data) => { ipcMain.on(Constants.IPC_Install_Upgrade, (event, data) => {
if (os.platform() === 'win32') {
helpers helpers
.executeAsync(data.Source) .executeAsync(data.Source)
.then(() => { .then(() => {
mainWindow.close(); closeApplication();
}) })
.catch(error => { .catch(error => {
standardIPCReply(event, Constants.IPC_Install_Upgrade_Reply, { standardIPCReply(event, Constants.IPC_Install_Upgrade_Reply, {
Source: data.Source, Source: data.Source,
}, error); }, error);
}); });
} else if (data.Source.toLocaleLowerCase().endsWith('.dmg')) {
helpers
.executeAsync('open', ['-a', 'Finder', data.Source])
.then(() => {
closeApplication();
})
.catch(error => {
standardIPCReply(event, Constants.IPC_Install_Upgrade_Reply, {
Source: data.Source,
}, error);
});
} else if (data.Source.toLocaleLowerCase().endsWith('.appimage')) {
// TODO Generate and execute script with delay
/*helpers
.executeAsync(data.Source)
.then(() => {
closeApplication();
})
.catch(error => {
standardIPCReply(event, Constants.IPC_Install_Upgrade_Reply, {
Source: data.Source,
}, error);
});*/
} else {
standardIPCReply(event, Constants.IPC_Install_Upgrade_Reply, {
Source: data.Source,
}, Error('Unsupported upgrade: ' + data.Source));
}
}); });
ipcMain.on(Constants.IPC_Mount_Drive, (event, data) => { ipcMain.on(Constants.IPC_Mount_Drive, (event, data) => {
expectedUnmount[data.StorageType] = false;
const dataDirectory = helpers.resolvePath(data.Directory); const dataDirectory = helpers.resolvePath(data.Directory);
if (mountedLocations.indexOf(data.Location) !== -1) {
console.log(data.StorageType + ' already mounted: ' + data.Location);
} else {
mountedLocations.push(data.Location);
mountedData[data.Location] = {
DataDirectory: dataDirectory,
Version: data.Version,
StorageType: data.StorageType,
};
const errorHandler = (pid, error) => { const errorHandler = (pid, error) => {
mountedPIDs.splice(mountedPIDs.indexOf(pid), 1); if (mountedLocations.indexOf(data.Location) !== -1) {
mountedLocations.splice(mountedLocations.indexOf(data.Location), 1);
delete mountedData[data.Location];
}
standardIPCReply(event, Constants.IPC_Unmount_Drive_Reply, { standardIPCReply(event, Constants.IPC_Unmount_Drive_Reply, {
PID: -1, Expected: expectedUnmount[data.StorageType],
Location: data.Location,
StorageType: data.StorageType, StorageType: data.StorageType,
}, error || Error(data.StorageType + ' Unmounted')); }, error || Error(data.StorageType + ' Unmounted'));
}; };
helpers.executeMount(dataDirectory, data.Version, data.StorageType, data.Location, (error, pid) => { helpers
.executeMount(dataDirectory, data.Version, data.StorageType, data.Location, data.NoConsoleSupported, (error, pid) => {
errorHandler(pid, error); errorHandler(pid, error);
}) })
.then(pid => { .then(() => {
if (pid !== -1) {
mountedPIDs.push(pid);
}
standardIPCReply(event, Constants.IPC_Mount_Drive_Reply, { standardIPCReply(event, Constants.IPC_Mount_Drive_Reply, {
PID: pid,
StorageType: data.StorageType, StorageType: data.StorageType,
}); });
}) })
.catch(error => { .catch(error => {
errorHandler(-1, error); errorHandler(-1, error);
}); });
}
}); });
ipcMain.on(Constants.IPC_Save_State, (event, data) => { ipcMain.on(Constants.IPC_Save_State, (event, data) => {
@@ -459,16 +733,18 @@ ipcMain.on(Constants.IPC_Set_Config_Values, (event, data) => {
}); });
ipcMain.on(Constants.IPC_Shutdown, () => { ipcMain.on(Constants.IPC_Shutdown, () => {
app.quit(); closeApplication();
}); });
ipcMain.on(Constants.IPC_Unmount_Drive, (event, data) => { ipcMain.on(Constants.IPC_Unmount_Drive, (event, data) => {
clearManualMountDetection(data.StorageType);
const dataDirectory = helpers.resolvePath(data.Directory);
expectedUnmount[data.StorageType] = true;
helpers helpers
.stopProcessByPID(data.PID) .stopMountProcess(dataDirectory, data.Version, data.StorageType)
.then((pid)=> { .then((result)=> {
if (mountedPIDs.indexOf(pid) === -1) { console.log(result);
event.sender.send(Constants.IPC_Unmount_Drive_Reply);
}
}) })
.catch((e) => { .catch((e) => {
console.log(e); console.log(e);

View File

@@ -4,6 +4,7 @@ const os = require('os');
const axios = require('axios'); const axios = require('axios');
const exec = require('child_process').exec; const exec = require('child_process').exec;
const spawn = require('child_process').spawn; const spawn = require('child_process').spawn;
const Constants = require('./src/constants');
const tryParse = (j, def) => { const tryParse = (j, def) => {
try { try {
@@ -37,18 +38,15 @@ module.exports.detectRepertoryMounts = (directory, version) => {
}); });
process.on('exit', () => { process.on('exit', () => {
resolve(tryParse(result, { let defaultData = {};
Hyperspace: { for (const provider of Constants.PROVIDER_LIST) {
defaultData[provider] = {
Active: false, Active: false,
Location: '', Location: '',
PID: -1, PID: -1,
}, };
Sia: { }
Active: false, resolve(tryParse(result, defaultData));
Location: '',
PID: -1,
},
}));
}); });
process.unref(); process.unref();
}); });
@@ -120,15 +118,15 @@ module.exports.executeAndWait = command => {
}); });
}; };
module.exports.executeAsync = (command) => { module.exports.executeAsync = (command, args=[]) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const launchProcess = (count, timeout) => { const launchProcess = (count, timeout) => {
const processOptions = { const processOptions = {
detached: true, detached: true,
shell: true, shell: false,
}; };
const process = new spawn(command, [], processOptions); const process = new spawn(command, args, processOptions);
const pid = process.pid; const pid = process.pid;
process.on('error', (err) => { process.on('error', (err) => {
@@ -141,11 +139,14 @@ module.exports.executeAsync = (command) => {
}); });
process.on('exit', (code) => { process.on('exit', (code) => {
if (code !== 0) {
if (++count === 5) { if (++count === 5) {
reject(code, pid); reject(code, pid);
} else { } else {
clearTimeout(timeout);
setTimeout(() => launchProcess(count, setTimeout(() => resolve(), 3000)), 1000); setTimeout(() => launchProcess(count, setTimeout(() => resolve(), 3000)), 1000);
} }
}
}); });
process.unref(); process.unref();
@@ -155,28 +156,33 @@ module.exports.executeAsync = (command) => {
}); });
}; };
module.exports.executeMount = (directory, version, storageType, location, exitCallback) => { module.exports.executeMount = (directory, version, storageType, location, noConsoleSupported, exitCallback) => {
return new Promise((resolve) => { return new Promise((resolve) => {
const processOptions = { const processOptions = {
detached: true, detached: false,
shell: true, shell: os.platform() !== 'darwin',
stdio: 'ignore',
}; };
const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory'); const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory');
const args = []; const args = [];
if (storageType.toLowerCase() === 'hyperspace') { if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) {
args.push('-hs'); args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]);
} }
if (os.platform() === 'linux') {
args.push("-o"); if ((os.platform() === 'linux') || (os.platform() === 'darwin')) {
args.push("big_writes"); args.push('-o');
args.push('big_writes');
args.push('-f');
if (noConsoleSupported) {
args.push('-nc');
} }
if (os.platform() === 'win32') { } else if (os.platform() === 'win32') {
args.push('-hidden'); args.push('-hidden');
} }
args.push(location); args.push(location);
const process = new spawn(command, args, processOptions); let process = new spawn(command, args, processOptions);
const pid = process.pid; const pid = process.pid;
const timeout = setTimeout(() => { const timeout = setTimeout(() => {
@@ -187,11 +193,11 @@ module.exports.executeMount = (directory, version, storageType, location, exitCa
clearTimeout(timeout); clearTimeout(timeout);
exitCallback(err, pid); exitCallback(err, pid);
}); });
process.on('exit', (code) => { process.on('exit', (code) => {
clearTimeout(timeout); clearTimeout(timeout);
exitCallback(code, pid); exitCallback(code, pid);
}); });
process.unref();
}); });
}; };
@@ -206,8 +212,8 @@ module.exports.getConfig = (directory, version, storageType) => {
const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory'); const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory');
const args = []; const args = [];
args.push('-dc'); args.push('-dc');
if (storageType.toLowerCase() === 'hyperspace') { if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) {
args.push('-hs'); args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]);
} }
const process = new spawn(command, args, processOptions); const process = new spawn(command, args, processOptions);
@@ -252,8 +258,8 @@ module.exports.getConfigTemplate = (directory, version, storageType) => {
const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory'); const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory');
const args = []; const args = [];
args.push('-gt'); args.push('-gt');
if (storageType.toLowerCase() === 'hyperspace') { if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) {
args.push('-hs'); args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]);
} }
const process = new spawn(command, args, processOptions); const process = new spawn(command, args, processOptions);
@@ -276,8 +282,8 @@ module.exports.getConfigTemplate = (directory, version, storageType) => {
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) {
reject(Error('Dependency list is empty')); reject(Error('Dependency list is invalid'));
} }
let missing = []; let missing = [];
@@ -334,7 +340,7 @@ module.exports.getMissingDependencies = dependencies => {
} else { } else {
for (const dep of dependencies) { for (const dep of dependencies) {
try { try {
if (!fs.lstatSync(dep.file).isFile()) { if (!(fs.lstatSync(dep.file).isFile() || fs.lstatSync(dep.file).isSymbolicLink())) {
missing.push(dep); missing.push(dep);
} }
} catch (e) { } catch (e) {
@@ -415,8 +421,8 @@ module.exports.setConfigValue = (name, value, directory, storageType, version) =
args.push('-set'); args.push('-set');
args.push(name); args.push(name);
args.push(value); args.push(value);
if (storageType.toLowerCase() === 'hyperspace') { if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) {
args.push('-hs'); args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]);
} }
const process = new spawn(command, args, processOptions); const process = new spawn(command, args, processOptions);
@@ -433,27 +439,51 @@ module.exports.setConfigValue = (name, value, directory, storageType, version) =
}); });
}; };
module.exports.stopProcessByPID = pid => { module.exports.stopMountProcess = (directory, version, storageType) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const processOptions = { const processOptions = {
detached: true, detached: os.platform() === 'darwin',
shell: false, shell: os.platform() !== 'darwin',
windowsHide: true, windowsHide: true,
}; };
const command = (os.platform() === 'win32') ? 'taskkill.exe' : ''; const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory');
const args = []; const args = ['-unmount'];
args.push('/PID'); if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) {
args.push(pid); args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]);
}
const process = new spawn(command, args, processOptions); const process = new spawn(command, args, processOptions);
const pid = process.pid;
process.on('error', (err) => { process.on('error', (err) => {
reject(err); reject(err);
}); });
process.on('exit', () => { process.on('exit', (code) => {
setTimeout(()=>resolve(pid), 3000); resolve({
PID: pid,
Code: code,
}); });
});
if (os.platform() === 'darwin') {
process.unref(); process.unref();
}
}); });
}; };
module.exports.stopMountProcessSync = (directory, version, storageType) => {
const processOptions = {
detached: true,
shell: os.platform() !== 'darwin',
windowsHide: true,
};
const command = path.join(directory, version, (os.platform() === 'win32') ? 'repertory.exe' : 'repertory');
const args = ['-unmount'];
if (Constants.PROVIDER_ARG[storageType.toLowerCase()].length > 0) {
args.push(Constants.PROVIDER_ARG[storageType.toLowerCase()]);
}
const process = new spawn(command, args, processOptions);
process.unref();
};

View File

@@ -1,10 +1,13 @@
{ {
"name": "repertory-ui", "name": "repertory-ui",
"version": "1.0.1", "version": "1.0.2",
"private": true, "private": true,
"author": "scott.e.graves@gmail.com",
"description": "GUI for Repertory - Repertory allows you to mount Hyperspace, Sia and/or SiaPrime blockchain storage solutions via FUSE on Linux/OS X or via WinFSP on Windows.",
"license": "MIT",
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.1", "@fortawesome/fontawesome-svg-core": "^1.2.10",
"@fortawesome/free-solid-svg-icons": "^5.1.1", "@fortawesome/free-solid-svg-icons": "^5.6.1",
"@fortawesome/react-fontawesome": "^0.1.0", "@fortawesome/react-fontawesome": "^0.1.0",
"auto-launch": "^5.0.5", "auto-launch": "^5.0.5",
"autoprefixer": "7.1.6", "autoprefixer": "7.1.6",
@@ -17,12 +20,12 @@
"babel-runtime": "6.26.0", "babel-runtime": "6.26.0",
"case-sensitive-paths-webpack-plugin": "2.1.1", "case-sensitive-paths-webpack-plugin": "2.1.1",
"chalk": "1.1.3", "chalk": "1.1.3",
"color": "^3.0.0", "color": "^3.1.0",
"color-string": "^1.5.2", "color-string": "^1.5.2",
"css-loader": "0.28.7", "css-loader": "0.28.7",
"dotenv": "4.0.0", "dotenv": "4.0.0",
"dotenv-expand": "4.2.0", "dotenv-expand": "4.2.0",
"electron-debug": "^2.0.0", "electron-debug": "^2.1.0",
"eslint": "4.10.0", "eslint": "4.10.0",
"eslint-config-react-app": "^2.1.0", "eslint-config-react-app": "^2.1.0",
"eslint-loader": "1.9.0", "eslint-loader": "1.9.0",
@@ -35,23 +38,23 @@
"fs-extra": "3.0.1", "fs-extra": "3.0.1",
"html-webpack-plugin": "2.29.0", "html-webpack-plugin": "2.29.0",
"jest": "20.0.4", "jest": "20.0.4",
"node-schedule": "^1.3.0", "node-schedule": "^1.3.1",
"npm": "^6.2.0", "npm": "^6.6.0",
"object-assign": "4.1.1", "object-assign": "4.1.1",
"postcss-flexbugs-fixes": "3.2.0", "postcss-flexbugs-fixes": "3.2.0",
"postcss-loader": "2.0.8", "postcss-loader": "2.0.8",
"promise": "8.0.1", "promise": "8.0.1",
"raf": "3.4.0", "raf": "3.4.0",
"react": "^16.4.1", "react": "^16.6.1",
"react-css-modules": "^4.7.4", "react-css-modules": "^4.7.8",
"react-dev-utils": "^5.0.1", "react-dev-utils": "^5.0.3",
"react-dom": "^16.4.1", "react-dom": "^16.6.1",
"react-loader-spinner": "^2.0.6", "react-loader-spinner": "^2.3.0",
"react-tooltip": "^3.8.4", "react-tooltip": "^3.9.0",
"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",
"unzipper": "^0.9.3", "unzipper": "^0.9.6",
"url-loader": "0.6.2", "url-loader": "0.6.2",
"webpack": "3.8.1", "webpack": "3.8.1",
"webpack-dev-server": "2.9.4", "webpack-dev-server": "2.9.4",
@@ -63,14 +66,20 @@
"start": "node scripts/start.js", "start": "node scripts/start.js",
"build": "node scripts/build.js", "build": "node scripts/build.js",
"test": "node scripts/test.js --env=jsdom", "test": "node scripts/test.js --env=jsdom",
"electron-dev": "cross-env ELECTRON_START_URL=http://localhost:3000 electron .", "electron-dev": "cross-env ELECTRON_START_URL=http://localhost:3000 electron %NODE_DEBUG_OPTION% .",
"pack": "npm run build && electron-builder --dir", "electron-dev-unix": "cross-env ELECTRON_START_URL=http://localhost:3000 electron $NODE_DEBUG_OPTION .",
"dist": "npm run build && electron-builder" "pack": "npm run build && electron-builder --dir --x64",
"dist": "npm run build && electron-builder --x64",
"dist-all": "npm run build && electron-builder --x64 --win --linux --mac",
"dist-mac": "npm run build && electron-builder --x64 --mac",
"dist-linux": "npm run build && electron-builder --x64 --linux",
"dist-win": "npm run build && electron-builder --x64 --win",
"postinstall": "electron-builder install-app-deps"
}, },
"devDependencies": { "devDependencies": {
"cross-env": "^5.2.0", "cross-env": "^5.2.0",
"electron": "^3.0.2", "electron": "^4.1.0",
"electron-builder": "^20.28.4", "electron-builder": "^20.38.5",
"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"
}, },
@@ -120,6 +129,7 @@
"homepage": "./", "homepage": "./",
"build": { "build": {
"appId": "repertory-ui", "appId": "repertory-ui",
"artifactName": "${productName}_${version}_${os}_${arch}.${ext}",
"files": [ "files": [
"./electron.js", "./electron.js",
"./src/constants.js", "./src/constants.js",
@@ -128,10 +138,19 @@
"./helpers.js" "./helpers.js"
], ],
"linux": { "linux": {
"icon": "./build/icon.icns" "category": "Utility",
"icon": "./build/logo.png",
"target": "AppImage"
},
"mac": {
"category": "public.app-category.utilities",
"icon": "./build/icon_color.icns",
"target": "dmg",
"darkModeSupport": true
}, },
"win": { "win": {
"icon": "./build/icon.ico" "icon": "./build/icon.ico",
"target": "nsis"
}, },
"directories": { "directories": {
"buildResources": "public" "buildResources": "public"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

BIN
public/icon_color.icns Normal file

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 361 KiB

BIN
public/logo.xcf Normal file

Binary file not shown.

BIN
public/logo_both_mac.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

BIN
public/logo_mac.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

View File

@@ -1,24 +1,24 @@
{ {
"Locations": { "Locations": {
"win32": { "win32": {
"1.0.1": { "1.0.2": {
"hash": "", "hash": "",
"urls": [ "urls": ["https://pixeldrain.com/u/4oJeVntd"]
"https://sia.pixeldrain.com/api/file/Alo1IF1u" }
]
}, },
"1.0.0": { "darwin": {
"1.0.2": {
"hash": "", "hash": "",
"urls": [ "urls": ["https://pixeldrain.com/u/OtxPlbOI"]
"https://sia.pixeldrain.com/api/file/qXM6pVZZ"
]
} }
} }
}, },
"Versions": { "Versions": {
"win32": [ "win32": [
"1.0.1", "1.0.2"
"1.0.0" ],
"darwin": [
"1.0.2"
] ]
} }
} }

View File

@@ -1,4 +1,4 @@
import React, {Component} from 'react'; import React from 'react';
import axios from 'axios'; 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';
@@ -15,35 +15,37 @@ import ReleaseVersionDisplay from './components/ReleaseVersionDisplay/ReleaseVer
import Text from './components/UI/Text/Text'; 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'; import UpgradeUI from './components/UpgradeUI/UpgradeUI';
import IPCContainer from './containers/IPCContainer/IPCContainer';
const Constants = require('./constants'); const Constants = require('./constants');
const Scheduler = require('node-schedule'); const Scheduler = require('node-schedule');
let ipcRenderer = null; class App extends IPCContainer {
if (!process.versions.hasOwnProperty('electron')) {
ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null);
}
class App extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
if (ipcRenderer) { for (const provider of Constants.PROVIDER_LIST) {
ipcRenderer.on(Constants.IPC_Check_Installed_Reply, this.onCheckInstalledReply); this.state[provider] = {
ipcRenderer.on(Constants.IPC_Download_File_Complete, this.onDownloadFileComplete); AutoMount: false,
ipcRenderer.on(Constants.IPC_Download_File_Progress, this.onDownloadFileProgress); AutoRestart: false,
ipcRenderer.on(Constants.IPC_Extract_Release_Complete, this.onExtractReleaseComplete); MountLocation: '',
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.send(Constants.IPC_Get_State, Constants.DATA_LOCATIONS[this.props.platform]);
Scheduler.scheduleJob('23 11 * * *', this.updateCheckScheduledJob);
} }
} }
this.setRequestHandler(Constants.IPC_Check_Installed_Reply, this.onCheckInstalledReply);
this.setRequestHandler(Constants.IPC_Download_File_Complete, this.onDownloadFileComplete);
this.setRequestHandler(Constants.IPC_Download_File_Progress, this.onDownloadFileProgress);
this.setRequestHandler(Constants.IPC_Extract_Release_Complete, this.onExtractReleaseComplete);
this.setRequestHandler(Constants.IPC_Get_State_Reply, this.onGetStateReply);
this.setRequestHandler(Constants.IPC_Grab_Releases_Reply, this.onGrabReleasesReply);
this.setRequestHandler(Constants.IPC_Grab_UI_Releases_Reply, this.onGrabUiReleasesReply);
this.setRequestHandler(Constants.IPC_Install_Dependency_Reply, this.onInstallDependencyReply);
this.setRequestHandler(Constants.IPC_Install_Upgrade_Reply, this.onInstallUpgradeReply);
this.sendRequest(Constants.IPC_Get_State, Constants.DATA_LOCATIONS[this.props.platform]);
Scheduler.scheduleJob('23 11 * * *', this.updateCheckScheduledJob);
}
state = { state = {
AllowDownload: false, AllowDownload: false,
AutoMountProcessed: false, AutoMountProcessed: false,
@@ -60,14 +62,9 @@ class App extends Component {
DownloadingRelease: false, DownloadingRelease: false,
DownloadingUpgrade: false, DownloadingUpgrade: false,
ExtractActive: false, ExtractActive: false,
Hyperspace: {
AutoMount: false,
MountLocation: '',
},
LocationsLookup: {}, LocationsLookup: {},
MissingDependencies: [], MissingDependencies: [],
MountsBusy: false, MountsBusy: false,
Platform: 'unknown',
Release: 3, Release: 3,
ReleaseTypes: [ ReleaseTypes: [
'Release', 'Release',
@@ -76,10 +73,6 @@ class App extends Component {
'Alpha', 'Alpha',
], ],
InstalledVersion: 'none', InstalledVersion: 'none',
Sia: {
AutoMount: false,
MountLocation: '',
},
UpgradeAvailable: false, UpgradeAvailable: false,
UpgradeData: {}, UpgradeData: {},
UpgradeDismissed: false, UpgradeDismissed: false,
@@ -101,30 +94,24 @@ class App extends Component {
} }
}; };
checkVersionInstalled = (release, version, versionLookup) => { checkVersionInstalled = () => {
if (!versionLookup) {
versionLookup = this.state.VersionLookup;
}
const selectedVersion = versionLookup[this.state.ReleaseTypes[release]][version];
this.setState({ this.setState({
AllowDownload: false, AllowDownload: false,
}); }, ()=> {
const selectedVersion = this.getSelectedVersion();
if (selectedVersion !== 'unavailable') { if (selectedVersion !== 'unavailable') {
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(Constants.IPC_Check_Installed, { this.sendRequest(Constants.IPC_Check_Installed, {
Dependencies: dependencies, Dependencies: dependencies,
Directory: Constants.DATA_LOCATIONS[this.props.platform], Directory: Constants.DATA_LOCATIONS[this.props.platform],
Version: selectedVersion, Version: selectedVersion,
}); });
} }
} });
}; };
closeErrorDisplay = () => { closeErrorDisplay = () => {
@@ -133,9 +120,7 @@ class App extends Component {
} }
if (this.state.ErrorCritical) { if (this.state.ErrorCritical) {
if (ipcRenderer) { this.sendRequest(Constants.IPC_Shutdown);
ipcRenderer.send(Constants.IPC_Shutdown);
}
} else { } else {
this.setState({ this.setState({
DisplayError: false, DisplayError: false,
@@ -144,51 +129,63 @@ class App extends Component {
} }
}; };
componentWillUnmount = () => { extractFileNameFromURL = url => {
if (ipcRenderer) { const parts = url.split('/');
ipcRenderer.removeListener(Constants.IPC_Check_Installed_Reply, this.onCheckInstalledReply); return parts[parts.length - 1];
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); extractRelease = (data) => {
ipcRenderer.removeListener(Constants.IPC_Get_State_Reply, this.onGetStateReply); if (data.Success) {
ipcRenderer.removeListener(Constants.IPC_Grab_Releases_Reply, this.onGrabReleasesReply); const selectedVersion = this.getSelectedVersion();
ipcRenderer.removeListener(Constants.IPC_Grab_UI_Releases_Reply, this.onGrabUiReleasesReply); this.sendRequest(Constants.IPC_Extract_Release, {
ipcRenderer.removeListener(Constants.IPC_Install_Dependency_Reply, this.onInstallDependencyReply); Directory: Constants.DATA_LOCATIONS[this.props.platform],
ipcRenderer.removeListener(Constants.IPC_Install_Upgrade_Reply, this.onInstallUpgradeReply); Source: data.Destination,
Version: selectedVersion,
});
} }
this.setState({
DownloadActive: false,
DownloadProgress: 0.0,
DownloadingRelease: false,
ExtractActive: data.Success,
DownloadName: '',
});
};
getSelectedVersion = () => {
return this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]][this.state.Version];
}; };
grabReleases = () => { grabReleases = () => {
if (this.props.platform !== 'unknown') { if (this.props.platform !== 'unknown') {
if (ipcRenderer) { this.sendRequest(Constants.IPC_Grab_Releases);
ipcRenderer.send(Constants.IPC_Grab_Releases); this.sendRequest(Constants.IPC_Grab_UI_Releases);
ipcRenderer.send(Constants.IPC_Grab_UI_Releases);
}
} }
}; };
handleAutoMountChanged = (storageType, e) => { handleAutoMountChanged = (storageType, e) => {
let sia = { const state = {
...this.state.Sia ...this.state[storageType],
AutoMount: e.target.checked,
};
this.setState({
[storageType]: state,
}, ()=> {
this.saveState();
});
}; };
let hyperspace = { handleAutoRestartChanged = (storageType, e) => {
...this.state.Hyperspace const state = {
...this.state[storageType],
AutoRestart: e.target.checked,
}; };
if (storageType === 'Hyperspace') {
hyperspace.AutoMount = e.target.checked;
this.setState({ this.setState({
Hyperspace: hyperspace, [storageType]: state,
}, ()=> {
this.saveState();
}); });
} else if (storageType === 'Sia') {
sia.AutoMount = e.target.checked;
this.setState({
Sia: sia,
});
}
this.saveState(this.state.Release, this.state.Version, sia, hyperspace);
}; };
handleConfigClicked = (storageType) => { handleConfigClicked = (storageType) => {
@@ -204,22 +201,17 @@ class App extends Component {
}; };
handleDependencyDownload = (url) => { handleDependencyDownload = (url) => {
if (ipcRenderer) {
const items = url.split('/');
const fileName = items[items.length - 1];
this.setState({ this.setState({
DownloadActive: true, DownloadActive: true,
DownloadingDependency: true, DownloadingDependency: true,
DownloadName: fileName, DownloadName: this.extractFileNameFromURL(url),
}); }, ()=> {
this.sendRequest(Constants.IPC_Download_File, {
ipcRenderer.send(Constants.IPC_Download_File, {
Directory: Constants.DATA_LOCATIONS[this.props.platform], Directory: Constants.DATA_LOCATIONS[this.props.platform],
Filename: fileName, Filename: this.state.DownloadName,
URL: url, URL: url,
}); });
} });
}; };
handleMountLocationChanged = (storageType, location) => { handleMountLocationChanged = (storageType, location) => {
@@ -229,15 +221,9 @@ class App extends Component {
}; };
this.setState({ this.setState({
[storageType]: state, [storageType]: state,
}, ()=> {
this.saveState();
}); });
const hyperspace = (storageType === 'Hyperspace') ? state : {
...this.state.Hyperspace,
};
const sia = storageType === 'Sia' ? state : {
...this.state.Sia,
};
this.saveState(this.state.Release, this.state.Version, sia, hyperspace);
}; };
handleReleaseChanged = (e) => { handleReleaseChanged = (e) => {
@@ -246,54 +232,81 @@ class App extends Component {
this.setState({ this.setState({
Release: val, Release: val,
Version: versionIndex Version: versionIndex
}, ()=> {
this.saveState();
this.checkVersionInstalled( );
}); });
this.saveState(val, versionIndex, this.state.Sia, this.state.Hyperspace);
this.checkVersionInstalled(val, versionIndex);
}; };
handleReleaseDownload = () => { handleReleaseDownload = () => {
const selectedVersion = this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]][this.state.Version]; const selectedVersion = this.getSelectedVersion();
const fileName = selectedVersion + '.zip'; const fileName = selectedVersion + '.zip';
if (ipcRenderer) {
this.setState({ this.setState({
DownloadActive: true, DownloadActive: true,
DownloadingRelease: true, DownloadingRelease: true,
DownloadName: fileName, DownloadName: fileName,
}); }, () => {
this.sendRequest(Constants.IPC_Download_File, {
ipcRenderer.send(Constants.IPC_Download_File, {
Directory: Constants.DATA_LOCATIONS[this.props.platform], Directory: Constants.DATA_LOCATIONS[this.props.platform],
Filename: fileName, Filename: this.state.DownloadName,
URL: this.state.LocationsLookup[selectedVersion].urls[0], URL: this.state.LocationsLookup[selectedVersion].urls[0],
}); });
} });
}; };
handleUIDownload = () => { handleUIDownload = () => {
if (ipcRenderer) {
this.setState({ this.setState({
DownloadActive: true, DownloadActive: true,
DownloadingUpgrade: true, DownloadingUpgrade: true,
DownloadName: 'UI Upgrade', DownloadName: 'UI Upgrade',
}); }, ()=> {
const url = this.state.UpgradeData.urls[0];
ipcRenderer.send(Constants.IPC_Download_File, { this.sendRequest(Constants.IPC_Download_File, {
Directory: Constants.DATA_LOCATIONS[this.props.platform], Directory: Constants.DATA_LOCATIONS[this.props.platform],
Filename: this.props.platform === 'win32' ? 'upgrade.exe' : 'upgrade', Filename: this.props.platform === 'win32' ? 'upgrade.exe' : this.extractFileNameFromURL(url),
URL: this.state.UpgradeData.urls[0], URL: url,
});
}); });
} else {
this.setState({UpgradeDismissed: true});
}
}; };
handleVersionChanged = (e) => { handleVersionChanged = (e) => {
const val = parseInt(e.target.value, 10);
this.setState({ this.setState({
Version: val Version: parseInt(e.target.value, 10),
}, ()=> {
this.saveState();
this.checkVersionInstalled( );
}); });
this.saveState(this.state.Release, val, this.state.Sia, this.state.Hyperspace); };
this.checkVersionInstalled(this.state.Release, val);
installDependency = data => {
if (data.Success) {
this.sendRequest(Constants.IPC_Install_Dependency, {
Source: data.Destination,
URL: data.URL,
});
}
this.setState({
DownloadActive: false,
DownloadProgress: 0.0,
DownloadingDependency: data.Success,
DownloadName: '',
});
};
installUpgrade = data => {
if (data.Success) {
this.sendRequest(Constants.IPC_Install_Upgrade, {
Source: data.Destination,
});
} else {
this.setState({
DownloadActive: false,
DownloadProgress: 0.0,
DownloadingUpgrade: false,
DownloadName: '',
});
}
}; };
notifyAutoMountProcessed = () => { notifyAutoMountProcessed = () => {
@@ -336,48 +349,11 @@ class App extends Component {
onDownloadFileComplete = (event, arg) => { onDownloadFileComplete = (event, arg) => {
if (this.state.DownloadingRelease) { if (this.state.DownloadingRelease) {
if (arg.data.Success) { this.extractRelease(arg.data);
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) { } else if (this.state.DownloadingDependency) {
if (arg.data.Success) { this.installDependency(arg.data);
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) { } else if (this.state.DownloadingUpgrade) {
if (arg.data.Success) { this.installUpgrade(arg.data);
ipcRenderer.send(Constants.IPC_Install_Upgrade, {
Source: arg.data.Destination,
});
} else {
this.setState({
DownloadActive: false,
DownloadProgress: 0.0,
DownloadingUpgrade: false,
DownloadName: '',
});
}
} else { } else {
this.setState({ this.setState({
DownloadActive: false, DownloadActive: false,
@@ -394,32 +370,41 @@ class App extends Component {
}; };
onExtractReleaseComplete = (event, arg) => { onExtractReleaseComplete = (event, arg) => {
ipcRenderer.send(Constants.IPC_Delete_File, { this.sendRequest(Constants.IPC_Delete_File, {
FilePath: arg.data.Source, FilePath: arg.data.Source,
}); });
this.setState({ this.setState({
ExtractActive: false, ExtractActive: false,
}, ()=> {
this.checkVersionInstalled( );
}); });
this.checkVersionInstalled(this.state.Release, this.state.Version);
}; };
onGetStateReply = (event, arg) => { onGetStateReply = (event, arg) => {
if (arg.data) { if (arg.data) {
if (arg.data.Hyperspace.AutoMount === undefined) { let state = {
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, Release: arg.data.Release,
Sia: arg.data.Sia,
Version: arg.data.Version, Version: arg.data.Version,
}); };
for (const provider of Constants.PROVIDER_LIST) {
let data = arg.data[provider] || this.state[provider];
if (data.AutoMount === undefined) {
data['AutoMount'] = false;
} }
if (data.AutoRestart === undefined) {
data['AutoRestart'] = false;
}
state[provider] = data;
}
this.setState(state, ()=> {
this.grabReleases(); this.grabReleases();
});
} else {
this.grabReleases();
}
}; };
onGrabReleasesReply = ()=> { onGrabReleasesReply = ()=> {
@@ -428,7 +413,7 @@ class App extends Component {
let version = this.state.Version; let version = this.state.Version;
if ((version === -1) || !versionLookup[this.state.ReleaseTypes[this.state.Release]][version]) { if ((version === -1) || !versionLookup[this.state.ReleaseTypes[this.state.Release]][version]) {
version = latestVersion; version = latestVersion;
this.saveState(this.state.Release, version, this.state.Sia, this.state.Hyperspace); this.saveState(version);
} }
this.setState({ this.setState({
@@ -437,12 +422,13 @@ class App extends Component {
Version: version, Version: version,
VersionAvailable: version !== latestVersion, VersionAvailable: version !== latestVersion,
VersionLookup: versionLookup, VersionLookup: versionLookup,
}, () => {
this.checkVersionInstalled( );
}); });
this.checkVersionInstalled(this.state.Release, version, versionLookup);
}; };
axios.get(Constants.RELEASES_URL) axios
.get(Constants.RELEASES_URL)
.then(response => { .then(response => {
const versionLookup = { const versionLookup = {
Alpha: response.data.Versions.Alpha[this.props.platform], Alpha: response.data.Versions.Alpha[this.props.platform],
@@ -475,7 +461,8 @@ class App extends Component {
}; };
onGrabUiReleasesReply = ()=> { onGrabUiReleasesReply = ()=> {
axios.get(Constants.UI_RELEASES_URL) axios
.get(Constants.UI_RELEASES_URL)
.then(response => { .then(response => {
const data = response.data; const data = response.data;
if (data.Versions && if (data.Versions &&
@@ -497,14 +484,18 @@ class App extends Component {
}; };
onInstallDependencyReply = (event, arg) => { onInstallDependencyReply = (event, arg) => {
ipcRenderer.send(Constants.IPC_Delete_File, { if (arg.data.Success && arg.data.Source.toLowerCase().endsWith('.dmg')) {
this.waitForDependencyInstall(arg.data.URL);
} else {
this.sendRequest(Constants.IPC_Delete_File, {
FilePath: arg.data.Source, FilePath: arg.data.Source,
}); });
this.checkVersionInstalled(this.state.Release, this.state.Version); this.checkVersionInstalled();
}
}; };
onInstallUpgradeReply = (event, arg) => { onInstallUpgradeReply = (event, arg) => {
ipcRenderer.sendSync(Constants.IPC_Delete_File, { this.sendSyncRequest(Constants.IPC_Delete_File, {
FilePath: arg.data.Source, FilePath: arg.data.Source,
}); });
@@ -515,18 +506,19 @@ class App extends Component {
}); });
}; };
saveState = (release, version, sia, hyperspace)=> { saveState = version => {
if (ipcRenderer) { let state = {
ipcRenderer.send(Constants.IPC_Save_State, { Release: this.state.Release,
Version: version || this.state.Version,
};
for (const provider of Constants.PROVIDER_LIST) {
state[provider] = this.state[provider];
}
this.sendRequest(Constants.IPC_Save_State, {
Directory: Constants.DATA_LOCATIONS[this.props.platform], Directory: Constants.DATA_LOCATIONS[this.props.platform],
State: { State: state
Hyperspace: hyperspace,
Release: release,
Sia: sia,
Version: version,
}
}); });
}
}; };
setErrorState = (error, action, critical) => { setErrorState = (error, action, critical) => {
@@ -544,10 +536,28 @@ class App extends Component {
} }
}; };
waitForDependencyInstall = (url) => {
const dep = this.state.MissingDependencies.find(d => {
return d.download === url;
});
const i = setInterval(()=> {
const ret = this.sendSyncRequest(Constants.IPC_Check_Dependency_Installed, {
File: dep.file,
});
if (ret.data.Exists) {
clearInterval(i);
setTimeout(() => {
this.checkVersionInstalled();
}, 10000);
}
}, 3000);
};
render() { render() {
const selectedVersion = (this.state.Version === -1) ? const selectedVersion = (this.state.Version === -1) ?
'unavailable' : 'unavailable' :
this.state.VersionLookup[this.state.ReleaseTypes[this.state.Release]][this.state.Version]; this.getSelectedVersion();
const downloadEnabled = this.state.AllowDownload && const downloadEnabled = this.state.AllowDownload &&
!this.state.MountsBusy && !this.state.MountsBusy &&
@@ -555,11 +565,18 @@ class App extends Component {
(selectedVersion !== 'unavailable') && (selectedVersion !== 'unavailable') &&
(selectedVersion !== this.state.InstalledVersion); (selectedVersion !== this.state.InstalledVersion);
const allowMount = this.state.InstalledVersion !== 'none';
const missingDependencies = (this.state.MissingDependencies.length > 0); const missingDependencies = (this.state.MissingDependencies.length > 0);
const allowMount = this.state.InstalledVersion !== 'none' && !missingDependencies;
const allowConfig = this.state.LocationsLookup[selectedVersion] && const allowConfig = this.state.LocationsLookup[selectedVersion] &&
this.state.LocationsLookup[selectedVersion].config_support; this.state.LocationsLookup[selectedVersion].config_support;
const allowSiaPrime = this.state.LocationsLookup[selectedVersion] &&
this.state.LocationsLookup[selectedVersion].siaprime_support;
const noConsoleSupported = this.state.LocationsLookup[selectedVersion] &&
this.state.LocationsLookup[selectedVersion].no_console_supported;
const showDependencies = missingDependencies && const showDependencies = missingDependencies &&
!this.state.DownloadActive; !this.state.DownloadActive;
@@ -605,9 +622,8 @@ class App extends Component {
<DependencyList allowDownload={!this.state.DownloadingDependency} <DependencyList allowDownload={!this.state.DownloadingDependency}
dependencies={this.state.MissingDependencies} dependencies={this.state.MissingDependencies}
onDownload={this.handleDependencyDownload}/> onDownload={this.handleDependencyDownload}/>
}
</Modal> </Modal>
) );
} }
let downloadDisplay = null; let downloadDisplay = null;
@@ -616,7 +632,8 @@ class App extends Component {
<Modal> <Modal>
<DownloadProgress display={this.state.DownloadName} <DownloadProgress display={this.state.DownloadName}
progress={this.state.DownloadProgress}/> progress={this.state.DownloadProgress}/>
</Modal>); </Modal>
);
} }
let upgradeDisplay = null; let upgradeDisplay = null;
@@ -634,7 +651,7 @@ class App extends Component {
let key = 0; let key = 0;
mainContent.push(( mainContent.push((
<div key={'rvd_' + key++} <div key={'rvd_' + key++}
style={{height: '44%'}}> style={{height: '25%'}}>
<ReleaseVersionDisplay disabled={this.state.DownloadActive || this.state.ExtractActive || this.state.MountsBusy} <ReleaseVersionDisplay disabled={this.state.DownloadActive || this.state.ExtractActive || this.state.MountsBusy}
downloadClicked={this.handleReleaseDownload} downloadClicked={this.handleReleaseDownload}
downloadDisabled={!downloadEnabled} downloadDisabled={!downloadEnabled}
@@ -651,21 +668,27 @@ class App extends Component {
)); ));
if (allowMount) { if (allowMount) {
let providerProps = {};
for (const provider of Constants.PROVIDER_LIST) {
const providerLower = provider.toLowerCase();
providerProps[providerLower] = this.state[provider];
}
mainContent.push(( mainContent.push((
<div key={'md_' + key++} <div key={'md_' + key++}>
style={{height: '56%'}}> <MountItems {...providerProps}
<MountItems allowConfig={allowConfig} allowConfig={allowConfig}
allowSiaPrime={allowSiaPrime}
noConsoleSupported={noConsoleSupported}
autoMountChanged={this.handleAutoMountChanged} autoMountChanged={this.handleAutoMountChanged}
autoMountProcessed={this.notifyAutoMountProcessed} autoMountProcessed={this.notifyAutoMountProcessed}
autoRestartChanged={this.handleAutoRestartChanged}
changed={this.handleMountLocationChanged} changed={this.handleMountLocationChanged}
configClicked={this.handleConfigClicked} configClicked={this.handleConfigClicked}
directory={Constants.DATA_LOCATIONS[this.props.platform]} directory={Constants.DATA_LOCATIONS[this.props.platform]}
errorHandler={this.setErrorState} errorHandler={this.setErrorState}
hyperspace={this.state.Hyperspace}
mountsBusy={this.notifyMountsBusy} mountsBusy={this.notifyMountsBusy}
platform={this.props.platform} platform={this.props.platform}
processAutoMount={!this.state.AutoMountProcessed} processAutoMount={!this.state.AutoMountProcessed}
sia={this.state.Sia}
version={this.state.InstalledVersion}/> version={this.state.InstalledVersion}/>
</div> </div>
)); ));
@@ -698,12 +721,12 @@ class App extends Component {
col={dimensions => dimensions.columns - 6} col={dimensions => dimensions.columns - 6}
colSpan={5} colSpan={5}
row={1} row={1}
rowSpan={remain=>remain - 2}/> rowSpan={remain=>remain - 1}/>
</Grid> </Grid>
</Box> </Box>
</div> </div>
<div styleName='Content'> <div styleName='Content'>
<Box dxStyle={{padding: '8px'}}> <Box dxStyle={{padding: '8px 8px 0px 8px'}}>
{mainContent} {mainContent}
</Box> </Box>
</div> </div>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 77 KiB

After

Width:  |  Height:  |  Size: 70 KiB

View File

@@ -0,0 +1,12 @@
input.Input {
margin: 0;
padding: 3px;
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;
width: 100%;
}

View File

@@ -18,7 +18,7 @@ export default CSSModules((props) => {
rowSpan={6}> rowSpan={6}>
<img alt='' <img alt=''
height={'16px'} height={'16px'}
onClick={props.configClicked} onClick={props.disabled ? (e)=>{e.preventDefault();} : props.configClicked}
src={configureImage} src={configureImage}
style={{padding: 0, border: 0, margin: 0, cursor: 'pointer'}} style={{padding: 0, border: 0, margin: 0, cursor: 'pointer'}}
width={'16px'}/> width={'16px'}/>
@@ -27,31 +27,47 @@ export default CSSModules((props) => {
} }
let inputColumnSpan; let inputColumnSpan;
let inputControl = null; let inputControls = null;
if (props.platform === 'win32') { if (props.platform === 'win32') {
inputColumnSpan = 20; inputColumnSpan = 20;
inputControl = <DropDown changed={props.changed} inputControls = <DropDown changed={props.changed}
colSpan={inputColumnSpan} colSpan={inputColumnSpan}
disabled={!props.allowMount || props.mounted} disabled={!props.allowMount || props.mounted || props.disabled}
items={props.items} items={props.items}
row={secondRow} row={secondRow}
rowSpan={7} rowSpan={7}
selected={props.items.indexOf(props.location)}/>; selected={props.items.indexOf(props.location)}/>;
} else { } else {
inputColumnSpan = 60; inputColumnSpan = 58;
inputControl = ( inputControls = [];
<RootElem colSpan={inputColumnSpan} let key = 0;
inputControls.push((
<RootElem colSpan={inputColumnSpan - 8}
key={'i' + key++}
row={secondRow} row={secondRow}
rowSpan={7}> rowSpan={7}>
<input disabled={!props.allowMount || props.mounted} <input disabled={!props.allowMount || props.mounted || props.disabled}
maxLength={4096}
onChange={props.changed} onChange={props.changed}
size={4096}
styleName={'Input'}
type={'text'} type={'text'}
value={props.location}/> value={props.location}/>
</RootElem>); </RootElem>
));
inputControls.push((
<Button clicked={()=>props.browseClicked(props.title, props.location)}
col={inputColumnSpan - 7}
colSpan={7}
disabled={props.mounted || props.disabled || !props.allowMount}
key={'b' + key++}
row={secondRow}
rowSpan={7}>...</Button>
));
} }
const buttonDisplay = props.allowMount ? const buttonDisplay = props.allowMount || props.disabled ?
(props.mounted ? 'Unmount' : 'Mount') : (props.mounted ? 'Unmount' : 'Mount') :
<Loader color={'var(--heading_text_color)'} <Loader color={'var(--heading_text_color)'}
height='19px' height='19px'
@@ -59,26 +75,39 @@ export default CSSModules((props) => {
width='19px'/>; width='19px'/>;
const actionsDisplay = ( const actionsDisplay = (
<Button clicked={()=>props.clicked(props.title, !props.mounted, props.location, props.pid)} <Button clicked={()=>props.clicked(props.title, !props.mounted, props.location)}
col={inputColumnSpan + 2} col={inputColumnSpan + 2}
colSpan={20} colSpan={21}
disabled={!props.allowMount} disabled={!props.allowMount || props.disabled}
row={secondRow} row={secondRow}
rowSpan={7}> rowSpan={7}>
{buttonDisplay} {buttonDisplay}
</Button>); </Button>);
const autoMountControl = ( const autoMountControl = (
<RootElem col={inputColumnSpan + 23} <RootElem col={inputColumnSpan + 24}
colSpan={26} colSpan={28}
row={secondRow} row={secondRow}
rowSpan={7}> rowSpan={7}>
<input checked={props.autoMount} <input checked={props.autoMount}
disabled={props.disabled}
onChange={props.autoMountChanged} onChange={props.autoMountChanged}
type='checkbox'/>Auto-mount type='checkbox'/>Auto-mount
</RootElem> </RootElem>
); );
const autoRestartControl = (
<RootElem col={inputColumnSpan + 24 + 28}
colSpan={24}
row={secondRow}
rowSpan={7}>
<input checked={props.autoRestart}
disabled={props.disabled}
onChange={props.autoRestartChanged}
type='checkbox'/>Restart
</RootElem>
);
return ( return (
<Grid> <Grid>
{configButton} {configButton}
@@ -87,9 +116,10 @@ export default CSSModules((props) => {
rowSpan={5} rowSpan={5}
text={props.title} text={props.title}
type={'Heading1'}/> type={'Heading1'}/>
{inputControl} {inputControls}
{actionsDisplay} {actionsDisplay}
{autoMountControl} {autoMountControl}
{autoRestartControl}
</Grid> </Grid>
); );
}, styles, {allowMultiple: true}); }, styles, {allowMultiple: true});

View File

@@ -8,19 +8,57 @@ import Button from '../UI/Button/Button';
import UpgradeIcon from '../UpgradeIcon/UpgradeIcon'; import UpgradeIcon from '../UpgradeIcon/UpgradeIcon';
export default CSSModules((props) => { export default CSSModules((props) => {
let optionsDisplay = null; let optionsDisplay = [];
let key = 0;
if (props.releaseExtracting) { if (props.releaseExtracting) {
optionsDisplay = <Text align='center' optionsDisplay.push((
row={13} <Text col={dimensions => (dimensions.columns / 3) * 2}
rowSpan={7}
colSpan={'remain'} colSpan={'remain'}
text={'Activating <' + props.installedVersion + '>'}/> key={key++}
rowSpan={4}
text={'Activating'}
textAlign={'left'}
type={'Heading2'}/>
));
optionsDisplay.push((
<Text col={dimensions => (dimensions.columns / 3) * 2}
colSpan={'remain'}
key={key++}
row={5}
rowSpan={7}
text={props.installedVersion}
textAlign={'left'}/>
));
} else if (props.downloadDisabled) {
optionsDisplay.push((
<Text col={dimensions => (dimensions.columns / 3) * 2}
colSpan={'remain'}
key={key++}
rowSpan={4}
text={'Installed'}
textAlign={'left'}
type={'Heading2'}/>
));
optionsDisplay.push((
<Text col={dimensions => (dimensions.columns / 3) * 2}
colSpan={'remain'}
key={key++}
row={5}
rowSpan={7}
text={props.installedVersion}
textAlign={'left'}/>
));
} else { } else {
optionsDisplay = <Button clicked={props.downloadClicked} optionsDisplay.push((
<Button clicked={props.downloadClicked}
col={dimensions => (dimensions.columns / 3) * 2}
colSpan={20} colSpan={20}
key={key++}
disabled={props.downloadDisabled} disabled={props.downloadDisabled}
row={13} row={5}
rowSpan={7}>Install</Button>; rowSpan={7}>Install</Button>
));
} }
return ( return (
@@ -56,18 +94,6 @@ export default CSSModules((props) => {
row={5} row={5}
rowSpan={7} rowSpan={7}
selected={props.version}/> 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} {optionsDisplay}
</Grid> </Grid>
); );

View File

@@ -9,9 +9,28 @@ exports.DATA_LOCATIONS = {
}; };
exports.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.PROVIDER_LIST = [
'Hyperspace',
'Sia',
'SiaPrime'
];
exports.PROVIDER_ARG = {
hyperspace: '-hs',
sia: '',
siaprime: '-sp'
};
exports.IPC_Browse_Directory = 'browse_directory';
exports.IPC_Check_Dependency_Installed = 'check_dependency_installed';
exports.IPC_Check_Dependency_Installed_Reply = 'check_dependency_installed';
exports.IPC_Check_Installed = 'check_installed'; exports.IPC_Check_Installed = 'check_installed';
exports.IPC_Check_Installed_Reply = 'check_installed_reply'; exports.IPC_Check_Installed_Reply = 'check_installed_reply';
exports.IPC_Check_Mount_Location = 'check_mount_location';
exports.IPC_Delete_File = 'delete_file'; exports.IPC_Delete_File = 'delete_file';
exports.IPC_Detect_Mounts = 'detect_mounts'; exports.IPC_Detect_Mounts = 'detect_mounts';

View File

@@ -5,29 +5,23 @@ import Button from '../../components/UI/Button/Button';
import ConfigurationItem from '../../components/ConfigurationItem/ConfigurationItem'; import ConfigurationItem from '../../components/ConfigurationItem/ConfigurationItem';
import CSSModules from 'react-css-modules'; import CSSModules from 'react-css-modules';
import Modal from '../../components/UI/Modal/Modal'; import Modal from '../../components/UI/Modal/Modal';
import IPCContainer from '../IPCContainer/IPCContainer';
const Constants = require('../../constants'); const Constants = require('../../constants');
let ipcRenderer = null; class Configuration extends IPCContainer {
if (!process.versions.hasOwnProperty('electron')) {
ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null);
}
class Configuration extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
if (ipcRenderer) { this.setRequestHandler(Constants.IPC_Get_Config_Template_Reply, this.onGetConfigTemplateReply);
ipcRenderer.on(Constants.IPC_Get_Config_Template_Reply, this.onGetConfigTemplateReply); this.setRequestHandler(Constants.IPC_Get_Config_Reply, this.onGetConfigReply);
ipcRenderer.on(Constants.IPC_Get_Config_Reply, this.onGetConfigReply); this.setRequestHandler(Constants.IPC_Set_Config_Values_Reply, this.onSetConfigValuesReply);
ipcRenderer.on(Constants.IPC_Set_Config_Values_Reply, this.onSetConfigValuesReply);
ipcRenderer.send(Constants.IPC_Get_Config_Template, { this.sendRequest(Constants.IPC_Get_Config_Template, {
Directory: this.props.directory, Directory: this.props.directory,
StorageType: this.props.storageType, StorageType: this.props.storageType,
Version: this.props.version, Version: this.props.version,
}); });
} }
}
state = { state = {
ChangedItems: [], ChangedItems: [],
@@ -78,14 +72,6 @@ class Configuration extends Component {
} }
}; };
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) => { createItemList = (config, template) => {
const objectList = []; const objectList = [];
const itemList = Object const itemList = Object
@@ -164,12 +150,13 @@ class Configuration extends Component {
if (arg.data.Success) { if (arg.data.Success) {
this.setState({ this.setState({
Template: arg.data.Template, Template: arg.data.Template,
}); }, ()=> {
ipcRenderer.send(Constants.IPC_Get_Config, { this.sendRequest(Constants.IPC_Get_Config, {
Directory: this.props.directory, Directory: this.props.directory,
StorageType: this.props.storageType, StorageType: this.props.storageType,
Version: this.props.version, Version: this.props.version,
}); });
});
} else { } else {
this.props.errorHandler(arg.data.Error, () => { this.props.errorHandler(arg.data.Error, () => {
this.props.closed(); this.props.closed();
@@ -182,11 +169,9 @@ class Configuration extends Component {
}; };
saveAndClose = () => { saveAndClose = () => {
if (ipcRenderer) {
this.setState({ this.setState({
Saving: true, Saving: true,
}); }, ()=> {
const changedItems = []; const changedItems = [];
for (const item of this.state.ChangedItems) { for (const item of this.state.ChangedItems) {
changedItems.push({ changedItems.push({
@@ -206,13 +191,13 @@ class Configuration extends Component {
} }
} }
ipcRenderer.send(Constants.IPC_Set_Config_Values, { this.sendRequest(Constants.IPC_Set_Config_Values, {
Directory: this.props.directory, Directory: this.props.directory,
Items: changedItems, Items: changedItems,
StorageType: this.props.storageType, StorageType: this.props.storageType,
Version: this.props.version, Version: this.props.version,
}); });
} });
}; };
render() { render() {

View File

@@ -0,0 +1,51 @@
import {Component} from 'react';
export default class extends Component {
constructor(props) {
super(props);
if (!process.versions.hasOwnProperty('electron')) {
this.ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null);
}
}
handlerList = {};
ipcRenderer;
componentWillUnmount() {
if (this.ipcRenderer) {
for (let name in this.handlerList) {
if (this.handlerList.hasOwnProperty(name)) {
this.ipcRenderer.removeListener(name, this.handlerList[name]);
}
}
}
this.handlerList = {};
};
sendRequest = (name, data) => {
if (this.ipcRenderer) {
this.ipcRenderer.send(name, data);
}
};
sendSyncRequest = (name, data) => {
if (this.ipcRenderer) {
return this.ipcRenderer.sendSync(name + '_sync', data);
} else {
return {
Success: false,
Error: 'IPC not available. Running in browser?',
};
}
};
setRequestHandler = (name, callback) => {
if (this.ipcRenderer) {
this.handlerList[name] = callback;
this.ipcRenderer.on(name, callback);
}
};
};

View File

@@ -1,140 +1,176 @@
import React from 'react'; import React from 'react';
import {Component} from 'react'; import Box from '../../components/UI/Box/Box';
import Button from '../../components/UI/Button/Button';
import CSSModules from 'react-css-modules'; import CSSModules from 'react-css-modules';
import styles from './MountItems.css'; import styles from './MountItems.css';
import Modal from '../../components/UI/Modal/Modal';
import MountItem from '../../components/MountItem/MountItem'; import MountItem from '../../components/MountItem/MountItem';
import IPCContainer from '../IPCContainer/IPCContainer';
const Constants = require('../../constants'); const Constants = require('../../constants');
let ipcRenderer = null; class MountItems extends IPCContainer {
if (!process.versions.hasOwnProperty('electron')) {
ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null);
}
class MountItems extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
if (ipcRenderer) {
ipcRenderer.on(Constants.IPC_Detect_Mounts_Reply, this.onDetectMountsReply); for (const provider of Constants.PROVIDER_LIST) {
ipcRenderer.on(Constants.IPC_Mount_Drive_Reply, this.onMountDriveReply); this.state[provider] = {
ipcRenderer.on(Constants.IPC_Unmount_Drive_Reply, this.onUnmountDriveReply); AllowMount: false,
DriveLetters: [],
Mounted: false,
};
}
this.setRequestHandler(Constants.IPC_Detect_Mounts_Reply, this.onDetectMountsReply);
this.setRequestHandler(Constants.IPC_Mount_Drive_Reply, this.onMountDriveReply);
this.setRequestHandler(Constants.IPC_Unmount_Drive_Reply, this.onUnmountDriveReply);
this.detectMounts(); this.detectMounts();
} }
}
retryIntervals = {};
state = { state = {
Hyperspace: { DisplayRetry: false,
AllowMount: false, RetryItems: {},
DriveLetters: [],
Mounted: false,
PID: -1,
},
Sia: {
AllowMount: false,
DriveLetters: [],
Mounted: false,
PID: -1,
},
}; };
componentWillUnmount = () => { cancelRetryMount = (storageType, stateCallback) => {
if (ipcRenderer) { clearInterval(this.retryIntervals[storageType]);
ipcRenderer.removeListener(Constants.IPC_Detect_Mounts_Reply, this.onDetectMountsReply); delete this.retryIntervals[storageType];
ipcRenderer.removeListener(Constants.IPC_Mount_Drive_Reply, this.onMountDriveReply);
ipcRenderer.removeListener(Constants.IPC_Unmount_Drive_Reply, this.onUnmountDriveReply); if (stateCallback) {
let retryItems = {
...this.state.RetryItems,
};
delete retryItems[storageType];
this.setState({
DisplayRetry: Object.keys(retryItems).length > 0,
RetryItems: retryItems,
}, stateCallback);
} }
}; };
componentWillUnmount() {
for (const storageType in this.state.RetryItems) {
if (this.state.RetryItems.hasOwnProperty(storageType)) {
this.cancelRetryMount(storageType);
}
}
super.componentWillUnmount();
};
detectMounts = ()=> { detectMounts = ()=> {
if (!this.state.DisplayRetry) {
this.props.mountsBusy(true); this.props.mountsBusy(true);
ipcRenderer.send(Constants.IPC_Detect_Mounts, { this.sendRequest(Constants.IPC_Detect_Mounts, {
Directory: this.props.directory, Directory: this.props.directory,
Version: this.props.version, Version: this.props.version,
}); });
}
}; };
handleMountLocationChanged = (systemType, value) => { handleBrowseLocation = (storageType, location) => {
location = this.sendSyncRequest(Constants.IPC_Browse_Directory, {
Title: storageType + ' Mount Location',
Location: location,
});
if (location && (location.length > 0)) {
this.handleMountLocationChanged(storageType, location);
}
};
handleMountLocationChanged = (storageType, value) => {
if (this.props.platform === 'win32') { if (this.props.platform === 'win32') {
this.props.changed(systemType, this.state[systemType].DriveLetters[value]); this.props.changed(storageType, this.state[storageType].DriveLetters[value]);
} } else {
else { this.props.changed(storageType, value);
this.props.changed(systemType, value);
} }
}; };
handleMountUnMount = (storageType, mount, location, pid) => { handleMountUnMount = (storageType, mount, location) => {
if (ipcRenderer) { if (!location || (location.trim().length === 0)) {
const state = { this.props.errorHandler('Mount location is not set');
} else {
let allowAction = true;
if (mount && (this.props.platform !== 'win32')) {
const result = this.sendSyncRequest(Constants.IPC_Check_Mount_Location, {
Location: location,
});
if (!result.Success) {
allowAction = false;
this.props.errorHandler(result.Error.toString());
}
}
if (allowAction) {
const storageState = {
...this.state[storageType], ...this.state[storageType],
AllowMount: false, AllowMount: false,
}; };
this.setState({
[storageType]: state,
});
this.props.mountsBusy(true); this.props.mountsBusy(true);
this.setState({
[storageType]: storageState,
}, () => {
if (mount) { if (mount) {
ipcRenderer.send(Constants.IPC_Mount_Drive, { this.sendRequest(Constants.IPC_Mount_Drive, {
Directory: this.props.directory, Directory: this.props.directory,
Location: location, Location: location,
NoConsoleSupported: this.props.noConsoleSupported,
StorageType: storageType, StorageType: storageType,
Version: this.props.version, Version: this.props.version,
}); });
} else { } else {
ipcRenderer.send(Constants.IPC_Unmount_Drive, { this.sendRequest(Constants.IPC_Unmount_Drive, {
Directory: this.props.directory, Directory: this.props.directory,
Location: location, Location: location,
PID: pid,
StorageType: storageType, StorageType: storageType,
Version: this.props.version, Version: this.props.version,
}); });
} }
});
}
} }
}; };
onDetectMountsReply = (event, arg) => { onDetectMountsReply = (event, arg) => {
if (arg.data.Success) { if (!this.state.DisplayRetry && arg.data.Success) {
const sia = { let state = {};
...this.state.Sia, let mountsBusy = false;
for (const provider of Constants.PROVIDER_LIST) {
state[provider] = {
...this.state[provider],
AllowMount: true, AllowMount: true,
DriveLetters: (arg.data.DriveLetters.Sia), DriveLetters: (arg.data.DriveLetters[provider]),
Mounted: (arg.data.Locations.Sia.length > 0), Mounted: (arg.data.Locations[provider].length > 0),
PID: arg.data.PIDS.Sia,
}; };
const hs = { mountsBusy = mountsBusy || state[provider].Mounted;
...this.state.Hyperspace, }
AllowMount: true, this.props.mountsBusy(mountsBusy);
DriveLetters: (arg.data.DriveLetters.Hyperspace),
Mounted: (arg.data.Locations.Hyperspace.length > 0), this.setState(state, () => {
PID: arg.data.PIDS.Hyperspace, const updateMountLocation = (data, provider) => {
const providerLower = provider.toLowerCase();
let location = data.Locations[provider];
if (location.length === 0) {
location = (this.props.platform === 'win32') ?
this.props[providerLower].MountLocation || data.DriveLetters[provider][0] :
this.props[providerLower].MountLocation;
}
if (location !== this.props[providerLower].MountLocation) {
this.props.changed(provider, location);
}
}; };
this.setState({ for (const provider of Constants.PROVIDER_LIST) {
Hyperspace: hs, updateMountLocation(arg.data, provider);
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(); this.performAutoMount();
});
} else { } else {
this.props.errorHandler(arg.data.Error); this.props.errorHandler(arg.data.Error);
} }
@@ -143,66 +179,134 @@ class MountItems extends Component {
onMountDriveReply = (event, arg) => { onMountDriveReply = (event, arg) => {
const state = { const state = {
...this.state[arg.data.StorageType], ...this.state[arg.data.StorageType],
PID: arg.data.PID,
Mounted: arg.data.Success, Mounted: arg.data.Success,
}; };
this.setState({ this.setState({
[arg.data.StorageType]: state, [arg.data.StorageType]: state,
}); }, ()=> {
this.detectMounts(); this.detectMounts();
});
}; };
onUnmountDriveReply = (event, arg) => { onUnmountDriveReply = (event, arg) => {
if (arg && arg.data && !arg.data.Expected && arg.data.Location && this.props[arg.data.StorageType.toLowerCase()].AutoRestart) {
const storageType = arg.data.StorageType;
if (!this.state.RetryItems[storageType]) {
let retryItems = {
...this.state.RetryItems
};
retryItems[storageType] = {
RetrySeconds: 10,
};
const storageState = {
...this.state[arg.data.StorageType],
AllowMount: false,
Mounted: false,
};
this.setState({
[storageType]: storageState,
DisplayRetry: true,
RetryItems: retryItems,
}, () => {
this.retryIntervals[storageType] = setInterval(() => {
let retryItems = {
...this.state.RetryItems,
};
const retrySeconds = retryItems[storageType].RetrySeconds - 1;
if (retrySeconds === 0) {
this.cancelRetryMount(storageType, () => {
this.handleMountUnMount(storageType, true, arg.data.Location);
});
} else {
retryItems[storageType].RetrySeconds = retrySeconds;
this.setState({
RetryItems: retryItems,
});
}
},1000);
});
}
} else {
this.detectMounts(); this.detectMounts();
}
}; };
performAutoMount = ()=> { performAutoMount = ()=> {
if (this.props.processAutoMount) { if (this.props.processAutoMount) {
this.props.autoMountProcessed(); this.props.autoMountProcessed();
if (this.props.hyperspace.AutoMount && const processAutoMount = (provider) => {
!this.state.Hyperspace.Mounted && const providerLower = provider.toLowerCase();
(this.props.hyperspace.MountLocation.length > 0)) { if (this.props[providerLower].AutoMount &&
this.handleMountUnMount('Hyperspace', true, this.props.hyperspace.MountLocation); !this.state[provider].Mounted &&
(this.props[providerLower].MountLocation.length > 0)) {
this.handleMountUnMount(provider, true, this.props[providerLower].MountLocation);
} }
if (this.props.sia.AutoMount && };
!this.state.Sia.Mounted &&
(this.props.sia.MountLocation.length > 0)) { for (const provider of Constants.PROVIDER_LIST) {
this.handleMountUnMount('Sia', true, this.props.sia.MountLocation); processAutoMount(provider);
} }
} }
}; };
render() { render() {
let retryDisplay;
if (this.state.DisplayRetry) {
let retryList = [];
let retryListCount = 0;
for (const storageType in this.state.RetryItems) {
if (this.state.RetryItems.hasOwnProperty(storageType)) {
retryListCount++;
retryList.push(<Button
clicked={()=>this.cancelRetryMount(storageType, ()=> this.detectMounts())}
key={'b' + retryListCount}>Cancel {storageType} Remount ({this.state.RetryItems[storageType].RetrySeconds}s)</Button>);
if (retryListCount < Object.keys(this.state.RetryItems).length) {
retryList.push(<div style={{paddingTop: '8px'}} key={'d' + retryListCount}/>);
}
}
}
retryDisplay = (
<Modal>
<Box dxStyle={{padding: '8px', minWidth: '70vw'}}>
<h1 style={{textAlign: 'center', paddingBottom: '8px', color: 'var(--text_color_error)'}}>Mount Failed</h1>
{retryList}
</Box>
</Modal>
)
}
let items = [];
for (const provider of Constants.PROVIDER_LIST) {
const providerLower = provider.toLowerCase();
items.push((
<MountItem allowConfig={this.props.allowConfig}
allowMount={this.state[provider].AllowMount}
autoMount={this.props[providerLower].AutoMount}
autoMountChanged={(e)=>this.props.autoMountChanged(provider, e)}
autoRestart={this.props[providerLower].AutoRestart}
autoRestartChanged={(e)=>this.props.autoRestartChanged(provider, e)}
browseClicked={this.handleBrowseLocation}
changed={(e) => this.handleMountLocationChanged(provider, e.target.value)}
clicked={this.handleMountUnMount}
configClicked={()=>this.props.configClicked(provider)}
items={this.state[provider].DriveLetters}
key={'mi_' + items.length}
location={this.props[providerLower].MountLocation}
mounted={this.state[provider].Mounted}
platform={this.props.platform}
title={provider} />
));
if (items.length !== this.state.length) {
items.push(<div key={'di_' + items.length}
style={{paddingTop: '12px'}} />)
}
}
return ( return (
<div styleName='MountItems'> <div styleName='MountItems'>
<MountItem allowConfig={this.props.allowConfig} {retryDisplay}
allowMount={this.state.Hyperspace.AllowMount} {items}
autoMount={this.props.hyperspace.AutoMount}
autoMountChanged={(e)=>this.props.autoMountChanged('Hyperspace', e)}
changed={(e) => this.handleMountLocationChanged('Hyperspace', e.target.value)}
clicked={this.handleMountUnMount}
configClicked={()=>this.props.configClicked('Hyperspace')}
items={this.state.Hyperspace.DriveLetters}
location={this.props.hyperspace.MountLocation}
mounted={this.state.Hyperspace.Mounted}
pid={this.state.Hyperspace.PID}
platform={this.props.platform}
title={'Hyperspace'}/>
<div style={{paddingTop: '8px'}}/>
<MountItem allowConfig={this.props.allowConfig}
allowMount={this.state.Sia.AllowMount}
autoMount={this.props.sia.AutoMount}
autoMountChanged={(e)=>this.props.autoMountChanged('Sia', e)}
changed={(e) => this.handleMountLocationChanged('Sia', e.target.value)}
clicked={this.handleMountUnMount}
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

@@ -14,11 +14,13 @@
--heading_text_color: rgba(161, 190, 219, 0.7); --heading_text_color: rgba(161, 190, 219, 0.7);
--heading_other_text_color: var(--heading_text_color); --heading_other_text_color: var(--heading_text_color);
--text_color_transition: color 0.3s; --text_color_transition: color 0.3s;
--default_font_size: 4vmin
} }
* { * {
font-family: 'Nunito', sans-serif; font-family: 'Nunito', sans-serif;
font-size: 5vh; font-size: var(--default_font_size);
} }
*::-moz-focus-inner { *::-moz-focus-inner {

View File

@@ -11,6 +11,11 @@ if (!process.versions.hasOwnProperty('electron')) {
const ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null); const ipcRenderer = ((window && window.require) ? window.require('electron').ipcRenderer : null);
if (ipcRenderer) { if (ipcRenderer) {
ipcRenderer.on(Constants.IPC_Get_Platform_Reply, (event, arg) => { ipcRenderer.on(Constants.IPC_Get_Platform_Reply, (event, arg) => {
if (arg.data === 'linux') {
let root = document.documentElement;
root.style.setProperty('--default_font_size', '4.8vmin');
}
ReactDOM.render(<App platform={arg.data} version={packageJson.version}/>, document.getElementById('root')); ReactDOM.render(<App platform={arg.data} version={packageJson.version}/>, document.getElementById('root'));
registerServiceWorker(); registerServiceWorker();
}); });