From 910804426864bba08b3b09cf1be5e75f5b742f14 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Mon, 9 Nov 2020 15:57:57 +0300 Subject: [PATCH 1/5] refactor(core-blocks): CB-479 apply reusable snackbar components to all types of snackbars --- .../SessionExpiredDialogService.ts | 2 +- .../src/Snackbars/ActionSnackbar.tsx | 56 +++----- .../src/Snackbars/ProcessSnackbar.tsx | 1 - .../core-blocks/src/Snackbars/Snackbar.tsx | 64 ++++----- .../src/Snackbars/SnackbarCommonStyles.ts | 111 --------------- .../SnackbarMarkups/SnackbarBody.tsx | 5 +- .../SnackbarMarkups/SnackbarFooter.tsx | 5 + webapp/packages/core-blocks/src/index.ts | 6 +- .../NotificationsItem/NotificationsItem.tsx | 6 +- .../ExportNotification/ExportNotification.tsx | 131 +++++++----------- .../ExportNotificationController.ts | 4 +- 11 files changed, 117 insertions(+), 274 deletions(-) delete mode 100644 webapp/packages/core-blocks/src/Snackbars/SnackbarCommonStyles.ts diff --git a/webapp/packages/core-app/src/shared/SessionExpireDialog/SessionExpiredDialogService.ts b/webapp/packages/core-app/src/shared/SessionExpireDialog/SessionExpiredDialogService.ts index 952f4e1347..ecc9b2b006 100644 --- a/webapp/packages/core-app/src/shared/SessionExpireDialog/SessionExpiredDialogService.ts +++ b/webapp/packages/core-app/src/shared/SessionExpireDialog/SessionExpiredDialogService.ts @@ -37,7 +37,7 @@ export class SessionExpiredDialogService extends Bootstrap { this.notificationService.customNotification(() => ActionSnackbar, { actionText: 'app_root_session_expired_reload', onAction: () => location.reload(), - }, { title: 'app_root_session_expired_title', persistent: true, type: ENotificationType.Info }); + }, { title: 'app_root_session_expired_title', persistent: true, type: ENotificationType.Error }); } } } diff --git a/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx b/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx index 1822d0ceba..6e0e620bb3 100644 --- a/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx +++ b/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx @@ -7,16 +7,16 @@ */ import { observer } from 'mobx-react'; -import { useEffect, useState } from 'react'; -import styled, { use } from 'reshadow'; -import { Button, IconButton } from '@cloudbeaver/core-blocks'; +import { Button } from '@cloudbeaver/core-blocks'; import { INotificationExtraProps, NotificationComponent } from '@cloudbeaver/core-events'; import { useTranslate } from '@cloudbeaver/core-localization'; -import { useStyles } from '@cloudbeaver/core-theming'; -import { NotificationMark } from './NotificationMark'; -import { SNACKBAR_COMMON_STYLES } from './SnackbarCommonStyles'; +import { SnackbarBody } from './SnackbarMarkups/SnackbarBody'; +import { SnackbarContent } from './SnackbarMarkups/SnackbarContent'; +import { SnackbarFooter } from './SnackbarMarkups/SnackbarFooter'; +import { SnackbarStatus } from './SnackbarMarkups/SnackbarStatus'; +import { SnackbarWrapper } from './SnackbarMarkups/SnackbarWrapper'; interface Props extends INotificationExtraProps { onAction: () => void; @@ -26,36 +26,22 @@ interface Props extends INotificationExtraProps { export const ActionSnackbar: NotificationComponent = observer(function ActionSnackbar({ notification, onAction, actionText, }) { - const styles = useStyles(SNACKBAR_COMMON_STYLES); - const [mounted, setMounted] = useState(false); const translate = useTranslate(); - const timeStringFromTimestamp = notification.timestamp ? new Date(notification.timestamp).toLocaleTimeString() : ''; - const translatedTitle = translate(notification.title); - useEffect(() => { - setMounted(true); - }, []); - - return styled(styles)( - - - - - {translatedTitle} - {notification.message && {translate(notification.message)}} - - - {timeStringFromTimestamp} - - - - - - {!notification.persistent && ( - notification.close(false)} /> - )} - + return ( + notification.close(false)}> + + + + + + + + ); }); diff --git a/webapp/packages/core-blocks/src/Snackbars/ProcessSnackbar.tsx b/webapp/packages/core-blocks/src/Snackbars/ProcessSnackbar.tsx index aace43a7ba..0cbf6271c4 100644 --- a/webapp/packages/core-blocks/src/Snackbars/ProcessSnackbar.tsx +++ b/webapp/packages/core-blocks/src/Snackbars/ProcessSnackbar.tsx @@ -19,7 +19,6 @@ import { SnackbarContent } from './SnackbarMarkups/SnackbarContent'; import { SnackbarFooter } from './SnackbarMarkups/SnackbarFooter'; import { SnackbarStatus } from './SnackbarMarkups/SnackbarStatus'; import { SnackbarWrapper } from './SnackbarMarkups/SnackbarWrapper'; - interface Props extends INotificationProcessExtraProps { closeDelay?: number; displayDelay?: number; diff --git a/webapp/packages/core-blocks/src/Snackbars/Snackbar.tsx b/webapp/packages/core-blocks/src/Snackbars/Snackbar.tsx index 7fe4eda77f..f71113b352 100644 --- a/webapp/packages/core-blocks/src/Snackbars/Snackbar.tsx +++ b/webapp/packages/core-blocks/src/Snackbars/Snackbar.tsx @@ -7,24 +7,25 @@ */ import { observer } from 'mobx-react'; -import { useEffect, useState } from 'react'; -import styled, { use } from 'reshadow'; -import { Button, IconButton, SNACKBAR_COMMON_STYLES } from '@cloudbeaver/core-blocks'; +import { Button } from '@cloudbeaver/core-blocks'; import { ENotificationType } from '@cloudbeaver/core-events'; import { useTranslate } from '@cloudbeaver/core-localization'; -import { useStyles } from '@cloudbeaver/core-theming'; import { useStateDelay } from '../useStateDelay'; -import { NotificationMark } from './NotificationMark'; +import { SnackbarBody } from './SnackbarMarkups/SnackbarBody'; +import { SnackbarContent } from './SnackbarMarkups/SnackbarContent'; +import { SnackbarFooter } from './SnackbarMarkups/SnackbarFooter'; +import { SnackbarStatus } from './SnackbarMarkups/SnackbarStatus'; +import { SnackbarWrapper } from './SnackbarMarkups/SnackbarWrapper'; interface SnackbarProps { - type?: ENotificationType; + type: ENotificationType; message?: string; title: string; closeDelay: number; disableShowDetails?: boolean; - time?: number; + time: number; onClose: (delayDeleting?: boolean) => void; state?: { deleteDelay: number }; onShowDetails?: () => void; @@ -41,42 +42,27 @@ export const Snackbar: React.FC = observer(function Snackbar({ state, time, }) { - const styles = useStyles(SNACKBAR_COMMON_STYLES); - const [mounted, setMounted] = useState(false); const translate = useTranslate(); - const timeStringFromTimestamp = time ? new Date(time).toLocaleTimeString() : ''; - const translatedTitle = translate(title); useStateDelay(closeDelay > 0, closeDelay, onClose); - useEffect(() => { - setMounted(true); - }, []); - - return styled(styles)( - - {type && } - - - {translatedTitle} - {message && {translate(message)}} - - - {timeStringFromTimestamp} + return ( + onClose(false)}> + + + + {onShowDetails && ( - - - + )} - - - onClose(false)} /> - + + + ); }); diff --git a/webapp/packages/core-blocks/src/Snackbars/SnackbarCommonStyles.ts b/webapp/packages/core-blocks/src/Snackbars/SnackbarCommonStyles.ts deleted file mode 100644 index 6c0b797281..0000000000 --- a/webapp/packages/core-blocks/src/Snackbars/SnackbarCommonStyles.ts +++ /dev/null @@ -1,111 +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_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; - box-sizing: border-box; - overflow: hidden; - width: 500px; - margin-bottom: 16px; - margin-left: 16px; - padding: 16px 16px; - 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-body { - display: flex; - flex-direction: column; - flex: 1 0 0; - & body-text-block { - margin-top: 8px; - padding-right: 24px; - & message { - font-size: 16px; - opacity: 0.8; - overflow: auto; - max-height: 200px; - margin-bottom: 8px; - word-break: break-word; - } - } - } - - text-block-title { - composes: theme-typography--headline6 from global; - max-width: 392px; - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - line-height: 1.55rem; - font-weight: 700; - margin: 0; - padding: 0; - margin-bottom: 8px; - } - - notification-footer { - display: flex; - align-items: flex-end; - justify-content: space-between; - } - - footer-time { - composes: theme-typography--caption from global; - opacity: 0.7; - } - - NotificationMark { - padding-right: 12px; - } - - actions { - display: flex; - & Button { - margin-left: 16px; - &:first-child { - margin-left: auto; - } - } - } - IconButton { - position: absolute; - top: 8px; - right: 8px; - height: 22px; - width: 22px; - &:hover { - opacity: 0.7; - } - } - ` -); diff --git a/webapp/packages/core-blocks/src/Snackbars/SnackbarMarkups/SnackbarBody.tsx b/webapp/packages/core-blocks/src/Snackbars/SnackbarMarkups/SnackbarBody.tsx index 3ec581ca6e..00ae0b76a2 100644 --- a/webapp/packages/core-blocks/src/Snackbars/SnackbarMarkups/SnackbarBody.tsx +++ b/webapp/packages/core-blocks/src/Snackbars/SnackbarMarkups/SnackbarBody.tsx @@ -47,12 +47,15 @@ interface ISnackbarBodyProps { className?: string; } -export const SnackbarBody: React.FC = function SnackbarBody({ title, message, className }) { +export const SnackbarBody: React.FC = function SnackbarBody({ + title, message, className, children, +}) { return styled(SNACKBAR_BODY_STYLES)( {title} {message && {message}} + {children} ); diff --git a/webapp/packages/core-blocks/src/Snackbars/SnackbarMarkups/SnackbarFooter.tsx b/webapp/packages/core-blocks/src/Snackbars/SnackbarMarkups/SnackbarFooter.tsx index 1a1deb38f5..67f1a54780 100644 --- a/webapp/packages/core-blocks/src/Snackbars/SnackbarMarkups/SnackbarFooter.tsx +++ b/webapp/packages/core-blocks/src/Snackbars/SnackbarMarkups/SnackbarFooter.tsx @@ -21,6 +21,11 @@ const SNACKBAR_FOOTER_STYLES = css` actions:empty { display: none; } + actions:not(:empty) { + & > *:not(:first-child) { + margin-left: 16px; + } + } `; interface ISnackbarFooter { diff --git a/webapp/packages/core-blocks/src/index.ts b/webapp/packages/core-blocks/src/index.ts index 438f9db4f8..cf7fd722e2 100644 --- a/webapp/packages/core-blocks/src/index.ts +++ b/webapp/packages/core-blocks/src/index.ts @@ -50,7 +50,11 @@ export * from './useClipboard'; export * from './useFocus'; export * from './BlocksLocaleService'; export * from './Snackbars/NotificationMark'; +export * from './Snackbars/SnackbarMarkups/SnackbarWrapper'; +export * from './Snackbars/SnackbarMarkups/SnackbarStatus'; +export * from './Snackbars/SnackbarMarkups/SnackbarContent'; +export * from './Snackbars/SnackbarMarkups/SnackbarBody'; +export * from './Snackbars/SnackbarMarkups/SnackbarFooter'; export * from './Snackbars/Snackbar'; export * from './Snackbars/ActionSnackbar'; export * from './Snackbars/ProcessSnackbar'; -export * from './Snackbars/SnackbarCommonStyles'; diff --git a/webapp/packages/core-notifications/src/NotificationsItem/NotificationsItem.tsx b/webapp/packages/core-notifications/src/NotificationsItem/NotificationsItem.tsx index c34e04ce77..7dfb6358d3 100644 --- a/webapp/packages/core-notifications/src/NotificationsItem/NotificationsItem.tsx +++ b/webapp/packages/core-notifications/src/NotificationsItem/NotificationsItem.tsx @@ -6,6 +6,8 @@ * you may not use this file except in compliance with the License. */ +import { observer } from 'mobx-react'; + import { Snackbar } from '@cloudbeaver/core-blocks'; import { useController } from '@cloudbeaver/core-di'; import { INotification } from '@cloudbeaver/core-events'; @@ -16,7 +18,7 @@ interface Props { notification: INotification; } -export const NotificationsItem: React.FC = function Notification({ notification }) { +export const NotificationsItem: React.FC = observer(function Notification({ notification }) { const controller = useController(NotificationItemController, notification); if (notification.customComponent) { @@ -37,4 +39,4 @@ export const NotificationsItem: React.FC = function Notification({ notifi onShowDetails={controller.handleShowDetails} /> ); -}; +}); diff --git a/webapp/packages/plugin-data-export/src/ExportNotification/ExportNotification.tsx b/webapp/packages/plugin-data-export/src/ExportNotification/ExportNotification.tsx index 8e29fe7355..9cb3e5c704 100644 --- a/webapp/packages/plugin-data-export/src/ExportNotification/ExportNotification.tsx +++ b/webapp/packages/plugin-data-export/src/ExportNotification/ExportNotification.tsx @@ -7,11 +7,10 @@ */ import { observer } from 'mobx-react'; -import { useEffect, useState } from 'react'; -import styled, { use, css } from 'reshadow'; +import styled, { css } from 'reshadow'; import { - Button, Loader, IconButton, SNACKBAR_COMMON_STYLES, NotificationMark + Button, SnackbarWrapper, SnackbarStatus, SnackbarContent, SnackbarBody, SnackbarFooter } from '@cloudbeaver/core-blocks'; import { useController } from '@cloudbeaver/core-di'; import { ENotificationType, NotificationComponentProps } from '@cloudbeaver/core-events'; @@ -22,9 +21,6 @@ import { EDeferredState } from '@cloudbeaver/core-utils'; import { ExportNotificationController } from './ExportNotificationController'; const styles = css` - Loader { - margin-right: 16px; - } source-name { composes: theme-typography--body2 from global; padding-top: 16px; @@ -35,12 +31,6 @@ const styles = css` margin: 0; } } - loader-container { - & Loader { - width: 40px; - height: 40px; - } - } `; type Props = NotificationComponentProps<{ @@ -52,84 +42,63 @@ export const ExportNotification: React.FC = observer(function ExportNotif }) { const controller = useController(ExportNotificationController, notification); const translate = useTranslate(); - const [mounted, setMounted] = useState(false); - const timeStringFromTimestamp = notification.timestamp ? new Date(notification.timestamp).toLocaleTimeString() : ''; const exportNotificationType = controller.isSuccess ? ENotificationType.Info : ENotificationType.Error; - const translatedStatus = translate(controller.status); - useEffect(() => { - setMounted(true); - }, []); - - return styled(useStyles(SNACKBAR_COMMON_STYLES, styles))( - - {!controller.isPending && } - {controller.isPending && ( - - - - )} - - - {translatedStatus} - - {controller.sourceName} - {controller.task?.context.sourceName && ( -
-                {controller.task?.context.sourceName}
-              
- )} -
-
- - {timeStringFromTimestamp} - - {controller.isSuccess && ( - <> - - - - )} - {controller.hasDetails && ( + return styled(useStyles(styles))( + notification.close(false)}> + + + + {controller.task?.context.sourceName && ( +
+              {controller.task?.context.sourceName}
+            
+ )} +
+ + {controller.isSuccess && ( + <> - )} - {controller.isPending && ( - )} -
-
-
- {!controller.isPending && ( - - )} -
+ + )} + {controller.hasDetails && ( + + )} + {controller.isPending && ( + + )} + + + + ); }); diff --git a/webapp/packages/plugin-data-export/src/ExportNotification/ExportNotificationController.ts b/webapp/packages/plugin-data-export/src/ExportNotification/ExportNotificationController.ts index 2cb55e4bfa..459d1cc51a 100644 --- a/webapp/packages/plugin-data-export/src/ExportNotification/ExportNotificationController.ts +++ b/webapp/packages/plugin-data-export/src/ExportNotification/ExportNotificationController.ts @@ -87,12 +87,12 @@ export class ExportNotificationController implements IInitializableController { delete = (): void => { this.dataExportProcessService.delete(this.notification.extraProps.source); - this.notification.close(); + this.notification.close(false); }; download = (): void => { this.dataExportProcessService.download(this.notification.extraProps.source); - this.notification.close(); + this.notification.close(false); }; cancel = (): void => { From 2cb90a73b5ba519756b823b56bce74729275d92e Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Thu, 12 Nov 2020 13:18:51 +0300 Subject: [PATCH 2/5] refactor(core-blocks): CB-479 remove message from SnackbarBody, revert closeable prop --- .../src/Snackbars/ActionSnackbar.tsx | 9 ++++---- .../src/Snackbars/ProcessSnackbar.tsx | 6 ++++-- .../core-blocks/src/Snackbars/Snackbar.tsx | 6 ++++-- .../SnackbarMarkups/SnackbarBody.tsx | 8 +++---- .../SnackbarMarkups/SnackbarWrapper.tsx | 8 +++---- .../ExportNotification/ExportNotification.tsx | 21 ++++++++++++------- 6 files changed, 34 insertions(+), 24 deletions(-) diff --git a/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx b/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx index 6e0e620bb3..8d540d9756 100644 --- a/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx +++ b/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx @@ -29,13 +29,12 @@ export const ActionSnackbar: NotificationComponent = observer(function Ac const translate = useTranslate(); return ( - notification.close(false)}> + notification.close(false)}> - + + {notification.message && translate(notification.message)} + )} - {controller.hasDetails && ( + {getExportNotificationType() === ENotificationType.Error && ( )} - {controller.isPending && ( + {getExportNotificationType() === ENotificationType.Loading && ( )} - {getExportNotificationType() === ENotificationType.Error && ( + {status === ENotificationType.Error && ( )} - {getExportNotificationType() === ENotificationType.Loading && ( + {status === ENotificationType.Loading && (