Merge remote-tracking branch 'origin/devel' into CB-2344-projects-administration

This commit is contained in:
Wroud
2022-09-12 17:14:25 +03:00
13 changed files with 133 additions and 56 deletions
@@ -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!;
};
}
+2 -1
View File
@@ -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,
],
};
@@ -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>,
@@ -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> {