[Application configuration support] [Fix component unmount leak]

This commit is contained in:
Scott E. Graves
2018-09-30 11:00:23 -05:00
parent f11ddb6d75
commit 9968116242
17 changed files with 880 additions and 164 deletions

View File

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

View File

@@ -0,0 +1,287 @@
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';
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('get_config_template_reply', this.onGetConfigTemplateReply);
ipcRenderer.on('get_config_reply', this.onGetConfigReply);
ipcRenderer.on('set_config_values_reply', this.onSetConfigValuesReply);
ipcRenderer.send('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('get_config_reply', this.onGetConfigReply);
ipcRenderer.removeListener('get_config_template_reply', this.onGetConfigTemplateReply);
ipcRenderer.removeListener('set_config_values', 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,
});
}
};
onGetConfigTemplateReply = (event, arg) => {
if (arg.data.Success) {
this.setState({
Template: arg.data.Template,
});
ipcRenderer.send('get_config', {
Directory: this.props.directory,
StorageType: this.props.storageType,
Version: this.props.version,
});
}
};
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('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>
);
}
let 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}
<h1>Settings</h1>
{configurationItems}
</div>
</Box>
</div>
);
}
}
export default CSSModules(Configuration, styles, {allowMultiple: true});

View File

@@ -13,66 +13,9 @@ class MountItems extends Component {
constructor(props) {
super(props);
if (ipcRenderer) {
ipcRenderer.on('detect_mounts_reply', (event, arg) => {
if (arg.data.Success) {
const sia = {
...this.state.Sia,
AllowMount: true,
DriveLetters: (arg.data.DriveLetters.Sia),
Mounted: (arg.data.Locations.Sia.length > 0),
PID: arg.data.PIDS.Sia,
};
const hs = {
...this.state.Hyperspace,
AllowMount: true,
DriveLetters: (arg.data.DriveLetters.Hyperspace),
Mounted: (arg.data.Locations.Hyperspace.length > 0),
PID: arg.data.PIDS.Hyperspace,
};
this.setState({
Hyperspace: hs,
Sia: sia,
});
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();
}
});
ipcRenderer.on('mount_drive_reply', (event, arg) => {
const state = {
...this.state[arg.data.StorageType],
PID: arg.data.PID,
Mounted: arg.data.Success,
};
this.setState({
[arg.data.StorageType]: state,
});
this.detectMounts();
});
ipcRenderer.on('unmount_drive_reply', (event, arg) => {
this.detectMounts();
});
ipcRenderer.on('detect_mounts_reply', this.onDetectMountsReply);
ipcRenderer.on('mount_drive_reply', this.onMountDriveReply);
ipcRenderer.on('unmount_drive_reply', this.onUnmountDriveReply);
this.detectMounts();
}
@@ -93,6 +36,14 @@ class MountItems extends Component {
},
};
componentWillUnmount = () => {
if (ipcRenderer) {
ipcRenderer.removeListener('detect_mounts_reply', this.onDetectMountsReply);
ipcRenderer.removeListener('mount_drive_reply', this.onMountDriveReply);
ipcRenderer.removeListener('unmount_drive_reply', this.onUnmountDriveReply);
}
};
detectMounts = ()=> {
this.props.mountsBusy(true);
ipcRenderer.send('detect_mounts', {
@@ -141,6 +92,67 @@ 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();
}
};
onMountDriveReply = (event, arg) => {
const state = {
...this.state[arg.data.StorageType],
PID: arg.data.PID,
Mounted: arg.data.Success,
};
this.setState({
[arg.data.StorageType]: state,
});
this.detectMounts();
};
onUnmountDriveReply = (event, arg) => {
this.detectMounts();
};
performAutoMount = ()=> {
if (this.props.processAutoMount) {
this.props.autoMountProcessed();
@@ -160,28 +172,32 @@ class MountItems extends Component {
render() {
return (
<div styleName='MountItems'>
<MountItem allowMount={this.state.Hyperspace.AllowMount}
<MountItem allowConfig={this.props.allowConfig}
allowMount={this.state.Hyperspace.AllowMount}
autoMount={this.props.hyperspace.AutoMount}
autoMountChanged={(e)=>this.props.autoMountChanged('Hyperspace', e)}
mounted={this.state.Hyperspace.Mounted}
items={this.state.Hyperspace.DriveLetters}
platform={this.props.platform}
title={'Hyperspace'}
location={this.props.hyperspace.MountLocation}
changed={(e) => this.handleMountLocationChanged('Hyperspace', e.target.value)}
clicked={this.handleMountUnMount}
pid={this.state.Hyperspace.PID}/>
<MountItem allowMount={this.state.Sia.AllowMount}
configClicked={()=>this.props.configClicked('Hyperspace')}
items={this.state.Hyperspace.DriveLetters}
location={this.props.hyperspace.MountLocation}
mounted={this.state.Hyperspace.Mounted}
pid={this.state.Hyperspace.PID}
platform={this.props.platform}
title={'Hyperspace'}/>
<MountItem allowConfig={this.props.allowConfig}
allowMount={this.state.Sia.AllowMount}
autoMount={this.props.sia.AutoMount}
autoMountChanged={(e)=>this.props.autoMountChanged('Sia', e)}
mounted={this.state.Sia.Mounted}
items={this.state.Sia.DriveLetters}
platform={this.props.platform}
title={'Sia'}
location={this.props.sia.MountLocation}
changed={(e) => this.handleMountLocationChanged('Sia', e.target.value)}
clicked={this.handleMountUnMount}
pid={this.state.Sia.PID}/>
configClicked={()=>this.props.configClicked('Sia')}
items={this.state.Sia.DriveLetters}
location={this.props.sia.MountLocation}
mounted={this.state.Sia.Mounted}
pid={this.state.Sia.PID}
platform={this.props.platform}
title={'Sia'}/>
</div>);
}
}