mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Cb 4760 add to value panel more clear abstractions and separation of the logic (#2688)
* CB-4760 refactors value panel boolean presentation * CB-4760 refactors value panel image presentation * CB-4760 refactors value panel text presentation (part 1) * CB-4760 refactors value panel text presentation (part 2) * CB-4760 moves ValuePanel presentation layer to ValuePanel folder * CB-4760 fixes type in file name * CB-4760 modifies isBlob truncated logic * CB-4760 booleanValuePresentation uses actions helper * CB-4760 reverts value panel helpers location * CB-4760 reverts getResultSetActions * CB-4760 add preprocessBooleanValue helper instead of hook * CB-4760 reverts truncated message isTruncated logic * CB-4760 cleanup * CB-4760 cleanup * CB-4760 test commit for save file history * CB-4760 test commit for save file history [2] * CB-4760 test commit for save file history [3] * CB-4760 moves back ValuePanelPresentation directory to src folder to save git history * CB-4760 pr fixes * CB-4760 adds placeholder for non selected elements in boolean value presentation (value panel) * CB-4760 fixes placeholder translation for non selected elements in boolean value presentation (value panel) * CB-4760 fix: app restart process * CB-4760 fix: app restart process --------- Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com> Co-authored-by: Aleksei Potsetsuev <wrouds@gmail.com>
This commit is contained in:
co-authored by
mr-anton-t
Aleksei Potsetsuev
parent
1c9b4f1ece
commit
a65dff32d6
@@ -17,6 +17,7 @@ import { IDiWrapper, inversifyWrapper } from './inversifyWrapper';
|
||||
import type { PluginManifest } from './PluginManifest';
|
||||
|
||||
export interface IStartData {
|
||||
restart: boolean;
|
||||
preload: boolean;
|
||||
}
|
||||
|
||||
@@ -29,11 +30,14 @@ export class App {
|
||||
|
||||
constructor(plugins: PluginManifest[] = []) {
|
||||
this.plugins = plugins;
|
||||
this.onStart = new Executor();
|
||||
this.onStart = new Executor<IStartData>(undefined, () => true);
|
||||
this.loadedServices = new Map();
|
||||
this.isAppServiceBound = false;
|
||||
|
||||
this.onStart.addHandler(async ({ preload }) => {
|
||||
this.onStart.addHandler(async ({ restart, preload }) => {
|
||||
if (preload && restart) {
|
||||
this.dispose();
|
||||
}
|
||||
await this.registerServices(preload);
|
||||
await this.initializeServices(preload);
|
||||
await this.loadServices(preload);
|
||||
@@ -44,14 +48,13 @@ export class App {
|
||||
});
|
||||
}
|
||||
|
||||
async start(): Promise<void> {
|
||||
await this.onStart.execute({ preload: true });
|
||||
await this.onStart.execute({ preload: false });
|
||||
async start(restart = false): Promise<void> {
|
||||
await this.onStart.execute({ preload: true, restart });
|
||||
await this.onStart.execute({ preload: false, restart });
|
||||
}
|
||||
|
||||
async restart(): Promise<void> {
|
||||
this.dispose();
|
||||
await this.start();
|
||||
await this.start(true);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
|
||||
-58
@@ -1,58 +0,0 @@
|
||||
import { computed, observable } from 'mobx';
|
||||
|
||||
import { useObservableRef } from '@cloudbeaver/core-blocks';
|
||||
|
||||
import type { IDatabaseDataModel } from '../../IDatabaseDataModel';
|
||||
import type { IDatabaseResultSet } from '../../IDatabaseResultSet';
|
||||
import { ResultSetConstraintAction } from './ResultSetConstraintAction';
|
||||
import { ResultSetDataAction } from './ResultSetDataAction';
|
||||
import { ResultSetDataContentAction } from './ResultSetDataContentAction';
|
||||
import { ResultSetEditAction } from './ResultSetEditAction';
|
||||
import { ResultSetFormatAction } from './ResultSetFormatAction';
|
||||
import { ResultSetSelectAction } from './ResultSetSelectAction';
|
||||
import { ResultSetViewAction } from './ResultSetViewAction';
|
||||
|
||||
interface IResultActionsArgs {
|
||||
resultIndex: number;
|
||||
model: IDatabaseDataModel<any, IDatabaseResultSet>;
|
||||
}
|
||||
|
||||
export function useResultSetActions({ model, resultIndex }: IResultActionsArgs) {
|
||||
return useObservableRef(
|
||||
() => ({
|
||||
get dataAction(): ResultSetDataAction {
|
||||
return this.model.source.getAction(this.resultIndex, ResultSetDataAction);
|
||||
},
|
||||
get selectAction(): ResultSetSelectAction {
|
||||
return this.model.source.getAction(this.resultIndex, ResultSetSelectAction);
|
||||
},
|
||||
get editAction(): ResultSetEditAction {
|
||||
return this.model.source.getAction(this.resultIndex, ResultSetEditAction);
|
||||
},
|
||||
get contentAction(): ResultSetDataContentAction {
|
||||
return this.model.source.getAction(this.resultIndex, ResultSetDataContentAction);
|
||||
},
|
||||
get formatAction(): ResultSetFormatAction {
|
||||
return this.model.source.getAction(this.resultIndex, ResultSetFormatAction);
|
||||
},
|
||||
get constraintAction(): ResultSetConstraintAction {
|
||||
return this.model.source.getAction(this.resultIndex, ResultSetConstraintAction);
|
||||
},
|
||||
get viewAction(): ResultSetViewAction {
|
||||
return this.model.source.getAction(this.resultIndex, ResultSetViewAction);
|
||||
},
|
||||
}),
|
||||
{
|
||||
dataAction: computed,
|
||||
selectAction: computed,
|
||||
editAction: computed,
|
||||
contentAction: computed,
|
||||
formatAction: computed,
|
||||
constraintAction: computed,
|
||||
viewAction: computed,
|
||||
model: observable.ref,
|
||||
resultIndex: observable.ref,
|
||||
},
|
||||
{ model, resultIndex },
|
||||
);
|
||||
}
|
||||
+18
-24
@@ -9,6 +9,7 @@ import { observer } from 'mobx-react-lite';
|
||||
|
||||
import { Radio, TextPlaceholder, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import type { TabContainerPanelComponent } from '@cloudbeaver/core-ui';
|
||||
import { isDefined } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { ResultSetEditAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetEditAction';
|
||||
import { ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction';
|
||||
@@ -17,41 +18,34 @@ import { ResultSetViewAction } from '../../DatabaseDataModel/Actions/ResultSet/R
|
||||
import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet';
|
||||
import type { IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService';
|
||||
import classes from './BooleanValuePresentation.module.css';
|
||||
import { isStringifiedBoolean } from './isBooleanValuePresentationAvailable';
|
||||
import { preprocessBooleanValue } from './preprocessBooleanValue';
|
||||
|
||||
export const BooleanValuePresentation: TabContainerPanelComponent<IDataValuePanelProps<any, IDatabaseResultSet>> = observer(
|
||||
function BooleanValuePresentation({ model, resultIndex }) {
|
||||
const translate = useTranslate();
|
||||
const selection = model.source.getAction(resultIndex, ResultSetSelectAction);
|
||||
const activeElements = selection.getActiveElements();
|
||||
|
||||
const selectAction = model.source.getAction(resultIndex, ResultSetSelectAction);
|
||||
const viewAction = model.source.getAction(resultIndex, ResultSetViewAction);
|
||||
const editAction = model.source.getAction(resultIndex, ResultSetEditAction);
|
||||
const formatAction = model.source.getAction(resultIndex, ResultSetFormatAction);
|
||||
|
||||
const activeElements = selectAction.getActiveElements();
|
||||
|
||||
if (activeElements.length === 0) {
|
||||
return null;
|
||||
return <TextPlaceholder>{translate('data_viewer_presentation_value_no_active_elements')}</TextPlaceholder>;
|
||||
}
|
||||
|
||||
let value: boolean | null | undefined;
|
||||
|
||||
const view = model.source.getAction(resultIndex, ResultSetViewAction);
|
||||
const editor = model.source.getAction(resultIndex, ResultSetEditAction);
|
||||
|
||||
const firstSelectedCell = activeElements[0];
|
||||
const cellValue = view.getCellValue(firstSelectedCell);
|
||||
const cellValue = viewAction.getCellValue(firstSelectedCell);
|
||||
const value = preprocessBooleanValue(cellValue);
|
||||
|
||||
if (typeof cellValue === 'string' && isStringifiedBoolean(cellValue)) {
|
||||
value = cellValue.toLowerCase() === 'true';
|
||||
} else if (typeof cellValue === 'boolean' || cellValue === null) {
|
||||
value = cellValue;
|
||||
}
|
||||
|
||||
if (value === undefined) {
|
||||
if (!isDefined(value)) {
|
||||
return <TextPlaceholder>{translate('data_viewer_presentation_value_boolean_placeholder')}</TextPlaceholder>;
|
||||
}
|
||||
|
||||
const format = model.source.getAction(resultIndex, ResultSetFormatAction);
|
||||
|
||||
const column = view.getColumn(firstSelectedCell.column);
|
||||
const column = viewAction.getColumn(firstSelectedCell.column);
|
||||
const nullable = column?.required === false;
|
||||
const readonly = model.isReadonly(resultIndex) || model.isDisabled(resultIndex) || format.isReadOnly(firstSelectedCell);
|
||||
const readonly = model.isReadonly(resultIndex) || model.isDisabled(resultIndex) || formatAction.isReadOnly(firstSelectedCell);
|
||||
|
||||
return (
|
||||
<div className={classes.container}>
|
||||
@@ -61,7 +55,7 @@ export const BooleanValuePresentation: TabContainerPanelComponent<IDataValuePane
|
||||
mod={['primary']}
|
||||
checked={value === true}
|
||||
disabled={readonly}
|
||||
onClick={() => editor.set(firstSelectedCell, true)}
|
||||
onClick={() => editAction.set(firstSelectedCell, true)}
|
||||
>
|
||||
TRUE
|
||||
</Radio>
|
||||
@@ -71,7 +65,7 @@ export const BooleanValuePresentation: TabContainerPanelComponent<IDataValuePane
|
||||
mod={['primary']}
|
||||
checked={value === false}
|
||||
disabled={readonly}
|
||||
onClick={() => editor.set(firstSelectedCell, false)}
|
||||
onClick={() => editAction.set(firstSelectedCell, false)}
|
||||
>
|
||||
FALSE
|
||||
</Radio>
|
||||
@@ -82,7 +76,7 @@ export const BooleanValuePresentation: TabContainerPanelComponent<IDataValuePane
|
||||
mod={['primary']}
|
||||
checked={value === null}
|
||||
disabled={readonly}
|
||||
onClick={() => editor.set(firstSelectedCell, null)}
|
||||
onClick={() => editAction.set(firstSelectedCell, null)}
|
||||
>
|
||||
NULL
|
||||
</Radio>
|
||||
|
||||
+1
-7
@@ -38,17 +38,11 @@ export class BooleanValuePresentationBootstrap extends Bootstrap {
|
||||
|
||||
if (activeElements.length > 0) {
|
||||
const view = context.model.source.getAction(context.resultIndex, ResultSetViewAction);
|
||||
|
||||
const firstSelectedCell = activeElements[0];
|
||||
const cellValue = view.getCellValue(firstSelectedCell);
|
||||
|
||||
if (cellValue === undefined) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const column = view.getColumn(firstSelectedCell.column);
|
||||
|
||||
return column === undefined || !isBooleanValuePresentationAvailable(cellValue, column);
|
||||
return cellValue === undefined || column === undefined || !isBooleanValuePresentationAvailable(cellValue, column);
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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 { IResultSetValue } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction';
|
||||
import { isStringifiedBoolean } from './isBooleanValuePresentationAvailable';
|
||||
|
||||
export function preprocessBooleanValue(cellValue: IResultSetValue): boolean | null | undefined {
|
||||
if (typeof cellValue === 'string' && isStringifiedBoolean(cellValue)) {
|
||||
return cellValue.toLowerCase() === 'true';
|
||||
}
|
||||
|
||||
if (typeof cellValue === 'boolean' || cellValue === null) {
|
||||
return cellValue;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
+5
-162
@@ -5,39 +5,26 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { action, computed, observable } from 'mobx';
|
||||
import { action, observable } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { ActionIconButton, Button, Container, Fill, Loader, s, useObservableRef, useS, useSuspense, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { selectFiles } from '@cloudbeaver/core-browser';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { ActionIconButton, Button, Container, Fill, Loader, s, useS, useSuspense, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { type TabContainerPanelComponent, useTabLocalState } from '@cloudbeaver/core-ui';
|
||||
import { blobToBase64, bytesToSize, download, getMIME, isImageFormat, isValidUrl, throttle } from '@cloudbeaver/core-utils';
|
||||
import { blobToBase64, bytesToSize, throttle } 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';
|
||||
import { ResultSetDataContentAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction';
|
||||
import { ResultSetEditAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetEditAction';
|
||||
import { ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction';
|
||||
import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction';
|
||||
import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet';
|
||||
import type { IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService';
|
||||
import { QuotaPlaceholder } from '../QuotaPlaceholder';
|
||||
import styles from './ImageValuePresentation.module.css';
|
||||
import { useValuePanelImageValue } from './useValuePanelImageValue';
|
||||
|
||||
export const ImageValuePresentation: TabContainerPanelComponent<IDataValuePanelProps<any, IDatabaseResultSet>> = observer(
|
||||
function ImageValuePresentation({ model, resultIndex }) {
|
||||
const translate = useTranslate();
|
||||
const suspense = useSuspense();
|
||||
const notificationService = useService(NotificationService);
|
||||
const style = useS(styles);
|
||||
|
||||
const state = useTabLocalState(() =>
|
||||
observable(
|
||||
{
|
||||
@@ -52,156 +39,12 @@ export const ImageValuePresentation: TabContainerPanelComponent<IDataValuePanelP
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
const data = useObservableRef(
|
||||
() => ({
|
||||
get editAction(): ResultSetEditAction {
|
||||
return this.model.source.getAction(this.resultIndex, ResultSetEditAction);
|
||||
},
|
||||
get contentAction(): ResultSetDataContentAction {
|
||||
return this.model.source.getAction(this.resultIndex, ResultSetDataContentAction);
|
||||
},
|
||||
get selectAction(): ResultSetSelectAction {
|
||||
return this.model.source.getAction(this.resultIndex, ResultSetSelectAction);
|
||||
},
|
||||
get formatAction(): ResultSetFormatAction {
|
||||
return this.model.source.getAction(this.resultIndex, ResultSetFormatAction);
|
||||
},
|
||||
get selectedCell(): IResultSetElementKey | undefined {
|
||||
const activeElements = this.selectAction.getActiveElements();
|
||||
|
||||
if (activeElements.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return activeElements[0];
|
||||
},
|
||||
get cellValue() {
|
||||
if (this.selectedCell === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.formatAction.get(this.selectedCell);
|
||||
},
|
||||
get src(): string | Blob | null {
|
||||
if (isResultSetBlobValue(this.cellValue)) {
|
||||
// uploaded file preview
|
||||
return this.cellValue.blob;
|
||||
}
|
||||
|
||||
if (this.staticSrc) {
|
||||
return this.staticSrc;
|
||||
}
|
||||
|
||||
if (this.cacheBlob) {
|
||||
// uploaded file preview
|
||||
return this.cacheBlob;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
get staticSrc(): string | null {
|
||||
if (this.truncated) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isResultSetBinaryValue(this.cellValue)) {
|
||||
return `data:${getMIME(this.cellValue.binary)};base64,${this.cellValue.binary}`;
|
||||
}
|
||||
|
||||
if (typeof this.cellValue === 'string' && isValidUrl(this.cellValue) && isImageFormat(this.cellValue)) {
|
||||
return this.cellValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
get cacheBlob() {
|
||||
if (!this.selectedCell) {
|
||||
return null;
|
||||
}
|
||||
return this.contentAction.retrieveBlobFromCache(this.selectedCell);
|
||||
},
|
||||
get canSave() {
|
||||
if (this.truncated && this.selectedCell) {
|
||||
return this.contentAction.isDownloadable(this.selectedCell);
|
||||
}
|
||||
|
||||
return this.staticSrc && !this.truncated;
|
||||
},
|
||||
get canUpload() {
|
||||
if (!this.selectedCell) {
|
||||
return false;
|
||||
}
|
||||
return this.formatAction.isBinary(this.selectedCell);
|
||||
},
|
||||
get truncated() {
|
||||
if (isResultSetFileValue(this.cellValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.selectedCell && this.contentAction.isBlobTruncated(this.selectedCell);
|
||||
},
|
||||
async download() {
|
||||
try {
|
||||
if (this.src) {
|
||||
download(this.src, '', true);
|
||||
} else if (this.selectedCell) {
|
||||
await this.contentAction.downloadFileData(this.selectedCell);
|
||||
} else {
|
||||
throw new Error("Can't save image");
|
||||
}
|
||||
} catch (exception: any) {
|
||||
this.notificationService.logException(exception, 'data_viewer_presentation_value_content_download_error');
|
||||
}
|
||||
},
|
||||
async upload() {
|
||||
selectFiles(files => {
|
||||
const file = files?.[0] ?? undefined;
|
||||
if (file && this.selectedCell) {
|
||||
this.editAction.set(this.selectedCell, createResultSetBlobValue(file));
|
||||
}
|
||||
});
|
||||
},
|
||||
async loadFullImage() {
|
||||
if (!this.selectedCell) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.contentAction.resolveFileDataUrl(this.selectedCell);
|
||||
} catch (exception: any) {
|
||||
this.notificationService.logException(exception, 'data_viewer_presentation_value_content_download_error');
|
||||
}
|
||||
},
|
||||
}),
|
||||
{
|
||||
editAction: computed,
|
||||
contentAction: computed,
|
||||
selectAction: computed,
|
||||
formatAction: computed,
|
||||
selectedCell: computed,
|
||||
cellValue: computed,
|
||||
canUpload: computed,
|
||||
src: computed,
|
||||
cacheBlob: computed,
|
||||
canSave: computed,
|
||||
truncated: computed,
|
||||
model: observable.ref,
|
||||
resultIndex: observable.ref,
|
||||
download: action.bound,
|
||||
upload: action.bound,
|
||||
loadFullImage: action.bound,
|
||||
},
|
||||
{ model, resultIndex, notificationService },
|
||||
);
|
||||
|
||||
const data = useValuePanelImageValue({ model, resultIndex });
|
||||
const loading = model.isLoading();
|
||||
|
||||
const valueSize = bytesToSize(isResultSetContentValue(data.cellValue) ? data.cellValue.contentLength ?? 0 : 0);
|
||||
const isTruncatedMessageDisplay = !!data.truncated && !data.src;
|
||||
const isDownloadable = isTruncatedMessageDisplay && !!data.selectedCell && data.contentAction.isDownloadable(data.selectedCell);
|
||||
const isCacheDownloading = isDownloadable && data.contentAction.isLoading(data.selectedCell);
|
||||
|
||||
const debouncedDownload = useMemo(() => throttle(() => data.download(), 1000, false), []);
|
||||
const srcGetter = suspense.observedValue(
|
||||
'src',
|
||||
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* 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 { action, computed, observable } from 'mobx';
|
||||
|
||||
import { useObservableRef } from '@cloudbeaver/core-blocks';
|
||||
import { selectFiles } from '@cloudbeaver/core-browser';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { 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 { isResultSetFileValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetFileValue';
|
||||
import { ResultSetDataContentAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction';
|
||||
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';
|
||||
|
||||
interface Props {
|
||||
model: IDatabaseDataModel<any, IDatabaseResultSet>;
|
||||
resultIndex: number;
|
||||
}
|
||||
|
||||
export function useValuePanelImageValue({ model, resultIndex }: Props) {
|
||||
const notificationService = useService(NotificationService);
|
||||
const selectAction = model.source.getAction(resultIndex, ResultSetSelectAction);
|
||||
const formatAction = model.source.getAction(resultIndex, ResultSetFormatAction);
|
||||
const contentAction = model.source.getAction(resultIndex, ResultSetDataContentAction);
|
||||
const editAction = model.source.getAction(resultIndex, ResultSetEditAction);
|
||||
|
||||
return useObservableRef(
|
||||
() => ({
|
||||
get selectedCell(): IResultSetElementKey | undefined {
|
||||
return this.selectAction.getActiveElements()?.[0];
|
||||
},
|
||||
get cellValue() {
|
||||
if (this.selectedCell === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.formatAction.get(this.selectedCell);
|
||||
},
|
||||
get src(): string | Blob | null {
|
||||
if (isResultSetBlobValue(this.cellValue)) {
|
||||
// uploaded file preview
|
||||
return this.cellValue.blob;
|
||||
}
|
||||
|
||||
if (this.staticSrc) {
|
||||
return this.staticSrc;
|
||||
}
|
||||
|
||||
if (this.cacheBlob) {
|
||||
// uploaded file preview
|
||||
return this.cacheBlob;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
get staticSrc(): string | null {
|
||||
if (this.truncated) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isResultSetBinaryValue(this.cellValue)) {
|
||||
return `data:${getMIME(this.cellValue.binary)};base64,${this.cellValue.binary}`;
|
||||
}
|
||||
|
||||
if (typeof this.cellValue === 'string' && isValidUrl(this.cellValue) && isImageFormat(this.cellValue)) {
|
||||
return this.cellValue;
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
get cacheBlob() {
|
||||
if (!this.selectedCell) {
|
||||
return null;
|
||||
}
|
||||
return this.contentAction.retrieveBlobFromCache(this.selectedCell);
|
||||
},
|
||||
get canSave() {
|
||||
if (this.truncated && this.selectedCell) {
|
||||
return this.contentAction.isDownloadable(this.selectedCell);
|
||||
}
|
||||
|
||||
return this.staticSrc && !this.truncated;
|
||||
},
|
||||
get canUpload() {
|
||||
if (!this.selectedCell) {
|
||||
return false;
|
||||
}
|
||||
return this.formatAction.isBinary(this.selectedCell);
|
||||
},
|
||||
get truncated() {
|
||||
if (isResultSetFileValue(this.cellValue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.selectedCell && this.contentAction.isBlobTruncated(this.selectedCell);
|
||||
},
|
||||
async download() {
|
||||
try {
|
||||
if (this.src) {
|
||||
download(this.src, '', true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.selectedCell) {
|
||||
await this.contentAction.downloadFileData(this.selectedCell);
|
||||
return;
|
||||
}
|
||||
|
||||
throw new Error("Can't save image");
|
||||
} catch (exception: any) {
|
||||
this.notificationService.logException(exception, 'data_viewer_presentation_value_content_download_error');
|
||||
}
|
||||
},
|
||||
async upload() {
|
||||
selectFiles(files => {
|
||||
const file = files?.[0];
|
||||
if (file && this.selectedCell) {
|
||||
this.editAction.set(this.selectedCell, createResultSetBlobValue(file));
|
||||
}
|
||||
});
|
||||
},
|
||||
async loadFullImage() {
|
||||
if (!this.selectedCell) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.contentAction.resolveFileDataUrl(this.selectedCell);
|
||||
} catch (exception: any) {
|
||||
this.notificationService.logException(exception, 'data_viewer_presentation_value_content_download_error');
|
||||
}
|
||||
},
|
||||
}),
|
||||
{
|
||||
selectedCell: computed,
|
||||
cellValue: computed,
|
||||
canUpload: computed,
|
||||
src: computed,
|
||||
cacheBlob: computed,
|
||||
canSave: computed,
|
||||
truncated: computed,
|
||||
model: observable.ref,
|
||||
resultIndex: observable.ref,
|
||||
download: action.bound,
|
||||
upload: action.bound,
|
||||
loadFullImage: action.bound,
|
||||
},
|
||||
{ model, resultIndex, notificationService, selectAction, formatAction, contentAction, editAction },
|
||||
);
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import { Container, Link, s, usePermission, useS, useTranslate } from '@cloudbea
|
||||
import { EAdminPermission } from '@cloudbeaver/core-root';
|
||||
|
||||
import type { IResultSetElementKey } from '../DatabaseDataModel/Actions/ResultSet/IResultSetDataKey';
|
||||
import { useResultSetActions } from '../DatabaseDataModel/Actions/ResultSet/useResultSetActions';
|
||||
import { ResultSetDataContentAction } from '../DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction';
|
||||
import type { IDatabaseDataModel } from '../DatabaseDataModel/IDatabaseDataModel';
|
||||
import type { IDatabaseResultSet } from '../DatabaseDataModel/IDatabaseResultSet';
|
||||
import styles from './QuotaPlaceholder.module.css';
|
||||
@@ -35,7 +35,7 @@ export const QuotaPlaceholder: React.FC<React.PropsWithChildren<Props>> = observ
|
||||
const translate = useTranslate();
|
||||
const admin = usePermission(EAdminPermission.admin);
|
||||
const style = useS(styles);
|
||||
const { contentAction } = useResultSetActions({ model, resultIndex });
|
||||
const contentAction = model.source.getAction(resultIndex, ResultSetDataContentAction);
|
||||
const limitInfo = elementKey ? contentAction.getLimitInfo(elementKey) : null;
|
||||
|
||||
return (
|
||||
|
||||
+1
@@ -25,6 +25,7 @@ interface Props {
|
||||
export const TextValueEditor = observer<Props>(function TextValueEditor({ contentType, valueGetter, readonly, lineWrapping, onChange }) {
|
||||
const value = valueGetter();
|
||||
const typeExtension = useMemo(() => getTypeExtension(contentType!) ?? [], [contentType]);
|
||||
|
||||
const extensions = useCodemirrorExtensions(undefined, typeExtension);
|
||||
const dataViewerService = useService(DataViewerService);
|
||||
|
||||
|
||||
+29
-25
@@ -13,17 +13,21 @@ import { useService } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { TabContainerPanelComponent, TabList, TabsState, TabStyles, useTabLocalState } from '@cloudbeaver/core-ui';
|
||||
|
||||
import { ResultSetDataContentAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction';
|
||||
import { ResultSetEditAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetEditAction';
|
||||
import { ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction';
|
||||
import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction';
|
||||
import { useResultSetActions } from '../../DatabaseDataModel/Actions/ResultSet/useResultSetActions';
|
||||
import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet';
|
||||
import type { IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService';
|
||||
import { getDefaultLineWrapping } from './getDefaultLineWrapping';
|
||||
import { isTextValueReadonly } from './isTextValueReadonly';
|
||||
import styles from './shared/TextValuePresentation.module.css';
|
||||
import TextValuePresentationTab from './shared/TextValuePresentationTab.module.css';
|
||||
import { TextValueEditor } from './TextValueEditor';
|
||||
import { TextValuePresentationService } from './TextValuePresentationService';
|
||||
import { TextValueTruncatedMessage } from './TextValueTruncatedMessage';
|
||||
import { useTextValue } from './useTextValue';
|
||||
import { useAutoContentType } from './useAutoContentType';
|
||||
import { useTextValueGetter } from './useTextValueGetter';
|
||||
|
||||
const tabRegistry: StyleRegistry = [[TabStyles, { mode: 'append', styles: [TextValuePresentationTab] }]];
|
||||
|
||||
@@ -33,13 +37,13 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
|
||||
const notificationService = useService(NotificationService);
|
||||
const textValuePresentationService = useService(TextValuePresentationService);
|
||||
const style = useS(styles, TextValuePresentationTab);
|
||||
const selection = model.source.getAction(resultIndex, ResultSetSelectAction);
|
||||
const activeElements = selection.getActiveElements();
|
||||
const selectAction = model.source.getAction(resultIndex, ResultSetSelectAction);
|
||||
const formatAction = model.source.getAction(resultIndex, ResultSetFormatAction);
|
||||
const activeElements = selectAction.getActiveElements();
|
||||
const firstSelectedCell = activeElements.length ? activeElements[0] : undefined;
|
||||
const { contentAction, editAction, formatAction } = useResultSetActions({
|
||||
model,
|
||||
resultIndex,
|
||||
});
|
||||
const contentAction = model.source.getAction(resultIndex, ResultSetDataContentAction);
|
||||
const editAction = model.source.getAction(resultIndex, ResultSetEditAction);
|
||||
|
||||
const state = useTabLocalState(() =>
|
||||
observable({
|
||||
lineWrapping: null as boolean | null,
|
||||
@@ -53,25 +57,25 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const textValueInfo = useTextValue({
|
||||
const contentType = useAutoContentType({
|
||||
dataFormat,
|
||||
model,
|
||||
resultIndex,
|
||||
dataFormat,
|
||||
currentContentType: state.currentContentType,
|
||||
elementKey: firstSelectedCell,
|
||||
formatAction,
|
||||
});
|
||||
const autoLineWrapping = getDefaultLineWrapping(textValueInfo.contentType);
|
||||
const textValueGetter = useTextValueGetter({
|
||||
contentAction,
|
||||
editAction,
|
||||
formatAction,
|
||||
dataFormat,
|
||||
contentType,
|
||||
elementKey: firstSelectedCell,
|
||||
});
|
||||
const autoLineWrapping = getDefaultLineWrapping(contentType);
|
||||
const lineWrapping = state.lineWrapping ?? autoLineWrapping;
|
||||
|
||||
const isSelectedCellReadonly =
|
||||
firstSelectedCell &&
|
||||
(formatAction.isReadOnly(firstSelectedCell) ||
|
||||
formatAction.isBinary(firstSelectedCell) ||
|
||||
formatAction.isGeometry(firstSelectedCell) ||
|
||||
contentAction.isTextTruncated(firstSelectedCell));
|
||||
const isReadonlyByResultIndex = model.isReadonly(resultIndex) || model.isDisabled(resultIndex) || !firstSelectedCell;
|
||||
const isReadonly = isSelectedCellReadonly || isReadonlyByResultIndex;
|
||||
const isReadonly = isTextValueReadonly({ model, resultIndex, contentAction, cell: firstSelectedCell, formatAction });
|
||||
const canSave = firstSelectedCell && contentAction.isDownloadable(firstSelectedCell);
|
||||
|
||||
function valueChangeHandler(newValue: string) {
|
||||
@@ -94,7 +98,7 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
|
||||
|
||||
async function selectTabHandler(tabId: string) {
|
||||
// currentContentType may be selected automatically we don't want to change state in this case
|
||||
if (tabId !== textValueInfo.contentType) {
|
||||
if (tabId !== contentType) {
|
||||
state.setContentType(tabId);
|
||||
}
|
||||
}
|
||||
@@ -111,7 +115,7 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
|
||||
dataFormat={dataFormat}
|
||||
resultIndex={resultIndex}
|
||||
container={textValuePresentationService.tabs}
|
||||
currentTabId={textValueInfo.contentType}
|
||||
currentTabId={contentType}
|
||||
model={model}
|
||||
lazy
|
||||
onChange={tab => selectTabHandler(tab.tabId)}
|
||||
@@ -125,10 +129,10 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
|
||||
<Loader suspense>
|
||||
<Group overflow maximum box>
|
||||
<TextValueEditor
|
||||
contentType={textValueInfo.contentType}
|
||||
contentType={contentType}
|
||||
lineWrapping={lineWrapping}
|
||||
readonly={isReadonly}
|
||||
valueGetter={textValueInfo.valueGetter}
|
||||
valueGetter={textValueGetter}
|
||||
onChange={valueChangeHandler}
|
||||
/>
|
||||
</Group>
|
||||
|
||||
+5
-3
@@ -15,7 +15,8 @@ import { bytesToSize, isNotNullDefined } from '@cloudbeaver/core-utils';
|
||||
import type { IResultSetElementKey } from '../../DatabaseDataModel/Actions/ResultSet/IResultSetDataKey';
|
||||
import { isResultSetBlobValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetBlobValue';
|
||||
import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue';
|
||||
import { useResultSetActions } from '../../DatabaseDataModel/Actions/ResultSet/useResultSetActions';
|
||||
import { ResultSetDataContentAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction';
|
||||
import { ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction';
|
||||
import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel';
|
||||
import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet';
|
||||
import { QuotaPlaceholder } from '../QuotaPlaceholder';
|
||||
@@ -30,7 +31,8 @@ interface Props {
|
||||
export const TextValueTruncatedMessage = observer<Props>(function TextValueTruncatedMessage({ model, resultIndex, elementKey }) {
|
||||
const translate = useTranslate();
|
||||
const notificationService = useService(NotificationService);
|
||||
const { contentAction, formatAction } = useResultSetActions({ model, resultIndex });
|
||||
const contentAction = model.source.getAction(resultIndex, ResultSetDataContentAction);
|
||||
const formatAction = model.source.getAction(resultIndex, ResultSetFormatAction);
|
||||
const contentValue = formatAction.get(elementKey);
|
||||
let isTruncated = contentAction.isTextTruncated(elementKey);
|
||||
const isCacheLoaded = !!contentAction.retrieveFullTextFromCache(elementKey);
|
||||
@@ -43,8 +45,8 @@ export const TextValueTruncatedMessage = observer<Props>(function TextValueTrunc
|
||||
if (!isTruncated || isCacheLoaded) {
|
||||
return null;
|
||||
}
|
||||
const isTextColumn = formatAction.isText(elementKey);
|
||||
|
||||
const isTextColumn = formatAction.isText(elementKey);
|
||||
const valueSize =
|
||||
isResultSetContentValue(contentValue) && isNotNullDefined(contentValue.contentLength) ? bytesToSize(contentValue.contentLength) : undefined;
|
||||
|
||||
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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 { IResultSetElementKey } from '../../DatabaseDataModel/Actions/ResultSet/IResultSetDataKey';
|
||||
import type { ResultSetDataContentAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction';
|
||||
import type { ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction';
|
||||
import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel';
|
||||
import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet';
|
||||
|
||||
interface Args {
|
||||
contentAction: ResultSetDataContentAction;
|
||||
formatAction: ResultSetFormatAction;
|
||||
model: IDatabaseDataModel<any, IDatabaseResultSet>;
|
||||
resultIndex: number;
|
||||
cell: IResultSetElementKey | undefined;
|
||||
}
|
||||
|
||||
export function isTextValueReadonly({ contentAction, formatAction, model, resultIndex, cell }: Args) {
|
||||
if (!cell) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return (
|
||||
formatAction.isReadOnly(cell) ||
|
||||
formatAction.isBinary(cell) ||
|
||||
formatAction.isGeometry(cell) ||
|
||||
contentAction.isTextTruncated(cell) ||
|
||||
model.isReadonly(resultIndex) ||
|
||||
model.isDisabled(resultIndex)
|
||||
);
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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 { useService } from '@cloudbeaver/core-di';
|
||||
import type { ResultDataFormat } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import type { IResultSetElementKey } from '../../DatabaseDataModel/Actions/ResultSet/IResultSetDataKey';
|
||||
import { isResultSetBlobValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetBlobValue';
|
||||
import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue';
|
||||
import type { IResultSetValue, ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction';
|
||||
import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel';
|
||||
import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet';
|
||||
import { TextValuePresentationService } from './TextValuePresentationService';
|
||||
|
||||
interface Args {
|
||||
resultIndex: number;
|
||||
model: IDatabaseDataModel<any, IDatabaseResultSet>;
|
||||
dataFormat: ResultDataFormat | null;
|
||||
currentContentType: string | null;
|
||||
elementKey?: IResultSetElementKey;
|
||||
formatAction: ResultSetFormatAction;
|
||||
}
|
||||
|
||||
const DEFAULT_CONTENT_TYPE = 'text/plain';
|
||||
|
||||
function getContentTypeFromResultSetValue(contentValue: IResultSetValue) {
|
||||
if (isResultSetContentValue(contentValue)) {
|
||||
return contentValue.contentType;
|
||||
}
|
||||
|
||||
if (isResultSetBlobValue(contentValue)) {
|
||||
return contentValue.blob.type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function preprocessDefaultContentType(contentType: string | null | undefined) {
|
||||
if (contentType) {
|
||||
switch (contentType) {
|
||||
case 'text/json':
|
||||
return 'application/json';
|
||||
case 'application/octet-stream':
|
||||
return 'application/octet-stream;type=base64';
|
||||
default:
|
||||
return contentType;
|
||||
}
|
||||
}
|
||||
|
||||
return DEFAULT_CONTENT_TYPE;
|
||||
}
|
||||
|
||||
export function useAutoContentType({ dataFormat, model, formatAction, resultIndex, currentContentType, elementKey }: Args) {
|
||||
const textValuePresentationService = useService(TextValuePresentationService);
|
||||
const activeTabs = textValuePresentationService.tabs.getDisplayed({
|
||||
dataFormat: dataFormat,
|
||||
model,
|
||||
resultIndex: resultIndex,
|
||||
});
|
||||
const contentValue = elementKey ? formatAction.get(elementKey) : null;
|
||||
const contentValueType = getContentTypeFromResultSetValue(contentValue);
|
||||
const defaultContentType = preprocessDefaultContentType(contentValueType);
|
||||
|
||||
if (currentContentType === null) {
|
||||
currentContentType = defaultContentType;
|
||||
}
|
||||
|
||||
if (activeTabs.length > 0 && !activeTabs.some(tab => tab.key === currentContentType)) {
|
||||
currentContentType = activeTabs[0].key;
|
||||
}
|
||||
|
||||
return currentContentType;
|
||||
}
|
||||
+11
-62
@@ -8,47 +8,33 @@
|
||||
import { observable } from 'mobx';
|
||||
|
||||
import { useObservableRef, useSuspense } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import type { ResultDataFormat } from '@cloudbeaver/core-sdk';
|
||||
import { blobToBase64, isNotNullDefined, removeMetadataFromDataURL } from '@cloudbeaver/core-utils';
|
||||
|
||||
import type { IResultSetElementKey } from '../../DatabaseDataModel/Actions/ResultSet/IResultSetDataKey';
|
||||
import { isResultSetBlobValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetBlobValue';
|
||||
import { isResultSetContentValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetContentValue';
|
||||
import { useResultSetActions } from '../../DatabaseDataModel/Actions/ResultSet/useResultSetActions';
|
||||
import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel';
|
||||
import type { IDatabaseResultSet } from '../../DatabaseDataModel/IDatabaseResultSet';
|
||||
import type { ResultSetDataContentAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction';
|
||||
import type { ResultSetEditAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetEditAction';
|
||||
import type { ResultSetFormatAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction';
|
||||
import { formatText } from './formatText';
|
||||
import { MAX_BLOB_PREVIEW_SIZE } from './MAX_BLOB_PREVIEW_SIZE';
|
||||
import { TextValuePresentationService } from './TextValuePresentationService';
|
||||
|
||||
interface IUseTextValueArgs {
|
||||
resultIndex: number;
|
||||
model: IDatabaseDataModel<any, IDatabaseResultSet>;
|
||||
dataFormat: ResultDataFormat | null;
|
||||
currentContentType: string | null;
|
||||
elementKey?: IResultSetElementKey;
|
||||
}
|
||||
|
||||
interface ITextValueInfo {
|
||||
valueGetter: () => string;
|
||||
contentType: string;
|
||||
elementKey?: IResultSetElementKey;
|
||||
contentAction: ResultSetDataContentAction;
|
||||
formatAction: ResultSetFormatAction;
|
||||
editAction: ResultSetEditAction;
|
||||
}
|
||||
|
||||
const DEFAULT_CONTENT_TYPE = 'text/plain';
|
||||
type ValueGetter = () => string;
|
||||
|
||||
export function useTextValue({ model, dataFormat, resultIndex, currentContentType, elementKey }: IUseTextValueArgs): ITextValueInfo {
|
||||
const { formatAction, editAction, contentAction } = useResultSetActions({ model, resultIndex });
|
||||
export function useTextValueGetter({ contentType, elementKey, formatAction, contentAction, editAction }: IUseTextValueArgs): ValueGetter {
|
||||
const suspense = useSuspense();
|
||||
const contentValue = elementKey ? formatAction.get(elementKey) : null;
|
||||
const textValuePresentationService = useService(TextValuePresentationService);
|
||||
const activeTabs = textValuePresentationService.tabs.getDisplayed({
|
||||
dataFormat: dataFormat,
|
||||
model: model,
|
||||
resultIndex: resultIndex,
|
||||
});
|
||||
const limitInfo = elementKey ? contentAction.getLimitInfo(elementKey) : null;
|
||||
|
||||
const observedContentValue = useObservableRef(
|
||||
{
|
||||
contentValue,
|
||||
@@ -57,40 +43,6 @@ export function useTextValue({ model, dataFormat, resultIndex, currentContentTyp
|
||||
{ contentValue: observable.ref, limitInfo: observable.ref },
|
||||
);
|
||||
|
||||
let contentType = currentContentType;
|
||||
let autoContentType = DEFAULT_CONTENT_TYPE;
|
||||
let contentValueType;
|
||||
|
||||
if (isResultSetContentValue(contentValue)) {
|
||||
contentValueType = contentValue.contentType;
|
||||
}
|
||||
|
||||
if (isResultSetBlobValue(contentValue)) {
|
||||
contentValueType = contentValue.blob.type;
|
||||
}
|
||||
|
||||
if (contentValueType) {
|
||||
switch (contentValueType) {
|
||||
case 'text/json':
|
||||
autoContentType = 'application/json';
|
||||
break;
|
||||
case 'application/octet-stream':
|
||||
autoContentType = 'application/octet-stream;type=base64';
|
||||
break;
|
||||
default:
|
||||
autoContentType = contentValueType;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (contentType === null) {
|
||||
contentType = autoContentType ?? DEFAULT_CONTENT_TYPE;
|
||||
}
|
||||
|
||||
if (activeTabs.length > 0 && !activeTabs.some(tab => tab.key === contentType)) {
|
||||
contentType = activeTabs[0].key;
|
||||
}
|
||||
|
||||
const parsedBlobValueGetter = suspense.observedValue(
|
||||
'value-blob',
|
||||
() => ({
|
||||
@@ -135,14 +87,11 @@ export function useTextValue({ model, dataFormat, resultIndex, currentContentTyp
|
||||
}
|
||||
|
||||
if (!editAction.isElementEdited(elementKey) || isBinary) {
|
||||
value = formatText(contentType!, value);
|
||||
value = formatText(contentType, value);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
return {
|
||||
valueGetter,
|
||||
contentType,
|
||||
};
|
||||
return valueGetter;
|
||||
}
|
||||
@@ -30,6 +30,7 @@ export default [
|
||||
['data_viewer_data_save_error_title', 'Error occurred while saving changes'],
|
||||
['data_viewer_auto_refresh_settings', 'Auto refresh Settings'],
|
||||
['data_viewer_auto_refresh_settings_stop_on_error', 'Stop on error'],
|
||||
['data_viewer_presentation_value_no_active_elements', 'No selected table cells'],
|
||||
['data_viewer_presentation_value_title', 'Value'],
|
||||
['data_viewer_presentation_value_text_line_wrapping_wrap', 'Wrap lines'],
|
||||
['data_viewer_presentation_value_text_line_wrapping_no_wrap', "Don't wrap lines"],
|
||||
|
||||
@@ -26,6 +26,7 @@ export default [
|
||||
['data_viewer_data_save_error_title', 'Error occurred while saving changes'],
|
||||
['data_viewer_auto_refresh_settings', 'Auto refresh Settings'],
|
||||
['data_viewer_auto_refresh_settings_stop_on_error', 'Stop on error'],
|
||||
['data_viewer_presentation_value_no_active_elements', 'No selected table cells'],
|
||||
['data_viewer_presentation_value_title', 'Valore'],
|
||||
['data_viewer_presentation_value_text_line_wrapping_wrap', 'Wrap lines'],
|
||||
['data_viewer_presentation_value_text_line_wrapping_no_wrap', "Don't wrap lines"],
|
||||
|
||||
@@ -30,6 +30,7 @@ export default [
|
||||
['data_viewer_data_save_error_title', 'Ошибка сохранения изменений'],
|
||||
['data_viewer_auto_refresh_settings', 'Параметры автообновления'],
|
||||
['data_viewer_auto_refresh_settings_stop_on_error', 'Остановить при ошибке'],
|
||||
['data_viewer_presentation_value_no_active_elements', 'Нет выбраных ячеек таблицы'],
|
||||
['data_viewer_presentation_value_title', 'Значение'],
|
||||
['data_viewer_presentation_value_text_line_wrapping_wrap', 'Переносить строки'],
|
||||
['data_viewer_presentation_value_text_line_wrapping_no_wrap', 'Не переносить строки'],
|
||||
|
||||
@@ -30,6 +30,7 @@ export default [
|
||||
['data_viewer_data_save_error_title', 'Error occurred while saving changes'],
|
||||
['data_viewer_auto_refresh_settings', 'Auto refresh Settings'],
|
||||
['data_viewer_auto_refresh_settings_stop_on_error', 'Stop on error'],
|
||||
['data_viewer_presentation_value_no_active_elements', 'No selected table cells'],
|
||||
['data_viewer_presentation_value_title', '值'],
|
||||
['data_viewer_presentation_value_text_line_wrapping_wrap', 'Wrap lines'],
|
||||
['data_viewer_presentation_value_text_line_wrapping_no_wrap', "Don't wrap lines"],
|
||||
|
||||
Reference in New Issue
Block a user