diff --git a/awx/ui/client/lib/components/_index.less b/awx/ui/client/lib/components/_index.less index 729d577b54..1902014e11 100644 --- a/awx/ui/client/lib/components/_index.less +++ b/awx/ui/client/lib/components/_index.less @@ -5,3 +5,4 @@ @import 'popover/_index'; @import 'tabs/_index'; @import 'utility/_index'; +@import 'truncate/_index'; diff --git a/awx/ui/client/lib/components/components.strings.js b/awx/ui/client/lib/components/components.strings.js index 804dfd81ce..2fba205de5 100644 --- a/awx/ui/client/lib/components/components.strings.js +++ b/awx/ui/client/lib/components/components.strings.js @@ -1,6 +1,6 @@ function ComponentsStrings (BaseString) { BaseString.call(this, 'components'); - + let t = this.t; let ns = this.components; @@ -42,6 +42,11 @@ function ComponentsStrings (BaseString) { ns.lookup = { NOT_FOUND: t('That value was not found. Please enter or select a valid value.') }; + + ns.truncate = { + DEFAULT: t('Copy full revision to clipboard.'), + COPIED: t('Copied to clipboard.') + } } ComponentsStrings.$inject = ['BaseStringService']; diff --git a/awx/ui/client/lib/components/index.js b/awx/ui/client/lib/components/index.js index fa8b5d6d0b..40a0264e28 100644 --- a/awx/ui/client/lib/components/index.js +++ b/awx/ui/client/lib/components/index.js @@ -19,6 +19,7 @@ import panelBody from './panel/body.directive'; import popover from './popover/popover.directive'; import tab from './tabs/tab.directive'; import tabGroup from './tabs/group.directive'; +import truncate from './truncate/truncate.directive'; import BaseInputController from './input/base.controller'; import ComponentsStrings from './components.strings'; @@ -46,7 +47,8 @@ angular .directive('atPopover', popover) .directive('atTab', tab) .directive('atTabGroup', tabGroup) - .service('BaseInputController', BaseInputController) - .service('ComponentsStrings', ComponentsStrings); + .directive('atTruncate', truncate) + .service('ComponentsStrings', ComponentsStrings) + .service('BaseInputController', BaseInputController); diff --git a/awx/ui/client/lib/components/popover/_index.less b/awx/ui/client/lib/components/popover/_index.less index 8959835ba9..9018729eda 100644 --- a/awx/ui/client/lib/components/popover/_index.less +++ b/awx/ui/client/lib/components/popover/_index.less @@ -7,13 +7,19 @@ } .at-Popover-icon { - .at-mixin-ButtonIcon(); + .at-mixin-ButtonIcon(); color: @at-color-icon-popover; font-size: @at-font-size-icon; - padding: 0; + padding: 1px; margin: 0; } +.at-Popover-icon--defaultCursor { + i > { + cursor: default; + } +} + .at-Popover-container { visibility: hidden; opacity: 0; @@ -24,7 +30,7 @@ height: auto; position: fixed; z-index: 2000; - margin: 0 0 0 18px; + margin: 0; border-radius: @at-border-radius; box-shadow: 0 5px 10px rgba(0,0,0, 0.2); transition: opacity .15s linear; @@ -36,7 +42,7 @@ position: fixed; z-index: 1999; padding: 0; - margin: 8px 0 0 3px; + margin: 0; } .at-Popover-title { @@ -48,5 +54,5 @@ .at-Popover-text { margin: 0; padding: 0; - font-size: @at-font-size-body; + font-size: @at-font-size; } diff --git a/awx/ui/client/lib/components/popover/popover.directive.js b/awx/ui/client/lib/components/popover/popover.directive.js index 66857f6e4c..8f8ee5198a 100644 --- a/awx/ui/client/lib/components/popover/popover.directive.js +++ b/awx/ui/client/lib/components/popover/popover.directive.js @@ -1,10 +1,22 @@ +const DEFAULT_POSITION = 'right'; +const DEFAULT_ACTION = 'click'; +const DEFAULT_ICON = 'fa fa-question-circle'; +const DEFAULT_ALIGNMENT = 'inline'; +const DEFAULT_ARROW_HEIGHT = 14; +const DEFAULT_PADDING = 10; +const DEFAULT_REFRESH_DELAY = 50; +const DEFAULT_RESET_ON_EXIT = false; + function atPopoverLink (scope, el, attr, controllers) { let popoverController = controllers[0]; let container = el[0]; let popover = container.getElementsByClassName('at-Popover-container')[0]; let icon = container.getElementsByTagName('i')[0]; - popoverController.init(scope, container, icon, popover); + let done = scope.$watch('state', () => { + popoverController.init(scope, container, icon, popover); + done(); + }); } function AtPopoverController () { @@ -13,13 +25,35 @@ function AtPopoverController () { let container; let icon; let popover; + let scope; - vm.init = (scope, _container_, _icon_, _popover_) => { + vm.init = (_scope_, _container_, _icon_, _popover_) => { + scope = _scope_; icon = _icon_; popover = _popover_; - scope.inline = true; - icon.addEventListener('click', vm.createDisplayListener()); + scope.popover = scope.state.popover || {}; + + scope.popover.text = scope.state.help_text || scope.popover.text; + scope.popover.title = scope.state.label || scope.popover.title; + scope.popover.inline = scope.popover.inline || DEFAULT_ALIGNMENT; + scope.popover.position = scope.popover.position || DEFAULT_POSITION; + scope.popover.icon = scope.popover.icon || DEFAULT_ICON; + scope.popover.on = scope.popover.on || DEFAULT_ACTION; + scope.popover.resetOnExit = scope.popover.resetOnExit || DEFAULT_RESET_ON_EXIT; + + if (scope.popover.resetOnExit) { + scope.originalText = scope.popover.text; + scope.originalTitle = scope.popover.title; + } + + icon.addEventListener(scope.popover.on, vm.createDisplayListener()); + + scope.$watch('popover.text', vm.refresh); + + if (scope.popover.click) { + icon.addEventListener('click', scope.popover.click); + } }; vm.createDismissListener = (createEvent) => { @@ -30,16 +64,30 @@ function AtPopoverController () { return; } - vm.open = false; + vm.dismiss(); - popover.style.visibility = 'hidden'; - popover.style.opacity = 0; + if (scope.popover.on === 'mouseenter') { + icon.removeEventListener('mouseleave', vm.dismissListener); + } else { + window.addEventListener(scope.popover.on, vm.dismissListener); + } - window.removeEventListener('click', vm.dismissListener); window.removeEventListener('resize', vm.dismissListener); }; }; + vm.dismiss = (refresh) => { + if (!refresh && scope.popover.resetOnExit) { + scope.popover.text = scope.originalText; + scope.popover.title = scope.originalTitle; + } + + vm.open = false; + + popover.style.visibility = 'hidden'; + popover.style.opacity = 0; + }; + vm.isClickWithinPopover = (event, popover) => { let box = popover.getBoundingClientRect(); @@ -61,38 +109,96 @@ function AtPopoverController () { 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)); - - arrow.style.top = (iPos.top - iPos.height) + 'px'; - arrow.style.left = iPos.right + 'px'; - - if (cy < (pHeight / 2)) { - popover.style.top = '10px'; - } else { - popover.style.top = (cy - pHeight / 2) + 'px'; - } - - popover.style.left = cx + 'px'; - popover.style.visibility = 'visible'; - popover.style.opacity = 1; + vm.display(); vm.dismissListener = vm.createDismissListener(event); - window.addEventListener('click', vm.dismissListener); + if (scope.popover.on === 'mouseenter') { + icon.addEventListener('mouseleave', vm.dismissListener); + } else { + window.addEventListener(scope.popover.on, vm.dismissListener); + } + window.addEventListener('resize', vm.dismissListener); }; }; + + vm.refresh = () => { + if (!vm.open) { + return; + } + + vm.dismiss(true); + window.setTimeout(vm.display, DEFAULT_REFRESH_DELAY); + }; + + vm.getPositions = () => { + let arrow = popover.getElementsByClassName('at-Popover-arrow')[0]; + + arrow.style.lineHeight = `${DEFAULT_ARROW_HEIGHT}px`; + arrow.children[0].style.lineHeight = `${DEFAULT_ARROW_HEIGHT}px`; + + let data = { + arrow, + icon: icon.getBoundingClientRect(), + popover: popover.getBoundingClientRect(), + windowHeight: window.innerHeight + }; + + data.cx = Math.floor(data.icon.left + (data.icon.width / 2)); + data.cy = Math.floor(data.icon.top + (data.icon.height / 2)); + + return data; + }; + + vm.display = () => { + vm.open = true; + + let positions = vm.getPositions(); + + popover.style.visibility = 'visible'; + popover.style.opacity = 1; + + if (scope.popover.position === 'right') { + vm.displayRight(positions); + } else if (scope.popover.position === 'top') { + vm.displayTop(positions); + } + }; + + vm.displayRight = (pos) => { + let arrowTop = Math.floor((pos.cy - (pos.icon.height / 2))); + let arrowLeft = pos.cx + DEFAULT_PADDING; + + let popoverTop; + let popoverLeft = arrowLeft + DEFAULT_PADDING - 1; + + if (pos.cy < (pos.popover.height / 2)) { + popoverTop = DEFAULT_PADDING; + } else { + popoverTop = Math.floor((pos.cy - pos.popover.height / 2)); + } + + pos.arrow.style.top = `${arrowTop}px`; + pos.arrow.style.left = `${arrowLeft}px`; + + popover.style.top = `${popoverTop}px`; + popover.style.left = `${popoverLeft}px`; + }; + + vm.displayTop = (pos) => { + let arrowTop = pos.icon.top - pos.icon.height - DEFAULT_PADDING; + let arrowLeft = Math.floor(pos.icon.right - pos.icon.width - (pos.arrow.style.width / 2)); + + let popoverTop = pos.icon.top - pos.popover.height - pos.icon.height - 5; + let popoverLeft = Math.floor(pos.cx - (pos.popover.width / 2)); + + pos.arrow.style.top = `${arrowTop}px`; + pos.arrow.style.left = `${arrowLeft}px`; + + popover.style.top = `${popoverTop}px`; + popover.style.left = `${popoverLeft}px`; + }; } function atPopover (pathService) { @@ -115,4 +221,4 @@ atPopover.$inject = [ 'PathService' ]; -export default atPopover; +export default atPopover; \ No newline at end of file diff --git a/awx/ui/client/lib/components/popover/popover.partial.html b/awx/ui/client/lib/components/popover/popover.partial.html index f8acff1c84..0aa71a84ff 100644 --- a/awx/ui/client/lib/components/popover/popover.partial.html +++ b/awx/ui/client/lib/components/popover/popover.partial.html @@ -1,16 +1,16 @@ -