Convert file fields to use native Electron dialog + add directory selector field #124
7 changed files with 101 additions and 12 deletions
|
@ -23,8 +23,8 @@ Web UI version numbers should always match the corresponding version of LBRY App
|
||||||
* Enable windows code signing of binary
|
* Enable windows code signing of binary
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
*
|
* Use directory selector instead of ugly text box on Settings page
|
||||||
*
|
* Use Electron's native file selector everywhere instead of WebKit file selector
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
* Error modals now display full screen properly
|
* Error modals now display full screen properly
|
||||||
|
|
58
ui/js/component/file-selector.js
Normal file
58
ui/js/component/file-selector.js
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const {remote} = require('electron');
|
||||||
|
class FileSelector extends React.Component {
|
||||||
|
static propTypes = {
|
||||||
|
type: React.PropTypes.oneOf(['file', 'directory']),
|
||||||
|
initPath: React.PropTypes.string,
|
||||||
|
onFileChosen: React.PropTypes.func,
|
||||||
|
}
|
||||||
|
|
||||||
|
static defaultProps = {
|
||||||
|
type: 'file',
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillMount() {
|
||||||
|
this.setState({
|
||||||
|
path: this.props.initPath || null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
handleButtonClick() {
|
||||||
|
remote.dialog.showOpenDialog({
|
||||||
|
properties: [this.props.type == 'file' ? 'openFile' : 'openDirectory'],
|
||||||
|
}, (paths) => {
|
||||||
|
if (!paths) { // User hit cancel, so do nothing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = paths[0];
|
||||||
|
this.setState({
|
||||||
|
path: path,
|
||||||
|
});
|
||||||
|
if (this.props.onFileChosen) {
|
||||||
|
this.props.onFileChosen(path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className="file-selector">
|
||||||
|
<button type="button" className="file-selector__choose-button" onClick={() => this.handleButtonClick()}>
|
||||||
|
{this.props.type == 'file' ?
|
||||||
|
'Choose File' :
|
||||||
|
'Choose Directory'}
|
||||||
|
</button>
|
||||||
|
{' '}
|
||||||
|
<span className="file-selector__path">
|
||||||
|
{this.state.path ?
|
||||||
|
this.state.path :
|
||||||
|
'No File Chosen'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FileSelector;
|
|
@ -1,7 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import FileSelector from './file-selector.js';
|
||||||
import {Icon} from './common.js';
|
import {Icon} from './common.js';
|
||||||
|
|
||||||
var formFieldCounter = 0,
|
var formFieldCounter = 0,
|
||||||
|
formFieldFileSelectorTypes = ['file', 'directory'],
|
||||||
formFieldNestedLabelTypes = ['radio', 'checkbox'];
|
formFieldNestedLabelTypes = ['radio', 'checkbox'];
|
||||||
|
|
||||||
function formFieldId() {
|
function formFieldId() {
|
||||||
|
@ -19,6 +21,14 @@ export let FormField = React.createClass({
|
||||||
postfix: React.PropTypes.string,
|
postfix: React.PropTypes.string,
|
||||||
hasError: React.PropTypes.bool
|
hasError: React.PropTypes.bool
|
||||||
},
|
},
|
||||||
|
handleFileChosen: function(path) {
|
||||||
|
this.refs.field.value = path;
|
||||||
|
if (this.props.onChange) { // Updating inputs programmatically doesn't generate an event, so we have to make our own
|
||||||
|
const event = new Event('change', {bubbles: true})
|
||||||
|
this.refs.field.dispatchEvent(event); // This alone won't generate a React event, but we use it to attach the field as a target
|
||||||
|
this.props.onChange(event);
|
||||||
|
}
|
||||||
|
},
|
||||||
getInitialState: function() {
|
getInitialState: function() {
|
||||||
return {
|
return {
|
||||||
isError: null,
|
isError: null,
|
||||||
|
@ -26,17 +36,29 @@ export let FormField = React.createClass({
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
componentWillMount: function() {
|
componentWillMount: function() {
|
||||||
if (['text', 'number', 'radio', 'checkbox', 'file'].includes(this.props.type)) {
|
if (['text', 'number', 'radio', 'checkbox'].includes(this.props.type)) {
|
||||||
this._element = 'input';
|
this._element = 'input';
|
||||||
this._type = this.props.type;
|
this._type = this.props.type;
|
||||||
} else if (this.props.type == 'text-number') {
|
} else if (this.props.type == 'text-number') {
|
||||||
this._element = 'input';
|
this._element = 'input';
|
||||||
this._type = 'text';
|
this._type = 'text';
|
||||||
|
} else if (formFieldFileSelectorTypes.includes(this.props.type)) {
|
||||||
|
this._element = 'input';
|
||||||
|
this._type = 'hidden';
|
||||||
} else {
|
} else {
|
||||||
// Non <input> field, e.g. <select>, <textarea>
|
// Non <input> field, e.g. <select>, <textarea>
|
||||||
this._element = this.props.type;
|
this._element = this.props.type;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
componentDidMount: function() {
|
||||||
|
/**
|
||||||
|
* We have to add the webkitdirectory attribute here because React doesn't allow it in JSX
|
||||||
|
* https://github.com/facebook/react/issues/3468
|
||||||
|
*/
|
||||||
|
if (this.props.type == 'directory') {
|
||||||
|
this.refs.field.webkitdirectory = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
showError: function(text) {
|
showError: function(text) {
|
||||||
this.setState({
|
this.setState({
|
||||||
isError: true,
|
isError: true,
|
||||||
|
@ -49,9 +71,6 @@ export let FormField = React.createClass({
|
||||||
getValue: function() {
|
getValue: function() {
|
||||||
if (this.props.type == 'checkbox') {
|
if (this.props.type == 'checkbox') {
|
||||||
return this.refs.field.checked;
|
return this.refs.field.checked;
|
||||||
} else if (this.props.type == 'file') {
|
|
||||||
return this.refs.field.files.length && this.refs.field.files[0].path ?
|
|
||||||
this.refs.field.files[0].path : null;
|
|
||||||
} else {
|
} else {
|
||||||
return this.refs.field.value;
|
return this.refs.field.value;
|
||||||
}
|
}
|
||||||
|
@ -87,6 +106,10 @@ export let FormField = React.createClass({
|
||||||
{this.props.label}
|
{this.props.label}
|
||||||
</label> :
|
</label> :
|
||||||
element }
|
element }
|
||||||
|
{ formFieldFileSelectorTypes.includes(this.props.type) ?
|
||||||
|
<FileSelector type={this.props.type} onFileChosen={this.handleFileChosen}
|
||||||
|
{... this.props.defaultValue ? {initPath: this.props.defaultValue} : {}} /> :
|
||||||
|
null }
|
||||||
{ this.props.postfix ? <span className="form-field__postfix">{this.props.postfix}</span> : '' }
|
{ this.props.postfix ? <span className="form-field__postfix">{this.props.postfix}</span> : '' }
|
||||||
{ isError && this.state.errorMessage ? <div className="form-field__error">{this.state.errorMessage}</div> : '' }
|
{ isError && this.state.errorMessage ? <div className="form-field__error">{this.state.errorMessage}</div> : '' }
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -30,7 +30,7 @@ const DeveloperPage = React.createClass({
|
||||||
},
|
},
|
||||||
handleUpgradeFileChange: function(event) {
|
handleUpgradeFileChange: function(event) {
|
||||||
this.setState({
|
this.setState({
|
||||||
upgradePath: event.target.files[0].path,
|
upgradePath: event.target.value,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
handleForceUpgradeClick: function() {
|
handleForceUpgradeClick: function() {
|
||||||
|
|
|
@ -97,11 +97,11 @@ var SettingsPage = React.createClass({
|
||||||
<h3>Download Directory</h3>
|
<h3>Download Directory</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className="card__content">
|
<div className="card__content">
|
||||||
<FormRow type="text"
|
<FormRow type="directory"
|
||||||
name="download_directory"
|
name="download_directory"
|
||||||
defaultValue={this.state.daemonSettings.download_directory}
|
defaultValue={this.state.daemonSettings.download_directory}
|
||||||
helper="LBRY downloads will be saved here."
|
helper="LBRY downloads will be saved here."
|
||||||
onChange={this.onDownloadDirChange} />
|
onChange={this.onDownloadDirChange} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
<section className="card">
|
<section className="card">
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
@import "component/_button.scss";
|
@import "component/_button.scss";
|
||||||
@import "component/_card.scss";
|
@import "component/_card.scss";
|
||||||
@import "component/_file-actions.scss";
|
@import "component/_file-actions.scss";
|
||||||
|
@import "component/_file-selector.scss";
|
||||||
@import "component/_file-tile.scss";
|
@import "component/_file-tile.scss";
|
||||||
@import "component/_form-field.scss";
|
@import "component/_form-field.scss";
|
||||||
@import "component/_header.scss";
|
@import "component/_header.scss";
|
||||||
|
|
7
ui/scss/component/_file-selector.scss
Normal file
7
ui/scss/component/_file-selector.scss
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
.file-selector__choose-button {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-selector__path {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
Loading…
Reference in a new issue