mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-16 14:50:15 +08:00
Merge remote-tracking branch 'origin/devel' into CB-2344-projects-administration
This commit is contained in:
@@ -57,7 +57,6 @@ const buttonStyles = css`
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
const buttonMod = {
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import styled, { css } from 'reshadow';
|
||||
import styled, { css, use } from 'reshadow';
|
||||
|
||||
import { uuid } from '@cloudbeaver/core-utils';
|
||||
|
||||
@@ -19,6 +19,10 @@ interface Props extends React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLIn
|
||||
const styles = css`
|
||||
label {
|
||||
cursor: pointer;
|
||||
|
||||
&[|disabled] {
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -39,7 +43,7 @@ export const UploadArea: React.FC<Props> = function UploadArea({ id, reset, clas
|
||||
return styled(styles)(
|
||||
<>
|
||||
<input {...rest} type='file' id={_id} hidden onChange={handleChange} />
|
||||
<label htmlFor={_id} className={className} title={rest.title}>
|
||||
<label htmlFor={_id} {...use({ disabled: rest.disabled })} className={className} title={rest.title}>
|
||||
{children}
|
||||
</label>
|
||||
</>
|
||||
|
||||
@@ -21,24 +21,38 @@
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-button__label {
|
||||
@extend .mdc-button__label;
|
||||
}
|
||||
|
||||
.theme-button__icon {
|
||||
@extend .mdc-button__icon;
|
||||
}
|
||||
|
||||
.theme-button_ripple {
|
||||
@extend .mdc-button__ripple;
|
||||
}
|
||||
|
||||
.theme-button_raised {
|
||||
@extend .mdc-button--raised;
|
||||
}
|
||||
|
||||
.theme-button_unelevated {
|
||||
@extend .mdc-button--unelevated;
|
||||
}
|
||||
|
||||
.theme-button_outlined {
|
||||
@extend .mdc-button--outlined;
|
||||
|
||||
&[disabled] {
|
||||
border-color: change-color($color: $mdc-theme-on-surface, $alpha: 0.37);
|
||||
color: change-color($color: $mdc-theme-on-surface, $alpha: 0.37);
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.theme-button_secondary {
|
||||
@include mdc-button-ink-color(secondary, $query);
|
||||
@include mdc-states($color: secondary, $query: $query, $ripple-target: $mdc-button-ripple-target);
|
||||
@@ -54,6 +68,7 @@
|
||||
@include mdc-states($color: on-secondary, $query: $query, $ripple-target: $mdc-button-ripple-target);
|
||||
}
|
||||
}
|
||||
|
||||
.theme-button_background {
|
||||
@include mdc-button-ink-color(background, $query);
|
||||
@include mdc-states($color: background, $query: $query, $ripple-target: $mdc-button-ripple-target);
|
||||
@@ -69,4 +84,4 @@
|
||||
@include mdc-states($color: on-secondary, $query: $query, $ripple-target: $mdc-button-ripple-target);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
export type LastPromiseGetterKey = Array<string | number | boolean | undefined | null>;
|
||||
export type LastPromiseGetter<R> = (key: LastPromiseGetterKey, query: () => Promise<R>) => Promise<R>;
|
||||
export function createLastPromiseGetter<R>(): LastPromiseGetter<R> {
|
||||
let lastKey: LastPromiseGetterKey | undefined = undefined;
|
||||
let lastPromise: Promise<R> | undefined = undefined;
|
||||
|
||||
return async (key: LastPromiseGetterKey, getter: () => Promise<R>) => {
|
||||
const currentKeyStr = JSON.stringify(key);
|
||||
const lastKeyStr = lastKey ? JSON.stringify(lastKey) : undefined;
|
||||
|
||||
if (currentKeyStr !== lastKeyStr) {
|
||||
lastKey = key;
|
||||
lastPromise = getter();
|
||||
}
|
||||
|
||||
return lastPromise!;
|
||||
};
|
||||
}
|
||||
@@ -44,4 +44,5 @@ export * from './createPath';
|
||||
export * from './debounce';
|
||||
export * from './svgToDataUri';
|
||||
export * from './uriToBlob';
|
||||
export * from './utf8ToBase64';
|
||||
export * from './utf8ToBase64';
|
||||
export * from './createLastPromiseGetter';
|
||||
@@ -202,7 +202,8 @@ export const SSH: TabContainerPanelComponent<Props> = observer(function SSH({
|
||||
onChange={handleKeyUpload}
|
||||
>
|
||||
<Button
|
||||
tag='div'
|
||||
tag="div"
|
||||
disabled={disabled || readonly || !enabled}
|
||||
mod={['outlined']}
|
||||
>
|
||||
{translate('ui_file')}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 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 { PluginManagerService, PluginSettings } from '@cloudbeaver/core-plugin';
|
||||
|
||||
export interface ConnectionsSettings {
|
||||
hideConnectionViewForUsers: boolean;
|
||||
}
|
||||
|
||||
const defaultSettings: ConnectionsSettings = {
|
||||
hideConnectionViewForUsers: false,
|
||||
};
|
||||
|
||||
@injectable()
|
||||
export class ConnectionsSettingsService {
|
||||
readonly settings: PluginSettings<ConnectionsSettings>;
|
||||
|
||||
constructor(private readonly pluginManagerService: PluginManagerService) {
|
||||
this.settings = this.pluginManagerService.getPluginSettings('connections', defaultSettings);
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,15 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { EAdminPermission } from '@cloudbeaver/core-administration';
|
||||
import { Connection, ConnectionInfoResource, ConnectionsManagerService, createConnectionParam } from '@cloudbeaver/core-connections';
|
||||
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { DATA_CONTEXT_NAV_NODE, EObjectFeature, NavNodeManagerService } from '@cloudbeaver/core-navigation-tree';
|
||||
import { CONNECTION_NAVIGATOR_VIEW_SETTINGS, isNavigatorViewSettingsEqual, NavigatorViewSettings } from '@cloudbeaver/core-root';
|
||||
import { CONNECTION_NAVIGATOR_VIEW_SETTINGS, isNavigatorViewSettingsEqual, NavigatorViewSettings, PermissionsService } from '@cloudbeaver/core-root';
|
||||
import { ActionService, ACTION_DELETE, DATA_CONTEXT_MENU, DATA_CONTEXT_MENU_NESTED, MenuSeparatorItem, MenuService } from '@cloudbeaver/core-view';
|
||||
|
||||
import { ConnectionsSettingsService } from '../ConnectionsSettingsService';
|
||||
import { PublicConnectionFormService } from '../PublicConnectionForm/PublicConnectionFormService';
|
||||
import { ACTION_CONNECTION_DISCONNECT } from './Actions/ACTION_CONNECTION_DISCONNECT';
|
||||
import { ACTION_CONNECTION_EDIT } from './Actions/ACTION_CONNECTION_EDIT';
|
||||
@@ -32,6 +34,8 @@ export class ConnectionMenuBootstrap extends Bootstrap {
|
||||
private readonly actionService: ActionService,
|
||||
private readonly menuService: MenuService,
|
||||
private readonly publicConnectionFormService: PublicConnectionFormService,
|
||||
private readonly connectionsSettingsService: ConnectionsSettingsService,
|
||||
private readonly permissionsService: PermissionsService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
@@ -39,6 +43,13 @@ export class ConnectionMenuBootstrap extends Bootstrap {
|
||||
register(): void | Promise<void> {
|
||||
this.menuService.addCreator({
|
||||
isApplicable: context => {
|
||||
if (
|
||||
this.connectionsSettingsService.settings.getValue('hideConnectionViewForUsers')
|
||||
&& !this.permissionsService.has(EAdminPermission.admin)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const connection = context.tryGet(DATA_CONTEXT_CONNECTION);
|
||||
|
||||
if (!connection?.connected) {
|
||||
|
||||
@@ -14,6 +14,7 @@ import { ConnectionDriverPropertiesTabService } from './ConnectionForm/DriverPro
|
||||
import { ConnectionOptionsTabService } from './ConnectionForm/Options/ConnectionOptionsTabService';
|
||||
import { ConnectionOriginInfoTabService } from './ConnectionForm/OriginInfo/ConnectionOriginInfoTabService';
|
||||
import { ConnectionSSHTabService } from './ConnectionForm/SSH/ConnectionSSHTabService';
|
||||
import { ConnectionsSettingsService } from './ConnectionsSettingsService';
|
||||
import { ConnectionMenuBootstrap } from './ContextMenu/ConnectionMenuBootstrap';
|
||||
import { LocaleService } from './LocaleService';
|
||||
import { ConnectionFoldersBootstrap } from './NavNodes/ConnectionFoldersBootstrap';
|
||||
@@ -37,5 +38,6 @@ export const connectionPlugin: PluginManifest = {
|
||||
ConnectionSSHTabService,
|
||||
ConnectionOriginInfoTabService,
|
||||
ConnectionFoldersBootstrap,
|
||||
ConnectionsSettingsService,
|
||||
],
|
||||
};
|
||||
|
||||
+5
-1
@@ -73,7 +73,11 @@ export const NavigationTabsBar = observer<Props>(function NavigationTabsBar({ cl
|
||||
}, []);
|
||||
|
||||
if (navigation.tabIdList.length === 0) {
|
||||
return <TextPlaceholder>{translate('app_shared_navigationTabsBar_placeholder')}</TextPlaceholder>;
|
||||
return (
|
||||
<TextPlaceholder>
|
||||
{translate('app_shared_navigationTabsBar_placeholder')}
|
||||
</TextPlaceholder>
|
||||
);
|
||||
}
|
||||
|
||||
return styled(style)(
|
||||
|
||||
@@ -60,7 +60,11 @@ export interface ISQLEditorData {
|
||||
executeQueryNewTab(): Promise<void>;
|
||||
showExecutionPlan(): Promise<void>;
|
||||
executeScript(): Promise<void>;
|
||||
getHintProposals(position: number, simple: boolean): Promise<SQLProposal[]>;
|
||||
getHintProposals(
|
||||
position: number,
|
||||
word: string,
|
||||
simple: boolean
|
||||
): Promise<SQLProposal[]>;
|
||||
executeQueryAction<T>(
|
||||
segment: ISQLScriptSegment | undefined,
|
||||
action: (query: ISQLScriptSegment) => Promise<T>,
|
||||
|
||||
+5
-38
@@ -18,16 +18,11 @@ import { action } from 'mobx';
|
||||
import type { IControlledCodeMirror } from 'react-codemirror2';
|
||||
|
||||
import { useExecutor, useObservableRef } from '@cloudbeaver/core-blocks';
|
||||
import type { SqlCompletionProposal } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import type { ISQLEditorData } from '../ISQLEditorData';
|
||||
import type { SQLCodeEditorController } from '../SQLCodeEditor/SQLCodeEditorController';
|
||||
|
||||
interface ISQLCodeEditorPanelData {
|
||||
proposalsWordFrom: Position | null;
|
||||
proposalsWord: string | null;
|
||||
proposalsMode: boolean | null;
|
||||
proposals: SqlCompletionProposal[] | null;
|
||||
readonly activeSuggest: boolean;
|
||||
readonly bindings: Omit<IControlledCodeMirror, 'value'>;
|
||||
closeHint(): void;
|
||||
@@ -51,7 +46,6 @@ export function useSQLCodeEditorPanel(
|
||||
controller: SQLCodeEditorController | null
|
||||
): ISQLCodeEditorPanelData {
|
||||
const editorPanelData = useObservableRef<ISQLCodeEditorPanelDataPrivate>(() => ({
|
||||
proposals: null,
|
||||
activeSuggest: true,
|
||||
options: {
|
||||
theme: 'material',
|
||||
@@ -225,38 +219,11 @@ export function useSQLCodeEditorPanel(
|
||||
: from
|
||||
);
|
||||
|
||||
const proposalWord = word.slice(0, 1);
|
||||
let proposals = editorPanelData.proposals;
|
||||
|
||||
if (
|
||||
proposals === null
|
||||
|| editorPanelData.proposalsWord !== proposalWord
|
||||
|| editorPanelData.proposalsMode !== options.completeSingle
|
||||
|| editorPanelData.proposalsWordFrom?.ch !== from.ch
|
||||
|| editorPanelData.proposalsWordFrom.line !== from.line
|
||||
|| !word.startsWith(editorPanelData.proposalsWord)
|
||||
) {
|
||||
const proposalsMode = options.completeSingle ?? false;
|
||||
editorPanelData.proposalsMode = proposalsMode;
|
||||
editorPanelData.proposalsWord = proposalWord;
|
||||
editorPanelData.proposalsWordFrom = from;
|
||||
editorPanelData.proposals = [];
|
||||
|
||||
proposals = await editorPanelData.data.getHintProposals(cursorPosition, !options.completeSingle);
|
||||
|
||||
if (
|
||||
editorPanelData.proposalsWord === proposalWord
|
||||
&& editorPanelData.proposalsWordFrom === from
|
||||
&& editorPanelData.proposalsMode === proposalsMode
|
||||
) {
|
||||
editorPanelData.proposals = proposals;
|
||||
|
||||
if (editor.state.completionActive && proposals.length > 0) {
|
||||
editor.state.completionActive.update();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
let proposals = await editorPanelData.data.getHintProposals(
|
||||
cursorPosition,
|
||||
word,
|
||||
!options.completeSingle
|
||||
);
|
||||
|
||||
proposals = proposals.filter(
|
||||
({ displayString }) => (
|
||||
|
||||
@@ -14,8 +14,8 @@ import { ConnectionExecutionContextService, createConnectionParam } from '@cloud
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { CommonDialogService, ConfirmationDialog, DialogueStateResult } from '@cloudbeaver/core-dialogs';
|
||||
import { SyncExecutor } from '@cloudbeaver/core-executor';
|
||||
import type { SqlDialectInfo } from '@cloudbeaver/core-sdk';
|
||||
import { throttleAsync } from '@cloudbeaver/core-utils';
|
||||
import type { SqlCompletionProposal, SqlDialectInfo } from '@cloudbeaver/core-sdk';
|
||||
import { createLastPromiseGetter, LastPromiseGetter, throttleAsync } from '@cloudbeaver/core-utils';
|
||||
|
||||
import type { ISqlEditorTabState } from '../ISqlEditorTabState';
|
||||
import type { ISqlDataSource } from '../SqlDataSource/ISqlDataSource';
|
||||
@@ -38,6 +38,7 @@ interface ISQLEditorDataPrivate extends ISQLEditorData {
|
||||
readonly commonDialogService: CommonDialogService;
|
||||
readonly sqlResultTabsService: SqlResultTabsService;
|
||||
readonly dataSource: ISqlDataSource | undefined;
|
||||
readonly getLastAutocomplete: LastPromiseGetter<SqlCompletionProposal[]>;
|
||||
|
||||
cursor: ICursor;
|
||||
readonlyState: boolean;
|
||||
@@ -179,22 +180,37 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData {
|
||||
this.onUpdate.execute();
|
||||
},
|
||||
|
||||
getHintProposals: throttleAsync(async function getHintProposals(this: ISQLEditorDataPrivate, position, simple) {
|
||||
getLastAutocomplete: createLastPromiseGetter(),
|
||||
|
||||
getHintProposals: throttleAsync(async function getHintProposals(
|
||||
this: ISQLEditorDataPrivate,
|
||||
position,
|
||||
word,
|
||||
simple
|
||||
) {
|
||||
if (!this.dataSource?.executionContext) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const proposals = await this.sqlEditorService
|
||||
const { connectionId, id, defaultSchema, defaultCatalog } = this.dataSource.executionContext;
|
||||
|
||||
return this.getLastAutocomplete([
|
||||
connectionId,
|
||||
id,
|
||||
defaultSchema,
|
||||
defaultCatalog,
|
||||
position,
|
||||
simple,
|
||||
word.slice(0, 1),
|
||||
], () => this.sqlEditorService
|
||||
.getAutocomplete(
|
||||
this.dataSource.executionContext.connectionId,
|
||||
this.dataSource.executionContext.id,
|
||||
connectionId,
|
||||
id,
|
||||
this.value,
|
||||
position,
|
||||
undefined,
|
||||
simple
|
||||
);
|
||||
|
||||
return proposals;
|
||||
));
|
||||
}, 1000 / 3),
|
||||
|
||||
async formatScript(): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user