1
0
mirror of https://github.com/ansible/awx.git synced 2024-11-02 09:51:09 +03:00

Add partial implementation of drag and drop ssh key support

This commit is contained in:
gconsidine 2017-06-01 16:57:16 -04:00
parent 0ca53024f0
commit 952958a83c
15 changed files with 196 additions and 49 deletions

View File

@ -85,7 +85,7 @@ function AtFormController (eventService) {
let handled; let handled;
if (err.status === 400) { if (err.status === 400) {
handled = vm.setValidationErrors(err.data); handled = vm.handleValidationErrors(err.data);
} }
if (!handled) { if (!handled) {
@ -93,27 +93,37 @@ function AtFormController (eventService) {
} }
}; };
vm.setValidationErrors = errors => { vm.handleValidationErrors = errors => {
let errorMessageSet = false; let errorMessageSet = vm.setValidationMessages(errors);
if (errorMessageSet) {
vm.check();
}
return errorMessageSet;
};
vm.setValidationMessages = (errors, errorSet) => {
let errorMessageSet = errorSet || false;
for (let id in errors) { for (let id in errors) {
if (!Array.isArray(errors[id]) && typeof errors[id] === 'object') {
errorMessageSet = vm.setValidationMessages(errors[id], errorMessageSet);
continue;
}
vm.components vm.components
.filter(component => component.category === 'input') .filter(component => component.category === 'input')
.filter(component => errors[component.state.id])
.forEach(component => { .forEach(component => {
if (component.state._id === id) {
errorMessageSet = true; errorMessageSet = true;
component.state._rejected = true; component.state._rejected = true;
component.state._isValid = false; component.state._isValid = false;
component.state._message = errors[id].join(' '); component.state._message = errors[component.state.id].join(' ');
}
}); });
} }
if (errorMessageSet) {
vm.check();
}
return errorMessageSet; return errorMessageSet;
}; };

View File

@ -15,8 +15,14 @@
} }
} }
.at-InputGroup-button {
height: 100%;
}
.at-Input-button { .at-Input-button {
width: 72px; width: @at-input-button-width;
display: block;
height: 100%;
&, &:active, &:hover, &:focus { &, &:active, &:hover, &:focus {
color: @at-gray-dark-3x; color: @at-gray-dark-3x;
@ -36,6 +42,21 @@
} }
} }
.at-InputFile--hidden {
position: absolute;
height: 100%;
width: 100%;
left: 0;
right: @at-input-button-width;
z-index: -2;
opacity: 0;
}
.at-InputFile--drag {
z-index: 3;
}
.at-InputGroup { .at-InputGroup {
padding: 0; padding: 0;
margin: 0; margin: 0;
@ -64,6 +85,10 @@
height: @at-space-6x; height: @at-space-6x;
} }
.at-InputLabel {
display: inline-block;
}
.at-InputLabel-name { .at-InputLabel-name {
color: @at-gray-dark-4x; color: @at-gray-dark-4x;
font-size: @at-font-size-2x; font-size: @at-font-size-2x;
@ -71,6 +96,14 @@
text-transform: uppercase; text-transform: uppercase;
} }
.at-InputLabel-hint {
margin-left: @at-space-4x;
color: @at-gray-dark-3x;
font-size: @at-font-size;
font-weight: @at-font-weight;
line-height: @at-line-height-short;
}
.at-InputMessage--rejected { .at-InputMessage--rejected {
font-size: @at-font-size; font-size: @at-font-size;
color: @at-red; color: @at-red;
@ -82,8 +115,7 @@
color: @at-red; color: @at-red;
font-weight: @at-font-weight-2x; font-weight: @at-font-weight-2x;
font-size: @at-font-size-2x; font-size: @at-font-size-2x;
line-height: @at-line-height-short; margin: 0;
margin: @at-space-3x @at-space 0 0;
} }
.at-InputSelect { .at-InputSelect {
@ -123,3 +155,6 @@
} }
} }
.at-Textarea {
.at-mixin-FontFixedWidth();
}

View File

@ -75,9 +75,9 @@ function AtInputGroupController ($scope, $compile) {
if (input.type === 'string') { if (input.type === 'string') {
if (!input.multiline) { if (!input.multiline) {
if (input.secret) { if (input.secret) {
config._component = 'at-input-text';
} else {
config._component = 'at-input-secret'; config._component = 'at-input-secret';
} else {
config._component = 'at-input-text';
} }
} else { } else {
config._expand = true; config._expand = true;

View File

@ -1,5 +1,6 @@
<label class="at-InputLabel at-u-flat"> <label class="at-InputLabel">
<span ng-if="state.required" class="pull-left at-InputLabel-required">*</span> <span ng-if="state.required" class="at-InputLabel-required">*</span>
<span class="pull-left at-InputLabel-name">{{::state.label}}</span> <span class="at-InputLabel-name">{{::state.label}}</span>
<at-popover class="pull-left" state="state"></at-popover> <at-popover state="state"></at-popover>
<span ng-if="state._hint" class="at-InputLabel-hint">{{::state._hint}}</span>
</label> </label>

View File

@ -1,25 +1,89 @@
const DEFAULT_HINT = 'HINT: Drag and drop an SSH private key file on the field below.';
function atInputTextareaSecretLink (scope, element, attrs, controllers) { function atInputTextareaSecretLink (scope, element, attrs, controllers) {
let formController = controllers[0]; let formController = controllers[0];
let inputController = controllers[1]; let inputController = controllers[1];
if (scope.tab === '1') { if (scope.tab === '1') {
element.find('input')[0].focus(); element.find('textarea')[0].focus();
} }
inputController.init(scope, element, formController); inputController.init(scope, element, formController);
} }
function AtInputTextareaSecretController (baseInputController) { function AtInputTextareaSecretController (baseInputController, eventService) {
let vm = this || {}; let vm = this || {};
vm.init = (scope, element, form) => { let scope;
baseInputController.call(vm, 'input', scope, element, form); let textarea;
let input;
vm.init = (_scope_, element, form) => {
baseInputController.call(vm, 'input', _scope_, element, form);
scope = _scope_;
textarea = element.find('textarea')[0];
if (scope.state.format === 'ssh_private_key') {
scope.ssh = true;
scope.state._hint = scope.state._hint || DEFAULT_HINT;
input = element.find('input')[0];
vm.setFileListeners(textarea, input);
} else {
scope.isShown = true;
scope.buttonText = 'HIDE';
}
vm.check(); vm.check();
}; };
vm.setFileListeners = (textarea, input) => {
textarea
let eventNames = [
'drag',
'dragstart',
'dragend',
'dragover',
'dragenter',
'dragleave',
'drop'
];
eventService.addListener(textarea, 'dragenter', event => {
console.log('enter');
scope.drag = true;
});
eventService.addListener(input, ['dragleave', 'dragover'], event => {
console.log('exit');
scope.drag = false;
});
eventService.addListener(input, 'drop', event => {
vm.readFile(event.originalEvent.dataTransfer.files);
});
eventService.addListener(input, eventNames, event => {
event.stopPropagation();
});
};
vm.readFile = () => {
console.log(file);
};
vm.toggle = () => {
if (scope.isShown) {
scope.buttonText = 'SHOW';
} else {
scope.buttonText = 'HIDE';
} }
AtInputTextareaSecretController.$inject = ['BaseInputController']; scope.isShown = !scope.isShown;
};
}
AtInputTextareaSecretController.$inject = ['BaseInputController', 'EventService'];
function atInputTextareaSecret (pathService) { function atInputTextareaSecret (pathService) {
return { return {

View File

@ -2,14 +2,29 @@
<div class="form-group at-u-flat"> <div class="form-group at-u-flat">
<at-input-label></at-input-label> <at-input-label></at-input-label>
<textarea class="form-control at-Input" <div class="input-group">
<span class="input-group-btn at-InputGroup-button">
<button class="btn at-ButtonHollow--white at-Input-button"
ng-disabled="state._disabled || form.disabled"
ng-click="vm.toggle()">
{{ buttonText }}
</button>
</span>
<input ng-show="ssh"
class="at-InputFile--hidden"
ng-class="{'at-InputFile--drag': drag }"
type="file"
name="files" />
<textarea class="form-control at-Input at-Textarea"
ng-model="state._value" ng-model="state._value"
ng-class="{ 'at-Input--rejected': state._rejected }" ng-class="{ 'at-Input--rejected': state._rejected }"
ng-attr-maxlength="{{ state.max_length || undefined }}" ng-attr-maxlength="{{ state.max_length || undefined }}"
ng-attr-tabindex="{{ tab || undefined }}" ng-attr-tabindex="{{ tab || undefined }}"
ng-attr-placeholder="{{::state._placeholder || undefined }}" ng-attr-placeholder="{{::state._placeholder || undefined }}"
ng-change="vm.check()" ng-change="vm.check()"
ng-disabled="state._disabled || form.disabled" /></textarea> ng-disabled="state._disabled || form.disabled" />
</textarea>
</div>
<at-input-message></at-input-message> <at-input-message></at-input-message>
</div> </div>

View File

@ -2,7 +2,7 @@
<div class="form-group at-u-flat"> <div class="form-group at-u-flat">
<at-input-label></at-input-label> <at-input-label></at-input-label>
<textarea class="form-control at-Input" <textarea class="form-control at-Input at-Textarea"
ng-model="state._value" ng-model="state._value"
ng-class="{ 'at-Input--rejected': state._rejected }" ng-class="{ 'at-Input--rejected': state._rejected }"
ng-attr-maxlength="{{ state.max_length || undefined }}" ng-attr-maxlength="{{ state.max_length || undefined }}"

View File

@ -1,6 +1,9 @@
.at-Popover { .at-Popover {
padding: 0 0 0 @at-space-4x; padding: 0 0 0 @at-space-3x;
line-height: @at-line-height-short; }
.at-Popover--inline {
display: inline-block;
} }
.at-Popover-icon { .at-Popover-icon {

View File

@ -4,7 +4,7 @@ function atPopoverLink (scope, el, attr, controllers) {
let popover = container.getElementsByClassName('at-Popover-container')[0]; let popover = container.getElementsByClassName('at-Popover-container')[0];
let icon = container.getElementsByTagName('i')[0]; let icon = container.getElementsByTagName('i')[0];
popoverController.init(container, icon, popover); popoverController.init(scope, container, icon, popover);
} }
function AtPopoverController () { function AtPopoverController () {
@ -14,9 +14,10 @@ function AtPopoverController () {
let icon; let icon;
let popover; let popover;
vm.init = (_container_, _icon_, _popover_) => { vm.init = (scope, _container_, _icon_, _popover_) => {
icon = _icon_; icon = _icon_;
popover = _popover_; popover = _popover_;
scope.inline = scope.state._inline || true;
icon.addEventListener('click', vm.createDisplayListener()); icon.addEventListener('click', vm.createDisplayListener());
}; };

View File

@ -1,4 +1,6 @@
<div ng-show="state.help_text" class="at-Popover"> <div ng-show="state.help_text"
class="at-Popover"
ng-class="{ 'at-Popover--inline': inline }">
<span class="at-Popover-icon"> <span class="at-Popover-icon">
<i class="fa fa-question-circle"></i> <i class="fa fa-question-circle"></i>
</span> </span>

View File

@ -14,14 +14,22 @@ function EventService () {
el el
}; };
if (Array.isArray(name)) {
name.forEach(e => listener.el.addEventListener(e, listener.fn));
} else {
listener.el.addEventListener(listener.name, listener.fn); listener.el.addEventListener(listener.name, listener.fn);
}
return listener; return listener;
}; };
this.remove = listeners => { this.remove = listeners => {
listeners.forEach(listener => { listeners.forEach(listener => {
if (Array.isArray(listener.name)) {
listener.name.forEach(name => listener.el.removeEventListener(name, listener.fn));
} else {
listener.el.removeEventListener(listener.name, listener.fn); listener.el.removeEventListener(listener.name, listener.fn);
}
}); });
}; };
} }

View File

@ -76,3 +76,7 @@
color: @at-gray-dark-3x; color: @at-gray-dark-3x;
} }
} }
.at-mixin-FontFixedWidth () {
font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
}

View File

@ -4,7 +4,8 @@
* 1. Colors * 1. Colors
* 2. Typography * 2. Typography
* 3. Layout * 3. Layout
* 4. Misc * 4. Input
* 5. Misc
*/ */
// 1. Colors -------------------------------------------------------------------------------------- // 1. Colors --------------------------------------------------------------------------------------
@ -59,7 +60,10 @@
@at-space-5x: 15px; @at-space-5x: 15px;
@at-space-6x: 20px; @at-space-6x: 20px;
// 4. Misc -------------------------------------------------------------------------------------- // 4. Input ---------------------------------------------------------------------------------------
@at-input-button-width: 72px;
// 5. Misc ----------------------------------------------------------------------------------------
@at-border-radius: 5px; @at-border-radius: 5px;
@at-input-height: 30px; @at-input-height: 30px;
@at-popover-width: 320px; @at-popover-width: 320px;