dbeaver/pro#9614 add predefined param for connection type (#4454)

* dbeaver/pro#9614 add predefined param for connection type

* dbeaver/pro#9614 add connection type selector

* dbeaver/pro#9614 make menu indicator more visible

* dbeaver/pro#9614 add extra colors

* dbeaver/pro#9614 outdate connections

* dbeaver/pro#9614 load types in bootstrap phrase

* dbeaver/pro#9614 always use useConnectionTypeColor

* dbeaver/pro#9614 remove unused import

* dbeaver/pro#9614 add useConnectionTypeLoader hook

* dbeaver/pro#9614 add default hint

* dbeaver/pro#9614 do not wrap arg:item by default

* dbeaver/pro#9614 use predefined unset for all types

* dbeaver/pro#9614 use common mapped unset color

---------

Co-authored-by: Ainur <ainur.iagudin@dbeaver.com>
Co-authored-by: Evgenia <139753579+EvgeniaBzzz@users.noreply.github.com>
This commit is contained in:
alex
2026-07-13 22:11:10 +02:00
committed by GitHub
co-authored by Ainur Evgenia
parent 025944a867
commit 31ee01b6fa
57 changed files with 514 additions and 62 deletions
@@ -664,6 +664,8 @@ type ConnectionType @since(version: "26.1.2") {
autoCloseConnections: Boolean!
"Auto-close idle connections period in seconds for this connection type"
closeIdleConnectionPeriod: Int!
"Specifies whether this connection type is system-defined or user-defined. System-defined connection types cannot be deleted."
predefined: Boolean!
}
####################################################
@@ -13,9 +13,10 @@ import { memo } from 'react';
interface Props {
color: string;
isSelected?: boolean;
className?: string;
}
export const ColorIndicator = memo(function ColorIndicator({ color, isSelected }: Props) {
export const ColorIndicator = memo(function ColorIndicator({ color, isSelected, className }: Props) {
const isLight = ColorConvert(color).isLight();
const selectedMark = isLight ? 'dbv-kit-color-picker__color--selected-light' : 'dbv-kit-color-picker__color--selected-dark';
@@ -24,6 +25,7 @@ export const ColorIndicator = memo(function ColorIndicator({ color, isSelected }
className={clsx(
'dbv_kit_color-picker__color tw:w-5 tw:h-5 tw:rounded-full tw:border tw:border-(--dbv-kit-color-picker-border-color)',
isSelected && selectedMark,
className,
)}
style={{ backgroundColor: color }}
/>
@@ -12,6 +12,6 @@ export function normalizeColorToHex(color: string): string {
return ColorConvert(color).hex();
}
export function normalizeColorToRgb(color: string): string {
return ColorConvert(color).rgb().string();
}
export function normalizeColorToRgb(color: string): ReturnType<typeof ColorConvert.rgb> {
return ColorConvert(color).rgb();
}
@@ -26,7 +26,9 @@ export { IconButton, IconButtonBase, type IconButtonProps } from './IconButton/I
export { Checkbox, CheckboxBase, type CheckboxProps } from './Checkbox/Checkbox.js';
export { CheckboxIndicator, type ICheckboxIndicatorProps } from './Checkbox/CheckboxIndicator.js';
export { ColorPicker } from './ColorPicker/ColorPicker.js';
export { ColorIndicator } from './ColorPicker/ColorIndicator.js';
export { ColorPickerBase, type ColorPickerProps } from './ColorPicker/ColorPickerBase.js';
export { normalizeColorToRgb } from './ColorPicker/colorUtils.js';
export { Input, InputBase, type InputProps } from './Input/Input.js';
export {
SearchPanel,
@@ -14,11 +14,12 @@ import { useObjectRef } from '../../useObjectRef.js';
import { useObservableRef } from '../../useObservableRef.js';
import { useS } from '../../useS.js';
import type { ITreeNodeState } from './ITreeNodeState.js';
import style from './TreeNode.module.css';
import componentStyle from './TreeNode.module.css';
import { type ITreeNodeContext, TreeNodeContext } from './TreeNodeContext.js';
interface Props extends ITreeNodeState {
className?: string;
style?: React.CSSProperties;
children?: React.ReactNode;
onClick?: (leaf: boolean) => Promise<void> | void;
onExpand?: () => Promise<void> | void;
@@ -43,12 +44,13 @@ export const TreeNode = observer<Props, HTMLDivElement | null>(
externalExpanded,
leaf = false,
className,
style,
children,
...handlers
},
ref,
) {
const styles = useS(style);
const styles = useS(componentStyle);
const handlersRef = useObjectRef(handlers);
const nodeContext = useObservableRef<IInnerTreeNodeContext>(
@@ -115,7 +117,7 @@ export const TreeNode = observer<Props, HTMLDivElement | null>(
);
return (
<div ref={ref} className={s(styles, { node: true }, className)}>
<div ref={ref} className={s(styles, { node: true }, className)} style={style}>
<TreeNodeContext.Provider value={nodeContext}>{children}</TreeNodeContext.Provider>
</div>
);
+2
View File
@@ -259,3 +259,5 @@ export * from './ObjectPropertyInfo/evaluate.js';
export * from './ObjectPropertyInfo/getObjectPropertyDefaults.js';
export * from './useVisible.js';
export * from './SAVED_VALUE_INDICATOR.js';
export * from './FormControls/Field.js';
export * from './FormControls/FieldLabel.js';
@@ -37,6 +37,7 @@
"@cloudbeaver/core-root": "workspace:*",
"@cloudbeaver/core-sdk": "workspace:*",
"@cloudbeaver/core-settings": "workspace:*",
"@cloudbeaver/core-theming": "workspace:*",
"@cloudbeaver/core-utils": "workspace:*",
"@dbeaver/js-helpers": "workspace:*",
"mobx": "^6",
@@ -0,0 +1,132 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@cloudbeaver/core-di';
import { CachedMapAllKey, CachedMapResource, resourceKeyList, type ResourceKey } from '@cloudbeaver/core-resource';
import { ServerConfigResource } from '@cloudbeaver/core-root';
import { GraphQLService, type ConnectionType as ConnectionTypeFragment } from '@cloudbeaver/core-sdk';
import { isNotNullDefined } from '@dbeaver/js-helpers';
import { ConnectionInfoResource, createConnectionParam } from '../ConnectionInfoResource.js';
export type ConnectionType = ConnectionTypeFragment;
export const NEW_CONNECTION_TYPE_SYMBOL = Symbol('new-connection-type');
export type NewConnectionType = ConnectionType & { [NEW_CONNECTION_TYPE_SYMBOL]: boolean; timestamp: number };
@injectable(() => [GraphQLService, ConnectionInfoResource, ServerConfigResource])
export class ConnectionTypeResource extends CachedMapResource<string, ConnectionType> {
constructor(
private readonly graphQLService: GraphQLService,
private readonly connectionInfoResource: ConnectionInfoResource,
serverConfigResource: ServerConfigResource,
) {
super();
this.onItemDelete.addHandler(this.handleTypeChanges.bind(this));
this.onItemUpdate.addHandler(this.handleTypeChanges.bind(this));
this.onDataOutdated.addHandler(this.handleTypeChanges.bind(this));
this.sync(
serverConfigResource,
() => {},
() => CachedMapAllKey,
);
}
protected async loader(originalKey: ResourceKey<string>): Promise<Map<string, ConnectionType>> {
const all = this.aliases.isAlias(originalKey, CachedMapAllKey);
const { types } = await this.graphQLService.sdk.getConnectionTypes();
const key = resourceKeyList(types.map(type => type.id));
if (all) {
this.replace(key, types);
} else {
this.set(key, types);
}
return this.data;
}
compare(connectionTypeA: ConnectionType, connectionTypeB: ConnectionType): number {
if (connectionTypeA.predefined !== connectionTypeB.predefined) {
return connectionTypeA.predefined ? -1 : 1;
}
return connectionTypeA.name.localeCompare(connectionTypeB.name);
}
cleanNewFlags(): void {
for (const connection of this.data.values()) {
(connection as NewConnectionType)[NEW_CONNECTION_TYPE_SYMBOL] = false;
}
}
async refreshAll(): Promise<Map<string, ConnectionType>> {
await this.refresh(CachedMapAllKey);
return this.data;
}
protected override validateKey(key: string): boolean {
return typeof key === 'string';
}
private handleTypeChanges(key: ResourceKey<string>) {
const types = this.get(key);
if (!types) {
return;
}
const typeIds: string[] = [];
if (Array.isArray(types)) {
typeIds.push(...types.filter(isNotNullDefined).map(type => type.id));
} else {
typeIds.push(types.id);
}
const connections = this.connectionInfoResource.values.filter(connection => typeIds.includes(connection.connectionType));
if (!connections.length) {
return;
}
const result = resourceKeyList(connections.map(connection => createConnectionParam(connection.projectId, connection.id)));
this.connectionInfoResource.markOutdated(result);
}
}
function isNewConnectionType(connection: ConnectionType | NewConnectionType): connection is NewConnectionType {
return (connection as NewConnectionType)[NEW_CONNECTION_TYPE_SYMBOL];
}
export function compareNewConnectionTypes(a: ConnectionType, b: ConnectionType): number {
if (isNewConnectionType(a) && isNewConnectionType(b)) {
return b.timestamp - a.timestamp;
}
if (isNewConnectionType(b)) {
return 1;
}
if (isNewConnectionType(a)) {
return -1;
}
return 0;
}
export function mapColorValue(value: string): string {
return `rgb(${value})`;
}
export const PREDEFINED_UNSET_COLOR = '255,255,255';
export const MAPPED_PREDEFINED_UNSET_COLOR = `rgba(${PREDEFINED_UNSET_COLOR}, 0)`;
export const DEFAULT_TYPE_ID = 'dev';
@@ -0,0 +1,54 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@cloudbeaver/core-di';
import { ThemeService } from '@cloudbeaver/core-theming';
import { ConnectionTypeResource, mapColorValue, PREDEFINED_UNSET_COLOR } from './ConnectionTypeResource.js';
import { ConnectionInfoResource } from '../ConnectionInfoResource.js';
import type { IConnectionInfoParams } from '../CONNECTION_INFO_PARAM_SCHEMA.js';
@injectable(() => [ConnectionTypeResource, ConnectionInfoResource, ThemeService])
export class ConnectionTypeService {
constructor(
private readonly connectionTypeResource: ConnectionTypeResource,
private readonly connectionInfoResource: ConnectionInfoResource,
private readonly themeService: ThemeService,
) {}
getConnectionTypeColor(connectionKey: IConnectionInfoParams): string | undefined {
const connection = this.connectionInfoResource.get(connectionKey);
if (!connection) {
return;
}
return this.getTypeColor(connection.connectionType);
}
getTypeColor(typeId: string): string | undefined {
const theme = this.themeService.currentTheme?.type;
const connectionType = this.connectionTypeResource.get(typeId);
if (!connectionType) {
return;
}
let color = connectionType.colorLight;
if (connectionType.colorDark && theme === 'dark') {
color = connectionType.colorDark;
}
if (color === PREDEFINED_UNSET_COLOR) {
return;
}
return mapColorValue(color);
}
}
@@ -0,0 +1,22 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { useService } from '@cloudbeaver/core-di';
import type { IConnectionInfoParams } from '../CONNECTION_INFO_PARAM_SCHEMA.js';
import { ConnectionTypeService } from './ConnectionTypeService.js';
export function useConnectionTypeColor(connectionKey: IConnectionInfoParams | undefined): string | undefined {
const connectionTypeService = useService(ConnectionTypeService);
if (!connectionKey) {
return;
}
return connectionTypeService.getConnectionTypeColor(connectionKey);
}
@@ -0,0 +1,25 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { useResource } from '@cloudbeaver/core-blocks';
import { CachedMapAllKey } from '@cloudbeaver/core-resource';
import { UserInfoResource } from '@cloudbeaver/core-authentication';
import { ConnectionTypeResource } from './ConnectionTypeResource.js';
import { ConnectionInfoActiveProjectKey, ConnectionInfoResource } from '../ConnectionInfoResource.js';
/**
* Preloads connection types once at a higher level — avoids per-node or per-connection useResource
* subscriptions, which cause perfomance issues with 500+ entities. Used alongside useConnectionTypeColor.
*/
export function useConnectionTypeLoader(): void {
const userInfoResource = useResource(useConnectionTypeLoader, UserInfoResource, undefined);
useResource(useConnectionTypeLoader, ConnectionInfoResource, ConnectionInfoActiveProjectKey, { active: userInfoResource.resource.hasAccess() });
useResource(useConnectionTypeLoader, ConnectionTypeResource, CachedMapAllKey);
}
@@ -63,3 +63,8 @@ export * from './useDBDriver.js';
export * from './USER_NAME_PROPERTY_ID.js';
export * from './parseConnectionKey.js';
export * from './DBDriverExpertSettingsResource.js';
export * from './ConnectionType/ConnectionTypeResource.js';
export * from './ConnectionType/ConnectionTypeService.js';
export * from './ConnectionType/useConnectionTypeColor.js';
export * from './ConnectionType/useConnectionTypeLoader.js';
@@ -32,6 +32,8 @@ import { ConnectionFolderEventHandler } from './ConnectionFolderEventHandler.js'
import { ConnectionExecutionContextResource } from './ConnectionExecutionContext/ConnectionExecutionContextResource.js';
import { ConnectionDialectResource } from './ConnectionDialectResource.js';
import { DBDriverExpertSettingsResource } from './DBDriverExpertSettingsResource.js';
import { ConnectionTypeService } from './ConnectionType/ConnectionTypeService.js';
import { ConnectionTypeResource } from './ConnectionType/ConnectionTypeResource.js';
export default ModuleRegistry.add({
name: '@cloudbeaver/core-connections',
@@ -57,6 +59,7 @@ export default ModuleRegistry.add({
.addSingleton(Dependency, proxy(ConnectionFolderResource))
.addSingleton(Dependency, proxy(ConnectionExecutionContextResource))
.addSingleton(Dependency, proxy(DBDriverExpertSettingsResource))
.addSingleton(Dependency, proxy(ConnectionTypeResource))
.addSingleton(ConnectionDialectResource)
.addSingleton(NavNodeExtensionsService)
.addSingleton(DBDriverResource)
@@ -80,6 +83,8 @@ export default ModuleRegistry.add({
.addSingleton(ConnectionFolderResource)
.addSingleton(ConnectionExecutionContextService)
.addSingleton(ConnectionFolderEventHandler)
.addSingleton(ConnectionExecutionContextResource);
.addSingleton(ConnectionExecutionContextResource)
.addSingleton(ConnectionTypeResource)
.addSingleton(ConnectionTypeService);
},
});
@@ -64,6 +64,9 @@
{
"path": "../core-settings"
},
{
"path": "../core-theming"
},
{
"path": "../core-utils"
},
@@ -2,6 +2,7 @@ export default [
['name', 'en'],
['ui_rename_taken_or_invalid', 'This name is unavailable or invalid'],
['ui_tag_new', 'New'],
['ui_theme', 'Theme'],
['ui_light_theme', 'Light'],
['ui_dark_theme', 'Dark'],
['ui_system_theme', 'Device'],
@@ -149,7 +150,7 @@ export default [
['ui_project', 'Project'],
['ui_type', 'Type'],
['ui_field_is_required', 'Please fill in this field'],
['ui_delete_confirmation_message', 'You are about to delete "{arg:item}". Are you sure?'],
['ui_delete_confirmation_message', 'You are about to delete {arg:item}. Are you sure?'],
['ui_not_selected', 'Not selected'],
['ui_reset', 'Reset'],
['ui_documentaion', 'Documentation'],
@@ -157,6 +158,7 @@ export default [
['ui_allow', 'Allow'],
['ui_decline', 'Decline'],
['ui_declined', 'Declined'],
['ui_default', 'Default'],
['root_permission_denied', "You don't have permissions"],
['root_permission_no_permission', "You don't have permission for this action"],
@@ -143,7 +143,7 @@ export default [
['ui_project', 'Project'],
['ui_type', 'Type'],
['ui_field_is_required', 'Please fill in this field'],
['ui_delete_confirmation_message', 'You are about to delete "{arg:item}". Are you sure?'],
['ui_delete_confirmation_message', 'You are about to delete {arg:item}. Are you sure?'],
['ui_not_selected', 'Not selected'],
['ui_reset', 'Reset'],
['ui_documentaion', 'Documentation'],
@@ -140,7 +140,7 @@ export default [
['ui_project', 'Project'],
['ui_type', 'Type'],
['ui_field_is_required', 'Please fill in this field'],
['ui_delete_confirmation_message', 'You are about to delete "{arg:item}". Are you sure?'],
['ui_delete_confirmation_message', 'You are about to delete {arg:item}. Are you sure?'],
['ui_not_selected', 'Not selected'],
['ui_reset', 'Reset'],
['ui_documentaion', 'Documentation'],
@@ -2,6 +2,7 @@ export default [
['name', 'ru'],
['ui_rename_taken_or_invalid', 'Имя занято или недопустимо'],
['ui_tag_new', 'Новый'],
['ui_theme', 'Тема'],
['ui_light_theme', 'Светлая'],
['ui_dark_theme', 'Темная'],
['ui_system_theme', 'Устройство'],
@@ -154,6 +155,7 @@ export default [
['ui_allow', 'Разрешить'],
['ui_decline', 'Отклонить'],
['ui_declined', 'Отклонено'],
['ui_default', 'По умолчанию'],
['root_permission_denied', 'Отказано в доступе'],
['root_permission_no_permission', 'У вас нет разрешения на это действие'],
@@ -146,7 +146,7 @@ export default [
['ui_project', 'Project'],
['ui_type', 'Type'],
['ui_field_is_required', 'Please fill in this field'],
['ui_delete_confirmation_message', 'You are about to delete "{arg:item}". Are you sure?'],
['ui_delete_confirmation_message', 'You are about to delete {arg:item}. Are you sure?'],
['ui_not_selected', 'Not selected'],
['ui_reset', 'Reset'],
['ui_documentaion', '文档'],
@@ -0,0 +1,5 @@
query getConnectionTypes {
types: listConnectionTypes {
...ConnectionType
}
}
@@ -0,0 +1,18 @@
fragment ConnectionType on ConnectionType {
id
name
description
colorLight
colorDark
autocommit
confirmExecute
confirmDataChange
smartCommit
smartCommitRecover
autoCloseTransactions
closeIdleTransactionPeriod
autoCloseConnections
closeIdleConnectionPeriod
predefined
}
@@ -13,4 +13,5 @@ fragment DatabaseConnection on ConnectionInfo {
canViewSettings
canEdit
canDelete
connectionType
}
@@ -28,7 +28,7 @@
}
&:not(:global([aria-selected='true'])) {
background-color: transparent !important;
background-color: transparent;
}
}
+4 -3
View File
@@ -13,19 +13,19 @@ import { getComputed, s, useS, useTranslate } from '@cloudbeaver/core-blocks';
import { TabContext } from '../TabContext.js';
import { useTabDragAndDrop } from '../useTabDragAndDrop.js';
import style from './Tab.module.css';
import componentStyles from './Tab.module.css';
import { TabActions } from './TabActions.js';
import type { TabProps } from './TabProps.js';
import { useTab } from './useTab.js';
export const Tab = observer<TabProps>(function Tab({ after, ...props }) {
const translate = useTranslate();
const { tabId, title, disabled, className, children, onOpen, onClose, onClick } = props;
const { tabId, title, disabled, className, style, children, onOpen, onClose, onClick } = props;
const ref = useRef<HTMLButtonElement>(null);
const tabContext = useMemo(() => ({ tabId }), [tabId]);
const tab = useTab(tabId, onOpen, onClose, onClick);
const info = tab.getInfo();
const styles = useS(style);
const styles = useS(componentStyles);
const canClose = getComputed(() => !!onClose || (tab.closable && tab.state.closable));
const {
@@ -76,6 +76,7 @@ export const Tab = observer<TabProps>(function Tab({ after, ...props }) {
title={translate(title ?? info?.title)}
id={tabId}
className={s(styles, { tab: true }, className)}
style={style}
disabled={disabled}
tabbable
onClick={tab.handleOpen}
@@ -18,6 +18,7 @@ export type TabProps = PropsWithChildren<{
selected?: boolean;
disabled?: boolean;
className?: string;
style?: React.CSSProperties;
after?: React.ReactElement;
onOpen?: (tab: ITabData<any>) => void;
onClose?: (tab: ITabData<any>) => void;
@@ -48,6 +48,7 @@
"@cloudbeaver/plugin-top-app-bar": "workspace:*",
"@dbeaver/jdbc-uri-parser": "workspace:*",
"@dbeaver/js-helpers": "workspace:*",
"@dbeaver/ui-kit": "workspace:*",
"mobx": "^6",
"mobx-react-lite": "^4",
"react": "^19",
@@ -37,4 +37,5 @@ export const CONNECTION_CONFIG_SCHEMA = schema.object({
url: schema.string().optional(),
userName: schema.string().optional(),
userPassword: schema.string().optional(),
connectionType: schema.string().optional(),
});
@@ -58,6 +58,7 @@ const defaultStateGetter = (connectionId?: string, credentials?: Record<string,
expertSettingsValues: {},
networkHandlersConfig: [],
providerProperties: {},
connectionType: 'dev',
}) as IConnectionFormOptionsState;
export class ConnectionFormOptionsPart extends FormPart<IConnectionFormOptionsState, IConnectionFormState> {
@@ -200,10 +201,11 @@ export class ConnectionFormOptionsPart extends FormPart<IConnectionFormOptionsSt
return;
}
const [authPropertiesInfo, customOptionsInfo, providerPropertiesInfo] = await Promise.all([
const [authPropertiesInfo, customOptionsInfo, providerPropertiesInfo, connection] = await Promise.all([
this.connectionInfoAuthPropertiesResource.load(this.connectionKey),
this.connectionInfoCustomOptionsResource.load(this.connectionKey),
this.connectionInfoProviderPropertiesResource.load(this.connectionKey),
this.connectionInfoResource.load(this.connectionKey),
]);
const config: ConnectionConfig = defaultStateGetter();
@@ -223,6 +225,8 @@ export class ConnectionFormOptionsPart extends FormPart<IConnectionFormOptionsSt
config.url = customOptionsInfo.url;
config.folder = customOptionsInfo.folder;
config.connectionType = connection.connectionType;
config.authModelId = authPropertiesInfo.authModel;
config.saveCredentials = authPropertiesInfo.credentialsSaved;
config.sharedCredentials = authPropertiesInfo.sharedCredentials;
@@ -0,0 +1,48 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { Container, Group, GroupTitle, Select, useResource, useTranslate } from '@cloudbeaver/core-blocks';
import { CachedMapAllKey } from '@cloudbeaver/core-resource';
import { useService } from '@cloudbeaver/core-di';
import { isNotNullDefined } from '@dbeaver/js-helpers';
import { ColorIndicator } from '@dbeaver/ui-kit';
import { ConnectionTypeResource, ConnectionTypeService, DEFAULT_TYPE_ID, MAPPED_PREDEFINED_UNSET_COLOR } from '@cloudbeaver/core-connections';
import type { ConnectionConfig } from '@cloudbeaver/core-sdk';
interface Props {
config: ConnectionConfig;
}
export const ConnectionTypeForm = observer<Props>(function ConnectionTypeForm({ config }) {
const translate = useTranslate();
const connectionTypeService = useService(ConnectionTypeService);
const connectionTypeResource = useResource(ConnectionTypeForm, ConnectionTypeResource, CachedMapAllKey);
const types = connectionTypeResource.data.filter(isNotNullDefined);
return (
<Group form gap>
<GroupTitle>{translate('plugin_connections_connection_type_label')}</GroupTitle>
<Container gap dense>
<Select
items={types}
name="connectionType"
state={config}
keySelector={t => t.id}
valueSelector={t => (t.id === DEFAULT_TYPE_ID ? `${t.name} (${translate('ui_default')})` : t.name)}
iconSelector={t => {
const color = connectionTypeService.getTypeColor(t.id);
return <ColorIndicator className="tw:w-3.5! tw:h-3.5!" color={color ?? MAPPED_PREDEFINED_UNSET_COLOR} />;
}}
/>
</Container>
</Group>
);
});
@@ -64,6 +64,7 @@ import { ProviderPropertiesForm } from './ProviderPropertiesForm.js';
import { getConnectionFormOptionsPart } from './getConnectionFormOptionsPart.js';
import type { IConnectionFormProps } from '../IConnectionFormState.js';
import { ConnectionFormService } from '../ConnectionFormService.js';
import { ConnectionTypeForm } from './ConnectionTypeForm.js';
const PROFILE_AUTH_MODEL_ID = 'profile';
@@ -309,6 +310,7 @@ export const Options: TabContainerPanelComponent<IConnectionFormProps> = observe
)}
</Group>
)}
<ConnectionTypeForm config={optionsPart.state} />
<Placeholder container={connectionFormService.connectionContainer} formState={formState} />
</Container>
<Container medium gap>
@@ -1,15 +1,19 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { ConnectionTypeResource } from '@cloudbeaver/core-connections';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
@injectable()
@injectable(() => [ConnectionTypeResource])
export class PluginBootstrap extends Bootstrap {
constructor() {
super();
}
override async load(): Promise<void> {}
}
@@ -44,4 +44,6 @@ export default [
['plugin_connections_connection_client_version', 'Client version: {arg:version} \n'],
['plugin_connections_connection_server_version', 'Server version: {arg:version} \n'],
['plugin_connections_connection_connection_time', 'Connection time: {arg:time} \n'],
['plugin_connections_connection_type_label', 'Connection type'],
];
@@ -47,4 +47,6 @@ export default [
['plugin_connections_connection_client_version', 'Версия клиента: {arg:version} \n'],
['plugin_connections_connection_server_version', 'Версия сервера: {arg:version} \n'],
['plugin_connections_connection_connection_time', 'Время подключения: {arg:time} \n'],
['plugin_connections_connection_type_label', 'Тип подключения'],
];
@@ -7,6 +7,9 @@
"composite": true
},
"references": [
{
"path": "../../common-react/@dbeaver/ui-kit"
},
{
"path": "../../common-typescript/@dbeaver/jdbc-uri-parser"
},
@@ -29,6 +29,7 @@ import {
import { useService } from '@cloudbeaver/core-di';
import { ResultDataFormat } from '@cloudbeaver/core-sdk';
import { CaptureView } from '@cloudbeaver/core-view';
import { createConnectionParam, useConnectionTypeColor } from '@cloudbeaver/core-connections';
import { type IDatabaseDataOptions } from '../DatabaseDataModel/IDatabaseDataOptions.js';
import { DataPresentationService, DataPresentationType } from '../DataPresentationService.js';
@@ -44,6 +45,7 @@ import { TableToolsPanel } from './TableToolsPanel.js';
import style from './TableViewer.module.css';
import { TableViewerStorageService } from './TableViewerStorageService.js';
import { IDatabaseDataConstraintAction } from '../DatabaseDataModel/Actions/IDatabaseDataConstraintAction.js';
import { ResultSetDataSource } from '../ResultSet/ResultSetDataSource.js';
export interface TableViewerProps {
tableId: string;
@@ -182,6 +184,10 @@ export const TableViewer = observer<TableViewerProps, HTMLDivElement>(
// }
// }, [dataFormat]);
const context = dataModel?.source instanceof ResultSetDataSource ? dataModel.source.executionContext?.context : undefined;
const connectionKey = context ? createConnectionParam(context.projectId, context.connectionId) : undefined;
const typeColor = useConnectionTypeColor(connectionKey);
if (!dataModel) {
return <TextPlaceholder>{translate('plugin_data_viewer_no_available_presentation')}</TextPlaceholder>;
}
@@ -208,7 +214,7 @@ export const TableViewer = observer<TableViewerProps, HTMLDivElement>(
return (
<CaptureView className={s(styles, { captureView: true })} view={dataViewerView}>
<div ref={mergedRef} tabIndex={0} className={s(styles, { tableViewer: true }, className)}>
<div ref={mergedRef} tabIndex={0} className={s(styles, { tableViewer: true }, className)} style={{ background: typeColor }}>
<div className={s(styles, { tableContent: true })}>
{!isStatistics && (
<TablePresentationBar
@@ -292,7 +298,14 @@ export const TableViewer = observer<TableViewerProps, HTMLDivElement>(
/>
)}
</div>
<TableFooter model={dataModel} resultIndex={resultIndex} simple={simple} tabIndex={0} data-presentation-tools />
<TableFooter
style={{ background: typeColor }}
model={dataModel}
resultIndex={resultIndex}
simple={simple}
tabIndex={0}
data-presentation-tools
/>
</div>
</CaptureView>
);
@@ -1,18 +1,23 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
.connectionIcon {
position: relative;
display: flex;
align-items: center;
}
.connectionImageWithMask {
border-radius: var(--theme-form-element-radius);
&.small {
box-sizing: border-box;
}
}
@@ -1,25 +1,28 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import { useDeferredValue } from 'react';
import { s, useS, useVisible } from '@cloudbeaver/core-blocks';
import { useConnectionTypeColor } from '@cloudbeaver/core-connections';
import { isNotNullDefined } from '@dbeaver/js-helpers';
import styles from './ConnectionIcon.module.css';
import ConnectionImageWithMaskSvgBackgroundStyles from './ConnectionImageWithMask.module.css';
import { ConnectionIconLazyPart, type ConnectionIconProps } from './ConnectionIconLazyPart.js';
import { useDeferredValue } from 'react';
export const ConnectionIcon = observer<ConnectionIconProps>(function ConnectionIcon({ className, connectionKey, ...props }) {
const style = useS(styles, ConnectionImageWithMaskSvgBackgroundStyles);
const { isVisible, setRef } = useVisible();
const deferredIsVisible = useDeferredValue(isVisible);
const typeColor = useConnectionTypeColor(connectionKey ?? undefined);
if (!isNotNullDefined(connectionKey)) {
return null;
@@ -27,6 +30,7 @@ export const ConnectionIcon = observer<ConnectionIconProps>(function ConnectionI
return (
<div ref={setRef} className={s(style, { connectionIcon: true }, className)}>
{typeColor && <div className="tw:h-5 tw:w-1 tw:mr-1 tw:rounded-xs" style={{ background: typeColor }} />}
{deferredIsVisible && <ConnectionIconLazyPart connectionKey={connectionKey} {...props} />}
</div>
);
@@ -25,6 +25,7 @@
"@cloudbeaver/core-app": "workspace:*",
"@cloudbeaver/core-authentication": "workspace:*",
"@cloudbeaver/core-blocks": "workspace:*",
"@cloudbeaver/core-connections": "workspace:*",
"@cloudbeaver/core-di": "workspace:*",
"@cloudbeaver/core-events": "workspace:*",
"@cloudbeaver/core-executor": "workspace:*",
@@ -14,6 +14,7 @@ import { Loader, SContext, type StyleRegistry, useExecutor } from '@cloudbeaver/
import { useService } from '@cloudbeaver/core-di';
import { type ITabData, TabList, TabPanel, TabsState, TabStyles } from '@cloudbeaver/core-ui';
import { CaptureView } from '@cloudbeaver/core-view';
import { useConnectionTypeLoader } from '@cloudbeaver/core-connections';
import { clsx } from '@dbeaver/ui-kit';
import { NavigationTabsService } from '../NavigationTabsService.js';
@@ -48,6 +49,8 @@ export const NavigationTabsBar = observer<Props>(function NavigationTabsBar({ cl
const handleSelect = useCallback((tabId: string) => navigation.selectTab(tabId), [navigation]);
const handleClose = useCallback((tabId: string) => navigation.closeTab(tabId), [navigation]);
useConnectionTypeLoader();
function unloadTabs() {
navigation.unloadTabs();
}
@@ -107,7 +110,12 @@ export const NavigationTabsBar = observer<Props>(function NavigationTabsBar({ cl
onReorder={handleReorder}
>
<div className="tw:outline-none tw:flex-1 tw:flex tw:flex-col tw:max-w-full">
<TabList className={clsx("tw:overflow-auto tw:max-w-full theme-background-secondary theme-text-on-secondary", navigationSettings.hasMultipleRows && 'tw:flex-wrap')}>
<TabList
className={clsx(
'tw:overflow-auto tw:max-w-full theme-background-secondary theme-text-on-secondary',
navigationSettings.hasMultipleRows && 'tw:flex-wrap',
)}
>
<SContext registry={tabsRegistry}>
{navigation.tabIdList.map(tabId => (
<TabHandlerTab key={tabId} tabId={tabId} onSelect={handleSelect} onClose={handleClose} />
@@ -28,6 +28,9 @@
{
"path": "../core-cli"
},
{
"path": "../core-connections"
},
{
"path": "../core-di"
},
@@ -26,6 +26,7 @@ import {
import { useService } from '@cloudbeaver/core-di';
import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events';
import { EObjectFeature, NavNodeInfoResource, NavTreeResource } from '@cloudbeaver/core-navigation-tree';
import { createConnectionParam, useConnectionTypeColor } from '@cloudbeaver/core-connections';
import type { NavTreeControlComponent, NavTreeControlProps } from '../ElementsTree/NavigationNodeComponent.js';
import style from '../ElementsTree/NavigationTreeNode/NavigationNode/NavigationNodeControl.module.css';
@@ -74,10 +75,14 @@ export const ConnectionNavNodeControl: NavTreeControlComponent = observer<NavTre
tooltip += `\n${translate('ui_type')}: ${translate('core_connections_connection_temporary')}`;
}
const key = node.projectId && node.objectId ? createConnectionParam(node.projectId, node.objectId) : undefined;
const typeColor = useConnectionTypeColor(key);
return (
<TreeNodeControl
ref={mergedRef}
className={s(styles, { treeNodeControl: true, dragging: !!dndElement }, className)}
style={{ background: typeColor }}
onClick={onClick}
onContextMenu={handleContextMenuOpen}
>
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -8,9 +8,10 @@
import { observer } from 'mobx-react-lite';
import { Translate, TreeNodeNestedMessage } from '@cloudbeaver/core-blocks';
import { NAV_NODE_TYPE_CONNECTION } from '@cloudbeaver/core-connections';
import { createConnectionParam, NAV_NODE_TYPE_CONNECTION, useConnectionTypeColor } from '@cloudbeaver/core-connections';
import { useService } from '@cloudbeaver/core-di';
import { NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree';
import { clsx } from '@dbeaver/ui-kit';
import type { NavigationNodeRendererComponent } from '../ElementsTree/NavigationNodeComponent.js';
import { NavigationNodeRendererLoader } from '../ElementsTree/NavigationTreeNode/NavigationNodeRendererLoader.js';
@@ -40,6 +41,9 @@ const ConnectionRenderer: NavigationNodeRendererComponent = observer(function Ma
const navNodeInfoResource = useService(NavNodeInfoResource);
const node = navNodeInfoResource.get(nodeId);
const connectionKey = node?.projectId && node.objectId ? createConnectionParam(node.projectId, node.objectId) : undefined;
const typeColor = useConnectionTypeColor(connectionKey);
if (!node) {
return (
<TreeNodeNestedMessage>
@@ -54,7 +58,11 @@ const ConnectionRenderer: NavigationNodeRendererComponent = observer(function Ma
path={path}
expanded={expanded}
dragging={dragging}
className={className}
style={typeColor ? ({ ['--connection-type-color']: typeColor } as React.CSSProperties) : undefined}
className={clsx(
typeColor && 'tw:[&_[data-tree-node-control]]:border-l-2 tw:[&_[data-tree-node-control]]:border-l-[var(--connection-type-color)]',
className,
)}
control={ConnectionNavNodeControl}
component={component}
/>
@@ -18,6 +18,7 @@ export type NavTreeNodeComponent = React.FC<{
expanded?: boolean;
big?: boolean;
className?: string;
style?: React.CSSProperties;
}>;
export type NavTreeControlProps = {
@@ -27,6 +28,7 @@ export type NavTreeControlProps = {
dndPlaceholder?: boolean;
expanded?: boolean;
className?: string;
style?: React.CSSProperties;
onClick?: (event: React.MouseEvent<HTMLDivElement>) => void;
};
@@ -40,6 +42,7 @@ export type NavigationNodeComponent = React.FC<{
dragging?: boolean;
expanded?: boolean;
className?: string;
style?: React.CSSProperties;
}>;
export type NavigationNodeRendererComponent = React.FC<{
@@ -49,4 +52,5 @@ export type NavigationNodeRendererComponent = React.FC<{
expanded?: boolean;
dragging?: boolean;
className?: string;
style?: React.CSSProperties;
}>;
@@ -16,7 +16,7 @@ import { useDNDData } from '@cloudbeaver/core-ui';
import { useNavTreeDropBox } from '../../useNavTreeDropBox.js';
import type { NavigationNodeComponent } from '../NavigationNodeComponent.js';
import style from './NavigationNode.module.css';
import componentStyle from './NavigationNode.module.css';
import { DATA_ATTRIBUTE_NODE_EDITING } from './NavigationNode/DATA_ATTRIBUTE_NODE_EDITING.js';
import { NavigationNodeNested } from './NavigationNode/NavigationNodeNested.js';
import { NavigationNodeControlRenderer } from './NavigationNodeControlRenderer.js';
@@ -29,8 +29,9 @@ export const NavigationNode: NavigationNodeComponent = observer(function Navigat
control: externalControl,
expanded: externalExpanded,
className,
style,
}) {
const styles = useS(style);
const styles = useS(componentStyle);
const navNodeManagerService = useService(NavNodeManagerService);
const navNode = useNavigationNode(node, path);
const context = useDataContext();
@@ -96,6 +97,7 @@ export const NavigationNode: NavigationNodeComponent = observer(function Navigat
externalExpanded={externalExpanded}
leaf={navNode.leaf}
className={s(styles, { treeNode: true, hovered: hasNodes, expanded: externalExpanded ?? navNode.expanded }, className)}
style={style}
onExpand={navNode.expand}
onClick={navNode.click}
onOpen={navNode.open}
@@ -26,5 +26,5 @@
.icon {
width: 16px;
height: 10px;
fill: #dedede;
fill: #4d4b4bbd;
}
@@ -13,6 +13,7 @@ import { useService } from '@cloudbeaver/core-di';
import { NavNodeInfoResource, NavTreeResource, ProjectsNavNodeService, ROOT_NODE_PATH } from '@cloudbeaver/core-navigation-tree';
import { ProjectsService } from '@cloudbeaver/core-projects';
import { CaptureView } from '@cloudbeaver/core-view';
import { useConnectionTypeLoader } from '@cloudbeaver/core-connections';
import { ElementsTreeToolsStyles } from '../index.js';
import { NavNodeViewService } from '../NodesManager/NavNodeView/NavNodeViewService.js';
@@ -61,6 +62,8 @@ export const NavigationTree = observer(function NavigationTree() {
const navTreeResource = useService(NavTreeResource);
const navNodeViewService = useService(NavNodeViewService);
useConnectionTypeLoader();
const root = ROOT_NODE_PATH;
const { handleOpen, handleSelect, handleSelectReset } = useNavigationTree();
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -49,7 +49,7 @@ export const VirtualFolderPanel: NavNodeTransformViewComponent = observer(functi
<TextPlaceholder>{translate('plugin_object_viewer_table_no_items')}</TextPlaceholder>
) : (
<div className={classes['tabWrapper']}>
<TableLoader objects={objects} hasNextPage={pagination?.hasNextPage ?? false} loadMore={pagination.loadMore} />
<TableLoader objects={objects} objectId={nodeId} hasNextPage={pagination?.hasNextPage ?? false} loadMore={pagination.loadMore} />
</div>
)}
</>
@@ -115,13 +115,15 @@ export const ObjectFolders = observer<IProps>(function ObjectFolders({ tab }) {
return folders.length > 0 ? (
<TabsState currentTabId={folderId} orientation="vertical" localState={innerTabState} lazy onChange={openFolder}>
<SContext registry={objectFoldersRegistry}>
<TabList aria-label="Object folders" vertical>
{folders.map(folderId => (
<FolderTabRenderer key={folderId} nodeId={nodeId} folderId={folderId} parents={parents} />
))}
</TabList>
<div>
<TabList aria-label="Object folders" vertical>
{folders.map(folderId => (
<FolderTabRenderer key={folderId} nodeId={nodeId} folderId={folderId} parents={parents} />
))}
</TabList>
</div>
{folders.map(folderId => (
<TabPanel key={folderId} tabId={folderId}>
<TabPanel key={folderId} className="theme-background-surface" tabId={folderId}>
<FolderPanelRenderer key={folderId} nodeId={nodeId} folderId={folderId} parents={parents} />
</TabPanel>
))}
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -8,6 +8,7 @@
import { observer } from 'mobx-react-lite';
import { s, useS } from '@cloudbeaver/core-blocks';
import { useConnectionTypeColor } from '@cloudbeaver/core-connections';
import type { ObjectPagePanelComponent } from '../ObjectPage/ObjectPage.js';
import { ObjectFolders } from './ObjectFolders.js';
@@ -15,9 +16,10 @@ import classes from './ObjectPropertiesPagePanel.module.css';
export const ObjectPropertiesPagePanel: ObjectPagePanelComponent = observer(function ObjectPropertiesPagePanel({ tab }) {
const styles = useS(classes);
const typeColor = useConnectionTypeColor(tab.handlerState.connectionKey);
return (
<div className={s(styles, { wrapper: true })}>
<div className={s(styles, { wrapper: true })} style={{ background: typeColor }}>
<ObjectFolders tab={tab} />
</div>
);
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2025 DBeaver Corp and others
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -50,7 +50,7 @@ export const ObjectPropertyTable = observer<ObjectPropertyTableProps>(function O
<TextPlaceholder>{translate('plugin_object_viewer_table_no_items')}</TextPlaceholder>
) : (
<div className={s(styles, { box: true }, className)}>
<TableLoader objects={objects} hasNextPage={pagination.hasNextPage} loadMore={pagination.loadMore} />
<TableLoader objects={objects} hasNextPage={pagination.hasNextPage} objectId={objectId} loadMore={pagination.loadMore} />
</div>
)}
</>
@@ -20,9 +20,10 @@ import { MENU_OBJECT_VIEWER_FOOTER } from './MENU_OBJECT_VIEWER_FOOTER.js';
interface Props {
state: TableState;
className?: string;
style?: React.CSSProperties;
}
export const ObjectPropertyTableFooter = observer<Props>(function ObjectPropertyTableFooter({ state, className }) {
export const ObjectPropertyTableFooter = observer<Props>(function ObjectPropertyTableFooter({ state, className, style }) {
const styles = useS(MenuBarStyles, MenuBarItemStyles, MenuBarGroupStyles);
const navNodeInfoResource = useService(NavNodeInfoResource);
const menu = useMenu({ menu: MENU_OBJECT_VIEWER_FOOTER });
@@ -35,5 +36,5 @@ export const ObjectPropertyTableFooter = observer<Props>(function ObjectProperty
context.set(DATA_CONTEXT_NAV_NODES, getSelected, id);
});
return <MenuBar className={s(styles, { floating: true, withLabel: true }, className)} menu={menu} compact={false} />;
return <MenuBar className={s(styles, { floating: true, withLabel: true }, className)} style={style} menu={menu} compact={false} />;
});
@@ -16,19 +16,22 @@ import { useTabLocalState } from '@cloudbeaver/core-ui';
import { DataGrid, useCreateGridReactiveValue } from '@cloudbeaver/plugin-data-grid';
import { getObjectPropertyDisplayValue, getObjectPropertyType, getObjectPropertyValue } from '@cloudbeaver/core-sdk';
import { CheckboxIndicator } from '@dbeaver/ui-kit';
import { useNode } from '@cloudbeaver/plugin-navigation-tree';
import { ObjectPropertyTableFooter } from '../ObjectPropertyTableFooter.js';
import classes from './Table.module.css';
import { ObjectMenuCell } from './ObjectMenuCell.js';
import { SelectorFormatter } from './Columns/ColumnSelect/SelectorFormatter.js';
import { createConnectionParam, useConnectionTypeColor } from '@cloudbeaver/core-connections';
export interface TableProps {
export interface ITableProps {
objects: DBObject[];
hasNextPage: boolean;
objectId: string;
loadMore: () => void;
}
export const Table = observer<TableProps>(function Table({ objects, hasNextPage, loadMore }) {
export const Table = observer<ITableProps>(function Table({ objects, hasNextPage, objectId, loadMore }) {
const styles = useS(classes);
const navTreeResource = useService(NavTreeResource);
@@ -37,6 +40,7 @@ export const Table = observer<TableProps>(function Table({ objects, hasNextPage,
const tableState = useTable();
const tabLocalState = useTabLocalState<IScrollState>(() => ({ scrollTop: 0, scrollLeft: 0 }));
const { node } = useNode(objectId);
const scrollBox = tableContainer?.querySelector<HTMLDivElement>('[role="grid"]') ?? null;
useControlledScroll(scrollBox, tabLocalState);
@@ -135,6 +139,9 @@ export const Table = observer<TableProps>(function Table({ objects, hasNextPage,
return colIdx !== 0;
}
const connectionKey = node?.objectId && node.projectId ? createConnectionParam(node.projectId, node.objectId) : undefined;
const typeColor = useConnectionTypeColor(connectionKey);
if (objects.length === 0) {
return null;
}
@@ -160,7 +167,7 @@ export const Table = observer<TableProps>(function Table({ objects, hasNextPage,
</Link>
</div>
)}
<ObjectPropertyTableFooter className={s(styles, { objectPropertyTableFooter: true })} state={tableState} />
<ObjectPropertyTableFooter className={s(styles, { objectPropertyTableFooter: true })} state={tableState} style={{ background: typeColor }} />
</div>
);
});
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -9,12 +9,12 @@ import { observer } from 'mobx-react-lite';
import { useContext } from 'react';
import { useObjectInfoTooltip } from '@cloudbeaver/core-blocks';
import { ConnectionInfoResource } from '@cloudbeaver/core-connections';
import { ConnectionInfoResource, useConnectionTypeColor } from '@cloudbeaver/core-connections';
import { useDataContext } from '@cloudbeaver/core-data-context';
import { useService } from '@cloudbeaver/core-di';
import { NavNodeManagerService } from '@cloudbeaver/core-navigation-tree';
import { ProjectInfoResource } from '@cloudbeaver/core-projects';
import { type ITabData, Tab, TabIcon, TabTitle } from '@cloudbeaver/core-ui';
import { type ITabData, Tab, TabIcon, TabTitle, useTab } from '@cloudbeaver/core-ui';
import { CaptureViewContext } from '@cloudbeaver/core-view';
import type { TabHandlerTabComponent } from '@cloudbeaver/plugin-navigation-tabs';
import { useNode } from '@cloudbeaver/plugin-navigation-tree';
@@ -36,9 +36,18 @@ export const ObjectViewerTab: TabHandlerTabComponent<IObjectViewerTabState> = ob
const nodeInfo = node ? navNodeManagerService.getNodeContainerInfo(node.uri) : undefined;
const tooltip = useObjectInfoTooltip(connection?.name, nodeInfo?.catalogId, nodeInfo?.schemaId, project?.name);
const { selected } = useTab(tab.id);
const typeColor = useConnectionTypeColor(tab.handlerState.connectionKey);
return (
<Tab tabId={tab.id} title={`${title}${tooltip ? '\n' + tooltip : ''}`} menuContext={tabMenuContext} onOpen={handleSelect} onClose={handleClose}>
<Tab
style={{ backgroundColor: selected ? undefined : typeColor }}
tabId={tab.id}
title={`${title}${tooltip ? '\n' + tooltip : ''}`}
menuContext={tabMenuContext}
onOpen={handleSelect}
onClose={handleClose}
>
<TabIcon icon={node?.icon || tab.handlerState.tabIcon} />
<TabTitle>{title}</TabTitle>
</Tab>
@@ -1,6 +1,6 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
* Copyright (C) 2020-2026 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
@@ -9,11 +9,11 @@ import { observer } from 'mobx-react-lite';
import { useContext } from 'react';
import { IconOrImage, s, useObjectInfoTooltip, useTranslate } from '@cloudbeaver/core-blocks';
import { ConnectionInfoResource, createConnectionParam } from '@cloudbeaver/core-connections';
import { ConnectionInfoResource, createConnectionParam, useConnectionTypeColor } from '@cloudbeaver/core-connections';
import { useDataContext, useDataContextLink } from '@cloudbeaver/core-data-context';
import { useService } from '@cloudbeaver/core-di';
import { ProjectInfoResource } from '@cloudbeaver/core-projects';
import { type ITabData, Tab, TabIcon, TabTitle } from '@cloudbeaver/core-ui';
import { type ITabData, Tab, TabIcon, TabTitle, useTab } from '@cloudbeaver/core-ui';
import { CaptureViewContext } from '@cloudbeaver/core-view';
import type { TabHandlerTabComponent } from '@cloudbeaver/plugin-navigation-tabs';
import {
@@ -32,6 +32,8 @@ export const SqlEditorTab: TabHandlerTabComponent<ISqlEditorTabState> = observer
const tabMenuContext = useDataContext(viewContext);
const handlerState = tab.handlerState;
const { selected } = useTab(tab.id);
useDataContextLink(tabMenuContext, (context, id) => {
context.set(DATA_CONTEXT_SQL_EDITOR_TAB, true, id);
context.set(DATA_CONTEXT_SQL_EDITOR_STATE, handlerState, id);
@@ -61,9 +63,18 @@ export const SqlEditorTab: TabHandlerTabComponent<ISqlEditorTabState> = observer
const handleClose = onClose ? ({ tabId }: ITabData<any>) => onClose(tabId) : undefined;
const tooltip = useObjectInfoTooltip(connection?.name, executionContext?.defaultCatalog, executionContext?.defaultSchema, project?.name);
const connectionKey = executionContext ? createConnectionParam(executionContext.projectId, executionContext.connectionId) : undefined;
const typeColor = useConnectionTypeColor(connectionKey);
return (
<Tab tabId={tab.id} title={`${name}${tooltip ? '\n' + tooltip : ''}`} menuContext={tabMenuContext} onOpen={handleSelect} onClose={handleClose}>
<Tab
style={{ backgroundColor: selected ? undefined : typeColor }}
tabId={tab.id}
title={`${name}${tooltip ? '\n' + tooltip : ''}`}
menuContext={tabMenuContext}
onOpen={handleSelect}
onClose={handleClose}
>
<TabIcon icon={icon} />
<TabTitle>{name}</TabTitle>
{isReadonly && isScript && (
@@ -15,7 +15,7 @@ import type { ISqlEditorTabState } from '../ISqlEditorTabState.js';
import { DATA_CONTEXT_SQL_EDITOR_DATA } from './DATA_CONTEXT_SQL_EDITOR_DATA.js';
import { DATA_CONTEXT_SQL_EDITOR_STATE } from '../DATA_CONTEXT_SQL_EDITOR_STATE.js';
import type { ISQLEditorData } from './ISQLEditorData.js';
import style from './SQLEditorActions.module.css';
import componentStyle from './SQLEditorActions.module.css';
import { SqlEditorActionsMenu } from './SqlEditorActionsMenu.js';
import { SqlEditorTools } from './SqlEditorTools.js';
import { SQL_EDITOR_ACTIONS_MENU } from './SQL_EDITOR_ACTIONS_MENU.js';
@@ -24,10 +24,11 @@ interface Props {
data: ISQLEditorData;
state: ISqlEditorTabState;
className?: string;
style?: React.CSSProperties;
}
export const SQLEditorActions = observer<Props>(function SQLEditorActions({ data, state, className }) {
const styles = useS(style);
export const SQLEditorActions = observer<Props>(function SQLEditorActions({ data, state, className, style }) {
const styles = useS(componentStyle);
const menu = useMenu({ menu: SQL_EDITOR_ACTIONS_MENU });
useDataContextLink(menu.context, (context, id) => {
@@ -38,7 +39,7 @@ export const SQLEditorActions = observer<Props>(function SQLEditorActions({ data
});
return (
<div className={s(styles, { container: true }, className)}>
<div className={s(styles, { container: true }, className)} style={style}>
<div className={s(styles, { actions: true })} onMouseDown={preventFocusHandler}>
<SqlEditorActionsMenu state={state} context={menu.context} />
</div>
@@ -13,6 +13,7 @@ import { useService } from '@cloudbeaver/core-di';
import { type ITabData, TabList, TabListStyles, TabPanelList, TabsState, TabStyles } from '@cloudbeaver/core-ui';
import { MetadataMap } from '@cloudbeaver/core-utils';
import { useCaptureViewContext } from '@cloudbeaver/core-view';
import { createConnectionParam, useConnectionTypeColor } from '@cloudbeaver/core-connections';
import { type ISqlEditorModeProps, SqlEditorModeService } from '../SqlEditorModeService.js';
import { DATA_CONTEXT_SQL_EDITOR_DATA } from './DATA_CONTEXT_SQL_EDITOR_DATA.js';
@@ -33,6 +34,7 @@ export const SqlEditor = observer<ISqlEditorProps>(function SqlEditor({ state, c
const split = useSplit();
const style = useS(styles, SqlEditorTab);
const sqlEditorModeService = useService(SqlEditorModeService);
const data = useSqlEditor(state);
useActiveQuery(data);
const [modesState] = useState(() => new MetadataMap<string, any>());
@@ -60,6 +62,10 @@ export const SqlEditor = observer<ISqlEditorProps>(function SqlEditor({ state, c
}
}, [isEditorEmpty]);
const context = data.model.dataSource?.executionContext;
const connectionKey = context ? createConnectionParam(context.projectId, context.connectionId) : undefined;
const typeColor = useConnectionTypeColor(connectionKey);
return (
<TabsState
currentTabId={state.currentModeId}
@@ -72,10 +78,10 @@ export const SqlEditor = observer<ISqlEditorProps>(function SqlEditor({ state, c
>
<SContext registry={sqlEditorRegistry}>
<div className={s(style, { sqlEditor: true }, className)}>
<SQLEditorActions data={data} state={state} />
<SQLEditorActions data={data} state={state} style={{ background: typeColor }} />
<TabPanelList />
{displayedEditors > 1 ? (
<div className={s(style, { tabs: true })}>
<div className={s(style, { tabs: true })} style={{ background: typeColor }}>
<TabList vertical rotated />
</div>
) : null}
+3
View File
@@ -1688,6 +1688,7 @@ __metadata:
"@cloudbeaver/core-root": "workspace:*"
"@cloudbeaver/core-sdk": "workspace:*"
"@cloudbeaver/core-settings": "workspace:*"
"@cloudbeaver/core-theming": "workspace:*"
"@cloudbeaver/core-utils": "workspace:*"
"@cloudbeaver/tests-runner": "workspace:*"
"@cloudbeaver/tsconfig": "workspace:*"
@@ -2896,6 +2897,7 @@ __metadata:
"@cloudbeaver/tsconfig": "workspace:*"
"@dbeaver/jdbc-uri-parser": "workspace:*"
"@dbeaver/js-helpers": "workspace:*"
"@dbeaver/ui-kit": "workspace:*"
"@types/react": "npm:^19"
mobx: "npm:^6"
mobx-react-lite: "npm:^4"
@@ -3576,6 +3578,7 @@ __metadata:
"@cloudbeaver/core-authentication": "workspace:*"
"@cloudbeaver/core-blocks": "workspace:*"
"@cloudbeaver/core-cli": "workspace:*"
"@cloudbeaver/core-connections": "workspace:*"
"@cloudbeaver/core-di": "workspace:*"
"@cloudbeaver/core-events": "workspace:*"
"@cloudbeaver/core-executor": "workspace:*"