Merge pull request #309 from dbeaver/feat/system-objects

Feat/system objects
This commit is contained in:
Alexey
2021-03-16 21:14:19 +03:00
committed by GitHub
29 changed files with 350 additions and 129 deletions
@@ -6,7 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { ConnectionInfoResource, isSimpleNavigatorView } from '@cloudbeaver/core-connections';
import { ConnectionInfoResource, CONNECTION_NAVIGATOR_VIEW_SETTINGS, isNavigatorViewSettingsEqual, NavigatorViewSettings } from '@cloudbeaver/core-connections';
import { injectable } from '@cloudbeaver/core-di';
import { ContextMenuService, IMenuPanel } from '@cloudbeaver/core-dialogs';
import { NotificationService } from '@cloudbeaver/core-events';
@@ -46,23 +46,25 @@ export class NavigationTreeContextMenuService {
});
}
private isConnectionSimpleView(nodeId: string): boolean {
const connectionId = NodeManagerUtils.connectionNodeIdToConnectionId(nodeId);
private getConnectionFromNodeId(nodeId: string) {
return this.connectionInfoResource.get(NodeManagerUtils.connectionNodeIdToConnectionId(nodeId));
}
const connection = this.connectionInfoResource.get(connectionId);
private isSimpleNavigatorView(nodeId: string) {
const currentSettings = this.getConnectionFromNodeId(nodeId)?.navigatorSettings;
if (!connection) {
if (!currentSettings) {
return false;
}
return isSimpleNavigatorView(connection.navigatorSettings);
return isNavigatorViewSettingsEqual(currentSettings, CONNECTION_NAVIGATOR_VIEW_SETTINGS.simple);
}
private async changeConnectionView(nodeId: string, simple: boolean) {
private async changeConnectionView(nodeId: string, settings: NavigatorViewSettings) {
const connectionId = NodeManagerUtils.connectionNodeIdToConnectionId(nodeId);
try {
await this.connectionInfoResource.changeConnectionView(connectionId, simple);
await this.connectionInfoResource.changeConnectionView(connectionId, settings);
await this.navNodeManagerService.refreshTree(nodeId);
} catch (exception) {
this.notificationService.logException(exception);
@@ -88,12 +90,15 @@ export class NavigationTreeContextMenuService {
{
id: 'simple',
title: 'app_navigationTree_connection_view_option_simple',
isDisabled: context => this.isConnectionSimpleView(context.data.id),
type: 'radio',
isChecked: context => this.isSimpleNavigatorView(context.data.id),
isPresent(context) {
return context.contextType === NavigationTreeContextMenuService.nodeContextType
&& context.data.objectFeatures.includes(EObjectFeature.dataSource);
},
onClick: async context => await this.changeConnectionView(context.data.id, true),
onClick: async context =>
await this.changeConnectionView(context.data.id, CONNECTION_NAVIGATOR_VIEW_SETTINGS.simple),
}
);
this.contextMenuService.addMenuItem<NavNode>(
@@ -101,12 +106,40 @@ export class NavigationTreeContextMenuService {
{
id: 'advanced',
title: 'app_navigationTree_connection_view_option_advanced',
isDisabled: context => !this.isConnectionSimpleView(context.data.id),
type: 'radio',
isChecked: context => !this.isSimpleNavigatorView(context.data.id),
separator: true,
isPresent(context) {
return context.contextType === NavigationTreeContextMenuService.nodeContextType
&& context.data.objectFeatures.includes(EObjectFeature.dataSource);
},
onClick: async context => await this.changeConnectionView(context.data.id, false),
onClick: async context =>
await this.changeConnectionView(context.data.id, CONNECTION_NAVIGATOR_VIEW_SETTINGS.advanced),
}
);
this.contextMenuService.addMenuItem<NavNode>(
this.getNodeViewMenuItemToken(),
{
id: 'systemObjects',
title: 'app_navigationTree_connection_view_option_showSystemObjects',
type: 'checkbox',
isChecked: context => !!this.getConnectionFromNodeId(context.data.id)?.navigatorSettings.showSystemObjects,
isPresent(context) {
return context.contextType === NavigationTreeContextMenuService.nodeContextType
&& context.data.objectFeatures.includes(EObjectFeature.dataSource);
},
onClick: async context => {
const currentSettings = this.getConnectionFromNodeId(context.data.id)?.navigatorSettings;
if (!currentSettings) {
return;
}
return await this.changeConnectionView(context.data.id, {
...currentSettings,
showSystemObjects: !currentSettings.showSystemObjects,
});
},
}
);
}
@@ -13,6 +13,8 @@ export default [
['app_navigationTree_connection_view', 'Connection view'],
['app_navigationTree_connection_view_option_simple', 'Simple'],
['app_navigationTree_connection_view_option_advanced', 'Advanced'],
['app_navigationTree_connection_view_option_custom', 'Custom'],
['app_navigationTree_connection_view_option_showSystemObjects', 'Show system objects'],
['app_navigationTree_refreshNode', 'Refresh'],
['app_navigationTree_context_disconnect', 'Disconnect'],
['app_shared_inlineEditor_dialog_title', 'Edit mode'],
@@ -16,6 +16,8 @@ export default [
['app_navigationTree_connection_view', 'Отображение'],
['app_navigationTree_connection_view_option_simple', 'Упрощенное'],
['app_navigationTree_connection_view_option_advanced', 'Продвинутое'],
['app_navigationTree_connection_view_option_custom', 'Настраиваемое'],
['app_navigationTree_connection_view_option_showSystemObjects', 'Показывать системные объекты'],
['app_navigationTree_refreshNode', 'Обновить'],
['app_navigationTree_context_disconnect', 'Отключить'],
['app_shared_inlineEditor_dialog_title', 'Редактирование'],
@@ -8,18 +8,20 @@
import { observer } from 'mobx-react-lite';
import { useCallback, useContext, useState } from 'react';
import { use } from 'reshadow';
import type { IGridItemsLayoutProps } from '../../Containers/LayoutProps';
import { FormContext } from '../FormContext';
import { isControlPresented } from '../isControlPresented';
import { CheckboxMarkup } from './CheckboxMarkup';
export type CheckboxMod = 'primary' | 'small';
export type CheckboxBaseProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'type' | 'value' | 'checked'> & IGridItemsLayoutProps & {
value?: string;
checkboxLabel?: string;
mod?: 'surface';
long?: boolean;
mod?: CheckboxMod[];
ripple?: boolean;
};
type CheckboxOnChangeEvent<T> =
@@ -55,8 +57,9 @@ export const Checkbox: CheckboxType = observer(function Checkbox({
checkboxLabel,
checked: checkedControlled,
children,
className,
mod,
ripple,
className,
long,
autoHide,
onChange,
@@ -114,8 +117,9 @@ export const Checkbox: CheckboxType = observer(function Checkbox({
checked={checked}
label={checkboxLabel}
className={className}
mod={mod}
ripple={ripple}
onChange={handleChange}
{...use({ mod })}
/>
);
});
@@ -8,7 +8,9 @@
import styled, { css } from 'reshadow';
import { composes, useStyles } from '@cloudbeaver/core-theming';
import { Composes, composes, useStyles } from '@cloudbeaver/core-theming';
import type { CheckboxMod } from './Checkbox';
const checkboxStyles = composes(
css`
@@ -46,7 +48,7 @@ const checkboxStyles = composes(
`
);
const checkboxMod = {
const checkboxMod: Record<CheckboxMod, Composes> = {
primary: composes(
css`
checkbox {
@@ -54,6 +56,25 @@ const checkboxMod = {
}
`
),
small: composes(
css`
checkbox {
composes: theme-checkbox_small from global;
}
`,
css`
checkbox-container {
& checkbox {
width: 14px;
height: 14px;
}
& checkbox-background {
width: 14px;
height: 14px;
}
}
`,
),
};
const checkboxState = {
@@ -76,12 +97,14 @@ const checkboxState = {
interface ICheckboxMarkupProps extends React.InputHTMLAttributes<HTMLInputElement> {
label?: string;
indeterminate?: boolean;
ripple?: boolean;
mod?: CheckboxMod[];
}
export const CheckboxMarkup: React.FC<ICheckboxMarkupProps> = function CheckboxMarkup({
label, className, title, ...rest
label, className, title, mod = ['primary'], ripple = true, ...rest
}) {
return styled(useStyles(checkboxStyles, checkboxMod.primary, rest.disabled
return styled(useStyles(checkboxStyles, ...(mod || []).map(mod => checkboxMod[mod]), rest.disabled
&& checkboxState.disabled, rest.checked && checkboxState.checked))(
<checkbox-container className={className} title={title} as='div'>
<checkbox as='div'>
@@ -92,7 +115,9 @@ export const CheckboxMarkup: React.FC<ICheckboxMarkupProps> = function CheckboxM
</checkbox-checkmark>
<checkbox-mixedmark as='div' />
</checkbox-background>
<checkbox-ripple as='div' />
{ripple && (
<checkbox-ripple as='div' />
)}
</checkbox>
{label && rest.id && <checkbox-label as='label' htmlFor={rest.id}>{label}</checkbox-label>}
</checkbox-container>
@@ -49,7 +49,7 @@ export const FieldCheckboxNew: CheckboxType = function FieldCheckboxNew({
{...rest}
checked={checkedControlled}
/>
<field-label htmlFor={rest.value || rest.name} as="label">{children}</field-label>
<field-label htmlFor={rest.value || rest.name} title={rest.title} as="label">{children}</field-label>
</field>
);
};
@@ -17,24 +17,24 @@ import { RadioGroupContext } from './RadioGroupContext';
const radioStyles = composes(
css`
radio {
composes: theme-radio from global;
}
radio-background {
composes: theme-radio_background from global;
}
input {
composes: theme-radio_native-control from global;
}
radio-outer-circle {
composes: theme-radio_outer-circle from global;
}
radio-inner-circle {
composes: theme-radio_inner-circle from global;
}
radio-ripple {
composes: theme-radio_ripple from global;
}
radio {
composes: theme-radio from global;
}
radio-background {
composes: theme-radio_background from global;
}
input {
composes: theme-radio_native-control from global;
}
radio-outer-circle {
composes: theme-radio_outer-circle from global;
}
radio-inner-circle {
composes: theme-radio_inner-circle from global;
}
radio-ripple {
composes: theme-radio_ripple from global;
}
`,
css`
field {
@@ -55,10 +55,41 @@ const radioMod = {
}
`
),
small: composes(
css`
radio {
composes: theme-radio_small from global;
}
`,
css`
field {
& radio {
width: 14px;
height: 14px;
}
& radio-background {
width: 14px;
height: 14px;
}
& radio-inner-circle {
border-width: 7px;
}
}
`
),
};
const noRippleStyles = composes(
css`
radio {
composes: theme-radio_no-ripple from global;
}
`
);
type BaseProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value' | 'checked'> & {
mod?: Array<keyof typeof radioMod>;
ripple?: boolean;
};
type ControlledProps = BaseProps & {
@@ -91,6 +122,7 @@ export const Radio: RadioType = observer(function Radio({
checked: controlledChecked,
onChange,
mod,
ripple = true,
className,
children,
...rest
@@ -131,7 +163,7 @@ export const Radio: RadioType = observer(function Radio({
checked = state[name] === value;
}
return styled(useStyles(radioStyles, ...(mod || []).map(mod => radioMod[mod])))(
return styled(useStyles(radioStyles, ...(mod || []).map(mod => radioMod[mod]), !ripple && noRippleStyles))(
<field as="div" className={className}>
<radio as="div">
<input
@@ -147,7 +179,7 @@ export const Radio: RadioType = observer(function Radio({
<radio-outer-circle as="div" />
<radio-inner-circle as="div" />
</radio-background>
<radio-ripple as="div" />
{ripple && <radio-ripple as="div" />}
</radio>
<label htmlFor={id}>{children}</label>
</field>
@@ -85,7 +85,6 @@ const RenderField: React.FC<RenderFieldProps> = observer(function RenderField({
checkboxLabel={property.displayName}
title={property.description}
disabled={disabled}
mod='surface'
/>
);
}
@@ -212,7 +212,6 @@ export const Options: TabContainerPanelComponent<IConnectionFormTabProps> = obse
disabled={edit || form.form.disabled}
readOnly={form.form.readonly}
// autoHide={} // maybe better to use autoHide
mod='surface'
>{translate('connections_connection_template')}
</FieldCheckboxNew>
)}
@@ -245,7 +244,6 @@ export const Options: TabContainerPanelComponent<IConnectionFormTabProps> = obse
value={data.config.connectionId + 'authNeeded'}
state={data.config}
disabled={form.form.disabled || form.form.readonly}
mod='surface'
gridItemMax
>{translate('connections_connection_edit_save_credentials')}
</FieldCheckboxNew>
@@ -12,7 +12,7 @@ import styled from 'reshadow';
import { css } from 'reshadow';
import { useAdministrationSettings } from '@cloudbeaver/core-administration';
import { Group, SubmittingForm, useMapResource, Button, ColoredContainer, InputFieldNew, FieldCheckboxNew, BASE_CONTAINERS_STYLES, SwitchNew, Grid, GroupItem, Container } from '@cloudbeaver/core-blocks';
import { Group, SubmittingForm, useMapResource, Button, ColoredContainer, InputFieldNew, FieldCheckboxNew, BASE_CONTAINERS_STYLES, SwitchNew, GroupItem, Container } from '@cloudbeaver/core-blocks';
import type { TabContainerPanelComponent } from '@cloudbeaver/core-blocks';
import { useTranslate } from '@cloudbeaver/core-localization';
import { useStyles } from '@cloudbeaver/core-theming';
@@ -160,7 +160,6 @@ export const SSH: TabContainerPanelComponent<IConnectionFormTabProps> = observer
value={SSH_TUNNEL_ID + ' savePassword'}
state={state}
disabled={disabled || !enabled || form.form.readonly}
mod='surface'
>{translate('connections_network_handler_ssh_tunnel_save_password')}
</FieldCheckboxNew>
)}
@@ -23,15 +23,26 @@ import {
ResourceKey,
ResourceKeyUtils,
TestConnectionMutation,
NavigatorSettingsInput,
} from '@cloudbeaver/core-sdk';
import { ConnectionsResource, DatabaseConnection } from './Administration/ConnectionsResource';
import { CONNECTION_NAVIGATOR_VIEW_SETTINGS } from './ConnectionNavigatorViewSettings';
import type { NavigatorViewSettings } from './ConnectionNavigatorViewSettings';
export type Connection = DatabaseConnection & { authProperties?: UserConnectionAuthPropertiesFragment[] };
export type ConnectionInitConfig = Omit<InitConnectionMutationVariables, 'includeOrigin' | 'customIncludeOriginDetails' | 'includeAuthProperties' | 'customIncludeNetworkHandlerCredentials'>;
export type ConnectionInfoIncludes = Omit<GetUserConnectionsQueryVariables, 'id'>;
export const DEFAULT_NAVIGATOR_VIEW_SETTINGS: NavigatorSettingsInput = {
showOnlyEntities: false,
hideFolders: false,
hideVirtualModel: false,
hideSchemas: false,
mergeEntities: false,
showSystemObjects: false,
showUtilityObjects: false,
};
@injectable()
export class ConnectionInfoResource extends CachedMapResource<string, Connection, ConnectionInfoIncludes> {
readonly onConnectionCreate: IExecutor<Connection>;
@@ -170,13 +181,12 @@ export class ConnectionInfoResource extends CachedMapResource<string, Connection
return this.get(config.id)!;
}
async changeConnectionView(id: string, simple: boolean): Promise<Connection> {
async changeConnectionView(id: string, settings: NavigatorViewSettings): Promise<Connection> {
await this.performUpdate(id, [], async () => {
const settings = simple ? CONNECTION_NAVIGATOR_VIEW_SETTINGS.simple : CONNECTION_NAVIGATOR_VIEW_SETTINGS.advanced;
const connectionNavigatorViewSettings = this.get(id)?.navigatorSettings || DEFAULT_NAVIGATOR_VIEW_SETTINGS;
const { connection } = await this.graphQLService.sdk.setConnectionNavigatorSettings({
id,
settings,
settings: { ...connectionNavigatorViewSettings, ...settings },
...this.getDefaultIncludes(),
...this.getIncludesMap(id),
});
@@ -8,16 +8,16 @@
import type { NavigatorSettingsInput } from '@cloudbeaver/core-sdk';
type NavigatorView = 'simple' | 'advanced';
export type NavigatorView = 'simple' | 'advanced';
export type NavigatorViewSettings = Partial<NavigatorSettingsInput>;
export const CONNECTION_NAVIGATOR_VIEW_SETTINGS: Record<NavigatorView, NavigatorSettingsInput> = {
export const CONNECTION_NAVIGATOR_VIEW_SETTINGS: Record<NavigatorView, NavigatorViewSettings> = {
simple: {
showOnlyEntities: true,
hideFolders: true,
hideVirtualModel: true,
hideSchemas: false,
mergeEntities: false,
showSystemObjects: false,
showUtilityObjects: false,
},
advanced: {
@@ -26,13 +26,13 @@ export const CONNECTION_NAVIGATOR_VIEW_SETTINGS: Record<NavigatorView, Navigator
hideVirtualModel: false,
hideSchemas: false,
mergeEntities: false,
showSystemObjects: false,
showUtilityObjects: false,
},
};
export function isSimpleNavigatorView(settings: NavigatorSettingsInput): boolean {
const simple = CONNECTION_NAVIGATOR_VIEW_SETTINGS.simple;
return !Object.keys(simple)
.some(key => settings[key as keyof NavigatorSettingsInput] !== simple[key as keyof NavigatorSettingsInput]);
export function isNavigatorViewSettingsEqual(
settings: NavigatorSettingsInput, settingsToCompare: NavigatorViewSettings
): boolean {
return !(Object.keys(settingsToCompare) as Array<keyof NavigatorSettingsInput>)
.some(key => settings[key] !== settingsToCompare[key]);
}
@@ -127,7 +127,6 @@ export const DatabaseAuthDialog = observer(function DatabaseAuthDialog({
checkboxLabel={translate('connections_connection_edit_save_credentials')}
disabled={controller.isAuthenticating}
state={controller.config}
mod='surface'
/>
</FormGroup>
)}
@@ -81,7 +81,6 @@ export const SSHAuthForm: React.FC<Props> = observer(function SSHAuthForm({
state={state}
checkboxLabel={translate('connections_network_handler_ssh_tunnel_save_password')}
disabled={disabled}
mod='surface'
/>
</FormGroup>
)}
@@ -11,7 +11,7 @@ import { uuid } from '@cloudbeaver/core-utils';
import type { IMenuPanel } from '../IMenuPanel';
import { ComputedMenuItemModel, IComputedMenuItemOptions } from '../models/ComputedMenuItemModel';
import { MenuOptionsStore } from '../models/MenuOptionsStore';
import { MenuItemType, MenuOptionsStore } from '../models/MenuOptionsStore';
import type { IContextMenuItem } from './IContextMenuItem';
import type { IMenuContext } from './IMenuContext';
@@ -89,6 +89,9 @@ class ComputedMenuItemOptionsWithContext<T> implements IComputedMenuItemOptions
isHidden?: () => boolean;
// set icon or getter
icon?: string;
isChecked?: () => boolean;
type?: MenuItemType;
separator?: boolean;
iconGetter?: () => string | undefined;
constructor(private options: IContextMenuItem<T>,
@@ -97,6 +100,8 @@ class ComputedMenuItemOptionsWithContext<T> implements IComputedMenuItemOptions
this.title = options.title;
this.titleGetter = options.titleGetter;
this.icon = options.icon;
this.type = options.type;
this.separator = options.separator;
this.iconGetter = options.iconGetter;
this.id = `${options.id}-${context.contextId!}`;
@@ -110,6 +115,9 @@ class ComputedMenuItemOptionsWithContext<T> implements IComputedMenuItemOptions
if (options.isHidden) {
this.isHidden = () => options.isHidden!(this.context);
}
if (options.isChecked) {
this.isChecked = () => options.isChecked!(this.context);
}
}
}
@@ -19,4 +19,5 @@ export interface IContextMenuItem<T> extends IMenuItemOptions {
isDisabled?: (context: IMenuContext<T>) => boolean;
// When the item is present in menu it can be hidden based on certain conditions
isHidden?: (context: IMenuContext<T>) => boolean;
isChecked?: (context: IMenuContext<T>) => boolean;
}
@@ -12,6 +12,8 @@ import type { MenuInitialState } from 'reakit/Menu';
import type { TLocalizationToken } from '@cloudbeaver/core-localization';
import type { Style } from '@cloudbeaver/core-theming';
import type { MenuItemType } from './models/MenuOptionsStore';
export type MenuMod = 'primary' | 'surface' | 'secondary';
export interface IMenuPanel {
@@ -28,6 +30,9 @@ export interface IMenuItem {
isHidden?: boolean;
icon?: string; // path to icon or svg icon name
panel?: IMenuPanel; // if menu has sub-items
type?: MenuItemType;
separator?: boolean;
isChecked?: boolean;
}
export type MenuTriggerProps = PropsWithChildren<{
@@ -9,10 +9,11 @@
import { useObserver } from 'mobx-react-lite';
import styled from 'reshadow';
import { Icon } from '@cloudbeaver/core-blocks';
import { Checkbox, Icon, IconOrImage, Radio } from '@cloudbeaver/core-blocks';
import type { IMenuItem } from '@cloudbeaver/core-dialogs';
import { Translate } from '@cloudbeaver/core-localization';
import { useStyles, Style } from '@cloudbeaver/core-theming';
import { use } from '@reshadow/react';
import { menuPanelStyles } from './menuPanelStyles';
@@ -25,17 +26,37 @@ export const MenuPanelItem: React.FC<MenuPanelItemProps> = function MenuPanelIte
menuItem,
style = [],
}) {
const { title, panel } = useObserver(() => ({ // TODO: provide title and panel via props
const { title, panel, icon, isChecked, controlType, separator } = useObserver(() => ({ // TODO: provide title and panel via props
title: menuItem.title,
panel: menuItem.panel,
icon: menuItem.icon,
isChecked: menuItem.isChecked,
controlType: menuItem.type,
separator: menuItem.separator,
}));
let control = null;
if (controlType === 'radio') {
control = <Radio checked={isChecked} mod={['primary', 'small']} ripple={false} />;
} else if (controlType === 'checkbox') {
control = <Checkbox checked={isChecked} mod={['primary', 'small']} ripple={false} />;
}
return styled(useStyles(menuPanelStyles, ...style))(
<menu-panel-item as="div">
<menu-item-text as="span">
<menu-panel-item as="div" {...use({ separator })}>
<menu-item-content as='div'>
{icon ? (
<IconOrImage icon={icon} />
) : control}
</menu-item-content>
<menu-item-text as="div">
<Translate token={title} />
</menu-item-text>
{panel && <Icon name="arrow" viewBox="0 0 16 16" />}
<menu-item-content as='div'>
{panel && <Icon name="arrow" viewBox="0 0 16 16" />}
</menu-item-content>
</menu-panel-item>
);
};
@@ -12,7 +12,7 @@ import {
} from 'react';
import {
MenuButton,
Menu, MenuItem, MenuStateReturn, useMenuState
Menu, MenuItem, MenuStateReturn, useMenuState, MenuItemCheckbox, MenuItemRadio
} from 'reakit/Menu';
import styled, { use } from 'reshadow';
@@ -117,6 +117,42 @@ const MenuPanelElement = observer(function MenuPanelElement({
);
}
if (item.type === 'radio') {
return styled(styles)(
<MenuItemRadio
{...menu}
{...use({ hidden: item.isHidden })}
aria-label={item.id}
disabled={item.isDisabled}
name={item.id}
value={item.title}
checked={item.isChecked}
onClick={onClick}
>
<MenuPanelItem menuItem={item} style={style} />
</MenuItemRadio>
);
}
if (item.type === 'checkbox') {
return styled(styles)(
<MenuItemCheckbox
{...menu}
{...use({ hidden: item.isHidden })}
aria-label={item.id}
disabled={item.isDisabled}
name={item.id}
value={item.title}
checked={item.isChecked}
onClick={onClick}
>
<MenuPanelItem menuItem={item} style={style} />
</MenuItemCheckbox>
);
}
return styled(styles)(
<MenuItem
{...menu}
@@ -16,12 +16,17 @@ export const menuPanelStyles = composes(
composes: theme-background-surface theme-text-on-surface from global;
}
MenuItem,
MenuItemCheckbox,
MenuItemRadio,
MenuButton {
composes: theme-ripple from global;
}
MenuItemElement {
composes: theme-ripple from global;
}
menu-panel-item {
composes: theme-border-color-background from global;
}
`,
css`
MenuButton {
@@ -30,8 +35,6 @@ export const menuPanelStyles = composes(
outline: none !important;
color: inherit;
cursor: pointer;
padding: 12px 0;
& box {
display: flex;
align-items: center;
@@ -52,11 +55,12 @@ export const menuPanelStyles = composes(
margin-top: -12px;
}
}
MenuItem {
MenuItem, MenuItemCheckbox, MenuItemRadio {
display: flex;
align-items: center;
border: none;
padding: 0;
background: none;
padding: 6px 16px;
text-align: left;
outline: none;
color: inherit;
@@ -69,26 +73,42 @@ export const menuPanelStyles = composes(
&:hover, &:global([aria-expanded="true"]) {
font-weight: 600;
& Icon {
opacity: 1;
}
}
}
menu-panel-item {
flex: 1;
display: flex;
align-items: center;
height: 30px;
padding: 0 4px;
&[|separator] {
border-bottom: 1px solid;
}
& menu-item-text {
display: block;
padding: 0 4px;
flex: 1;
}
& menu-item-content {
width: 24px;
height: 24px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
& Icon {
width: 16px;
height: 16px;
opacity: 0.5;
padding-left: 16px;
}
& IconOrImage {
width: 14px;
height: 14px;
}
}
`
@@ -11,18 +11,21 @@ import { computed, makeObservable } from 'mobx';
import type { IMenuItem, IMenuPanel } from '@cloudbeaver/core-dialogs';
import type { TLocalizationToken } from '@cloudbeaver/core-localization';
import type { IMenuItemOptions } from './MenuOptionsStore';
import type { IMenuItemOptions, MenuItemType } from './MenuOptionsStore';
export interface IComputedMenuItemOptions extends IMenuItemOptions {
onClick?: () => void;
isDisabled?: () => boolean;
isHidden?: () => boolean;
isChecked?: () => boolean;
}
export class ComputedMenuItemModel implements IMenuItem {
id: string;
onClick?: () => void;
panel?: IMenuPanel;
type?: MenuItemType;
separator?: boolean;
rtl?: boolean;
get title(): TLocalizationToken {
@@ -47,15 +50,22 @@ export class ComputedMenuItemModel implements IMenuItem {
return this.options.isHidden ? this.options.isHidden() : false;
}
get isChecked() {
return this.options.isChecked ? this.options.isChecked() : false;
}
constructor(private options: IComputedMenuItemOptions) {
makeObservable(this, {
title: computed,
isDisabled: computed,
icon: computed,
isHidden: computed,
isChecked: computed,
});
this.id = options.id;
this.type = options.type;
this.separator = options.separator;
this.rtl = options.rtl;
this.panel = options.panel;
this.onClick = this.options.onClick;
@@ -13,6 +13,7 @@ import type { IMenuPanel } from '../IMenuPanel';
const DEFAULT_ITEM_ORDER = 100;
export type MenuItemType = 'checkbox' | 'radio';
export interface IMenuItemOptions {
id: string;
// set title or getter
@@ -24,6 +25,8 @@ export interface IMenuItemOptions {
order?: number;
isPanel?: boolean;
panel?: IMenuPanel;
type?: MenuItemType;
separator?: boolean;
rtl?: boolean;
}
@@ -6,44 +6,53 @@
* you may not use this file except in compliance with the License.
*/
@import '@material/checkbox/mixins';
$mdc-checkbox-icon-size: 16px;
@import "@material/checkbox/mixins";
$mdc-checkbox-icon-size: 16px;
@mixin checkbox-core-styles($query: mdc-feature-all()) {
@include mdc-checkbox-core-styles($query);
.theme-checkbox {
composes: mdc-checkbox;
}
.theme-checkbox_native-control {
composes: mdc-checkbox__native-control;
}
.theme-checkbox__background {
composes: mdc-checkbox__background;
}
.theme-checkbox__checkmark {
composes: mdc-checkbox__checkmark;
}
.theme-checkbox__checkmark-path {
composes: mdc-checkbox__checkmark-path;
}
.theme-checkbox__mixedmark {
@mixin checkbox-core-styles($query: mdc-feature-all()) {
@include mdc-checkbox-core-styles($query);
.theme-checkbox {
composes: mdc-checkbox;
}
.theme-checkbox_native-control {
composes: mdc-checkbox__native-control;
}
.theme-checkbox__background {
composes: mdc-checkbox__background;
}
.theme-checkbox__checkmark {
composes: mdc-checkbox__checkmark;
}
.theme-checkbox__checkmark-path {
composes: mdc-checkbox__checkmark-path;
}
.theme-checkbox__mixedmark {
composes: mdc-checkbox__mixedmark;
}
.theme-checkbox__ripple {
}
.theme-checkbox__ripple {
composes: mdc-checkbox__ripple;
}
.theme-checkbox--checked {
composes: mdc-checkbox--selected;
}
.theme-checkbox--disabled {
composes: mdc-checkbox--disabled;
}
.theme-checkbox_primary {
$mdc-checkbox-mark-color: $mdc-theme-on-primary;
@include mdc-checkbox-ink-color($mdc-checkbox-mark-color, $query);
@include mdc-checkbox-disabled-ink-color(rgba($mdc-checkbox-mark-color, .50), $query);
@include mdc-checkbox-container-colors($mdc-checkbox-border-color, transparent, $mdc-theme-primary, $mdc-theme-primary )
}
}
}
.theme-checkbox--checked {
composes: mdc-checkbox--selected;
}
.theme-checkbox--disabled {
composes: mdc-checkbox--disabled;
}
.theme-checkbox_primary {
$mdc-checkbox-mark-color: $mdc-theme-on-primary;
@include mdc-checkbox-ink-color($mdc-checkbox-mark-color, $query);
@include mdc-checkbox-disabled-ink-color(rgba($mdc-checkbox-mark-color, 0.5), $query);
@include mdc-checkbox-container-colors(
$mdc-checkbox-border-color,
transparent,
$mdc-theme-primary,
$mdc-theme-primary
);
}
.theme-checkbox_small {
@include mdc-checkbox-density(-3);
}
}
@@ -52,7 +52,7 @@
}
.theme-border-color-#{$style} {
@include mdc-feature-targets($feat-color) {
@include mdc-theme-prop(border-color, $style);
@include mdc-theme-prop(border-color, $style, true);
}
}
}
@@ -6,11 +6,11 @@
* you may not use this file except in compliance with the License.
*/
@import '@material/radio/mixins';
@import "@material/radio/mixins";
@mixin radio-core-styles($query: mdc-feature-all()) {
@include mdc-radio-core-styles;
.theme-radio {
composes: mdc-radio;
}
@@ -32,8 +32,8 @@
.theme-radio_primary {
$mdc-radio-baseline-theme-color: primary;
$mdc-radio-unchecked-color: rgba(mdc-theme-prop-value(on-surface), .54);
$mdc-radio-disabled-circle-color: rgba(mdc-theme-prop-value(on-surface), .26);
$mdc-radio-unchecked-color: rgba(mdc-theme-prop-value(on-surface), 0.54);
$mdc-radio-disabled-circle-color: rgba(mdc-theme-prop-value(on-surface), 0.26);
@include mdc-radio-unchecked-stroke-color($mdc-radio-unchecked-color, $query: $query);
@include mdc-radio-checked-stroke-color($mdc-radio-baseline-theme-color, $query: $query);
@@ -43,4 +43,12 @@
@include mdc-radio-disabled-ink-color($mdc-radio-disabled-circle-color, $query: $query);
@include mdc-radio-focus-indicator-color($mdc-radio-baseline-theme-color, $query: $query);
}
}
.theme-radio_small {
@include mdc-radio-density(-3);
}
.theme-radio_no-ripple {
@include mdc-radio-ripple-size(0);
}
}
@@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite';
import React, { useCallback } from 'react';
import { FormGroup, Switch } from '@cloudbeaver/core-blocks';
import { isSimpleNavigatorView, CONNECTION_NAVIGATOR_VIEW_SETTINGS } from '@cloudbeaver/core-connections';
import { CONNECTION_NAVIGATOR_VIEW_SETTINGS, isNavigatorViewSettingsEqual } from '@cloudbeaver/core-connections';
import { useTranslate } from '@cloudbeaver/core-localization';
import type { IServerConfigurationPageState } from '../IServerConfigurationPageState';
@@ -24,13 +24,13 @@ export const ServerConfigurationNavigatorViewForm: React.FC<Props> = observer(fu
}) {
const translate = useTranslate();
const isSimpleView = isSimpleNavigatorView(configs.navigatorConfig);
const isSimpleView = isNavigatorViewSettingsEqual(configs.navigatorConfig, CONNECTION_NAVIGATOR_VIEW_SETTINGS.simple);
const onNavigatorViewChangeHandler = useCallback((value: boolean) => {
if (value) {
configs.navigatorConfig = { ...CONNECTION_NAVIGATOR_VIEW_SETTINGS.simple };
configs.navigatorConfig = { ...configs.navigatorConfig, ...CONNECTION_NAVIGATOR_VIEW_SETTINGS.simple };
} else {
configs.navigatorConfig = { ...CONNECTION_NAVIGATOR_VIEW_SETTINGS.advanced };
configs.navigatorConfig = { ...configs.navigatorConfig, ...CONNECTION_NAVIGATOR_VIEW_SETTINGS.advanced };
}
}, [configs]);
@@ -10,7 +10,7 @@ import { observable, makeObservable } from 'mobx';
import { AdministrationScreenService } from '@cloudbeaver/core-administration';
import { UsersResource } from '@cloudbeaver/core-authentication';
import { CONNECTION_NAVIGATOR_VIEW_SETTINGS } from '@cloudbeaver/core-connections';
import { DEFAULT_NAVIGATOR_VIEW_SETTINGS } from '@cloudbeaver/core-connections';
import { injectable } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { IExecutor, Executor } from '@cloudbeaver/core-executor';
@@ -152,7 +152,7 @@ export class ServerConfigurationService {
customConnectionsEnabled: true,
sessionExpireTime: 30,
},
navigatorConfig: { ...CONNECTION_NAVIGATOR_VIEW_SETTINGS.advanced },
navigatorConfig: { ...DEFAULT_NAVIGATOR_VIEW_SETTINGS },
};
}
@@ -103,7 +103,6 @@ export const UserInfo: TabContainerPanelComponent<IUserFormProps> = observer(fun
checkboxLabel={role.roleName || role.roleId}
checked={!!controller.credentials.roles.get(role.roleId)}
disabled={controller.isSaving}
mod='surface'
onChange={checked => handleRoleChange(role.roleId, checked)}
/>
</FormGroup>
@@ -115,7 +115,6 @@ export const ConnectionDialog = observer(function ConnectionDialog({
checkboxLabel={translate('connections_connection_edit_save_credentials')}
disabled={controller.isConnecting}
state={controller.config}
mod='surface'
/>
</FormGroup>
)}