Skip to content

Commit

Permalink
Merge pull request #2256 from dbeaver/CB-3555-view-byte-type-data-dec…
Browse files Browse the repository at this point in the history
…oded

Cb 3555 view byte type data decoded
  • Loading branch information
sergeyteleshev authored Dec 29, 2023
2 parents 1d5dc14 + b66fce1 commit d6cd15c
Show file tree
Hide file tree
Showing 16 changed files with 200 additions and 32 deletions.
19 changes: 19 additions & 0 deletions webapp/packages/core-utils/src/base64ToHex.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/

// be careful with this when you calculate a big size blobs
// it can block the main thread and cause freezes
export function base64ToHex(base64String: string): string {
const raw = atob(base64String);
let result = '';
for (let i = 0; i < raw.length; i++) {
const hex = raw.charCodeAt(i).toString(16);
result += hex.length === 2 ? hex : `0${hex}`;
}
return result.toUpperCase();
}
1 change: 1 addition & 0 deletions webapp/packages/core-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export * from './underscore';

export * from './base64ToBlob';
export * from './blobToBase64';
export * from './base64ToHex';
export * from './bytesToSize';
export * from './cacheValue';
export * from './clsx';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { IResultSetContentValue } from './IResultSetContentValue';

export interface IResultSetBinaryFileValue extends IResultSetContentValue {
binary: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import type { IResultSetBinaryFileValue } from './IResultSetBinaryFileValue';
import type { IResultSetContentValue } from './IResultSetContentValue';

export function isResultSetBinaryFileValue(value: IResultSetContentValue): value is IResultSetBinaryFileValue {
return value.contentType === 'application/octet-stream' && Boolean(value?.binary);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,12 @@
*/
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { ResultDataFormat } from '@cloudbeaver/core-sdk';
import { getMIME, isImageFormat, isValidUrl } from '@cloudbeaver/core-utils';

import { isResultSetBlobValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetBlobValue';
import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue';
import type { IResultSetValue } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction';
import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction';
import { ResultSetViewAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetViewAction';
import { DataValuePanelService } from '../../TableViewer/ValuePanel/DataValuePanelService';
import { ImageValuePresentation } from './ImageValuePresentation';
import { isImageValuePresentationAvailable } from './isImageValuePresentationAvailable';

@injectable()
export class ImageValuePresentationBootstrap extends Bootstrap {
Expand Down Expand Up @@ -46,7 +43,7 @@ export class ImageValuePresentationBootstrap extends Bootstrap {

const cellValue = view.getCellValue(firstSelectedCell);

return !this.isImage(cellValue);
return !isImageValuePresentationAvailable(cellValue);
}

return true;
Expand All @@ -55,19 +52,4 @@ export class ImageValuePresentationBootstrap extends Bootstrap {
}

load(): void {}

private isImage(value: IResultSetValue) {
if (isResultSetContentValue(value) && value?.binary) {
return getMIME(value.binary || '') !== null;
}
if (isResultSetContentValue(value) || isResultSetBlobValue(value)) {
return value?.contentType?.startsWith('image/') ?? false;
}

if (typeof value !== 'string') {
return false;
}

return isValidUrl(value) && isImageFormat(value);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 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 { getMIME, isImageFormat, isValidUrl } from '@cloudbeaver/core-utils';

import { isResultSetBlobValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetBlobValue';
import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue';
import type { IResultSetValue } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction';

export function isImageValuePresentationAvailable(value: IResultSetValue) {
if (isResultSetContentValue(value) && value?.binary) {
return getMIME(value.binary || '') !== null;
}
if (isResultSetContentValue(value) || isResultSetBlobValue(value)) {
return value?.contentType?.startsWith('image/') ?? false;
}

if (typeof value !== 'string') {
return false;
}

return isValidUrl(value) && isImageFormat(value);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,13 @@ import { ResultSetDataContentAction } from '../../DatabaseDataModel/Actions/Resu
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 { QuotaPlaceholder } from '../QuotaPlaceholder';
import { VALUE_PANEL_TOOLS_STYLES } from '../ValuePanelTools/VALUE_PANEL_TOOLS_STYLES';
import { getTypeExtension } from './getTypeExtension';
import { TextValuePresentationService } from './TextValuePresentationService';
import { useAutoFormat } from './useAutoFormat';
import { useTextValue } from './useTextValue';

const styles = css`
Tab {
Expand Down Expand Up @@ -72,7 +71,7 @@ const styles = css`
`;

export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelProps<any, IDatabaseResultSet>> = observer(
function TextValuePresentation({ model, resultIndex }) {
function TextValuePresentation({ model, resultIndex, dataFormat }) {
const translate = useTranslate();
const notificationService = useService(NotificationService);
const quotasService = useService(QuotasService);
Expand Down Expand Up @@ -106,7 +105,6 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr

const focusCell = selection.getFocusedElement();

let stringValue = '';
let contentType = 'text/plain';
let firstSelectedCell: IResultSetElementKey | undefined;
let readonly = true;
Expand All @@ -120,7 +118,6 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
firstSelectedCell = selection.elements[0] || focusCell;

const value = format.get(firstSelectedCell);
stringValue = format.getText(firstSelectedCell);
readonly = format.isReadOnly(firstSelectedCell) || format.isBinary(firstSelectedCell);

if (isResultSetContentValue(value)) {
Expand Down Expand Up @@ -151,8 +148,6 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
state.setDefaultContentType(contentType);
}

const formatter = useAutoFormat();

function handleChange(newValue: string) {
if (firstSelectedCell && !readonly) {
editor.set(firstSelectedCell, newValue);
Expand All @@ -171,19 +166,25 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
}
}

const autoFormat = !!firstSelectedCell && !editor.isElementEdited(firstSelectedCell);
const canSave = !!firstSelectedCell && content.isDownloadable(firstSelectedCell);
const typeExtension = useMemo(() => getTypeExtension(state.currentContentType) ?? [], [state.currentContentType]);
const extensions = useCodemirrorExtensions(undefined, typeExtension);

const value = autoFormat ? formatter.format(state.currentContentType, stringValue) : stringValue;
const value = useTextValue({
model,
resultIndex,
currentContentType: state.currentContentType,
});

return styled(style)(
<container>
<actions>
<TabsState
dataFormat={dataFormat}
resultIndex={resultIndex}
container={textValuePresentationService.tabs}
currentTabId={state.currentContentType}
model={model}
lazy
onChange={tab => state.setContentType(tab.tabId)}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { ResultDataFormat } from '@cloudbeaver/core-sdk';

import { DataValuePanelService } from '../../TableViewer/ValuePanel/DataValuePanelService';
import { isBlobPresentationAvailable } from './isTextValuePresentationAvailable';
import { TextValuePresentationService } from './TextValuePresentationService';

const TextValuePresentation = lazy(async () => {
Expand Down Expand Up @@ -47,18 +48,36 @@ export class TextValuePresentationBootstrap extends Bootstrap {
name: 'data_viewer_presentation_value_text_html_title',
order: Number.MAX_SAFE_INTEGER,
panel: () => React.Fragment,
isHidden: (_, context) => isBlobPresentationAvailable(context),
});
this.textValuePresentationService.add({
key: 'text/xml',
name: 'data_viewer_presentation_value_text_xml_title',
order: Number.MAX_SAFE_INTEGER,
panel: () => React.Fragment,
isHidden: (_, context) => isBlobPresentationAvailable(context),
});
this.textValuePresentationService.add({
key: 'application/json',
name: 'data_viewer_presentation_value_text_json_title',
order: Number.MAX_SAFE_INTEGER,
panel: () => React.Fragment,
isHidden: (_, context) => isBlobPresentationAvailable(context),
});

this.textValuePresentationService.add({
key: 'text/hex',
name: 'data_viewer_presentation_value_text_hex_title',
order: Number.MAX_SAFE_INTEGER,
panel: () => React.Fragment,
isHidden: (_, context) => !isBlobPresentationAvailable(context),
});
this.textValuePresentationService.add({
key: 'text/base64',
name: 'data_viewer_presentation_value_text_base64_title',
order: Number.MAX_SAFE_INTEGER,
panel: () => React.Fragment,
isHidden: (_, context) => !isBlobPresentationAvailable(context),
});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,21 @@
import { injectable } from '@cloudbeaver/core-di';
import { ITabInfo, ITabInfoOptions, TabsContainer } from '@cloudbeaver/core-ui';

import type { IDataValuePanelOptions, IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService';

@injectable()
export class TextValuePresentationService {
readonly tabs: TabsContainer;
readonly tabs: TabsContainer<IDataValuePanelProps<any>, IDataValuePanelOptions>;

constructor() {
this.tabs = new TabsContainer('Value presentation');
}

get(tabId: string): ITabInfo | undefined {
get(tabId: string): ITabInfo<IDataValuePanelProps<any>, IDataValuePanelOptions> | undefined {
return this.tabs.getTabInfo(tabId);
}

add(tabInfo: ITabInfoOptions): void {
add(tabInfo: ITabInfoOptions<IDataValuePanelProps<any>, IDataValuePanelOptions>): void {
this.tabs.add(tabInfo);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 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 { isResultSetBinaryFileValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetBinaryFileValue';
import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue';
import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction';
import { ResultSetViewAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetViewAction';
import type { IDatabaseDataResult } from '../../DatabaseDataModel/IDatabaseDataResult';
import type { IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService';

export function isBlobPresentationAvailable(context: IDataValuePanelProps<any, IDatabaseDataResult> | undefined): boolean {
if (!context?.model.source.hasResult(context.resultIndex)) {
return true;
}

const selection = context.model.source.getAction(context.resultIndex, ResultSetSelectAction);

const focusedElement = selection.getFocusedElement();

if (selection.elements.length > 0 || focusedElement) {
const view = context.model.source.getAction(context.resultIndex, ResultSetViewAction);

const firstSelectedCell = selection.elements[0] || focusedElement;

const cellValue = view.getCellValue(firstSelectedCell);

return isResultSetContentValue(cellValue) && isResultSetBinaryFileValue(cellValue);
}

return true;
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
* you may not use this file except in compliance with the License.
*/
import { useObjectRef } from '@cloudbeaver/core-blocks';
import { base64ToHex } from '@cloudbeaver/core-utils';

import type { IResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/IResultSetContentValue';

export function useAutoFormat() {
return useObjectRef(
Expand All @@ -25,6 +28,20 @@ export function useAutoFormat() {
return value;
}
},
formatBlob(type: string, value: IResultSetContentValue) {
if (!value.binary) {
return value.text;
}

switch (type) {
case 'text/base64':
return value.binary;
case 'text/hex':
return base64ToHex(value.binary);
default:
return value.text;
}
},
}),
false,
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 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 { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue';
import { ResultSetEditAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetEditAction';
import { ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction';
import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction';
import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel';
import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet';
import { useAutoFormat } from './useAutoFormat';

interface IUseTextValueArgs {
resultIndex: number;
model: IDatabaseDataModel<any, IDatabaseResultSet>;
currentContentType: string;
}

export function useTextValue({ model, resultIndex, currentContentType }: IUseTextValueArgs) {
const format = model.source.getAction(resultIndex, ResultSetFormatAction);
const editor = model.source.getAction(resultIndex, ResultSetEditAction);
const selection = model.source.getAction(resultIndex, ResultSetSelectAction);
const focusCell = selection.getFocusedElement();
const firstSelectedCell = selection.elements?.[0] ?? focusCell;
const autoFormat = !!firstSelectedCell && !editor.isElementEdited(firstSelectedCell);
const formatter = useAutoFormat();

if (!autoFormat) {
return;
}

const blob = format.get(firstSelectedCell);

if (isResultSetContentValue(blob)) {
const value = formatter.formatBlob(currentContentType, blob);

if (value) {
return value;
}
}

return formatter.format(currentContentType, format.getText(firstSelectedCell));
}
2 changes: 2 additions & 0 deletions webapp/packages/plugin-data-viewer/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +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/isResultSetBlobValue';
export * from './DatabaseDataModel/Actions/ResultSet/isResultSetComplexValue';
export * from './DatabaseDataModel/Actions/ResultSet/isResultSetContentValue';
Expand Down
2 changes: 2 additions & 0 deletions webapp/packages/plugin-data-viewer/src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export default [
['data_viewer_presentation_value_text_html_title', 'HTML'],
['data_viewer_presentation_value_text_xml_title', 'XML'],
['data_viewer_presentation_value_text_json_title', 'JSON'],
['data_viewer_presentation_value_text_hex_title', 'HEX'],
['data_viewer_presentation_value_text_base64_title', 'Base64'],
['data_viewer_presentation_value_image_title', 'Image'],
['data_viewer_presentation_value_image_fit', 'Fit Window'],
['data_viewer_presentation_value_image_original_size', 'Original Size'],
Expand Down
Loading

0 comments on commit d6cd15c

Please sign in to comment.