diff --git a/wherehows-web/app/components/application/banner-alerts.ts b/wherehows-web/app/components/application/banner-alerts.ts new file mode 100644 index 0000000000..f7c84784cd --- /dev/null +++ b/wherehows-web/app/components/application/banner-alerts.ts @@ -0,0 +1,49 @@ +import Component from '@ember/component'; +import { inject as service } from '@ember/service'; +import { computed, get, set } from '@ember/object'; +import ComputedProperty from '@ember/object/computed'; +import BannerService, { IBanner } from 'wherehows-web/services/banners'; + +export default class BannerAlerts extends Component { + /** + * Imports the service used to handle actual activation and dismissal of banners. The service also + * maintains the banners list + * @type {Serivce} + */ + banners: ComputedProperty = service(); + + /** + * Sets the tagname for the html element rendered by this component + */ + tagName = 'section'; + + /** + * Sets the classnames to attach to the html element rendered by this component + */ + classNames = ['banner-alerts']; + + /** + * Binds classnames to specific truthy values of properties on this component + */ + classNameBindings = ['isShowingBanners:banner-alerts--show:banner-alerts--hide']; + + /** + * References the banners service computation on whether or not we should be showing banners + */ + isShowingBanners: ComputedProperty = computed.alias('banners.isShowingBanners'); + + actions = { + /** + * Triggered by the user by clicking the dismiss icon on the banner, triggers the exiting state on the + * topmost (first in queue) banner and starts the timer/css animation for the dismissal action + * @param this - explicit this declaration for typescript + * @param {IBanner} banner - the banner as a subject for the dismissal action + */ + onDismissBanner(this: BannerAlerts, banner: IBanner) { + const banners = get(this, 'banners'); + + set(banner, 'isExiting', true); + banners.dequeue(); + } + }; +} diff --git a/wherehows-web/app/controllers/application.js b/wherehows-web/app/controllers/application.js index 10a02a33b2..69f637d5ad 100644 --- a/wherehows-web/app/controllers/application.js +++ b/wherehows-web/app/controllers/application.js @@ -22,6 +22,12 @@ export default Controller.extend({ notifications: service(), + /** + * Adds the service for banners in order to trigger the application to render the banners when + * they are triggered + */ + banners: service(), + init() { this._super(...arguments); diff --git a/wherehows-web/app/services/banners.ts b/wherehows-web/app/services/banners.ts new file mode 100644 index 0000000000..39da94c157 --- /dev/null +++ b/wherehows-web/app/services/banners.ts @@ -0,0 +1,87 @@ +import Service from '@ember/service'; +import { computed, get } from '@ember/object'; +import { NotificationEvent } from 'wherehows-web/services/notifications'; +import { delay } from 'wherehows-web/utils/promise-delay'; + +/** + * Expected properties to be found on a basic banner object added to our list + */ +export interface IBanner { + content: string; + type: NotificationEvent; + isExiting: boolean; + isDismissable: boolean; + iconName: string; +} + +/** + * When creating a new banner, helps to determine whether that banner isDismissable + */ +const isDismissableMap: { [key: string]: boolean } = { + [NotificationEvent['info']]: true, + [NotificationEvent['confirm']]: true, + [NotificationEvent['error']]: false +}; + +/** + * When creating a new banner, helps to determine the kind of font awesome icon indicator will be on + * the left side of the banner + */ +const iconNameMap: { [key: string]: string } = { + [NotificationEvent['info']]: 'info-circle', + [NotificationEvent['confirm']]: 'exclamation-circle', + [NotificationEvent['error']]: 'times-circle' +}; + +export default class BannerService extends Service { + /** + * Our cached list of banner objects to be rendered + * @type {Array} + */ + banners: Array = []; + + /** + * Computes the banners list elements to toggle the isShowingBanners flag, which not only activates the + * banner component but also triggers the navbar and app-container body to shift up/down to accommodate + * for the banner component space + * @type {ComputedProperty} + */ + isShowingBanners = computed('banners.@each.isExiting', function() { + const banners = get(this, 'banners'); + // Note: If we have no banners, flag should always be false. If we have more than one banner, flag + // should always be true, BUT if we only have one banner and it is in an exiting state we can go ahead + // and trigger this to be false to line up with the animation + return banners.length > 0 && (banners.length > 1 || !banners[0].isExiting); + }); + + /** + * Method to actually take care of removing the first banner from our queue. + */ + async dequeue(): Promise { + // Note: Since dequeuing the banner will remove it from the DOM, we don't want to actually dequeue + // until the removal animation, which takes 0.7 seconds, is completed. + const animationSpeed = 0.8; + const dismissDelay = delay(animationSpeed); + const banners = get(this, 'banners'); + + await dismissDelay; + banners.removeAt(0, 1); + } + + /** + * Method to add the banner to our queue. Takes the content and type of banner and creates a + * standardized interface that our servie can understand + * @param message - the message to put in the banner's content box + * @param {NotificationEvent} type - what type of banner notification we are going for (which will + * determine the appearance and interaction properties) + */ + addBanner(message: string, type: NotificationEvent = NotificationEvent['info']): void { + get(this, 'banners').addObject({ + type, + content: message, + isExiting: false, + isDismissable: isDismissableMap[type], + iconName: iconNameMap[type] + }); + } +} diff --git a/wherehows-web/app/styles/base/_base.scss b/wherehows-web/app/styles/base/_base.scss index a656e26885..ee9d2b36ec 100644 --- a/wherehows-web/app/styles/base/_base.scss +++ b/wherehows-web/app/styles/base/_base.scss @@ -11,13 +11,21 @@ html { * Apply padding to top and bottom to account for navigation and footer */ body { - padding-top: $nav-min-height; - padding-bottom: $nav-min-height; background-color: set-color(white, base); overflow-y: scroll; overflow-x: hidden; } +.app-container { + margin-top: $nav-min-height; + margin-bottom: $nav-min-height; + transition: margin 0.7s ease; + + &.banner-alert-offset { + margin-top: $nav-min-height + 52px; + } +} + /** * Make all elements from the DOM inherit from the parent box-sizing * Since `*` has a specificity of 0, it does not override the `html` value diff --git a/wherehows-web/app/styles/components/_all.scss b/wherehows-web/app/styles/components/_all.scss index 1263c7b0e2..2327555c68 100644 --- a/wherehows-web/app/styles/components/_all.scss +++ b/wherehows-web/app/styles/components/_all.scss @@ -1,5 +1,6 @@ @import 'navbar'; @import 'hero'; +@import 'application/all'; @import 'avatar/all'; @import 'dataset-author/all'; @import 'dataset-compliance/all'; diff --git a/wherehows-web/app/styles/components/_navbar.scss b/wherehows-web/app/styles/components/_navbar.scss index 516be3be25..37bcec5261 100644 --- a/wherehows-web/app/styles/components/_navbar.scss +++ b/wherehows-web/app/styles/components/_navbar.scss @@ -11,6 +11,7 @@ $item-spacing: 10px; * Explicitly sets the .navbar min-height to a shared value */ min-height: $nav-min-height; + transition: top 0.7s ease; &-inverse { /** @@ -32,6 +33,10 @@ $item-spacing: 10px; } } } + + &.navbar-top-offset { + top: 52px; + } } /** diff --git a/wherehows-web/app/styles/components/application/_all.scss b/wherehows-web/app/styles/components/application/_all.scss new file mode 100644 index 0000000000..4dd7ae4d40 --- /dev/null +++ b/wherehows-web/app/styles/components/application/_all.scss @@ -0,0 +1 @@ +@import 'banner-alerts'; diff --git a/wherehows-web/app/styles/components/application/_banner-alerts.scss b/wherehows-web/app/styles/components/application/_banner-alerts.scss new file mode 100644 index 0000000000..886e8e8fe4 --- /dev/null +++ b/wherehows-web/app/styles/components/application/_banner-alerts.scss @@ -0,0 +1,82 @@ +.banner-alerts { + position: fixed; + top: 0; + width: 100vw; + transition: height 0.6s ease; + overflow: hidden; + + &--show { + height: 52px; + } + + &--hide { + height: 0; + } + + .banner-alert { + transition: height 0.6s ease; + position: fixed; + top: 0; + left: 0; + width: 100vw; + min-width: 1128px; + overflow: hidden; + padding: 8px 24px; + display: flex; + flex-wrap: nowrap; + align-items: center; + color: get-color(white); + min-height: 0; + + &--active { + height: 52px; + } + + &--exiting { + height: 0; + padding: 0 24px; + } + + &--info { + background-color: get-color(slate6); + border-bottom: 1px solid get-color(slate3, 0.5); + } + + &--confirm { + background-color: get-color(orange5); + border-bottom: 1px solid get-color(orange3, 0.5); + } + + &--error { + background-color: get-color(red5); + border-bottom: 1px solid get-color(red3, 0.5); + } + + &:first-child { + z-index: 999; + } + + &:nth-child(2) { + z-index: 99; + } + + &:nth-child(n + 3) { + z-index: 1; + } + + &__icon { + margin-right: 16px; + } + + &__content { + width: 90vw; + min-width: 1056px; + } + + &__dismiss { + background-color: transparent; + border: none; + cursor: pointer; + } + } +} diff --git a/wherehows-web/app/templates/application.hbs b/wherehows-web/app/templates/application.hbs index 030d9037ca..2d926efc67 100644 --- a/wherehows-web/app/templates/application.hbs +++ b/wherehows-web/app/templates/application.hbs @@ -1,18 +1,22 @@ {{#if session.isAuthenticated}} + {{application/banner-alerts}} + {{partial "navbar"}} - {{#hero-container}} -
- Search for datasets, metrics and flows -
-
- {{search-bar-form didSearch=(action "didSearch")}} -
- {{/hero-container}} +
+ {{#hero-container}} +
+ Search for datasets, metrics and flows +
+
+ {{search-bar-form didSearch=(action "didSearch")}} +
+ {{/hero-container}} -
- {{partial "main"}} -
+
+ {{partial "main"}} +
+
{{notifications-service service=notifications}} {{else}} diff --git a/wherehows-web/app/templates/components/application/banner-alerts.hbs b/wherehows-web/app/templates/components/application/banner-alerts.hbs new file mode 100644 index 0000000000..2b8dbe977b --- /dev/null +++ b/wherehows-web/app/templates/components/application/banner-alerts.hbs @@ -0,0 +1,13 @@ +{{#each banners.banners as |banner|}} + +{{/each}} \ No newline at end of file diff --git a/wherehows-web/app/templates/navbar.hbs b/wherehows-web/app/templates/navbar.hbs index 156e73d0e5..1033e2c4ef 100644 --- a/wherehows-web/app/templates/navbar.hbs +++ b/wherehows-web/app/templates/navbar.hbs @@ -1,4 +1,4 @@ -