mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
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 commitd86b35e47a. * 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 commitd5df008e89. * 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:
co-authored by
s.teleshev
Evgenia Bezborodova
parent
1d497fe14f
commit
898e7d9b9d
@@ -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
-1
@@ -1,5 +1,5 @@
|
||||
import type { IResultSetContentValue } from './IResultSetContentValue';
|
||||
|
||||
export interface IResultSetBinaryFileValue extends IResultSetContentValue {
|
||||
export interface IResultSetBinaryValue extends IResultSetContentValue {
|
||||
binary: string;
|
||||
}
|
||||
+2
-2
@@ -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>;
|
||||
|
||||
+48
-4
@@ -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) {
|
||||
|
||||
+10
@@ -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);
|
||||
}
|
||||
|
||||
-5
@@ -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);
|
||||
}
|
||||
+13
@@ -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;
|
||||
}
|
||||
+1
-1
@@ -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 {
|
||||
+31
-38
@@ -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;
|
||||
}
|
||||
+40
-42
@@ -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>
|
||||
);
|
||||
});
|
||||
|
||||
+18
-21
@@ -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>,
|
||||
);
|
||||
|
||||
+2
-2
@@ -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;
|
||||
|
||||
+17
-28
@@ -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', '无法获取脚本'],
|
||||
|
||||
Reference in New Issue
Block a user