diff --git a/awx/ui/client/features/credentials/add-credentials.view.html b/awx/ui/client/features/credentials/add-credentials.view.html index 24e5fee3fc..8e566c2c23 100644 --- a/awx/ui/client/features/credentials/add-credentials.view.html +++ b/awx/ui/client/features/credentials/add-credentials.view.html @@ -12,7 +12,9 @@ - + + Type Details + diff --git a/awx/ui/client/lib/components/dynamic/_index.less b/awx/ui/client/lib/components/dynamic/_index.less index c6417b01e7..357d648081 100644 --- a/awx/ui/client/lib/components/dynamic/_index.less +++ b/awx/ui/client/lib/components/dynamic/_index.less @@ -1,12 +1,27 @@ .at-DynamicInputGroup { padding: 0; + margin: 0; margin: @at-space-6x 0 0 0; } -.at-DynamicInputGroup-inset { +.at-DynamicInputGroup-border { position: absolute; width: @at-inset-width; height: 100%; background: @at-gray; left: -@at-inset-width; } + +.at-DynamicInputGroup-title { + .at-mixin-Heading(@at-font-size-2x); + margin-top: 0; + margin-left: @at-space-5x; + margin-bottom: @at-space-4x; +} + +.at-DynamicInputGroup-divider { + clear: both; + margin: 0; + padding: 0; + height: @at-space-6x; +} diff --git a/awx/ui/client/lib/components/dynamic/input-group.directive.js b/awx/ui/client/lib/components/dynamic/input-group.directive.js index c615b640e6..a1df892aca 100644 --- a/awx/ui/client/lib/components/dynamic/input-group.directive.js +++ b/awx/ui/client/lib/components/dynamic/input-group.directive.js @@ -52,7 +52,9 @@ function AtDynamicInputGroupController ($scope, $compile) { inputs.forEach((input, i) => { if (input.type === 'string') { - if (input.secret) { + if (input.secret && input.multiline) { + input.component = 'at-input-textarea'; + } else if (input.secret) { input.component = 'at-input-secret'; } else if (input.multiline) { input.component = 'at-input-textarea'; @@ -61,10 +63,9 @@ function AtDynamicInputGroupController ($scope, $compile) { } } - components.push({ - options: input, + components.push(Object.assign({ element: vm.createElement(input, i) - }); + }, input)); }); return components; @@ -83,10 +84,15 @@ function AtDynamicInputGroupController ($scope, $compile) { vm.insert = components => { let group = document.createElement('div'); + let divider = angular.element(`
`)[0]; - components.forEach(component => { - group.appendChild(component.element[0]); - }); + for (let i = 0; i < components.length; i++) { + if (i !== 0 && (i % (12 / scope.col)) === 0) { + group.appendChild(divider); + } + + group.appendChild(components[i].element[0]); + } element.appendChild(group); }; diff --git a/awx/ui/client/lib/components/dynamic/input-group.partial.html b/awx/ui/client/lib/components/dynamic/input-group.partial.html index a30915c282..288476baba 100644 --- a/awx/ui/client/lib/components/dynamic/input-group.partial.html +++ b/awx/ui/client/lib/components/dynamic/input-group.partial.html @@ -1,4 +1,11 @@ -
-
-
+
+
+
+
+
+

+
+
+
+
diff --git a/awx/ui/client/lib/components/index.js b/awx/ui/client/lib/components/index.js index c983838abb..348722a82c 100644 --- a/awx/ui/client/lib/components/index.js +++ b/awx/ui/client/lib/components/index.js @@ -6,7 +6,9 @@ import formAction from './form/action.directive'; import inputLabel from './input/label.directive'; import inputSearch from './input/search.directive'; import inputSelect from './input/select.directive'; +import inputSecret from './input/secret.directive'; import inputText from './input/text.directive'; +import inputTextarea from './input/textarea.directive'; import panel from './panel/panel.directive'; import panelHeading from './panel/heading.directive'; import panelBody from './panel/body.directive'; @@ -14,6 +16,9 @@ import popover from './popover/popover.directive'; import toggleButton from './toggle/button.directive'; import toggleContent from './toggle/content.directive'; +import BaseInputController from './input/base.controller'; + + angular .module('at.lib.components', []) .directive('atActionGroup', actionGroup) @@ -23,13 +28,16 @@ angular .directive('atFormAction', formAction) .directive('atInputLabel', inputLabel) .directive('atInputSearch', inputSearch) + .directive('atInputSecret', inputSecret) .directive('atInputSelect', inputSelect) .directive('atInputText', inputText) + .directive('atInputTextarea', inputTextarea) .directive('atPanel', panel) .directive('atPanelHeading', panelHeading) .directive('atPanelBody', panelBody) .directive('atPopover', popover) .directive('atToggleButton', toggleButton) - .directive('atToggleContent', toggleContent); + .directive('atToggleContent', toggleContent) + .service('BaseInputController', BaseInputController); diff --git a/awx/ui/client/lib/components/input/_index.less b/awx/ui/client/lib/components/input/_index.less index c20b04ef25..a92b8fbdbf 100644 --- a/awx/ui/client/lib/components/input/_index.less +++ b/awx/ui/client/lib/components/input/_index.less @@ -19,6 +19,9 @@ } .at-InputLabel { +} + +.at-InputLabel-name { color: @at-gray-dark-4x; font-size: @at-font-size-2x; font-weight: @at-font-weight; diff --git a/awx/ui/client/lib/components/input/base.controller.js b/awx/ui/client/lib/components/input/base.controller.js new file mode 100644 index 0000000000..978528cd44 --- /dev/null +++ b/awx/ui/client/lib/components/input/base.controller.js @@ -0,0 +1,38 @@ +function BaseInputController () { + return function extend (type, scope, form) { + let vm = this; + + scope.state = scope.state || {}; + + scope.state.required = scope.state.required || false; + scope.state.isValid = scope.state.isValid || false; + scope.state.disabled = scope.state.disabled || false; + + form.use(type, scope); + + vm.validate = () => { + let isValid = true; + + if (scope.state.required && !scope.state.value) { + isValid = false; + } + + if (scope.state.validate && !scope.state.validate(scope.state.value)) { + isValid = false; + } + + return isValid; + }; + + vm.check = () => { + let isValid = vm.validate(); + + if (isValid !== scope.state.isValid) { + scope.state.isValid = isValid; + form.check(); + } + }; + }; +} + +export default BaseInputController; diff --git a/awx/ui/client/lib/components/input/label.partial.html b/awx/ui/client/lib/components/input/label.partial.html index 4130ee1a3c..124ae8a67f 100644 --- a/awx/ui/client/lib/components/input/label.partial.html +++ b/awx/ui/client/lib/components/input/label.partial.html @@ -1,5 +1,5 @@ diff --git a/awx/ui/client/lib/components/input/secret.directive.js b/awx/ui/client/lib/components/input/secret.directive.js new file mode 100644 index 0000000000..6bf95c2021 --- /dev/null +++ b/awx/ui/client/lib/components/input/secret.directive.js @@ -0,0 +1,44 @@ +function atInputSecretLink (scope, el, attrs, controllers) { + let formController = controllers[0]; + let inputController = controllers[1]; + + if (scope.tab === '1') { + el.find('input')[0].focus(); + } + + inputController.init(scope, formController); +} + +function AtInputSecretController (baseInputController) { + let vm = this || {}; + + vm.init = (scope, form) => { + baseInputController.call(vm, 'input', scope, form); + + vm.check(); + }; +} + +AtInputSecretController.$inject = ['BaseInputController']; + +function atInputSecret (pathService) { + return { + restrict: 'E', + transclude: true, + replace: true, + require: ['^^atForm', 'atInputSecret'], + templateUrl: pathService.getPartialPath('components/input/secret'), + controller: AtInputSecretController, + controllerAs: 'vm', + link: atInputSecretLink, + scope: { + state: '=', + col: '@', + tab: '@' + } + }; +} + +atInputSecret.$inject = ['PathService']; + +export default atInputSecret; diff --git a/awx/ui/client/lib/components/input/secret.partial.html b/awx/ui/client/lib/components/input/secret.partial.html new file mode 100644 index 0000000000..f9441e6ecc --- /dev/null +++ b/awx/ui/client/lib/components/input/secret.partial.html @@ -0,0 +1,10 @@ +
+
+ + +
+
diff --git a/awx/ui/client/lib/components/input/select.directive.js b/awx/ui/client/lib/components/input/select.directive.js index 8ac2bef4a4..73f4d66e95 100644 --- a/awx/ui/client/lib/components/input/select.directive.js +++ b/awx/ui/client/lib/components/input/select.directive.js @@ -13,27 +13,19 @@ function atInputSelectLink (scope, el, attrs, controllers) { inputController.init(formController, scope, elements); } -function AtInputSelectController (eventService) { +function AtInputSelectController (baseInputController, eventService) { let vm = this || {}; let scope; - let state; - let form; let input; let select; - vm.init = (_form_, _scope_, elements) => { - form = _form_; + vm.init = (form, _scope_, elements) => { + baseInputController.call(vm, 'input', _scope_, form); + + scope = _scope_; input = elements.input; select = elements.select; - scope = _scope_; - state = scope.state || {}; - - state.required = state.required || false; - state.isValid = state.isValid || false; - state.disabled = state.disabled || false; - - form.use('input', scope); vm.setListeners(); vm.check(); @@ -56,32 +48,9 @@ function AtInputSelectController (eventService) { scope.$on('$destroy', () => eventService.remove(listeners)); }; - - vm.validate = () => { - let isValid = true; - - if (state.required && !state.value) { - isValid = false; - } - - if (state.validate && !state.validate(state.value)) { - isValid = false; - } - - return isValid; - }; - - vm.check = () => { - let isValid = vm.validate(); - - if (isValid !== state.isValid) { - state.isValid = isValid; - form.check(); - } - }; } -AtInputSelectController.$inject = ['EventService']; +AtInputSelectController.$inject = ['BaseInputController', 'EventService']; function atInputSelect (pathService) { return { diff --git a/awx/ui/client/lib/components/input/text.directive.js b/awx/ui/client/lib/components/input/text.directive.js index 8b2443bb97..e72be4faa4 100644 --- a/awx/ui/client/lib/components/input/text.directive.js +++ b/awx/ui/client/lib/components/input/text.directive.js @@ -6,54 +6,21 @@ function atInputTextLink (scope, el, attrs, controllers) { el.find('input')[0].focus(); } - inputController.init(formController, scope); + inputController.init(scope, formController); } -function AtInputTextController () { +function AtInputTextController (baseInputController) { let vm = this || {}; - let scope; - let state; - let form; - - vm.init = (_form_, _scope_) => { - form = _form_; - scope = _scope_; - state = scope.state || {}; - - state.required = state.required || false; - state.isValid = state.isValid || false; - state.disabled = state.disabled || false; - - form.use('input', scope); + vm.init = (scope, form) => { + baseInputController.call(vm, 'input', scope, form); vm.check(); }; - - vm.validate = () => { - let isValid = true; - - if (state.required && !state.value) { - isValid = false; - } - - if (state.validate && !state.validate(state.value)) { - isValid = false; - } - - return isValid; - }; - - vm.check = () => { - let isValid = vm.validate(); - - if (isValid !== state.isValid) { - state.isValid = isValid; - form.check(); - } - }; } +AtInputTextController.$inject = ['BaseInputController']; + function atInputText (pathService) { return { restrict: 'E', diff --git a/awx/ui/client/lib/components/input/textarea.directive.js b/awx/ui/client/lib/components/input/textarea.directive.js new file mode 100644 index 0000000000..5dc9e6e9d5 --- /dev/null +++ b/awx/ui/client/lib/components/input/textarea.directive.js @@ -0,0 +1,44 @@ +function atInputTextareaLink (scope, el, attrs, controllers) { + let formController = controllers[0]; + let inputController = controllers[1]; + + if (scope.tab === '1') { + el.find('input')[0].focus(); + } + + inputController.init(scope, formController); +} + +function AtInputTextareaController (baseInputController) { + let vm = this || {}; + + vm.init = (scope, form) => { + baseInputController.call(vm, 'input', scope, form); + + vm.check(); + }; +} + +AtInputTextareaController.$inject = ['BaseInputController']; + +function atInputTextarea (pathService) { + return { + restrict: 'E', + transclude: true, + replace: true, + require: ['^^atForm', 'atInputTextarea'], + templateUrl: pathService.getPartialPath('components/input/textarea'), + controller: AtInputTextareaController, + controllerAs: 'vm', + link: atInputTextareaLink, + scope: { + state: '=', + col: '@', + tab: '@' + } + }; +} + +atInputTextarea.$inject = ['PathService']; + +export default atInputTextarea; diff --git a/awx/ui/client/lib/components/input/textarea.partial.html b/awx/ui/client/lib/components/input/textarea.partial.html new file mode 100644 index 0000000000..d535dc876d --- /dev/null +++ b/awx/ui/client/lib/components/input/textarea.partial.html @@ -0,0 +1,11 @@ +
+
+ + +
+
diff --git a/awx/ui/client/lib/components/panel/_index.less b/awx/ui/client/lib/components/panel/_index.less index 3770023b6c..1bf5227be8 100644 --- a/awx/ui/client/lib/components/panel/_index.less +++ b/awx/ui/client/lib/components/panel/_index.less @@ -20,11 +20,5 @@ } .at-Panel-headingTitle { - color: @at-gray-dark-4x; - font-size: @at-font-size-3x; - font-weight: @at-font-weight-2x; - line-height: @at-line-height-short; - text-transform: uppercase; - margin: 0; - padding: 0; + .at-mixin-Heading(@at-font-size-3x); } diff --git a/awx/ui/client/lib/components/popover/_index.less b/awx/ui/client/lib/components/popover/_index.less index b3bbb5f60a..4c731079f0 100644 --- a/awx/ui/client/lib/components/popover/_index.less +++ b/awx/ui/client/lib/components/popover/_index.less @@ -19,6 +19,7 @@ border-radius: @at-border-radius; box-shadow: 0 5px 10px rgba(0,0,0, 0.2); transition: opacity .15s linear; + font-weight: @at-font-weight } .at-Popover-arrow { diff --git a/awx/ui/client/lib/components/popover/popover.directive.js b/awx/ui/client/lib/components/popover/popover.directive.js index dba58a1680..5881a93c68 100644 --- a/awx/ui/client/lib/components/popover/popover.directive.js +++ b/awx/ui/client/lib/components/popover/popover.directive.js @@ -1,64 +1,91 @@ -let pathService; - -function link (scope, el, attr) { +function atPopoverLink (scope, el, attr, controllers) { + let popoverController = controllers[0]; let icon = el[0]; let popover = icon.getElementsByClassName('at-Popover-container')[0]; - el.on('click', createDisplayListener(icon, popover)); + popoverController.init(icon, popover); } -function createDisplayListener (icon, popover) { - return event => { - let arrow = popover.getElementsByClassName('at-Popover-arrow')[0]; +function AtPopoverController () { + let vm = this; - let iPos = icon.getBoundingClientRect(); - let pPos = popover.getBoundingClientRect(); + let icon; + let popover; - let wHeight = window.clientHeight; - let pHeight = pPos.height; + vm.init = (_icon_, _popover_) => { + icon = _icon_; + popover = _popover_; - let cx = Math.floor(iPos.left + (iPos.width / 2)); - let cy = Math.floor(iPos.top + (iPos.height / 2)); + icon.addEventListener('click', vm.createDisplayListener()); + }; - if (cy < (pHeight / 2)) { - popover.style.top = '10px'; - } else { - popover.style.top = (cy - pHeight / 2) + 'px'; - } + vm.createDismissListener = (createEvent) => { + return event => { + event.stopPropagation(); - popover.style.left = cx + 'px'; + vm.open = false; - arrow.style.top = iPos.top + 'px'; - arrow.style.left = iPos.left + 20 + 'px'; + popover.style.visibility = 'hidden'; + popover.style.opacity = 0; - popover.style.visibility = 'visible'; - popover.style.opacity = 1; + window.removeEventListener('click', vm.dismissListener); + window.removeEventListener('resize', vm.dismissListener); + }; + }; - let dismissListener = createDismissListener(popover); + vm.createDisplayListener = () => { + return event => { + if (vm.open) { + return; + } - window.addEventListener('mousedown', dismissListener); - window.addEventListener('resize', dismissListener); + event.stopPropagation(); + + vm.open = true; + + let arrow = popover.getElementsByClassName('at-Popover-arrow')[0]; + + let iPos = icon.getBoundingClientRect(); + let pPos = popover.getBoundingClientRect(); + + let wHeight = window.clientHeight; + let pHeight = pPos.height; + + let cx = Math.floor(iPos.left + (iPos.width / 2)); + let cy = Math.floor(iPos.top + (iPos.height / 2)); + + if (cy < (pHeight / 2)) { + popover.style.top = '10px'; + } else { + popover.style.top = (cy - pHeight / 2) + 'px'; + } + + popover.style.left = cx + 'px'; + + arrow.style.top = iPos.top + 'px'; + arrow.style.left = iPos.left + 20 + 'px'; + + popover.style.visibility = 'visible'; + popover.style.opacity = 1; + + vm.dismissListener = vm.createDismissListener(event); + + window.addEventListener('click', vm.dismissListener); + window.addEventListener('resize', vm.dismissListener); + }; }; } -function createDismissListener (popover) { - return function dismissListener () { - popover.style.visibility = 'hidden'; - popover.style.opacity = 0; - - window.removeEventListener('mousedown', dismissListener); - }; -} - -function atPopover (_pathService_) { - pathService = _pathService_; - +function atPopover (pathService) { return { restrict: 'E', replace: true, transclude: true, + require: ['atPopover'], templateUrl: pathService.getPartialPath('components/popover/popover'), - link, + controller: AtPopoverController, + controllerAs: 'vm', + link: atPopoverLink, scope: { state: '=' } diff --git a/awx/ui/client/lib/components/popover/popover.partial.html b/awx/ui/client/lib/components/popover/popover.partial.html index 1ad5a9dcbf..613a722b58 100644 --- a/awx/ui/client/lib/components/popover/popover.partial.html +++ b/awx/ui/client/lib/components/popover/popover.partial.html @@ -1,4 +1,4 @@ -
+
@@ -6,6 +6,6 @@
-
{{::state.options.help_text}}
+
{{::state.help_text}}
diff --git a/awx/ui/client/lib/theme/_mixins.less b/awx/ui/client/lib/theme/_mixins.less index 28368fa30a..a061874bc2 100644 --- a/awx/ui/client/lib/theme/_mixins.less +++ b/awx/ui/client/lib/theme/_mixins.less @@ -10,6 +10,16 @@ } } +.at-mixin-Heading (@size) { + color: @at-gray-dark-4x; + font-size: @size; + font-weight: @at-font-weight-2x; + line-height: @at-line-height-short; + text-transform: uppercase; + margin: 0; + padding: 0; +} + .at-mixin-Button () { padding: @at-space-2x @at-space-4x; }