diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseDataActionDecorator.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseDataActionDecorator.ts new file mode 100644 index 0000000000..b25e989321 --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/DatabaseDataActionDecorator.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 type { IDatabaseDataActionClass } from '../IDatabaseDataAction'; + +export function databaseDataAction>() { + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + return (constructor: U) => { }; +} diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/IDatabaseDataSelectAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/IDatabaseDataSelectAction.ts new file mode 100644 index 0000000000..73a189b88b --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/IDatabaseDataSelectAction.ts @@ -0,0 +1,31 @@ +/* + * 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 type { IExecutor } from '@cloudbeaver/core-executor'; + +import type { IDatabaseDataAction } from '../IDatabaseDataAction'; +import type { IDatabaseDataResult } from '../IDatabaseDataResult'; + +export type DatabaseDataEditorActionsData = { + type: 'select'; + resultId: string; + key: TKey; + selected: boolean; +} | { + type: 'clear'; + resultId: string; +}; + +export interface IDatabaseDataSelectAction + extends IDatabaseDataAction { + readonly actions: IExecutor>; + isSelected: () => boolean; + isElementSelected: (key: TKey) => boolean; + set: (key: TKey, selected: boolean) => void; + clear: () => void; +} diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSetSelectAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSetSelectAction.ts new file mode 100644 index 0000000000..774c3593aa --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSetSelectAction.ts @@ -0,0 +1,109 @@ +/* + * 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 { Executor, IExecutor } from '@cloudbeaver/core-executor'; +import { ResultDataFormat } from '@cloudbeaver/core-sdk'; + +import type { IDatabaseResultSet } from '../IDatabaseResultSet'; +import { databaseDataAction } from './DatabaseDataActionDecorator'; +import type { DatabaseDataEditorActionsData, IDatabaseDataSelectAction } from './IDatabaseDataSelectAction'; + +export interface IResultSetSelectKey { + row: number; + column?: number; +} + +@databaseDataAction() +export class ResultSetSelectAction implements IDatabaseDataSelectAction { + static dataFormat = ResultDataFormat.Resultset; + + result: IDatabaseResultSet; + + readonly actions: IExecutor>; + private selectedElements: Map; + + constructor(result: IDatabaseResultSet) { + this.result = result; + this.actions = new Executor(); + this.selectedElements = new Map(); + } + + updateResult(result: IDatabaseResultSet): void { + this.result = result; + } + + isSelected(): boolean { + return this.selectedElements.size > 0; + } + + isElementSelected(key: IResultSetSelectKey): boolean { + const row = this.selectedElements.get(key.row); + + if (!row) { + return false; + } + + if (key.column !== undefined) { + return row.includes(key.column); + } + + return row.length === this.result.data?.columns?.length; + } + + set(key: IResultSetSelectKey, selected: boolean): void { + try { + if (!this.selectedElements.has(key.row)) { + if (!selected) { + return; + } + this.selectedElements.set(key.row, []); + } + + const columns = this.selectedElements.get(key.row)!; + + if (key.column) { + if (selected) { + if (!columns.includes(key.column)) { + columns.push(key.column); + } + } else { + const index = columns.indexOf(key.column); + + if (index >= 0) { + columns.splice(index, 1); + } + + if (columns.length === 0) { + this.selectedElements.delete(key.row); + } + } + return; + } + + if (selected) { + columns.push(...(this.result.data?.columns?.map((c, i) => i) || [])); + } else { + this.selectedElements.delete(key.row); + } + } finally { + this.actions.execute({ + type: 'select', + resultId: this.result.id, + key, + selected, + }); + } + } + + clear(): void { + this.actions.execute({ + type: 'clear', + resultId: this.result.id, + }); + } +} diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataActions.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataActions.ts new file mode 100644 index 0000000000..18f6c24efd --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataActions.ts @@ -0,0 +1,57 @@ +/* + * 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 { makeObservable, observable } from 'mobx'; + +import type { IDatabaseDataAction, IDatabaseDataActionClass } from './IDatabaseDataAction'; +import type { IDatabaseDataActions } from './IDatabaseDataActions'; +import type { IDatabaseDataResult } from './IDatabaseDataResult'; + +type ActionsMap = Map< +IDatabaseDataActionClass>, +IDatabaseDataAction +>; + +export class DatabaseDataActions implements IDatabaseDataActions { + private actions: Map>; + + constructor() { + this.actions = new Map(); + + makeObservable, 'actions'>(this, { + actions: observable.shallow, + }); + } + + get >( + result: TResult, + Action: IDatabaseDataActionClass + ): T { + if (!this.actions.has(result.id)) { + this.actions.set(result.id, observable.map(undefined, { deep: false })); + } + + const actionsMap = this.actions.get(result.id)!; + + if (!actionsMap.has(Action)) { + actionsMap.set(Action, new Action(result)); + } + + return actionsMap.get(Action)! as T; + } + + updateResults(results: TResult[]): void { + const keys = Array.from(this.actions.keys()); + + for (const key of keys) { + if (!results.some(result => result.id === key)) { + this.actions.delete(key); + } + } + } +} diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts index d07a822b14..29b8885cca 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts @@ -11,6 +11,9 @@ import { observable, makeObservable } from 'mobx'; import { ResultDataFormat } from '@cloudbeaver/core-sdk'; import type { IExecutionContext } from '../IExecutionContext'; +import { DatabaseDataActions } from './DatabaseDataActions'; +import type { IDatabaseDataAction, IDatabaseDataActionClass } from './IDatabaseDataAction'; +import type { IDatabaseDataActions } from './IDatabaseDataActions'; import type { IDatabaseDataEditor, IDatabaseDataResultEditor } from './IDatabaseDataEditor'; import type { IDatabaseDataResult } from './IDatabaseDataResult'; import { DatabaseDataAccessMode, IDatabaseDataSource, IRequestInfo } from './IDatabaseDataSource'; @@ -21,6 +24,7 @@ implements IDatabaseDataSource { dataFormat: ResultDataFormat; supportedDataFormats: ResultDataFormat[]; editor: IDatabaseDataEditor | null; + actions: IDatabaseDataActions; results: TResult[]; offset: number; count: number; @@ -50,6 +54,7 @@ implements IDatabaseDataSource { activeSave: observable, }); + this.actions = new DatabaseDataActions(); this.access = DatabaseDataAccessMode.Default; this.results = []; this.editor = null; @@ -68,6 +73,17 @@ implements IDatabaseDataSource { this.error = null; } + getAction>( + resultIndex: number, + action: IDatabaseDataActionClass + ): T { + if (!this.hasResult(resultIndex)) { + throw new Error('Result index out of range'); + } + + return this.actions.get(this.results[resultIndex], action); + } + abstract cancel(): Promise | boolean; hasResult(resultIndex: number): boolean { diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataAction.ts new file mode 100644 index 0000000000..0174afaf9d --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataAction.ts @@ -0,0 +1,24 @@ +/* + * 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 type { ResultDataFormat } from '@cloudbeaver/core-sdk'; + +import type { IDatabaseDataResult } from './IDatabaseDataResult'; + +export interface IDatabaseDataActionClass< + TResult extends IDatabaseDataResult, + TAction extends IDatabaseDataAction +> { + new (result: TResult): TAction; + dataFormat: ResultDataFormat; +} + +export interface IDatabaseDataAction { + readonly result: TResult; + updateResult: (result: TResult) => void; +} diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataActions.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataActions.ts new file mode 100644 index 0000000000..5202aad4e5 --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataActions.ts @@ -0,0 +1,19 @@ +/* + * 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 type { IDatabaseDataAction, IDatabaseDataActionClass } from './IDatabaseDataAction'; +import type { IDatabaseDataResult } from './IDatabaseDataResult'; + +export interface IDatabaseDataActions { + get: >( + result: TResult, + action: IDatabaseDataActionClass + ) => T; + + updateResults: (results: TResult[]) => void; +} diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts index d7849d0e84..017bdb7d61 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts @@ -9,6 +9,8 @@ import type { ResultDataFormat } from '@cloudbeaver/core-sdk'; import type { IExecutionContext } from '../IExecutionContext'; +import type { IDatabaseDataAction, IDatabaseDataActionClass } from './IDatabaseDataAction'; +import type { IDatabaseDataActions } from './IDatabaseDataActions'; import type { IDatabaseDataEditor, IDatabaseDataResultEditor } from './IDatabaseDataEditor'; import type { IDatabaseDataResult } from './IDatabaseDataResult'; @@ -26,6 +28,7 @@ export interface IDatabaseDataSource; readonly editor: IDatabaseDataEditor | null; readonly results: TResult[]; readonly offset: number; @@ -41,6 +44,12 @@ export interface IDatabaseDataSource boolean; + getAction: >( + resultIndex: number, + action: IDatabaseDataActionClass + ) => T; + + /** @deprecated will be moved to getAction */ getEditor: (resultIndex: number) => IDatabaseDataResultEditor; getResult: (index: number) => TResult | null; diff --git a/webapp/packages/plugin-data-viewer/src/index.ts b/webapp/packages/plugin-data-viewer/src/index.ts index f006f27fb4..d558e124e3 100644 --- a/webapp/packages/plugin-data-viewer/src/index.ts +++ b/webapp/packages/plugin-data-viewer/src/index.ts @@ -2,10 +2,15 @@ import { manifest } from './manifest'; export default manifest; +export * from './DatabaseDataModel/Actions/DatabaseDataActionDecorator'; +export * from './DatabaseDataModel/Actions/DatabaseDataActionDecorator'; +export * from './DatabaseDataModel/DatabaseDataActions'; export * from './DatabaseDataModel/DatabaseDataEditor'; export * from './DatabaseDataModel/DatabaseDataFormat'; export * from './DatabaseDataModel/DatabaseDataModel'; export * from './DatabaseDataModel/DatabaseDataSource'; +export * from './DatabaseDataModel/IDatabaseDataAction'; +export * from './DatabaseDataModel/IDatabaseDataActions'; export * from './DatabaseDataModel/IDatabaseDataEditor'; export * from './DatabaseDataModel/IDatabaseDataModel'; export * from './DatabaseDataModel/IDatabaseDataResult';