From 661ebc94f573b860cc2bb8a43d4e32c7bbd42105 Mon Sep 17 00:00:00 2001 From: Wroud Date: Fri, 19 Nov 2021 16:57:29 +0300 Subject: [PATCH] refactor: CB-1452 loader context --- .../core-authentication/src/RolesResource.ts | 32 +++------ .../core-blocks/src/Loader/ILoadableState.ts | 14 ++++ .../core-blocks/src/Loader/Loader.tsx | 71 ++++++++++++++----- .../core-blocks/src/Loader/LoaderContext.ts | 15 ++++ .../core-blocks/src/Loader/LoaderState.ts | 13 ++++ .../Administration/Users/Roles/RolesPage.tsx | 4 +- .../Users/Roles/RolesTable/RolesTable.tsx | 48 ++++++++----- .../Users/Roles/RolesTable/useRolesTable.tsx | 14 ++-- .../Users/UsersAdministration.tsx | 2 +- .../Users/UsersAdministrationService.ts | 19 +---- .../Users/UsersTable/UsersTable.tsx | 51 ++++++------- 11 files changed, 173 insertions(+), 110 deletions(-) create mode 100644 webapp/packages/core-blocks/src/Loader/ILoadableState.ts create mode 100644 webapp/packages/core-blocks/src/Loader/LoaderContext.ts create mode 100644 webapp/packages/core-blocks/src/Loader/LoaderState.ts diff --git a/webapp/packages/core-authentication/src/RolesResource.ts b/webapp/packages/core-authentication/src/RolesResource.ts index 773452078c..6face14008 100644 --- a/webapp/packages/core-authentication/src/RolesResource.ts +++ b/webapp/packages/core-authentication/src/RolesResource.ts @@ -15,9 +15,9 @@ import { ResourceKeyList, ResourceKeyUtils, AdminRoleInfoFragment, - AdminConnectionGrantInfo + AdminConnectionGrantInfo, + CachedMapAllKey } from '@cloudbeaver/core-sdk'; -import { MetadataMap } from '@cloudbeaver/core-utils'; const NEW_ROLE_SYMBOL = Symbol('new-role'); @@ -26,29 +26,17 @@ type NewRole = RoleInfo & { [NEW_ROLE_SYMBOL]: boolean; timestamp: number }; @injectable() export class RolesResource extends CachedMapResource { - static keyAll = resourceKeyList(['all'], 'all'); - - private loadedKeyMetadata: MetadataMap; constructor(private graphQLService: GraphQLService) { super(); - this.loadedKeyMetadata = new MetadataMap(() => false); - } - - has(id: string): boolean { - if (this.loadedKeyMetadata.has(id)) { - return this.loadedKeyMetadata.get(id); - } - - return this.data.has(id); } async loadAll(): Promise> { - await this.load(RolesResource.keyAll); + await this.load(CachedMapAllKey); return this.data; } async refreshAll(): Promise> { - await this.refresh(RolesResource.keyAll); + await this.refresh(CachedMapAllKey); return this.data; } @@ -96,11 +84,13 @@ export class RolesResource extends CachedMapResource { } protected async loader(key: ResourceKey): Promise> { - const all = ResourceKeyUtils.hasMark(key, RolesResource.keyAll.mark); + const all = ResourceKeyUtils.includes(key, CachedMapAllKey); + + await ResourceKeyUtils.forEachAsync(all ? CachedMapAllKey : key, async key => { + const roleId = all ? undefined : key; - await ResourceKeyUtils.forEachAsync(all ? RolesResource.keyAll : key, async key => { const { roles } = await this.graphQLService.sdk.getRolesList({ - roleId: !all ? key : undefined, + roleId, }); if (all) { @@ -108,10 +98,6 @@ export class RolesResource extends CachedMapResource { } this.updateRoles(...roles); - - if (all) { - this.loadedKeyMetadata.set(RolesResource.keyAll.list[0], true); - } }); return this.data; diff --git a/webapp/packages/core-blocks/src/Loader/ILoadableState.ts b/webapp/packages/core-blocks/src/Loader/ILoadableState.ts new file mode 100644 index 0000000000..2312085d9d --- /dev/null +++ b/webapp/packages/core-blocks/src/Loader/ILoadableState.ts @@ -0,0 +1,14 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +export interface ILoadableState { + isLoading: () => boolean; + isLoaded: () => boolean; + exception?: Error[] | Error | null; + reload?: () => void; +} diff --git a/webapp/packages/core-blocks/src/Loader/Loader.tsx b/webapp/packages/core-blocks/src/Loader/Loader.tsx index 0d90f42166..f14ba4aced 100644 --- a/webapp/packages/core-blocks/src/Loader/Loader.tsx +++ b/webapp/packages/core-blocks/src/Loader/Loader.tsx @@ -6,16 +6,19 @@ * you may not use this file except in compliance with the License. */ +import { observable } from 'mobx'; import { observer } from 'mobx-react-lite'; -import { useState, useEffect } from 'react'; +import { useState, useEffect, useContext } from 'react'; import styled, { use } from 'reshadow'; import { Translate } from '@cloudbeaver/core-localization'; import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming'; +import { uuid } from '@cloudbeaver/core-utils'; import { Button } from '../Button'; import { ExceptionMessage } from '../ExceptionMessage'; import { StaticImage } from '../StaticImage'; +import { ILoaderContext, LoaderContext } from './LoaderContext'; import { loaderStyles, overlayStyles } from './loaderStyles'; export interface ILoadableState { @@ -77,6 +80,10 @@ export const Loader = observer(function Loader({ children, onCancel, }) { + const context = useContext(LoaderContext); + const [loaderId] = useState(() => uuid()); + const [contextState] = useState(() => ({ state: observable(new Set()) })); + let exception: Error | null = null; let reload: (() => void) | undefined; @@ -116,6 +123,7 @@ export const Loader = observer(function Loader({ style = useStyles(loaderStyles, style, overlay && overlayStyles); const [isVisible, setVisible] = useState(loading); const spinnerURL = (secondary || overlay) ? spinnerType.secondary : spinnerType.primary; + const refLoaderDisplayed = { state: false }; useEffect(() => { if (!loading) { @@ -126,21 +134,40 @@ export const Loader = observer(function Loader({ const id = setTimeout(() => { setVisible(loading); }, 500); - return () => clearTimeout(id); + + return () => { + clearTimeout(id); + }; }, [loading]); + useEffect(() => { + if (context) { + if (refLoaderDisplayed.state) { + context.state.add(loaderId); + } else { + context.state.delete(loaderId); + } + } + }); + + useEffect(() => () => { + if (context) { + context.state.delete(loaderId); + } + }, []); + if (exception && !loading) { return styled(style)( ); } - if (children && (!loader || !loading)) { + if (children && (!loader || !loading) && !overlay) { if (loaded) { if (typeof children === 'function') { - return <>{children()}; + return {children()}; } else { - return <>{children}; + return {children}; } } @@ -149,25 +176,35 @@ export const Loader = observer(function Loader({ } } - if ((!isVisible && overlay) || !loading) { + if ((!isVisible && overlay) || !loading || contextState.state.size > 0) { + if (overlay) { + return {children}; + } + return null; } + refLoaderDisplayed.state = true; + return styled(style)( - - - {!hideMessage && } - {onCancel && ( - - - - )} - + > + + + + )} + + ); }); diff --git a/webapp/packages/core-blocks/src/Loader/LoaderContext.ts b/webapp/packages/core-blocks/src/Loader/LoaderContext.ts new file mode 100644 index 0000000000..785f0333cb --- /dev/null +++ b/webapp/packages/core-blocks/src/Loader/LoaderContext.ts @@ -0,0 +1,15 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { createContext } from 'react'; + +export interface ILoaderContext { + state: Set; +} + +export const LoaderContext = createContext(null); diff --git a/webapp/packages/core-blocks/src/Loader/LoaderState.ts b/webapp/packages/core-blocks/src/Loader/LoaderState.ts new file mode 100644 index 0000000000..114dd07018 --- /dev/null +++ b/webapp/packages/core-blocks/src/Loader/LoaderState.ts @@ -0,0 +1,13 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 type { ILoadableState } from './ILoadableState'; + +export type LoaderState = ILoadableState | { + loading: boolean; +}; diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Roles/RolesPage.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Roles/RolesPage.tsx index baa8e680b5..b4240cf669 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Roles/RolesPage.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Roles/RolesPage.tsx @@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite'; import styled, { css, use } from 'reshadow'; import { ADMINISTRATION_TOOLS_PANEL_STYLES, IAdministrationItemSubItem } from '@cloudbeaver/core-administration'; -import { BASE_CONTAINERS_STYLES, ToolsAction, Loader, ToolsPanel } from '@cloudbeaver/core-blocks'; +import { BASE_CONTAINERS_STYLES, ToolsAction, ToolsPanel } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { useTranslate } from '@cloudbeaver/core-localization'; import { composes, useStyles } from '@cloudbeaver/core-theming'; @@ -104,10 +104,10 @@ export const RolesPage = observer(function RolesPage({ - diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Roles/RolesTable/RolesTable.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Roles/RolesTable/RolesTable.tsx index c5f6f713a6..acba8dfbbe 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Roles/RolesTable/RolesTable.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Roles/RolesTable/RolesTable.tsx @@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite'; import styled, { css, use } from 'reshadow'; import type { RoleInfo } from '@cloudbeaver/core-authentication'; -import { Table, TableHeader, TableColumnHeader, TableBody, TableSelect } from '@cloudbeaver/core-blocks'; +import { Table, TableHeader, TableColumnHeader, TableBody, TableSelect, Loader, ILoadableState } from '@cloudbeaver/core-blocks'; import { useTranslate } from '@cloudbeaver/core-localization'; import { useStyles } from '@cloudbeaver/core-theming'; @@ -19,36 +19,46 @@ import { Role } from './Role'; const styles = css` Table { width: 100%; + min-height: 140px; /* loader overlay size */ + } +`; + +const loaderStyle = css` + ExceptionMessage { + padding: 24px; } `; interface Props { roles: RoleInfo[]; + state: ILoadableState; selectedItems: Map; expandedItems: Map; } -export const RolesTable = observer(function RolesTable({ roles, selectedItems, expandedItems }) { +export const RolesTable = observer(function RolesTable({ roles, state, selectedItems, expandedItems }) { const translate = useTranslate(); const keys = roles.map(role => role.roleId); return styled(useStyles(styles))( - - - - - - - {translate('administration_roles_role_id')} - {translate('administration_roles_role_name')} - {translate('administration_roles_role_description')} - - - - {roles.map(role => ( - - ))} - -
+ + + + + + + + {translate('administration_roles_role_id')} + {translate('administration_roles_role_name')} + {translate('administration_roles_role_description')} + + + + {roles.map(role => ( + + ))} + +
+
); }); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Roles/RolesTable/useRolesTable.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Roles/RolesTable/useRolesTable.tsx index 20ba6d0223..6084b376fb 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Roles/RolesTable/useRolesTable.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Roles/RolesTable/useRolesTable.tsx @@ -9,17 +9,18 @@ import { computed, observable } from 'mobx'; import { compareRoles, RoleInfo, RolesResource } from '@cloudbeaver/core-authentication'; -import { TableState, useObservableRef } from '@cloudbeaver/core-blocks'; +import { ILoadableState, TableState, useMapResource, useObservableRef } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { CommonDialogService, ConfirmationDialogDelete, DialogueStateResult } from '@cloudbeaver/core-dialogs'; import { NotificationService } from '@cloudbeaver/core-events'; import { useTranslate } from '@cloudbeaver/core-localization'; -import { resourceKeyList } from '@cloudbeaver/core-sdk'; +import { CachedMapAllKey, resourceKeyList } from '@cloudbeaver/core-sdk'; interface State { tableState: TableState; processing: boolean; roles: RoleInfo[]; + state: ILoadableState; update: () => Promise; delete: () => Promise; } @@ -27,15 +28,16 @@ interface State { export function useRolesTable(): Readonly { const notificationService = useService(NotificationService); const dialogService = useService(CommonDialogService); - const resource = useService(RolesResource); + const resource = useMapResource(useRolesTable, RolesResource, CachedMapAllKey); const translate = useTranslate(); return useObservableRef(() => ({ tableState: new TableState(), processing: false, + state: resource, get roles() { - return resource.values.slice().sort(compareRoles); + return resource.resource.values.slice().sort(compareRoles); }, async update() { if (this.processing) { @@ -44,7 +46,7 @@ export function useRolesTable(): Readonly { try { this.processing = true; - await resource.refreshAll(); + await resource.resource.refreshAll(); notificationService.logSuccess({ title: 'administration_roles_role_list_update_success' }); } catch (exception) { notificationService.logException(exception, 'administration_roles_role_list_update_fail'); @@ -77,7 +79,7 @@ export function useRolesTable(): Readonly { try { this.processing = true; - await resource.deleteRole(resourceKeyList(deletionList)); + await resource.resource.deleteRole(resourceKeyList(deletionList)); this.tableState.unselect(); this.tableState.unexpand(deletionList); diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministration.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministration.tsx index 001d9fe291..eee824d525 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministration.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministration.tsx @@ -55,7 +55,7 @@ export const UsersAdministration: AdministrationItemContentComponent = observer( } return styled(style)( - + {translate('authentication_administration_item_users')} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministrationService.ts b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministrationService.ts index 9d49bcaeb7..cc8a1bdc17 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministrationService.ts +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersAdministrationService.ts @@ -7,10 +7,9 @@ */ import { AdministrationItemService } from '@cloudbeaver/core-administration'; -import { AdminUser, RolesResource, UsersResource } from '@cloudbeaver/core-authentication'; +import { AdminUser, RolesResource } from '@cloudbeaver/core-authentication'; import { PlaceholderContainer } from '@cloudbeaver/core-blocks'; import { injectable, Bootstrap } from '@cloudbeaver/core-di'; -import { NotificationService } from '@cloudbeaver/core-events'; import { CreateRoleService } from './Roles/CreateRoleService'; import { UsersAdministration } from './UsersAdministration'; @@ -29,8 +28,6 @@ export class UsersAdministrationService extends Bootstrap { constructor( private administrationItemService: AdministrationItemService, - private notificationService: NotificationService, - private usersResource: UsersResource, private createUserService: CreateUserService, private readonly rolesResource: RolesResource, private readonly createRoleService: CreateRoleService, @@ -62,7 +59,6 @@ export class UsersAdministrationService extends Bootstrap { ], getContentComponent: () => UsersAdministration, getDrawerComponent: () => UsersDrawerItem, - onActivate: this.loadUsers.bind(this), }); this.userDetailsInfoPlaceholder.add(Origin, 0); } @@ -75,22 +71,9 @@ export class UsersAdministrationService extends Bootstrap { } } - private async loadUsers() { - try { - await this.usersResource.loadAll(); - } catch (exception) { - this.notificationService.logException(exception, 'Error occurred while loading users'); - } - } - private async loadRoles(param: string | null) { if (param === 'create') { this.createRoleService.fillData(); } - try { - await this.rolesResource.loadAll(); - } catch (exception) { - this.notificationService.logException(exception, 'Error occurred while loading roles'); - } } } diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UsersTable.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UsersTable.tsx index 9021f1b6e0..ea9563f936 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UsersTable.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UsersTable.tsx @@ -11,9 +11,7 @@ import styled, { css, use } from 'reshadow'; import { ADMINISTRATION_TOOLS_PANEL_STYLES, IAdministrationItemSubItem } from '@cloudbeaver/core-administration'; import { AdminUser, UsersResource } from '@cloudbeaver/core-authentication'; -import { - Table, TableHeader, TableColumnHeader, TableBody, TableSelect, getComputed, useMapResource, ToolsAction, ToolsPanel -} from '@cloudbeaver/core-blocks'; +import { Table, TableHeader, TableColumnHeader, TableBody, TableSelect, getComputed, useMapResource, ToolsAction, ToolsPanel, Loader } from '@cloudbeaver/core-blocks'; import { useController, useService } from '@cloudbeaver/core-di'; import { useTranslate } from '@cloudbeaver/core-localization'; import { CachedMapAllKey } from '@cloudbeaver/core-sdk'; @@ -52,15 +50,18 @@ const layoutStyles = composes( ` ); +const loaderStyle = css` + ExceptionMessage { + padding: 24px; + } +`; + const styles = css` Table { width: 100%; + min-height: 140px; /* loader overlay size */ } - Loader { - height: 100%; - } - ToolsPanel { border-bottom: none; } @@ -137,27 +138,29 @@ export const UsersTable = observer(function UsersTable({ sub, param }) { )} - +
- - {isLocalProviderAvailable && ( - - - - )} - - {translate('authentication_user_name')} - {translate('authentication_user_role')} - - - - {users.map(user => )} - -
+ > + + {isLocalProviderAvailable && ( + + + + )} + + {translate('authentication_user_name')} + {translate('authentication_user_role')} + + + + {users.map(user => )} + + +