From 4dbd0185b76228131e46622f95c468f7e2194bc0 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Fri, 2 Oct 2020 12:56:23 +0300 Subject: [PATCH 1/6] feature(core-blocks): added action snackbar and persistent logic CB-217 --- .../src/Snackbars/ActionSnackbar.tsx | 59 ++++++++++ .../src/Snackbars/SnackbarCommonStyles.ts | 103 ++++++++++++++++++ .../core-blocks/src/Snackbars/index.ts | 1 + webapp/packages/core-blocks/src/index.ts | 1 + .../packages/core-events/src/INotification.ts | 25 +++-- .../core-events/src/NotificationService.ts | 42 +++++-- .../NotificationItemController.ts | 3 + .../NotificationsItem/NotificationsItem.tsx | 3 +- .../Snackbar/NotificationMark.tsx | 2 + .../NotificationsItem/Snackbar/Snackbar.tsx | 2 + .../packages/core-notifications/src/index.ts | 1 + .../src/DataExportService.ts | 4 +- 12 files changed, 226 insertions(+), 20 deletions(-) create mode 100644 webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx create mode 100644 webapp/packages/core-blocks/src/Snackbars/SnackbarCommonStyles.ts create mode 100644 webapp/packages/core-blocks/src/Snackbars/index.ts diff --git a/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx b/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx new file mode 100644 index 0000000000..d69a65282d --- /dev/null +++ b/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx @@ -0,0 +1,59 @@ +/* + * cloudbeaver - Cloud Database Manager + * Copyright (C) 2020 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { useEffect, useState } from 'react'; +import styled, { use } from 'reshadow'; + +import { Button, IconButton } from '@cloudbeaver/core-blocks'; +import { ENotificationType, NotificationComponentProps } from '@cloudbeaver/core-events'; +import { useTranslate } from '@cloudbeaver/core-localization'; +import { NotificationMark } from '@cloudbeaver/core-notifications'; +import { useStyles } from '@cloudbeaver/core-theming'; + +import { SNACKBAR_COMMON_STYLES } from './SnackbarCommonStyles'; + +type ExtraProps = { + onAction: () => void; + btnText: string; +} + +export function ActionSnackbar({ notification, onAction, btnText }: NotificationComponentProps & ExtraProps) { + const styles = useStyles(SNACKBAR_COMMON_STYLES); + const [mounted, setMounted] = useState(false); + const translate = useTranslate(); + + useEffect(() => { + setMounted(true); + }, []); + + return styled(styles)( + + + {notification.type && } + {translate(notification.title)} + {!notification.persistent && notification.close && ( + + )} + + + {onAction && btnText && ( + + + + )} + + + ); +} diff --git a/webapp/packages/core-blocks/src/Snackbars/SnackbarCommonStyles.ts b/webapp/packages/core-blocks/src/Snackbars/SnackbarCommonStyles.ts new file mode 100644 index 0000000000..a23b7e70d7 --- /dev/null +++ b/webapp/packages/core-blocks/src/Snackbars/SnackbarCommonStyles.ts @@ -0,0 +1,103 @@ +/* + * cloudbeaver - Cloud Database Manager + * Copyright (C) 2020 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { css } from 'reshadow'; + +import { composes } from '@cloudbeaver/core-theming'; + +export const SNACKBAR_COMMON_STYLES = composes( + css` + notification { + composes: theme-background-surface theme-text-on-surface from global; + } + `, + css` + notification { + composes: theme-elevation-z5 from global; + position: relative; + display: flex; + flex-direction: column; + box-sizing: border-box; + overflow: hidden; + width: 500px; + margin-bottom: 16px; + margin-left: 16px; + padding: 18px 24px; + line-height: 1.5; + opacity: 0; + border-radius: 4px; + transition: opacity 0.3s ease-in-out, transform 0.5s ease-in-out; + transform: translateX(-100%); + + &[use|mounted] { + transform: translateX(0); + opacity: 1; + } + &[use|closing] { + opacity: 0; + } + } + + notification-header { + display: flex; + } + + message { + flex: 1; + overflow: auto; + max-height: 200px; + padding-right: 24px; + word-break: break-word; + } + + NotificationMark { + display: block; + box-sizing: border-box; + overflow: hidden; + width: 24px; + height: 24px; + font-size: 24px; + font-style: normal; + line-height: 0; + text-align: center; + text-transform: none; + vertical-align: -0.125em; + text-rendering: optimizeLegibility; + + &[type='Info'] :global(svg) { + fill: #52c41a !important; + } + & :global(svg) { + fill: #ed3b26 !important; + } + } + + NotificationMark + message { + padding-left: 24px; + } + + actions { + display: flex; + + &:not(:empty) { + margin-top: 24px; + } + + & Button { + margin-left: 16px; + + &:first-child { + margin-left: auto; + } + } + } + IconButton { + color: rgba(0, 0, 0, 0.45); + } + ` +); diff --git a/webapp/packages/core-blocks/src/Snackbars/index.ts b/webapp/packages/core-blocks/src/Snackbars/index.ts new file mode 100644 index 0000000000..9be31aa53e --- /dev/null +++ b/webapp/packages/core-blocks/src/Snackbars/index.ts @@ -0,0 +1 @@ +export * from './ActionSnackbar'; diff --git a/webapp/packages/core-blocks/src/index.ts b/webapp/packages/core-blocks/src/index.ts index 296d82253c..ebf5627d0f 100644 --- a/webapp/packages/core-blocks/src/index.ts +++ b/webapp/packages/core-blocks/src/index.ts @@ -32,3 +32,4 @@ export * from './StaticImage'; export * from './TextPlaceholder'; export * from './useClipboard'; export * from './useFocus'; +export * from './Snackbars'; diff --git a/webapp/packages/core-events/src/INotification.ts b/webapp/packages/core-events/src/INotification.ts index 6ea55579a2..faf341f299 100644 --- a/webapp/packages/core-events/src/INotification.ts +++ b/webapp/packages/core-events/src/INotification.ts @@ -9,34 +9,41 @@ export enum ENotificationType { Info = 'Info', Error = 'Error', + Success = 'Success', Custom =' Custom' } -export type NotificationComponentProps = { - notification: INotification; +export type NotificationComponentProps> = { + notification: INotification; onClose: () => void; } -export type NotificationComponent = React.FunctionComponent> +export type NotificationComponent< + TSource = undefined, + TProps = Record +> = React.FunctionComponent & TProps> -export interface INotification { +export interface INotification> { readonly id: number; type: ENotificationType; title: string; message?: string; details?: string | Error; + persistent?: boolean; isSilent: boolean; - customComponent?: () => NotificationComponent; - source: T; + customComponent?: () => NotificationComponent; + source: TSource; close: () => void; showDetails: () => void; + extraProps: TProps; } -export interface INotificationOptions { +export interface INotificationOptions> { title: string; message?: string; details?: string | Error; isSilent?: boolean; persistent?: boolean; - customComponent?: () => NotificationComponent; - source?: T; + customComponent?: () => NotificationComponent; + source?: TSource; + extraProps?: TProps; } diff --git a/webapp/packages/core-events/src/NotificationService.ts b/webapp/packages/core-events/src/NotificationService.ts index cf73942a60..7d81b715ca 100644 --- a/webapp/packages/core-events/src/NotificationService.ts +++ b/webapp/packages/core-events/src/NotificationService.ts @@ -17,46 +17,72 @@ import { @injectable() export class NotificationService { - readonly notificationList = new OrderedMap>(({ id }) => id); + readonly notificationList = new OrderedMap>(({ id }) => id); private notificationNextId = 0 + private maxPersistentAllow = 1 constructor( private settings: EventsSettingsService, ) {} - notify(options: INotificationOptions, type: ENotificationType) { + notify>(options: INotificationOptions, type: ENotificationType) { const id = this.notificationNextId++; - const notification: INotification = { + const notification: INotification = { id, title: options.title, message: options.message, details: options.details, isSilent: !!options.isSilent, customComponent: options.customComponent, + extraProps: options.extraProps || {} as TProps, source: options.source!, + persistent: options.persistent, type, close: this.close.bind(this, id), showDetails: this.showDetails.bind(this, id), }; + const persistent = this.notificationList.values.filter(value => value.persistent); + const persistentQty = persistent.length; + if (persistentQty >= this.maxPersistentAllow && notification.persistent) { + return; + } this.notificationList.addValue(notification); - if (this.notificationList.values.length > this.settings.settings.getValue('notificationsPool')) { + const filteredNotificationList = this.notificationList.values.filter(notification => !notification.persistent); + + if (filteredNotificationList.length > this.settings.settings.getValue('notificationsPool')) { + if (this.notificationList.get(this.notificationList.keys[0])?.persistent) { + if (this.maxPersistentAllow > 1) { + let i = 1; + while (this.notificationList.get(this.notificationList.keys[i])?.persistent) { + i++; + } + this.notificationList.remove(this.notificationList.keys[i]); + return; + } + this.notificationList.remove(this.notificationList.keys[1]); + return; + } this.notificationList.remove(this.notificationList.keys[0]); + } } - customNotification( - component: () => NotificationComponent, + customNotification>( + type: ENotificationType, + component: () => NotificationComponent, + props?: TProps, source?: T, - options?: INotificationOptions + options?: INotificationOptions ) { this.notify({ title: '', ...options, customComponent: component, + extraProps: props || {} as TProps, source, - }, ENotificationType.Custom); + }, type); } logInfo(notification: INotificationOptions) { diff --git a/webapp/packages/core-notifications/src/NotificationsItem/NotificationItemController.ts b/webapp/packages/core-notifications/src/NotificationsItem/NotificationItemController.ts index d6f973aad8..95ce527a3e 100644 --- a/webapp/packages/core-notifications/src/NotificationsItem/NotificationItemController.ts +++ b/webapp/packages/core-notifications/src/NotificationsItem/NotificationItemController.ts @@ -32,6 +32,9 @@ export class NotificationItemController implements IInitializableController { } if (notification.type !== ENotificationType.Error) { + if (notification.persistent) { + return; + } this.closeAfter = FadeTimeout; } } diff --git a/webapp/packages/core-notifications/src/NotificationsItem/NotificationsItem.tsx b/webapp/packages/core-notifications/src/NotificationsItem/NotificationsItem.tsx index 9a06908cd0..e5d777fb99 100644 --- a/webapp/packages/core-notifications/src/NotificationsItem/NotificationsItem.tsx +++ b/webapp/packages/core-notifications/src/NotificationsItem/NotificationsItem.tsx @@ -26,13 +26,14 @@ export const NotificationsItem = observer(function Notification({ notification } if (notification.customComponent) { const Custom = notification.customComponent(); - return ; + return ; } return ( & { export function NotificationMark({ type, ...props }: IconProps) { return (
+ {/* todo change to info icon */} {type === ENotificationType.Info && } {type === ENotificationType.Error && } + {type === ENotificationType.Success && }
); } diff --git a/webapp/packages/core-notifications/src/NotificationsItem/Snackbar/Snackbar.tsx b/webapp/packages/core-notifications/src/NotificationsItem/Snackbar/Snackbar.tsx index c21477c028..da8b47e42d 100644 --- a/webapp/packages/core-notifications/src/NotificationsItem/Snackbar/Snackbar.tsx +++ b/webapp/packages/core-notifications/src/NotificationsItem/Snackbar/Snackbar.tsx @@ -24,6 +24,7 @@ type SnackbarProps = { disableShowDetails?: boolean; onClose?: () => void; onShowDetails?: () => void; + persistent?: boolean; } export function Snackbar({ @@ -33,6 +34,7 @@ export function Snackbar({ disableShowDetails, onClose, onShowDetails, + persistent, }: SnackbarProps) { const styles = useStyles(SNACKBAR_STYLES); const [mounted, setMounted] = useState(false); diff --git a/webapp/packages/core-notifications/src/index.ts b/webapp/packages/core-notifications/src/index.ts index 5afabefcd1..e5c75ca861 100644 --- a/webapp/packages/core-notifications/src/index.ts +++ b/webapp/packages/core-notifications/src/index.ts @@ -2,3 +2,4 @@ export * from './NotificationsItem/Snackbar/styles'; export * from './NotificationsItem/Snackbar/NotificationMark'; export * from './NotificationsItem/ErrorDetailsDialog/ErrorDetailsDialog'; export * from './Notifications'; +export * from './NotificationsItem/Snackbar/NotificationMark'; diff --git a/webapp/packages/plugin-data-export/src/DataExportService.ts b/webapp/packages/plugin-data-export/src/DataExportService.ts index e6a8945c71..45d9d1015b 100644 --- a/webapp/packages/plugin-data-export/src/DataExportService.ts +++ b/webapp/packages/plugin-data-export/src/DataExportService.ts @@ -7,7 +7,7 @@ */ import { injectable } from '@cloudbeaver/core-di'; -import { NotificationService } from '@cloudbeaver/core-events'; +import { ENotificationType, NotificationService } from '@cloudbeaver/core-events'; import { DataTransferParameters } from '@cloudbeaver/core-sdk'; import { DataExportProcessService } from './DataExportProcessService'; @@ -48,7 +48,7 @@ export class DataExportService { parameters ); - this.notificationService.customNotification(() => ExportNotification, taskId); + this.notificationService.customNotification(ENotificationType.Custom, () => ExportNotification, {}, taskId); return taskId; } } From b233170f746ed1d13f22d490b2a2bae278a19173 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Fri, 2 Oct 2020 14:32:10 +0300 Subject: [PATCH 2/6] feature(core-events): added session expired notify logic CB-217 --- webapp/packages/core-app/src/index.ts | 1 + .../SessionExpire/SessionExpireService.ts | 14 ++++++++++++-- .../SessionExpire/SessionExpiredDialog.tsx | 6 ++---- .../src/Snackbars/ActionSnackbar.tsx | 2 +- .../packages/core-bootstrap/src/AppBootstrap.ts | 3 +-- webapp/packages/core-bootstrap/src/manifest.ts | 4 ++-- webapp/packages/core-dialogs/src/index.ts | 2 -- .../core-events/src/NotificationService.ts | 17 ++++------------- .../core-localization/src/locales/ru.ts | 2 +- 9 files changed, 24 insertions(+), 27 deletions(-) rename webapp/packages/{core-dialogs/src => core-app/src/shared}/SessionExpire/SessionExpireService.ts (64%) rename webapp/packages/{core-dialogs/src => core-app/src/shared}/SessionExpire/SessionExpiredDialog.tsx (88%) diff --git a/webapp/packages/core-app/src/index.ts b/webapp/packages/core-app/src/index.ts index a630baaa5a..31ed3514ed 100644 --- a/webapp/packages/core-app/src/index.ts +++ b/webapp/packages/core-app/src/index.ts @@ -21,6 +21,7 @@ export * from './shared/NavigationTabs/NavigationTabsService'; export * from './shared/NavigationTabs/TabNavigationContext'; export * from './shared/ToolsPanel/LogViewTab/LogViewerMenuService'; export * from './shared/ToolsPanel/LogViewTab/LogViewerService'; +export * from './shared/SessionExpire/SessionExpireService'; export * from './NavigationTree/NavigationTreeService'; export * from './NavigationTree/NavigationTreeContextMenuService'; diff --git a/webapp/packages/core-dialogs/src/SessionExpire/SessionExpireService.ts b/webapp/packages/core-app/src/shared/SessionExpire/SessionExpireService.ts similarity index 64% rename from webapp/packages/core-dialogs/src/SessionExpire/SessionExpireService.ts rename to webapp/packages/core-app/src/shared/SessionExpire/SessionExpireService.ts index 7a9096c371..4a99feb637 100644 --- a/webapp/packages/core-dialogs/src/SessionExpire/SessionExpireService.ts +++ b/webapp/packages/core-app/src/shared/SessionExpire/SessionExpireService.ts @@ -8,12 +8,14 @@ import { Subject } from 'rxjs'; +import { ActionSnackbar } from '@cloudbeaver/core-blocks'; import { injectable } from '@cloudbeaver/core-di'; +import { CommonDialogService } from '@cloudbeaver/core-dialogs'; +import { ENotificationType, NotificationService } from '@cloudbeaver/core-events'; import { GQLError, GraphQLService, EServerErrorCode } from '@cloudbeaver/core-sdk'; -import { CommonDialogService } from '../CommonDialog/CommonDialogService'; import { SessionExpiredDialog } from './SessionExpiredDialog'; @injectable() @@ -23,6 +25,7 @@ export class SessionExpireService { onSessionExpire = new Subject(); constructor( private graphQLService: GraphQLService, + private notificationService: NotificationService, private commonDialogService: CommonDialogService ) { } @@ -40,7 +43,14 @@ export class SessionExpireService { && !this.isNotifiedAboutExpiredSession) { this.isNotifiedAboutExpiredSession = true; this.onSessionExpire.next(); - await this.commonDialogService.open(SessionExpiredDialog, null); + try { + await this.commonDialogService.open(SessionExpiredDialog, null); + } finally { + this.notificationService.customNotification(ENotificationType.Error, () => ActionSnackbar, { + btnText: 'app_root_session_expired_reload', + onAction: () => location.reload(), + }, undefined, { title: 'app_root_session_expired_title', persistent: true }); + } } throw exception; } diff --git a/webapp/packages/core-dialogs/src/SessionExpire/SessionExpiredDialog.tsx b/webapp/packages/core-app/src/shared/SessionExpire/SessionExpiredDialog.tsx similarity index 88% rename from webapp/packages/core-dialogs/src/SessionExpire/SessionExpiredDialog.tsx rename to webapp/packages/core-app/src/shared/SessionExpire/SessionExpiredDialog.tsx index dde252d8f6..2f593b3af5 100644 --- a/webapp/packages/core-dialogs/src/SessionExpire/SessionExpiredDialog.tsx +++ b/webapp/packages/core-app/src/shared/SessionExpire/SessionExpiredDialog.tsx @@ -7,16 +7,14 @@ */ import { observer } from 'mobx-react'; -import { useCallback } from 'react'; +import React, { useCallback } from 'react'; import styled, { css } from 'reshadow'; import { Button } from '@cloudbeaver/core-blocks'; +import { CommonDialogWrapper, DialogComponentProps } from '@cloudbeaver/core-dialogs'; import { useTranslate } from '@cloudbeaver/core-localization'; import { useStyles } from '@cloudbeaver/core-theming'; -import { CommonDialogWrapper } from '../CommonDialog/CommonDialog/CommonDialogWrapper'; -import { DialogComponentProps } from '../CommonDialog/CommonDialogService'; - const styles = css` dialog-content { display: flex; diff --git a/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx b/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx index d69a65282d..a4be24a14e 100644 --- a/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx +++ b/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx @@ -49,7 +49,7 @@ export function ActionSnackbar({ notification, onAction, btnText }: Notification onClick={onAction} disabled={false} > - {btnText} + {translate(btnText)} )} diff --git a/webapp/packages/core-bootstrap/src/AppBootstrap.ts b/webapp/packages/core-bootstrap/src/AppBootstrap.ts index 17546199db..c32fe07f54 100644 --- a/webapp/packages/core-bootstrap/src/AppBootstrap.ts +++ b/webapp/packages/core-bootstrap/src/AppBootstrap.ts @@ -11,10 +11,9 @@ import { NavigationTabsService, NavigationTreeContextMenuService, LogViewerMenuService, - ConnectionSchemaManagerService, + ConnectionSchemaManagerService, SessionExpireService } from '@cloudbeaver/core-app'; import { injectable } from '@cloudbeaver/core-di'; -import { SessionExpireService } from '@cloudbeaver/core-dialogs'; import { ExceptionsCatcherService } from '@cloudbeaver/core-events'; import { ThemeService } from '@cloudbeaver/core-theming'; diff --git a/webapp/packages/core-bootstrap/src/manifest.ts b/webapp/packages/core-bootstrap/src/manifest.ts index 5fc38ddd6a..c2eea708ff 100644 --- a/webapp/packages/core-bootstrap/src/manifest.ts +++ b/webapp/packages/core-bootstrap/src/manifest.ts @@ -24,7 +24,7 @@ import { ConnectionSchemaManagerService, NavigationTabsService, NavigationTreeContextMenuService, SettingsMenuService, LogViewerService, LogViewerMenuService, TopNavService, - AppScreenService, CoreSettingsService, AdministrationTopAppBarBootstrapService + AppScreenService, CoreSettingsService, AdministrationTopAppBarBootstrapService, SessionExpireService } from '@cloudbeaver/core-app'; import { AuthInfoService, AuthProviderService, AuthProvidersResource, RolesManagerService, RolesResource, UsersResource @@ -43,7 +43,7 @@ import { ConnectionsAdministrationNavService } from '@cloudbeaver/core-connections'; import { PluginManifest } from '@cloudbeaver/core-di'; -import { CommonDialogService, ContextMenuService, SessionExpireService } from '@cloudbeaver/core-dialogs'; +import { CommonDialogService, ContextMenuService } from '@cloudbeaver/core-dialogs'; import { NotificationService, ExceptionsCatcherService, EventsSettingsService } from '@cloudbeaver/core-events'; import { LocalizationService } from '@cloudbeaver/core-localization'; import { PluginManagerService } from '@cloudbeaver/core-plugin'; diff --git a/webapp/packages/core-dialogs/src/index.ts b/webapp/packages/core-dialogs/src/index.ts index 691d8ac6db..0952fa08e1 100644 --- a/webapp/packages/core-dialogs/src/index.ts +++ b/webapp/packages/core-dialogs/src/index.ts @@ -14,5 +14,3 @@ export * from './Menu/models/ComputedMenuPanelModel'; export * from './Menu/ContextMenu/ContextMenuService'; export * from './Menu/ContextMenu/IContextMenuItem'; export * from './Menu/ContextMenu/IMenuContext'; - -export * from './SessionExpire/SessionExpireService'; diff --git a/webapp/packages/core-events/src/NotificationService.ts b/webapp/packages/core-events/src/NotificationService.ts index 7d81b715ca..d3ec887415 100644 --- a/webapp/packages/core-events/src/NotificationService.ts +++ b/webapp/packages/core-events/src/NotificationService.ts @@ -52,20 +52,11 @@ export class NotificationService { const filteredNotificationList = this.notificationList.values.filter(notification => !notification.persistent); if (filteredNotificationList.length > this.settings.settings.getValue('notificationsPool')) { - if (this.notificationList.get(this.notificationList.keys[0])?.persistent) { - if (this.maxPersistentAllow > 1) { - let i = 1; - while (this.notificationList.get(this.notificationList.keys[i])?.persistent) { - i++; - } - this.notificationList.remove(this.notificationList.keys[i]); - return; - } - this.notificationList.remove(this.notificationList.keys[1]); - return; + let i = 0; + while (this.notificationList.get(this.notificationList.keys[i])?.persistent) { + i++; } - this.notificationList.remove(this.notificationList.keys[0]); - + this.notificationList.remove(this.notificationList.keys[i]); } } diff --git a/webapp/packages/core-localization/src/locales/ru.ts b/webapp/packages/core-localization/src/locales/ru.ts index 5c1e9e6f66..759a7bb1d6 100644 --- a/webapp/packages/core-localization/src/locales/ru.ts +++ b/webapp/packages/core-localization/src/locales/ru.ts @@ -18,7 +18,7 @@ export const defaultRULocale = [ ['block_properties_table_value', 'Значение'], ['block_properties_table_add', 'Добавить'], ['root_permission_denied', 'Отказано в доступе'], - ['app_root_session_expired_title', 'Сесстия истекла'], + ['app_root_session_expired_title', 'Сессия истекла'], ['app_root_session_expired_message', 'Сессия истекла. Хотите перезагрузить страницу?'], ['app_root_session_expired_reload', 'Перезагрузить'], ['app_shared_connectionMenu_connection', 'Подключение'], From ad7c373e813c42a678648fcb6fb672f1f105bace Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Sun, 4 Oct 2020 14:54:29 +0300 Subject: [PATCH 3/6] feature(core-blocks): refactored custom notification function CB-217 --- .../SessionExpire/SessionExpireService.ts | 6 ++-- .../src/Snackbars/ActionSnackbar.tsx | 10 +++--- .../core-blocks/src/Snackbars/index.ts | 1 - webapp/packages/core-blocks/src/index.ts | 2 +- .../core-events/src/EventsSettingsService.ts | 2 ++ .../packages/core-events/src/INotification.ts | 22 ++++++++----- .../core-events/src/NotificationService.ts | 31 +++++++++---------- .../NotificationItemController.ts | 5 +-- .../src/DataExportService.ts | 2 +- .../ExportNotificationController.ts | 10 +++--- 10 files changed, 47 insertions(+), 44 deletions(-) delete mode 100644 webapp/packages/core-blocks/src/Snackbars/index.ts diff --git a/webapp/packages/core-app/src/shared/SessionExpire/SessionExpireService.ts b/webapp/packages/core-app/src/shared/SessionExpire/SessionExpireService.ts index 4a99feb637..2cb382bc09 100644 --- a/webapp/packages/core-app/src/shared/SessionExpire/SessionExpireService.ts +++ b/webapp/packages/core-app/src/shared/SessionExpire/SessionExpireService.ts @@ -46,10 +46,10 @@ export class SessionExpireService { try { await this.commonDialogService.open(SessionExpiredDialog, null); } finally { - this.notificationService.customNotification(ENotificationType.Error, () => ActionSnackbar, { - btnText: 'app_root_session_expired_reload', + this.notificationService.customNotification(() => ActionSnackbar, { + actionText: 'app_root_session_expired_reload', onAction: () => location.reload(), - }, undefined, { title: 'app_root_session_expired_title', persistent: true }); + }, { title: 'app_root_session_expired_title', persistent: true, type: ENotificationType.Error }); } } throw exception; diff --git a/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx b/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx index a4be24a14e..2bcb52ffca 100644 --- a/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx +++ b/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx @@ -10,7 +10,7 @@ import { useEffect, useState } from 'react'; import styled, { use } from 'reshadow'; import { Button, IconButton } from '@cloudbeaver/core-blocks'; -import { ENotificationType, NotificationComponentProps } from '@cloudbeaver/core-events'; +import { NotificationComponentProps } from '@cloudbeaver/core-events'; import { useTranslate } from '@cloudbeaver/core-localization'; import { NotificationMark } from '@cloudbeaver/core-notifications'; import { useStyles } from '@cloudbeaver/core-theming'; @@ -19,10 +19,10 @@ import { SNACKBAR_COMMON_STYLES } from './SnackbarCommonStyles'; type ExtraProps = { onAction: () => void; - btnText: string; + actionText: string; } -export function ActionSnackbar({ notification, onAction, btnText }: NotificationComponentProps & ExtraProps) { +export function ActionSnackbar({ notification, onAction, actionText }: NotificationComponentProps & ExtraProps) { const styles = useStyles(SNACKBAR_COMMON_STYLES); const [mounted, setMounted] = useState(false); const translate = useTranslate(); @@ -41,7 +41,7 @@ export function ActionSnackbar({ notification, onAction, btnText }: Notification )} - {onAction && btnText && ( + {onAction && actionText && ( )} diff --git a/webapp/packages/core-blocks/src/Snackbars/index.ts b/webapp/packages/core-blocks/src/Snackbars/index.ts deleted file mode 100644 index 9be31aa53e..0000000000 --- a/webapp/packages/core-blocks/src/Snackbars/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ActionSnackbar'; diff --git a/webapp/packages/core-blocks/src/index.ts b/webapp/packages/core-blocks/src/index.ts index f7824d8543..293e67f71f 100644 --- a/webapp/packages/core-blocks/src/index.ts +++ b/webapp/packages/core-blocks/src/index.ts @@ -32,5 +32,5 @@ export * from './StaticImage'; export * from './TextPlaceholder'; export * from './useClipboard'; export * from './useFocus'; -export * from './Snackbars'; export * from './BlocksLocaleService'; +export * from './Snackbars/ActionSnackbar'; diff --git a/webapp/packages/core-events/src/EventsSettingsService.ts b/webapp/packages/core-events/src/EventsSettingsService.ts index c248ec4e62..c0a11b2f5b 100644 --- a/webapp/packages/core-events/src/EventsSettingsService.ts +++ b/webapp/packages/core-events/src/EventsSettingsService.ts @@ -11,6 +11,8 @@ import { PluginManagerService } from '@cloudbeaver/core-plugin'; const defaultSettings = { notificationsPool: 5, + maxPersistentAllow: 1, + }; export type EventsSettings = typeof defaultSettings diff --git a/webapp/packages/core-events/src/INotification.ts b/webapp/packages/core-events/src/INotification.ts index faf341f299..89350faca1 100644 --- a/webapp/packages/core-events/src/INotification.ts +++ b/webapp/packages/core-events/src/INotification.ts @@ -13,16 +13,23 @@ export enum ENotificationType { Custom =' Custom' } -export type NotificationComponentProps> = { - notification: INotification; +export type INotificationExtraProps = Record & { + source?: T; +} + +export type NotificationComponentProps = Record> = { + notification: INotification; onClose: () => void; } + export type NotificationComponent< TSource = undefined, - TProps = Record + TProps = Record, > = React.FunctionComponent & TProps> -export interface INotification> { +export interface INotification = Record> { readonly id: number; type: ENotificationType; title: string; @@ -31,19 +38,18 @@ export interface INotification persistent?: boolean; isSilent: boolean; customComponent?: () => NotificationComponent; - source: TSource; close: () => void; showDetails: () => void; - extraProps: TProps; + extraProps?: TProps; } -export interface INotificationOptions> { +export interface INotificationOptions = Record> { title: string; message?: string; details?: string | Error; isSilent?: boolean; persistent?: boolean; customComponent?: () => NotificationComponent; - source?: TSource; extraProps?: TProps; } diff --git a/webapp/packages/core-events/src/NotificationService.ts b/webapp/packages/core-events/src/NotificationService.ts index d3ec887415..c562990f20 100644 --- a/webapp/packages/core-events/src/NotificationService.ts +++ b/webapp/packages/core-events/src/NotificationService.ts @@ -12,23 +12,26 @@ import { OrderedMap } from '@cloudbeaver/core-utils'; import { EventsSettingsService } from './EventsSettingsService'; import { - ENotificationType, INotification, INotificationOptions, NotificationComponent + ENotificationType, INotification, INotificationExtraProps, INotificationOptions, NotificationComponent } from './INotification'; @injectable() export class NotificationService { + // todo change to common new Map() readonly notificationList = new OrderedMap>(({ id }) => id); private notificationNextId = 0 - private maxPersistentAllow = 1 constructor( private settings: EventsSettingsService, ) {} - notify>(options: INotificationOptions, type: ENotificationType) { + notify = Record>( + options: INotificationOptions, type: ENotificationType + ) { const id = this.notificationNextId++; - const notification: INotification = { + const notification: INotification = { id, title: options.title, message: options.message, @@ -36,16 +39,14 @@ export class NotificationService { isSilent: !!options.isSilent, customComponent: options.customComponent, extraProps: options.extraProps || {} as TProps, - source: options.source!, persistent: options.persistent, type, close: this.close.bind(this, id), showDetails: this.showDetails.bind(this, id), }; const persistent = this.notificationList.values.filter(value => value.persistent); - const persistentQty = persistent.length; - if (persistentQty >= this.maxPersistentAllow && notification.persistent) { - return; + if (persistent.length >= this.settings.settings.getValue('maxPersistentAllow') && notification.persistent) { + throw new Error(`You cannot create more than ${this.settings.settings.getValue('maxPersistentAllow')} persistent snackbars`); } this.notificationList.addValue(notification); @@ -60,20 +61,18 @@ export class NotificationService { } } - customNotification>( - type: ENotificationType, - component: () => NotificationComponent, - props?: TProps, - source?: T, - options?: INotificationOptions + customNotification = Record>( + component: () => NotificationComponent, + props?: TProps & INotificationExtraProps, + options?: INotificationOptions & { type?: ENotificationType; } ) { this.notify({ title: '', ...options, customComponent: component, extraProps: props || {} as TProps, - source, - }, type); + }, options?.type ?? ENotificationType.Custom); } logInfo(notification: INotificationOptions) { diff --git a/webapp/packages/core-notifications/src/NotificationsItem/NotificationItemController.ts b/webapp/packages/core-notifications/src/NotificationsItem/NotificationItemController.ts index 95ce527a3e..7fbaa17ee3 100644 --- a/webapp/packages/core-notifications/src/NotificationsItem/NotificationItemController.ts +++ b/webapp/packages/core-notifications/src/NotificationsItem/NotificationItemController.ts @@ -31,10 +31,7 @@ export class NotificationItemController implements IInitializableController { this.handleShowDetails = this.showDetails.bind(this); } - if (notification.type !== ENotificationType.Error) { - if (notification.persistent) { - return; - } + if (notification.type !== ENotificationType.Error && !notification.persistent) { this.closeAfter = FadeTimeout; } } diff --git a/webapp/packages/plugin-data-export/src/DataExportService.ts b/webapp/packages/plugin-data-export/src/DataExportService.ts index 45d9d1015b..dc01b98b48 100644 --- a/webapp/packages/plugin-data-export/src/DataExportService.ts +++ b/webapp/packages/plugin-data-export/src/DataExportService.ts @@ -48,7 +48,7 @@ export class DataExportService { parameters ); - this.notificationService.customNotification(ENotificationType.Custom, () => ExportNotification, {}, taskId); + this.notificationService.customNotification(() => ExportNotification, { source: taskId }); return taskId; } } diff --git a/webapp/packages/plugin-data-export/src/ExportNotification/ExportNotificationController.ts b/webapp/packages/plugin-data-export/src/ExportNotification/ExportNotificationController.ts index 4c1e58eb87..3f2d8ab3bf 100644 --- a/webapp/packages/plugin-data-export/src/ExportNotification/ExportNotificationController.ts +++ b/webapp/packages/plugin-data-export/src/ExportNotification/ExportNotificationController.ts @@ -35,7 +35,7 @@ export class ExportNotificationController implements IInitializableController { } get task() { - return this.dataExportProcessService.exportProcesses.get(this.notification.source); + return this.dataExportProcessService.exportProcesses.get(this.notification?.extraProps?.source); } get hasDetails() { @@ -69,7 +69,7 @@ export class ExportNotificationController implements IInitializableController { } get downloadUrl() { - return this.dataExportProcessService.downloadUrl(this.notification.source); + return this.dataExportProcessService.downloadUrl(this.notification?.extraProps?.source); } private notification!: INotification; @@ -86,17 +86,17 @@ export class ExportNotificationController implements IInitializableController { } delete = () => { - this.dataExportProcessService.delete(this.notification.source); + this.dataExportProcessService.delete(this.notification?.extraProps?.source); this.notification.close(); } download = () => { - this.dataExportProcessService.download(this.notification.source); + this.dataExportProcessService.download(this.notification?.extraProps?.source); this.notification.close(); } cancel = () => { - this.dataExportProcessService.cancel(this.notification.source); + this.dataExportProcessService.cancel(this.notification?.extraProps?.source); } showDetails= async () => { From ff60ed23b3b282883c9ea2ed100b3fb2a8c700ae Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Sun, 4 Oct 2020 19:39:23 +0300 Subject: [PATCH 4/6] feature(core-app): removed React import CB-217 --- .../core-app/src/shared/SessionExpire/SessionExpiredDialog.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/packages/core-app/src/shared/SessionExpire/SessionExpiredDialog.tsx b/webapp/packages/core-app/src/shared/SessionExpire/SessionExpiredDialog.tsx index 2f593b3af5..924ca79494 100644 --- a/webapp/packages/core-app/src/shared/SessionExpire/SessionExpiredDialog.tsx +++ b/webapp/packages/core-app/src/shared/SessionExpire/SessionExpiredDialog.tsx @@ -7,7 +7,7 @@ */ import { observer } from 'mobx-react'; -import React, { useCallback } from 'react'; +import { useCallback } from 'react'; import styled, { css } from 'reshadow'; import { Button } from '@cloudbeaver/core-blocks'; From bd66fada5177582bfa03fcb1b364b7e0aea41dc0 Mon Sep 17 00:00:00 2001 From: Wroud Date: Mon, 5 Oct 2020 02:22:33 +0300 Subject: [PATCH 5/6] fix(core-notifications): CB-217 some refactoring --- .../SessionExpire/SessionExpireService.ts | 2 +- .../src/Snackbars/ActionSnackbar.tsx | 35 +++--- webapp/packages/core-blocks/src/index.ts | 1 + .../packages/core-events/src/INotification.ts | 21 ++-- .../core-events/src/NotificationService.ts | 13 ++- .../NotificationsItem/NotificationsItem.tsx | 3 +- .../NotificationsItem/Snackbar/Snackbar.tsx | 13 +-- .../src/NotificationsItem/Snackbar/styles.ts | 103 ------------------ .../packages/core-notifications/src/index.ts | 1 - .../ExportNotification/ExportNotification.tsx | 17 +-- .../ExportNotificationController.ts | 8 +- 11 files changed, 58 insertions(+), 159 deletions(-) delete mode 100644 webapp/packages/core-notifications/src/NotificationsItem/Snackbar/styles.ts diff --git a/webapp/packages/core-app/src/shared/SessionExpire/SessionExpireService.ts b/webapp/packages/core-app/src/shared/SessionExpire/SessionExpireService.ts index 2cb382bc09..c8dbec6d5c 100644 --- a/webapp/packages/core-app/src/shared/SessionExpire/SessionExpireService.ts +++ b/webapp/packages/core-app/src/shared/SessionExpire/SessionExpireService.ts @@ -30,7 +30,7 @@ export class SessionExpireService { ) { } - subscribe() { + subscribe(): void { this.graphQLService.registerInterceptor(this.sessionExpiredInterceptor.bind(this)); } diff --git a/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx b/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx index 2bcb52ffca..0448c30564 100644 --- a/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx +++ b/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx @@ -22,7 +22,9 @@ type ExtraProps = { actionText: string; } -export function ActionSnackbar({ notification, onAction, actionText }: NotificationComponentProps & ExtraProps) { +export const ActionSnackbar: React.FC = function ActionSnackbar({ + notification, onAction, actionText, +}) { const styles = useStyles(SNACKBAR_COMMON_STYLES); const [mounted, setMounted] = useState(false); const translate = useTranslate(); @@ -32,28 +34,25 @@ export function ActionSnackbar({ notification, onAction, actionText }: Notificat }, []); return styled(styles)( - + - {notification.type && } + {translate(notification.title)} - {!notification.persistent && notification.close && ( - + {!notification.persistent && ( + )} - {onAction && actionText && ( - - - - )} + + + ); -} +}; diff --git a/webapp/packages/core-blocks/src/index.ts b/webapp/packages/core-blocks/src/index.ts index 293e67f71f..d1f022baf7 100644 --- a/webapp/packages/core-blocks/src/index.ts +++ b/webapp/packages/core-blocks/src/index.ts @@ -34,3 +34,4 @@ export * from './useClipboard'; export * from './useFocus'; export * from './BlocksLocaleService'; export * from './Snackbars/ActionSnackbar'; +export * from './Snackbars/SnackbarCommonStyles'; diff --git a/webapp/packages/core-events/src/INotification.ts b/webapp/packages/core-events/src/INotification.ts index 89350faca1..1ebf9b8712 100644 --- a/webapp/packages/core-events/src/INotification.ts +++ b/webapp/packages/core-events/src/INotification.ts @@ -17,19 +17,21 @@ export type INotificationExtraProps = Record & { source?: T; } -export type NotificationComponentProps = Record> = { +export type NotificationComponentProps< + TSource = undefined, + TProps extends INotificationExtraProps = INotificationExtraProps> = { notification: INotification; onClose: () => void; } export type NotificationComponent< TSource = undefined, - TProps = Record, + TProps extends INotificationExtraProps = INotificationExtraProps, > = React.FunctionComponent & TProps> -export interface INotification = Record> { +export interface INotification< + TSource = undefined, + TProps extends INotificationExtraProps = INotificationExtraProps> { readonly id: number; type: ENotificationType; title: string; @@ -37,19 +39,20 @@ TSource> = Record> { details?: string | Error; persistent?: boolean; isSilent: boolean; + extraProps: TProps; customComponent?: () => NotificationComponent; close: () => void; showDetails: () => void; - extraProps?: TProps; } -export interface INotificationOptions = Record> { +export interface INotificationOptions< + TSource = undefined, + TProps extends INotificationExtraProps = INotificationExtraProps> { title: string; message?: string; details?: string | Error; isSilent?: boolean; persistent?: boolean; - customComponent?: () => NotificationComponent; extraProps?: TProps; + customComponent?: () => NotificationComponent; } diff --git a/webapp/packages/core-events/src/NotificationService.ts b/webapp/packages/core-events/src/NotificationService.ts index c562990f20..19e20324ac 100644 --- a/webapp/packages/core-events/src/NotificationService.ts +++ b/webapp/packages/core-events/src/NotificationService.ts @@ -44,10 +44,14 @@ export class NotificationService { close: this.close.bind(this, id), showDetails: this.showDetails.bind(this, id), }; - const persistent = this.notificationList.values.filter(value => value.persistent); - if (persistent.length >= this.settings.settings.getValue('maxPersistentAllow') && notification.persistent) { - throw new Error(`You cannot create more than ${this.settings.settings.getValue('maxPersistentAllow')} persistent snackbars`); + + if (notification.persistent) { + const persistentNotifications = this.notificationList.values.filter(value => value.persistent); + if (persistentNotifications.length >= this.settings.settings.getValue('maxPersistentAllow')) { + throw new Error(`You cannot create more than ${this.settings.settings.getValue('maxPersistentAllow')} persistent notification`); + } } + this.notificationList.addValue(notification); const filteredNotificationList = this.notificationList.values.filter(notification => !notification.persistent); @@ -61,8 +65,7 @@ export class NotificationService { } } - customNotification = Record>( + customNotification = Record>( component: () => NotificationComponent, props?: TProps & INotificationExtraProps, options?: INotificationOptions & { type?: ENotificationType; } diff --git a/webapp/packages/core-notifications/src/NotificationsItem/NotificationsItem.tsx b/webapp/packages/core-notifications/src/NotificationsItem/NotificationsItem.tsx index e5d777fb99..7d37bf6bcc 100644 --- a/webapp/packages/core-notifications/src/NotificationsItem/NotificationsItem.tsx +++ b/webapp/packages/core-notifications/src/NotificationsItem/NotificationsItem.tsx @@ -33,11 +33,10 @@ export const NotificationsItem = observer(function Notification({ notification } ); }); diff --git a/webapp/packages/core-notifications/src/NotificationsItem/Snackbar/Snackbar.tsx b/webapp/packages/core-notifications/src/NotificationsItem/Snackbar/Snackbar.tsx index da8b47e42d..51e33dbb4f 100644 --- a/webapp/packages/core-notifications/src/NotificationsItem/Snackbar/Snackbar.tsx +++ b/webapp/packages/core-notifications/src/NotificationsItem/Snackbar/Snackbar.tsx @@ -9,13 +9,12 @@ import { useEffect, useState } from 'react'; import styled, { use } from 'reshadow'; -import { Button, IconButton } from '@cloudbeaver/core-blocks'; +import { Button, IconButton, SNACKBAR_COMMON_STYLES } from '@cloudbeaver/core-blocks'; import { ENotificationType } from '@cloudbeaver/core-events'; import { useTranslate } from '@cloudbeaver/core-localization'; import { useStyles } from '@cloudbeaver/core-theming'; import { NotificationMark } from './NotificationMark'; -import { SNACKBAR_STYLES } from './styles'; type SnackbarProps = { type?: ENotificationType; @@ -24,7 +23,6 @@ type SnackbarProps = { disableShowDetails?: boolean; onClose?: () => void; onShowDetails?: () => void; - persistent?: boolean; } export function Snackbar({ @@ -34,9 +32,8 @@ export function Snackbar({ disableShowDetails, onClose, onShowDetails, - persistent, }: SnackbarProps) { - const styles = useStyles(SNACKBAR_STYLES); + const styles = useStyles(SNACKBAR_COMMON_STYLES); const [mounted, setMounted] = useState(false); const [closing, setClosing] = useState(false); const translate = useTranslate(); @@ -62,12 +59,12 @@ export function Snackbar({ }, []); return styled(styles)( - + {type && } {translate(text)} {!closeAfter && onClose && ( - + )} @@ -76,8 +73,8 @@ export function Snackbar({ diff --git a/webapp/packages/core-notifications/src/NotificationsItem/Snackbar/styles.ts b/webapp/packages/core-notifications/src/NotificationsItem/Snackbar/styles.ts deleted file mode 100644 index 1c1b2e0f5e..0000000000 --- a/webapp/packages/core-notifications/src/NotificationsItem/Snackbar/styles.ts +++ /dev/null @@ -1,103 +0,0 @@ -/* - * cloudbeaver - Cloud Database Manager - * Copyright (C) 2020 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ - -import { css } from 'reshadow'; - -import { composes } from '@cloudbeaver/core-theming'; - -export const SNACKBAR_STYLES = composes( - css` - notification { - composes: theme-background-surface theme-text-on-surface from global; - } - `, - css` - notification { - composes: theme-elevation-z5 from global; - position: relative; - display: flex; - flex-direction: column; - box-sizing: border-box; - overflow: hidden; - width: 500px; - margin-bottom: 16px; - margin-left: 16px; - padding: 18px 24px; - line-height: 1.5; - opacity: 0; - border-radius: 4px; - transition: opacity 0.3s ease-in-out, transform 0.5s ease-in-out; - transform: translateX(-100%); - - &[use|mounted] { - transform: translateX(0); - opacity: 1; - } - &[use|closing] { - opacity: 0; - } - } - - notification-header { - display: flex; - } - - message { - flex: 1; - overflow: auto; - max-height: 200px; - padding-right: 24px; - word-break: break-word; - } - - NotificationMark { - display: block; - box-sizing: border-box; - overflow: hidden; - width: 24px; - height: 24px; - font-size: 24px; - font-style: normal; - line-height: 0; - text-align: center; - text-transform: none; - vertical-align: -0.125em; - text-rendering: optimizeLegibility; - - &[type='Info'] :global(svg) { - fill: #52c41a !important; - } - & :global(svg) { - fill: #ed3b26 !important; - } - } - - NotificationMark + message { - padding-left: 24px; - } - - actions { - display: flex; - - &:not(:empty) { - margin-top: 24px; - } - - & Button { - margin-left: 16px; - - &:first-child { - margin-left: auto; - } - } - } - IconButton { - color: rgba(0, 0, 0, 0.45); - } - ` -); diff --git a/webapp/packages/core-notifications/src/index.ts b/webapp/packages/core-notifications/src/index.ts index e5c75ca861..c1be625de8 100644 --- a/webapp/packages/core-notifications/src/index.ts +++ b/webapp/packages/core-notifications/src/index.ts @@ -1,4 +1,3 @@ -export * from './NotificationsItem/Snackbar/styles'; export * from './NotificationsItem/Snackbar/NotificationMark'; export * from './NotificationsItem/ErrorDetailsDialog/ErrorDetailsDialog'; export * from './Notifications'; diff --git a/webapp/packages/plugin-data-export/src/ExportNotification/ExportNotification.tsx b/webapp/packages/plugin-data-export/src/ExportNotification/ExportNotification.tsx index 806580dfd6..5a30c513e5 100644 --- a/webapp/packages/plugin-data-export/src/ExportNotification/ExportNotification.tsx +++ b/webapp/packages/plugin-data-export/src/ExportNotification/ExportNotification.tsx @@ -10,11 +10,12 @@ import { observer } from 'mobx-react'; import { useEffect, useState } from 'react'; import styled, { use, css } from 'reshadow'; -import { Button, Loader, IconButton } from '@cloudbeaver/core-blocks'; +import { + Button, Loader, IconButton, SNACKBAR_COMMON_STYLES +} from '@cloudbeaver/core-blocks'; import { useController } from '@cloudbeaver/core-di'; import { NotificationComponentProps } from '@cloudbeaver/core-events'; import { useTranslate } from '@cloudbeaver/core-localization'; -import { SNACKBAR_STYLES } from '@cloudbeaver/core-notifications'; import { useStyles } from '@cloudbeaver/core-theming'; import { EDeferredState } from '@cloudbeaver/core-utils'; @@ -50,13 +51,13 @@ export const ExportNotification = observer(function ExportNotification({ setMounted(true); }, []); - return styled(useStyles(SNACKBAR_STYLES, styles))( - + return styled(useStyles(SNACKBAR_COMMON_STYLES, styles))( + {translate(controller.status)} {!controller.isPending && ( - + )} @@ -78,8 +79,8 @@ export const ExportNotification = observer(function ExportNotification({ tag='a' href={controller.downloadUrl} mod={['unelevated']} - onClick={controller.download} download + onClick={controller.download} > {translate('data_transfer_notification_download')} @@ -89,8 +90,8 @@ export const ExportNotification = observer(function ExportNotification({ @@ -99,8 +100,8 @@ export const ExportNotification = observer(function ExportNotification({ diff --git a/webapp/packages/plugin-data-export/src/ExportNotification/ExportNotificationController.ts b/webapp/packages/plugin-data-export/src/ExportNotification/ExportNotificationController.ts index 3f2d8ab3bf..83320d2540 100644 --- a/webapp/packages/plugin-data-export/src/ExportNotification/ExportNotificationController.ts +++ b/webapp/packages/plugin-data-export/src/ExportNotification/ExportNotificationController.ts @@ -69,7 +69,7 @@ export class ExportNotificationController implements IInitializableController { } get downloadUrl() { - return this.dataExportProcessService.downloadUrl(this.notification?.extraProps?.source); + return this.dataExportProcessService.downloadUrl(this.notification.extraProps.source); } private notification!: INotification; @@ -86,17 +86,17 @@ export class ExportNotificationController implements IInitializableController { } delete = () => { - this.dataExportProcessService.delete(this.notification?.extraProps?.source); + this.dataExportProcessService.delete(this.notification.extraProps.source); this.notification.close(); } download = () => { - this.dataExportProcessService.download(this.notification?.extraProps?.source); + this.dataExportProcessService.download(this.notification.extraProps.source); this.notification.close(); } cancel = () => { - this.dataExportProcessService.cancel(this.notification?.extraProps?.source); + this.dataExportProcessService.cancel(this.notification.extraProps.source); } showDetails= async () => { From 45ff2ca4def850cd589c3b1a5fe323ae0d8b0d8d Mon Sep 17 00:00:00 2001 From: Wroud Date: Mon, 5 Oct 2020 02:28:47 +0300 Subject: [PATCH 6/6] fix(core-events): CB-217 throw first --- .../core-events/src/NotificationService.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/webapp/packages/core-events/src/NotificationService.ts b/webapp/packages/core-events/src/NotificationService.ts index 19e20324ac..308bce0466 100644 --- a/webapp/packages/core-events/src/NotificationService.ts +++ b/webapp/packages/core-events/src/NotificationService.ts @@ -29,6 +29,13 @@ export class NotificationService { TSource> = Record>( options: INotificationOptions, type: ENotificationType ) { + if (options.persistent) { + const persistentNotifications = this.notificationList.values.filter(value => value.persistent); + if (persistentNotifications.length >= this.settings.settings.getValue('maxPersistentAllow')) { + throw new Error(`You cannot create more than ${this.settings.settings.getValue('maxPersistentAllow')} persistent notification`); + } + } + const id = this.notificationNextId++; const notification: INotification = { @@ -45,13 +52,6 @@ export class NotificationService { showDetails: this.showDetails.bind(this, id), }; - if (notification.persistent) { - const persistentNotifications = this.notificationList.values.filter(value => value.persistent); - if (persistentNotifications.length >= this.settings.settings.getValue('maxPersistentAllow')) { - throw new Error(`You cannot create more than ${this.settings.settings.getValue('maxPersistentAllow')} persistent notification`); - } - } - this.notificationList.addValue(notification); const filteredNotificationList = this.notificationList.values.filter(notification => !notification.persistent);