From 57f7d0b4aeb0f3a506d83d85111c331ba38148d9 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Wed, 13 Apr 2022 13:55:12 +0200 Subject: [PATCH 1/3] feat(plugin-data-viewer): CB-1500 possibility to save and see large lobs --- .../packages/core-app/public/icons/icons.svg | 12 ++ .../core-localization/src/locales/en.ts | 1 + .../core-localization/src/locales/it.ts | 1 + .../core-localization/src/locales/ru.ts | 1 + .../core-localization/src/locales/zh.ts | 1 + .../DataGridContextMenuSaveContentService.ts | 37 ++-- .../useGridSelectionContext.tsx | 14 +- .../src/DataGrid/DataGridTable.tsx | 4 +- .../src/DataGrid/useGridSelectedCellsCopy.ts | 6 +- .../src/DataGrid/useTableData.ts | 10 +- .../src/locales/en.ts | 1 + .../src/locales/it.ts | 1 + .../src/locales/ru.ts | 1 + .../src/locales/zh.ts | 1 + .../src/ContainerDataSource.ts | 4 + .../src/DataViewerContentSaverService.ts | 65 ------- ...ntentValue.ts => ResultSetContentValue.ts} | 6 + .../Actions/ResultSet/ResultSetDataAction.ts | 2 +- ...sUtils.ts => ResultSetDataElementUtils.ts} | 14 +- .../Actions/ResultSet/ResultSetEditAction.ts | 26 +-- .../ResultSet/ResultSetFormatAction.ts | 6 +- .../ResultSet/ResultSetSelectAction.ts | 46 ++--- .../Actions/ResultSet/ResultSetViewAction.ts | 12 +- .../DatabaseDataModel/DatabaseDataManager.ts | 127 ++++++++++++ .../DatabaseDataModel/DatabaseDataSource.ts | 3 + .../DatabaseDataModel/IDatabaseDataManager.ts | 19 ++ .../DatabaseDataModel/IDatabaseDataSource.ts | 3 + .../ValuePanelPresentation/ContentLoader.tsx | 49 +++++ .../ImageValue/ImageValuePresentation.tsx | 182 +++++++++++++++--- .../ImageValuePresentationBootstrap.ts | 2 +- .../TextValue/TextValuePresentation.tsx | 39 +++- .../VALUE_PANEL_TOOLS_STYLES.ts | 36 ++++ .../packages/plugin-data-viewer/src/index.ts | 7 +- .../plugin-data-viewer/src/locales/en.ts | 2 + .../plugin-data-viewer/src/locales/it.ts | 2 + .../plugin-data-viewer/src/locales/ru.ts | 2 + .../plugin-data-viewer/src/locales/zh.ts | 2 + .../plugin-data-viewer/src/manifest.ts | 2 - .../src/GISValuePresentation.tsx | 4 +- .../plugin-sql-editor/src/QueryDataSource.ts | 4 +- 40 files changed, 566 insertions(+), 191 deletions(-) delete mode 100644 webapp/packages/plugin-data-viewer/src/DataViewerContentSaverService.ts rename webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/{isResultSetContentValue.ts => ResultSetContentValue.ts} (74%) rename webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/{ResultSetDataKeysUtils.ts => ResultSetDataElementUtils.ts} (51%) create mode 100644 webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataManager.ts create mode 100644 webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataManager.ts create mode 100644 webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ContentLoader.tsx create mode 100644 webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ValuePanelTools/VALUE_PANEL_TOOLS_STYLES.ts diff --git a/webapp/packages/core-app/public/icons/icons.svg b/webapp/packages/core-app/public/icons/icons.svg index 58492acbfe..86782beddc 100644 --- a/webapp/packages/core-app/public/icons/icons.svg +++ b/webapp/packages/core-app/public/icons/icons.svg @@ -358,4 +358,16 @@ + + + + + + + + + + + + diff --git a/webapp/packages/core-localization/src/locales/en.ts b/webapp/packages/core-localization/src/locales/en.ts index addaf932c7..0c2cd2d2e3 100644 --- a/webapp/packages/core-localization/src/locales/en.ts +++ b/webapp/packages/core-localization/src/locales/en.ts @@ -69,6 +69,7 @@ export const defaultENLocale = [ ['ui_close_all_to_the_right', 'Close all to the Right'], ['ui_close_all_to_the_left', 'Close all to the Left'], ['ui_or', 'Or'], + ['ui_download', 'Download'], ['root_permission_denied', "You don't have permissions"], ['root_permission_no_permission', "You don't have permission for this action"], diff --git a/webapp/packages/core-localization/src/locales/it.ts b/webapp/packages/core-localization/src/locales/it.ts index bc90074f26..1a4290666c 100644 --- a/webapp/packages/core-localization/src/locales/it.ts +++ b/webapp/packages/core-localization/src/locales/it.ts @@ -48,6 +48,7 @@ export const defaultITLocale = [ ['ui_close_all_to_the_right', 'Close all to the Right'], ['ui_close_all_to_the_left', 'Close all to the Left'], ['ui_or', 'Or'], + ['ui_download', 'Download'], ['root_permission_denied', 'Non hai i permessi'], ['app_root_session_expire_warning_title', 'La sessione sta per scadere'], diff --git a/webapp/packages/core-localization/src/locales/ru.ts b/webapp/packages/core-localization/src/locales/ru.ts index e3711fb411..de80dba9ed 100644 --- a/webapp/packages/core-localization/src/locales/ru.ts +++ b/webapp/packages/core-localization/src/locales/ru.ts @@ -67,6 +67,7 @@ export const defaultRULocale = [ ['ui_close_all_to_the_right', 'Закрыть все справа'], ['ui_close_all_to_the_left', 'Закрыть все слева'], ['ui_or', 'Или'], + ['ui_download', 'Загрузить'], ['root_permission_denied', 'Отказано в доступе'], ['root_permission_no_permission', 'У вас нет разрешения на это действие'], diff --git a/webapp/packages/core-localization/src/locales/zh.ts b/webapp/packages/core-localization/src/locales/zh.ts index 055f284e9a..0bec5655ee 100644 --- a/webapp/packages/core-localization/src/locales/zh.ts +++ b/webapp/packages/core-localization/src/locales/zh.ts @@ -69,6 +69,7 @@ export const defaultZHLocale = [ ['ui_close_all_to_the_right', 'Close all to the Right'], ['ui_close_all_to_the_left', 'Close all to the Left'], ['ui_or', 'Or'], + ['ui_download', 'Download'], ['root_permission_denied', '您没有权限'], ['root_permission_no_permission', '您没有权限执行此操作'], diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuSaveContentService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuSaveContentService.ts index cbf7bccdee..b48af47b49 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuSaveContentService.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuSaveContentService.ts @@ -7,8 +7,8 @@ */ import { injectable } from '@cloudbeaver/core-di'; -import { download } from '@cloudbeaver/core-utils'; -import { DataViewerContentSaverService } from '@cloudbeaver/plugin-data-viewer'; +import { NotificationService } from '@cloudbeaver/core-events'; +import { ResultSetDataElementUtils } from '@cloudbeaver/plugin-data-viewer'; import { DataGridContextMenuService } from './DataGridContextMenuService'; @@ -18,7 +18,7 @@ export class DataGridContextMenuSaveContentService { constructor( private readonly dataGridContextMenuService: DataGridContextMenuService, - private readonly dataViewerContentSaverService: DataViewerContentSaverService + private readonly notificationService: NotificationService ) { } getMenuContentSaveToken(): string { @@ -37,21 +37,28 @@ export class DataGridContextMenuSaveContentService { return context.contextType === DataGridContextMenuService.cellContext; }, onClick: async context => { - const url = await this.dataViewerContentSaverService.getElementValueURL( - context.data.model, - context.data.resultIndex, - context.data.key - ); - - if (url) { - download(url); + try { + await context.data.model.source.dataManager.downloadFileFor( + context.data.key, + context.data.resultIndex + ); + } catch (exception) { + this.notificationService.logException(exception as any, 'data_grid_table_context_menu_save_value_error'); } }, - isHidden: context => !this.dataViewerContentSaverService.canSaveElementValue( - context.data.model, - context.data.resultIndex, - context.data.key + isHidden: context => !context.data.model.source.dataManager.canGetFileURLFor( + context.data.key, + context.data.resultIndex ), + isDisabled: context => { + const { model, key } = context.data; + + if (!model.source.dataManager.activeElement) { + return false; + } + + return ResultSetDataElementUtils.isEqual(key, model.source.dataManager.activeElement); + }, } ); } diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridSelection/useGridSelectionContext.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridSelection/useGridSelectionContext.tsx index cd86c37f1f..34beaa3ce8 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridSelection/useGridSelectionContext.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridSelection/useGridSelectionContext.tsx @@ -10,7 +10,7 @@ import { action, observable } from 'mobx'; import { useState } from 'react'; import { useObjectRef } from '@cloudbeaver/core-blocks'; -import { IResultSetColumnKey, IResultSetElementKey, IResultSetRowKey, ResultSetDataKeysUtils, ResultSetSelectAction } from '@cloudbeaver/plugin-data-viewer'; +import { IResultSetColumnKey, IResultSetElementKey, IResultSetRowKey, ResultSetDataElementUtils, ResultSetSelectAction } from '@cloudbeaver/plugin-data-viewer'; import type { ITableData } from '../TableDataContext'; import type { IDraggingPosition } from '../useGridDragging'; @@ -52,7 +52,7 @@ export function useGridSelectionContext( let selected = true; const rowsSelection: IResultSetElementKey[][] = []; - const columnsToSelect: Array = columns.length > 0 ? columns : [undefined]; + const columnsToSelect: Array = columns.length > 0 ? columns : [undefined]; for (let rowIndex = firstRowIndex; rowIndex <= lastRowIndex; rowIndex++) { const row = props.tableData.getRow(rowIndex); @@ -90,15 +90,15 @@ export function useGridSelectionContext( const newElements = rowSelection .filter( element => !rowsSelection[i] - .some(column => ResultSetDataKeysUtils.isEqual(column.column, element)) + .some(column => ResultSetDataElementUtils.isKeyEqual(column.column, element)) ) .map(column => ({ row, column })); - temporarySelection.set(ResultSetDataKeysUtils.serialize(row), + temporarySelection.set(ResultSetDataElementUtils.serializeKey(row), [...rowsSelection[i], ...newElements] .filter(column => { if (selected) { - return !rowSelection.some(key => ResultSetDataKeysUtils.isEqual(key, column.column)); + return !rowSelection.some(key => ResultSetDataElementUtils.isKeyEqual(key, column.column)); } return true; })); @@ -174,13 +174,13 @@ export function useGridSelectionContext( const row = props.tableData.getRow(rowIdx); - const temporaryRowSelection = state.temporarySelection.get(ResultSetDataKeysUtils.serialize(row)); + const temporaryRowSelection = state.temporarySelection.get(ResultSetDataElementUtils.serializeKey(row)); if (temporaryRowSelection) { if (column === undefined) { return (temporaryRowSelection || []).length === props.tableData.columnKeys.length; } - return temporaryRowSelection.some(key => ResultSetDataKeysUtils.isEqual(key.column, column)); + return temporaryRowSelection.some(key => ResultSetDataElementUtils.isKeyEqual(key.column, column)); } return props.selectionAction.isElementSelected({ row, column }); 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 ddcb563d27..49042f07d7 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx @@ -21,7 +21,7 @@ import { useStyles } from '@cloudbeaver/core-theming'; import { ClipboardService } from '@cloudbeaver/core-ui'; import { DatabaseDataSelectActionsData, DatabaseEditChangeType, IDatabaseResultSet, IDataPresentationProps, - IResultSetEditActionData, IResultSetElementKey, IResultSetPartialKey, ResultSetDataKeysUtils, ResultSetSelectAction + IResultSetEditActionData, IResultSetElementKey, IResultSetPartialKey, ResultSetDataElementUtils, ResultSetSelectAction } from '@cloudbeaver/plugin-data-viewer'; import { CellPosition, EditingContext } from '../Editing/EditingContext'; @@ -412,7 +412,7 @@ export const DataGridTable = observer ResultSetDataKeysUtils.isEqual(columnKey, column)) + !selectedColumns.some(columnKey => ResultSetDataElementUtils.isKeyEqual(columnKey, column)) ) { continue; } - const cellKey = rowSelection.find(key => ResultSetDataKeysUtils.isEqual(key.column, column)); + const cellKey = rowSelection.find(key => ResultSetDataElementUtils.isKeyEqual(key.column, column)); if (cellKey) { rowCellsValues.push(getCellCopyValue(tableData, cellKey)); 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 edb5ea5183..c231108603 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useTableData.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useTableData.ts @@ -13,7 +13,7 @@ import { useObservableRef } from '@cloudbeaver/core-blocks'; import { TextTools, uuid } from '@cloudbeaver/core-utils'; import { IDatabaseDataModel, IDatabaseResultSet, IResultSetColumnKey, IResultSetElementKey, IResultSetRowKey, - ResultSetConstraintAction, ResultSetDataAction, ResultSetDataKeysUtils, + ResultSetConstraintAction, ResultSetDataAction, ResultSetDataElementUtils, ResultSetEditAction, ResultSetFormatAction, ResultSetViewAction } from '@cloudbeaver/plugin-data-viewer'; @@ -115,7 +115,7 @@ export function useTableData( getColumnByDataIndex(key) { return this.columns.find(column => ( column.columnDataIndex !== null - && ResultSetDataKeysUtils.isEqual(column.columnDataIndex, key) + && ResultSetDataElementUtils.isKeyEqual(column.columnDataIndex, key) ))!; }, getColumnInfo(key) { @@ -131,11 +131,11 @@ export function useTableData( return this.columns .findIndex(column => ( column.columnDataIndex !== null - && ResultSetDataKeysUtils.isEqual(columnKey, column.columnDataIndex) + && ResultSetDataElementUtils.isKeyEqual(columnKey, column.columnDataIndex) )); }, getRowIndexFromKey(rowKey) { - return this.rows.findIndex(row => ResultSetDataKeysUtils.isEqual(rowKey, row)); + return this.rows.findIndex(row => ResultSetDataElementUtils.isKeyEqual(rowKey, row)); }, getColumnsInRange(startIndex, endIndex) { if (startIndex === endIndex) { @@ -170,7 +170,7 @@ export function useTableData( return ( !column.editable - || this.format.isReadOnly(key) + || this.format.isReadOnly(key) ); }, }), { diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/locales/en.ts b/webapp/packages/plugin-data-spreadsheet-new/src/locales/en.ts index f835fca55f..d2b854a353 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/locales/en.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/locales/en.ts @@ -19,6 +19,7 @@ export default [ ['data_grid_table_tooltip_column_header_order', 'Sort by column'], ['data_grid_table_context_menu_filter_dialog_title', 'Edit value'], ['data_grid_table_context_menu_filter_clipboard_permission', 'Give access to clipboard'], + ['data_grid_table_context_menu_save_value_error', 'Failed to save value'], ['data_grid_table_index_column_tooltip', 'Select whole table'], ['data_grid_table_readonly_tooltip', 'Read-only'], ]; diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/locales/it.ts b/webapp/packages/plugin-data-spreadsheet-new/src/locales/it.ts index eaac0a29d1..6c82540d8b 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/locales/it.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/locales/it.ts @@ -14,6 +14,7 @@ export default [ ['data_grid_table_tooltip_column_header_order', 'Ordina per colonna'], ['data_grid_table_context_menu_filter_dialog_title', 'Modifica valore'], ['data_grid_table_context_menu_filter_clipboard_permission', 'Dai accesso agli appunti'], + ['data_grid_table_context_menu_save_value_error', 'Failed to save value'], ['data_grid_table_index_column_tooltip', 'Seleziona tutta la tabella'], ['data_grid_table_readonly_tooltip', 'In sola lettura'], ]; diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/locales/ru.ts b/webapp/packages/plugin-data-spreadsheet-new/src/locales/ru.ts index 43576cac2c..360fd1267c 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/locales/ru.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/locales/ru.ts @@ -19,6 +19,7 @@ export default [ ['data_grid_table_tooltip_column_header_order', 'Сортировка по колонке'], ['data_grid_table_context_menu_filter_dialog_title', 'Редактировать значение'], ['data_grid_table_context_menu_filter_clipboard_permission', 'Дать доступ к буферу обмена'], + ['data_grid_table_context_menu_save_value_error', 'Не удалось сохранить значение'], ['data_grid_table_index_column_tooltip', 'Выбрать всю таблицу'], ['data_grid_table_readonly_tooltip', 'Доступно только для чтения'], ]; diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/locales/zh.ts b/webapp/packages/plugin-data-spreadsheet-new/src/locales/zh.ts index 4bdb95606e..37bb8b2298 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/locales/zh.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/locales/zh.ts @@ -19,6 +19,7 @@ export default [ ['data_grid_table_tooltip_column_header_order', '按列排序'], ['data_grid_table_context_menu_filter_dialog_title', '编辑值'], ['data_grid_table_context_menu_filter_clipboard_permission', '授予访问剪贴板的权限'], + ['data_grid_table_context_menu_save_value_error', 'Failed to save value'], ['data_grid_table_index_column_tooltip', '选择整个表'], ['data_grid_table_readonly_tooltip', '只读'], ]; diff --git a/webapp/packages/plugin-data-viewer/src/ContainerDataSource.ts b/webapp/packages/plugin-data-viewer/src/ContainerDataSource.ts index b75d304504..c8831bd25c 100644 --- a/webapp/packages/plugin-data-viewer/src/ContainerDataSource.ts +++ b/webapp/packages/plugin-data-viewer/src/ContainerDataSource.ts @@ -14,7 +14,9 @@ import { AsyncTaskInfoService, GraphQLService, ResultDataFormat, SqlExecuteInfo, import { DocumentEditAction } from './DatabaseDataModel/Actions/Document/DocumentEditAction'; import { ResultSetEditAction } from './DatabaseDataModel/Actions/ResultSet/ResultSetEditAction'; +import { DatabaseDataManager } from './DatabaseDataModel/DatabaseDataManager'; import { DatabaseDataSource } from './DatabaseDataModel/DatabaseDataSource'; +import type { IDatabaseDataManager } from './DatabaseDataModel/IDatabaseDataManager'; import type { IDatabaseDataOptions } from './DatabaseDataModel/IDatabaseDataOptions'; import type { IDatabaseResultSet } from './DatabaseDataModel/IDatabaseResultSet'; @@ -24,6 +26,7 @@ export interface IDataContainerOptions extends IDatabaseDataOptions { export class ContainerDataSource extends DatabaseDataSource { currentTask: ITask | null; + dataManager: IDatabaseDataManager; get canCancel(): boolean { return this.currentTask?.cancellable || false; @@ -36,6 +39,7 @@ export class ContainerDataSource extends DatabaseDataSource TRUNCATED_LIMIT; +} \ No newline at end of file diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataAction.ts index 8ad4414c0f..88e0cc7c30 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataAction.ts @@ -17,7 +17,7 @@ import { databaseDataAction } from '../DatabaseDataActionDecorator'; import type { IDatabaseDataResultAction } from '../IDatabaseDataResultAction'; import type { IResultSetContentValue } from './IResultSetContentValue'; import type { IResultSetColumnKey, IResultSetElementKey, IResultSetRowKey } from './IResultSetDataKey'; -import { isResultSetContentValue } from './isResultSetContentValue'; +import { isResultSetContentValue } from './ResultSetContentValue'; import type { IResultSetValue } from './ResultSetFormatAction'; @databaseDataAction() diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataKeysUtils.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataElementUtils.ts similarity index 51% rename from webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataKeysUtils.ts rename to webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataElementUtils.ts index 927e4b6ed8..243c519f0e 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataKeysUtils.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataElementUtils.ts @@ -6,10 +6,16 @@ * you may not use this file except in compliance with the License. */ -import type { IResultSetColumnKey, IResultSetRowKey } from './IResultSetDataKey'; +import type { IResultSetColumnKey, IResultSetElementKey, IResultSetRowKey } from './IResultSetDataKey'; -export const ResultSetDataKeysUtils = { - serialize(key: IResultSetColumnKey | IResultSetRowKey): string { +export const ResultSetDataElementUtils = { + serialize(element: IResultSetElementKey): string { + return this.serializeKey(element.column) + this.serializeKey(element.row); + }, + isEqual(a: IResultSetElementKey, b: IResultSetElementKey) { + return this.isKeyEqual(a.column, b.column) && this.isKeyEqual(a.row, b.row); + }, + serializeKey(key: IResultSetColumnKey | IResultSetRowKey): string { let base = `${key.index}`; if ('key' in key) { @@ -18,7 +24,7 @@ export const ResultSetDataKeysUtils = { return base; }, - isEqual(a: T, b: T): boolean { + isKeyEqual(a: T, b: T): boolean { if (a.index !== b.index) { return false; } 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 b5ab7adfd1..7607bb0c36 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 @@ -18,9 +18,9 @@ import { databaseDataAction } from '../DatabaseDataActionDecorator'; import { DatabaseEditAction } from '../DatabaseEditAction'; import { DatabaseEditChangeType, IDatabaseDataEditActionData, IDatabaseDataEditActionValue, IDatabaseDataEditApplyActionData, IDatabaseDataEditApplyActionUpdate } from '../IDatabaseDataEditAction'; import type { IResultSetColumnKey, IResultSetElementKey, IResultSetRowKey } from './IResultSetDataKey'; -import { isResultSetContentValue } from './isResultSetContentValue'; +import { isResultSetContentValue } from './ResultSetContentValue'; import { ResultSetDataAction } from './ResultSetDataAction'; -import { ResultSetDataKeysUtils } from './ResultSetDataKeysUtils'; +import { ResultSetDataElementUtils } from './ResultSetDataElementUtils'; import type { IResultSetValue } from './ResultSetFormatAction'; export interface IResultSetUpdate { @@ -100,7 +100,7 @@ export class ResultSetEditAction } isElementEdited(key: IResultSetElementKey): boolean { - const update = this.editorData.get(ResultSetDataKeysUtils.serialize(key.row)); + const update = this.editorData.get(ResultSetDataElementUtils.serializeKey(key.row)); if (!update) { return false; @@ -114,7 +114,7 @@ export class ResultSetEditAction } isRowEdited(key: IResultSetRowKey): boolean { - const update = this.editorData.get(ResultSetDataKeysUtils.serialize(key)); + const update = this.editorData.get(ResultSetDataElementUtils.serializeKey(key)); if (!update) { return false; @@ -124,7 +124,7 @@ export class ResultSetEditAction } getElementState(key: IResultSetElementKey): DatabaseEditChangeType | null { - const update = this.editorData.get(ResultSetDataKeysUtils.serialize(key.row)); + const update = this.editorData.get(ResultSetDataElementUtils.serializeKey(key.row)); if (!update) { return null; @@ -143,7 +143,7 @@ export class ResultSetEditAction get(key: IResultSetElementKey): IResultSetValue | undefined { return this.editorData - .get(ResultSetDataKeysUtils.serialize(key.row)) + .get(ResultSetDataElementUtils.serializeKey(key.row)) ?.update[key.column.index]; } @@ -217,7 +217,7 @@ export class ResultSetEditAction const rowKeys = new Set(); for (const key of keys) { - const serialized = ResultSetDataKeysUtils.serialize(key.row); + const serialized = ResultSetDataElementUtils.serializeKey(key.row); if (!rowKeys.has(serialized)) { rows.push(key.row); @@ -233,7 +233,7 @@ export class ResultSetEditAction let value = this.data.getRowValue(row); const editedValue = this.editorData - .get(ResultSetDataKeysUtils.serialize(row)); + .get(ResultSetDataElementUtils.serializeKey(row)); if (editedValue) { value = editedValue.update; @@ -248,7 +248,7 @@ export class ResultSetEditAction const deleted: Array> = []; for (const key of keys) { - const serializedKey = ResultSetDataKeysUtils.serialize(key.row); + const serializedKey = ResultSetDataElementUtils.serializeKey(key.row); const update = this.editorData.get(serializedKey); if (update?.type === DatabaseEditChangeType.add) { @@ -280,7 +280,7 @@ export class ResultSetEditAction } deleteRow(key: IResultSetRowKey, column?: IResultSetColumnKey, silent?: boolean): void { - const serializedKey = ResultSetDataKeysUtils.serialize(key); + const serializedKey = ResultSetDataElementUtils.serializeKey(key); const update = this.editorData.get(serializedKey); if (update && update.type !== DatabaseEditChangeType.delete) { @@ -401,7 +401,7 @@ export class ResultSetEditAction const revertedAdditions: Array> = []; for (const key of keys) { - const row = ResultSetDataKeysUtils.serialize(key.row); + const row = ResultSetDataElementUtils.serializeKey(key.row); const update = this.editorData.get(row); if (!update) { @@ -520,7 +520,7 @@ export class ResultSetEditAction if (update.source && !update.source.some( (value, i) => !this.compareCellValue(value, update.update[i]) )) { - this.editorData.delete(ResultSetDataKeysUtils.serialize(update.row)); + this.editorData.delete(ResultSetDataElementUtils.serializeKey(update.row)); } } @@ -529,7 +529,7 @@ export class ResultSetEditAction type: DatabaseEditChangeType, update?: IResultSetValue[] ): [IResultSetUpdate, boolean] { - const key = ResultSetDataKeysUtils.serialize(row); + const key = ResultSetDataElementUtils.serializeKey(row); let created = false; if (!this.editorData.has(key)) { diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction.ts index bfd4d1f75b..8a9bb981e2 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction.ts @@ -15,7 +15,7 @@ import { databaseDataAction } from '../DatabaseDataActionDecorator'; import { DatabaseEditChangeType } from '../IDatabaseDataEditAction'; import type { IDatabaseDataFormatAction } from '../IDatabaseDataFormatAction'; import type { IResultSetElementKey, IResultSetPartialKey } from './IResultSetDataKey'; -import { isResultSetContentValue } from './isResultSetContentValue'; +import { isResultSetContentValue } from './ResultSetContentValue'; import { ResultSetEditAction } from './ResultSetEditAction'; import { ResultSetViewAction } from './ResultSetViewAction'; @@ -27,8 +27,8 @@ export class ResultSetFormatAction extends DatabaseDataAction { static dataFormat = [ResultDataFormat.Resultset]; - private view: ResultSetViewAction; - private edit: ResultSetEditAction; + private readonly view: ResultSetViewAction; + private readonly edit: ResultSetEditAction; constructor( source: IDatabaseDataSource, 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 d8b5a02410..f61c071d7f 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 @@ -19,7 +19,7 @@ import { DatabaseEditChangeType, IDatabaseDataEditActionData, IDatabaseDataEditA import type { DatabaseDataSelectActionsData } from '../IDatabaseDataSelectAction'; import type { IResultSetColumnKey, IResultSetElementKey, IResultSetPartialKey, IResultSetRowKey } from './IResultSetDataKey'; import { ResultSetDataAction } from './ResultSetDataAction'; -import { ResultSetDataKeysUtils } from './ResultSetDataKeysUtils'; +import { ResultSetDataElementUtils } from './ResultSetDataElementUtils'; import { ResultSetEditAction } from './ResultSetEditAction'; import type { IResultSetValue } from './ResultSetFormatAction'; import { ResultSetViewAction } from './ResultSetViewAction'; @@ -36,10 +36,10 @@ export class ResultSetSelectAction extends DatabaseSelectAction; private focusedElement: IResultSetElementKey | null; - private view: ResultSetViewAction; - private edit: ResultSetEditAction; - private data: ResultSetDataAction; - private validationDisposer: IReactionDisposer; + private readonly view: ResultSetViewAction; + private readonly edit: ResultSetEditAction; + private readonly data: ResultSetDataAction; + private readonly validationDisposer: IReactionDisposer; constructor( source: IDatabaseDataSource, @@ -68,9 +68,9 @@ export class ResultSetSelectAction extends DatabaseSelectAction this.view.rowKeys, (current, previous) => { if (this.focusedElement) { const focus = this.focusedElement; - const currentIndex = current.findIndex(key => ResultSetDataKeysUtils.isEqual(key, focus.row)); + const currentIndex = current.findIndex(key => ResultSetDataElementUtils.isKeyEqual(key, focus.row)); - const focusIndex = previous.findIndex(key => ResultSetDataKeysUtils.isEqual(key, focus.row)); + const focusIndex = previous.findIndex(key => ResultSetDataElementUtils.isKeyEqual(key, focus.row)); if (currentIndex >= 0 && focusIndex === -1) { return; @@ -81,10 +81,10 @@ export class ResultSetSelectAction extends DatabaseSelectAction ResultSetDataKeysUtils.isEqual(key, focus.row))) { + if (!current.some(key => ResultSetDataElementUtils.isKeyEqual(key, focus.row))) { for (let index = focusIndex; index >= 0; index--) { const previousElement = previous[index]; - const row = current.find(key => ResultSetDataKeysUtils.isEqual(key, previousElement)); + const row = current.find(key => ResultSetDataElementUtils.isKeyEqual(key, previousElement)); if (row) { this.focus({ ...this.focusedElement, row }); @@ -93,7 +93,7 @@ export class ResultSetSelectAction extends DatabaseSelectAction ResultSetDataKeysUtils.isEqual(key, nextElement)); + const row = current.find(key => ResultSetDataElementUtils.isKeyEqual(key, nextElement)); if (row) { this.focus({ ...this.focusedElement, row }); @@ -119,8 +119,8 @@ export class ResultSetSelectAction extends DatabaseSelectAction ResultSetDataKeysUtils.isEqual(selected.column, key)); + return list.some(selected => ResultSetDataElementUtils.isKeyEqual(selected.column, key)); } private removeColumnSelection(list: IResultSetElementKey[], key: IResultSetColumnKey) { - const index = list.findIndex(selected => ResultSetDataKeysUtils.isEqual(selected.column, key)); + const index = list.findIndex(selected => ResultSetDataElementUtils.isKeyEqual(selected.column, key)); if (index >= 0) { list.splice(index, 1); diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetViewAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetViewAction.ts index bfff2aa6b3..dab977a1b4 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetViewAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetViewAction.ts @@ -17,9 +17,9 @@ import { databaseDataAction } from '../DatabaseDataActionDecorator'; import type { IDatabaseDataResultAction } from '../IDatabaseDataResultAction'; import type { IResultSetContentValue } from './IResultSetContentValue'; import type { IResultSetColumnKey, IResultSetElementKey, IResultSetRowKey } from './IResultSetDataKey'; -import { isResultSetContentValue } from './isResultSetContentValue'; +import { isResultSetContentValue } from './ResultSetContentValue'; import { ResultSetDataAction } from './ResultSetDataAction'; -import { ResultSetDataKeysUtils } from './ResultSetDataKeysUtils'; +import { ResultSetDataElementUtils } from './ResultSetDataElementUtils'; import { ResultSetEditAction } from './ResultSetEditAction'; import type { IResultSetValue } from './ResultSetFormatAction'; @@ -47,8 +47,8 @@ export class ResultSetViewAction extends DatabaseDataAction, @@ -85,11 +85,11 @@ export class ResultSetViewAction extends DatabaseDataAction ResultSetDataKeysUtils.isEqual(row, key)); + return this.rowKeys.findIndex(row => ResultSetDataElementUtils.isKeyEqual(row, key)); } columnIndex(key: IResultSetColumnKey): number { - return this.columnKeys.findIndex(column => ResultSetDataKeysUtils.isEqual(column, key)); + return this.columnKeys.findIndex(column => ResultSetDataElementUtils.isKeyEqual(column, key)); } nextKey(key: IResultSetElementKey): IResultSetElementKey | null { diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataManager.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataManager.ts new file mode 100644 index 0000000000..8ad02a7513 --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataManager.ts @@ -0,0 +1,127 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { makeObservable, observable } from 'mobx'; + +import type { GraphQLService } from '@cloudbeaver/core-sdk'; +import { download, GlobalConstants } from '@cloudbeaver/core-utils'; + +import type { IResultSetElementKey } from './Actions/ResultSet/IResultSetDataKey'; +import { isResultSetContentValue } from './Actions/ResultSet/ResultSetContentValue'; +import { ResultSetDataAction } from './Actions/ResultSet/ResultSetDataAction'; +import { ResultSetDataElementUtils } from './Actions/ResultSet/ResultSetDataElementUtils'; +import type { IResultSetValue } from './Actions/ResultSet/ResultSetFormatAction'; +import { ResultSetViewAction } from './Actions/ResultSet/ResultSetViewAction'; +import type { IDatabaseDataManager } from './IDatabaseDataManager'; +import type { IDatabaseDataSource } from './IDatabaseDataSource'; +import type { IDatabaseResultSet } from './IDatabaseResultSet'; + +const RESULT_VALUE_PATH = 'sql-result-value'; + +export class DatabaseDataManager> implements IDatabaseDataManager { + private readonly cache: Map; + activeElement: IResultSetElementKey | null; + + constructor( + private readonly graphQLService: GraphQLService, + private readonly source: TSource + ) { + this.cache = new Map(); + this.activeElement = null; + + makeObservable, 'cache'>(this, { + cache: observable, + activeElement: observable.ref, + }); + } + + canGetFileURLFor(element: IResultSetElementKey, resultIndex: number) { + const view = this.source.getAction(resultIndex, ResultSetViewAction); + const cellValue = view.getCellValue(element); + + return isResultSetContentValue(cellValue); + } + + async getFileURLFor(element: IResultSetElementKey, resultIndex: number) { + const result = this.source.getResult(resultIndex); + const data = this.source.getAction(resultIndex, ResultSetDataAction); + const column = data.getColumn(element.column); + const row = data.getRowValue(element.row); + + if (!result || !row || !column) { + throw new Error('Failed to get arguments data'); + } + + const url = await this.source.runTask( + async () => { + try { + this.activeElement = element; + const fileName = await this.loadFileName(result, column.position, row); + return this.generateFileURL(fileName); + } finally { + this.activeElement = null; + } + } + ); + + return url; + } + + async resolveFileURLFor(element: IResultSetElementKey, resultIndex: number) { + const hash = this.getHashFor(element, resultIndex); + + if (this.cache.has(hash)) { + return this.cache.get(hash)!; + } + + const url = await this.getFileURLFor(element, resultIndex); + this.cache.set(hash, url); + + return url; + } + + async downloadFileFor(element: IResultSetElementKey, resultIndex: number) { + const url = await this.getFileURLFor(element, resultIndex); + download(url); + } + + retrieveFileURLFromCacheFor(element: IResultSetElementKey, resultIndex: number) { + const hash = this.getHashFor(element, resultIndex); + return this.cache.get(hash); + } + + clearCache() { + this.cache.clear(); + } + + private generateFileURL(fileName: string) { + return `${GlobalConstants.serviceURI}/${RESULT_VALUE_PATH}/${fileName}`; + } + + private getHashFor(element: IResultSetElementKey, resultIndex: number) { + return `${ResultSetDataElementUtils.serialize(element)}_${resultIndex}`; + } + + private async loadFileName(result: IDatabaseResultSet, columnIndex: number, row: IResultSetValue[]) { + if (!result.id) { + throw new Error("Result's id must be provided"); + } + + const response = await this.graphQLService.sdk.getResultsetDataURL({ + resultsId: result.id, + connectionId: result.connectionId, + contextId: result.contextId, + lobColumnIndex: columnIndex, + row: { + data: row, + }, + }); + + return response.url; + } +} \ No newline at end of file diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts index 1428c1793d..1cd85897be 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataSource.ts @@ -14,11 +14,13 @@ import { ResultDataFormat } from '@cloudbeaver/core-sdk'; import { DatabaseDataActions } from './DatabaseDataActions'; import type { IDatabaseDataAction, IDatabaseDataActionClass, IDatabaseDataActionInterface } from './IDatabaseDataAction'; import type { IDatabaseDataActions } from './IDatabaseDataActions'; +import type { IDatabaseDataManager } from './IDatabaseDataManager'; import type { IDatabaseDataResult } from './IDatabaseDataResult'; import { DatabaseDataAccessMode, IDatabaseDataSource, IRequestInfo } from './IDatabaseDataSource'; export abstract class DatabaseDataSource implements IDatabaseDataSource { + abstract readonly dataManager: IDatabaseDataManager; access: DatabaseDataAccessMode; dataFormat: ResultDataFormat; supportedDataFormats: ResultDataFormat[]; @@ -171,6 +173,7 @@ export abstract class DatabaseDataSource boolean; + getFileURLFor: (element: IResultSetElementKey, resultIndex: number) => Promise; + resolveFileURLFor: (element: IResultSetElementKey, resultIndex: number) => Promise; + downloadFileFor: (element: IResultSetElementKey, resultIndex: number) => Promise; + retrieveFileURLFromCacheFor: (element: IResultSetElementKey, resultIndex: number) => string | undefined; + clearCache: () => void; +} \ No newline at end of file diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts index df6142c9fc..9380587968 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataSource.ts @@ -9,8 +9,10 @@ import type { IConnectionExecutionContext } from '@cloudbeaver/core-connections'; import type { ResultDataFormat } from '@cloudbeaver/core-sdk'; +import type { DatabaseDataManager } from './DatabaseDataManager'; import type { IDatabaseDataAction, IDatabaseDataActionClass, IDatabaseDataActionInterface } from './IDatabaseDataAction'; import type { IDatabaseDataActions } from './IDatabaseDataActions'; +import type { IDatabaseDataManager } from './IDatabaseDataManager'; import type { IDatabaseDataResult } from './IDatabaseDataResult'; export interface IRequestInfo { @@ -26,6 +28,7 @@ export enum DatabaseDataAccessMode { } export interface IDatabaseDataSource { + readonly dataManager: IDatabaseDataManager; readonly access: DatabaseDataAccessMode; readonly dataFormat: ResultDataFormat; readonly supportedDataFormats: ResultDataFormat[]; diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ContentLoader.tsx b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ContentLoader.tsx new file mode 100644 index 0000000000..6dc8445bad --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ContentLoader.tsx @@ -0,0 +1,49 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { observer } from 'mobx-react-lite'; +import styled, { css } from 'reshadow'; + +import { Button } from '@cloudbeaver/core-blocks'; +import { useTranslate } from '@cloudbeaver/core-localization'; + +interface Props { + disabled?: boolean; + loading?: boolean; + className?: string; + onLoad: () => void; +} + +const style = css` + loader { + margin: auto; + display: flex; + align-items: center; + justify-content: center; + flex-direction: column; + } + loader-label { + text-align: center; + } +`; + +export const ContentLoader = observer(function ContentLoader({ disabled, loading, className, onLoad, children }) { + const translate = useTranslate(); + return styled(style)( + + {children} + + + ); +}); \ No newline at end of file diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentation.tsx b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentation.tsx index 955f7feba5..66fb0c72bb 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentation.tsx +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentation.tsx @@ -6,21 +6,26 @@ * you may not use this file except in compliance with the License. */ +import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react-lite'; -import { useState } from 'react'; import styled, { css, use } from 'reshadow'; -import type { TabContainerPanelComponent } from '@cloudbeaver/core-ui'; -import { Button } from '@cloudbeaver/core-blocks'; +import { IconOrImage, useObservableRef } from '@cloudbeaver/core-blocks'; +import { useService } from '@cloudbeaver/core-di'; +import { NotificationService } from '@cloudbeaver/core-events'; import { useTranslate } from '@cloudbeaver/core-localization'; import { useStyles } from '@cloudbeaver/core-theming'; +import type { TabContainerPanelComponent } from '@cloudbeaver/core-ui'; import { getMIME, isImageFormat, isValidUrl } from '@cloudbeaver/core-utils'; -import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue'; +import { isResultSetContentValue, isResultSetContentValueTruncated } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetContentValue'; +import { ResultSetDataElementUtils } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetDataElementUtils'; import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction'; import { ResultSetViewAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetViewAction'; import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet'; import type { IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService'; +import { ContentLoader } from '../ContentLoader'; +import { VALUE_PANEL_TOOLS_STYLES } from '../ValuePanelTools/VALUE_PANEL_TOOLS_STYLES'; const styles = css` img { @@ -36,17 +41,11 @@ const styles = css` container { display: flex; + gap: 16px; flex: 1; flex-direction: column; } - - tools { - display: flex; - flex: 0; - justify-content: flex-end; - padding-bottom: 16px; - } - + image { flex: 1; display: flex; @@ -54,40 +53,159 @@ const styles = css` } `; +interface IToolsProps { + loading?: boolean; + stretch?: boolean; + onToggleStretch?: () => void; + onSave?: () => void; +} + +const Tools = observer(function Tools({ loading, stretch, onToggleStretch, onSave }) { + const translate = useTranslate(); + + return styled(VALUE_PANEL_TOOLS_STYLES)( + + {onSave && ( + + + + + + )} + {onToggleStretch && ( + + + + + + + + + )} + + ); +}); + export const ImageValuePresentation: TabContainerPanelComponent> = observer(function ImageValuePresentation({ model, resultIndex, }) { const translate = useTranslate(); - const [stretch, setStretch] = useState(false); - const selection = model.source.getAction(resultIndex, ResultSetSelectAction); + const notificationService = useService(NotificationService); + const style = useStyles(styles); - const focusCell = selection.getFocusedElement(); + const state = useObservableRef(() => ({ + get selectedCell() { + const selection = this.model.source.getAction(this.resultIndex, ResultSetSelectAction); + const focusCell = selection.getFocusedElement(); - let src: string | undefined; + return selection.elements[0] || focusCell; + }, + get cellValue() { + const view = this.model.source.getAction(this.resultIndex, ResultSetViewAction); + const cellValue = view.getCellValue(this.selectedCell); - if (selection.elements.length > 0 || focusCell) { - const view = model.source.getAction(resultIndex, ResultSetViewAction); - const firstSelectedCell = selection.elements[0] || focusCell; + return cellValue; + }, + get src() { + if (this.savedSrc) { + return this.savedSrc; + } - const cellValue = view.getCellValue(firstSelectedCell); + if (isResultSetContentValue(this.cellValue) && this.cellValue.binary) { + return `data:${getMIME(this.cellValue.binary)};base64,${this.cellValue.binary}`; + } else if (typeof this.cellValue === 'string' && isValidUrl(this.cellValue) && isImageFormat(this.cellValue)) { + return this.cellValue; + } - if (isResultSetContentValue(cellValue) && cellValue.binary) { - src = `data:${getMIME(cellValue.binary)};base64,${cellValue.binary}`; - } else if (typeof cellValue === 'string' && isValidUrl(cellValue) && isImageFormat(cellValue)) { - src = cellValue; - } + return ''; + }, + get savedSrc() { + return this.model.source.dataManager.retrieveFileURLFromCacheFor(this.selectedCell, this.resultIndex); + }, + get canSave() { + return this.model.source.dataManager.canGetFileURLFor(this.selectedCell, this.resultIndex); + }, + get truncated() { + return isResultSetContentValue(this.cellValue) && isResultSetContentValueTruncated(this.cellValue); + }, + stretch: false, + toggleStretch() { + this.stretch = !this.stretch; + }, + async save() { + try { + await this.model.source.dataManager.downloadFileFor(this.selectedCell, this.resultIndex); + } catch (exception) { + this.notificationService.logException(exception as any, 'data_viewer_presentation_value_content_download_error'); + } + }, + }), { + selectedCell: computed, + cellValue: computed, + src: computed, + savedSrc: computed, + canSave: computed, + truncated: computed, + stretch: observable.ref, + toggleStretch: action.bound, + save: action.bound, + }, { model, resultIndex, notificationService }); + + const save = state.canSave ? state.save : undefined; + const loading = model.isLoading(); + + if (state.truncated && state.canSave && !state.savedSrc) { + const load = async () => { + try { + await model.source.dataManager.resolveFileURLFor(state.selectedCell, resultIndex); + } catch (exception) { + notificationService.logException(exception as any, 'data_viewer_presentation_value_content_download_error'); + } + }; + + return styled(style)( + + + {translate('data_viewer_presentation_value_content_trimmed_placeholder')} + + + + ); } - return styled(useStyles(styles))( + return styled(style)( - - - - - + + ); -}); +}); \ No newline at end of file diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentationBootstrap.ts b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentationBootstrap.ts index 463207a008..d596bdadb9 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentationBootstrap.ts +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentationBootstrap.ts @@ -11,7 +11,7 @@ import { ResultDataFormat } from '@cloudbeaver/core-sdk'; import { getMIME, isImageFormat, isValidUrl } from '@cloudbeaver/core-utils'; import type { IResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/IResultSetContentValue'; -import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue'; +import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetContentValue'; import type { IResultSetValue } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction'; import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction'; import { ResultSetViewAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetViewAction'; diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentation.tsx b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentation.tsx index e344c2b6ea..19be94fed5 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentation.tsx +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentation.tsx @@ -10,20 +10,23 @@ import { observable } from 'mobx'; import { observer } from 'mobx-react-lite'; import styled, { css } from 'reshadow'; -import { BASE_CONTAINERS_STYLES, Textarea, useObservableRef } from '@cloudbeaver/core-blocks'; +import { BASE_CONTAINERS_STYLES, IconOrImage, Textarea, useObservableRef } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; +import { NotificationService } from '@cloudbeaver/core-events'; +import { useTranslate } from '@cloudbeaver/core-localization'; import { useStyles } from '@cloudbeaver/core-theming'; import { BASE_TAB_STYLES, TabContainerPanelComponent, TabList, TabsState, UNDERLINE_TAB_STYLES } from '@cloudbeaver/core-ui'; import { CodeEditorLoader } from '@cloudbeaver/plugin-codemirror'; import type { IResultSetElementKey } from '../../DatabaseDataModel/Actions/ResultSet/IResultSetDataKey'; -import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue'; +import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetContentValue'; import { ResultSetEditAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetEditAction'; import { ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction'; import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction'; import { ResultSetViewAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetViewAction'; import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet'; import type { IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService'; +import { VALUE_PANEL_TOOLS_STYLES } from '../ValuePanelTools/VALUE_PANEL_TOOLS_STYLES'; import { TextValuePresentationService } from './TextValuePresentationService'; const styles = css` @@ -32,6 +35,7 @@ const styles = css` } container { display: flex; + gap: 16px; flex-direction: column; overflow: auto; flex: 1; @@ -69,8 +73,10 @@ export const TextValuePresentation: TabContainerPanelComponent ({ currentContentType: 'text/plain', lastContentType: 'text/plain', @@ -135,8 +141,21 @@ export const TextValuePresentation: TabContainerPanelComponent { + if (!firstSelectedCell) { + return; + } + + try { + await model.source.dataManager.downloadFileFor(firstSelectedCell, resultIndex); + } catch (exception) { + notificationService.logException(exception as any, 'data_viewer_presentation_value_content_download_error'); + } + }; + const useCodeEditor = state.currentContentType !== 'text/plain'; const autoFormat = firstSelectedCell && !editor.isElementEdited(firstSelectedCell); + const canSave = firstSelectedCell && model.source.dataManager.canGetFileURLFor(firstSelectedCell, resultIndex); return styled(style)( @@ -176,6 +195,20 @@ export const TextValuePresentation: TabContainerPanelComponent )} + {canSave && ( + + + + + + + + )} ); }); diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ValuePanelTools/VALUE_PANEL_TOOLS_STYLES.ts b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ValuePanelTools/VALUE_PANEL_TOOLS_STYLES.ts new file mode 100644 index 0000000000..a68b613c57 --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ValuePanelTools/VALUE_PANEL_TOOLS_STYLES.ts @@ -0,0 +1,36 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { css } from 'reshadow'; + +export const VALUE_PANEL_TOOLS_STYLES = css` + tools-container { + display: flex; + justify-content: flex-end; + gap: 8px; + } + tools { + composes: theme-background-surface theme-text-on-surface theme-border-color-background theme-form-element-radius from global; + display: flex; + box-sizing: border-box; + border: 2px solid; + } + tools-action { + composes: theme-ripple from global; + box-sizing: border-box; + background: inherit; + cursor: pointer; + padding: 4px; + width: 24px; + height: 100%; + } + IconOrImage { + width: 100%; + height: 100%; + } +`; \ No newline at end of file diff --git a/webapp/packages/plugin-data-viewer/src/index.ts b/webapp/packages/plugin-data-viewer/src/index.ts index e3982fc049..e6f9d51a97 100644 --- a/webapp/packages/plugin-data-viewer/src/index.ts +++ b/webapp/packages/plugin-data-viewer/src/index.ts @@ -8,10 +8,10 @@ export * from './DatabaseDataModel/Actions/Document/IDatabaseDataDocument'; export * from './DatabaseDataModel/Actions/Document/IDocumentElementKey'; export * from './DatabaseDataModel/Actions/ResultSet/IResultSetDataKey'; export * from './DatabaseDataModel/Actions/ResultSet/IResultSetContentValue'; -export * from './DatabaseDataModel/Actions/ResultSet/isResultSetContentValue'; +export * from './DatabaseDataModel/Actions/ResultSet/ResultSetContentValue'; export * from './DatabaseDataModel/Actions/ResultSet/ResultSetConstraintAction'; export * from './DatabaseDataModel/Actions/ResultSet/ResultSetDataAction'; -export * from './DatabaseDataModel/Actions/ResultSet/ResultSetDataKeysUtils'; +export * from './DatabaseDataModel/Actions/ResultSet/ResultSetDataElementUtils'; export * from './DatabaseDataModel/Actions/ResultSet/ResultSetEditAction'; export * from './DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction'; export * from './DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction'; @@ -38,7 +38,8 @@ export * from './DatabaseDataModel/IDatabaseDataSource'; export * from './DatabaseDataModel/IDatabaseResultSet'; export * from './DatabaseDataModel/Order'; export * from './DataViewerService'; -export * from './DataViewerContentSaverService'; +export * from './DatabaseDataModel/DatabaseDataManager'; +export * from './DatabaseDataModel/IDatabaseDataManager'; // All Services and Components that is provided by this plugin should be exported here export * from './TableViewer/TableViewerStorageService'; diff --git a/webapp/packages/plugin-data-viewer/src/locales/en.ts b/webapp/packages/plugin-data-viewer/src/locales/en.ts index 4b048609ac..688573780d 100644 --- a/webapp/packages/plugin-data-viewer/src/locales/en.ts +++ b/webapp/packages/plugin-data-viewer/src/locales/en.ts @@ -27,6 +27,8 @@ export default [ ['data_viewer_presentation_value_image_fit', 'Fit Window'], ['data_viewer_presentation_value_image_original_size', 'Original Size'], ['data_viewer_presentation_value_boolean_placeholder', "Can't show current value as boolean"], + ['data_viewer_presentation_value_content_trimmed_placeholder', 'The size of the value exceeds the limit'], + ['data_viewer_presentation_value_content_download_error', 'Download failed'], ['data_viewer_script_preview', 'Script'], ['data_viewer_script_preview_dialog_title', 'Preview changes'], ['data_viewer_script_preview_error_title', "Can't get the script"], diff --git a/webapp/packages/plugin-data-viewer/src/locales/it.ts b/webapp/packages/plugin-data-viewer/src/locales/it.ts index 4fe9d282da..4098f486d5 100644 --- a/webapp/packages/plugin-data-viewer/src/locales/it.ts +++ b/webapp/packages/plugin-data-viewer/src/locales/it.ts @@ -23,5 +23,7 @@ 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_presentation_value_content_trimmed_placeholder', 'The size of the value exceeds the limit'], + ['data_viewer_presentation_value_content_download_error', 'Download failed'], ['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 f6cef7db1e..14fcd04a56 100644 --- a/webapp/packages/plugin-data-viewer/src/locales/ru.ts +++ b/webapp/packages/plugin-data-viewer/src/locales/ru.ts @@ -23,6 +23,8 @@ export default [ ['data_viewer_presentation_value_image_fit', 'Растянуть'], ['data_viewer_presentation_value_image_original_size', 'Оригинальный размер'], ['data_viewer_presentation_value_boolean_placeholder', 'Не удалось отобразить текущее значение как boolean'], + ['data_viewer_presentation_value_content_trimmed_placeholder', 'Размер значения превышает лимит'], + ['data_viewer_presentation_value_content_download_error', 'Не удалось загрузить файл'], ['data_viewer_script_preview', 'Скрипт'], ['data_viewer_script_preview_dialog_title', 'Предпросмотр изменений'], ['data_viewer_script_preview_error_title', 'Не удалось получить скрипт'], diff --git a/webapp/packages/plugin-data-viewer/src/locales/zh.ts b/webapp/packages/plugin-data-viewer/src/locales/zh.ts index 83244020a4..55692b3e87 100644 --- a/webapp/packages/plugin-data-viewer/src/locales/zh.ts +++ b/webapp/packages/plugin-data-viewer/src/locales/zh.ts @@ -27,6 +27,8 @@ export default [ ['data_viewer_presentation_value_image_fit', '适应窗口'], ['data_viewer_presentation_value_image_original_size', '原始尺寸'], ['data_viewer_presentation_value_boolean_placeholder', '无法将当前值显示为布尔值'], + ['data_viewer_presentation_value_content_trimmed_placeholder', 'The size of the value exceeds the limit'], + ['data_viewer_presentation_value_content_download_error', 'Download failed'], ['data_viewer_script_preview', '脚本'], ['data_viewer_script_preview_dialog_title', '预览更改'], ['data_viewer_script_preview_error_title', '无法获取脚本'], diff --git a/webapp/packages/plugin-data-viewer/src/manifest.ts b/webapp/packages/plugin-data-viewer/src/manifest.ts index c06b7471e8..641fb6fdeb 100644 --- a/webapp/packages/plugin-data-viewer/src/manifest.ts +++ b/webapp/packages/plugin-data-viewer/src/manifest.ts @@ -10,7 +10,6 @@ import type { PluginManifest } from '@cloudbeaver/core-di'; import { DataPresentationService } from './DataPresentationService'; import { DataViewerBootstrap } from './DataViewerBootstrap'; -import { DataViewerContentSaverService } from './DataViewerContentSaverService'; import { DataViewerDataChangeConfirmationService } from './DataViewerDataChangeConfirmationService'; import { DataViewerService } from './DataViewerService'; import { DataViewerSettingsService } from './DataViewerSettingsService'; @@ -52,6 +51,5 @@ export const manifest: PluginManifest = { DataValuePanelBootstrap, DataViewerSettingsService, DataViewerService, - DataViewerContentSaverService, ], }; diff --git a/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx b/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx index 43e5322b1b..083f915229 100644 --- a/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx +++ b/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx @@ -12,7 +12,7 @@ import wellknown from 'wellknown'; import { TextPlaceholder } from '@cloudbeaver/core-blocks'; import { useTranslate } from '@cloudbeaver/core-localization'; -import { IDatabaseResultSet, ResultSetSelectAction, IResultSetElementKey, IDatabaseDataModel, ResultSetViewAction, ResultSetDataKeysUtils } from '@cloudbeaver/plugin-data-viewer'; +import { IDatabaseResultSet, ResultSetSelectAction, IResultSetElementKey, IDatabaseDataModel, ResultSetViewAction, ResultSetDataElementUtils } from '@cloudbeaver/plugin-data-viewer'; import { IGeoJSONFeature, IAssociatedValue, LeafletMap } from './LeafletMap'; import { ResultSetGISAction } from './ResultSetGISAction'; @@ -69,7 +69,7 @@ export const GISValuePresentation = observer(function GISValuePresentatio const values: IAssociatedValue[] = []; for (const column of view.columnKeys) { - if (ResultSetDataKeysUtils.isEqual(column, cell.column)) { + if (ResultSetDataElementUtils.isKeyEqual(column, cell.column)) { continue; } diff --git a/webapp/packages/plugin-sql-editor/src/QueryDataSource.ts b/webapp/packages/plugin-sql-editor/src/QueryDataSource.ts index 6b172d8295..363c64fad3 100644 --- a/webapp/packages/plugin-sql-editor/src/QueryDataSource.ts +++ b/webapp/packages/plugin-sql-editor/src/QueryDataSource.ts @@ -11,7 +11,7 @@ import { observable, makeObservable } from 'mobx'; import type { IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections'; import type { ITask } from '@cloudbeaver/core-executor'; import { AsyncTaskInfoService, GraphQLService, ResultDataFormat, SqlExecuteInfo, SqlQueryResults, UpdateResultsDataBatchMutationVariables } from '@cloudbeaver/core-sdk'; -import { DatabaseDataSource, DocumentEditAction, IDatabaseDataOptions, IDatabaseResultSet, IRequestInfo, ResultSetEditAction } from '@cloudbeaver/plugin-data-viewer'; +import { DatabaseDataManager, DatabaseDataSource, DocumentEditAction, IDatabaseDataManager, IDatabaseDataOptions, IDatabaseResultSet, IRequestInfo, ResultSetEditAction } from '@cloudbeaver/plugin-data-viewer'; export interface IDataQueryOptions extends IDatabaseDataOptions { query: string; @@ -22,6 +22,7 @@ export interface IQueryRequestInfo extends IRequestInfo { } export class QueryDataSource extends DatabaseDataSource { + readonly dataManager: IDatabaseDataManager; currentTask: ITask | null; requestInfo: IQueryRequestInfo; @@ -35,6 +36,7 @@ export class QueryDataSource extends DatabaseDataSource Date: Thu, 14 Apr 2022 13:46:27 +0200 Subject: [PATCH 2/3] feat(plugin-data-viewer): CB-1500 naming --- .../DataGridContextMenuSaveContentService.ts | 23 ++++------ .../useGridSelectionContext.tsx | 12 ++--- .../src/DataGrid/DataGridTable.tsx | 4 +- .../src/DataGrid/useGridSelectedCellsCopy.ts | 6 +-- .../src/DataGrid/useTableData.ts | 8 ++-- .../Actions/ResultSet/ResultSetDataAction.ts | 2 +- ...mentUtils.ts => ResultSetDataKeysUtils.ts} | 14 +++--- .../Actions/ResultSet/ResultSetEditAction.ts | 26 +++++------ .../ResultSet/ResultSetFormatAction.ts | 2 +- .../ResultSet/ResultSetSelectAction.ts | 38 ++++++++-------- .../Actions/ResultSet/ResultSetViewAction.ts | 8 ++-- ...entValue.ts => isResultSetContentValue.ts} | 0 .../DatabaseDataModel/DatabaseDataManager.ts | 44 +++++++++---------- .../DatabaseDataModel/IDatabaseDataManager.ts | 10 ++--- .../ImageValue/ImageValuePresentation.tsx | 14 +++--- .../ImageValuePresentationBootstrap.ts | 2 +- .../TextValue/TextValuePresentation.tsx | 14 +++--- .../packages/plugin-data-viewer/src/index.ts | 4 +- .../src/GISValuePresentation.tsx | 4 +- 19 files changed, 114 insertions(+), 121 deletions(-) rename webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/{ResultSetDataElementUtils.ts => ResultSetDataKeysUtils.ts} (60%) rename webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/{ResultSetContentValue.ts => isResultSetContentValue.ts} (100%) diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuSaveContentService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuSaveContentService.ts index b48af47b49..87277e9a3f 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuSaveContentService.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuSaveContentService.ts @@ -8,7 +8,7 @@ import { injectable } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; -import { ResultSetDataElementUtils } from '@cloudbeaver/plugin-data-viewer'; +import { ResultSetDataKeysUtils } from '@cloudbeaver/plugin-data-viewer'; import { DataGridContextMenuService } from './DataGridContextMenuService'; @@ -38,27 +38,20 @@ export class DataGridContextMenuSaveContentService { }, onClick: async context => { try { - await context.data.model.source.dataManager.downloadFileFor( - context.data.key, - context.data.resultIndex - ); + await context.data.model.source.dataManager.downloadFileData(context.data.key, context.data.resultIndex); } catch (exception) { this.notificationService.logException(exception as any, 'data_grid_table_context_menu_save_value_error'); } }, - isHidden: context => !context.data.model.source.dataManager.canGetFileURLFor( + isHidden: context => !context.data.model.source.dataManager.canDownload( context.data.key, context.data.resultIndex ), - isDisabled: context => { - const { model, key } = context.data; - - if (!model.source.dataManager.activeElement) { - return false; - } - - return ResultSetDataElementUtils.isEqual(key, model.source.dataManager.activeElement); - }, + isDisabled: context => context.data.model.isLoading() || ( + !!context.data.model.source.dataManager.activeElement && ResultSetDataKeysUtils.isElementsKeyEqual( + context.data.key, context.data.model.source.dataManager.activeElement + ) + ), } ); } diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridSelection/useGridSelectionContext.tsx b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridSelection/useGridSelectionContext.tsx index 34beaa3ce8..0a07679c66 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridSelection/useGridSelectionContext.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridSelection/useGridSelectionContext.tsx @@ -10,7 +10,7 @@ import { action, observable } from 'mobx'; import { useState } from 'react'; import { useObjectRef } from '@cloudbeaver/core-blocks'; -import { IResultSetColumnKey, IResultSetElementKey, IResultSetRowKey, ResultSetDataElementUtils, ResultSetSelectAction } from '@cloudbeaver/plugin-data-viewer'; +import { IResultSetColumnKey, IResultSetElementKey, IResultSetRowKey, ResultSetDataKeysUtils, ResultSetSelectAction } from '@cloudbeaver/plugin-data-viewer'; import type { ITableData } from '../TableDataContext'; import type { IDraggingPosition } from '../useGridDragging'; @@ -90,15 +90,15 @@ export function useGridSelectionContext( const newElements = rowSelection .filter( element => !rowsSelection[i] - .some(column => ResultSetDataElementUtils.isKeyEqual(column.column, element)) + .some(column => ResultSetDataKeysUtils.isEqual(column.column, element)) ) .map(column => ({ row, column })); - temporarySelection.set(ResultSetDataElementUtils.serializeKey(row), + temporarySelection.set(ResultSetDataKeysUtils.serialize(row), [...rowsSelection[i], ...newElements] .filter(column => { if (selected) { - return !rowSelection.some(key => ResultSetDataElementUtils.isKeyEqual(key, column.column)); + return !rowSelection.some(key => ResultSetDataKeysUtils.isEqual(key, column.column)); } return true; })); @@ -174,13 +174,13 @@ export function useGridSelectionContext( const row = props.tableData.getRow(rowIdx); - const temporaryRowSelection = state.temporarySelection.get(ResultSetDataElementUtils.serializeKey(row)); + const temporaryRowSelection = state.temporarySelection.get(ResultSetDataKeysUtils.serialize(row)); if (temporaryRowSelection) { if (column === undefined) { return (temporaryRowSelection || []).length === props.tableData.columnKeys.length; } - return temporaryRowSelection.some(key => ResultSetDataElementUtils.isKeyEqual(key.column, column)); + return temporaryRowSelection.some(key => ResultSetDataKeysUtils.isEqual(key.column, column)); } return props.selectionAction.isElementSelected({ row, column }); 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 49042f07d7..ddcb563d27 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridTable.tsx @@ -21,7 +21,7 @@ import { useStyles } from '@cloudbeaver/core-theming'; import { ClipboardService } from '@cloudbeaver/core-ui'; import { DatabaseDataSelectActionsData, DatabaseEditChangeType, IDatabaseResultSet, IDataPresentationProps, - IResultSetEditActionData, IResultSetElementKey, IResultSetPartialKey, ResultSetDataElementUtils, ResultSetSelectAction + IResultSetEditActionData, IResultSetElementKey, IResultSetPartialKey, ResultSetDataKeysUtils, ResultSetSelectAction } from '@cloudbeaver/plugin-data-viewer'; import { CellPosition, EditingContext } from '../Editing/EditingContext'; @@ -412,7 +412,7 @@ export const DataGridTable = observer ResultSetDataElementUtils.isKeyEqual(columnKey, column)) + !selectedColumns.some(columnKey => ResultSetDataKeysUtils.isEqual(columnKey, column)) ) { continue; } - const cellKey = rowSelection.find(key => ResultSetDataElementUtils.isKeyEqual(key.column, column)); + const cellKey = rowSelection.find(key => ResultSetDataKeysUtils.isEqual(key.column, column)); if (cellKey) { rowCellsValues.push(getCellCopyValue(tableData, cellKey)); 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 c231108603..adaad50dad 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useTableData.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useTableData.ts @@ -13,7 +13,7 @@ import { useObservableRef } from '@cloudbeaver/core-blocks'; import { TextTools, uuid } from '@cloudbeaver/core-utils'; import { IDatabaseDataModel, IDatabaseResultSet, IResultSetColumnKey, IResultSetElementKey, IResultSetRowKey, - ResultSetConstraintAction, ResultSetDataAction, ResultSetDataElementUtils, + ResultSetConstraintAction, ResultSetDataAction, ResultSetDataKeysUtils, ResultSetEditAction, ResultSetFormatAction, ResultSetViewAction } from '@cloudbeaver/plugin-data-viewer'; @@ -115,7 +115,7 @@ export function useTableData( getColumnByDataIndex(key) { return this.columns.find(column => ( column.columnDataIndex !== null - && ResultSetDataElementUtils.isKeyEqual(column.columnDataIndex, key) + && ResultSetDataKeysUtils.isEqual(column.columnDataIndex, key) ))!; }, getColumnInfo(key) { @@ -131,11 +131,11 @@ export function useTableData( return this.columns .findIndex(column => ( column.columnDataIndex !== null - && ResultSetDataElementUtils.isKeyEqual(columnKey, column.columnDataIndex) + && ResultSetDataKeysUtils.isEqual(columnKey, column.columnDataIndex) )); }, getRowIndexFromKey(rowKey) { - return this.rows.findIndex(row => ResultSetDataElementUtils.isKeyEqual(rowKey, row)); + return this.rows.findIndex(row => ResultSetDataKeysUtils.isEqual(rowKey, row)); }, getColumnsInRange(startIndex, endIndex) { if (startIndex === endIndex) { diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataAction.ts index 88e0cc7c30..8ad4414c0f 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataAction.ts @@ -17,7 +17,7 @@ import { databaseDataAction } from '../DatabaseDataActionDecorator'; import type { IDatabaseDataResultAction } from '../IDatabaseDataResultAction'; import type { IResultSetContentValue } from './IResultSetContentValue'; import type { IResultSetColumnKey, IResultSetElementKey, IResultSetRowKey } from './IResultSetDataKey'; -import { isResultSetContentValue } from './ResultSetContentValue'; +import { isResultSetContentValue } from './isResultSetContentValue'; import type { IResultSetValue } from './ResultSetFormatAction'; @databaseDataAction() diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataElementUtils.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataKeysUtils.ts similarity index 60% rename from webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataElementUtils.ts rename to webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataKeysUtils.ts index 243c519f0e..d07b472bd9 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataElementUtils.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetDataKeysUtils.ts @@ -8,14 +8,14 @@ import type { IResultSetColumnKey, IResultSetElementKey, IResultSetRowKey } from './IResultSetDataKey'; -export const ResultSetDataElementUtils = { - serialize(element: IResultSetElementKey): string { - return this.serializeKey(element.column) + this.serializeKey(element.row); +export const ResultSetDataKeysUtils = { + serializeElementKey(elementKey: IResultSetElementKey): string { + return this.serialize(elementKey.column) + this.serialize(elementKey.row); }, - isEqual(a: IResultSetElementKey, b: IResultSetElementKey) { - return this.isKeyEqual(a.column, b.column) && this.isKeyEqual(a.row, b.row); + isElementsKeyEqual(a: IResultSetElementKey, b: IResultSetElementKey) { + return this.isEqual(a.column, b.column) && this.isEqual(a.row, b.row); }, - serializeKey(key: IResultSetColumnKey | IResultSetRowKey): string { + serialize(key: IResultSetColumnKey | IResultSetRowKey): string { let base = `${key.index}`; if ('key' in key) { @@ -24,7 +24,7 @@ export const ResultSetDataElementUtils = { return base; }, - isKeyEqual(a: T, b: T): boolean { + isEqual(a: T, b: T): boolean { if (a.index !== b.index) { return false; } 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 7607bb0c36..b5ab7adfd1 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 @@ -18,9 +18,9 @@ import { databaseDataAction } from '../DatabaseDataActionDecorator'; import { DatabaseEditAction } from '../DatabaseEditAction'; import { DatabaseEditChangeType, IDatabaseDataEditActionData, IDatabaseDataEditActionValue, IDatabaseDataEditApplyActionData, IDatabaseDataEditApplyActionUpdate } from '../IDatabaseDataEditAction'; import type { IResultSetColumnKey, IResultSetElementKey, IResultSetRowKey } from './IResultSetDataKey'; -import { isResultSetContentValue } from './ResultSetContentValue'; +import { isResultSetContentValue } from './isResultSetContentValue'; import { ResultSetDataAction } from './ResultSetDataAction'; -import { ResultSetDataElementUtils } from './ResultSetDataElementUtils'; +import { ResultSetDataKeysUtils } from './ResultSetDataKeysUtils'; import type { IResultSetValue } from './ResultSetFormatAction'; export interface IResultSetUpdate { @@ -100,7 +100,7 @@ export class ResultSetEditAction } isElementEdited(key: IResultSetElementKey): boolean { - const update = this.editorData.get(ResultSetDataElementUtils.serializeKey(key.row)); + const update = this.editorData.get(ResultSetDataKeysUtils.serialize(key.row)); if (!update) { return false; @@ -114,7 +114,7 @@ export class ResultSetEditAction } isRowEdited(key: IResultSetRowKey): boolean { - const update = this.editorData.get(ResultSetDataElementUtils.serializeKey(key)); + const update = this.editorData.get(ResultSetDataKeysUtils.serialize(key)); if (!update) { return false; @@ -124,7 +124,7 @@ export class ResultSetEditAction } getElementState(key: IResultSetElementKey): DatabaseEditChangeType | null { - const update = this.editorData.get(ResultSetDataElementUtils.serializeKey(key.row)); + const update = this.editorData.get(ResultSetDataKeysUtils.serialize(key.row)); if (!update) { return null; @@ -143,7 +143,7 @@ export class ResultSetEditAction get(key: IResultSetElementKey): IResultSetValue | undefined { return this.editorData - .get(ResultSetDataElementUtils.serializeKey(key.row)) + .get(ResultSetDataKeysUtils.serialize(key.row)) ?.update[key.column.index]; } @@ -217,7 +217,7 @@ export class ResultSetEditAction const rowKeys = new Set(); for (const key of keys) { - const serialized = ResultSetDataElementUtils.serializeKey(key.row); + const serialized = ResultSetDataKeysUtils.serialize(key.row); if (!rowKeys.has(serialized)) { rows.push(key.row); @@ -233,7 +233,7 @@ export class ResultSetEditAction let value = this.data.getRowValue(row); const editedValue = this.editorData - .get(ResultSetDataElementUtils.serializeKey(row)); + .get(ResultSetDataKeysUtils.serialize(row)); if (editedValue) { value = editedValue.update; @@ -248,7 +248,7 @@ export class ResultSetEditAction const deleted: Array> = []; for (const key of keys) { - const serializedKey = ResultSetDataElementUtils.serializeKey(key.row); + const serializedKey = ResultSetDataKeysUtils.serialize(key.row); const update = this.editorData.get(serializedKey); if (update?.type === DatabaseEditChangeType.add) { @@ -280,7 +280,7 @@ export class ResultSetEditAction } deleteRow(key: IResultSetRowKey, column?: IResultSetColumnKey, silent?: boolean): void { - const serializedKey = ResultSetDataElementUtils.serializeKey(key); + const serializedKey = ResultSetDataKeysUtils.serialize(key); const update = this.editorData.get(serializedKey); if (update && update.type !== DatabaseEditChangeType.delete) { @@ -401,7 +401,7 @@ export class ResultSetEditAction const revertedAdditions: Array> = []; for (const key of keys) { - const row = ResultSetDataElementUtils.serializeKey(key.row); + const row = ResultSetDataKeysUtils.serialize(key.row); const update = this.editorData.get(row); if (!update) { @@ -520,7 +520,7 @@ export class ResultSetEditAction if (update.source && !update.source.some( (value, i) => !this.compareCellValue(value, update.update[i]) )) { - this.editorData.delete(ResultSetDataElementUtils.serializeKey(update.row)); + this.editorData.delete(ResultSetDataKeysUtils.serialize(update.row)); } } @@ -529,7 +529,7 @@ export class ResultSetEditAction type: DatabaseEditChangeType, update?: IResultSetValue[] ): [IResultSetUpdate, boolean] { - const key = ResultSetDataElementUtils.serializeKey(row); + const key = ResultSetDataKeysUtils.serialize(row); let created = false; if (!this.editorData.has(key)) { diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction.ts index 8a9bb981e2..43da51673d 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction.ts @@ -15,7 +15,7 @@ import { databaseDataAction } from '../DatabaseDataActionDecorator'; import { DatabaseEditChangeType } from '../IDatabaseDataEditAction'; import type { IDatabaseDataFormatAction } from '../IDatabaseDataFormatAction'; import type { IResultSetElementKey, IResultSetPartialKey } from './IResultSetDataKey'; -import { isResultSetContentValue } from './ResultSetContentValue'; +import { isResultSetContentValue } from './isResultSetContentValue'; import { ResultSetEditAction } from './ResultSetEditAction'; import { ResultSetViewAction } from './ResultSetViewAction'; 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 f61c071d7f..42ab68ca90 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 @@ -19,7 +19,7 @@ import { DatabaseEditChangeType, IDatabaseDataEditActionData, IDatabaseDataEditA import type { DatabaseDataSelectActionsData } from '../IDatabaseDataSelectAction'; import type { IResultSetColumnKey, IResultSetElementKey, IResultSetPartialKey, IResultSetRowKey } from './IResultSetDataKey'; import { ResultSetDataAction } from './ResultSetDataAction'; -import { ResultSetDataElementUtils } from './ResultSetDataElementUtils'; +import { ResultSetDataKeysUtils } from './ResultSetDataKeysUtils'; import { ResultSetEditAction } from './ResultSetEditAction'; import type { IResultSetValue } from './ResultSetFormatAction'; import { ResultSetViewAction } from './ResultSetViewAction'; @@ -68,9 +68,9 @@ export class ResultSetSelectAction extends DatabaseSelectAction this.view.rowKeys, (current, previous) => { if (this.focusedElement) { const focus = this.focusedElement; - const currentIndex = current.findIndex(key => ResultSetDataElementUtils.isKeyEqual(key, focus.row)); + const currentIndex = current.findIndex(key => ResultSetDataKeysUtils.isEqual(key, focus.row)); - const focusIndex = previous.findIndex(key => ResultSetDataElementUtils.isKeyEqual(key, focus.row)); + const focusIndex = previous.findIndex(key => ResultSetDataKeysUtils.isEqual(key, focus.row)); if (currentIndex >= 0 && focusIndex === -1) { return; @@ -81,10 +81,10 @@ export class ResultSetSelectAction extends DatabaseSelectAction ResultSetDataElementUtils.isKeyEqual(key, focus.row))) { + if (!current.some(key => ResultSetDataKeysUtils.isEqual(key, focus.row))) { for (let index = focusIndex; index >= 0; index--) { const previousElement = previous[index]; - const row = current.find(key => ResultSetDataElementUtils.isKeyEqual(key, previousElement)); + const row = current.find(key => ResultSetDataKeysUtils.isEqual(key, previousElement)); if (row) { this.focus({ ...this.focusedElement, row }); @@ -93,7 +93,7 @@ export class ResultSetSelectAction extends DatabaseSelectAction ResultSetDataElementUtils.isKeyEqual(key, nextElement)); + const row = current.find(key => ResultSetDataKeysUtils.isEqual(key, nextElement)); if (row) { this.focus({ ...this.focusedElement, row }); @@ -119,8 +119,8 @@ export class ResultSetSelectAction extends DatabaseSelectAction ResultSetDataElementUtils.isKeyEqual(selected.column, key)); + return list.some(selected => ResultSetDataKeysUtils.isEqual(selected.column, key)); } private removeColumnSelection(list: IResultSetElementKey[], key: IResultSetColumnKey) { - const index = list.findIndex(selected => ResultSetDataElementUtils.isKeyEqual(selected.column, key)); + const index = list.findIndex(selected => ResultSetDataKeysUtils.isEqual(selected.column, key)); if (index >= 0) { list.splice(index, 1); diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetViewAction.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetViewAction.ts index dab977a1b4..c7a2450498 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetViewAction.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetViewAction.ts @@ -17,9 +17,9 @@ import { databaseDataAction } from '../DatabaseDataActionDecorator'; import type { IDatabaseDataResultAction } from '../IDatabaseDataResultAction'; import type { IResultSetContentValue } from './IResultSetContentValue'; import type { IResultSetColumnKey, IResultSetElementKey, IResultSetRowKey } from './IResultSetDataKey'; -import { isResultSetContentValue } from './ResultSetContentValue'; +import { isResultSetContentValue } from './isResultSetContentValue'; import { ResultSetDataAction } from './ResultSetDataAction'; -import { ResultSetDataElementUtils } from './ResultSetDataElementUtils'; +import { ResultSetDataKeysUtils } from './ResultSetDataKeysUtils'; import { ResultSetEditAction } from './ResultSetEditAction'; import type { IResultSetValue } from './ResultSetFormatAction'; @@ -85,11 +85,11 @@ export class ResultSetViewAction extends DatabaseDataAction ResultSetDataElementUtils.isKeyEqual(row, key)); + return this.rowKeys.findIndex(row => ResultSetDataKeysUtils.isEqual(row, key)); } columnIndex(key: IResultSetColumnKey): number { - return this.columnKeys.findIndex(column => ResultSetDataElementUtils.isKeyEqual(column, key)); + return this.columnKeys.findIndex(column => ResultSetDataKeysUtils.isEqual(column, key)); } nextKey(key: IResultSetElementKey): IResultSetElementKey | null { diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetContentValue.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/isResultSetContentValue.ts similarity index 100% rename from webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/ResultSetContentValue.ts rename to webapp/packages/plugin-data-viewer/src/DatabaseDataModel/Actions/ResultSet/isResultSetContentValue.ts diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataManager.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataManager.ts index 8ad02a7513..072cc85996 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataManager.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataManager.ts @@ -12,9 +12,9 @@ import type { GraphQLService } from '@cloudbeaver/core-sdk'; import { download, GlobalConstants } from '@cloudbeaver/core-utils'; import type { IResultSetElementKey } from './Actions/ResultSet/IResultSetDataKey'; -import { isResultSetContentValue } from './Actions/ResultSet/ResultSetContentValue'; +import { isResultSetContentValue } from './Actions/ResultSet/isResultSetContentValue'; import { ResultSetDataAction } from './Actions/ResultSet/ResultSetDataAction'; -import { ResultSetDataElementUtils } from './Actions/ResultSet/ResultSetDataElementUtils'; +import { ResultSetDataKeysUtils } from './Actions/ResultSet/ResultSetDataKeysUtils'; import type { IResultSetValue } from './Actions/ResultSet/ResultSetFormatAction'; import { ResultSetViewAction } from './Actions/ResultSet/ResultSetViewAction'; import type { IDatabaseDataManager } from './IDatabaseDataManager'; @@ -29,7 +29,7 @@ export class DatabaseDataManager> constructor( private readonly graphQLService: GraphQLService, - private readonly source: TSource + private readonly source: IDatabaseDataSource ) { this.cache = new Map(); this.activeElement = null; @@ -40,21 +40,21 @@ export class DatabaseDataManager> }); } - canGetFileURLFor(element: IResultSetElementKey, resultIndex: number) { + canDownload(element: IResultSetElementKey, resultIndex: number) { const view = this.source.getAction(resultIndex, ResultSetViewAction); const cellValue = view.getCellValue(element); return isResultSetContentValue(cellValue); } - async getFileURLFor(element: IResultSetElementKey, resultIndex: number) { + async getFileDataUrl(element: IResultSetElementKey, resultIndex: number) { const result = this.source.getResult(resultIndex); const data = this.source.getAction(resultIndex, ResultSetDataAction); const column = data.getColumn(element.column); const row = data.getRowValue(element.row); if (!result || !row || !column) { - throw new Error('Failed to get arguments data'); + throw new Error('Failed to get value metadata information'); } const url = await this.source.runTask( @@ -62,7 +62,7 @@ export class DatabaseDataManager> try { this.activeElement = element; const fileName = await this.loadFileName(result, column.position, row); - return this.generateFileURL(fileName); + return this.generateFileDataUrl(fileName); } finally { this.activeElement = null; } @@ -72,39 +72,39 @@ export class DatabaseDataManager> return url; } - async resolveFileURLFor(element: IResultSetElementKey, resultIndex: number) { - const hash = this.getHashFor(element, resultIndex); + async resolveFileDataUrl(element: IResultSetElementKey, resultIndex: number) { + const cache = this.retrieveFileDataUrlFromCache(element, resultIndex); - if (this.cache.has(hash)) { - return this.cache.get(hash)!; + if (cache) { + return cache; } - const url = await this.getFileURLFor(element, resultIndex); - this.cache.set(hash, url); + const url = await this.getFileDataUrl(element, resultIndex); + this.cache.set(this.getHash(element, resultIndex), url); return url; } - async downloadFileFor(element: IResultSetElementKey, resultIndex: number) { - const url = await this.getFileURLFor(element, resultIndex); - download(url); + retrieveFileDataUrlFromCache(element: IResultSetElementKey, resultIndex: number) { + const hash = this.getHash(element, resultIndex); + return this.cache.get(hash); } - retrieveFileURLFromCacheFor(element: IResultSetElementKey, resultIndex: number) { - const hash = this.getHashFor(element, resultIndex); - return this.cache.get(hash); + async downloadFileData(element: IResultSetElementKey, resultIndex: number) { + const url = await this.getFileDataUrl(element, resultIndex); + download(url); } clearCache() { this.cache.clear(); } - private generateFileURL(fileName: string) { + private generateFileDataUrl(fileName: string) { return `${GlobalConstants.serviceURI}/${RESULT_VALUE_PATH}/${fileName}`; } - private getHashFor(element: IResultSetElementKey, resultIndex: number) { - return `${ResultSetDataElementUtils.serialize(element)}_${resultIndex}`; + private getHash(element: IResultSetElementKey, resultIndex: number) { + return `${ResultSetDataKeysUtils.serializeElementKey(element)}_${resultIndex}`; } private async loadFileName(result: IDatabaseResultSet, columnIndex: number, row: IResultSetValue[]) { diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataManager.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataManager.ts index e48e8a9892..c9148ed8c9 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataManager.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataManager.ts @@ -10,10 +10,10 @@ import type { IResultSetElementKey } from './Actions/ResultSet/IResultSetDataKey export interface IDatabaseDataManager { activeElement: IResultSetElementKey | null; - canGetFileURLFor: (element: IResultSetElementKey, resultIndex: number) => boolean; - getFileURLFor: (element: IResultSetElementKey, resultIndex: number) => Promise; - resolveFileURLFor: (element: IResultSetElementKey, resultIndex: number) => Promise; - downloadFileFor: (element: IResultSetElementKey, resultIndex: number) => Promise; - retrieveFileURLFromCacheFor: (element: IResultSetElementKey, resultIndex: number) => string | undefined; + canDownload: (element: IResultSetElementKey, resultIndex: number) => boolean; + getFileDataUrl: (element: IResultSetElementKey, resultIndex: number) => Promise; + resolveFileDataUrl: (element: IResultSetElementKey, resultIndex: number) => Promise; + retrieveFileDataUrlFromCache: (element: IResultSetElementKey, resultIndex: number) => string | undefined; + downloadFileData: (element: IResultSetElementKey, resultIndex: number) => Promise; clearCache: () => void; } \ No newline at end of file diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentation.tsx b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentation.tsx index 66fb0c72bb..03249089c8 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentation.tsx +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentation.tsx @@ -18,8 +18,8 @@ import { useStyles } from '@cloudbeaver/core-theming'; import type { TabContainerPanelComponent } from '@cloudbeaver/core-ui'; import { getMIME, isImageFormat, isValidUrl } from '@cloudbeaver/core-utils'; -import { isResultSetContentValue, isResultSetContentValueTruncated } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetContentValue'; -import { ResultSetDataElementUtils } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetDataElementUtils'; +import { isResultSetContentValue, isResultSetContentValueTruncated } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue'; +import { ResultSetDataKeysUtils } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetDataKeysUtils'; import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction'; import { ResultSetViewAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetViewAction'; import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet'; @@ -136,10 +136,10 @@ export const ImageValuePresentation: TabContainerPanelComponent { try { - await model.source.dataManager.resolveFileURLFor(state.selectedCell, resultIndex); + await model.source.dataManager.resolveFileDataUrl(state.selectedCell, resultIndex); } catch (exception) { notificationService.logException(exception as any, 'data_viewer_presentation_value_content_download_error'); } @@ -183,7 +183,7 @@ export const ImageValuePresentation: TabContainerPanelComponent { + function handleChange(newValue: string) { if (firstSelectedCell && !readonly) { editor.set(firstSelectedCell, newValue); } - }; + } - const save = async () => { + async function save() { if (!firstSelectedCell) { return; } try { - await model.source.dataManager.downloadFileFor(firstSelectedCell, resultIndex); + await model.source.dataManager.downloadFileData(firstSelectedCell, resultIndex); } catch (exception) { notificationService.logException(exception as any, 'data_viewer_presentation_value_content_download_error'); } - }; + } const useCodeEditor = state.currentContentType !== 'text/plain'; const autoFormat = firstSelectedCell && !editor.isElementEdited(firstSelectedCell); - const canSave = firstSelectedCell && model.source.dataManager.canGetFileURLFor(firstSelectedCell, resultIndex); + const canSave = firstSelectedCell && model.source.dataManager.canDownload(firstSelectedCell, resultIndex); return styled(style)( diff --git a/webapp/packages/plugin-data-viewer/src/index.ts b/webapp/packages/plugin-data-viewer/src/index.ts index e6f9d51a97..12ecac1c8e 100644 --- a/webapp/packages/plugin-data-viewer/src/index.ts +++ b/webapp/packages/plugin-data-viewer/src/index.ts @@ -8,10 +8,10 @@ export * from './DatabaseDataModel/Actions/Document/IDatabaseDataDocument'; export * from './DatabaseDataModel/Actions/Document/IDocumentElementKey'; export * from './DatabaseDataModel/Actions/ResultSet/IResultSetDataKey'; export * from './DatabaseDataModel/Actions/ResultSet/IResultSetContentValue'; -export * from './DatabaseDataModel/Actions/ResultSet/ResultSetContentValue'; +export * from './DatabaseDataModel/Actions/ResultSet/isResultSetContentValue'; export * from './DatabaseDataModel/Actions/ResultSet/ResultSetConstraintAction'; export * from './DatabaseDataModel/Actions/ResultSet/ResultSetDataAction'; -export * from './DatabaseDataModel/Actions/ResultSet/ResultSetDataElementUtils'; +export * from './DatabaseDataModel/Actions/ResultSet/ResultSetDataKeysUtils'; export * from './DatabaseDataModel/Actions/ResultSet/ResultSetEditAction'; export * from './DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction'; export * from './DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction'; diff --git a/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx b/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx index 083f915229..43e5322b1b 100644 --- a/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx +++ b/webapp/packages/plugin-gis-viewer/src/GISValuePresentation.tsx @@ -12,7 +12,7 @@ import wellknown from 'wellknown'; import { TextPlaceholder } from '@cloudbeaver/core-blocks'; import { useTranslate } from '@cloudbeaver/core-localization'; -import { IDatabaseResultSet, ResultSetSelectAction, IResultSetElementKey, IDatabaseDataModel, ResultSetViewAction, ResultSetDataElementUtils } from '@cloudbeaver/plugin-data-viewer'; +import { IDatabaseResultSet, ResultSetSelectAction, IResultSetElementKey, IDatabaseDataModel, ResultSetViewAction, ResultSetDataKeysUtils } from '@cloudbeaver/plugin-data-viewer'; import { IGeoJSONFeature, IAssociatedValue, LeafletMap } from './LeafletMap'; import { ResultSetGISAction } from './ResultSetGISAction'; @@ -69,7 +69,7 @@ export const GISValuePresentation = observer(function GISValuePresentatio const values: IAssociatedValue[] = []; for (const column of view.columnKeys) { - if (ResultSetDataElementUtils.isKeyEqual(column, cell.column)) { + if (ResultSetDataKeysUtils.isEqual(column, cell.column)) { continue; } From 2ae76499198fc7fc7eae26ae899fb2c2f7f37138 Mon Sep 17 00:00:00 2001 From: Naumov Alexey Date: Thu, 14 Apr 2022 16:31:42 +0200 Subject: [PATCH 3/3] fix(plugin-data-viewer): CB-1500 remove extra generic --- .../packages/core-app/public/icons/icons.svg | 18 ++++++++------- webapp/packages/core-utils/src/download.ts | 6 ++++- .../DataGridContextMenuSaveContentService.ts | 6 ++--- .../DatabaseDataModel/DatabaseDataManager.ts | 6 ++--- .../DatabaseDataModel/IDatabaseDataManager.ts | 2 +- .../ImageValue/ImageValuePresentation.tsx | 23 ++++++++++++------- .../TextValue/TextValuePresentation.tsx | 2 +- 7 files changed, 38 insertions(+), 25 deletions(-) diff --git a/webapp/packages/core-app/public/icons/icons.svg b/webapp/packages/core-app/public/icons/icons.svg index 86782beddc..01304442a5 100644 --- a/webapp/packages/core-app/public/icons/icons.svg +++ b/webapp/packages/core-app/public/icons/icons.svg @@ -359,15 +359,17 @@ - - - - + + + + + + + + + - - - - + diff --git a/webapp/packages/core-utils/src/download.ts b/webapp/packages/core-utils/src/download.ts index 0cd0dee02c..e5f452b990 100644 --- a/webapp/packages/core-utils/src/download.ts +++ b/webapp/packages/core-utils/src/download.ts @@ -6,11 +6,15 @@ * you may not use this file except in compliance with the License. */ -export function download(content: Blob | string, fileName = ''): void { +export function download(content: Blob | string, fileName = '', blank?: boolean): void { const saveLink = document.createElement('a'); saveLink.tabIndex = -1; saveLink.download = fileName; + if (blank) { + saveLink.target = '_blank'; + saveLink.rel = 'noopener'; + } saveLink.style.display = 'none'; document.body.appendChild(saveLink); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuSaveContentService.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuSaveContentService.ts index 87277e9a3f..2933995c41 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuSaveContentService.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/DataGridContextMenu/DataGridContextMenuSaveContentService.ts @@ -39,11 +39,11 @@ export class DataGridContextMenuSaveContentService { onClick: async context => { try { await context.data.model.source.dataManager.downloadFileData(context.data.key, context.data.resultIndex); - } catch (exception) { - this.notificationService.logException(exception as any, 'data_grid_table_context_menu_save_value_error'); + } catch (exception: any) { + this.notificationService.logException(exception, 'data_grid_table_context_menu_save_value_error'); } }, - isHidden: context => !context.data.model.source.dataManager.canDownload( + isHidden: context => !context.data.model.source.dataManager.isContent( context.data.key, context.data.resultIndex ), diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataManager.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataManager.ts index 072cc85996..5213354983 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataManager.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/DatabaseDataManager.ts @@ -23,7 +23,7 @@ import type { IDatabaseResultSet } from './IDatabaseResultSet'; const RESULT_VALUE_PATH = 'sql-result-value'; -export class DatabaseDataManager> implements IDatabaseDataManager { +export class DatabaseDataManager implements IDatabaseDataManager { private readonly cache: Map; activeElement: IResultSetElementKey | null; @@ -34,13 +34,13 @@ export class DatabaseDataManager> this.cache = new Map(); this.activeElement = null; - makeObservable, 'cache'>(this, { + makeObservable(this, { cache: observable, activeElement: observable.ref, }); } - canDownload(element: IResultSetElementKey, resultIndex: number) { + isContent(element: IResultSetElementKey, resultIndex: number) { const view = this.source.getAction(resultIndex, ResultSetViewAction); const cellValue = view.getCellValue(element); diff --git a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataManager.ts b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataManager.ts index c9148ed8c9..6049931303 100644 --- a/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataManager.ts +++ b/webapp/packages/plugin-data-viewer/src/DatabaseDataModel/IDatabaseDataManager.ts @@ -10,7 +10,7 @@ import type { IResultSetElementKey } from './Actions/ResultSet/IResultSetDataKey export interface IDatabaseDataManager { activeElement: IResultSetElementKey | null; - canDownload: (element: IResultSetElementKey, resultIndex: number) => boolean; + isContent: (element: IResultSetElementKey, resultIndex: number) => boolean; getFileDataUrl: (element: IResultSetElementKey, resultIndex: number) => Promise; resolveFileDataUrl: (element: IResultSetElementKey, resultIndex: number) => Promise; retrieveFileDataUrlFromCache: (element: IResultSetElementKey, resultIndex: number) => string | undefined; diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentation.tsx b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentation.tsx index 03249089c8..3d2522a1d1 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentation.tsx +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/ImageValue/ImageValuePresentation.tsx @@ -16,7 +16,7 @@ import { NotificationService } from '@cloudbeaver/core-events'; import { useTranslate } from '@cloudbeaver/core-localization'; import { useStyles } from '@cloudbeaver/core-theming'; import type { TabContainerPanelComponent } from '@cloudbeaver/core-ui'; -import { getMIME, isImageFormat, isValidUrl } from '@cloudbeaver/core-utils'; +import { download, getMIME, isImageFormat, isValidUrl } from '@cloudbeaver/core-utils'; import { isResultSetContentValue, isResultSetContentValueTruncated } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue'; import { ResultSetDataKeysUtils } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetDataKeysUtils'; @@ -138,8 +138,11 @@ export const ImageValuePresentation: TabContainerPanelComponent { try { await model.source.dataManager.resolveFileDataUrl(state.selectedCell, resultIndex); - } catch (exception) { - notificationService.logException(exception as any, 'data_viewer_presentation_value_content_download_error'); + } catch (exception: any) { + notificationService.logException(exception, 'data_viewer_presentation_value_content_download_error'); } }; diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentation.tsx b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentation.tsx index 78369ecb7b..f94fd16879 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentation.tsx +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentation.tsx @@ -155,7 +155,7 @@ export const TextValuePresentation: TabContainerPanelComponent