diff --git a/webapp/packages/core-app/src/Body.m.css b/webapp/packages/core-app/src/Body.m.css index 60f9ce527a..82a6e1457b 100644 --- a/webapp/packages/core-app/src/Body.m.css +++ b/webapp/packages/core-app/src/Body.m.css @@ -1,8 +1,8 @@ .bodyContent { - composes: theme-background-surface theme-text-on-surface theme-typography from global; - height: 100vh; - display: flex; - padding: 0 !important; - flex-direction: column; - overflow: hidden; + composes: theme-background-surface theme-text-on-surface theme-typography from global; + height: var(--app-height); + display: flex; + padding: 0 !important; + flex-direction: column; + overflow: hidden; } diff --git a/webapp/packages/core-app/src/Body.tsx b/webapp/packages/core-app/src/Body.tsx index bf74491a03..1729fe301c 100644 --- a/webapp/packages/core-app/src/Body.tsx +++ b/webapp/packages/core-app/src/Body.tsx @@ -20,6 +20,7 @@ import { DNDProvider } from '@cloudbeaver/core-ui'; import { useAppVersion } from '@cloudbeaver/core-version'; import style from './Body.m.css'; +import { useAppHeight } from './useAppHeight'; export const Body = observer(function Body() { // const serverConfigLoader = useResource(Body, ServerConfigResource, undefined); @@ -42,6 +43,8 @@ export const Body = observer(function Body() { document.documentElement.dataset.backendVersion = backendVersion; }); + useAppHeight(); + return ( diff --git a/webapp/packages/core-app/src/useAppHeight.ts b/webapp/packages/core-app/src/useAppHeight.ts new file mode 100644 index 0000000000..21f5241fd9 --- /dev/null +++ b/webapp/packages/core-app/src/useAppHeight.ts @@ -0,0 +1,27 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 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 { useLayoutEffect } from 'react'; + +import { throttle } from '@cloudbeaver/core-utils'; + +// we need it because 100vh cuts the bottom of the page on mobile devices +const handleBodyHeight = throttle(() => { + const doc = document.documentElement; + doc.style.setProperty('--app-height', `${window.innerHeight}px`); +}, 50); + +export function useAppHeight() { + useLayoutEffect(() => { + handleBodyHeight(); + window.addEventListener('resize', handleBodyHeight); + + return () => { + window.removeEventListener('resize', handleBodyHeight); + }; + }); +} diff --git a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogWrapper.m.css b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogWrapper.m.css index de8c1dcc8b..5b75c7d969 100644 --- a/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogWrapper.m.css +++ b/webapp/packages/core-blocks/src/CommonDialog/CommonDialog/CommonDialogWrapper.m.css @@ -21,7 +21,7 @@ &.small { min-width: 404px; min-height: 262px; - max-height: max(100vh - 48px, 262px); + max-height: max(var(--app-height) - 48px, 262px); &.fixedSize { width: 404px; @@ -34,7 +34,7 @@ &.medium { min-width: 576px; min-height: 374px; - max-height: max(100vh - 48px, 374px); + max-height: max(var(--app-height) - 48px, 374px); &.fixedSize { width: 576px; @@ -47,7 +47,7 @@ &.large { min-width: 720px; min-height: 468px; - max-height: max(100vh - 48px, 468px); + max-height: max(var(--app-height) - 48px, 468px); &.fixedSize { width: 720px; diff --git a/webapp/packages/core-blocks/src/DisplayError.m.css b/webapp/packages/core-blocks/src/DisplayError.m.css index a9dca10b17..8254e30bc5 100644 --- a/webapp/packages/core-blocks/src/DisplayError.m.css +++ b/webapp/packages/core-blocks/src/DisplayError.m.css @@ -5,7 +5,7 @@ overflow: auto; &.root { - height: 100vh; + height: var(--app-height); } } .errorInnerBlock { diff --git a/webapp/packages/core-bootstrap/src/renderLayout.m.css b/webapp/packages/core-bootstrap/src/renderLayout.m.css index 028a292406..729727bf6c 100644 --- a/webapp/packages/core-bootstrap/src/renderLayout.m.css +++ b/webapp/packages/core-bootstrap/src/renderLayout.m.css @@ -1,3 +1,3 @@ .loader { - height: 100vh; -} \ No newline at end of file + height: var(--app-height); +} diff --git a/webapp/packages/core-theming/src/styles/_variables.scss b/webapp/packages/core-theming/src/styles/_variables.scss index d1a5ed2085..5e842c761a 100644 --- a/webapp/packages/core-theming/src/styles/_variables.scss +++ b/webapp/packages/core-theming/src/styles/_variables.scss @@ -26,20 +26,21 @@ $mdc-theme-extra-property-values: ( $mdc-theme-property-values: map-merge($mdc-theme-property-values, $mdc-theme-extra-property-values); -$theme-class: "theme-#{$theme-name}"; +$theme-class: 'theme-#{$theme-name}'; $theme-form-element-radius: 3px !default; $theme-group-element-radius: 4px !default; $theme-menu-bar-small-action-radius: 3px !default; +$app-height: 100vh !default; @mixin css-variables { .theme-form-element-radius { border-radius: $theme-form-element-radius; } - + .theme-group-element-radius { border-radius: $theme-group-element-radius; } - + .theme-menu-bar-small-action-radius { border-radius: $theme-menu-bar-small-action-radius; } diff --git a/webapp/packages/core-utils/src/formatNumber.ts b/webapp/packages/core-utils/src/formatNumber.ts new file mode 100644 index 0000000000..6e119070e9 --- /dev/null +++ b/webapp/packages/core-utils/src/formatNumber.ts @@ -0,0 +1,24 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2023 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 function formatNumber(n: number, d: number) { + if (n < 1000) { + return n.toString(); + } + + const numStr = n.toString(); + const exponent = numStr.length - (numStr.length % 3); + + const power = Math.pow(10, d); + const rounded = Math.round((n * power) / Math.pow(10, exponent)) / power; + + const units = ' kMBTPE'; + const unit = units[exponent / 3]; + + return rounded + unit; +} diff --git a/webapp/packages/core-utils/src/index.ts b/webapp/packages/core-utils/src/index.ts index b90c4e3bb4..02613fa438 100644 --- a/webapp/packages/core-utils/src/index.ts +++ b/webapp/packages/core-utils/src/index.ts @@ -70,3 +70,4 @@ export * from './removeMetadataFromBase64'; export * from './renamePathName'; export * from './removeLineBreak'; export * from './replaceSubstring'; +export * from './formatNumber'; diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserCredentialsList.m.css b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserCredentialsList.m.css index 94fd007403..f96a1e3e79 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserCredentialsList.m.css +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserCredentialsList.m.css @@ -6,7 +6,38 @@ * you may not use this file except in compliance with the License. */ -.staticImage { +.hasMoreIndicator { + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + border-radius: 50%; + background-color: var(--theme-primary); + color: var(--theme-on-primary); + font-size: 12px; + font-weight: 700; + + &:hover { + opacity: 0.8; + } +} + +.staticImage, +.hasMoreIndicator { width: 24px; height: 24px; } + +.menu { + flex-direction: row !important; +} + +.menuItem { + cursor: auto !important; +} + +.menuButton { + padding: 0; + background: transparent; + outline: none; +} diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserCredentialsList.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserCredentialsList.tsx index f5fcfde8e0..0bc6731a17 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserCredentialsList.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/UsersTable/UserCredentialsList.tsx @@ -7,14 +7,18 @@ */ import { observer } from 'mobx-react-lite'; import { Fragment } from 'react'; +import { Menu, MenuButton, MenuItem, useMenuState } from 'reakit'; +import styled from 'reshadow'; import { AUTH_PROVIDER_LOCAL_ID } from '@cloudbeaver/core-authentication'; -import { PlaceholderComponent, s, StaticImage, useS, useTranslate } from '@cloudbeaver/core-blocks'; +import { BASE_DROPDOWN_STYLES, PlaceholderComponent, s, StaticImage, useS, useTranslate } from '@cloudbeaver/core-blocks'; import type { ObjectOrigin } from '@cloudbeaver/core-sdk'; import type { IUserDetailsInfoProps } from '../UsersAdministrationService'; import style from './UserCredentialsList.m.css'; +const MAX_VISIBLE_CREDENTIALS = 3; + interface IUserCredentialsProps { origin: ObjectOrigin; className?: string; @@ -32,11 +36,43 @@ export const UserCredentials = observer(function UserCred }); export const UserCredentialsList: PlaceholderComponent = observer(function UserCredentialsList({ user }) { - return ( + const styles = useS(style); + const translate = useTranslate(); + const menu = useMenuState({ + placement: 'top', + gutter: 8, + }); + + const visibleCredentials = user.origins.slice(0, MAX_VISIBLE_CREDENTIALS); + + return styled(BASE_DROPDOWN_STYLES)( - {user.origins.map(origin => ( - + {visibleCredentials.map(origin => ( + ))} - + + {user.origins.length > MAX_VISIBLE_CREDENTIALS && ( + <> + +
+ +{user.origins.length - MAX_VISIBLE_CREDENTIALS} +
+
+ + + {user.origins.slice(MAX_VISIBLE_CREDENTIALS).map(origin => { + const isLocal = origin.type === AUTH_PROVIDER_LOCAL_ID; + const title = isLocal ? translate('authentication_administration_user_local') : origin.displayName; + + return ( + + + + ); + })} + + + )} + , ); }); diff --git a/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.m.css b/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.m.css index 1ed904ac9b..a3db0276b3 100644 --- a/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.m.css +++ b/webapp/packages/plugin-authentication/src/Dialog/AuthDialog.m.css @@ -1,6 +1,6 @@ .wrapper { min-height: 520px !important; - max-height: max(100vh - 48px, 520px) !important; + max-height: max(var(--app-height) - 48px, 520px) !important; } .submittingForm { overflow: auto; diff --git a/webapp/packages/plugin-d3js/src/index.ts b/webapp/packages/plugin-d3js/src/index.ts index 60b2f65e00..7c644ac5f8 100644 --- a/webapp/packages/plugin-d3js/src/index.ts +++ b/webapp/packages/plugin-d3js/src/index.ts @@ -17,5 +17,10 @@ export { interpolateRound, axisBottom, axisLeft, + scaleOrdinal, + pie, + arc, + schemeTableau10, + sum, } from 'd3'; -export type { Selection, ZoomBehavior, Line, DragBehavior, SubjectPosition } from 'd3'; +export type { Selection, ZoomBehavior, Line, DragBehavior, SubjectPosition, PieArcDatum, ScaleOrdinal } from 'd3';