Skip to content

Commit 176941e

Browse files
committed
INT-780 custom file picker using electron dialog
HTML file picker cannot set the value programmatically for security reasons. But in electron land we can use their `showOpenDialog`.
1 parent 2c53601 commit 176941e

File tree

8 files changed

+174
-59
lines changed

8 files changed

+174
-59
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@
8888
"ampersand-collection-filterable": "^0.2.1",
8989
"ampersand-collection-lodash-mixin": "^2.0.1",
9090
"ampersand-collection-rest-mixin": "^5.0.0",
91+
"ampersand-dom-bindings": "^3.7.0",
9192
"ampersand-filtered-subcollection": "^2.0.4",
9293
"ampersand-form-view": "^5.1.1",
9394
"ampersand-input-view": "^5.0.0",

src/connect/connect-form-view.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ var ConnectFormView = FormView.extend({
188188
el: this.parent.queryByHook('saveas-subview'),
189189
name: 'name',
190190
label: 'Name',
191-
placeholder: 'e.g. Shared Dev, Stats Box, PRODUCTION',
191+
placeholder: 'e.g. Shared Dev, QA Box, PRODUCTION',
192192
required: false
193193
})
194194
];

src/connect/filereader-default.jade

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@
33
.message.message-below.message-error(data-hook='message-container')
44
p(data-hook='message-text')
55
.form-item-file
6-
input(type="file", name="uploadKeyFile", multiple)
6+
.btn.btn-default(data-hook='load-file-button', style='text-transform: none;')
7+
i.fa.fa-file
8+
|  
9+
span(data-hook='button-label')= buttonTitle

src/connect/filereader-view.js

Lines changed: 161 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,182 @@
11
var InputView = require('./input-view');
22
var _ = require('lodash');
3-
// var path = require('path');
3+
var path = require('path');
4+
var remote = window.require('remote');
5+
var dialog = remote.require('dialog');
6+
var format = require('util').format;
7+
var bindings = require('ampersand-dom-bindings');
48
var fileReaderTemplate = require('./filereader-default.jade');
59

610
// var debug = require('debug')('scout:connect:filereader-view');
711

812
module.exports = InputView.extend({
913
template: fileReaderTemplate,
10-
clean: function() {
11-
var value;
12-
value = _.chain(this.input.files)
13-
.map(function(file) {
14-
return _.get(file, 'path', null);
15-
})
16-
.filter()
17-
.value();
18-
if (value.length === 0) {
19-
value = '';
14+
props: {
15+
inputValue: {
16+
type: 'array',
17+
required: true,
18+
default: function() {
19+
return [];
20+
}
21+
},
22+
removed: {
23+
type: 'boolean',
24+
required: true,
25+
default: false
2026
}
21-
return value;
2227
},
23-
setValue: function(value, skipValidation) {
24-
if (!this.input) {
25-
this.inputValue = value;
26-
return;
28+
derived: {
29+
buttonTitle: {
30+
deps: ['inputValue'],
31+
fn: function() {
32+
if (this.inputValue.length === 0) {
33+
return 'Choose certificate(s)';
34+
} else if (this.inputValue.length === 1) {
35+
return path.basename(this.inputValue[0]);
36+
}
37+
return format('%d files selected', this.inputValue.length);
38+
}
39+
},
40+
numSelectedFiles: {
41+
deps: ['inputValue'],
42+
fn: function() {
43+
return this.inputValue.length;
44+
}
45+
}
46+
},
47+
events: {
48+
'click [data-hook=load-file-button]': 'loadFileButtonClicked'
49+
},
50+
bindings: {
51+
buttonTitle: {
52+
type: 'text',
53+
hook: 'button-label'
54+
},
55+
'label': [
56+
{
57+
hook: 'label'
58+
},
59+
{
60+
type: 'toggle',
61+
hook: 'label'
62+
}
63+
],
64+
'message': {
65+
type: 'text',
66+
hook: 'message-text'
67+
},
68+
'showMessage': {
69+
type: 'toggle',
70+
hook: 'message-container'
2771
}
28-
this.input.value = '';
29-
/**
30-
* Cannot set input value for file types. @see INT-780
31-
*/
32-
// if (value || value === 0) {
33-
// if (!_.isArray(value)) {
34-
// value = [value];
35-
// }
36-
// if (value.length <= 1) {
37-
// this.input.value = path.basename(value);
38-
// } else {
39-
// this.input.value = 'multiple files';
40-
// }
41-
// this.input.files = _.map(value, function(f) {
42-
// return {
43-
// name: path.basename(f),
44-
// path: f
45-
// };
46-
// });
47-
// }
48-
this.inputValue = this.clean();
72+
},
73+
/**
74+
* Set value to empty array in spec, instead of '', set appropriate
75+
* invalidClass and validityClassSelector and always validate.
76+
* @param {Object} spec the spec to set up this input view
77+
*/
78+
initialize: function(spec) {
79+
spec = spec || {};
80+
_.defaults(spec, {value: []});
81+
this.invalidClass = 'has-error';
82+
this.validityClassSelector = '.form-item-file';
83+
InputView.prototype.initialize.call(this, spec);
84+
},
85+
/**
86+
* @todo (thomasr)
87+
* Because ampersand-input-view still uses [email protected] where
88+
* the render/remove/render cycle doesn't correctly set up the bindings
89+
* again, we need to re-initialize the bindings manually here. Once they
90+
* upgrade to [email protected] we can probably remove this entire
91+
* render method.
92+
*
93+
* @return {Object} this
94+
*/
95+
render: function() {
96+
this.renderWithTemplate(this);
97+
this.input = this.queryByHook('load-file-button');
98+
if (this.removed) {
99+
this._parsedBindings = bindings(this.bindings, this);
100+
this._initializeBindings();
101+
this.removed = false;
102+
}
103+
this.setValue(this.inputValue, !this.required);
104+
return this;
105+
},
106+
/**
107+
* Turn into no-op, as we don't work on input elements
108+
* @see ampersand-input-view.js#handleTypeChange
109+
*/
110+
handleTypeChange: function() {
111+
},
112+
/**
113+
* Turn into identity, as we don't need to trim the value
114+
* @param {Array} val the value to pass through
115+
* @return {Array} return the unchanged value
116+
*/
117+
clean: function(val) {
118+
return val;
119+
},
120+
/**
121+
* Turn into no-op, as we don't work on input elements
122+
* @see ampersand-input-view.js#initInputBindings
123+
*/
124+
// initInputBindings: function() {
125+
// },
126+
/**
127+
* Only call ampersand-view's remove, we don't need to remove event listeners
128+
* @see ampersand-input-view.js#remove
129+
*/
130+
remove: function() {
131+
this.removed = true;
132+
InputView.prototype.remove.apply(this, arguments);
133+
},
134+
/**
135+
* Not setting this.input.value here because our this.input is a div
136+
* @param {Array} value the value to assign to this.inputValue
137+
* @param {Boolean} skipValidation whether it should be validated or not
138+
* @see ampersand-input-view.js#setValue
139+
*/
140+
setValue: function(value, skipValidation) {
141+
this.inputValue = value;
49142
if (!skipValidation && !this.getErrorMessage()) {
50143
this.shouldValidate = true;
51144
} else if (skipValidation) {
52145
this.shouldValidate = false;
53146
}
54147
},
55-
handleChange: function() {
56-
if (this.inputValue && this.changed) {
57-
this.shouldValidate = true;
148+
/**
149+
* Need to change the value empty check to empty arrays instead
150+
* @return {String} error message
151+
* @see ampersand-input-view.js#getErrorMessage
152+
*/
153+
getErrorMessage: function() {
154+
var message = '';
155+
if (this.required && this.value.length === 0) {
156+
return this.requiredMessage;
58157
}
59-
// for `file` type input fields, this is the only event and we need
60-
// to set this.inputValue here again.
61-
this.inputValue = this.clean();
158+
(this.tests || []).some(function(test) {
159+
message = test.call(this, this.value) || '';
160+
return message;
161+
}, this);
162+
return message;
163+
},
164+
/**
165+
* Don't access this.input.value as we don't work on input elements
166+
* @see ampersand-input-view.js#beforeSubmit
167+
*/
168+
beforeSubmit: function() {
169+
// at the point where we've tried to submit, we want to validate
170+
// everything from now on.
171+
this.shouldValidate = true;
62172
this.runTests();
173+
},
174+
loadFileButtonClicked: function() {
175+
dialog.showOpenDialog({
176+
properties: ['openFile', 'multiSelections']
177+
}, function(filenames) {
178+
this.inputValue = filenames || [];
179+
this.handleChange();
180+
}.bind(this));
63181
}
64182
});

src/connect/index.jade

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@
2121
- if (!method.enabled) classNames.push('hidden')
2222
- if (i === 0) classNames.push('active')
2323
div(class=classNames, id='auth-#{method._id}')
24-
24+
25+
hr
26+
2527
div(data-hook='sslselect-subview')
2628

2729
- for method, i in sslMethods

src/connect/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,8 @@ var ConnectView = View.extend({
9595
'change input[name=name]': 'onNameInputChanged',
9696
'input input': 'onAnyInputChanged',
9797
'change input': 'onAnyInputChanged',
98-
'change select': 'onAnyInputChanged'
98+
'change select': 'onAnyInputChanged',
99+
'click div.btn': 'onAnyInputChanged'
99100
},
100101

101102
/**

src/connect/index.less

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,12 @@
139139
overflow: auto;
140140
}
141141
label {
142-
width: 32%;
142+
width: 35%;
143143
text-align: right;
144144
padding: 5px 15px 0 0;
145145
}
146146
input, select, .form-item-file {
147-
width: 68%;
147+
width: 65%;
148148
}
149149
.form-item-file {
150150
input {

src/connect/ssl.js

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,7 @@ var SERVER = {
3939
fields: [
4040
new FileReaderView({
4141
name: 'ssl_ca',
42-
type: 'file',
4342
label: 'Certificate Authority',
44-
placeholder: '',
4543
required: true
4644
})
4745
]
@@ -58,31 +56,23 @@ var ALL = {
5856
fields: [
5957
new FileReaderView({
6058
name: 'ssl_ca',
61-
type: 'file',
6259
label: 'Certificate Authority',
63-
placeholder: '',
6460
required: true
6561
}),
6662
new FileReaderView({
6763
name: 'ssl_private_key',
68-
type: 'file',
6964
label: 'Certificate Key',
70-
placeholder: '',
7165
required: true
7266
}),
7367
new FileReaderView({
7468
name: 'ssl_certificate',
75-
type: 'file',
7669
label: 'Certificate',
77-
placeholder: '',
7870
required: true
7971
}),
8072
new InputView({
8173
template: inputTemplate,
8274
name: 'ssl_private_key_password',
83-
type: 'password',
8475
label: 'Private Key Password',
85-
placeholder: '',
8676
required: false
8777
})
8878
]

0 commit comments

Comments
 (0)