CB-4570 add correct limits for text and blob columns in value panel (#2351)

* CB-4570 add correct limits for text and blob columns in value panel

* CB-4570 pr fixes

* CB-4570 adds shorter versions of limit messages

* CB-4570 pr fixes

* CB-4570 pr fixes

* CB-4570 code cleanup

* CB-4570 show more button moved to quota container

* CB-4570 code cleanup

* CB-4570 code cleanup

* CB-4570 quota placeholder to module css

* CB-4570 refactor: limit info in content action

* CB-4570 cleanup

* CB-4570 refactor: result set binary file implementation

* CB-4570 code clean

* CB-4570 adds QuotaPlaceholder getLimitInfo logic

* CB-4570 adds quota placeholder logic to not render when size is not truncated

* CB-4570 fix: shouldShowPasteButton reverted

* CB-4570 fix: show quota placeholder correctly

* CB-4570 code clean

* Revert "CB-4570 fix: show quota placeholder correctly"

This reverts commit d86b35e47a.

* CB-4570 fix: truncate image and text depending on current value panel presentation tab id

* Revert "Revert "CB-4570 fix: show quota placeholder correctly""

This reverts commit d5df008e89.

* CB-4570 code clean

* CB-4570 chore: dataContent has no image mentions

---------

Co-authored-by: s.teleshev <s.teleshev@mu.se>
Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
This commit is contained in:
sergeyteleshev
2024-02-09 19:31:56 +03:00
committed by GitHub
co-authored by s.teleshev Evgenia Bezborodova
parent 1d497fe14f
commit 898e7d9b9d
22 changed files with 196 additions and 154 deletions
@@ -98,6 +98,7 @@ export default [
['ui_upload', 'Upload'],
['ui_import', 'Import'],
['ui_view', 'View'],
['ui_show_more', 'Show more'],
['ui_limit', 'Limit'],
['ui_file_size', 'File size'],
['ui_processing_synchronization', 'Synchronization...'],
@@ -82,6 +82,7 @@ export default [
['ui_upload', 'Upload'],
['ui_import', 'Import'],
['ui_view', 'View'],
['ui_show_more', 'Show more'],
['ui_limit', 'Limit'],
['ui_file_size', 'File size'],
['ui_processing_synchronization', 'Synchronization...'],
@@ -94,6 +94,7 @@ export default [
['ui_upload', 'Загрузить'],
['ui_import', 'Импортировать'],
['ui_view', 'Смотреть'],
['ui_show_more', 'Показать больше'],
['ui_limit', 'Лимит'],
['ui_file_size', 'Размер файла'],
['ui_processing_synchronization', 'Синхронизация...'],
@@ -95,6 +95,7 @@ export default [
['ui_upload', 'Upload'],
['ui_import', 'Import'],
['ui_view', 'View'],
['ui_show_more', 'Show more'],
['ui_limit', 'Limit'],
['ui_file_size', 'File size'],
['ui_processing_synchronization', 'Synchronization...'],
@@ -1,5 +1,5 @@
import type { IResultSetContentValue } from './IResultSetContentValue';
export interface IResultSetBinaryFileValue extends IResultSetContentValue {
export interface IResultSetBinaryValue extends IResultSetContentValue {
binary: string;
}
@@ -5,12 +5,12 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { IResultSetContentValue } from './IResultSetContentValue';
import type { IResultSetElementKey } from './IResultSetDataKey';
export interface IResultSetDataContentAction {
activeElement: IResultSetElementKey | null;
isContentTruncated: (content: IResultSetContentValue) => boolean;
isBlobTruncated: (element: IResultSetElementKey) => boolean;
isTextTruncated: (element: IResultSetElementKey) => boolean;
isDownloadable: (element: IResultSetElementKey) => boolean;
getFileDataUrl: (element: IResultSetElementKey) => Promise<string>;
resolveFileDataUrl: (element: IResultSetElementKey) => Promise<string>;
@@ -9,13 +9,12 @@ import { makeObservable, observable } from 'mobx';
import { QuotasService } from '@cloudbeaver/core-root';
import { GraphQLService, ResultDataFormat } from '@cloudbeaver/core-sdk';
import { download, GlobalConstants } from '@cloudbeaver/core-utils';
import { bytesToSize, download, GlobalConstants, isNotNullDefined } from '@cloudbeaver/core-utils';
import { DatabaseDataAction } from '../../DatabaseDataAction';
import type { IDatabaseDataSource } from '../../IDatabaseDataSource';
import type { IDatabaseResultSet } from '../../IDatabaseResultSet';
import { databaseDataAction } from '../DatabaseDataActionDecorator';
import type { IResultSetContentValue } from './IResultSetContentValue';
import type { IResultSetDataContentAction } from './IResultSetDataContentAction';
import type { IResultSetElementKey } from './IResultSetDataKey';
import { isResultSetContentValue } from './isResultSetContentValue';
@@ -57,8 +56,53 @@ export class ResultSetDataContentAction extends DatabaseDataAction<any, IDatabas
});
}
isContentTruncated(content: IResultSetContentValue) {
return (content.contentLength ?? 0) > this.quotasService.getQuota('sqlBinaryPreviewMaxLength');
getLimitInfo(elementKey: IResultSetElementKey) {
const isTextColumn = this.format.isText(elementKey);
const isBlob = this.format.isBinary(elementKey);
const result = {
limit: undefined as number | undefined,
limitWithSize: undefined as string | undefined,
};
if (isTextColumn) {
result.limit = this.quotasService.getQuota('sqlTextPreviewMaxLength');
}
if (isBlob) {
result.limit = this.quotasService.getQuota('sqlBinaryPreviewMaxLength');
}
if (result.limit) {
result.limitWithSize = bytesToSize(result.limit);
}
return result;
}
isBlobTruncated(elementKey: IResultSetElementKey) {
const limit = this.getLimitInfo(elementKey).limit;
const content = this.format.get(elementKey);
const cachedBlobUrl = this.retrieveFileDataUrlFromCache(elementKey);
const isLoadedFullBlob = Boolean(cachedBlobUrl) && this.format.isBinary(elementKey);
if (!isNotNullDefined(limit) || !isResultSetContentValue(content) || isLoadedFullBlob || !this.format.isBinary(elementKey)) {
return false;
}
return (content.contentLength ?? 0) > limit;
}
isTextTruncated(elementKey: IResultSetElementKey) {
const limit = this.getLimitInfo(elementKey).limit;
const content = this.format.get(elementKey);
const cachedFullText = this.retrieveFileFullTextFromCache(elementKey);
const isLoadedFullText = Boolean(cachedFullText) && this.format.isText(elementKey);
if (!isNotNullDefined(limit) || !isResultSetContentValue(content) || isLoadedFullText) {
return false;
}
return (content.contentLength ?? 0) > limit;
}
isDownloadable(element: IResultSetElementKey) {
@@ -102,6 +102,16 @@ export class ResultSetFormatAction
return false;
}
isText(key: IResultSetPartialKey): boolean {
if (!key?.column) {
return false;
}
const column = this.view.getColumn(key.column);
return column?.dataKind?.toLocaleLowerCase() === 'string';
}
getHeaders(): string[] {
return this.view.columns.map(column => column.name!).filter(name => name !== undefined);
}
@@ -1,5 +0,0 @@
import type { IResultSetBinaryFileValue } from './IResultSetBinaryFileValue';
export function isResultSetBinaryFileValue(value: any): value is IResultSetBinaryFileValue {
return value?.contentType === 'application/octet-stream' && Boolean(value?.binary);
}
@@ -0,0 +1,13 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2024 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { IResultSetBinaryValue } from './IResultSetBinaryValue';
import { isResultSetContentValue } from './isResultSetContentValue';
export function isResultSetBinaryValue(value: any): value is IResultSetBinaryValue {
return isResultSetContentValue(value) && 'binary' in value;
}
@@ -17,7 +17,7 @@ interface IResultActionsArgs {
model: IDatabaseDataModel<any, IDatabaseResultSet>;
}
export function useResultActions({ model, resultIndex }: IResultActionsArgs) {
export function useResultSetActions({ model, resultIndex }: IResultActionsArgs) {
return useObservableRef(
() => ({
get dataAction(): ResultSetDataAction {
@@ -12,12 +12,12 @@ import { ActionIconButton, Button, Container, Fill, s, useObservableRef, useS, u
import { selectFiles } from '@cloudbeaver/core-browser';
import { useService } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { QuotasService } from '@cloudbeaver/core-root';
import { type TabContainerPanelComponent, useTabLocalState } from '@cloudbeaver/core-ui';
import { bytesToSize, download, getMIME, isImageFormat, isValidUrl } from '@cloudbeaver/core-utils';
import { createResultSetBlobValue } from '../../DatabaseDataModel/Actions/ResultSet/createResultSetBlobValue';
import type { IResultSetElementKey } from '../../DatabaseDataModel/Actions/ResultSet/IResultSetDataKey';
import { isResultSetBinaryValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetBinaryValue';
import { isResultSetBlobValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetBlobValue';
import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue';
import { isResultSetFileValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetFileValue';
@@ -66,7 +66,6 @@ export const ImageValuePresentation: TabContainerPanelComponent<IDataValuePanelP
function ImageValuePresentation({ model, resultIndex }) {
const translate = useTranslate();
const notificationService = useService(NotificationService);
const quotasService = useService(QuotasService);
const style = useS(styles);
const state = useTabLocalState(() =>
@@ -123,7 +122,7 @@ export const ImageValuePresentation: TabContainerPanelComponent<IDataValuePanelP
return URL.createObjectURL(this.cellValue.blob);
}
if (isResultSetContentValue(this.cellValue) && this.cellValue.binary) {
if (isResultSetBinaryValue(this.cellValue)) {
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;
@@ -154,12 +153,14 @@ export const ImageValuePresentation: TabContainerPanelComponent<IDataValuePanelP
if (isResultSetFileValue(this.cellValue)) {
return false;
}
if (isResultSetContentValue(this.cellValue)) {
if (this.cellValue.binary) {
return this.contentAction.isContentTruncated(this.cellValue);
}
}
return false;
return this.selectedCell && this.contentAction.isBlobTruncated(this.selectedCell);
},
get shouldShowImage() {
const isFullImage = !this.truncated && this.savedSrc;
const isImage = this.src && !this.truncated && !this.savedSrc;
return isFullImage || isImage;
},
async save() {
try {
@@ -206,27 +207,28 @@ export const ImageValuePresentation: TabContainerPanelComponent<IDataValuePanelP
const loading = model.isLoading();
const value = data.cellValue;
if (data.truncated && !data.savedSrc && isResultSetContentValue(value)) {
const limit = bytesToSize(quotasService.getQuota('sqlBinaryPreviewMaxLength'));
const valueSize = bytesToSize(value.contentLength ?? 0);
const load = async () => {
if (!data.selectedCell) {
return;
}
const load = async () => {
if (!data.selectedCell) {
return;
}
try {
await data.contentAction.resolveFileDataUrl(data.selectedCell);
} catch (exception: any) {
notificationService.logException(exception, 'data_viewer_presentation_value_content_download_error');
}
};
try {
await data.contentAction.resolveFileDataUrl(data.selectedCell);
} catch (exception: any) {
notificationService.logException(exception, 'data_viewer_presentation_value_content_download_error');
}
};
const valueSize = bytesToSize(isResultSetContentValue(value) ? value.contentLength ?? 0 : 0);
const isDownloadable = data.selectedCell && data.contentAction.isDownloadable(data.selectedCell);
return (
<Container vertical>
<Container fill overflow center>
<QuotaPlaceholder limit={limit} size={valueSize}>
{data.selectedCell && data.contentAction.isDownloadable(data.selectedCell) && (
return (
<Container vertical>
<Container fill overflow center>
{data.shouldShowImage && <img src={data.src} className={s(style, { img: true, stretch: state.stretch })} />}
{data.truncated ? (
<QuotaPlaceholder model={data.model} resultIndex={data.resultIndex} elementKey={data.selectedCell}>
{isDownloadable && (
<Button
disabled={loading}
loading={
@@ -235,20 +237,11 @@ export const ImageValuePresentation: TabContainerPanelComponent<IDataValuePanelP
}
onClick={load}
>
{translate('ui_view')}
{`${translate('ui_view')} (${valueSize})`}
</Button>
)}
</QuotaPlaceholder>
</Container>
<Tools loading={loading} onSave={save} onUpload={upload} />
</Container>
);
}
return (
<Container vertical>
<Container fill overflow center>
<img src={data.src} className={s(style, { img: true, stretch: state.stretch })} />
) : null}
</Container>
<Tools loading={loading} stretch={state.stretch} onToggleStretch={state.toggleStretch} onSave={save} onUpload={upload} />
</Container>
@@ -0,0 +1,7 @@
.link {
margin-left: 4px;
}
.limitWord {
text-transform: lowercase;
}
@@ -6,64 +6,62 @@
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import styled, { css } from 'reshadow';
import { Link, usePermission, useTranslate } from '@cloudbeaver/core-blocks';
import { Container, Link, s, usePermission, useS, useTranslate } from '@cloudbeaver/core-blocks';
import { EAdminPermission } from '@cloudbeaver/core-root';
import type { IResultSetElementKey } from '../DatabaseDataModel/Actions/ResultSet/IResultSetDataKey';
import { useResultSetActions } from '../DatabaseDataModel/Actions/ResultSet/useResultSetActions';
import type { IDatabaseDataModel } from '../DatabaseDataModel/IDatabaseDataModel';
import type { IDatabaseResultSet } from '../DatabaseDataModel/IDatabaseResultSet';
import styles from './QuotaPlaceholder.m.css';
interface Props {
limit?: string;
size?: string;
className?: string;
elementKey: IResultSetElementKey | undefined;
model: IDatabaseDataModel<any, IDatabaseResultSet>;
resultIndex: number;
keepSize?: boolean;
}
const style = css`
container {
margin: auto;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
p {
composes: theme-typography--body2 from global;
text-align: center;
margin: 0;
}
reason {
display: flex;
white-space: pre;
}
limit-word {
text-transform: lowercase;
}
`;
export const QuotaPlaceholder: React.FC<React.PropsWithChildren<Props>> = observer(function QuotaPlaceholder({ limit, size, className, children }) {
export const QuotaPlaceholder: React.FC<React.PropsWithChildren<Props>> = observer(function QuotaPlaceholder({
className,
children,
keepSize = false,
elementKey,
model,
resultIndex,
}) {
const translate = useTranslate();
const admin = usePermission(EAdminPermission.admin);
const style = useS(styles);
const { contentAction } = useResultSetActions({ model, resultIndex });
const limitInfo = elementKey ? contentAction.getLimitInfo(elementKey) : null;
return styled(style)(
<container className={className}>
<p>
return (
<Container className={className} keepSize={keepSize} vertical center>
<Container center vertical>
{translate('data_viewer_presentation_value_content_was_truncated')}
<reason>
{translate('data_viewer_presentation_value_content_truncated_placeholder') + ' '}
<limit-word>
<Container noWrap center>
<Container>{translate('data_viewer_presentation_value_content_truncated_placeholder')}</Container>
<Container className={s(style, { limitWord: true })} zeroBasis>
{admin ? (
<Link href="https://dbeaver.com/docs/cloudbeaver/Server-configuration/#resource-quotas" target="_blank" indicator>
<Link
className={s(style, { link: true })}
title={limitInfo?.limitWithSize}
href="https://dbeaver.com/docs/cloudbeaver/Server-configuration/#resource-quotas"
target="_blank"
indicator
>
{translate('ui_limit')}
</Link>
) : (
translate('ui_limit')
)}
</limit-word>
</reason>
{limit && `${translate('ui_limit')}: ${limit}`}
<br />
{size && `${translate('data_viewer_presentation_value_content_value_size')}: ${size}`}
</p>
{children}
</container>,
</Container>
</Container>
</Container>
<Container>{children}</Container>
</Container>
);
});
@@ -13,14 +13,13 @@ import styled, { css } from 'reshadow';
import { ActionIconButton, Button, Container, Fill, Group, useStyles, useTranslate } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { QuotasService } from '@cloudbeaver/core-root';
import { BASE_TAB_STYLES, TabContainerPanelComponent, TabList, TabsState, UNDERLINE_TAB_STYLES, useTabLocalState } from '@cloudbeaver/core-ui';
import { bytesToSize, isNotNullDefined } from '@cloudbeaver/core-utils';
import { EditorLoader, useCodemirrorExtensions } from '@cloudbeaver/plugin-codemirror6';
import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue';
import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction';
import { useResultActions } from '../../DatabaseDataModel/Actions/ResultSet/useResultActions';
import { useResultSetActions } from '../../DatabaseDataModel/Actions/ResultSet/useResultSetActions';
import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet';
import type { IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService';
import { QuotaPlaceholder } from '../QuotaPlaceholder';
@@ -55,18 +54,17 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
function TextValuePresentation({ model, resultIndex, dataFormat }) {
const translate = useTranslate();
const notificationService = useService(NotificationService);
const quotasService = useService(QuotasService);
const textValuePresentationService = useService(TextValuePresentationService);
const style = useStyles(styles, UNDERLINE_TAB_STYLES, VALUE_PANEL_TOOLS_STYLES);
const selection = model.source.getAction(resultIndex, ResultSetSelectAction);
const activeElements = selection.getActiveElements();
const firstSelectedCell = activeElements?.[0];
const firstSelectedCell = activeElements.length ? activeElements[0] : undefined;
const activeTabs = textValuePresentationService.tabs.getDisplayed({
dataFormat: dataFormat,
model: model,
resultIndex: resultIndex,
});
const { contentAction, editAction, formatAction } = useResultActions({
const { contentAction, editAction, formatAction } = useResultSetActions({
model,
resultIndex,
});
@@ -115,19 +113,19 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
const autoLineWrapping = getDefaultLineWrapping(contentType);
const lineWrapping = state.lineWrapping ?? autoLineWrapping;
const { textValue, isTruncated, isTextColumn, pasteFullText } = useTextValue({
const textValueData = useTextValue({
model,
resultIndex,
currentContentType: contentType,
elementKey: firstSelectedCell,
});
const isSelectedCellReadonly = firstSelectedCell && (formatAction.isReadOnly(firstSelectedCell) || formatAction.isBinary(firstSelectedCell));
const isReadonlyByResultIndex = model.isReadonly(resultIndex) || model.isDisabled(resultIndex) || !firstSelectedCell;
const isReadonly = isSelectedCellReadonly || isReadonlyByResultIndex;
const valueSize =
isResultSetContentValue(contentValue) && isNotNullDefined(contentValue.contentLength) ? bytesToSize(contentValue.contentLength) : undefined;
const limit = bytesToSize(quotasService.getQuota('sqlBinaryPreviewMaxLength'));
const canSave = firstSelectedCell && contentAction.isDownloadable(firstSelectedCell);
const shouldShowPasteButton = isTextColumn && isTruncated;
const shouldShowPasteButton = textValueData.isTextColumn && firstSelectedCell && contentAction.isTextTruncated(firstSelectedCell);
const typeExtension = useMemo(() => getTypeExtension(contentType!) ?? [], [contentType]);
const extensions = useCodemirrorExtensions(undefined, typeExtension);
@@ -180,18 +178,24 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
<Group maximum box>
<EditorLoader
key={isReadonly ? '1' : '0'}
value={textValue}
value={textValueData.textValue}
lineWrapping={lineWrapping}
readonly={isReadonly}
extensions={extensions}
onChange={valueChangeHandler}
/>
</Group>
{isTruncated && (
<Container keepSize>
<QuotaPlaceholder limit={limit} size={valueSize} />
</Container>
)}
{firstSelectedCell && contentAction.isTextTruncated(firstSelectedCell) ? (
<QuotaPlaceholder model={model} resultIndex={resultIndex} elementKey={firstSelectedCell} keepSize>
{shouldShowPasteButton && (
<Container keepSize>
<Button disabled={model.isLoading()} onClick={textValueData.pasteFullText}>
{`${translate('ui_show_more')} (${valueSize})`}
</Button>
</Container>
)}
</QuotaPlaceholder>
) : null}
<Container keepSize center overflow>
{canSave && (
<ActionIconButton title={translate('ui_download')} name="/icons/export.svg" disabled={model.isLoading()} img onClick={saveHandler} />
@@ -204,13 +208,6 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
onClick={toggleLineWrappingHandler}
/>
<Fill />
{shouldShowPasteButton && (
<Container keepSize>
<Button disabled={model.isLoading()} onClick={pasteFullText}>
{translate('data_viewer_presentation_value_content_full_text_button')}
</Button>
</Container>
)}
</Container>
</Container>,
);
@@ -5,7 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { isResultSetBinaryFileValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetBinaryFileValue';
import { isResultSetBinaryValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetBinaryValue';
import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue';
import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction';
import { ResultSetViewAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetViewAction';
@@ -28,7 +28,7 @@ export function isBlobPresentationAvailable(context: IDataValuePanelProps<any, I
const cellValue = view.getCellValue(firstSelectedCell);
return isResultSetContentValue(cellValue) && isResultSetBinaryFileValue(cellValue);
return isResultSetContentValue(cellValue) && isResultSetBinaryValue(cellValue);
}
return false;
@@ -9,10 +9,9 @@ import { useService } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { isNotNullDefined } from '@cloudbeaver/core-utils';
import { isResultSetBinaryFileValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetBinaryFileValue';
import type { IResultSetElementKey } from '../../DatabaseDataModel/Actions/ResultSet/IResultSetDataKey';
import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue';
import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction';
import { useResultActions } from '../../DatabaseDataModel/Actions/ResultSet/useResultActions';
import { useResultSetActions } from '../../DatabaseDataModel/Actions/ResultSet/useResultSetActions';
import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel';
import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet';
import { useAutoFormat } from './useAutoFormat';
@@ -21,64 +20,54 @@ interface IUseTextValueArgs {
resultIndex: number;
model: IDatabaseDataModel<any, IDatabaseResultSet>;
currentContentType: string;
elementKey?: IResultSetElementKey;
}
interface IUseTextValue {
textValue: string;
isTruncated: boolean;
isTextColumn: boolean;
pasteFullText(): Promise<void>;
}
export function useTextValue({ model, resultIndex, currentContentType }: IUseTextValueArgs): IUseTextValue {
const { formatAction, editAction, contentAction, dataAction } = useResultActions({ model, resultIndex });
const selection = model.source.getAction(resultIndex, ResultSetSelectAction);
const activeElements = selection.getActiveElements();
const firstSelectedCell = activeElements?.[0];
export function useTextValue({ model, resultIndex, currentContentType, elementKey }: IUseTextValueArgs): IUseTextValue {
const { formatAction, editAction, contentAction } = useResultSetActions({ model, resultIndex });
const formatter = useAutoFormat();
const columnType = firstSelectedCell ? dataAction.getColumn(firstSelectedCell.column)?.dataKind : '';
const isTextColumn = columnType?.toLocaleLowerCase() === 'string';
const contentValue = firstSelectedCell ? formatAction.get(firstSelectedCell) : null;
const cachedFullText = firstSelectedCell ? contentAction.retrieveFileFullTextFromCache(firstSelectedCell) : '';
const blob = firstSelectedCell ? formatAction.get(firstSelectedCell) : null;
const isTextColumn = elementKey ? formatAction.isText(elementKey) : false;
const contentValue = elementKey ? formatAction.get(elementKey) : null;
const isBinary = elementKey ? formatAction.isBinary(elementKey) : false;
const cachedFullText = elementKey ? contentAction.retrieveFileFullTextFromCache(elementKey) : '';
const notificationService = useService(NotificationService);
const result: IUseTextValue = {
textValue: '',
isTruncated: false,
isTextColumn,
async pasteFullText() {
if (!firstSelectedCell) {
if (!elementKey) {
return;
}
try {
await contentAction.getFileFullText(firstSelectedCell);
await contentAction.getFileFullText(elementKey);
} catch (exception) {
notificationService.logException(exception as any, 'data_viewer_presentation_value_content_paste_error');
}
},
};
if (!isNotNullDefined(firstSelectedCell)) {
if (!isNotNullDefined(elementKey)) {
return result;
}
if (isResultSetContentValue(contentValue)) {
result.isTruncated = contentAction.isContentTruncated(contentValue);
}
if (isTextColumn && cachedFullText) {
result.textValue = cachedFullText;
result.isTruncated = false;
}
if (editAction.isElementEdited(firstSelectedCell)) {
result.textValue = formatAction.getText(firstSelectedCell);
if (editAction.isElementEdited(elementKey)) {
result.textValue = formatAction.getText(elementKey);
}
if (isResultSetBinaryFileValue(blob)) {
const value = formatter.formatBlob(currentContentType, blob);
if (isBinary && isResultSetContentValue(contentValue)) {
const value = formatter.formatBlob(currentContentType, contentValue);
if (value) {
result.textValue = value;
@@ -86,7 +75,7 @@ export function useTextValue({ model, resultIndex, currentContentType }: IUseTex
}
if (!result.textValue) {
result.textValue = formatter.format(currentContentType, formatAction.getText(firstSelectedCell));
result.textValue = formatter.format(currentContentType, formatAction.getText(elementKey));
}
return result;
@@ -17,8 +17,8 @@ export * from './DatabaseDataModel/Actions/ResultSet/IResultSetComplexValue';
export * from './DatabaseDataModel/Actions/ResultSet/IResultSetFileValue';
export * from './DatabaseDataModel/Actions/ResultSet/IResultSetContentValue';
export * from './DatabaseDataModel/Actions/ResultSet/IResultSetGeometryValue';
export * from './DatabaseDataModel/Actions/ResultSet/IResultSetBinaryFileValue';
export * from './DatabaseDataModel/Actions/ResultSet/isResultSetBinaryFileValue';
export * from './DatabaseDataModel/Actions/ResultSet/IResultSetBinaryValue';
export * from './DatabaseDataModel/Actions/ResultSet/isResultSetBinaryValue';
export * from './DatabaseDataModel/Actions/ResultSet/isResultSetBlobValue';
export * from './DatabaseDataModel/Actions/ResultSet/isResultSetComplexValue';
export * from './DatabaseDataModel/Actions/ResultSet/isResultSetContentValue';
@@ -39,10 +39,8 @@ export default [
['data_viewer_presentation_value_boolean_placeholder', "Can't show current value as boolean"],
['data_viewer_presentation_value_content_truncated_placeholder', 'The size of the value exceeds the'],
['data_viewer_presentation_value_content_was_truncated', 'The value was truncated'],
['data_viewer_presentation_value_content_value_size', 'Value size'],
['data_viewer_presentation_value_content_download_error', 'Download failed'],
['data_viewer_presentation_value_content_paste_error', 'Cannot load full text'],
['data_viewer_presentation_value_content_full_text_button', 'View full text'],
['data_viewer_script_preview', 'Script'],
['data_viewer_script_preview_dialog_title', 'Preview changes'],
['data_viewer_script_preview_error_title', "Can't get the script"],
@@ -35,10 +35,8 @@ export default [
['data_viewer_presentation_value_boolean_placeholder', 'Non posso rappresentare il valore corrente come booleano'],
['data_viewer_presentation_value_content_truncated_placeholder', 'The size of the value exceeds the'],
['data_viewer_presentation_value_content_was_truncated', 'The value was truncated'],
['data_viewer_presentation_value_content_value_size', 'Value size'],
['data_viewer_presentation_value_content_download_error', 'Download failed'],
['data_viewer_presentation_value_content_paste_error', 'Cannot load full text'],
['data_viewer_presentation_value_content_full_text_button', 'View full text'],
['data_viewer_refresh_result_set', 'Refresh result set'],
['data_viewer_total_count_tooltip', 'Get total count'],
['data_viewer_total_count_failed', 'Failed to get total count'],
@@ -33,10 +33,8 @@ export default [
['data_viewer_presentation_value_boolean_placeholder', 'Не удалось отобразить текущее значение как boolean'],
['data_viewer_presentation_value_content_truncated_placeholder', 'Размер значения превышает'],
['data_viewer_presentation_value_content_was_truncated', 'Значение было обрезано'],
['data_viewer_presentation_value_content_value_size', 'Размер значения'],
['data_viewer_presentation_value_content_download_error', 'Не удалось загрузить файл'],
['data_viewer_presentation_value_content_paste_error', 'Не удалось загрузить весь текст'],
['data_viewer_presentation_value_content_full_text_button', 'Посмотреть весь текст'],
['data_viewer_script_preview', 'Скрипт'],
['data_viewer_script_preview_dialog_title', 'Предпросмотр изменений'],
['data_viewer_script_preview_error_title', 'Не удалось получить скрипт'],
@@ -39,10 +39,8 @@ export default [
['data_viewer_presentation_value_boolean_placeholder', '无法将当前值显示为布尔值'],
['data_viewer_presentation_value_content_truncated_placeholder', 'The size of the value exceeds the'],
['data_viewer_presentation_value_content_was_truncated', 'The value was truncated'],
['data_viewer_presentation_value_content_value_size', 'Value size'],
['data_viewer_presentation_value_content_download_error', 'Download failed'],
['data_viewer_presentation_value_content_paste_error', 'Cannot load full text'],
['data_viewer_presentation_value_content_full_text_button', 'View full text'],
['data_viewer_script_preview', '脚本'],
['data_viewer_script_preview_dialog_title', '预览更改'],
['data_viewer_script_preview_error_title', '无法获取脚本'],