feat(core-blocks): CB-1318 add "select all" logic to table

This commit is contained in:
Naumov Alexey
2021-10-13 13:37:52 +03:00
parent 6658de3dda
commit 64a66f7d2a
24 changed files with 300 additions and 125 deletions
@@ -6,7 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { observable, computed } from 'mobx';
import { observable, computed, action } from 'mobx';
import { observer } from 'mobx-react-lite';
import { useState, useCallback, useMemo } from 'react';
import styled, { use } from 'reshadow';
@@ -14,23 +14,59 @@ import styled, { use } from 'reshadow';
import { useStyles } from '@cloudbeaver/core-theming';
import { useObjectRef } from '../useObjectRef';
import { TableContext, ITableContext } from './TableContext';
import { useObservableRef } from '../useObservableRef';
import { TableContext, ITableContext, ITableState } from './TableContext';
interface Props {
keys?: any[];
selectedItems?: Map<any, boolean>;
expandedItems?: Map<any, boolean>;
size?: 'big';
className?: string;
onSelect?: (item: any, state: boolean) => void;
isItemSelectable?: (item: any) => boolean;
}
export const Table = observer<Props>(function Table({
selectedItems, expandedItems, className, size, children, onSelect,
keys, isItemSelectable, selectedItems, expandedItems, className, size, children, onSelect,
}) {
const props = useObjectRef({ onSelect });
const [selected] = useState<Map<any, boolean>>(() => selectedItems || observable(new Map()));
const [expanded] = useState<Map<any, boolean>>(() => expandedItems || observable(new Map()));
const state: ITableState = useObservableRef(() => ({
get selectableItems() {
if (!this.keys) {
return [];
}
if (!this.isItemSelectable) {
return this.keys;
}
return this.keys.filter(this.isItemSelectable);
},
get tableSelected() {
return this.selectableItems.length > 0 && this.selectableItems.every(item => selected.get(item));
},
selectTable() {
const tableSelected = this.tableSelected;
for (const item of this.selectableItems) {
selected.set(item, !tableSelected);
}
},
}), {
keys: observable.ref,
isItemSelectable: observable.ref,
selectableItems: computed,
tableSelected: computed,
selectTable: action.bound,
}, { keys, isItemSelectable });
const isExpanded = useMemo(() => computed(() => Array.from(expanded.values()).some(Boolean)), [expanded]);
const setItemSelect = useCallback((item: any, state: boolean) => {
selected.set(item, state);
if (props.onSelect) {
@@ -40,11 +76,12 @@ export const Table = observer<Props>(function Table({
const setItemExpand = useCallback((item: any, state: boolean) => expanded.set(item, state), []);
const clearSelection = useCallback(() => selected.clear(), []);
const collapse = useCallback(() => expanded.clear(), []);
const isExpanded = useMemo(() => computed(() => Array.from(expanded.values()).some(Boolean)), [expanded]);
const [context] = useState<ITableContext>(() => ({
state,
selectedItems: selected,
expandedItems: expanded,
isItemSelectable,
setItemExpand,
setItemSelect,
clearSelection,
@@ -8,7 +8,16 @@
import { createContext } from 'react';
export interface ITableState {
keys?: any[];
readonly selectableItems: any[];
readonly tableSelected: boolean;
selectTable: () => void;
isItemSelectable?: (item: any) => boolean;
}
export interface ITableContext {
state: ITableState;
selectedItems: Map<any, boolean>;
expandedItems: Map<any, boolean>;
setItemSelect: (item: any, state: boolean) => void;
@@ -30,6 +30,7 @@ interface Props {
selectOnItem?: boolean;
selectDisabled?: boolean;
disabled?: boolean;
title?: string;
className?: string;
onClick?: () => void;
onDoubleClick?: () => void;
@@ -42,6 +43,7 @@ export const TableItem = observer<Props>(function TableItem({
selectDisabled = false,
disabled,
children,
title,
className,
onClick,
onDoubleClick,
@@ -91,6 +93,7 @@ export const TableItem = observer<Props>(function TableItem({
return styled(styles)(
<TableItemContext.Provider value={itemContext}>
<tr
title={title}
className={className}
onClick={handleClick}
onDoubleClick={handleDoubleClick}
@@ -0,0 +1,42 @@
/*
* 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 { observer } from 'mobx-react-lite';
import { useContext } from 'react';
import { useTranslate } from '@cloudbeaver/core-localization';
import { Checkbox } from '../FormControls/Checkboxes/Checkbox';
import { TableContext } from './TableContext';
interface Props {
id: string;
disabled?: boolean;
tooltip?: string;
className?: string;
}
export const TableSelect = observer<Props>(function TableSelect({ id, disabled, tooltip, className }) {
const tableContext = useContext(TableContext);
const translate = useTranslate();
if (!tableContext) {
throw new Error('Context must be provided');
}
return (
<Checkbox
id={id}
className={className}
title={tooltip || translate('ui_select_all')}
disabled={disabled || !tableContext.state.selectableItems.length}
checked={tableContext.state.tableSelected}
onClick={tableContext.state.selectTable}
/>
);
});
@@ -0,0 +1,19 @@
/*
* 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 function getSelectedItems(items: Map<any, boolean>): any[] {
const result: any[] = [];
for (const [key, value] of items) {
if (value) {
result.push(key);
}
}
return result;
}
+2
View File
@@ -53,6 +53,8 @@ export * from './Table/TableItemSelect';
export * from './Table/TableItemSeparator';
export * from './Table/useTable';
export * from './Table/TableState';
export * from './Table/TableSelect';
export * from './Table/getSelectedItems';
export * from './Tabs/ITab';
export * from './Tabs/TabContext';
@@ -6,9 +6,9 @@
* you may not use this file except in compliance with the License.
*/
import { computed, observable } from 'mobx';
import { observable } from 'mobx';
import { observer } from 'mobx-react-lite';
import { useCallback, useMemo, useState } from 'react';
import { useCallback, useState } from 'react';
import styled, { css } from 'reshadow';
import type { RoleInfo } from '@cloudbeaver/core-authentication';
@@ -20,7 +20,9 @@ import {
BASE_CONTAINERS_STYLES,
Group,
Button,
useObjectRef
useObjectRef,
getComputed,
getSelectedItems
} from '@cloudbeaver/core-blocks';
import { TLocalizationToken, useTranslate } from '@cloudbeaver/core-localization';
import type { AdminUserInfoFragment } from '@cloudbeaver/core-sdk';
@@ -78,25 +80,20 @@ export const ConnectionAccessGrantedList = observer<Props>(function ConnectionAc
const translate = useTranslate();
const [selectedSubjects] = useState<Map<any, boolean>>(() => observable(new Map()));
const [filterState] = useState<IFilterState>(() => observable({ filterValue: '' }));
const selectedList = useMemo(() => computed(
() => Array.from(selectedSubjects.entries()).filter(([key, value]) => value).map(([key]) => key)
), [selectedSubjects]);
const selected = getComputed(() => Array.from(selectedSubjects.values()).some(v => v));
const revoke = useCallback(() => {
props.onRevoke(selectedList.get());
props.onRevoke(getSelectedItems(selectedSubjects));
selectedSubjects.clear();
}, []);
const roles = useMemo(() => computed(() => getFilteredRoles(
grantedRoles, filterState.filterValue
)), [filterState, grantedRoles]);
const users = useMemo(() => computed(() => getFilteredUsers(
grantedUsers, filterState.filterValue
)), [filterState, grantedUsers]);
const roles = getFilteredRoles(grantedRoles, filterState.filterValue);
const users = getFilteredUsers(grantedUsers, filterState.filterValue);
const keys = roles.map(role => role.roleId).concat(users.map(user => user.userId));
let tableInfoText: TLocalizationToken = 'connections_connection_access_admin_info';
if (!roles.get().length && !users.get().length) {
if (!keys.length) {
if (filterState.filterValue) {
tableInfoText = 'ui_search_no_result_placeholder';
} else {
@@ -108,34 +105,36 @@ export const ConnectionAccessGrantedList = observer<Props>(function ConnectionAc
<Group box medium overflow>
<container>
<ConnectionAccessTableHeader filterState={filterState} disabled={disabled}>
<Button disabled={disabled || !selectedList.get().length} mod={['outlined']} onClick={revoke}>{translate('ui_delete')}</Button>
<Button disabled={disabled || !selected} mod={['outlined']} onClick={revoke}>{translate('ui_delete')}</Button>
<Button disabled={disabled} mod={['unelevated']} onClick={props.onEdit}>{translate('ui_edit')}</Button>
</ConnectionAccessTableHeader>
<table-container>
<Table selectedItems={selectedSubjects}>
<ConnectionAccessTableInnerHeader />
<Table keys={keys} selectedItems={selectedSubjects}>
<ConnectionAccessTableInnerHeader disabled={disabled} />
<TableBody>
<TableItem item='tableInfo' selectDisabled>
<TableColumnValue colSpan={5}>
{translate(tableInfoText)}
</TableColumnValue>
</TableItem>
{roles.get().map(role => (
{roles.map(role => (
<ConnectionAccessTableItem
key={role.roleId}
id={role.roleId}
name={role.roleName || ''}
tooltip={role.roleName}
description={role.description}
icon='/icons/role.svg'
iconTooltip={translate('authentication_role_icon_tooltip')}
disabled={disabled}
/>
))}
{users.get().map(user => (
{users.map(user => (
<ConnectionAccessTableItem
key={user.userId}
id={user.userId}
name={user.userId}
tooltip={user.userId}
icon='/icons/user.svg'
iconTooltip={translate('authentication_user_icon_tooltip')}
disabled={disabled}
@@ -6,9 +6,9 @@
* you may not use this file except in compliance with the License.
*/
import { computed, observable } from 'mobx';
import { observable } from 'mobx';
import { observer } from 'mobx-react-lite';
import { useCallback, useMemo, useState } from 'react';
import { useCallback, useState } from 'react';
import styled, { css } from 'reshadow';
import type { RoleInfo } from '@cloudbeaver/core-authentication';
@@ -21,6 +21,8 @@ import {
Group,
Button,
useObjectRef,
getComputed,
getSelectedItems,
} from '@cloudbeaver/core-blocks';
import { useTranslate } from '@cloudbeaver/core-localization';
import type { AdminUserInfoFragment } from '@cloudbeaver/core-sdk';
@@ -78,59 +80,60 @@ export const ConnectionAccessList = observer<Props>(function ConnectionAccessLis
const translate = useTranslate();
const [selectedSubjects] = useState<Map<any, boolean>>(() => observable(new Map()));
const [filterState] = useState<IFilterState>(() => observable({ filterValue: '' }));
const selectedList = useMemo(() => computed(
() => Array.from(selectedSubjects.entries()).filter(([key, value]) => value).map(([key]) => key)
), [selectedSubjects]);
const roles = getFilteredRoles(roleList, filterState.filterValue);
const users = getFilteredUsers(userList, filterState.filterValue);
const keys = roles.map(role => role.roleId).concat(users.map(user => user.userId));
const selected = getComputed(() => Array.from(selectedSubjects.values()).some(v => v));
const grant = useCallback(() => {
props.onGrant(selectedList.get());
props.onGrant(getSelectedItems(selectedSubjects));
selectedSubjects.clear();
}, []);
const roles = useMemo(() => computed(() => getFilteredRoles(
roleList, filterState.filterValue
)), [filterState, roleList]);
const users = useMemo(() => computed(() => getFilteredUsers(
userList, filterState.filterValue
)), [filterState, userList]);
return styled(style)(
<Group box medium overflow>
<container>
<ConnectionAccessTableHeader filterState={filterState} disabled={disabled}>
<Button disabled={disabled || !selectedList.get().length} mod={['unelevated']} onClick={grant}>{translate('ui_add')}</Button>
<Button disabled={disabled || !selected} mod={['unelevated']} onClick={grant}>{translate('ui_add')}</Button>
</ConnectionAccessTableHeader>
<table-container>
<Table selectedItems={selectedSubjects}>
<ConnectionAccessTableInnerHeader />
<Table
keys={keys}
selectedItems={selectedSubjects}
isItemSelectable={item => !grantedSubjects.includes(item)}
>
<ConnectionAccessTableInnerHeader disabled={disabled} />
<TableBody>
{!roles.get().length && !users.get().length && filterState.filterValue && (
{!keys.length && filterState.filterValue && (
<TableItem item='tableInfo' selectDisabled>
<TableColumnValue colSpan={5}>
{translate('ui_search_no_result_placeholder')}
</TableColumnValue>
</TableItem>
)}
{roles.get().map(role => (
{roles.map(role => (
<ConnectionAccessTableItem
key={role.roleId}
id={role.roleId}
name={role.roleName || ''}
tooltip={role.roleName}
description={role.description}
icon='/icons/role.svg'
iconTooltip='authentication_role_icon_tooltip'
disabled={disabled || grantedSubjects.includes(role.roleId)}
iconTooltip={translate('authentication_role_icon_tooltip')}
disabled={disabled}
/>
))}
{users.get().map(user => (
{users.map(user => (
<ConnectionAccessTableItem
key={user.userId}
id={user.userId}
name={user.userId}
tooltip={user.userId}
icon='/icons/user.svg'
iconTooltip={translate('authentication_user_icon_tooltip')}
disabled={disabled || grantedSubjects.includes(user.userId)}
disabled={disabled}
/>
))}
</TableBody>
@@ -9,11 +9,12 @@
import { observer } from 'mobx-react-lite';
import styled, { css } from 'reshadow';
import { TableColumnHeader, TableHeader } from '@cloudbeaver/core-blocks';
import { TableColumnHeader, TableHeader, TableSelect } from '@cloudbeaver/core-blocks';
import { useTranslate } from '@cloudbeaver/core-localization';
import { composes, useStyles } from '@cloudbeaver/core-theming';
interface Props {
disabled?: boolean;
className?: string;
}
@@ -32,11 +33,13 @@ const styles = composes(
`
);
export const ConnectionAccessTableInnerHeader = observer<Props>(function ConnectionAccessTableInnerHeader({ className }) {
export const ConnectionAccessTableInnerHeader = observer<Props>(function ConnectionAccessTableInnerHeader({ disabled, className }) {
const translate = useTranslate();
return styled(useStyles(styles))(
<TableHeader className={className}>
<TableColumnHeader min />
<TableColumnHeader min>
<TableSelect id='selectUsersAndRoles' disabled={disabled} />
</TableColumnHeader>
<TableColumnHeader min />
<TableColumnHeader>{translate('connections_connection_access_user_or_role_name')}</TableColumnHeader>
<TableColumnHeader>{translate('connections_connection_description')}</TableColumnHeader>
@@ -7,17 +7,18 @@
*/
import { observer } from 'mobx-react-lite';
import { useContext } from 'react';
import styled, { css } from 'reshadow';
import { StaticImage, TableColumnValue, TableItem, TableItemSelect } from '@cloudbeaver/core-blocks';
import { TLocalizationToken, useTranslate } from '@cloudbeaver/core-localization';
import { StaticImage, TableColumnValue, TableContext, TableItem, TableItemSelect } from '@cloudbeaver/core-blocks';
interface Props {
id: any;
name: string;
icon: string;
disabled: boolean;
iconTooltip?: TLocalizationToken;
iconTooltip?: string;
tooltip?: string;
description?: string;
className?: string;
}
@@ -30,12 +31,20 @@ const style = css`
`;
export const ConnectionAccessTableItem = observer<Props>(function ConnectionAccessTableItem({
id, name, description, icon, iconTooltip, disabled, className,
id, name, description, icon, iconTooltip, tooltip, disabled: tableDisabled, className,
}) {
const translate = useTranslate();
const tableContext = useContext(TableContext);
if (!tableContext) {
throw new Error('Context must be provided');
}
const disabled = tableDisabled || tableContext.state.isItemSelectable?.(id) === false;
return styled(style)(
<TableItem
item={id}
title={tooltip}
disabled={disabled}
selectDisabled={disabled}
className={className}
@@ -43,7 +52,7 @@ export const ConnectionAccessTableItem = observer<Props>(function ConnectionAcce
<TableColumnValue centerContent flex>
<TableItemSelect disabled={disabled} />
</TableColumnValue>
<TableColumnValue><StaticImage icon={icon} title={translate(iconTooltip)} /></TableColumnValue>
<TableColumnValue><StaticImage icon={icon} title={iconTooltip} /></TableColumnValue>
<TableColumnValue>{name}</TableColumnValue>
<TableColumnValue>{description}</TableColumnValue>
</TableItem>
@@ -75,7 +75,7 @@ export const RenameDialog: DialogComponent<RenameDialogPayload, string> = functi
mod={['unelevated']}
onClick={() => resolveDialog(name)}
>
<Translate token={confirmActionText || 'ui_processing_ok'} />
<Translate token={confirmActionText || 'ui_rename'} />
</Button>
</>
)}
@@ -45,6 +45,8 @@ export const defaultENLocale = [
['ui_search_no_result_placeholder', 'No results have been found.'],
['ui_save_reminder', 'You have unsaved changes.'],
['ui_yes', 'Yes'],
['ui_select_all', 'Select all'],
['ui_reconnect', 'Reconnect'],
['root_permission_denied', 'You have no permissions'],
['app_root_session_expire_warning_title', 'Session is about to expire'],
@@ -43,6 +43,8 @@ export const defaultRULocale = [
['ui_search_no_result_placeholder', 'По вашему запросу ничего не найдено.'],
['ui_save_reminder', 'У вас есть несохраненные изменения.'],
['ui_yes', 'Да'],
['ui_select_all', 'Выбрать все'],
['ui_reconnect', 'Переподключиться'],
['root_permission_denied', 'Отказано в доступе'],
['app_root_session_expire_warning_title', 'Ваша сессия скоро закончится'],
@@ -21,6 +21,7 @@ import {
Button,
useObjectRef,
getComputed,
getSelectedItems,
} from '@cloudbeaver/core-blocks';
import { DBDriverResource } from '@cloudbeaver/core-connections';
import { useService } from '@cloudbeaver/core-di';
@@ -85,12 +86,12 @@ export const ConnectionList = observer<Props>(function ConnectionList({
const selected = getComputed(() => Array.from(selectedSubjects.values()).some(v => v));
const grant = useCallback(() => {
const selectedList = Array.from(selectedSubjects.entries()).filter(([key, value]) => value).map(([key]) => key);
props.onGrant(selectedList);
props.onGrant(getSelectedItems(selectedSubjects));
selectedSubjects.clear();
}, []);
const connections = getFilteredConnections(connectionList, filterState.filterValue);
const keys = connections.map(connection => connection.id);
return styled(style)(
<Group box medium overflow>
@@ -99,8 +100,12 @@ export const ConnectionList = observer<Props>(function ConnectionList({
<Button disabled={disabled || !selected} mod={['unelevated']} onClick={grant}>{translate('ui_add')}</Button>
</GrantedConnectionsTableHeader>
<table-container>
<Table selectedItems={selectedSubjects}>
<GrantedConnectionsTableInnerHeader />
<Table
keys={keys}
selectedItems={selectedSubjects}
isItemSelectable={item => !grantedSubjects.includes(item)}
>
<GrantedConnectionsTableInnerHeader disabled={disabled} />
<TableBody>
{!connections.length && filterState.filterValue && (
<TableItem item='tableInfo' selectDisabled>
@@ -116,9 +121,10 @@ export const ConnectionList = observer<Props>(function ConnectionList({
key={connection.id}
id={connection.id}
name={connection.name}
tooltip={connection.name}
host={`${connection.host || ''}${connection.host && connection.port ? ':' + connection.port : ''}`}
icon={driver?.icon}
disabled={disabled || grantedSubjects.includes(connection.id)}
disabled={disabled}
/>
);
})}
@@ -19,7 +19,8 @@ import {
BASE_CONTAINERS_STYLES,
Group,
Button,
useObjectRef
useObjectRef,
getSelectedItems
} from '@cloudbeaver/core-blocks';
import { getComputed } from '@cloudbeaver/core-blocks';
import { DBDriverResource } from '@cloudbeaver/core-connections';
@@ -82,16 +83,16 @@ export const GrantedConnectionList = observer<Props>(function GrantedConnectionL
const [selectedSubjects] = useState<Map<any, boolean>>(() => observable(new Map()));
const [filterState] = useState<IFilterState>(() => observable({ filterValue: '' }));
const connections = getFilteredConnections(grantedConnections, filterState.filterValue);
const keys = connections.map(connection => connection.id);
const selected = getComputed(() => Array.from(selectedSubjects.values()).some(v => v));
const revoke = useCallback(() => {
const selectedList = Array.from(selectedSubjects.entries()).filter(([key, value]) => value).map(([key]) => key);
props.onRevoke(selectedList);
props.onRevoke(getSelectedItems(selectedSubjects));
selectedSubjects.clear();
}, []);
const connections = getFilteredConnections(grantedConnections, filterState.filterValue);
let tableInfoText: TLocalizationToken | null = null;
if (!connections.length) {
if (filterState.filterValue) {
@@ -109,8 +110,8 @@ export const GrantedConnectionList = observer<Props>(function GrantedConnectionL
<Button disabled={disabled} mod={['unelevated']} onClick={props.onEdit}>{translate('ui_edit')}</Button>
</GrantedConnectionsTableHeader>
<table-container>
<Table selectedItems={selectedSubjects}>
<GrantedConnectionsTableInnerHeader />
<Table keys={keys} selectedItems={selectedSubjects}>
<GrantedConnectionsTableInnerHeader disabled={disabled} />
<TableBody>
<TableItem item='tableInfo' selectDisabled>
{tableInfoText && (
@@ -126,6 +127,7 @@ export const GrantedConnectionList = observer<Props>(function GrantedConnectionL
key={connection.id}
id={connection.id}
name={connection.name}
tooltip={connection.name}
host={`${connection.host || ''}${connection.port ? ':' + connection.port : ''}`}
icon={driver?.icon}
disabled={disabled}
@@ -9,11 +9,12 @@
import { observer } from 'mobx-react-lite';
import styled, { css } from 'reshadow';
import { TableColumnHeader, TableHeader } from '@cloudbeaver/core-blocks';
import { TableColumnHeader, TableHeader, TableSelect } from '@cloudbeaver/core-blocks';
import { useTranslate } from '@cloudbeaver/core-localization';
import { composes, useStyles } from '@cloudbeaver/core-theming';
interface Props {
disabled?: boolean;
className?: string;
}
@@ -32,11 +33,13 @@ const styles = composes(
`
);
export const GrantedConnectionsTableInnerHeader = observer<Props>(function GrantedConnectionsTableInnerHeader({ className }) {
export const GrantedConnectionsTableInnerHeader = observer<Props>(function GrantedConnectionsTableInnerHeader({ disabled, className }) {
const translate = useTranslate();
return styled(useStyles(styles))(
<TableHeader className={className}>
<TableColumnHeader min />
<TableColumnHeader min>
<TableSelect id='selectConnections' disabled={disabled} />
</TableColumnHeader>
<TableColumnHeader min />
<TableColumnHeader>{translate('connections_connection_name')}</TableColumnHeader>
<TableColumnHeader>{translate('connections_connection_address')}</TableColumnHeader>
@@ -7,10 +7,10 @@
*/
import { observer } from 'mobx-react-lite';
import { useContext } from 'react';
import styled, { css } from 'reshadow';
import { StaticImage, TableColumnValue, TableItem, TableItemSelect } from '@cloudbeaver/core-blocks';
import { TLocalizationToken, useTranslate } from '@cloudbeaver/core-localization';
import { StaticImage, TableColumnValue, TableContext, TableItem, TableItemSelect } from '@cloudbeaver/core-blocks';
interface Props {
id: any;
@@ -18,7 +18,8 @@ interface Props {
disabled: boolean;
host?: string;
icon?: string;
iconTooltip?: TLocalizationToken;
iconTooltip?: string;
tooltip?: string;
className?: string;
}
@@ -30,13 +31,20 @@ const style = css`
`;
export const GrantedConnectionsTableItem = observer<Props>(function GrantedConnectionsTableItem({
id, name, host, icon, iconTooltip, disabled, className,
id, name, host, icon, iconTooltip, tooltip, disabled: tableDisabled, className,
}) {
const translate = useTranslate();
const tableContext = useContext(TableContext);
if (!tableContext) {
throw new Error('Context must be provided');
}
const disabled = tableDisabled || tableContext.state.isItemSelectable?.(id) === false;
return styled(style)(
<TableItem
item={id}
title={tooltip}
disabled={disabled}
selectDisabled={disabled}
className={className}
@@ -44,7 +52,7 @@ export const GrantedConnectionsTableItem = observer<Props>(function GrantedConne
<TableColumnValue centerContent flex>
<TableItemSelect disabled={disabled} />
</TableColumnValue>
<TableColumnValue>{icon && <StaticImage icon={icon} title={translate(iconTooltip)} />}</TableColumnValue>
<TableColumnValue>{icon && <StaticImage icon={icon} title={iconTooltip} />}</TableColumnValue>
<TableColumnValue>{name}</TableColumnValue>
<TableColumnValue>{host && host}</TableColumnValue>
</TableItem>
@@ -21,7 +21,8 @@ import {
Group,
Button,
useObjectRef,
getComputed
getComputed,
getSelectedItems
} from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { TLocalizationToken, useTranslate } from '@cloudbeaver/core-localization';
@@ -68,10 +69,7 @@ interface Props {
}
export const GrantedUserList = observer<Props>(function GrantedUserList({
grantedUsers,
disabled,
onRevoke,
onEdit,
grantedUsers, disabled, onRevoke, onEdit,
}) {
const props = useObjectRef({ onRevoke, onEdit });
const style = useStyles(styles, BASE_CONTAINERS_STYLES);
@@ -84,14 +82,14 @@ export const GrantedUserList = observer<Props>(function GrantedUserList({
const selected = getComputed(() => Array.from(selectedSubjects.values()).some(v => v));
const users = getFilteredUsers(grantedUsers, filterState.filterValue);
const keys = users.map(user => user.userId);
const revoke = useCallback(() => {
const selectedList = Array.from(selectedSubjects.entries()).filter(([key, value]) => value).map(([key]) => key);
props.onRevoke(selectedList);
props.onRevoke(getSelectedItems(selectedSubjects));
selectedSubjects.clear();
}, []);
const users = getFilteredUsers(grantedUsers, filterState.filterValue);
let tableInfoText: TLocalizationToken | null = null;
if (!users.length) {
if (filterState.filterValue) {
@@ -109,8 +107,12 @@ export const GrantedUserList = observer<Props>(function GrantedUserList({
<Button disabled={disabled} mod={['unelevated']} onClick={props.onEdit}>{translate('ui_edit')}</Button>
</GrantedUsersTableHeader>
<table-container>
<Table selectedItems={selectedSubjects}>
<GrantedUsersTableInnerHeader />
<Table
keys={keys}
selectedItems={selectedSubjects}
isItemSelectable={item => !usersResource.isActiveUser(item)}
>
<GrantedUsersTableInnerHeader disabled={disabled} />
<TableBody>
<TableItem item='tableInfo' selectDisabled>
{tableInfoText && (
@@ -119,16 +121,20 @@ export const GrantedUserList = observer<Props>(function GrantedUserList({
</TableColumnValue>
)}
</TableItem>
{users.map(user => (
<GrantedUsersTableItem
key={user.userId}
id={user.userId}
name={`${user.userId}${usersResource.isActiveUser(user.userId) ? ' (you)' : ''}`}
icon='/icons/user.svg'
iconTooltip={translate('authentication_user_icon_tooltip')}
disabled={disabled}
/>
))}
{users.map(user => {
const activeUser = usersResource.isActiveUser(user.userId);
return (
<GrantedUsersTableItem
key={user.userId}
id={user.userId}
name={`${user.userId}${activeUser ? ' (you)' : ''}`}
tooltip={activeUser ? translate('administration_roles_role_granted_users_permission_denied') : user.userId}
icon='/icons/user.svg'
iconTooltip={translate('authentication_user_icon_tooltip')}
disabled={disabled}
/>
);
})}
</TableBody>
</Table>
</table-container>
@@ -9,11 +9,12 @@
import { observer } from 'mobx-react-lite';
import styled, { css } from 'reshadow';
import { TableColumnHeader, TableHeader } from '@cloudbeaver/core-blocks';
import { TableColumnHeader, TableHeader, TableSelect } from '@cloudbeaver/core-blocks';
import { useTranslate } from '@cloudbeaver/core-localization';
import { composes, useStyles } from '@cloudbeaver/core-theming';
interface Props {
disabled?: boolean;
className?: string;
}
@@ -32,11 +33,14 @@ const styles = composes(
`
);
export const GrantedUsersTableInnerHeader = observer<Props>(function GrantedUsersTableInnerHeader({ className }) {
export const GrantedUsersTableInnerHeader = observer<Props>(function GrantedUsersTableInnerHeader({ disabled, className }) {
const translate = useTranslate();
return styled(useStyles(styles))(
<TableHeader className={className}>
<TableColumnHeader min />
<TableColumnHeader min>
<TableSelect id='selectUsers' disabled={disabled} />
</TableColumnHeader>
<TableColumnHeader min />
<TableColumnHeader>{translate('administration_roles_role_granted_users_user_id')}</TableColumnHeader>
</TableHeader>
@@ -7,17 +7,18 @@
*/
import { observer } from 'mobx-react-lite';
import { useContext } from 'react';
import styled, { css } from 'reshadow';
import { StaticImage, TableColumnValue, TableItem, TableItemSelect } from '@cloudbeaver/core-blocks';
import { TLocalizationToken, useTranslate } from '@cloudbeaver/core-localization';
import { StaticImage, TableColumnValue, TableContext, TableItem, TableItemSelect } from '@cloudbeaver/core-blocks';
interface Props {
id: any;
name: string;
icon: string;
disabled: boolean;
iconTooltip?: TLocalizationToken;
iconTooltip?: string;
tooltip?: string;
className?: string;
}
@@ -29,12 +30,20 @@ const style = css`
`;
export const GrantedUsersTableItem = observer<Props>(function GrantedUsersTableItem({
id, name, icon, iconTooltip, disabled, className,
id, name, icon, iconTooltip, tooltip, disabled: tableDisabled, className,
}) {
const translate = useTranslate();
const tableContext = useContext(TableContext);
if (!tableContext) {
throw new Error('Context must be provided');
}
const disabled = tableDisabled || tableContext.state.isItemSelectable?.(id) === false;
return styled(style)(
<TableItem
item={id}
title={tooltip}
disabled={disabled}
selectDisabled={disabled}
className={className}
@@ -42,7 +51,7 @@ export const GrantedUsersTableItem = observer<Props>(function GrantedUsersTableI
<TableColumnValue centerContent flex>
<TableItemSelect disabled={disabled} />
</TableColumnValue>
<TableColumnValue><StaticImage icon={icon} title={translate(iconTooltip)} /></TableColumnValue>
<TableColumnValue><StaticImage icon={icon} title={iconTooltip} /></TableColumnValue>
<TableColumnValue>{name}</TableColumnValue>
</TableItem>
);
@@ -22,6 +22,7 @@ import {
Button,
useObjectRef,
getComputed,
getSelectedItems,
} from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { useTranslate } from '@cloudbeaver/core-localization';
@@ -84,14 +85,14 @@ export const UserList = observer<Props>(function UserList({
const selected = getComputed(() => Array.from(selectedSubjects.values()).some(v => v));
const users = getFilteredUsers(userList, filterState.filterValue);
const keys = users.map(user => user.userId);
const grant = useCallback(() => {
const selectedList = Array.from(selectedSubjects.entries()).filter(([key, value]) => value).map(([key]) => key);
props.onGrant(selectedList);
props.onGrant(getSelectedItems(selectedSubjects));
selectedSubjects.clear();
}, []);
const users = getFilteredUsers(userList, filterState.filterValue);
return styled(style)(
<Group box medium overflow>
<container>
@@ -99,8 +100,8 @@ export const UserList = observer<Props>(function UserList({
<Button disabled={disabled || !selected} mod={['unelevated']} onClick={grant}>{translate('ui_add')}</Button>
</GrantedUsersTableHeader>
<table-container>
<Table selectedItems={selectedSubjects}>
<GrantedUsersTableInnerHeader />
<Table keys={keys} selectedItems={selectedSubjects} isItemSelectable={item => !grantedUsers.includes(item)}>
<GrantedUsersTableInnerHeader disabled={disabled} />
<TableBody>
{!users.length && filterState.filterValue && (
<TableItem item='tableInfo' selectDisabled>
@@ -109,16 +110,20 @@ export const UserList = observer<Props>(function UserList({
</TableColumnValue>
</TableItem>
)}
{users.map(user => (
<GrantedUsersTableItem
key={user.userId}
id={user.userId}
name={`${user.userId}${usersResource.isActiveUser(user.userId) ? ' (you)' : ''}`}
icon='/icons/user.svg'
iconTooltip={translate('authentication_user_icon_tooltip')}
disabled={disabled || !!grantedUsers.includes(user.userId)}
/>
))}
{users.map(user => {
const activeUser = usersResource.isActiveUser(user.userId);
return (
<GrantedUsersTableItem
key={user.userId}
id={user.userId}
name={`${user.userId}${activeUser ? ' (you)' : ''}`}
tooltip={activeUser ? translate('administration_roles_role_granted_users_permission_denied') : user.userId}
icon='/icons/user.svg'
iconTooltip={translate('authentication_user_icon_tooltip')}
disabled={disabled}
/>
);
})}
</TableBody>
</Table>
</table-container>
@@ -98,6 +98,7 @@ export default [
['administration_roles_role_granted_users_user_id', 'User ID'],
['administration_roles_role_granted_users_user_name', 'User name'],
['administration_roles_role_granted_users_empty', 'No available users'],
['administration_roles_role_granted_users_permission_denied', "You can't edit your own permissions"],
['administration_roles_role_granted_connections_tab_title', 'Connections'],
['administration_roles_role_granted_connections_search_placeholder', 'Search for connection name...'],
@@ -86,6 +86,7 @@ export default [
['administration_roles_role_granted_users_user_id', 'ID пользователя'],
['administration_roles_role_granted_users_user_name', 'Имя пользователя'],
['administration_roles_role_granted_users_empty', 'Нет доступных пользователей'],
['administration_roles_role_granted_users_permission_denied', 'Вы не можете редактировать свои настройки доступа'],
['administration_roles_role_granted_connections_tab_title', 'Подключения'],
['administration_roles_role_granted_connections_search_placeholder', 'Поиск по названию подключения...'],
@@ -140,7 +140,7 @@ export class PublicConnectionFormService {
const result = await this.commonDialogService.open(ConfirmationDialog, {
title: 'connections_public_connection_edit_reconnect_title',
message: 'connections_public_connection_edit_reconnect_message',
confirmActionText: 'ui_yes',
confirmActionText: 'ui_reconnect',
});
if (result === DialogueStateResult.Rejected) {