diff --git a/webapp/packages/core/assets/locales/en.js b/webapp/packages/core/assets/locales/en.js index ba9bf6716e..7544839116 100644 --- a/webapp/packages/core/assets/locales/en.js +++ b/webapp/packages/core/assets/locales/en.js @@ -32,10 +32,15 @@ module.exports = { data_transfer_dialog_export: 'Export', data_transfer_dialog_exporting_object: 'Exporting:', data_transfer_dialog_configuration_title: 'Export configuration', + data_transfer_dialog_preparation: 'We prepare your file for export. Please wait...', + data_transfer_notification_preparation: 'We prepare your file for download. Please wait...', + data_transfer_notification_ready: 'File ready to download', + data_transfer_notification_error: 'Preparation failed or canceled', ui_stepper_back: 'Back', ui_processing_loading: 'Loading...', ui_processing_cancel: 'Cancel', + ui_processing_canceling: 'Canceling...', ui_processing_retry: 'Retry', ui_error_message: 'Error:', ui_errors_details: 'Details', diff --git a/webapp/packages/core/assets/locales/ru.js b/webapp/packages/core/assets/locales/ru.js index f293429a94..5c32d5cdbc 100644 --- a/webapp/packages/core/assets/locales/ru.js +++ b/webapp/packages/core/assets/locales/ru.js @@ -31,10 +31,15 @@ module.exports = { data_transfer_dialog_export: 'Экспортировать', data_transfer_dialog_exporting_object: 'Экспортировать:', data_transfer_dialog_configuration_title: 'Настройка экспорта', + data_transfer_dialog_preparation: 'Подготавливаем файл к экспорту. Пожалуйста подождите...', + data_transfer_notification_preparation: 'Подготавливаем файл к загрузке. Пожалуйста подождите...', + data_transfer_notification_ready: 'Файл готов к загрузке', + data_transfer_notification_error: 'Подготовка файла к загрузке не удалась или была отменена', ui_stepper_back: 'Назад', ui_processing_loading: 'Загрузка...', ui_processing_cancel: 'Отменить', + ui_processing_canceling: 'Отмена...', ui_processing_retry: 'Повторить', ui_error_message: 'Ошибка:', ui_errors_details: 'Информация', diff --git a/webapp/packages/core/src/app/Notifications/NotificationsItem/NotificationItemController.ts b/webapp/packages/core/src/app/Notifications/NotificationsItem/NotificationItemController.ts index 919b488d79..7cb58aa34b 100644 --- a/webapp/packages/core/src/app/Notifications/NotificationsItem/NotificationItemController.ts +++ b/webapp/packages/core/src/app/Notifications/NotificationsItem/NotificationItemController.ts @@ -20,18 +20,18 @@ const FadeTimeout = 5000; export class NotificationItemController implements IInitializableController { closeAfter = 0; @observable isDetailsDialogOpen = false; - private notification!: INotification; + private notification!: INotification; constructor(private commonDialogService: CommonDialogService) {} - init(notification: INotification) { + init(notification: INotification) { this.notification = notification; if (this.notification.details) { this.handleShowDetails = this.showDetails.bind(this); } - if (notification.type !== ENotificationType.Error) { + if (notification.type !== ENotificationType.Error && !notification.persistent) { this.closeAfter = FadeTimeout; } } diff --git a/webapp/packages/core/src/app/Notifications/NotificationsItem/NotificationsItem.tsx b/webapp/packages/core/src/app/Notifications/NotificationsItem/NotificationsItem.tsx index 7c7750f108..85169e5aa0 100644 --- a/webapp/packages/core/src/app/Notifications/NotificationsItem/NotificationsItem.tsx +++ b/webapp/packages/core/src/app/Notifications/NotificationsItem/NotificationsItem.tsx @@ -15,7 +15,7 @@ import { NotificationItemController } from './NotificationItemController'; import { Snackbar } from './Snackbar/Snackbar'; type NotificationProps = { - notification: INotification; + notification: INotification; } export const NotificationsItem = observer(function Notification({ notification }: NotificationProps) { @@ -24,6 +24,11 @@ export const NotificationsItem = observer(function Notification({ notification } } const controller = useController(NotificationItemController, notification); + if (notification.customComponent) { + const Custom = notification.customComponent(); + return ; + } + return ( void; onShowDetails?: () => void; } -export function Snackbar(props: SnackbarProps) { - const styles = useStyles(snackbarStyles); +export function Snackbar({ + type, + text, + closeAfter, + disableShowDetails, + onClose, + onShowDetails, +}: SnackbarProps) { + const styles = useStyles(SNACKBAR_STYLES); const [mounted, setMounted] = useState(false); const [closing, setClosing] = useState(false); - const { - closeAfter, onClose, onShowDetails, type, text, - } = props; + const translate = useTranslate(); useEffect(() => { setMounted(true); @@ -72,9 +78,9 @@ export function Snackbar(props: SnackbarProps) { type="button" mod={['outlined']} onClick={onShowDetails} - disabled={props.disableShowDetails} + disabled={disableShowDetails} > - Details + {translate('ui_errors_details')} )} diff --git a/webapp/packages/core/src/app/Notifications/NotificationsItem/Snackbar/styles.ts b/webapp/packages/core/src/app/Notifications/NotificationsItem/Snackbar/styles.ts index 1ddf27de36..45f786dc87 100644 --- a/webapp/packages/core/src/app/Notifications/NotificationsItem/Snackbar/styles.ts +++ b/webapp/packages/core/src/app/Notifications/NotificationsItem/Snackbar/styles.ts @@ -10,7 +10,7 @@ import { css } from 'reshadow'; import { composes } from '@dbeaver/core/theming'; -export const snackbarStyles = composes( +export const SNACKBAR_STYLES = composes( css` notification { composes: theme-background-surface theme-text-on-surface from global; diff --git a/webapp/packages/core/src/app/index.ts b/webapp/packages/core/src/app/index.ts index f468e13079..12b7c79027 100644 --- a/webapp/packages/core/src/app/index.ts +++ b/webapp/packages/core/src/app/index.ts @@ -18,6 +18,8 @@ export * from './NavigationTree/NavigationTreeService'; export * from './NavigationTree/NavigationTreeContextMenuService'; export * from './shared/ToolsPanel/LogViewTab/LogViewerMenuService'; export * from './shared/ToolsPanel/LogViewTab/LogViewerService'; +export * from './Notifications/NotificationsItem/Snackbar/styles'; +export * from './Notifications/NotificationsItem/Snackbar/NotificationMark'; // Models export * from './shared/NavigationTabs/ITab'; diff --git a/webapp/packages/core/src/app/shared/ConnectionsManager/ConnectionsManagerService.ts b/webapp/packages/core/src/app/shared/ConnectionsManager/ConnectionsManagerService.ts index 2340cc4b53..7884822bf1 100644 --- a/webapp/packages/core/src/app/shared/ConnectionsManager/ConnectionsManagerService.ts +++ b/webapp/packages/core/src/app/shared/ConnectionsManager/ConnectionsManagerService.ts @@ -38,12 +38,25 @@ export type DBSource = Pick export type ObjectContainer = Pick +type DBDriversMetadata = { + loaded: boolean; +} + @injectable() export class ConnectionsManagerService { @observable private connectionsMap: Map = new Map(); - readonly dbDrivers = new CachedResource(new Map(), this.refreshDriversAsync.bind(this)); - readonly connectionObjectContainers = new CachedResource(new Map(), this.refreshObjectContainersAsync.bind(this)); + readonly dbDrivers = new CachedResource( + new Map(), + this.refreshDriversAsync.bind(this), + (_, { loaded }) => loaded, + { loaded: false } + ); + readonly connectionObjectContainers = new CachedResource( + new Map(), + this.refreshObjectContainersAsync.bind(this), + this.isObjectContainersLoaded.bind(this) + ); @computed get connections(): Connection[] { return Array.from(this.connectionsMap.values()); @@ -129,24 +142,36 @@ export class ConnectionsManagerService { await this.nodesManagerService.updateRootChildren(); } + private isObjectContainersLoaded( + data: Map, + metadata: {}, + connectionId: string, + catalogId?: string, + ) { + return data.has(connectionId); + } + private async refreshObjectContainersAsync( data: Map, - refresh: boolean, + metadata: {}, + update: boolean, connectionId: string, catalogId?: string, ): Promise> { - if (refresh || !data.has(connectionId)) { - const { navGetStructContainers } = await this.graphQLService.gql.navGetStructContainers({ - connectionId, - catalogId, - }); - data.set(connectionId, [...navGetStructContainers.schemaList, ...navGetStructContainers.catalogList]); - } + const { navGetStructContainers } = await this.graphQLService.gql.navGetStructContainers({ + connectionId, + catalogId, + }); + data.set(connectionId, [...navGetStructContainers.schemaList, ...navGetStructContainers.catalogList]); return data; } - private async refreshDriversAsync(data: Map): Promise> { + private async refreshDriversAsync( + data: Map, + metadata: DBDriversMetadata, + update: boolean + ): Promise> { const { driverList } = await this.graphQLService.gql.driverList(); data.clear(); @@ -154,7 +179,7 @@ export class ConnectionsManagerService { for (const driver of driverList) { data.set(driver.id, driver); } - + metadata.loaded = true; return data; } diff --git a/webapp/packages/core/src/blocks/Button.tsx b/webapp/packages/core/src/blocks/Button.tsx index 797e19ea04..6935875401 100644 --- a/webapp/packages/core/src/blocks/Button.tsx +++ b/webapp/packages/core/src/blocks/Button.tsx @@ -12,52 +12,69 @@ import { useStyles, composes } from '@dbeaver/core/theming'; const buttonStyles = composes( css` - button { - composes: theme-button from global; - } - ripple { - composes: theme-button_ripple from global; - } + Button { + composes: theme-button from global; + } + ripple { + composes: theme-button_ripple from global; + } + `, + css` + Button { + display: flex; + } ` ); const buttonMod = { raised: composes( css` - button { + Button { composes: theme-button_raised from global; } ` ), unelevated: composes( css` - button { + Button { composes: theme-button_unelevated from global; } ` ), outlined: composes( css` - button { + Button { composes: theme-button_outlined from global; } ` ), secondary: composes( css` - button { + Button { composes: theme-button_secondary from global; } ` ), }; -type ButtonProps = React.ButtonHTMLAttributes & { +type ButtonProps = ( + React.ButtonHTMLAttributes + & React.LinkHTMLAttributes + ) & { mod?: (keyof typeof buttonMod)[]; + tag?: 'button' | 'a'; + href?: string; + download?: boolean; } -export function Button({ children, mod, ...rest }: ButtonProps) { +export function Button({ + children, + mod, + tag = 'button', + ...rest +}: ButtonProps) { + const Button = tag; return styled(useStyles(buttonStyles, ...(mod || []).map(mod => buttonMod[mod])))( - + ); } diff --git a/webapp/packages/core/src/blocks/Loader/Loader.tsx b/webapp/packages/core/src/blocks/Loader/Loader.tsx index 086b0ab4a0..e03af3f68f 100644 --- a/webapp/packages/core/src/blocks/Loader/Loader.tsx +++ b/webapp/packages/core/src/blocks/Loader/Loader.tsx @@ -20,6 +20,8 @@ export type LoaderProps = { loading?: boolean; /** disable cancel button */ cancelDisabled?: boolean; + /** hides message */ + hideMessage?: boolean; /** render loader as overlay with white spinner */ overlay?: boolean; /** loader with white spinner */ @@ -38,6 +40,7 @@ const spinnerType = { export function Loader({ cancelDisabled, overlay, + hideMessage, secondary, small, className, @@ -66,7 +69,7 @@ export function Loader({ return styled(useStyles(loaderStyles, ...(overlay ? [overlayStyles] : [])))( - + {!hideMessage && } {onCancel && ( + + + )} + {task.isFinished && !isSuccess && ( + + )} + {!task.isFinished && ( + + )} + + + + ); +});