From 2dcd165ced11de5b5206f5a6437a165656f687d8 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Mon, 13 Dec 2021 22:12:51 +0300 Subject: [PATCH 01/11] fix(plugin-data-viewer): CB-1021 change the data viewer header filter logic --- .../DataGridContextMenuFilterService.ts | 8 +- .../DataGridContextMenuOrderService.ts | 4 +- .../TableColumnHeader/OrderButton.tsx | 2 +- .../src/DataViewerTabService.ts | 4 +- .../Actions/DELETE_CONSTRAINTS_ACTION.ts | 15 +++ .../Actions/REFRESH_RESULT_SET_ACTION.ts | 15 +++ .../DatabaseDataModel/DatabaseDataModel.ts | 8 ++ .../DatabaseDataModel/DatabaseDataSource.ts | 13 ++- .../DatabaseDataModel/IDatabaseDataModel.ts | 1 + .../DatabaseDataModel/IDatabaseDataSource.ts | 2 + .../DATA_CONTEXT_TABLE_HEADER_MODEL.ts | 14 +++ .../DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX.ts | 11 +++ .../TableHeader/TABLE_HEADER_MENU.ts | 11 +++ .../TableViewer/TableHeader/TableHeader.tsx | 4 +- .../TableHeader/TableHeaderMenu.tsx | 98 +++++++++++++++++++ .../TableHeader/TableHeaderService.ts | 91 +++++++++++++++++ .../TableHeader/TableWhereFilter.tsx | 64 ++---------- .../TableViewer/TableHeader/useWhereFilter.ts | 81 +++++++++++++++ .../src/TableViewer/TableViewer.tsx | 3 +- .../plugin-data-viewer/src/locales/en.ts | 1 + .../plugin-data-viewer/src/locales/it.ts | 1 + .../plugin-data-viewer/src/locales/ru.ts | 1 + .../plugin-data-viewer/src/locales/zh.ts | 59 +++++------ 23 files changed, 415 insertions(+), 96 deletions(-) create mode 100644 webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/DELETE_CONSTRAINTS_ACTION.ts create mode 100644 webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/REFRESH_RESULT_SET_ACTION.ts create mode 100644 webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_TABLE_HEADER_MODEL.ts create mode 100644 webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX.ts create mode 100644 webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TABLE_HEADER_MENU.ts create mode 100644 webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderMenu.tsx create mode 100644 webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/useWhereFilter.ts diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/DataGridContextMenuFilterService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/DataGridContextMenuFilterService.ts index 5937a2b323..c18f1f1985 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/DataGridContextMenuFilterService.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuFilter/DataGridContextMenuFilterService.ts @@ -62,7 +62,7 @@ export class DataGridContextMenuFilterService { await model.requestDataAction(async () => { constraints.setFilter(columnLabel, operator, filterValue); - await model.refresh(true); + await model.request(true); }); } @@ -160,7 +160,7 @@ export class DataGridContextMenuFilterService { await model.requestDataAction(async () => { constraints.deleteData(); - await model.refresh(true); + await model.request(true); }); }, } @@ -403,7 +403,7 @@ export class DataGridContextMenuFilterService { await model.requestDataAction(async () => { constraints.deleteFilter(columnLabel); - await model.refresh(true); + await model.request(true); }); }, } @@ -430,7 +430,7 @@ export class DataGridContextMenuFilterService { await model.requestDataAction(async () => { constraints.deleteDataFilters(); - await model.refresh(true); + await model.request(true); }); }, } diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuOrderService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuOrderService.ts index 6585324ca8..8aaa42ea8e 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuOrderService.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuOrderService.ts @@ -35,7 +35,7 @@ export class DataGridContextMenuOrderService { await model.requestDataAction(async () => { constraints.setOrder(columnLabel, order, true); - await model.refresh(true); + await model.request(true); }); } @@ -143,7 +143,7 @@ export class DataGridContextMenuOrderService { const constraints = context.data.model.source.getAction(context.data.resultIndex, ResultSetConstraintAction); await context.data.model.requestDataAction(async () => { constraints.deleteOrders(); - await context.data.model.refresh(true); + await context.data.model.request(true); }); }, } diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/OrderButton.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/OrderButton.tsx index 22fb1f0169..5f7dfa1773 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/OrderButton.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/OrderButton.tsx @@ -70,7 +70,7 @@ export const OrderButton = observer(function OrderButton({ const nextOrder = getNextOrder(currentOrder); await model.requestDataAction(async () => { constraints.setOrder(attribute, nextOrder, e.ctrlKey || e.metaKey); - await model.refresh(true); + await model.request(true); }); }; diff --git a/webapp/packages/plugin-data-viewer/src/DataViewerTabService.ts b/webapp/packages/plugin-data-viewer/src/DataViewerTabService.ts index 0e55375980..79ea99a8cf 100644 --- a/webapp/packages/plugin-data-viewer/src/DataViewerTabService.ts +++ b/webapp/packages/plugin-data-viewer/src/DataViewerTabService.ts @@ -126,7 +126,7 @@ export class DataViewerTabService { // TODO: used for initial data fetch, but can repeat request each time data tab is selected, // so probably should be refactored and managed by presentation if (model.source.error === null && model.source.results.length === 0) { - model.refresh(); + model.request(); } } @@ -143,7 +143,7 @@ export class DataViewerTabService { await model.requestDataAction(() => { canClose = true; }); - } catch {} + } catch { } return canClose; } diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/DELETE_CONSTRAINTS_ACTION.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/DELETE_CONSTRAINTS_ACTION.ts new file mode 100644 index 0000000000..9a83a4a116 --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/DELETE_CONSTRAINTS_ACTION.ts @@ -0,0 +1,15 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { createAction } from '@cloudbeaver/core-view'; + +export const DELETE_CONSTRAINTS_ACTION = createAction('delete-constraints', { + label: 'data_grid_table_delete_filters_and_orders', + tooltip: 'data_grid_table_delete_filters_and_orders', + icon: 'erase', +}); diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/REFRESH_RESULT_SET_ACTION.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/REFRESH_RESULT_SET_ACTION.ts new file mode 100644 index 0000000000..7e6851b4d9 --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/REFRESH_RESULT_SET_ACTION.ts @@ -0,0 +1,15 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { createAction } from '@cloudbeaver/core-view'; + +export const REFRESH_RESULT_SET_ACTION = createAction('refresh-result-set', { + label: 'ui_refresh', + tooltip: 'data_viewer_refresh_result_set', + icon: 'reload', +}); diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataModel.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataModel.ts index dd80d521f1..bcad9c66fa 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataModel.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataModel.ts @@ -118,6 +118,14 @@ implements IDatabaseDataModel { } async refresh(concurrent?: boolean): Promise { + if (concurrent) { + await this.source.refreshData(); + return; + } + await this.requestDataAction(() => this.source.refreshData()); + } + + async request(concurrent?: boolean): Promise { if (concurrent) { await this.source.requestData(); return; diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts index 19ca8f307e..b3f7349812 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import { observable, makeObservable, action } from 'mobx'; +import { observable, makeObservable, action, toJS } from 'mobx'; import type { IConnectionExecutionContext } from '@cloudbeaver/core-connections'; import { ResultDataFormat } from '@cloudbeaver/core-sdk'; @@ -26,6 +26,7 @@ implements IDatabaseDataSource { results: TResult[]; offset: number; count: number; + prevOptions: Readonly | null; options: TOptions | null; requestInfo: IRequestInfo; error: Error | null; @@ -45,6 +46,7 @@ implements IDatabaseDataSource { this.results = []; this.offset = 0; this.count = 0; + this.prevOptions = null; this.options = null; this.disabled = false; this.activeRequest = null; @@ -69,6 +71,7 @@ implements IDatabaseDataSource { results: observable, offset: observable, count: observable, + prevOptions: observable, options: observable, requestInfo: observable, error: observable.ref, @@ -276,6 +279,13 @@ implements IDatabaseDataSource { } } + async refreshData(): Promise { + if (this.prevOptions) { + this.options = toJS(this.prevOptions); + } + await this.requestData(); + } + async saveData(): Promise { if (this.activeRequest) { try { @@ -318,6 +328,7 @@ implements IDatabaseDataSource { abstract dispose(): Promise; async requestDataAction(): Promise { + this.prevOptions = toJS(this.options); return this.request(this.results); } } diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts index b4df88841c..b7568cd363 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataModel.ts @@ -45,6 +45,7 @@ export interface IDatabaseDataModel Promise; save: () => Promise; refresh: (concurrent?: boolean) => Promise; + request: (concurrent?: boolean) => Promise; reload: () => Promise; requestDataPortion: (offset: number, count: number) => Promise; cancel: () => Promise | void; diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts index e5a6bee7da..6cd6388f42 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts @@ -33,6 +33,7 @@ export interface IDatabaseDataSource | null; readonly options: TOptions | null; readonly requestInfo: IRequestInfo; readonly error: Error | null; @@ -80,6 +81,7 @@ export interface IDatabaseDataSource Promise; runTask: (task: () => Promise) => Promise; requestData: () => Promise | void; + refreshData: () => Promise | void; saveData: () => Promise | void; cancel: () => Promise | void; clearError: () => void; diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_TABLE_HEADER_MODEL.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_TABLE_HEADER_MODEL.ts new file mode 100644 index 0000000000..9b8f342f7a --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_TABLE_HEADER_MODEL.ts @@ -0,0 +1,14 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { createDataContext } from '@cloudbeaver/core-view'; + +import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel'; +import type { IDatabaseDataOptions } from '../../DatabaseDataModel/IDatabaseDataOptions'; + +export const DATA_CONTEXT_TABLE_HEADER_MODEL = createDataContext>('table-model'); diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX.ts new file mode 100644 index 0000000000..3328ef6aa1 --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX.ts @@ -0,0 +1,11 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { createDataContext } from '@cloudbeaver/core-view'; + +export const DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX = createDataContext('table-result-index'); diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TABLE_HEADER_MENU.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TABLE_HEADER_MENU.ts new file mode 100644 index 0000000000..99213e81a6 --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TABLE_HEADER_MENU.ts @@ -0,0 +1,11 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { createMenu } from '@cloudbeaver/core-view'; + +export const TABLE_HEADER_MENU = createMenu('table-header', 'Table header menu'); diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeader.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeader.tsx index 374b07ed37..02cc79a389 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeader.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeader.tsx @@ -27,16 +27,18 @@ const styles = css` interface Props { model: IDatabaseDataModel; resultIndex: number; + className?: string; } export const TableHeader = observer(function TableHeader({ model, resultIndex, + className, }) { const service = useService(TableHeaderService); return styled(styles)( - + ); diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderMenu.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderMenu.tsx new file mode 100644 index 0000000000..53889f365b --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderMenu.tsx @@ -0,0 +1,98 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { css } from 'reshadow'; + +import type { PlaceholderComponent } from '@cloudbeaver/core-blocks'; +import { composes } from '@cloudbeaver/core-theming'; +import { MenuBar } from '@cloudbeaver/core-ui'; +import { useMenu } from '@cloudbeaver/core-view'; + +import { DATA_CONTEXT_TABLE_HEADER_MODEL } from './DATA_CONTEXT_TABLE_HEADER_MODEL'; +import { DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX } from './DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX'; +import { TABLE_HEADER_MENU } from './TABLE_HEADER_MENU'; +import type { ITableHeaderPlaceholderProps } from './TableHeaderService'; + +const TABLE_HEADER_MENU_BAR_STYLES = composes( + css` + menu-bar { + composes: theme-border-color-background theme-background-surface theme-text-on-surface from global; + } + menu-bar-item { + composes: theme-ripple from global; + } + MenuSeparator { + composes: theme-border-color-background from global; + } +`, + css` + menu-bar { + composes: theme-typography--body2 from global; + display: flex; + margin-left: 8px; + box-sizing: border-box; + border: 1px solid; + height: 24px; + } + + menu-bar-item { + padding: 4px; + display: flex; + align-items: center; + cursor: pointer; + background: transparent; + outline: none; + color: inherit; + + &[use|hidden] { + display: none; + } + + & IconOrImage { + display: block; + width: 16px; + } + + & Loader { + width: 16px; + } + + & item-label { + display: block; + text-transform: uppercase; + font-weight: 700; + } + + & IconOrImage + item-label, & Loader + item-label { + padding-left: 8px + } + } + + MenuSeparator { + height: 100%; + margin: 0; + border: 0; + border-right: 1px solid; + } + ` +); + +export const TableHeaderMenu: PlaceholderComponent = observer(function TableHeaderMenu({ + model, + resultIndex, +}) { + const menu = useMenu(TABLE_HEADER_MENU); + + menu.context.set(DATA_CONTEXT_TABLE_HEADER_MODEL, model); + menu.context.set(DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX, resultIndex); + + return ( + + ); +}); diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderService.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderService.ts index c076c11ccc..e4e347ffb1 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderService.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderService.ts @@ -8,8 +8,16 @@ import { PlaceholderContainer } from '@cloudbeaver/core-blocks'; import { injectable, Bootstrap } from '@cloudbeaver/core-di'; +import { MenuService, ActionService, DATA_CONTEXT_MENU, MenuSeparatorItem } from '@cloudbeaver/core-view'; +import { DELETE_CONSTRAINTS_ACTION } from '../../DatabaseDataModel/Actions/ResultSet/Actions/DELETE_CONSTRAINTS_ACTION'; +import { REFRESH_RESULT_SET_ACTION } from '../../DatabaseDataModel/Actions/ResultSet/Actions/REFRESH_RESULT_SET_ACTION'; +import { ResultSetConstraintAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetConstraintAction'; import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel'; +import { DATA_CONTEXT_TABLE_HEADER_MODEL } from './DATA_CONTEXT_TABLE_HEADER_MODEL'; +import { DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX } from './DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX'; +import { TABLE_HEADER_MENU } from './TABLE_HEADER_MENU'; +import { TableHeaderMenu } from './TableHeaderMenu'; import { TableWhereFilter } from './TableWhereFilter'; export interface ITableHeaderPlaceholderProps { @@ -21,8 +29,91 @@ export interface ITableHeaderPlaceholderProps { export class TableHeaderService extends Bootstrap { readonly tableHeaderPlaceholder = new PlaceholderContainer(); + constructor( + private readonly menuService: MenuService, + private readonly actionService: ActionService + ) { + super(); + } + register(): void { this.tableHeaderPlaceholder.add(TableWhereFilter, 1); + this.tableHeaderPlaceholder.add(TableHeaderMenu, 2); + + this.actionService.addHandler({ + id: 'table-header-menu-base-handler', + isActionApplicable(context, action) { + const menu = context.find(DATA_CONTEXT_MENU, TABLE_HEADER_MENU); + const model = context.tryGet(DATA_CONTEXT_TABLE_HEADER_MODEL); + const resultIndex = context.tryGet(DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX); + + if (!menu || !model || resultIndex === undefined) { + return false; + } + + return true; + }, + handler: async (context, action) => { + switch (action) { + case DELETE_CONSTRAINTS_ACTION: { + const model = context.get(DATA_CONTEXT_TABLE_HEADER_MODEL); + const resultIndex = context.get(DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX); + const constraints = model.source.tryGetAction(resultIndex, ResultSetConstraintAction); + + if (constraints) { + constraints.deleteData(); + await model.request(); + } + break; + } + case REFRESH_RESULT_SET_ACTION: { + const model = context.get(DATA_CONTEXT_TABLE_HEADER_MODEL); + await model.refresh(); + break; + } + } + }, + getActionInfo: (context, action) => { + if (context.get(DATA_CONTEXT_MENU) === TABLE_HEADER_MENU) { + return { ...action.info, label: '' }; + } + + return action.info; + }, + isDisabled: (context, action) => { + const model = context.get(DATA_CONTEXT_TABLE_HEADER_MODEL); + const resultIndex = context.get(DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX); + + if (model.isLoading() || model.isDisabled(resultIndex)) { + return true; + } + + if (action === DELETE_CONSTRAINTS_ACTION) { + const constraints = model.source.tryGetAction(resultIndex, ResultSetConstraintAction); + + if (constraints) { + return constraints.filterConstraints.length === 0 && constraints.orderConstraints.length === 0; + } + } + + if (action === REFRESH_RESULT_SET_ACTION) { + return false; + } + + return true; + }, + }); + + this.menuService.addCreator({ + isApplicable: context => context.get(DATA_CONTEXT_MENU) === TABLE_HEADER_MENU, + getItems: (context, items) => [ + ...items, + DELETE_CONSTRAINTS_ACTION, + new MenuSeparatorItem(), + REFRESH_RESULT_SET_ACTION, + + ], + }); } load(): void | Promise { } diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableWhereFilter.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableWhereFilter.tsx index 3575f6387e..f4e1864619 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableWhereFilter.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableWhereFilter.tsx @@ -7,16 +7,15 @@ */ import { observer } from 'mobx-react-lite'; -import { useCallback } from 'react'; import styled, { css } from 'reshadow'; import { InlineEditor } from '@cloudbeaver/core-app'; -import { PlaceholderComponent, useObjectRef } from '@cloudbeaver/core-blocks'; +import type { PlaceholderComponent } from '@cloudbeaver/core-blocks'; import { useTranslate } from '@cloudbeaver/core-localization'; import { composes, useStyles } from '@cloudbeaver/core-theming'; -import { ResultSetConstraintAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetConstraintAction'; import type { ITableHeaderPlaceholderProps } from './TableHeaderService'; +import { useWhereFilter } from './useWhereFilter'; const styles = composes( css` @@ -37,65 +36,20 @@ export const TableWhereFilter: PlaceholderComponent 0 && model.source.requestInfo.requestFilter) { - filterValue = model.source.requestInfo.requestFilter; - } - } - - const setValue = useCallback((filterValue: string) => { - model.source.options.whereFilter = filterValue; - - const constraints = model.source.tryGetAction(resultIndex, ResultSetConstraintAction); - constraints?.deleteFilters(); - }, [model.source.options]); - - const props = useObjectRef({ model, resultIndex, filterValue }); - - const handleApply = useCallback(() => { - const { model, resultIndex } = props; - if (model.isLoading() || model.isDisabled(resultIndex)) { - return; - } - model.refresh(); - }, []); - - const resetFilter = useCallback(async () => { - const { model, resultIndex } = props; - const constraints = model.source.getAction(resultIndex, ResultSetConstraintAction); - if (model.isLoading() || model.isDisabled(resultIndex)) { - return; - } - - await model.requestDataAction(async () => { - constraints.deleteDataFilters(); - - const applyNeeded = !!model.requestInfo.requestFilter; - if (applyNeeded) { - await model.refresh(true); - } - }); - }, []); + const state = useWhereFilter(model, resultIndex); return styled(useStyles(styles))( ); }); diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/useWhereFilter.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/useWhereFilter.ts new file mode 100644 index 0000000000..17a9556e87 --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/useWhereFilter.ts @@ -0,0 +1,81 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { action, computed } from 'mobx'; + +import { useObservableRef } from '@cloudbeaver/core-blocks'; + +import { ResultSetConstraintAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetConstraintAction'; +import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel'; +import type { IDatabaseDataOptions } from '../../DatabaseDataModel/IDatabaseDataOptions'; + +interface IState { + model: IDatabaseDataModel; + resultIndex: number; + readonly filter: string; + readonly constraints: ResultSetConstraintAction | null; + readonly disabled: boolean; + readonly applicableFilter: boolean; + changeHandler: (value: string) => void; + applyFilter: () => Promise; +} + +export function useWhereFilter( + model: IDatabaseDataModel, + resultIndex: number +): Readonly { + return useObservableRef(() => ({ + get filter() { + if (this.constraints?.filterConstraints.length && this.model.source.requestInfo.requestFilter) { + return this.model.requestInfo.requestFilter; + } + + return this.model.source.options?.whereFilter ?? ''; + }, + get constraints() { + if (!this.model.source.hasResult(this.resultIndex)) { + return null; + } + + return this.model.source.tryGetAction(this.resultIndex, ResultSetConstraintAction) ?? null; + }, + get disabled() { + const supported = this.constraints?.supported ?? false; + return !supported || this.model.isLoading() || this.model.isDisabled(resultIndex); + }, + get applicableFilter() { + return this.model.source.prevOptions?.whereFilter !== this.model.source.options?.whereFilter + || this.model.source.options?.whereFilter !== this.model.source.requestInfo.requestFilter; + }, + changeHandler(value: string) { + if (this.constraints) { + this.constraints.deleteFilters(); + } + + if (this.model.source.options) { + this.model.source.options.whereFilter = value; + } + }, + async applyFilter() { + if (!this.applicableFilter || this.model.isLoading() || this.model.isDisabled(this.resultIndex)) { + return; + } + + await this.model.request(); + }, + }), + { + filter: computed, + constraints: computed, + disabled: computed, + applicableFilter: computed, + changeHandler: action.bound, + applyFilter: action.bound, + }, + { model, resultIndex }); +} diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewer.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewer.tsx index 9878810717..1a1ef08f4d 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewer.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewer.tsx @@ -77,6 +77,7 @@ const viewerStyles = composes( margin: 0; } TablePresentationBar { + padding-top: 40px; &:first-child { margin-right: 4px; } @@ -235,7 +236,6 @@ export const TableViewer = observer(function TableViewer({ return styled(styles)( - (function TableViewer({ onPresentationChange={dataTableActions.setPresentation} /> + diff --git a/webapp/packages/plugin-data-viewer/src/locales/en.ts b/webapp/packages/plugin-data-viewer/src/locales/en.ts index 179eb364e6..2386f8b81a 100644 --- a/webapp/packages/plugin-data-viewer/src/locales/en.ts +++ b/webapp/packages/plugin-data-viewer/src/locales/en.ts @@ -27,4 +27,5 @@ export default [ ['data_viewer_script_preview', 'Script'], ['data_viewer_script_preview_dialog_title', 'Preview changes'], ['data_viewer_script_preview_error_title', "Can't get the script"], + ['data_viewer_refresh_result_set', 'Refresh result set'], ]; diff --git a/webapp/packages/plugin-data-viewer/src/locales/it.ts b/webapp/packages/plugin-data-viewer/src/locales/it.ts index 9606b766cb..ec99f8acda 100644 --- a/webapp/packages/plugin-data-viewer/src/locales/it.ts +++ b/webapp/packages/plugin-data-viewer/src/locales/it.ts @@ -21,4 +21,5 @@ export default [ ['data_viewer_presentation_value_image_fit', 'Adatta alla Finestra'], ['data_viewer_presentation_value_image_original_size', 'Dimensioni Originali'], ['data_viewer_presentation_value_boolean_placeholder', 'Non posso rappresentare il valore corrente come booleano'], + ['data_viewer_refresh_result_set', 'Refresh result set'], ]; diff --git a/webapp/packages/plugin-data-viewer/src/locales/ru.ts b/webapp/packages/plugin-data-viewer/src/locales/ru.ts index f7b2bfe54b..51c22a192c 100644 --- a/webapp/packages/plugin-data-viewer/src/locales/ru.ts +++ b/webapp/packages/plugin-data-viewer/src/locales/ru.ts @@ -23,4 +23,5 @@ export default [ ['data_viewer_script_preview', 'Скрипт'], ['data_viewer_script_preview_dialog_title', 'Предпросмотр изменений'], ['data_viewer_script_preview_error_title', 'Не удалось получить скрипт'], + ['data_viewer_refresh_result_set', 'Обновить резалт сет'], ]; diff --git a/webapp/packages/plugin-data-viewer/src/locales/zh.ts b/webapp/packages/plugin-data-viewer/src/locales/zh.ts index 06baf2b3f0..706066462b 100644 --- a/webapp/packages/plugin-data-viewer/src/locales/zh.ts +++ b/webapp/packages/plugin-data-viewer/src/locales/zh.ts @@ -1,30 +1,31 @@ export default [ - ['table_header_sql_expression', '输入SQL表达式以过滤结果'], - ['data_viewer_tab_title', '数据'], - ['data_viewer_value_edit', '编辑'], - ['data_viewer_value_apply', '应用'], - ['data_viewer_value_revert', '还原'], - ['data_viewer_value_revert_title', '还原未保存的更改'], - ['data_viewer_nodata_message', '没有数据展示'], - ['data_viewer_statistics_status', '状态:'], - ['data_viewer_statistics_duration', '用时:'], - ['data_viewer_statistics_updated_rows', '更新行:'], - ['data_viewer_action_edit_delete', '删除选中'], - ['data_viewer_action_edit_add', '添加'], - ['data_viewer_action_edit_revert', '还原选中'], - ['data_viewer_result_edited_title', '保存更改'], - ['data_viewer_result_edited_message', '结果集已编辑。是否将更改保存到数据库?'], - ['data_viewer_presentation_value_title', '值'], - ['data_viewer_presentation_value_text_title', '文本'], - ['data_viewer_presentation_value_text_plain_title', '文本'], - ['data_viewer_presentation_value_text_html_title', 'HTML'], - ['data_viewer_presentation_value_text_xml_title', 'XML'], - ['data_viewer_presentation_value_text_json_title', 'JSON'], - ['data_viewer_presentation_value_image_title', '图片'], - ['data_viewer_presentation_value_image_fit', '适应窗口'], - ['data_viewer_presentation_value_image_original_size', '原始尺寸'], - ['data_viewer_presentation_value_boolean_placeholder', "无法将当前值显示为布尔值"], - ['data_viewer_script_preview', '脚本'], - ['data_viewer_script_preview_dialog_title', '预览更改'], - ['data_viewer_script_preview_error_title', "无法获取脚本"], - ]; + ['table_header_sql_expression', '输入SQL表达式以过滤结果'], + ['data_viewer_tab_title', '数据'], + ['data_viewer_value_edit', '编辑'], + ['data_viewer_value_apply', '应用'], + ['data_viewer_value_revert', '还原'], + ['data_viewer_value_revert_title', '还原未保存的更改'], + ['data_viewer_nodata_message', '没有数据展示'], + ['data_viewer_statistics_status', '状态:'], + ['data_viewer_statistics_duration', '用时:'], + ['data_viewer_statistics_updated_rows', '更新行:'], + ['data_viewer_action_edit_delete', '删除选中'], + ['data_viewer_action_edit_add', '添加'], + ['data_viewer_action_edit_revert', '还原选中'], + ['data_viewer_result_edited_title', '保存更改'], + ['data_viewer_result_edited_message', '结果集已编辑。是否将更改保存到数据库?'], + ['data_viewer_presentation_value_title', '值'], + ['data_viewer_presentation_value_text_title', '文本'], + ['data_viewer_presentation_value_text_plain_title', '文本'], + ['data_viewer_presentation_value_text_html_title', 'HTML'], + ['data_viewer_presentation_value_text_xml_title', 'XML'], + ['data_viewer_presentation_value_text_json_title', 'JSON'], + ['data_viewer_presentation_value_image_title', '图片'], + ['data_viewer_presentation_value_image_fit', '适应窗口'], + ['data_viewer_presentation_value_image_original_size', '原始尺寸'], + ['data_viewer_presentation_value_boolean_placeholder', '无法将当前值显示为布尔值'], + ['data_viewer_script_preview', '脚本'], + ['data_viewer_script_preview_dialog_title', '预览更改'], + ['data_viewer_script_preview_error_title', '无法获取脚本'], + ['data_viewer_refresh_result_set', 'Refresh result set'], +]; From ab685ec30b539aed24355e93d0ea340652bac744 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Mon, 13 Dec 2021 22:22:22 +0300 Subject: [PATCH 02/11] fix(plugin-data-viewer): CB-1021 resolve conflicts --- webapp/packages/plugin-data-viewer/src/locales/zh.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/webapp/packages/plugin-data-viewer/src/locales/zh.ts b/webapp/packages/plugin-data-viewer/src/locales/zh.ts index d46edd26ef..26163566f9 100644 --- a/webapp/packages/plugin-data-viewer/src/locales/zh.ts +++ b/webapp/packages/plugin-data-viewer/src/locales/zh.ts @@ -28,4 +28,5 @@ export default [ ['data_viewer_script_preview', '脚本'], ['data_viewer_script_preview_dialog_title', '预览更改'], ['data_viewer_script_preview_error_title', '无法获取脚本'], + ['data_viewer_refresh_result_set', 'Refresh result set'], ]; From 25f2cb19ed61e265a4fb71ba1f54f7843d10a6b1 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Mon, 13 Dec 2021 22:35:00 +0300 Subject: [PATCH 03/11] fix(plugin-data-viewer): CB-1021 mover loader and error to pane-content --- .../src/TableViewer/TableViewer.tsx | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewer.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewer.tsx index 1a1ef08f4d..b957934260 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewer.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableViewer.tsx @@ -258,6 +258,13 @@ export const TableViewer = observer(function TableViewer({ presentation={presentation} resultIndex={resultIndex} /> + + dataModel.source.cancel()} + /> {valuePanelDisplayed && } @@ -275,13 +282,7 @@ export const TableViewer = observer(function TableViewer({ - - dataModel.source.cancel()} - /> + Date: Tue, 14 Dec 2021 21:18:42 +0300 Subject: [PATCH 04/11] fix(plugin-data-viewer): CB-1021 naming --- ... DATA_VIEWER_CONSTRAINTS_DELETE_ACTION.ts} | 2 +- ...CTION.ts => DATA_VIEWER_REFRESH_ACTION.ts} | 2 +- ...ONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL.ts} | 2 +- ...IEWER_DATABASE_DATA_MODEL_RESULT_INDEX.ts} | 2 +- ...s => DATA_VIEWER_DATA_MODEL_TOOLS_MENU.ts} | 2 +- .../TableHeader/TableHeaderMenu.tsx | 12 ++--- .../TableHeader/TableHeaderService.ts | 47 +++++++------------ .../TableHeader/TableWhereFilter.tsx | 4 +- .../TableViewer/TableHeader/useWhereFilter.ts | 12 ++--- 9 files changed, 36 insertions(+), 49 deletions(-) rename webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/{DELETE_CONSTRAINTS_ACTION.ts => DATA_VIEWER_CONSTRAINTS_DELETE_ACTION.ts} (80%) rename webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/{REFRESH_RESULT_SET_ACTION.ts => DATA_VIEWER_REFRESH_ACTION.ts} (82%) rename webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/{DATA_CONTEXT_TABLE_HEADER_MODEL.ts => DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL.ts} (74%) rename webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/{DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX.ts => DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL_RESULT_INDEX.ts} (65%) rename webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/{TABLE_HEADER_MENU.ts => DATA_VIEWER_DATA_MODEL_TOOLS_MENU.ts} (67%) diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/DELETE_CONSTRAINTS_ACTION.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/DATA_VIEWER_CONSTRAINTS_DELETE_ACTION.ts similarity index 80% rename from webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/DELETE_CONSTRAINTS_ACTION.ts rename to webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/DATA_VIEWER_CONSTRAINTS_DELETE_ACTION.ts index 9a83a4a116..c965dc4db8 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/DELETE_CONSTRAINTS_ACTION.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/DATA_VIEWER_CONSTRAINTS_DELETE_ACTION.ts @@ -8,7 +8,7 @@ import { createAction } from '@cloudbeaver/core-view'; -export const DELETE_CONSTRAINTS_ACTION = createAction('delete-constraints', { +export const DATA_VIEWER_CONSTRAINTS_DELETE_ACTION = createAction('data-viewer-constraints-delete', { label: 'data_grid_table_delete_filters_and_orders', tooltip: 'data_grid_table_delete_filters_and_orders', icon: 'erase', diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/REFRESH_RESULT_SET_ACTION.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/DATA_VIEWER_REFRESH_ACTION.ts similarity index 82% rename from webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/REFRESH_RESULT_SET_ACTION.ts rename to webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/DATA_VIEWER_REFRESH_ACTION.ts index 7e6851b4d9..d26c35424f 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/REFRESH_RESULT_SET_ACTION.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/Actions/DATA_VIEWER_REFRESH_ACTION.ts @@ -8,7 +8,7 @@ import { createAction } from '@cloudbeaver/core-view'; -export const REFRESH_RESULT_SET_ACTION = createAction('refresh-result-set', { +export const DATA_VIEWER_REFRESH_ACTION = createAction('data-viewer-refresh', { label: 'ui_refresh', tooltip: 'data_viewer_refresh_result_set', icon: 'reload', diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_TABLE_HEADER_MODEL.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL.ts similarity index 74% rename from webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_TABLE_HEADER_MODEL.ts rename to webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL.ts index 9b8f342f7a..2a8705a1ad 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_TABLE_HEADER_MODEL.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL.ts @@ -11,4 +11,4 @@ import { createDataContext } from '@cloudbeaver/core-view'; import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel'; import type { IDatabaseDataOptions } from '../../DatabaseDataModel/IDatabaseDataOptions'; -export const DATA_CONTEXT_TABLE_HEADER_MODEL = createDataContext>('table-model'); +export const DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL = createDataContext>('data-viewer-database-data-model'); diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL_RESULT_INDEX.ts similarity index 65% rename from webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX.ts rename to webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL_RESULT_INDEX.ts index 3328ef6aa1..e2d073e34c 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL_RESULT_INDEX.ts @@ -8,4 +8,4 @@ import { createDataContext } from '@cloudbeaver/core-view'; -export const DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX = createDataContext('table-result-index'); +export const DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL_RESULT_INDEX = createDataContext('data-viewer-database-data-model-result-index'); diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TABLE_HEADER_MENU.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_VIEWER_DATA_MODEL_TOOLS_MENU.ts similarity index 67% rename from webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TABLE_HEADER_MENU.ts rename to webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_VIEWER_DATA_MODEL_TOOLS_MENU.ts index 99213e81a6..7e3d491049 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TABLE_HEADER_MENU.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/DATA_VIEWER_DATA_MODEL_TOOLS_MENU.ts @@ -8,4 +8,4 @@ import { createMenu } from '@cloudbeaver/core-view'; -export const TABLE_HEADER_MENU = createMenu('table-header', 'Table header menu'); +export const DATA_VIEWER_DATA_MODEL_TOOLS_MENU = createMenu('data-viewer-data-model-tools', 'Data viewer data model tools menu'); diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderMenu.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderMenu.tsx index 53889f365b..1e501838cf 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderMenu.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderMenu.tsx @@ -14,9 +14,9 @@ import { composes } from '@cloudbeaver/core-theming'; import { MenuBar } from '@cloudbeaver/core-ui'; import { useMenu } from '@cloudbeaver/core-view'; -import { DATA_CONTEXT_TABLE_HEADER_MODEL } from './DATA_CONTEXT_TABLE_HEADER_MODEL'; -import { DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX } from './DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX'; -import { TABLE_HEADER_MENU } from './TABLE_HEADER_MENU'; +import { DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL } from './DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL'; +import { DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL_RESULT_INDEX } from './DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL_RESULT_INDEX'; +import { DATA_VIEWER_DATA_MODEL_TOOLS_MENU } from './DATA_VIEWER_DATA_MODEL_TOOLS_MENU'; import type { ITableHeaderPlaceholderProps } from './TableHeaderService'; const TABLE_HEADER_MENU_BAR_STYLES = composes( @@ -87,10 +87,10 @@ export const TableHeaderMenu: PlaceholderComponent model, resultIndex, }) { - const menu = useMenu(TABLE_HEADER_MENU); + const menu = useMenu(DATA_VIEWER_DATA_MODEL_TOOLS_MENU); - menu.context.set(DATA_CONTEXT_TABLE_HEADER_MODEL, model); - menu.context.set(DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX, resultIndex); + menu.context.set(DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL, model); + menu.context.set(DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL_RESULT_INDEX, resultIndex); return ( diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderService.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderService.ts index e4e347ffb1..7957bdfff0 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderService.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderService.ts @@ -8,15 +8,14 @@ import { PlaceholderContainer } from '@cloudbeaver/core-blocks'; import { injectable, Bootstrap } from '@cloudbeaver/core-di'; -import { MenuService, ActionService, DATA_CONTEXT_MENU, MenuSeparatorItem } from '@cloudbeaver/core-view'; +import { MenuService, ActionService, DATA_CONTEXT_MENU } from '@cloudbeaver/core-view'; -import { DELETE_CONSTRAINTS_ACTION } from '../../DatabaseDataModel/Actions/ResultSet/Actions/DELETE_CONSTRAINTS_ACTION'; -import { REFRESH_RESULT_SET_ACTION } from '../../DatabaseDataModel/Actions/ResultSet/Actions/REFRESH_RESULT_SET_ACTION'; +import { DATA_VIEWER_CONSTRAINTS_DELETE_ACTION } from '../../DatabaseDataModel/Actions/ResultSet/Actions/DATA_VIEWER_CONSTRAINTS_DELETE_ACTION'; import { ResultSetConstraintAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetConstraintAction'; import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel'; -import { DATA_CONTEXT_TABLE_HEADER_MODEL } from './DATA_CONTEXT_TABLE_HEADER_MODEL'; -import { DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX } from './DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX'; -import { TABLE_HEADER_MENU } from './TABLE_HEADER_MENU'; +import { DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL } from './DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL'; +import { DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL_RESULT_INDEX } from './DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL_RESULT_INDEX'; +import { DATA_VIEWER_DATA_MODEL_TOOLS_MENU } from './DATA_VIEWER_DATA_MODEL_TOOLS_MENU'; import { TableHeaderMenu } from './TableHeaderMenu'; import { TableWhereFilter } from './TableWhereFilter'; @@ -43,9 +42,9 @@ export class TableHeaderService extends Bootstrap { this.actionService.addHandler({ id: 'table-header-menu-base-handler', isActionApplicable(context, action) { - const menu = context.find(DATA_CONTEXT_MENU, TABLE_HEADER_MENU); - const model = context.tryGet(DATA_CONTEXT_TABLE_HEADER_MODEL); - const resultIndex = context.tryGet(DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX); + const menu = context.find(DATA_CONTEXT_MENU, DATA_VIEWER_DATA_MODEL_TOOLS_MENU); + const model = context.tryGet(DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL); + const resultIndex = context.tryGet(DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL_RESULT_INDEX); if (!menu || !model || resultIndex === undefined) { return false; @@ -55,9 +54,9 @@ export class TableHeaderService extends Bootstrap { }, handler: async (context, action) => { switch (action) { - case DELETE_CONSTRAINTS_ACTION: { - const model = context.get(DATA_CONTEXT_TABLE_HEADER_MODEL); - const resultIndex = context.get(DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX); + case DATA_VIEWER_CONSTRAINTS_DELETE_ACTION: { + const model = context.get(DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL); + const resultIndex = context.get(DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL_RESULT_INDEX); const constraints = model.source.tryGetAction(resultIndex, ResultSetConstraintAction); if (constraints) { @@ -66,29 +65,24 @@ export class TableHeaderService extends Bootstrap { } break; } - case REFRESH_RESULT_SET_ACTION: { - const model = context.get(DATA_CONTEXT_TABLE_HEADER_MODEL); - await model.refresh(); - break; - } } }, getActionInfo: (context, action) => { - if (context.get(DATA_CONTEXT_MENU) === TABLE_HEADER_MENU) { + if (context.get(DATA_CONTEXT_MENU) === DATA_VIEWER_DATA_MODEL_TOOLS_MENU) { return { ...action.info, label: '' }; } return action.info; }, isDisabled: (context, action) => { - const model = context.get(DATA_CONTEXT_TABLE_HEADER_MODEL); - const resultIndex = context.get(DATA_CONTEXT_TABLE_HEADER_RESULT_INDEX); + const model = context.get(DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL); + const resultIndex = context.get(DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL_RESULT_INDEX); if (model.isLoading() || model.isDisabled(resultIndex)) { return true; } - if (action === DELETE_CONSTRAINTS_ACTION) { + if (action === DATA_VIEWER_CONSTRAINTS_DELETE_ACTION) { const constraints = model.source.tryGetAction(resultIndex, ResultSetConstraintAction); if (constraints) { @@ -96,22 +90,15 @@ export class TableHeaderService extends Bootstrap { } } - if (action === REFRESH_RESULT_SET_ACTION) { - return false; - } - return true; }, }); this.menuService.addCreator({ - isApplicable: context => context.get(DATA_CONTEXT_MENU) === TABLE_HEADER_MENU, + isApplicable: context => context.get(DATA_CONTEXT_MENU) === DATA_VIEWER_DATA_MODEL_TOOLS_MENU, getItems: (context, items) => [ ...items, - DELETE_CONSTRAINTS_ACTION, - new MenuSeparatorItem(), - REFRESH_RESULT_SET_ACTION, - + DATA_VIEWER_CONSTRAINTS_DELETE_ACTION, ], }); } diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableWhereFilter.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableWhereFilter.tsx index f4e1864619..2041a8a0ad 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableWhereFilter.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableWhereFilter.tsx @@ -48,8 +48,8 @@ export const TableWhereFilter: PlaceholderComponent ); }); diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/useWhereFilter.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/useWhereFilter.ts index 17a9556e87..d5697c8a9d 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/useWhereFilter.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/useWhereFilter.ts @@ -21,8 +21,8 @@ interface IState { readonly constraints: ResultSetConstraintAction | null; readonly disabled: boolean; readonly applicableFilter: boolean; - changeHandler: (value: string) => void; - applyFilter: () => Promise; + set: (value: string) => void; + apply: () => Promise; } export function useWhereFilter( @@ -52,7 +52,7 @@ export function useWhereFilter( return this.model.source.prevOptions?.whereFilter !== this.model.source.options?.whereFilter || this.model.source.options?.whereFilter !== this.model.source.requestInfo.requestFilter; }, - changeHandler(value: string) { + set(value: string) { if (this.constraints) { this.constraints.deleteFilters(); } @@ -61,7 +61,7 @@ export function useWhereFilter( this.model.source.options.whereFilter = value; } }, - async applyFilter() { + async apply() { if (!this.applicableFilter || this.model.isLoading() || this.model.isDisabled(this.resultIndex)) { return; } @@ -74,8 +74,8 @@ export function useWhereFilter( constraints: computed, disabled: computed, applicableFilter: computed, - changeHandler: action.bound, - applyFilter: action.bound, + set: action.bound, + apply: action.bound, }, { model, resultIndex }); } From 21b2d0716ac9af3b28526b0c8273003a2c8f901b Mon Sep 17 00:00:00 2001 From: Wroud Date: Tue, 14 Dec 2021 21:29:03 +0300 Subject: [PATCH 05/11] fix: CB-1384 primary provider behavior --- .../plugin-administration/src/locales/en.ts | 2 +- .../AuthenticationProviders.tsx | 20 ++++++++++++++++--- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/webapp/packages/plugin-administration/src/locales/en.ts b/webapp/packages/plugin-administration/src/locales/en.ts index 08d51eaee1..f83c0bcde2 100644 --- a/webapp/packages/plugin-administration/src/locales/en.ts +++ b/webapp/packages/plugin-administration/src/locales/en.ts @@ -11,7 +11,7 @@ export default [ ['administration_configuration_wizard_configuration', 'Server configuration'], ['administration_configuration_wizard_configuration_step_description', 'Main server configuration'], ['administration_configuration_wizard_configuration_title', 'You can configure the main server parameters here.'], - ['administration_configuration_wizard_configuration_message', 'You can allow integration to external services such as SSO once the easy configuration is completed.\n\r Administrator is a super user who can configure server, set databases connections, manage other users and much more. Please, remember the entered password. It is not possible to recover administrator password automatically.'], + ['administration_configuration_wizard_configuration_message', 'You will be able to add additional services after the server configuration.\n\r Administrator is a super user who can configure server, set databases connections, manage other users and much more. Please, remember the entered password. It is not possible to recover administrator password automatically.'], ['administration_configuration_tools_save_tooltip', 'Save configuration'], ['administration_configuration_tools_cancel_tooltip', 'Reset changes'], diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/AuthenticationProviders.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/AuthenticationProviders.tsx index 1b6e4e7b00..ba844c4ddf 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/AuthenticationProviders.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/ServerConfiguration/AuthenticationProviders.tsx @@ -43,14 +43,19 @@ export const AuthenticationProviders: PlaceholderComponent {providerList.map(provider => { const links = authProviderService.getServiceDescriptionLinks(provider); - const disabled = provider.requiredFeatures.some(feat => !serverConfig.enabledFeatures?.includes(feat)); + let disabled = provider.requiredFeatures.some(feat => !serverConfig.enabledFeatures?.includes(feat)); const tooltip = disabled ? `Following services need to be enabled: "${provider.requiredFeatures.join(', ')}"` : ''; + if ( + !localProvider + && primaryProvider?.id === provider.id + && serverConfig.enabledAuthProviders?.length === 1 + && serverConfig.enabledAuthProviders.includes(provider.id) + ) { + disabled = true; + } + if ( configurationWizard && ( From 9a98b10523376555b55118207fe07cf68fac4d17 Mon Sep 17 00:00:00 2001 From: Wroud Date: Tue, 14 Dec 2021 22:07:25 +0300 Subject: [PATCH 06/11] feat: CB-1483 multiple rows duplication --- .../Actions/DatabaseEditAction.ts | 2 +- .../Actions/Document/DocumentEditAction.ts | 2 +- .../Actions/IDatabaseDataEditAction.ts | 2 +- .../Actions/ResultSet/ResultSetEditAction.ts | 22 +++++++------- .../TableFooterMenu/TableFooterMenuService.ts | 29 +++++++------------ 5 files changed, 25 insertions(+), 32 deletions(-) diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseEditAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseEditAction.ts index 34432c147f..b54d42b394 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseEditAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseEditAction.ts @@ -42,7 +42,7 @@ export abstract class DatabaseEditAction TValue | undefined; set: (key: TKey, value: TValue) => void; add: (key?: TKey) => void; - addCopy: (key: TKey) => void; + duplicate: (...key: TKey[]) => void; delete: (key: TKey) => void; applyUpdate: (result: TResult) => void; revert: (key: TKey) => void; diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetEditAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetEditAction.ts index deb6e53414..cd3dce5c3c 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetEditAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetEditAction.ts @@ -208,21 +208,23 @@ export class ResultSetEditAction } } - addCopy(key: IResultSetElementKey): void { - this.addRowCopy(key.row); + duplicate(...key: IResultSetElementKey[]): void { + this.addRowCopy(...key.map(key => key.row)); } - addRowCopy(row: IResultSetRowKey): void { - let value = this.data.getRowValue(row); + addRowCopy(...rows: IResultSetRowKey[]): void { + for (const row of rows) { + let value = this.data.getRowValue(row); - const editedValue = this.editorData - .get(ResultSetDataKeysUtils.serialize(row)); + const editedValue = this.editorData + .get(ResultSetDataKeysUtils.serialize(row)); - if (editedValue) { - value = editedValue.update; + if (editedValue) { + value = editedValue.update; + } + + this.addRow(row, JSON.parse(JSON.stringify(value))); } - - this.addRow(row, JSON.parse(JSON.stringify(value))); } delete(...keys: IResultSetElementKey[]): void { diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts index 58f22b560d..08fadc6cab 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts @@ -101,19 +101,17 @@ export class TableFooterMenuService { return !editor?.hasFeature('add'); }, isDisabled(context) { - const select = context.data.model.source.getActionImplementation( - context.data.resultIndex, - DatabaseSelectAction - ); - - const focus = select?.getFocusedElement(); - - return ( + if ( context.data.model.isLoading() || context.data.model.isDisabled(context.data.resultIndex) || !context.data.model.source.hasResult(context.data.resultIndex) - || !focus - ); + ) { + return true; + } + + const selectedElements = getActiveElements(context.data.model, context.data.resultIndex); + + return selectedElements.length === 0; }, onClick(context) { const editor = context.data.model.source.getActionImplementation( @@ -125,16 +123,9 @@ export class TableFooterMenuService { return; } - const select = context.data.model.source.getActionImplementation( - context.data.resultIndex, - DatabaseSelectAction - ); + const selectedElements = getActiveElements(context.data.model, context.data.resultIndex); - const focus = select?.getFocusedElement(); - - if (focus) { - editor.addCopy(focus); - } + editor.duplicate(...selectedElements); }, }); this.registerMenuItem({ From b9ecbfc3066c3fdc60754b0df60774cd25607839 Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 15 Dec 2021 01:28:32 +0300 Subject: [PATCH 07/11] feat(plugin-data-spreadsheet-new): CB-1506 keyboard navigation in the grid --- webapp/packages/core-blocks/src/useMouse.ts | 3 +- .../src/DataGrid/CellEditor/CellEditor.tsx | 2 +- .../DataGrid/CellRenderer/CellRenderer.tsx | 57 ++++++----- .../src/DataGrid/DataGridContext.ts | 1 + .../src/DataGrid/DataGridTable.tsx | 94 +++++++++++++++++-- .../TableColumnHeader/OrderButton.tsx | 5 + .../TableColumnHeader/TableColumnHeader.tsx | 13 ++- .../TableIndexColumnHeader.tsx | 17 +++- .../src/DataGrid/useGridSelectedCellsCopy.ts | 9 +- .../src/DataGrid/useTableData.ts | 6 +- .../src/Editing/useEditing.ts | 14 +-- .../ResultSet/ResultSetSelectAction.ts | 4 +- 12 files changed, 165 insertions(+), 60 deletions(-) diff --git a/webapp/packages/core-blocks/src/useMouse.ts b/webapp/packages/core-blocks/src/useMouse.ts index 7f9957df2f..8fa3cd2c1e 100644 --- a/webapp/packages/core-blocks/src/useMouse.ts +++ b/webapp/packages/core-blocks/src/useMouse.ts @@ -25,6 +25,7 @@ interface IState { export interface IMouseHook { state: IState; + ref: T | null; reference: (obj: T) => void; } @@ -73,5 +74,5 @@ export function useMouse(options: IOptions = {}): IMouseH }; }, [reference]); - return { state, reference: setReference }; + return { ref: reference, state, reference: setReference }; } diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/CellEditor/CellEditor.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/CellEditor/CellEditor.tsx index 46b2e1f5dc..c17b3dd23c 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/CellEditor/CellEditor.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/CellEditor/CellEditor.tsx @@ -41,7 +41,7 @@ export interface IEditorRef { focus: () => void; } -const lockNavigation = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End']; +const lockNavigation = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown', 'Home', 'End', 'Enter']; export const CellEditor = observer, 'row' | 'column' | 'onClose'>, IEditorRef>(function CellEditor({ row, diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/CellRenderer/CellRenderer.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/CellRenderer/CellRenderer.tsx index e85c99af73..9e19782505 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/CellRenderer/CellRenderer.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/CellRenderer/CellRenderer.tsx @@ -8,11 +8,11 @@ import { computed, observable } from 'mobx'; import { observer } from 'mobx-react-lite'; -import { useContext, useEffect, useMemo } from 'react'; -import type { CellRendererProps } from 'react-data-grid'; +import { useContext, useEffect } from 'react'; +import type { CalculatedColumn, CellRendererProps } from 'react-data-grid'; import { Cell } from 'react-data-grid'; -import { useMouse, useObjectRef, useObservableRef } from '@cloudbeaver/core-blocks'; +import { getComputed, useMouse, useObjectRef, useObservableRef } from '@cloudbeaver/core-blocks'; import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events'; import { IResultSetElementKey, IResultSetRowKey, isBooleanValuePresentationAvailable, DatabaseEditChangeType } from '@cloudbeaver/plugin-data-viewer'; @@ -65,7 +65,7 @@ export const CellRenderer = observer>(functi editionState: computed, }, { row, column, rowIdx }); - const classes = useMemo(() => computed(() => { + const classes = getComputed(() => { let classes = ''; if (cellContext.isSelected) { classes += ' rdg-cell-custom-selected'; @@ -88,7 +88,32 @@ export const CellRenderer = observer>(functi } } return classes; - }), []).get(); + }); + + function isEditable(column: CalculatedColumn): boolean { + if ( + !editingContext + || !tableDataContext + || !cellContext.cell + ) { + return false; + } + + const resultColumn = tableDataContext.getColumnInfo(cellContext.cell.column); + const value = tableDataContext.getCellValue(cellContext.cell); + + if (!resultColumn || value === undefined) { + return false; + } + + const handleByBooleanFormatter = isBooleanValuePresentationAvailable(value, resultColumn); + + return !( + !column.editable + || handleByBooleanFormatter + || tableDataContext.format.isReadOnly(cellContext.cell) + ); + } const state = useObjectRef(() => ({ mouseUp(event: React.MouseEvent) { @@ -112,27 +137,8 @@ export const CellRenderer = observer>(functi }, doubleClick(event: React.MouseEvent) { if ( - !this.editingContext - || !this.tableDataContext + !this.isEditable(this.column) || EventContext.has(event, EventStopPropagationFlag) - || !cellContext.cell - ) { - return; - } - - const resultColumn = this.tableDataContext.getColumnInfo(cellContext.cell.column); - const value = this.tableDataContext.getCellValue(cellContext.cell); - - if (!resultColumn || value === undefined) { - return; - } - - const handleByBooleanFormatter = isBooleanValuePresentationAvailable(value, resultColumn); - - if ( - !this.column.editable - || handleByBooleanFormatter - || this.tableDataContext.format.isReadOnly(cellContext.cell) ) { return; } @@ -147,6 +153,7 @@ export const CellRenderer = observer>(functi dataGridContext, editingContext, tableDataContext, + isEditable, }, ['doubleClick', 'mouseUp']); useEffect(() => () => editingContext?.closeEditor(cellContext.position), []); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContext.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContext.ts index fb9de9b3dd..b1e69b9259 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContext.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContext.ts @@ -25,6 +25,7 @@ export interface IDataGridContext { isGridInFocus: () => boolean; getEditorPortal: () => HTMLDivElement | null; getDataGridApi: () => DataGridHandle | null; + focus: () => void; } export const DataGridContext = createContext(undefined as any); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx index fb4cd0be06..e81cdcbdbb 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx @@ -64,10 +64,8 @@ export const DataGridTable = observer(null); - const gridSelectionContext = useGridSelectionContext(tableData, selectionAction); const editingContext = useEditing({ readonly: model.isReadonly() || model.isDisabled(resultIndex), onEdit: (position, key) => { @@ -80,22 +78,38 @@ export const DataGridTable = observer { + restoreFocus(); + }, }); + const tableData = useTableData(model, resultIndex, dataGridDivRef); + const gridSelectionContext = useGridSelectionContext(tableData, selectionAction); + + function restoreFocus() { + const gridDiv = gridContainerRef.current; + const focusSink = gridDiv?.querySelector('.rdg-focus-sink'); + focusSink?.focus(); + } + function isGridInFocus(): boolean { const gridDiv = gridContainerRef.current; const focusSink = gridDiv?.querySelector('.rdg-focus-sink'); @@ -121,7 +135,7 @@ export const DataGridTable = observer { dataGridRef.current?.selectCell({ idx: startPosition.colIdx, rowIdx: startPosition.rowIdx }); @@ -134,6 +148,66 @@ export const DataGridTable = observer) { + gridSelectedCellCopy.onKeydownHandler(event); + const cell = selectionAction.getFocusedElement(); + + if (!cell) { + return; + } + + const idx = tableData.getColumnIndexFromColumnKey(cell.column); + const rowIdx = tableData.getRowIndexFromKey(cell.row); + const position: CellPosition = { idx, rowIdx }; + + if (editingContext.isEditing(position)) { + return; + } + + switch (event.key) { + case 'Escape': { + tableData.editor.revert(cell); + return; + } + case 'Insert': { + if (event.altKey) { + if (event.ctrlKey || event.metaKey) { + tableData.editor.duplicate(cell); + } else { + tableData.editor.add(cell); + } + return; + } + } + } + const editingState = tableData.editor.getElementState(cell); + + if (editingState === DatabaseEditChangeType.delete) { + return; + } + + switch (event.key) { + case 'Delete': { + const editor = tableData.editor; + editor.delete(cell); + + if (editingState === DatabaseEditChangeType.add) { + if (rowIdx - 1 > 0) { + dataGridRef.current?.selectCell({ idx, rowIdx: rowIdx - 1 }); + } + } else { + if (rowIdx + 1 < tableData.rows.length) { + dataGridRef.current?.selectCell({ idx, rowIdx: rowIdx + 1 }); + } + } + + return; + } + } + + editingContext.edit({ idx, rowIdx }, event.key); + } + useEffect(() => { function syncEditor(data: IResultSetEditActionData) { const editor = tableData.editor; @@ -234,7 +308,7 @@ export const DataGridTable = observer editorRef.current, getDataGridApi: () => dataGridRef.current, - }), [model, actions, resultIndex, editorRef, dataGridRef, gridContainerRef]); + focus: restoreFocus, + }), [model, actions, resultIndex, editorRef, dataGridRef, gridContainerRef, restoreFocus]); if (!tableData.columns.length) { return {translate('data_grid_table_empty_placeholder')}; @@ -282,7 +357,7 @@ export const DataGridTable = observer @@ -290,7 +365,6 @@ export const DataGridTable = observer(function OrderButton({ }); }; + function preventFocus(event: React.MouseEvent) { + event.preventDefault(); + } + return styled(styles)( diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.tsx index 131e939ede..39d4ae005e 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableColumnHeader.tsx @@ -93,13 +93,18 @@ export const TableColumnHeader = observer>(function Tab } } } - const handleColumnSelection = (e: React.MouseEvent) => { - gridSelectionContext.selectColumn(calculatedColumn.idx, e.ctrlKey || e.metaKey); - }; + + function preventFocus(event: React.MouseEvent) { + event.preventDefault(); + } + + function handleClick(event: React.MouseEvent) { + gridSelectionContext.selectColumn(calculatedColumn.idx, event.ctrlKey || event.metaKey); + } return styled(useStyles(headerStyles))( - + {icon && } {!dataReadonly && columnReadOnly && } diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableIndexColumnHeader.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableIndexColumnHeader.tsx index 06c26b63a6..1253115a80 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableIndexColumnHeader.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableColumnHeader/TableIndexColumnHeader.tsx @@ -43,12 +43,21 @@ export const TableIndexColumnHeader = observer>(functio throw new Error('Contexts required'); } - const readonly = getComputed( - () => tableDataContext.isReadOnly() || dataGridContext.model.isReadonly() - ); + const readonly = getComputed(() => ( + tableDataContext.isReadOnly() + || dataGridContext.model.isReadonly() + )); + + function preventFocus(event: React.MouseEvent) { + event.preventDefault(); + } + + function handleClick(event: React.MouseEvent) { + selectionContext.selectTable(); + } return styled(styles)( - selectionContext.selectTable()}> + {readonly && } {props.column.name} diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useGridSelectedCellsCopy.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useGridSelectedCellsCopy.ts index 38ada8d52a..4be35a2bfa 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useGridSelectedCellsCopy.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useGridSelectedCellsCopy.ts @@ -16,7 +16,7 @@ import type { IDataGridSelectionContext } from './DataGridSelection/DataGridSele import type { ITableData } from './TableDataContext'; const EVENT_KEY_CODE = { - C: 'KeyC', + C: 'C', }; function getCellCopyValue(tableData: ITableData, key: IResultSetElementKey): string { @@ -65,9 +65,6 @@ function getSelectedCellsValue( return rowsValues.join('\r\n'); } -// needed for event.code -type IKeyboardEvent = React.KeyboardEvent & KeyboardEvent; - export function useGridSelectedCellsCopy( tableData: ITableData, resultSetSelectAction: ResultSetSelectAction, @@ -75,8 +72,8 @@ export function useGridSelectedCellsCopy( ) { const props = useObjectRef({ tableData, selectionContext, resultSetSelectAction }); - const onKeydownHandler = useCallback((event: IKeyboardEvent) => { - if ((event.ctrlKey || event.metaKey) && event.code === EVENT_KEY_CODE.C) { + const onKeydownHandler = useCallback((event: React.KeyboardEvent) => { + if ((event.ctrlKey || event.metaKey) && event.key === EVENT_KEY_CODE.C) { const focusedElement = props.resultSetSelectAction.getFocusedElement(); let value: string | null = null; diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useTableData.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useTableData.ts index 7d94d7e3a8..74f8d53c73 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useTableData.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useTableData.ts @@ -33,7 +33,8 @@ export const indexColumn: Column = { export function useTableData( model: IDatabaseDataModel, resultIndex: number, - gridDIVElement: React.RefObject + gridDIVElement: React.RefObject, + onCellKeyDown?: (event: React.KeyboardEvent) => void ): ITableData { const format = model.source.getAction(resultIndex, ResultSetFormatAction); const data = model.source.getAction(resultIndex, ResultSetDataAction); @@ -77,6 +78,9 @@ export function useTableData( editable: true, width: Math.min(300, measuredCells[index]), headerRenderer: TableColumnHeader, + editorOptions: { + onCellKeyDown, + }, })); columns.unshift(indexColumn); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/Editing/useEditing.ts b/webapp/packages/plugin-data-spreadsheet-new/src/Editing/useEditing.ts index 65189850de..1644a20467 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/Editing/useEditing.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/Editing/useEditing.ts @@ -25,6 +25,7 @@ function getPositionHash(position: CellPosition): string { interface IEditingOptions { readonly?: boolean; onEdit: (position: CellPosition, key?: string) => boolean; + onCloseEditor?: (position: CellPosition) => void; } export function useEditing(options: IEditingOptions): IEditingContext { @@ -44,12 +45,6 @@ export function useEditing(options: IEditingOptions): IEditingContext { if (state.options.readonly) { return; } - // TODO: not works yet - switch (key) { - case 'Escape': - state.editingCells.delete(getPositionHash(position)); - break; - } if (!state.options.onEdit(position, key)) { return; @@ -62,8 +57,15 @@ export function useEditing(options: IEditingOptions): IEditingContext { }, closeEditor(position: CellPosition) { const info = state.editingCells.get(getPositionHash(position)); + + if (!info.editing) { + return; + } + info.editing = false; state.editorOpened = false; + + state.options.onCloseEditor?.(position); }, close() { state.editingCells.clear(); diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction.ts index 3ade790767..73eff1e1b1 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction, toJS } from 'mobx'; import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor'; import { ResultDataFormat } from '@cloudbeaver/core-sdk'; @@ -233,7 +233,7 @@ export class ResultSetSelectAction extends DatabaseSelectAction Date: Wed, 15 Dec 2021 04:36:22 +0300 Subject: [PATCH 08/11] feat: CB-1506 cell copy/paste --- .../configs/webpack.product.dev.config.js | 16 ++++---- .../DataGrid/CellRenderer/CellRenderer.tsx | 5 +-- .../src/DataGrid/DataGridTable.tsx | 38 ++++++++++++++++--- .../src/DataGrid/TableDataContext.ts | 1 + .../src/DataGrid/useGridSelectedCellsCopy.ts | 7 +++- .../src/DataGrid/useTableData.ts | 14 ++++++- .../src/Editing/EditingContext.tsx | 2 +- .../src/Editing/useEditing.ts | 6 +-- .../src/SqlResultTabs/SqlQueryService.ts | 4 +- 9 files changed, 67 insertions(+), 26 deletions(-) diff --git a/webapp/packages/core-cli/configs/webpack.product.dev.config.js b/webapp/packages/core-cli/configs/webpack.product.dev.config.js index fdfbd5baac..408faf7b5a 100644 --- a/webapp/packages/core-cli/configs/webpack.product.dev.config.js +++ b/webapp/packages/core-cli/configs/webpack.product.dev.config.js @@ -14,17 +14,17 @@ const CopyWebpackPlugin = require('copy-webpack-plugin') const package = require(resolve('package.json')); -const certPath = resolve(__dirname, '../../../../../server-cert.pem') -const keyPath = resolve(__dirname, '../../../../../server-key.pem') +const certPath = resolve(__dirname, '../../../../../certs/private.pem') +const keyPath = resolve(__dirname, '../../../../../certs/private.key') let https = false; -// if(fs.existsSync(certPath) && fs.existsSync(keyPath)) { -// https = { -// key: fs.readFileSync(keyPath), -// cert: fs.readFileSync(certPath) -// } -// } +if(fs.existsSync(certPath) && fs.existsSync(keyPath)) { + https = { + key: fs.readFileSync(keyPath), + cert: fs.readFileSync(certPath), + } +} module.exports = (env, argv) => merge(commonConfig(env, argv), { entry: { diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/CellRenderer/CellRenderer.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/CellRenderer/CellRenderer.tsx index 9e19782505..e00d43c8ee 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/CellRenderer/CellRenderer.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/CellRenderer/CellRenderer.tsx @@ -109,9 +109,8 @@ export const CellRenderer = observer>(functi const handleByBooleanFormatter = isBooleanValuePresentationAvailable(value, resultColumn); return !( - !column.editable - || handleByBooleanFormatter - || tableDataContext.format.isReadOnly(cellContext.cell) + handleByBooleanFormatter + || tableDataContext.isCellReadonly(cellContext.cell) ); } diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx index e81cdcbdbb..c93f66a3e2 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx @@ -13,9 +13,12 @@ import type { DataGridHandle } from 'react-data-grid'; import styled from 'reshadow'; import { TextPlaceholder, useObjectRef } from '@cloudbeaver/core-blocks'; +import { useService } from '@cloudbeaver/core-di'; +import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events'; import { Executor } from '@cloudbeaver/core-executor'; import { useTranslate } from '@cloudbeaver/core-localization'; import { useStyles } from '@cloudbeaver/core-theming'; +import { ClipboardService } from '@cloudbeaver/core-ui'; import { DatabaseDataSelectActionsData, DatabaseEditChangeType, IDatabaseResultSet, IDataPresentationProps, IResultSetEditActionData, IResultSetElementKey, IResultSetPartialKey, ResultSetDataKeysUtils, ResultSetSelectAction @@ -51,6 +54,7 @@ const headerHeight = 28; export const DataGridTable = observer>(function DataGridTable({ model, actions, resultIndex, className }) { const translate = useTranslate(); + const clipboardService = useService(ClipboardService); const gridContainerRef = useRef(null); const editorRef = useRef(null); const dataGridDivRef = useRef(null); @@ -68,7 +72,7 @@ export const DataGridTable = observer { + onEdit: (position, code, key) => { const column = tableData.getColumn(position.idx); const row = tableData.getRow(position.rowIdx); @@ -78,7 +82,11 @@ export const DataGridTable = observer) { gridSelectedCellCopy.onKeydownHandler(event); + + if (EventContext.has(event, EventStopPropagationFlag)) { + return; + } + const cell = selectionAction.getFocusedElement(); if (!cell) { @@ -164,7 +177,7 @@ export const DataGridTable = observer tableData.editor.set(cell, value)) + .catch(); + } + return; + } } - editingContext.edit({ idx, rowIdx }, event.key); + editingContext.edit({ idx, rowIdx }, event.nativeEvent.code, event.key); } useEffect(() => { diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableDataContext.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableDataContext.ts index 659df20b5c..65bfeb14c6 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableDataContext.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/TableDataContext.ts @@ -49,6 +49,7 @@ export interface ITableData { isIndexColumn: (columnKey: string) => boolean; isIndexColumnInRange: (columnsRange: Array>) => boolean; isReadOnly: () => boolean; + isCellReadonly: (key: Partial) => boolean; } export const TableDataContext = createContext(undefined as any); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useGridSelectedCellsCopy.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useGridSelectedCellsCopy.ts index 4be35a2bfa..5d672e310a 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useGridSelectedCellsCopy.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useGridSelectedCellsCopy.ts @@ -9,6 +9,7 @@ import { useCallback } from 'react'; import { useObjectRef } from '@cloudbeaver/core-blocks'; +import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events'; import { copyToClipboard } from '@cloudbeaver/core-utils'; import { IResultSetColumnKey, IResultSetElementKey, ResultSetDataKeysUtils, ResultSetSelectAction } from '@cloudbeaver/plugin-data-viewer'; @@ -16,7 +17,7 @@ import type { IDataGridSelectionContext } from './DataGridSelection/DataGridSele import type { ITableData } from './TableDataContext'; const EVENT_KEY_CODE = { - C: 'C', + C: 'KeyC', }; function getCellCopyValue(tableData: ITableData, key: IResultSetElementKey): string { @@ -73,7 +74,9 @@ export function useGridSelectedCellsCopy( const props = useObjectRef({ tableData, selectionContext, resultSetSelectAction }); const onKeydownHandler = useCallback((event: React.KeyboardEvent) => { - if ((event.ctrlKey || event.metaKey) && event.key === EVENT_KEY_CODE.C) { + if ((event.ctrlKey || event.metaKey) && event.nativeEvent.code === EVENT_KEY_CODE.C) { + EventContext.set(event, EventStopPropagationFlag); + const focusedElement = props.resultSetSelectAction.getFocusedElement(); let value: string | null = null; diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useTableData.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useTableData.ts index 74f8d53c73..c19b371f0f 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useTableData.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useTableData.ts @@ -11,7 +11,7 @@ import type { Column } from 'react-data-grid'; import { useObservableRef } from '@cloudbeaver/core-blocks'; import { TextTools, uuid } from '@cloudbeaver/core-utils'; -import { IDatabaseDataModel, IDatabaseResultSet, IResultSetColumnKey, IResultSetRowKey, ResultSetDataAction, ResultSetDataKeysUtils, ResultSetEditAction, ResultSetFormatAction, ResultSetViewAction } from '@cloudbeaver/plugin-data-viewer'; +import { IDatabaseDataModel, IDatabaseResultSet, IResultSetColumnKey, IResultSetElementKey, IResultSetRowKey, ResultSetDataAction, ResultSetDataKeysUtils, ResultSetEditAction, ResultSetFormatAction, ResultSetViewAction } from '@cloudbeaver/plugin-data-viewer'; import { IndexFormatter } from './Formatters/IndexFormatter'; import { TableColumnHeader } from './TableColumnHeader/TableColumnHeader'; @@ -156,6 +156,18 @@ export function useTableData( isReadOnly() { return this.columnKeys.every(column => this.getColumnInfo(column)?.readOnly); }, + isCellReadonly(key: Partial) { + if (!key.column) { + return true; + } + + const column = this.getColumnByDataIndex(key.column); + + return ( + !column.editable + || this.format.isReadOnly(key) + ); + }, }), { columns: computed, rows: computed, diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/Editing/EditingContext.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/Editing/EditingContext.tsx index 1126fd493e..d0e3d273ce 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/Editing/EditingContext.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/Editing/EditingContext.tsx @@ -10,7 +10,7 @@ import { createContext } from 'react'; export interface IEditingContext { readonly readonly: boolean; - edit: (position: CellPosition, key?: string) => void; + edit: (position: CellPosition, code?: string, key?: string) => void; closeEditor: (position: CellPosition) => void; close: () => void; isEditorActive: () => boolean; diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/Editing/useEditing.ts b/webapp/packages/plugin-data-spreadsheet-new/src/Editing/useEditing.ts index 1644a20467..f8f42beafb 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/Editing/useEditing.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/Editing/useEditing.ts @@ -24,7 +24,7 @@ function getPositionHash(position: CellPosition): string { interface IEditingOptions { readonly?: boolean; - onEdit: (position: CellPosition, key?: string) => boolean; + onEdit: (position: CellPosition, code?: string, key?: string) => boolean; onCloseEditor?: (position: CellPosition) => void; } @@ -41,12 +41,12 @@ export function useEditing(options: IEditingOptions): IEditingContext { get readonly() { return state.readonly; }, - edit(position: CellPosition, key?: string) { + edit(position: CellPosition, code?: string, key?: string) { if (state.options.readonly) { return; } - if (!state.options.onEdit(position, key)) { + if (!state.options.onEdit(position, code, key)) { return; } diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryService.ts b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryService.ts index 4e40f189a2..135e518a68 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlQueryService.ts @@ -105,7 +105,7 @@ export class SqlQueryService { await model .setCountGain(getDefaultRowsCount()) .setSlice(0) - .refresh(); + .request(); this.sqlQueryResultService.updateGroupTabs(editorState, model, tabGroup.groupId); } catch (exception) { @@ -179,7 +179,7 @@ export class SqlQueryService { await model .setCountGain(getDefaultRowsCount()) .setSlice(0) - .refresh(); + .request(); statistics.executedQueries++; statistics.executeTime += source.requestInfo.requestDuration; From 2eb48b567f42fc567b3e048d83eac4cd6a22c825 Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 15 Dec 2021 05:07:43 +0300 Subject: [PATCH 09/11] chore: update browserlist --- webapp/yarn.lock | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/webapp/yarn.lock b/webapp/yarn.lock index 22011f6148..96f3d3b848 100644 --- a/webapp/yarn.lock +++ b/webapp/yarn.lock @@ -4542,15 +4542,10 @@ caniuse-api@^3.0.0: lodash.memoize "^4.1.2" lodash.uniq "^4.5.0" -caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001219: - version "1.0.30001243" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001243.tgz#d9250155c91e872186671c523f3ae50cfc94a3aa" - integrity sha512-vNxw9mkTBtkmLFnJRv/2rhs1yufpDfCkBZexG3Y0xdOH2Z/eE/85E4Dl5j1YUN34nZVsSp6vVRFQRrez9wJMRA== - -caniuse-lite@^1.0.30001274: - version "1.0.30001280" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001280.tgz#066a506046ba4be34cde5f74a08db7a396718fb7" - integrity sha512-kFXwYvHe5rix25uwueBxC569o53J6TpnGu0BEEn+6Lhl2vsnAumRFWEBhDft1fwyo6m1r4i+RqA4+163FpeFcA== +caniuse-lite@^1.0.0, caniuse-lite@^1.0.30000981, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001219, caniuse-lite@^1.0.30001274: + version "1.0.30001286" + resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001286.tgz" + integrity sha512-zaEMRH6xg8ESMi2eQ3R4eZ5qw/hJiVsO/HlLwniIwErij0JDr9P+8V4dtx1l+kLq6j3yy8l8W4fst1lBnat5wQ== capital-case@^1.0.4: version "1.0.4" From ff444a13bed62549203e6fb5d6f54324dcaa1fa6 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Wed, 15 Dec 2021 15:53:40 +0300 Subject: [PATCH 10/11] fix(server): CB-1432 change xml to json --- server/bundles/io.cloudbeaver.product.ce/plugin.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/bundles/io.cloudbeaver.product.ce/plugin.xml b/server/bundles/io.cloudbeaver.product.ce/plugin.xml index da48a6a55c..bb214bd59e 100644 --- a/server/bundles/io.cloudbeaver.product.ce/plugin.xml +++ b/server/bundles/io.cloudbeaver.product.ce/plugin.xml @@ -12,7 +12,7 @@ - + From 32eec43968cf6520974331d3af606d9aad546e7c Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 15 Dec 2021 16:38:03 +0300 Subject: [PATCH 11/11] fix: CB-1483 CB-1506 data grid editing --- .../DataGridContextMenuCellEditingService.ts | 4 +- .../src/DataGrid/DataGridTable.tsx | 49 ++++++++++++------- .../Actions/DatabaseSelectAction.ts | 1 + .../Actions/ResultSet/ResultSetEditAction.ts | 18 +++++-- .../ResultSet/ResultSetSelectAction.ts | 40 +++++++++++++++ .../TableFooterMenu/TableFooterMenuService.ts | 9 +--- 6 files changed, 91 insertions(+), 30 deletions(-) diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts index 5b1d192d98..e58cffca07 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuCellEditingService.ts @@ -7,7 +7,7 @@ */ import { injectable } from '@cloudbeaver/core-di'; -import { isBooleanValuePresentationAvailable, DatabaseEditChangeType, ResultSetEditAction, ResultSetFormatAction, ResultSetViewAction, DatabaseSelectAction, ResultSetSelectAction } from '@cloudbeaver/plugin-data-viewer'; +import { isBooleanValuePresentationAvailable, DatabaseEditChangeType, ResultSetEditAction, ResultSetFormatAction, ResultSetViewAction, ResultSetSelectAction } from '@cloudbeaver/plugin-data-viewer'; import { DataGridContextMenuService } from './DataGridContextMenuService'; @@ -124,7 +124,7 @@ export class DataGridContextMenuCellEditingService { }, onClick(context) { const editor = context.data.model.source.getAction(context.data.resultIndex, ResultSetEditAction); - editor.addRowCopy(context.data.key.row); + editor.duplicateRow(context.data.key.row); }, } ); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx index c93f66a3e2..9f01658aac 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx @@ -164,6 +164,8 @@ export const DataGridTable = observer tableData.editor.getElementState(cell) !== DatabaseEditChangeType.delete); - if (editingState === DatabaseEditChangeType.add) { - if (rowIdx - 1 > 0) { - dataGridRef.current?.selectCell({ idx, rowIdx: rowIdx - 1 }); - } - } else { - if (rowIdx + 1 < tableData.rows.length) { - dataGridRef.current?.selectCell({ idx, rowIdx: rowIdx + 1 }); + if (filteredRows.length > 0) { + const editor = tableData.editor; + const firstRow = filteredRows[0]; + const editingState = tableData.editor.getElementState(firstRow); + + editor.delete(...filteredRows); + + if (editingState === DatabaseEditChangeType.add) { + if (rowIdx - 1 > 0) { + dataGridRef.current?.selectCell({ idx, rowIdx: rowIdx - 1 }); + } + } else { + if (rowIdx + 1 < tableData.rows.length) { + dataGridRef.current?.selectCell({ idx, rowIdx: rowIdx + 1 }); + } } } return; } - case 'KeyV':{ + case 'KeyV': { + if (editingState === DatabaseEditChangeType.delete) { + return; + } + if (event.ctrlKey || event.metaKey) { if (!clipboardService.clipboardAvailable || clipboardService.state === 'denied' || tableData.isCellReadonly(cell)) { return; @@ -226,11 +237,15 @@ export const DataGridTable = observer tableData.editor.set(cell, value)) .catch(); + return; } - return; } } + if (editingState === DatabaseEditChangeType.delete) { + return; + } + editingContext.edit({ idx, rowIdx }, event.nativeEvent.code, event.key); } diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseSelectAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseSelectAction.ts index bd712f2414..161abf14ba 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseSelectAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseSelectAction.ts @@ -31,6 +31,7 @@ export abstract class DatabaseSelectAction key.row)); + duplicate(...keys: IResultSetElementKey[]): void { + const rows: IResultSetRowKey[] = []; + const rowKeys = new Set(); + + for (const key of keys) { + const serialized = ResultSetDataKeysUtils.serialize(key.row); + + if (!rowKeys.has(serialized)) { + rows.push(key.row); + rowKeys.add(serialized); + } + } + + this.duplicateRow(...rows); } - addRowCopy(...rows: IResultSetRowKey[]): void { + duplicateRow(...rows: IResultSetRowKey[]): void { for (const row of rows) { let value = this.data.getRowValue(row); diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction.ts index 73eff1e1b1..5c570ebb0c 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction.ts @@ -160,6 +160,46 @@ export class ResultSetSelectAction extends DatabaseSelectAction(); + + const elements = this.getSelectedElements(); + + for (const cell of elements) { + const key = ResultSetDataKeysUtils.serialize(cell.row); + + if (!rowsKeys.has(key)) { + cells.push(cell); + rowsKeys.add(key); + } + } + + return cells; + } + + getActiveRows(): IResultSetElementKey[] { + const elements = this.getSelectedRows(); + const focus = this.getFocusedElement(); + + if (elements.length === 0 && focus) { + return [focus]; + } + + return elements; + } + set(key: IResultSetPartialKey, selected: boolean, silent?: boolean): void { if (key.row === undefined) { for (const row of this.view.rowKeys) { diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts index 08fadc6cab..f401719239 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableFooter/TableFooterMenu/TableFooterMenuService.ts @@ -366,12 +366,5 @@ function getActiveElements(model: IDatabaseDataModel, DatabaseSelectAction ); - const selectedElements = select?.getSelectedElements() || []; - const focus = select?.getFocusedElement(); - - if (selectedElements.length === 0 && focus) { - selectedElements.push(focus); - } - - return selectedElements; + return select?.getActiveElements() ?? []; }