Convert file fields to use native Electron dialog + add directory selector field #124

Merged
alexliebowitz merged 5 commits from file-selector into redux 2017-05-19 15:12:10 +02:00
7 changed files with 101 additions and 12 deletions

View file

@ -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

View 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;

View file

@ -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>

View file

@ -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() {

View file

@ -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">

View file

@ -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";

View file

@ -0,0 +1,7 @@
.file-selector__choose-button {
font-size: 13px;
}
.file-selector__path {
font-size: 14px;
}