From 87e06ef5a8242830fc2a8f4e9a0497b60c7e2efb Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Tue, 9 Mar 2021 01:15:40 +0300 Subject: [PATCH 1/3] feat(core-dialogs): CB-747 add radio and checkbox to Menu component --- .../NavigationTreeContextMenuService.ts | 71 ++++++++++++++++--- webapp/packages/core-app/src/locales/en.ts | 2 + webapp/packages/core-app/src/locales/ru.ts | 2 + .../src/ConnectionInfoResource.ts | 6 +- .../src/ConnectionNavigatorViewSettings.ts | 41 +++++++++-- .../src/Menu/ContextMenu/ContextMenu.ts | 8 ++- .../src/Menu/ContextMenu/IContextMenuItem.ts | 1 + .../core-dialogs/src/Menu/IMenuPanel.ts | 4 ++ .../src/Menu/MenuPanel/MenuTrigger.tsx | 36 +++++++++- .../src/Menu/MenuPanel/menuPanelStyles.ts | 25 ++++++- .../src/Menu/models/ComputedMenuItemModel.ts | 10 ++- .../src/Menu/models/MenuOptionsStore.ts | 2 + .../ServerConfigurationNavigatorViewForm.tsx | 4 +- 13 files changed, 186 insertions(+), 26 deletions(-) diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts b/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts index 1357a6ecd3..312064af8b 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts @@ -6,10 +6,11 @@ * 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, getNavigatorView, isNavigatorSettingEnabled, NavigatorViewSettingsKeys } from '@cloudbeaver/core-connections'; import { injectable } from '@cloudbeaver/core-di'; import { ContextMenuService, IMenuPanel } from '@cloudbeaver/core-dialogs'; import { NotificationService } from '@cloudbeaver/core-events'; +import type { NavigatorSettingsInput } from '@cloudbeaver/core-sdk'; import type { NavNode } from '../shared/NodesManager/EntityTypes'; import { EObjectFeature } from '../shared/NodesManager/EObjectFeature'; @@ -46,23 +47,43 @@ export class NavigationTreeContextMenuService { }); } - private isConnectionSimpleView(nodeId: string): boolean { + private getConnectionViewSettings(nodeId: string) { const connectionId = NodeManagerUtils.connectionNodeIdToConnectionId(nodeId); const connection = this.connectionInfoResource.get(connectionId); if (!connection) { + return null; + } + + return connection.navigatorSettings; + } + + private getConnectionNavigatorView(nodeId: string) { + const connectionViewSettings = this.getConnectionViewSettings(nodeId); + + if (!connectionViewSettings) { + return null; + } + + return getNavigatorView(connectionViewSettings); + } + + private isConnectionViewSettingEnabled(nodeId: string, setting: NavigatorViewSettingsKeys) { + const connectionViewSettings = this.getConnectionViewSettings(nodeId); + + if (!connectionViewSettings) { return false; } - return isSimpleNavigatorView(connection.navigatorSettings); + return isNavigatorSettingEnabled(setting, connectionViewSettings); } - private async changeConnectionView(nodeId: string, simple: boolean) { + private async changeConnectionView(nodeId: string, settings: NavigatorSettingsInput) { 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 +109,17 @@ export class NavigationTreeContextMenuService { { id: 'simple', title: 'app_navigationTree_connection_view_option_simple', - isDisabled: context => this.isConnectionSimpleView(context.data.id), + type: 'radio', + isChecked: context => this.getConnectionNavigatorView(context.data.id) === 'simple', 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, + showSystemObjects: this.isConnectionViewSettingEnabled(context.data.id, 'showSystemObjects'), + }), } ); this.contextMenuService.addMenuItem( @@ -101,12 +127,39 @@ export class NavigationTreeContextMenuService { { id: 'advanced', title: 'app_navigationTree_connection_view_option_advanced', - isDisabled: context => !this.isConnectionSimpleView(context.data.id), + type: 'radio', + isChecked: context => this.getConnectionNavigatorView(context.data.id) === 'advanced', 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, + showSystemObjects: this.isConnectionViewSettingEnabled(context.data.id, 'showSystemObjects'), + }), + } + ); + this.contextMenuService.addMenuItem( + this.getNodeViewMenuItemToken(), + { + id: 'systemObjects', + title: 'app_navigationTree_connection_view_option_showSystemObjects', + isChecked: context => this.isConnectionViewSettingEnabled(context.data.id, 'showSystemObjects'), + isPresent(context) { + return context.contextType === NavigationTreeContextMenuService.nodeContextType + && context.data.objectFeatures.includes(EObjectFeature.dataSource); + }, + onClick: async context => { + const currentSettings = this.getConnectionViewSettings(context.data.id); + if (!currentSettings) { + return; + } + return await this.changeConnectionView(context.data.id, { + ...currentSettings, + showSystemObjects: !currentSettings['showSystemObjects'], + }); + }, } ); } diff --git a/webapp/packages/core-app/src/locales/en.ts b/webapp/packages/core-app/src/locales/en.ts index 35a9f0eb99..52c35f6a9c 100644 --- a/webapp/packages/core-app/src/locales/en.ts +++ b/webapp/packages/core-app/src/locales/en.ts @@ -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'], diff --git a/webapp/packages/core-app/src/locales/ru.ts b/webapp/packages/core-app/src/locales/ru.ts index 9ab94a9074..ea49c50235 100644 --- a/webapp/packages/core-app/src/locales/ru.ts +++ b/webapp/packages/core-app/src/locales/ru.ts @@ -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', 'Редактирование'], diff --git a/webapp/packages/core-connections/src/ConnectionInfoResource.ts b/webapp/packages/core-connections/src/ConnectionInfoResource.ts index 4da8b2fb2b..b73565ca68 100644 --- a/webapp/packages/core-connections/src/ConnectionInfoResource.ts +++ b/webapp/packages/core-connections/src/ConnectionInfoResource.ts @@ -22,10 +22,10 @@ import { GetUserConnectionsQueryVariables, ResourceKey, ResourceKeyUtils, + NavigatorSettingsInput, } from '@cloudbeaver/core-sdk'; import { ConnectionsResource, DatabaseConnection } from './Administration/ConnectionsResource'; -import { CONNECTION_NAVIGATOR_VIEW_SETTINGS } from './ConnectionNavigatorViewSettings'; export type Connection = DatabaseConnection & { authProperties?: UserConnectionAuthPropertiesFragment[] }; export type ConnectionInitConfig = Omit; @@ -167,10 +167,8 @@ export class ConnectionInfoResource extends CachedMapResource { + async changeConnectionView(id: string, settings: NavigatorSettingsInput): Promise { await this.performUpdate(id, [], async () => { - const settings = simple ? CONNECTION_NAVIGATOR_VIEW_SETTINGS.simple : CONNECTION_NAVIGATOR_VIEW_SETTINGS.advanced; - const { connection } = await this.graphQLService.sdk.setConnectionNavigatorSettings({ id, settings, diff --git a/webapp/packages/core-connections/src/ConnectionNavigatorViewSettings.ts b/webapp/packages/core-connections/src/ConnectionNavigatorViewSettings.ts index d3e66ec0c0..dd5a54e0c7 100644 --- a/webapp/packages/core-connections/src/ConnectionNavigatorViewSettings.ts +++ b/webapp/packages/core-connections/src/ConnectionNavigatorViewSettings.ts @@ -8,9 +8,12 @@ import type { NavigatorSettingsInput } from '@cloudbeaver/core-sdk'; -type NavigatorView = 'simple' | 'advanced'; +export type NavigatorView = 'simple' | 'advanced' | 'custom'; +export type NavigatorViewSettingsKeys = keyof NavigatorSettingsInput; -export const CONNECTION_NAVIGATOR_VIEW_SETTINGS: Record = { +type NavigatorViewSettings = Record, NavigatorSettingsInput>; + +export const CONNECTION_NAVIGATOR_VIEW_SETTINGS: NavigatorViewSettings = { simple: { showOnlyEntities: true, hideFolders: true, @@ -31,8 +34,34 @@ export const CONNECTION_NAVIGATOR_VIEW_SETTINGS: Record settings[key as keyof NavigatorSettingsInput] !== simple[key as keyof NavigatorSettingsInput]); +export function getNavigatorView(settings: NavigatorSettingsInput): NavigatorView { + const isSimple = isNavigatorViewSettingsEqual(settings, CONNECTION_NAVIGATOR_VIEW_SETTINGS.simple); + const isAdvanced = isNavigatorViewSettingsEqual(settings, CONNECTION_NAVIGATOR_VIEW_SETTINGS.advanced); + + if (isSimple) { + return 'simple'; + } + + if (isAdvanced) { + return 'advanced'; + } + + return 'custom'; +} + +export function isNavigatorSettingEnabled( + setting: NavigatorViewSettingsKeys, settings: NavigatorSettingsInput +): boolean { + return settings[setting]; +} + +function isNavigatorViewSettingsEqual(settings: NavigatorSettingsInput, settingsToCompare: NavigatorSettingsInput) { + return !(Object.keys(settingsToCompare) as NavigatorViewSettingsKeys[]) + .some(key => { + // we need to exclude it for now, cause we haven't implement custom navigator view logic yet + if (key === 'showSystemObjects') { + return false; + } + return settings[key] !== settingsToCompare[key]; + }); } diff --git a/webapp/packages/core-dialogs/src/Menu/ContextMenu/ContextMenu.ts b/webapp/packages/core-dialogs/src/Menu/ContextMenu/ContextMenu.ts index cf30d8e1ec..2911161a49 100644 --- a/webapp/packages/core-dialogs/src/Menu/ContextMenu/ContextMenu.ts +++ b/webapp/packages/core-dialogs/src/Menu/ContextMenu/ContextMenu.ts @@ -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 { MenuItemControlType, MenuOptionsStore } from '../models/MenuOptionsStore'; import type { IContextMenuItem } from './IContextMenuItem'; import type { IMenuContext } from './IMenuContext'; @@ -89,6 +89,8 @@ class ComputedMenuItemOptionsWithContext implements IComputedMenuItemOptions isHidden?: () => boolean; // set icon or getter icon?: string; + isChecked?: () => boolean; + type?: MenuItemControlType; iconGetter?: () => string | undefined; constructor(private options: IContextMenuItem, @@ -97,6 +99,7 @@ class ComputedMenuItemOptionsWithContext implements IComputedMenuItemOptions this.title = options.title; this.titleGetter = options.titleGetter; this.icon = options.icon; + this.type = options.type; this.iconGetter = options.iconGetter; this.id = `${options.id}-${context.contextId!}`; @@ -110,6 +113,9 @@ class ComputedMenuItemOptionsWithContext implements IComputedMenuItemOptions if (options.isHidden) { this.isHidden = () => options.isHidden!(this.context); } + if (options.isChecked) { + this.isChecked = () => options.isChecked!(this.context); + } } } diff --git a/webapp/packages/core-dialogs/src/Menu/ContextMenu/IContextMenuItem.ts b/webapp/packages/core-dialogs/src/Menu/ContextMenu/IContextMenuItem.ts index ae11b53f56..88822b08d3 100644 --- a/webapp/packages/core-dialogs/src/Menu/ContextMenu/IContextMenuItem.ts +++ b/webapp/packages/core-dialogs/src/Menu/ContextMenu/IContextMenuItem.ts @@ -19,4 +19,5 @@ export interface IContextMenuItem extends IMenuItemOptions { isDisabled?: (context: IMenuContext) => boolean; // When the item is present in menu it can be hidden based on certain conditions isHidden?: (context: IMenuContext) => boolean; + isChecked?: (context: IMenuContext) => boolean; } diff --git a/webapp/packages/core-dialogs/src/Menu/IMenuPanel.ts b/webapp/packages/core-dialogs/src/Menu/IMenuPanel.ts index 6e8f40db3e..f0f054be6b 100644 --- a/webapp/packages/core-dialogs/src/Menu/IMenuPanel.ts +++ b/webapp/packages/core-dialogs/src/Menu/IMenuPanel.ts @@ -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 { MenuItemControlType } from './models/MenuOptionsStore'; + export type MenuMod = 'primary' | 'surface' | 'secondary'; export interface IMenuPanel { @@ -28,6 +30,8 @@ export interface IMenuItem { isHidden?: boolean; icon?: string; // path to icon or svg icon name panel?: IMenuPanel; // if menu has sub-items + type?: MenuItemControlType; + isChecked?: boolean; } export type MenuTriggerProps = PropsWithChildren<{ diff --git a/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuTrigger.tsx b/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuTrigger.tsx index 658d440e30..23aafd1b11 100644 --- a/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuTrigger.tsx +++ b/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuTrigger.tsx @@ -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,40 @@ const MenuPanelElement = observer(function MenuPanelElement({ ); } + if (item.isChecked !== undefined) { + if (item.type === 'radio') { + return styled(styles)( + + + + + ); + } + return styled(styles)( + + + + ); + } + return styled(styles)( void; isDisabled?: () => boolean; isHidden?: () => boolean; + isChecked?: () => boolean; } export class ComputedMenuItemModel implements IMenuItem { id: string; onClick?: () => void; panel?: IMenuPanel; + type?: MenuItemControlType; rtl?: boolean; get title(): TLocalizationToken { @@ -47,15 +49,21 @@ export class ComputedMenuItemModel implements IMenuItem { return this.options.isHidden ? this.options.isHidden() : false; } + get isChecked() { + return this.options.isChecked ? this.options.isChecked() : undefined; + } + 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.rtl = options.rtl; this.panel = options.panel; this.onClick = this.options.onClick; diff --git a/webapp/packages/core-dialogs/src/Menu/models/MenuOptionsStore.ts b/webapp/packages/core-dialogs/src/Menu/models/MenuOptionsStore.ts index e538733c49..c09bc08a3a 100644 --- a/webapp/packages/core-dialogs/src/Menu/models/MenuOptionsStore.ts +++ b/webapp/packages/core-dialogs/src/Menu/models/MenuOptionsStore.ts @@ -13,6 +13,7 @@ import type { IMenuPanel } from '../IMenuPanel'; const DEFAULT_ITEM_ORDER = 100; +export type MenuItemControlType = 'checkbox' | 'radio'; export interface IMenuItemOptions { id: string; // set title or getter @@ -24,6 +25,7 @@ export interface IMenuItemOptions { order?: number; isPanel?: boolean; panel?: IMenuPanel; + type?: MenuItemControlType; rtl?: boolean; } diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationNavigatorViewForm.tsx b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationNavigatorViewForm.tsx index c9c4b89ed8..f91780f6cf 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationNavigatorViewForm.tsx +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationNavigatorViewForm.tsx @@ -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, getNavigatorView } from '@cloudbeaver/core-connections'; import { useTranslate } from '@cloudbeaver/core-localization'; import type { IServerConfigurationPageState } from '../IServerConfigurationPageState'; @@ -24,7 +24,7 @@ export const ServerConfigurationNavigatorViewForm: React.FC = observer(fu }) { const translate = useTranslate(); - const isSimpleView = isSimpleNavigatorView(configs.navigatorConfig); + const isSimpleView = getNavigatorView(configs.navigatorConfig) === 'simple'; const onNavigatorViewChangeHandler = useCallback((value: boolean) => { if (value) { From c185fc06795513a567852d39a244bb1b67d2284d Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Fri, 12 Mar 2021 14:50:57 +0300 Subject: [PATCH 2/3] feat(core-dialogs): CB-747 changed font controls icons to custom components --- .../NavigationTreeContextMenuService.ts | 60 +++++-------- webapp/packages/core-app/src/locales/ru.ts | 2 +- .../src/FormControls/Checkboxes/Checkbox.tsx | 12 ++- .../Checkboxes/CheckboxMarkup.tsx | 36 ++++++-- .../Checkboxes/FieldCheckboxNew.tsx | 2 +- .../core-blocks/src/FormControls/Radio.tsx | 24 ++++++ .../ObjectPropertyInfoForm.tsx | 1 - .../src/ConnectionForm/Options/Options.tsx | 2 - .../src/ConnectionForm/SSH/SSH.tsx | 3 +- .../src/ConnectionInfoResource.ts | 16 +++- .../src/ConnectionNavigatorViewSettings.ts | 43 ++-------- .../DatabaseAuthDialog/DatabaseAuthDialog.tsx | 1 - .../src/DatabaseAuthDialog/SSHAuthForm.tsx | 1 - .../src/Menu/ContextMenu/ContextMenu.ts | 6 +- .../core-dialogs/src/Menu/IMenuPanel.ts | 5 +- .../src/Menu/MenuPanel/MenuPanelItem.tsx | 32 +++++-- .../src/Menu/MenuPanel/MenuTrigger.tsx | 36 ++++---- .../src/Menu/MenuPanel/menuPanelStyles.ts | 52 ++++++------ .../src/Menu/models/ComputedMenuItemModel.ts | 8 +- .../src/Menu/models/MenuOptionsStore.ts | 5 +- .../core-theming/src/styles/_checkbox.scss | 85 ++++++++++--------- .../core-theming/src/styles/_color.scss | 2 +- .../core-theming/src/styles/_radio.scss | 15 ++-- .../ServerConfigurationNavigatorViewForm.tsx | 8 +- .../ServerConfigurationService.ts | 4 +- .../Users/UserForm/UserInfo.tsx | 1 - .../src/ConnectionDialog/ConnectionDialog.tsx | 1 - 27 files changed, 258 insertions(+), 205 deletions(-) diff --git a/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts b/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts index 312064af8b..ade2a43743 100644 --- a/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts +++ b/webapp/packages/core-app/src/NavigationTree/NavigationTreeContextMenuService.ts @@ -6,11 +6,10 @@ * you may not use this file except in compliance with the License. */ -import { ConnectionInfoResource, CONNECTION_NAVIGATOR_VIEW_SETTINGS, getNavigatorView, isNavigatorSettingEnabled, NavigatorViewSettingsKeys } 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'; -import type { NavigatorSettingsInput } from '@cloudbeaver/core-sdk'; import type { NavNode } from '../shared/NodesManager/EntityTypes'; import { EObjectFeature } from '../shared/NodesManager/EObjectFeature'; @@ -47,39 +46,21 @@ export class NavigationTreeContextMenuService { }); } - private getConnectionViewSettings(nodeId: string) { - const connectionId = NodeManagerUtils.connectionNodeIdToConnectionId(nodeId); - - const connection = this.connectionInfoResource.get(connectionId); - - if (!connection) { - return null; - } - - return connection.navigatorSettings; + private getConnectionFromNodeId(nodeId: string) { + return this.connectionInfoResource.get(NodeManagerUtils.connectionNodeIdToConnectionId(nodeId)); } - private getConnectionNavigatorView(nodeId: string) { - const connectionViewSettings = this.getConnectionViewSettings(nodeId); + private isSimpleNavigatorView(nodeId: string) { + const currentSettings = this.getConnectionFromNodeId(nodeId)?.navigatorSettings; - if (!connectionViewSettings) { - return null; - } - - return getNavigatorView(connectionViewSettings); - } - - private isConnectionViewSettingEnabled(nodeId: string, setting: NavigatorViewSettingsKeys) { - const connectionViewSettings = this.getConnectionViewSettings(nodeId); - - if (!connectionViewSettings) { + if (!currentSettings) { return false; } - return isNavigatorSettingEnabled(setting, connectionViewSettings); + return isNavigatorViewSettingsEqual(currentSettings, CONNECTION_NAVIGATOR_VIEW_SETTINGS.simple); } - private async changeConnectionView(nodeId: string, settings: NavigatorSettingsInput) { + private async changeConnectionView(nodeId: string, settings: NavigatorViewSettings) { const connectionId = NodeManagerUtils.connectionNodeIdToConnectionId(nodeId); try { @@ -110,16 +91,14 @@ export class NavigationTreeContextMenuService { id: 'simple', title: 'app_navigationTree_connection_view_option_simple', type: 'radio', - isChecked: context => this.getConnectionNavigatorView(context.data.id) === 'simple', + 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, { - ...CONNECTION_NAVIGATOR_VIEW_SETTINGS.simple, - showSystemObjects: this.isConnectionViewSettingEnabled(context.data.id, 'showSystemObjects'), - }), + await this.changeConnectionView(context.data.id, CONNECTION_NAVIGATOR_VIEW_SETTINGS.simple), + } ); this.contextMenuService.addMenuItem( @@ -128,36 +107,37 @@ export class NavigationTreeContextMenuService { id: 'advanced', title: 'app_navigationTree_connection_view_option_advanced', type: 'radio', - isChecked: context => this.getConnectionNavigatorView(context.data.id) === 'advanced', + 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, { - ...CONNECTION_NAVIGATOR_VIEW_SETTINGS.advanced, - showSystemObjects: this.isConnectionViewSettingEnabled(context.data.id, 'showSystemObjects'), - }), + await this.changeConnectionView(context.data.id, CONNECTION_NAVIGATOR_VIEW_SETTINGS.advanced), } ); + this.contextMenuService.addMenuItem( this.getNodeViewMenuItemToken(), { id: 'systemObjects', title: 'app_navigationTree_connection_view_option_showSystemObjects', - isChecked: context => this.isConnectionViewSettingEnabled(context.data.id, '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.getConnectionViewSettings(context.data.id); + const currentSettings = this.getConnectionFromNodeId(context.data.id)?.navigatorSettings; if (!currentSettings) { return; } + return await this.changeConnectionView(context.data.id, { ...currentSettings, - showSystemObjects: !currentSettings['showSystemObjects'], + showSystemObjects: !currentSettings.showSystemObjects, }); }, } diff --git a/webapp/packages/core-app/src/locales/ru.ts b/webapp/packages/core-app/src/locales/ru.ts index ea49c50235..66fb95bdb8 100644 --- a/webapp/packages/core-app/src/locales/ru.ts +++ b/webapp/packages/core-app/src/locales/ru.ts @@ -16,7 +16,7 @@ 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_custom', 'Настраиваемое'], ['app_navigationTree_connection_view_option_showSystemObjects', 'Показывать системные объекты'], ['app_navigationTree_refreshNode', 'Обновить'], ['app_navigationTree_context_disconnect', 'Отключить'], diff --git a/webapp/packages/core-blocks/src/FormControls/Checkboxes/Checkbox.tsx b/webapp/packages/core-blocks/src/FormControls/Checkboxes/Checkbox.tsx index 2f7ffd77cb..ed21232820 100644 --- a/webapp/packages/core-blocks/src/FormControls/Checkboxes/Checkbox.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Checkboxes/Checkbox.tsx @@ -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' | 'menu'; + export type CheckboxBaseProps = Omit, 'onChange' | 'type' | 'value' | 'checked'> & IGridItemsLayoutProps & { value?: string; checkboxLabel?: string; - mod?: 'surface'; long?: boolean; + mod?: CheckboxMod[]; + showRipple?: boolean; }; type CheckboxOnChangeEvent = @@ -55,8 +57,9 @@ export const Checkbox: CheckboxType = observer(function Checkbox({ checkboxLabel, checked: checkedControlled, children, - className, mod, + showRipple, + className, long, autoHide, onChange, @@ -114,8 +117,9 @@ export const Checkbox: CheckboxType = observer(function Checkbox({ checked={checked} label={checkboxLabel} className={className} + mod={mod} + showRipple={showRipple} onChange={handleChange} - {...use({ mod })} /> ); }); diff --git a/webapp/packages/core-blocks/src/FormControls/Checkboxes/CheckboxMarkup.tsx b/webapp/packages/core-blocks/src/FormControls/Checkboxes/CheckboxMarkup.tsx index a52a2c468a..5a94613b58 100644 --- a/webapp/packages/core-blocks/src/FormControls/Checkboxes/CheckboxMarkup.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Checkboxes/CheckboxMarkup.tsx @@ -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 = { primary: composes( css` checkbox { @@ -54,6 +56,26 @@ const checkboxMod = { } ` ), + menu: composes( + css` + checkbox { + composes: checkbox_menu from global; + } + `, + css` + checkbox-container { + & checkbox { + width: 14px; + height: 14px; + margin: -6px; + } + & checkbox-background { + width: 14px; + height: 14px; + } + } + ` + ), }; const checkboxState = { @@ -76,12 +98,14 @@ const checkboxState = { interface ICheckboxMarkupProps extends React.InputHTMLAttributes { label?: string; indeterminate?: boolean; + showRipple?: boolean; + mod?: CheckboxMod[]; } export const CheckboxMarkup: React.FC = function CheckboxMarkup({ - label, className, title, ...rest + label, className, title, mod = ['primary'], showRipple = 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))( @@ -92,7 +116,9 @@ export const CheckboxMarkup: React.FC = function CheckboxM - + {showRipple && ( + + )} {label && rest.id && {label}} diff --git a/webapp/packages/core-blocks/src/FormControls/Checkboxes/FieldCheckboxNew.tsx b/webapp/packages/core-blocks/src/FormControls/Checkboxes/FieldCheckboxNew.tsx index 14286e6d5c..a48f10bf75 100644 --- a/webapp/packages/core-blocks/src/FormControls/Checkboxes/FieldCheckboxNew.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Checkboxes/FieldCheckboxNew.tsx @@ -49,7 +49,7 @@ export const FieldCheckboxNew: CheckboxType = function FieldCheckboxNew({ {...rest} checked={checkedControlled} /> - {children} + {children} ); }; diff --git a/webapp/packages/core-blocks/src/FormControls/Radio.tsx b/webapp/packages/core-blocks/src/FormControls/Radio.tsx index 0f056ec80d..3c41c84921 100644 --- a/webapp/packages/core-blocks/src/FormControls/Radio.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Radio.tsx @@ -55,6 +55,30 @@ const radioMod = { } ` ), + menu: composes( + css` + radio { + composes: radio_menu from global; + } + `, + css` + field { + padding: 0; + & radio { + width: 14px; + height: 14px; + padding: 0; + } + & radio-background { + width: 14px; + height: 14px; + } + & radio-inner-circle { + border-width: 7px; + } + } + ` + ), }; type BaseProps = Omit, 'onChange' | 'value' | 'checked'> & { diff --git a/webapp/packages/core-blocks/src/ObjectPropertyInfoForm/ObjectPropertyInfoForm.tsx b/webapp/packages/core-blocks/src/ObjectPropertyInfoForm/ObjectPropertyInfoForm.tsx index f70915493b..255a5eba99 100644 --- a/webapp/packages/core-blocks/src/ObjectPropertyInfoForm/ObjectPropertyInfoForm.tsx +++ b/webapp/packages/core-blocks/src/ObjectPropertyInfoForm/ObjectPropertyInfoForm.tsx @@ -85,7 +85,6 @@ const RenderField: React.FC = observer(function RenderField({ checkboxLabel={property.displayName} title={property.description} disabled={disabled} - mod='surface' /> ); } diff --git a/webapp/packages/core-connections/src/ConnectionForm/Options/Options.tsx b/webapp/packages/core-connections/src/ConnectionForm/Options/Options.tsx index c7078bd6c7..e347462b0f 100644 --- a/webapp/packages/core-connections/src/ConnectionForm/Options/Options.tsx +++ b/webapp/packages/core-connections/src/ConnectionForm/Options/Options.tsx @@ -212,7 +212,6 @@ export const Options: TabContainerPanelComponent = obse disabled={edit || form.form.disabled} readOnly={form.form.readonly} // autoHide={} // maybe better to use autoHide - mod='surface' >{translate('connections_connection_template')} )} @@ -245,7 +244,6 @@ export const Options: TabContainerPanelComponent = 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')} diff --git a/webapp/packages/core-connections/src/ConnectionForm/SSH/SSH.tsx b/webapp/packages/core-connections/src/ConnectionForm/SSH/SSH.tsx index 2ef0e66eac..782230a28e 100644 --- a/webapp/packages/core-connections/src/ConnectionForm/SSH/SSH.tsx +++ b/webapp/packages/core-connections/src/ConnectionForm/SSH/SSH.tsx @@ -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 = observer value={SSH_TUNNEL_ID + ' savePassword'} state={state} disabled={disabled || !enabled || form.form.readonly} - mod='surface' >{translate('connections_network_handler_ssh_tunnel_save_password')} )} diff --git a/webapp/packages/core-connections/src/ConnectionInfoResource.ts b/webapp/packages/core-connections/src/ConnectionInfoResource.ts index d4fd2b4a14..0197656119 100644 --- a/webapp/packages/core-connections/src/ConnectionInfoResource.ts +++ b/webapp/packages/core-connections/src/ConnectionInfoResource.ts @@ -27,11 +27,22 @@ import { } from '@cloudbeaver/core-sdk'; import { ConnectionsResource, DatabaseConnection } from './Administration/ConnectionsResource'; +import type { NavigatorViewSettings } from './ConnectionNavigatorViewSettings'; export type Connection = DatabaseConnection & { authProperties?: UserConnectionAuthPropertiesFragment[] }; export type ConnectionInitConfig = Omit; export type ConnectionInfoIncludes = Omit; +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 { readonly onConnectionCreate: IExecutor; @@ -170,11 +181,12 @@ export class ConnectionInfoResource extends CachedMapResource { + async changeConnectionView(id: string, settings: NavigatorViewSettings): Promise { await this.performUpdate(id, [], async () => { + 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), }); diff --git a/webapp/packages/core-connections/src/ConnectionNavigatorViewSettings.ts b/webapp/packages/core-connections/src/ConnectionNavigatorViewSettings.ts index dd5a54e0c7..a0e9fd5576 100644 --- a/webapp/packages/core-connections/src/ConnectionNavigatorViewSettings.ts +++ b/webapp/packages/core-connections/src/ConnectionNavigatorViewSettings.ts @@ -8,19 +8,16 @@ import type { NavigatorSettingsInput } from '@cloudbeaver/core-sdk'; -export type NavigatorView = 'simple' | 'advanced' | 'custom'; -export type NavigatorViewSettingsKeys = keyof NavigatorSettingsInput; +export type NavigatorView = 'simple' | 'advanced'; +export type NavigatorViewSettings = Partial; -type NavigatorViewSettings = Record, NavigatorSettingsInput>; - -export const CONNECTION_NAVIGATOR_VIEW_SETTINGS: NavigatorViewSettings = { +export const CONNECTION_NAVIGATOR_VIEW_SETTINGS: Record = { simple: { showOnlyEntities: true, hideFolders: true, hideVirtualModel: true, hideSchemas: false, mergeEntities: false, - showSystemObjects: false, showUtilityObjects: false, }, advanced: { @@ -29,39 +26,13 @@ export const CONNECTION_NAVIGATOR_VIEW_SETTINGS: NavigatorViewSettings = { hideVirtualModel: false, hideSchemas: false, mergeEntities: false, - showSystemObjects: false, showUtilityObjects: false, }, }; -export function getNavigatorView(settings: NavigatorSettingsInput): NavigatorView { - const isSimple = isNavigatorViewSettingsEqual(settings, CONNECTION_NAVIGATOR_VIEW_SETTINGS.simple); - const isAdvanced = isNavigatorViewSettingsEqual(settings, CONNECTION_NAVIGATOR_VIEW_SETTINGS.advanced); - - if (isSimple) { - return 'simple'; - } - - if (isAdvanced) { - return 'advanced'; - } - - return 'custom'; -} - -export function isNavigatorSettingEnabled( - setting: NavigatorViewSettingsKeys, settings: NavigatorSettingsInput +export function isNavigatorViewSettingsEqual( + settings: NavigatorSettingsInput, settingsToCompare: NavigatorViewSettings ): boolean { - return settings[setting]; -} - -function isNavigatorViewSettingsEqual(settings: NavigatorSettingsInput, settingsToCompare: NavigatorSettingsInput) { - return !(Object.keys(settingsToCompare) as NavigatorViewSettingsKeys[]) - .some(key => { - // we need to exclude it for now, cause we haven't implement custom navigator view logic yet - if (key === 'showSystemObjects') { - return false; - } - return settings[key] !== settingsToCompare[key]; - }); + return !(Object.keys(settingsToCompare) as Array) + .some(key => settings[key] !== settingsToCompare[key]); } diff --git a/webapp/packages/core-connections/src/DatabaseAuthDialog/DatabaseAuthDialog.tsx b/webapp/packages/core-connections/src/DatabaseAuthDialog/DatabaseAuthDialog.tsx index fd0e8c8702..8ae53df07a 100644 --- a/webapp/packages/core-connections/src/DatabaseAuthDialog/DatabaseAuthDialog.tsx +++ b/webapp/packages/core-connections/src/DatabaseAuthDialog/DatabaseAuthDialog.tsx @@ -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' /> )} diff --git a/webapp/packages/core-connections/src/DatabaseAuthDialog/SSHAuthForm.tsx b/webapp/packages/core-connections/src/DatabaseAuthDialog/SSHAuthForm.tsx index a888d28260..9adc3657ff 100644 --- a/webapp/packages/core-connections/src/DatabaseAuthDialog/SSHAuthForm.tsx +++ b/webapp/packages/core-connections/src/DatabaseAuthDialog/SSHAuthForm.tsx @@ -81,7 +81,6 @@ export const SSHAuthForm: React.FC = observer(function SSHAuthForm({ state={state} checkboxLabel={translate('connections_network_handler_ssh_tunnel_save_password')} disabled={disabled} - mod='surface' /> )} diff --git a/webapp/packages/core-dialogs/src/Menu/ContextMenu/ContextMenu.ts b/webapp/packages/core-dialogs/src/Menu/ContextMenu/ContextMenu.ts index 2911161a49..0894ad83cc 100644 --- a/webapp/packages/core-dialogs/src/Menu/ContextMenu/ContextMenu.ts +++ b/webapp/packages/core-dialogs/src/Menu/ContextMenu/ContextMenu.ts @@ -11,7 +11,7 @@ import { uuid } from '@cloudbeaver/core-utils'; import type { IMenuPanel } from '../IMenuPanel'; import { ComputedMenuItemModel, IComputedMenuItemOptions } from '../models/ComputedMenuItemModel'; -import { MenuItemControlType, MenuOptionsStore } from '../models/MenuOptionsStore'; +import { MenuItemType, MenuOptionsStore } from '../models/MenuOptionsStore'; import type { IContextMenuItem } from './IContextMenuItem'; import type { IMenuContext } from './IMenuContext'; @@ -90,7 +90,8 @@ class ComputedMenuItemOptionsWithContext implements IComputedMenuItemOptions // set icon or getter icon?: string; isChecked?: () => boolean; - type?: MenuItemControlType; + type?: MenuItemType; + separator?: boolean; iconGetter?: () => string | undefined; constructor(private options: IContextMenuItem, @@ -100,6 +101,7 @@ class ComputedMenuItemOptionsWithContext implements IComputedMenuItemOptions 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!}`; diff --git a/webapp/packages/core-dialogs/src/Menu/IMenuPanel.ts b/webapp/packages/core-dialogs/src/Menu/IMenuPanel.ts index f0f054be6b..90a32ac124 100644 --- a/webapp/packages/core-dialogs/src/Menu/IMenuPanel.ts +++ b/webapp/packages/core-dialogs/src/Menu/IMenuPanel.ts @@ -12,7 +12,7 @@ import type { MenuInitialState } from 'reakit/Menu'; import type { TLocalizationToken } from '@cloudbeaver/core-localization'; import type { Style } from '@cloudbeaver/core-theming'; -import type { MenuItemControlType } from './models/MenuOptionsStore'; +import type { MenuItemType } from './models/MenuOptionsStore'; export type MenuMod = 'primary' | 'surface' | 'secondary'; @@ -30,7 +30,8 @@ export interface IMenuItem { isHidden?: boolean; icon?: string; // path to icon or svg icon name panel?: IMenuPanel; // if menu has sub-items - type?: MenuItemControlType; + type?: MenuItemType; + separator?: boolean; isChecked?: boolean; } diff --git a/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuPanelItem.tsx b/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuPanelItem.tsx index 08e33ce955..dc320320ca 100644 --- a/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuPanelItem.tsx +++ b/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuPanelItem.tsx @@ -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,38 @@ export const MenuPanelItem: React.FC = 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 = ; + } else if (controlType === 'checkbox') { + control = ; + } + return styled(useStyles(menuPanelStyles, ...style))( - - + + + {icon && ( + + )} + {control} + + - {panel && } + + {panel && } + + ); }; diff --git a/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuTrigger.tsx b/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuTrigger.tsx index 23aafd1b11..fb02eb8ddf 100644 --- a/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuTrigger.tsx +++ b/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuTrigger.tsx @@ -117,24 +117,25 @@ const MenuPanelElement = observer(function MenuPanelElement({ ); } - if (item.isChecked !== undefined) { - if (item.type === 'radio') { - return styled(styles)( - - - + if (item.type === 'radio') { + return styled(styles)( + + + - ); - } + ); + } + + if (item.type === 'checkbox') { return styled(styles)( + ); diff --git a/webapp/packages/core-dialogs/src/Menu/MenuPanel/menuPanelStyles.ts b/webapp/packages/core-dialogs/src/Menu/MenuPanel/menuPanelStyles.ts index 5f00cf8d9a..f2fe1d4e1c 100644 --- a/webapp/packages/core-dialogs/src/Menu/MenuPanel/menuPanelStyles.ts +++ b/webapp/packages/core-dialogs/src/Menu/MenuPanel/menuPanelStyles.ts @@ -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; @@ -54,9 +57,10 @@ export const menuPanelStyles = composes( } MenuItem, MenuItemCheckbox, MenuItemRadio { display: flex; + align-items: center; border: none; + padding: 0; background: none; - padding: 6px 16px 6px 20px; text-align: left; outline: none; color: inherit; @@ -69,47 +73,43 @@ export const menuPanelStyles = composes( &:hover, &:global([aria-expanded="true"]) { font-weight: 600; - & Icon { opacity: 1; } } } - MenuItemCheckbox:global([aria-checked="true"]) { - position: relative; - &:before { - content: "✓"; - position: absolute; - left: 6px; - top: 4px; - } - } - - MenuItemRadio:global([aria-checked="true"]) { - position: relative; - &:before { - content: "•"; - position: absolute; - left: 6px; - top: -3px; - font-size: 2em; - } - } menu-panel-item { flex: 1; display: flex; align-items: center; - + height: 30px; + padding: 0 5px; + &[|separator] { + border-bottom: 1px solid; + } & menu-item-text { display: block; + padding: 0 5px; flex: 1; } + & menu-item-content { + min-width: 25px; + min-height: 25px; + max-width: 25px; + max-height: 25px; + display: flex; + align-items: center; + justify-content: center; + } & Icon { width: 16px; height: 16px; opacity: 0.5; - padding-left: 16px; + } + & IconOrImage { + width: 14px; + height: 14px; } } ` diff --git a/webapp/packages/core-dialogs/src/Menu/models/ComputedMenuItemModel.ts b/webapp/packages/core-dialogs/src/Menu/models/ComputedMenuItemModel.ts index 7a7e8c336d..7d13c6a249 100644 --- a/webapp/packages/core-dialogs/src/Menu/models/ComputedMenuItemModel.ts +++ b/webapp/packages/core-dialogs/src/Menu/models/ComputedMenuItemModel.ts @@ -11,7 +11,7 @@ import { computed, makeObservable } from 'mobx'; import type { IMenuItem, IMenuPanel } from '@cloudbeaver/core-dialogs'; import type { TLocalizationToken } from '@cloudbeaver/core-localization'; -import type { IMenuItemOptions, MenuItemControlType } from './MenuOptionsStore'; +import type { IMenuItemOptions, MenuItemType } from './MenuOptionsStore'; export interface IComputedMenuItemOptions extends IMenuItemOptions { onClick?: () => void; @@ -24,7 +24,8 @@ export class ComputedMenuItemModel implements IMenuItem { id: string; onClick?: () => void; panel?: IMenuPanel; - type?: MenuItemControlType; + type?: MenuItemType; + separator?: boolean; rtl?: boolean; get title(): TLocalizationToken { @@ -50,7 +51,7 @@ export class ComputedMenuItemModel implements IMenuItem { } get isChecked() { - return this.options.isChecked ? this.options.isChecked() : undefined; + return this.options.isChecked ? this.options.isChecked() : false; } constructor(private options: IComputedMenuItemOptions) { @@ -64,6 +65,7 @@ export class ComputedMenuItemModel implements IMenuItem { 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; diff --git a/webapp/packages/core-dialogs/src/Menu/models/MenuOptionsStore.ts b/webapp/packages/core-dialogs/src/Menu/models/MenuOptionsStore.ts index c09bc08a3a..fd458186a8 100644 --- a/webapp/packages/core-dialogs/src/Menu/models/MenuOptionsStore.ts +++ b/webapp/packages/core-dialogs/src/Menu/models/MenuOptionsStore.ts @@ -13,7 +13,7 @@ import type { IMenuPanel } from '../IMenuPanel'; const DEFAULT_ITEM_ORDER = 100; -export type MenuItemControlType = 'checkbox' | 'radio'; +export type MenuItemType = 'checkbox' | 'radio'; export interface IMenuItemOptions { id: string; // set title or getter @@ -25,7 +25,8 @@ export interface IMenuItemOptions { order?: number; isPanel?: boolean; panel?: IMenuPanel; - type?: MenuItemControlType; + type?: MenuItemType; + separator?: boolean; rtl?: boolean; } diff --git a/webapp/packages/core-theming/src/styles/_checkbox.scss b/webapp/packages/core-theming/src/styles/_checkbox.scss index 9b1be876c2..32cf483576 100644 --- a/webapp/packages/core-theming/src/styles/_checkbox.scss +++ b/webapp/packages/core-theming/src/styles/_checkbox.scss @@ -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 ) - } - } \ No newline at end of file + } + .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 + ); + } + + .checkbox_menu { + @include mdc-checkbox-density(-3); + } +} diff --git a/webapp/packages/core-theming/src/styles/_color.scss b/webapp/packages/core-theming/src/styles/_color.scss index 6db461a5b8..60a84fdfca 100644 --- a/webapp/packages/core-theming/src/styles/_color.scss +++ b/webapp/packages/core-theming/src/styles/_color.scss @@ -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); } } } diff --git a/webapp/packages/core-theming/src/styles/_radio.scss b/webapp/packages/core-theming/src/styles/_radio.scss index 86ff192b38..0f1298e42a 100644 --- a/webapp/packages/core-theming/src/styles/_radio.scss +++ b/webapp/packages/core-theming/src/styles/_radio.scss @@ -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,9 @@ @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); } -} \ No newline at end of file + + .radio_menu { + @include mdc-radio-density(-3); + @include mdc-radio-ripple-size(0px); + } +} diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationNavigatorViewForm.tsx b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationNavigatorViewForm.tsx index f91780f6cf..89575ede21 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationNavigatorViewForm.tsx +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/Form/ServerConfigurationNavigatorViewForm.tsx @@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite'; import React, { useCallback } from 'react'; import { FormGroup, Switch } from '@cloudbeaver/core-blocks'; -import { CONNECTION_NAVIGATOR_VIEW_SETTINGS, getNavigatorView } 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 = observer(fu }) { const translate = useTranslate(); - const isSimpleView = getNavigatorView(configs.navigatorConfig) === 'simple'; + 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]); diff --git a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts index 441dafeff1..e752b6f2ec 100644 --- a/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts +++ b/webapp/packages/plugin-administration/src/ConfigurationWizard/ServerConfiguration/ServerConfigurationService.ts @@ -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 }, }; } diff --git a/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserInfo.tsx b/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserInfo.tsx index 484eda2186..61b575f16e 100644 --- a/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserInfo.tsx +++ b/webapp/packages/plugin-authentication/src/Administration/Users/UserForm/UserInfo.tsx @@ -103,7 +103,6 @@ export const UserInfo: TabContainerPanelComponent = 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)} /> diff --git a/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionDialog.tsx b/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionDialog.tsx index a2cc493e69..b1161b584d 100644 --- a/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionDialog.tsx +++ b/webapp/packages/plugin-connection-template/src/ConnectionDialog/ConnectionDialog.tsx @@ -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' /> )} From 1a3d8c41997f6b1314a0e66081455e9da3eab105 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Tue, 16 Mar 2021 16:49:36 +0300 Subject: [PATCH 3/3] feat(core-blocks): CB-747 styles fixes --- .../src/FormControls/Checkboxes/Checkbox.tsx | 8 +-- .../Checkboxes/CheckboxMarkup.tsx | 15 +++-- .../core-blocks/src/FormControls/Radio.tsx | 56 +++++++++++-------- .../src/Menu/MenuPanel/MenuPanelItem.tsx | 9 ++- .../src/Menu/MenuPanel/menuPanelStyles.ts | 11 ++-- .../core-theming/src/styles/_checkbox.scss | 2 +- .../core-theming/src/styles/_radio.scss | 7 ++- 7 files changed, 58 insertions(+), 50 deletions(-) diff --git a/webapp/packages/core-blocks/src/FormControls/Checkboxes/Checkbox.tsx b/webapp/packages/core-blocks/src/FormControls/Checkboxes/Checkbox.tsx index ed21232820..9b32abc2f9 100644 --- a/webapp/packages/core-blocks/src/FormControls/Checkboxes/Checkbox.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Checkboxes/Checkbox.tsx @@ -14,14 +14,14 @@ import { FormContext } from '../FormContext'; import { isControlPresented } from '../isControlPresented'; import { CheckboxMarkup } from './CheckboxMarkup'; -export type CheckboxMod = 'primary' | 'menu'; +export type CheckboxMod = 'primary' | 'small'; export type CheckboxBaseProps = Omit, 'onChange' | 'type' | 'value' | 'checked'> & IGridItemsLayoutProps & { value?: string; checkboxLabel?: string; long?: boolean; mod?: CheckboxMod[]; - showRipple?: boolean; + ripple?: boolean; }; type CheckboxOnChangeEvent = @@ -58,7 +58,7 @@ export const Checkbox: CheckboxType = observer(function Checkbox({ checked: checkedControlled, children, mod, - showRipple, + ripple, className, long, autoHide, @@ -118,7 +118,7 @@ export const Checkbox: CheckboxType = observer(function Checkbox({ label={checkboxLabel} className={className} mod={mod} - showRipple={showRipple} + ripple={ripple} onChange={handleChange} /> ); diff --git a/webapp/packages/core-blocks/src/FormControls/Checkboxes/CheckboxMarkup.tsx b/webapp/packages/core-blocks/src/FormControls/Checkboxes/CheckboxMarkup.tsx index 5a94613b58..5c7de15f09 100644 --- a/webapp/packages/core-blocks/src/FormControls/Checkboxes/CheckboxMarkup.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Checkboxes/CheckboxMarkup.tsx @@ -56,25 +56,24 @@ const checkboxMod: Record = { } ` ), - menu: composes( + small: composes( css` checkbox { - composes: checkbox_menu from global; - } + composes: theme-checkbox_small from global; + } `, css` checkbox-container { & checkbox { width: 14px; height: 14px; - margin: -6px; } & checkbox-background { width: 14px; height: 14px; } } - ` + `, ), }; @@ -98,12 +97,12 @@ const checkboxState = { interface ICheckboxMarkupProps extends React.InputHTMLAttributes { label?: string; indeterminate?: boolean; - showRipple?: boolean; + ripple?: boolean; mod?: CheckboxMod[]; } export const CheckboxMarkup: React.FC = function CheckboxMarkup({ - label, className, title, mod = ['primary'], showRipple = true, ...rest + label, className, title, mod = ['primary'], ripple = true, ...rest }) { return styled(useStyles(checkboxStyles, ...(mod || []).map(mod => checkboxMod[mod]), rest.disabled && checkboxState.disabled, rest.checked && checkboxState.checked))( @@ -116,7 +115,7 @@ export const CheckboxMarkup: React.FC = function CheckboxM - {showRipple && ( + {ripple && ( )} diff --git a/webapp/packages/core-blocks/src/FormControls/Radio.tsx b/webapp/packages/core-blocks/src/FormControls/Radio.tsx index 3c41c84921..5778d2db31 100644 --- a/webapp/packages/core-blocks/src/FormControls/Radio.tsx +++ b/webapp/packages/core-blocks/src/FormControls/Radio.tsx @@ -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,19 +55,17 @@ const radioMod = { } ` ), - menu: composes( + small: composes( css` radio { - composes: radio_menu from global; + composes: theme-radio_small from global; } `, css` field { - padding: 0; & radio { width: 14px; height: 14px; - padding: 0; } & radio-background { width: 14px; @@ -81,8 +79,17 @@ const radioMod = { ), }; +const noRippleStyles = composes( + css` + radio { + composes: theme-radio_no-ripple from global; + } + ` +); + type BaseProps = Omit, 'onChange' | 'value' | 'checked'> & { mod?: Array; + ripple?: boolean; }; type ControlledProps = BaseProps & { @@ -115,6 +122,7 @@ export const Radio: RadioType = observer(function Radio({ checked: controlledChecked, onChange, mod, + ripple = true, className, children, ...rest @@ -155,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))( - + {ripple && } diff --git a/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuPanelItem.tsx b/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuPanelItem.tsx index dc320320ca..bdf8f57420 100644 --- a/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuPanelItem.tsx +++ b/webapp/packages/core-dialogs/src/Menu/MenuPanel/MenuPanelItem.tsx @@ -38,18 +38,17 @@ export const MenuPanelItem: React.FC = function MenuPanelIte let control = null; if (controlType === 'radio') { - control = ; + control = ; } else if (controlType === 'checkbox') { - control = ; + control = ; } return styled(useStyles(menuPanelStyles, ...style))( - {icon && ( + {icon ? ( - )} - {control} + ) : control} diff --git a/webapp/packages/core-dialogs/src/Menu/MenuPanel/menuPanelStyles.ts b/webapp/packages/core-dialogs/src/Menu/MenuPanel/menuPanelStyles.ts index f2fe1d4e1c..17360cefad 100644 --- a/webapp/packages/core-dialogs/src/Menu/MenuPanel/menuPanelStyles.ts +++ b/webapp/packages/core-dialogs/src/Menu/MenuPanel/menuPanelStyles.ts @@ -84,20 +84,19 @@ export const menuPanelStyles = composes( display: flex; align-items: center; height: 30px; - padding: 0 5px; + padding: 0 4px; &[|separator] { border-bottom: 1px solid; } & menu-item-text { display: block; - padding: 0 5px; + padding: 0 4px; flex: 1; } & menu-item-content { - min-width: 25px; - min-height: 25px; - max-width: 25px; - max-height: 25px; + width: 24px; + height: 24px; + overflow: hidden; display: flex; align-items: center; justify-content: center; diff --git a/webapp/packages/core-theming/src/styles/_checkbox.scss b/webapp/packages/core-theming/src/styles/_checkbox.scss index 32cf483576..8066cc7b31 100644 --- a/webapp/packages/core-theming/src/styles/_checkbox.scss +++ b/webapp/packages/core-theming/src/styles/_checkbox.scss @@ -52,7 +52,7 @@ $mdc-checkbox-icon-size: 16px; ); } - .checkbox_menu { + .theme-checkbox_small { @include mdc-checkbox-density(-3); } } diff --git a/webapp/packages/core-theming/src/styles/_radio.scss b/webapp/packages/core-theming/src/styles/_radio.scss index 0f1298e42a..b0037f20e5 100644 --- a/webapp/packages/core-theming/src/styles/_radio.scss +++ b/webapp/packages/core-theming/src/styles/_radio.scss @@ -44,8 +44,11 @@ @include mdc-radio-focus-indicator-color($mdc-radio-baseline-theme-color, $query: $query); } - .radio_menu { + .theme-radio_small { @include mdc-radio-density(-3); - @include mdc-radio-ripple-size(0px); + } + + .theme-radio_no-ripple { + @include mdc-radio-ripple-size(0); } }