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
### 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
* 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 FileSelector from './file-selector.js';
import {Icon} from './common.js';
var formFieldCounter = 0,
formFieldFileSelectorTypes = ['file', 'directory'],
formFieldNestedLabelTypes = ['radio', 'checkbox'];
function formFieldId() {
@ -19,6 +21,14 @@ export let FormField = React.createClass({
postfix: React.PropTypes.string,
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() {
return {
isError: null,
@ -26,17 +36,29 @@ export let FormField = React.createClass({
}
},
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._type = this.props.type;
} else if (this.props.type == 'text-number') {
this._element = 'input';
this._type = 'text';
} else if (formFieldFileSelectorTypes.includes(this.props.type)) {
this._element = 'input';
this._type = 'hidden';
} else {
// Non <input> field, e.g. <select>, <textarea>
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) {
this.setState({
isError: true,
@ -49,9 +71,6 @@ export let FormField = React.createClass({
getValue: function() {
if (this.props.type == 'checkbox') {
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 {
return this.refs.field.value;
}
@ -87,6 +106,10 @@ export let FormField = React.createClass({
{this.props.label}
</label> :
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> : '' }
{ isError && this.state.errorMessage ? <div className="form-field__error">{this.state.errorMessage}</div> : '' }
</div>

View file

@ -30,7 +30,7 @@ const DeveloperPage = React.createClass({
},
handleUpgradeFileChange: function(event) {
this.setState({
upgradePath: event.target.files[0].path,
upgradePath: event.target.value,
});
},
handleForceUpgradeClick: function() {

View file

@ -97,11 +97,11 @@ var SettingsPage = React.createClass({
<h3>Download Directory</h3>
</div>
<div className="card__content">
<FormRow type="text"
name="download_directory"
defaultValue={this.state.daemonSettings.download_directory}
helper="LBRY downloads will be saved here."
onChange={this.onDownloadDirChange} />
<FormRow type="directory"
name="download_directory"
defaultValue={this.state.daemonSettings.download_directory}
helper="LBRY downloads will be saved here."
onChange={this.onDownloadDirChange} />
</div>
</section>
<section className="card">

View file

@ -6,6 +6,7 @@
@import "component/_button.scss";
@import "component/_card.scss";
@import "component/_file-actions.scss";
@import "component/_file-selector.scss";
@import "component/_file-tile.scss";
@import "component/_form-field.scss";
@import "component/_header.scss";

View file

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