diff --git a/demo-shell/src/app/components/cloud/community/community-start-process-cloud.component.ts b/demo-shell/src/app/components/cloud/community/community-start-process-cloud.component.ts index cffa9e5f38..f4412033ab 100644 --- a/demo-shell/src/app/components/cloud/community/community-start-process-cloud.component.ts +++ b/demo-shell/src/app/components/cloud/community/community-start-process-cloud.component.ts @@ -49,8 +49,7 @@ export class CommunityStartProcessCloudDemoComponent implements OnInit { openSnackMessage(event: any) { this.notificationService.openSnackMessage( - event.response.body.message, - 4000 + event.response.body.message ); } } diff --git a/demo-shell/src/app/components/cloud/community/community-start-task-cloud.component.ts b/demo-shell/src/app/components/cloud/community/community-start-task-cloud.component.ts index fda2cd29b2..68c28fe204 100644 --- a/demo-shell/src/app/components/cloud/community/community-start-task-cloud.component.ts +++ b/demo-shell/src/app/components/cloud/community/community-start-task-cloud.component.ts @@ -42,8 +42,7 @@ export class CommunityStartTaskCloudDemoComponent { openSnackMessage(event: any) { this.notificationService.openSnackMessage( - event.response.body.message, - 4000 + event.response.body.message ); } } diff --git a/demo-shell/src/app/components/file-view/file-view.component.ts b/demo-shell/src/app/components/file-view/file-view.component.ts index 5fb67fda7b..e8e4d75523 100644 --- a/demo-shell/src/app/components/file-view/file-view.component.ts +++ b/demo-shell/src/app/components/file-view/file-view.component.ts @@ -17,8 +17,14 @@ import { Component, OnInit, ViewEncapsulation } from '@angular/core'; import { ActivatedRoute, Router, PRIMARY_OUTLET } from '@angular/router'; -import { ContentService, AllowableOperationsEnum, PermissionsEnum, NodesApiService, FileUploadErrorEvent } from '@alfresco/adf-core'; -import { MatSnackBar } from '@angular/material/snack-bar'; +import { + ContentService, + AllowableOperationsEnum, + PermissionsEnum, + NodesApiService, + FileUploadErrorEvent, + NotificationService +} from '@alfresco/adf-core'; import { PreviewService } from '../../services/preview.service'; @Component({ @@ -64,10 +70,10 @@ export class FileViewComponent implements OnInit { constructor(private router: Router, private route: ActivatedRoute, - private snackBar: MatSnackBar, private nodeApiService: NodesApiService, private contentServices: ContentService, - private preview: PreviewService) { + private preview: PreviewService, + private notificationService: NotificationService) { } ngOnInit() { @@ -105,7 +111,7 @@ export class FileViewComponent implements OnInit { onUploadError(event: FileUploadErrorEvent) { const errorMessage = event.error; - this.snackBar.open(errorMessage, '', { duration: 4000 }); + this.notificationService.showError(errorMessage); } toggleEmptyMetadata() { diff --git a/demo-shell/src/app/components/files/version-manager-dialog-adapter.component.ts b/demo-shell/src/app/components/files/version-manager-dialog-adapter.component.ts index e15ba65209..c71aebd99d 100644 --- a/demo-shell/src/app/components/files/version-manager-dialog-adapter.component.ts +++ b/demo-shell/src/app/components/files/version-manager-dialog-adapter.component.ts @@ -18,9 +18,8 @@ import { Component, Inject, ViewEncapsulation } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MinimalNodeEntryEntity } from '@alfresco/js-api'; -import { MatSnackBar } from '@angular/material/snack-bar'; import { PreviewService } from '../../services/preview.service'; -import { FileUploadErrorEvent } from '@alfresco/adf-core'; +import { FileUploadErrorEvent, NotificationService } from '@alfresco/adf-core'; @Component({ templateUrl: './version-manager-dialog-adapter.component.html', @@ -37,8 +36,8 @@ export class VersionManagerDialogAdapterComponent { showVersionComparison = false; constructor(private previewService: PreviewService, + private notificationService: NotificationService, @Inject(MAT_DIALOG_DATA) data: any, - private snackBar: MatSnackBar, private containingDialog?: MatDialogRef) { this.contentEntry = data.contentEntry; this.newFileVersion = data.hasOwnProperty('newFileVersion') ? data.newFileVersion : this.newFileVersion; @@ -48,7 +47,7 @@ export class VersionManagerDialogAdapterComponent { uploadError(event: FileUploadErrorEvent) { const errorMessage = event.error; - this.snackBar.open(errorMessage, '', { duration: 4000 }); + this.notificationService.showError(errorMessage); } close() { diff --git a/docs/README.md b/docs/README.md index 5bdafc69a2..e523377016 100644 --- a/docs/README.md +++ b/docs/README.md @@ -116,6 +116,7 @@ for more information about installing and using the source code. | [Sidebar action menu component](core/components/sidebar-action-menu.component.md) | Displays a sidebar-action menu information panel. | [Source](../lib/core/layout/components/sidebar-action/sidebar-action-menu.component.ts) | | [Sidenav Layout component](core/components/sidenav-layout.component.md) | Displays the standard three-region ADF application layout. | [Source](../lib/core/layout/components/sidenav-layout/sidenav-layout.component.ts) | | [Sorting Picker Component](core/components/sorting-picker.component.md) | Selects from a set of predefined sorting definitions and directions. | [Source](../lib/core/sorting-picker/sorting-picker.component.ts) | +| [Snackbar Content Component](core/components/snackbar-content.component.md) | Custom content for Snackbar which allows use icon as action.| [Source](../lib/core/src/lib/snackbar-content/snackbar-content.component.ts) | | [Start Form component](core/components/start-form.component.md) | Displays the Start Form for a process. | [Source](../lib/process-services/src/lib/form/start-form.component.ts) | | [Text Mask directive](core/components/text-mask.component.md) | Implements text field input masks. | [Source](../lib/core/form/components/widgets/text/text-mask.component.ts) | | [Toolbar Divider Component](core/components/toolbar-divider.component.md) | Divides groups of elements in a Toolbar with a visual separator. | [Source](../lib/core/toolbar/toolbar-divider.component.ts) | diff --git a/docs/core/components/snackbar-content.component.md b/docs/core/components/snackbar-content.component.md new file mode 100644 index 0000000000..a50ed0ecea --- /dev/null +++ b/docs/core/components/snackbar-content.component.md @@ -0,0 +1,46 @@ +--- +Title: Snackbar Content Component +Added: v5.1.0 +Status: Active +Last reviewed: 2022-11-08 +--- + +# [Snackbar Content Component](lib/core/src/lib/snackbar-content/snackbar-content.component.ts "Defined in snackbar-content.component.ts") + +Custom content for Snackbar which allows use icon as action. + +## Basic Usage + +```ts +snackBar.openFromComponent(SnackbarContentComponent, { + data: { + message: 'Some message', + actionLabel: "Some action label", + showAction: true + } +}); +``` + +## Class members + +### Properties + +| Name | Type | Default value | Description | +|------|----------------|---------------|------------------------------------------------------------------| +| data | `SnackbarData` | false | Object which is injected into snackbar's content with it's data. | + +### Snackbar Data + +| Name | Type | Default value | Description | +|-------------|-----------|---------------|-------------------------------------------------------------------------| +| actionLabel | `string` | false | Displayed action as a text. | +| actionIcon | `string` | false | Displayed action as an material icon. | +| actionIconAriaLabel | `string` | false | Sets aria-label attribute for button with icon action. | +| message | `string` | false | Visible snackbar's message for user. | +| showAction | `boolean` | false | True if action should be visible, false in other case. | +| callActionOnIconClick | `boolean` | false | True if clicking on icon should to trigger action, false in other case. | + +## Details + +Snackbar allows using action as string by default which causes that there is no possibility to use mat-icon inside snackbar's content. +That custom content for Angular material Snackbar allows for that. diff --git a/docs/core/services/notification.service.md b/docs/core/services/notification.service.md index 0ef58f75ff..27779c9b93 100644 --- a/docs/core/services/notification.service.md +++ b/docs/core/services/notification.service.md @@ -38,18 +38,21 @@ Shows a notification message with optional feedback. - _message:_ `string` - Text message or translation key for the message. - _action:_ `string` - (Optional) Action name - _interpolateArgs:_ `any` - (Optional) The interpolation parameters to add for the translation + - _showError:_ `boolean` - (Optional) True if action should be visible, false if not. Default: true. - **Returns** [`MatSnackBarRef`](https://material.angular.io/components/snack-bar/overview)`` - - **showInfo**(message: `string`, action?: `string`, interpolateArgs?: `any`): [`MatSnackBarRef`](https://material.angular.io/components/snack-bar/overview)``
Rase info message - _message:_ `string` - Text message or translation key for the message. - _action:_ `string` - (Optional) Action name - _interpolateArgs:_ `any` - (Optional) The interpolation parameters to add for the translation + - _showError:_ `boolean` - (Optional) True if action should be visible, false if not. Default: true. - **Returns** [`MatSnackBarRef`](https://material.angular.io/components/snack-bar/overview)`` - - **showWarning**(message: `string`, action?: `string`, interpolateArgs?: `any`): [`MatSnackBarRef`](https://material.angular.io/components/snack-bar/overview)``
Rase warning message - _message:_ `string` - Text message or translation key for the message. - _action:_ `string` - (Optional) Action name - _interpolateArgs:_ `any` - (Optional) The interpolation parameters to add for the translation + - _showError:_ `boolean` - (Optional) True if action should be visible, false if not. Default: true. - **Returns** [`MatSnackBarRef`](https://material.angular.io/components/snack-bar/overview)`` - ## Details diff --git a/docs/versionIndex.md b/docs/versionIndex.md index 0091180269..815034fd6d 100644 --- a/docs/versionIndex.md +++ b/docs/versionIndex.md @@ -12,6 +12,7 @@ backend services have been tested with each released version of ADF. ## Versions +- [v5.1.0](#v510) - [v4.7.0](#v470) - [v4.6.0](#v460) - [v4.5.0](#v450) @@ -39,6 +40,12 @@ backend services have been tested with each released version of ADF. - [v2.1.0](#v210) - [v2.0.0](#v200) +## v5.1.0 + + +- [Snackbar Content](core/components/snackbar-content.component.md) + + ## v4.7.0 diff --git a/e2e/core/pages/notification.page.ts b/e2e/core/pages/notification.page.ts index 16d43bc0d2..a247cbc078 100644 --- a/e2e/core/pages/notification.page.ts +++ b/e2e/core/pages/notification.page.ts @@ -92,7 +92,7 @@ export class NotificationDemoPage { } async clickActionButton(): Promise { - await browser.executeScript(`document.querySelector("simple-snack-bar > div > button").click();`); + await browser.executeScript(`document.querySelector("[data-automation-id='adf-snackbar-message-content-action-button']").click();`); } async clearMessage(): Promise { diff --git a/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.component.spec.ts b/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.component.spec.ts index b6e6941c01..4fcf9af598 100644 --- a/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.component.spec.ts +++ b/lib/content-services/src/lib/permission-manager/components/permission-list/permission-list.component.spec.ts @@ -180,7 +180,7 @@ describe('PermissionListComponent', () => { .toBe('PERMISSION_MANAGER.LABELS.INHERITED-PERMISSIONS PERMISSION_MANAGER.LABELS.ON'); expect(element.querySelector('span[title="total"]').textContent.trim()) .toBe('PERMISSION_MANAGER.LABELS.INHERITED-SUBTITLE'); - expect(document.querySelector('simple-snack-bar').textContent) + expect(document.querySelector('.adf-snackbar-message-content').textContent) .toContain('PERMISSION_MANAGER.ERROR.NOT-ALLOWED'); }); diff --git a/lib/content-services/src/lib/upload/components/upload-drag-area.component.ts b/lib/content-services/src/lib/upload/components/upload-drag-area.component.ts index 75645f2d05..bab59eeef6 100644 --- a/lib/content-services/src/lib/upload/components/upload-drag-area.component.ts +++ b/lib/content-services/src/lib/upload/components/upload-drag-area.component.ts @@ -76,7 +76,7 @@ export class UploadDragAreaComponent extends UploadBase implements NodeAllowable const messageTranslate = this.translationService.instant('FILE_UPLOAD.MESSAGES.PROGRESS'); const actionTranslate = this.translationService.instant('FILE_UPLOAD.ACTION.UNDO'); - this.notificationService.openSnackMessageAction(messageTranslate, actionTranslate, 3000).onAction().subscribe(() => { + this.notificationService.openSnackMessageAction(messageTranslate, actionTranslate).onAction().subscribe(() => { this.uploadService.cancelUpload(...latestFilesAdded); }); } diff --git a/lib/core/src/lib/core.module.ts b/lib/core/src/lib/core.module.ts index 02535a3751..9df7a05df5 100644 --- a/lib/core/src/lib/core.module.ts +++ b/lib/core/src/lib/core.module.ts @@ -66,6 +66,7 @@ import { LegacyApiClientModule } from './api-factories/legacy-api-client.module' import { RichTextEditorModule } from './rich-text-editor/rich-text-editor.module'; import { HttpClientModule, HttpClientXsrfModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { AuthenticationService } from './services/authentication.service'; +import { MAT_SNACK_BAR_DEFAULT_OPTIONS } from '@angular/material/snack-bar'; @NgModule({ imports: [ @@ -177,7 +178,13 @@ export class CoreModule { multi: true }, { provide: HTTP_INTERCEPTORS, useClass: AuthenticationInterceptor, multi: true }, - { provide: Authentication, useClass: AuthenticationService } + { provide: Authentication, useClass: AuthenticationService }, + { + provide: MAT_SNACK_BAR_DEFAULT_OPTIONS, + useValue: { + duration: 10000 + } + } ] }; } diff --git a/lib/core/src/lib/i18n/en.json b/lib/core/src/lib/i18n/en.json index 654378d23b..1b4fdec6c7 100644 --- a/lib/core/src/lib/i18n/en.json +++ b/lib/core/src/lib/i18n/en.json @@ -21,6 +21,7 @@ "UNCLAIM": "RELEASE", "START PROCESS": "START PROCESS", "DATA_LOADING": "Data is loading", + "CLOSE": "CLOSE", "NOTIFICATIONS": { "NO_MESSAGE": "You have no notifications at this time.", "TITLE": "Notifications", diff --git a/lib/core/src/lib/notifications/components/notification-history.component.spec.ts b/lib/core/src/lib/notifications/components/notification-history.component.spec.ts index 5cbebe9889..a44f62d5e1 100644 --- a/lib/core/src/lib/notifications/components/notification-history.component.spec.ts +++ b/lib/core/src/lib/notifications/components/notification-history.component.spec.ts @@ -129,7 +129,7 @@ describe('Notification History Component', () => { expect(callBackSpy).toHaveBeenCalled(); done(); }); - }); + }, 10000); it('should show load more button when there are more notifications', (done) => { fixture.detectChanges(); @@ -164,7 +164,7 @@ describe('Notification History Component', () => { expect(notification).toBeDefined(); done(); }); - }); + }, 10000); it('should be able to change the maximum number of notifications displayed', (done) => { component.maxNotifications = 10; diff --git a/lib/core/src/lib/notifications/services/notification.service.spec.ts b/lib/core/src/lib/notifications/services/notification.service.spec.ts index 5e5c883231..8517a2ab63 100644 --- a/lib/core/src/lib/notifications/services/notification.service.spec.ts +++ b/lib/core/src/lib/notifications/services/notification.service.spec.ts @@ -20,7 +20,7 @@ import { OverlayModule } from '@angular/cdk/overlay'; import { Component } from '@angular/core'; import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { MatSnackBar, MatSnackBarModule, MatSnackBarConfig } from '@angular/material/snack-bar'; +import { MatSnackBar, MatSnackBarConfig, MatSnackBarModule } from '@angular/material/snack-bar'; import { NotificationService } from './notification.service'; import { TranslationService } from '../../services/translation.service'; import { setupTestBed } from '../../testing/setup-test-bed'; diff --git a/lib/core/src/lib/notifications/services/notification.service.ts b/lib/core/src/lib/notifications/services/notification.service.ts index 70dbb40a6f..7925daa44e 100644 --- a/lib/core/src/lib/notifications/services/notification.service.ts +++ b/lib/core/src/lib/notifications/services/notification.service.ts @@ -18,10 +18,11 @@ import { Injectable } from '@angular/core'; import { MatSnackBar, MatSnackBarRef, MatSnackBarConfig } from '@angular/material/snack-bar'; import { TranslationService } from '../../services/translation.service'; -import { AppConfigService, AppConfigValues } from '../../app-config/app-config.service'; import { Subject } from 'rxjs'; import { NotificationModel } from '../models/notification.model'; import { info, warning, error } from '../helpers/notification.factory'; +import { SnackbarContentComponent } from '../../snackbar-content'; +import { SnackBarData } from '../../snackbar-content/snack-bar-data'; const INFO_SNACK_CLASS = 'adf-info-snackbar'; const WARN_SNACK_CLASS = 'adf-warning-snackbar'; @@ -31,15 +32,10 @@ const ERROR_SNACK_CLASS = 'adf-error-snackbar'; providedIn: 'root' }) export class NotificationService { - - DEFAULT_DURATION_MESSAGE: number = 5000; - notifications$: Subject = new Subject(); constructor(private snackBar: MatSnackBar, - private translationService: TranslationService, - private appConfigService: AppConfigService) { - this.DEFAULT_DURATION_MESSAGE = this.appConfigService.get(AppConfigValues.NOTIFY_DURATION) || this.DEFAULT_DURATION_MESSAGE; + private translationService: TranslationService) { } /** @@ -50,7 +46,7 @@ export class NotificationService { * @param interpolateArgs The interpolation parameters to add for the translation * @returns Information/control object for the SnackBar */ - openSnackMessage(message: string, config?: number | MatSnackBarConfig, interpolateArgs?: any): MatSnackBarRef { + openSnackMessage(message: string, config?: number | MatSnackBarConfig>, interpolateArgs?: any): MatSnackBarRef { return this.dispatchNotification(message, null, config, interpolateArgs); } @@ -63,7 +59,7 @@ export class NotificationService { * @param interpolateArgs The interpolation parameters to add for the translation * @returns Information/control object for the SnackBar */ - openSnackMessageAction(message: string, action: string, config?: number | MatSnackBarConfig, interpolateArgs?: any): MatSnackBarRef { + openSnackMessageAction(message: string, action: string, config?: number | MatSnackBarConfig>, interpolateArgs?: any): MatSnackBarRef { return this.dispatchNotification(message, action, config, interpolateArgs); } @@ -73,9 +69,15 @@ export class NotificationService { * @param message Text message or translation key for the message. * @param action Action name * @param interpolateArgs The interpolation parameters to add for the translation + * @param showAction True if action should be visible, false if not. Default: true. */ - showError(message: string, action?: string, interpolateArgs?: any): MatSnackBarRef { - return this.dispatchNotification(message, action, { panelClass: ERROR_SNACK_CLASS }, interpolateArgs); + showError(message: string, action?: string, interpolateArgs?: any, showAction = true): MatSnackBarRef { + return this.dispatchNotification(message, action, { + panelClass: ERROR_SNACK_CLASS, + data: { + showAction + } + }, interpolateArgs); } /** @@ -84,9 +86,15 @@ export class NotificationService { * @param message Text message or translation key for the message. * @param action Action name * @param interpolateArgs The interpolation parameters to add for the translation + * @param showAction True if action should be visible, false if not. Default: true. */ - showInfo(message: string, action?: string, interpolateArgs?: any): MatSnackBarRef { - return this.dispatchNotification(message, action, { panelClass: INFO_SNACK_CLASS }, interpolateArgs); + showInfo(message: string, action?: string, interpolateArgs?: any, showAction = true): MatSnackBarRef { + return this.dispatchNotification(message, action, { + panelClass: INFO_SNACK_CLASS, + data: { + showAction + } + }, interpolateArgs); } /** @@ -95,9 +103,15 @@ export class NotificationService { * @param message Text message or translation key for the message. * @param action Action name * @param interpolateArgs The interpolation parameters to add for the translation + * @param showAction True if action should be visible, false if not. Default: true. */ - showWarning(message: string, action?: string, interpolateArgs?: any): MatSnackBarRef { - return this.dispatchNotification(message, action, { panelClass: WARN_SNACK_CLASS }, interpolateArgs); + showWarning(message: string, action?: string, interpolateArgs?: any, showAction = true): MatSnackBarRef { + return this.dispatchNotification(message, action, { + panelClass: WARN_SNACK_CLASS, + data: { + showAction + } + }, interpolateArgs); } /** @@ -116,20 +130,28 @@ export class NotificationService { this.notifications$.next(notification); } - private dispatchNotification(message: string, action?: string, config?: number | MatSnackBarConfig, interpolateArgs?: any): MatSnackBarRef { + private dispatchNotification(message: string, action?: string, config?: number | MatSnackBarConfig>, interpolateArgs?: any): MatSnackBarRef { const translatedMessage: string = this.translationService.instant(message, interpolateArgs); const translatedAction: string = this.translationService.instant(action, interpolateArgs); const createNotification = this.getNotificationCreator(config); this.notifications$.next(createNotification(translatedMessage)); - - return this.snackBar.open(translatedMessage, translatedAction, { - duration: (typeof config === 'number') ? config : this.DEFAULT_DURATION_MESSAGE, + return this.snackBar.openFromComponent(SnackbarContentComponent, { + ...(typeof config === 'number' && {duration: config}), panelClass: INFO_SNACK_CLASS, - ...( (typeof config === 'object') ? config : {} ) + ...( (typeof config === 'object') ? config : {} ), + data: { + actionLabel: translatedAction, + actionIcon: 'close', + actionIconAriaLabel: 'CLOSE', + message: translatedMessage, + showAction: true, + callActionOnIconClick: false, + ...( (typeof config === 'object') ? config.data : {} ) + } }); } - private getNotificationCreator(config?: number | MatSnackBarConfig) { + private getNotificationCreator(config?: number | MatSnackBarConfig>) { let panelClass: string = null; if (typeof config === 'object') { panelClass = Array.isArray(config.panelClass) ? config.panelClass[0] : config.panelClass; diff --git a/lib/core/src/lib/snackbar-content/index.ts b/lib/core/src/lib/snackbar-content/index.ts new file mode 100644 index 0000000000..a7e30cc675 --- /dev/null +++ b/lib/core/src/lib/snackbar-content/index.ts @@ -0,0 +1,18 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './public-api'; diff --git a/lib/core/src/lib/snackbar-content/public-api.ts b/lib/core/src/lib/snackbar-content/public-api.ts new file mode 100644 index 0000000000..c4866be15c --- /dev/null +++ b/lib/core/src/lib/snackbar-content/public-api.ts @@ -0,0 +1,20 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export * from './snackbar-content.component'; +export * from './snackbar-content.module'; +export * from './snack-bar-data'; diff --git a/lib/core/src/lib/snackbar-content/snack-bar-data.ts b/lib/core/src/lib/snackbar-content/snack-bar-data.ts new file mode 100644 index 0000000000..5201d9f258 --- /dev/null +++ b/lib/core/src/lib/snackbar-content/snack-bar-data.ts @@ -0,0 +1,25 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export interface SnackBarData { + actionLabel?: string; + actionIcon?: string; + actionIconAriaLabel?: string; + message: string; + showAction?: boolean; + callActionOnIconClick?: boolean; +} diff --git a/lib/core/src/lib/snackbar-content/snackbar-content.component.html b/lib/core/src/lib/snackbar-content/snackbar-content.component.html new file mode 100644 index 0000000000..d90c6596bf --- /dev/null +++ b/lib/core/src/lib/snackbar-content/snackbar-content.component.html @@ -0,0 +1,11 @@ +

{{ data.message }}

+
+ + +
diff --git a/lib/core/src/lib/snackbar-content/snackbar-content.component.scss b/lib/core/src/lib/snackbar-content/snackbar-content.component.scss new file mode 100644 index 0000000000..96ad97b137 --- /dev/null +++ b/lib/core/src/lib/snackbar-content/snackbar-content.component.scss @@ -0,0 +1,26 @@ +:host { + display: flex; + align-items: center; + justify-content: space-between; + + .adf-snackbar-message-content { + margin: 0; + } + + .adf-snackbar-message-content-action { + margin-left: 24px; + + .mat-button { + min-width: 0; + margin: -8px; + + &.adf-snackbar-message-content-action-button { + margin-right: 8px; + } + + &.adf-snackbar-message-content-action-icon { + padding: 0; + } + } + } +} diff --git a/lib/core/src/lib/snackbar-content/snackbar-content.component.spec.ts b/lib/core/src/lib/snackbar-content/snackbar-content.component.spec.ts new file mode 100644 index 0000000000..df3d5fb96e --- /dev/null +++ b/lib/core/src/lib/snackbar-content/snackbar-content.component.spec.ts @@ -0,0 +1,118 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { SnackbarContentComponent } from '@alfresco/adf-core'; +import { MatIcon, MatIconModule } from '@angular/material/icon'; +import { MAT_SNACK_BAR_DATA, MatSnackBarModule, MatSnackBarRef } from '@angular/material/snack-bar'; +import { MatButtonModule } from '@angular/material/button'; +import { By } from '@angular/platform-browser'; +import { TranslateModule } from '@ngx-translate/core'; + +describe('SnackbarContentComponent', () => { + let component: SnackbarContentComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + declarations: [SnackbarContentComponent], + imports: [ + MatIconModule, + MatSnackBarModule, + MatButtonModule, + TranslateModule.forRoot() + ], + providers: [{ + provide: MatSnackBarRef, + useValue: { + dismissWithAction() { + } + } + }, { + provide: MAT_SNACK_BAR_DATA, + useValue: {} + }] + }) + .compileComponents(); + + fixture = TestBed.createComponent(SnackbarContentComponent); + component = fixture.componentInstance; + }); + + it('should display message if message in data is set', () => { + component.data = { + message: 'Some message' + }; + fixture.detectChanges(); + expect(fixture.nativeElement.querySelector('.adf-snackbar-message-content').innerText).toBe(component.data.message); + }); + + it('should not display message if message in data is not set', () => { + fixture.detectChanges(); + expect(fixture.nativeElement.querySelector('.adf-snackbar-message-content').innerText).toBe(''); + }); + + it('should call snackBarRef.dismissWithAction() when button is clicked', () => { + component.data = { + message: '', + showAction: true, + actionLabel: 'Some action' + }; + spyOn(component.snackBarRef, 'dismissWithAction'); + fixture.detectChanges(); + fixture.nativeElement.querySelector('.adf-snackbar-message-content-action-button').click(); + expect(component.snackBarRef.dismissWithAction).toHaveBeenCalled(); + }); + + it('should display actionLabel if actionLabel in data is set', () => { + component.data = { + message: '', + showAction: true, + actionLabel: 'Some action action' + }; + fixture.detectChanges(); + expect(fixture.nativeElement.querySelector('.adf-snackbar-message-content-action-button').innerText).toBe(component.data.actionLabel); + }); + + it('should not display actionLabel if actionLabel in data is not set', () => { + component.data = { + message: '', + showAction: true + }; + fixture.detectChanges(); + expect(fixture.nativeElement.querySelector('.adf-snackbar-message-content-action-button')).toBeNull(); + }); + + it('should render icon if actionIcon in data is set', () => { + component.data = { + message: '', + showAction: true, + actionIcon: 'close' + }; + fixture.detectChanges(); + expect(fixture.debugElement.query(By.directive(MatIcon))).toBeDefined(); + }); + + it('should not render icon if actionIcon in data is not set', () => { + component.data = { + message: '', + showAction: true + }; + fixture.detectChanges(); + expect(fixture.debugElement.query(By.directive(MatIcon))).toBeNull(); + }); +}); diff --git a/lib/core/src/lib/snackbar-content/snackbar-content.component.ts b/lib/core/src/lib/snackbar-content/snackbar-content.component.ts new file mode 100644 index 0000000000..9805484368 --- /dev/null +++ b/lib/core/src/lib/snackbar-content/snackbar-content.component.ts @@ -0,0 +1,43 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { Component, Inject } from '@angular/core'; +import { MAT_SNACK_BAR_DATA, MatSnackBarRef } from '@angular/material/snack-bar'; +import { SnackBarData } from './snack-bar-data'; + +@Component({ + selector: 'adf-snackbar-content', + templateUrl: './snackbar-content.component.html', + styleUrls: ['./snackbar-content.component.scss'], + host: { + class: 'mat-simple-snackbar' + } +}) +export class SnackbarContentComponent { + constructor( + public snackBarRef: MatSnackBarRef, + @Inject(MAT_SNACK_BAR_DATA) public data: SnackBarData + ) { + if (!data) { + this.data = {message: ''}; + } + } + + onIconClicked(): void { + this.data.callActionOnIconClick ? this.snackBarRef.dismissWithAction() : this.snackBarRef.dismiss(); + } +} diff --git a/lib/core/src/lib/snackbar-content/snackbar-content.module.ts b/lib/core/src/lib/snackbar-content/snackbar-content.module.ts new file mode 100644 index 0000000000..af4f125505 --- /dev/null +++ b/lib/core/src/lib/snackbar-content/snackbar-content.module.ts @@ -0,0 +1,41 @@ +/*! + * @license + * Copyright 2019 Alfresco Software, Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { NgModule } from '@angular/core'; +import { MatIconModule } from '@angular/material/icon'; +import { CommonModule } from '@angular/common'; +import { SnackbarContentComponent } from './snackbar-content.component'; +import { MatSnackBarModule } from '@angular/material/snack-bar'; +import { MatButtonModule } from '@angular/material/button'; +import { TranslateModule } from '@ngx-translate/core'; + +@NgModule({ + imports: [ + CommonModule, + MatIconModule, + MatSnackBarModule, + MatButtonModule, + TranslateModule + ], + declarations: [ + SnackbarContentComponent + ], + exports: [ + SnackbarContentComponent + ] +}) +export class SnackbarContentModule {} diff --git a/lib/core/src/public-api.ts b/lib/core/src/public-api.ts index ba914a6e76..a2bd2078c5 100644 --- a/lib/core/src/public-api.ts +++ b/lib/core/src/public-api.ts @@ -45,6 +45,7 @@ export * from './lib/notifications/index'; export * from './lib/search-text/index'; export * from './lib/blank-page/index'; export * from './lib/rich-text-editor/index'; +export * from './lib/snackbar-content/index'; export * from './lib/utils/index'; export * from './lib/interface/index'; diff --git a/lib/testing/src/lib/protractor/core/pages/snackbar.page.ts b/lib/testing/src/lib/protractor/core/pages/snackbar.page.ts index 298f5fcc36..db384546c6 100644 --- a/lib/testing/src/lib/protractor/core/pages/snackbar.page.ts +++ b/lib/testing/src/lib/protractor/core/pages/snackbar.page.ts @@ -21,8 +21,8 @@ import { BrowserActions } from '../utils/browser-actions'; export class SnackbarPage { - notificationSnackBar = $$('simple-snack-bar span').first(); - snackBarAction = $('simple-snack-bar button span'); + notificationSnackBar = $$(`[data-automation-id='adf-snackbar-message-content']`).first(); + snackBarAction = $(`[data-automation-id='adf-snackbar-message-content-action-button']`); snackBarContainerCss = $$('.mat-snack-bar-container'); async waitForSnackBarToAppear(timeout = 5000) {