mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-28 03:41:48 +08:00
Merge branch 'devel' into dbeaver/pro#6364-add-option-to-set-default-view-for-project
This commit is contained in:
@@ -24,6 +24,12 @@ You can see a live demo of CloudBeaver here: https://demo.cloudbeaver.io
|
||||
[Database access instructions](https://github.com/dbeaver/cloudbeaver/wiki/Demo-Server)
|
||||
|
||||
## Changelog
|
||||
|
||||
### 25.2.3 2025-10-20
|
||||
- Added visualization for column reordering in the Data Editor;
|
||||
- Fixed the critical issue with duplicating the execution of queries executed by a shortcut in the SQL Editor;
|
||||
- Fixed the critical vulnerability (CVE-2025-61927) in the Happy DOM library. The library was updated to version 20.0.2.
|
||||
|
||||
### 25.2.2 2025-10-06
|
||||
- Replaced true/false text with intuitive checkboxes for boolean values in the metadata editor, making editing easier and faster;
|
||||
- Keyboard navigation was improved across the application. Additional shortcuts are available in the main info panel (top-right corner). To help you discover them faster, relevant keyboard shortcuts are also shown directly in the tooltips of Data Editor buttons;
|
||||
|
||||
+6
-5
@@ -1,6 +1,7 @@
|
||||
package io.cloudbeaver.model;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.DBPObject;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriver;
|
||||
import org.jkiss.dbeaver.model.connection.DBPDriverConstants;
|
||||
@@ -52,35 +53,35 @@ public class WebExpertSettingsProperties implements DBPObject {
|
||||
|
||||
public static class KeepAliveIntervalFieldValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
|
||||
@Override
|
||||
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
|
||||
public boolean isValidValue(@NotNull WebExpertSettingsProperties object, @Nullable Object value) throws IllegalArgumentException {
|
||||
return CommonUtils.toBoolean(!object.driver.isEmbedded(), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class AutoCommitFieldValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
|
||||
@Override
|
||||
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
|
||||
public boolean isValidValue(@NotNull WebExpertSettingsProperties object, @Nullable Object value) throws IllegalArgumentException {
|
||||
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_TRANSACTIONS), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ReadOnlyFieldValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
|
||||
@Override
|
||||
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
|
||||
public boolean isValidValue(@NotNull WebExpertSettingsProperties object, @Nullable Object value) throws IllegalArgumentException {
|
||||
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_READ_ONLY_MODE), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DefaultCatalogFieldVisibleValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
|
||||
@Override
|
||||
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
|
||||
public boolean isValidValue(@NotNull WebExpertSettingsProperties object, @Nullable Object value) throws IllegalArgumentException {
|
||||
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_CATALOG_SELECTION), true);
|
||||
}
|
||||
}
|
||||
|
||||
public static class DefaultSchemaFieldVisibleValidator implements IPropertyValueValidator<WebExpertSettingsProperties, Object> {
|
||||
@Override
|
||||
public boolean isValidValue(WebExpertSettingsProperties object, Object value) throws IllegalArgumentException {
|
||||
public boolean isValidValue(@NotNull WebExpertSettingsProperties object, @Nullable Object value) throws IllegalArgumentException {
|
||||
return CommonUtils.toBoolean(object.driver.getDriverParameter(DBPDriverConstants.PARAM_SUPPORTS_SCHEMA_SELECTION), true);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-1
@@ -41,6 +41,7 @@ import org.jkiss.dbeaver.utils.GeneralUtils;
|
||||
import org.jkiss.dbeaver.utils.RuntimeUtils;
|
||||
import org.jkiss.utils.CommonUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
@@ -204,7 +205,7 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
|
||||
initializeApplicationServices();
|
||||
try {
|
||||
try {
|
||||
this.instanceServer = new CloudBeaverInstanceServer();
|
||||
this.instanceServer = createInstanceServer();
|
||||
} catch (Exception e) {
|
||||
log.error("Error initializing instance server", e);
|
||||
}
|
||||
@@ -216,6 +217,10 @@ public abstract class BaseServletApplication extends BaseApplicationImpl impleme
|
||||
return EXIT_OK;
|
||||
}
|
||||
|
||||
protected CloudBeaverInstanceServer createInstanceServer() throws IOException {
|
||||
return new CloudBeaverInstanceServer();
|
||||
}
|
||||
|
||||
protected abstract void startServer() throws DBException;
|
||||
|
||||
@Override
|
||||
|
||||
+10
-1
@@ -62,6 +62,7 @@ public class ServletSystemInformationCollector<T extends ServletApplication> imp
|
||||
private DBPConnectionInformation smDatabaseInfo;
|
||||
private String workspacePath;
|
||||
private final DeploymentType deploymentType;
|
||||
private boolean internalDatabaseInfoCollected = false;
|
||||
|
||||
public ServletSystemInformationCollector(@NotNull T application) {
|
||||
this.application = application;
|
||||
@@ -158,7 +159,15 @@ public class ServletSystemInformationCollector<T extends ServletApplication> imp
|
||||
/**
|
||||
* Collects info about internal databases.
|
||||
*/
|
||||
public void collectInternalDatabaseUseInformation() throws DBException {
|
||||
public synchronized void collectInternalDatabaseUseInformation() throws DBException {
|
||||
if (internalDatabaseInfoCollected) {
|
||||
return;
|
||||
}
|
||||
collectInternalDatabaseInfo();
|
||||
this.internalDatabaseInfoCollected = true;
|
||||
}
|
||||
|
||||
protected void collectInternalDatabaseInfo() throws DBException {
|
||||
this.smDatabaseInfo = application.getAdminSecurityController(new NoAuthCredentialsProvider())
|
||||
.getInternalDatabaseInformation();
|
||||
}
|
||||
|
||||
+29
-8
@@ -16,20 +16,41 @@
|
||||
*/
|
||||
package io.cloudbeaver.model.cli;
|
||||
|
||||
import org.jkiss.dbeaver.model.cli.ApplicationCommandLine;
|
||||
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.cli.*;
|
||||
import org.jkiss.dbeaver.model.cli.model.CommandLineAuthenticator;
|
||||
import picocli.CommandLine;
|
||||
|
||||
public class CloudBeaverCommandLine extends ApplicationCommandLine<ApplicationInstanceController> {
|
||||
private static CloudBeaverCommandLine INSTANCE = null;
|
||||
|
||||
private CloudBeaverCommandLine() {
|
||||
@Nullable
|
||||
private final CommandLineAuthenticator authenticator;
|
||||
|
||||
public CloudBeaverCommandLine(@Nullable CommandLineAuthenticator authenticator) {
|
||||
super();
|
||||
this.authenticator = authenticator;
|
||||
}
|
||||
|
||||
public synchronized static CloudBeaverCommandLine getInstance() {
|
||||
if (INSTANCE == null) {
|
||||
INSTANCE = new CloudBeaverCommandLine();
|
||||
@Override
|
||||
protected CloudBeaverTopLevelCommand createTopLevelCommand(
|
||||
@Nullable ApplicationInstanceController applicationInstanceController,
|
||||
@NotNull CommandLineContext context,
|
||||
@NotNull CLIRunMeta runMeta
|
||||
) {
|
||||
if (authenticator != null) {
|
||||
context.setContextParameter(CLIConstants.CONTEXT_PARAM_AUTHENTICATOR, authenticator);
|
||||
}
|
||||
return INSTANCE;
|
||||
return new CloudBeaverTopLevelCommand(applicationInstanceController, context, runMeta);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected CommandLine initCommandLine(
|
||||
@Nullable ApplicationInstanceController applicationInstanceController,
|
||||
@NotNull CommandLineContext context,
|
||||
@NotNull CLIRunMeta runMeta
|
||||
) {
|
||||
return super.initCommandLine(applicationInstanceController, context, runMeta);
|
||||
}
|
||||
}
|
||||
|
||||
+12
-5
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
package io.cloudbeaver.model.cli;
|
||||
|
||||
import org.apache.commons.cli.CommandLine;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
|
||||
import org.jkiss.dbeaver.model.cli.ApplicationInstanceServer;
|
||||
@@ -25,20 +25,27 @@ import org.jkiss.dbeaver.model.cli.CLIProcessResult;
|
||||
import java.io.IOException;
|
||||
|
||||
public class CloudBeaverInstanceServer extends ApplicationInstanceServer<ApplicationInstanceController> {
|
||||
private final CloudBeaverCommandLine commandLine;
|
||||
|
||||
public CloudBeaverInstanceServer() throws IOException {
|
||||
super(ApplicationInstanceController.class);
|
||||
this.commandLine = new CloudBeaverCommandLine(null);
|
||||
}
|
||||
|
||||
protected CloudBeaverInstanceServer(@NotNull CloudBeaverCommandLine commandLine) throws IOException {
|
||||
super(ApplicationInstanceController.class);
|
||||
this.commandLine = commandLine;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public CLIProcessResult handleCommandLine(@NotNull String[] args) {
|
||||
CommandLine cmd = CloudBeaverCommandLine.getInstance().getCommandLine(args);
|
||||
try {
|
||||
return CloudBeaverCommandLine.getInstance().executeCommandLineCommands(
|
||||
cmd,
|
||||
return commandLine.executeCommandLineCommands(
|
||||
this,
|
||||
false,
|
||||
false
|
||||
false,
|
||||
args
|
||||
);
|
||||
} catch (Exception e) {
|
||||
return new CLIProcessResult(CLIProcessResult.PostAction.ERROR, "Error executing command: " + e.getMessage());
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* DBeaver - Universal Database Manager
|
||||
* Copyright (C) 2010-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package io.cloudbeaver.model.cli;
|
||||
|
||||
import org.jkiss.code.NotNull;
|
||||
import org.jkiss.code.Nullable;
|
||||
import org.jkiss.dbeaver.model.cli.ApplicationInstanceController;
|
||||
import org.jkiss.dbeaver.model.cli.CLIRunMeta;
|
||||
import org.jkiss.dbeaver.model.cli.CommandLineContext;
|
||||
import org.jkiss.dbeaver.model.cli.command.AbstractTopLevelCommand;
|
||||
import picocli.CommandLine;
|
||||
|
||||
@CommandLine.Command(name = "cbvr", description = "CloudBeaver commands", mixinStandardHelpOptions = true)
|
||||
public class CloudBeaverTopLevelCommand extends AbstractTopLevelCommand {
|
||||
protected CloudBeaverTopLevelCommand(
|
||||
@Nullable ApplicationInstanceController controller,
|
||||
@NotNull CommandLineContext context,
|
||||
@NotNull CLIRunMeta meta
|
||||
|
||||
) {
|
||||
super(controller, context, meta);
|
||||
}
|
||||
}
|
||||
@@ -314,11 +314,6 @@ public abstract class CBApplication<T extends CBServerConfig>
|
||||
}
|
||||
grantPermissionsToConnections();
|
||||
}
|
||||
try {
|
||||
this.systemInformationCollector.collectInternalDatabaseUseInformation();
|
||||
} catch (DBException e) {
|
||||
log.error("Error collecting system information", e);
|
||||
}
|
||||
|
||||
eventController.scheduleCheckJob();
|
||||
|
||||
|
||||
+8
-4
@@ -20,6 +20,7 @@ package io.cloudbeaver.service.core.impl;
|
||||
import io.cloudbeaver.*;
|
||||
import io.cloudbeaver.model.*;
|
||||
import io.cloudbeaver.model.app.ServletApplication;
|
||||
import io.cloudbeaver.model.app.ServletSystemInformationCollector;
|
||||
import io.cloudbeaver.model.session.WebSession;
|
||||
import io.cloudbeaver.registry.WebHandlerRegistry;
|
||||
import io.cloudbeaver.registry.WebSessionHandlerDescriptor;
|
||||
@@ -89,10 +90,13 @@ public class WebServiceCore implements DBWServiceCore {
|
||||
|
||||
@Override
|
||||
public WebPropertyInfo[] getSystemInformationProperties(@NotNull WebSession webSession) {
|
||||
return WebCommonUtils.getObjectProperties(
|
||||
webSession,
|
||||
WebAppUtils.getWebApplication().getSystemInformationCollector()
|
||||
);
|
||||
ServletSystemInformationCollector<?> collector = WebAppUtils.getWebApplication().getSystemInformationCollector();
|
||||
try {
|
||||
collector.collectInternalDatabaseUseInformation();
|
||||
} catch (DBException e) {
|
||||
log.error("Error collecting system information", e);
|
||||
}
|
||||
return WebCommonUtils.getObjectProperties(webSession, collector);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+7
-3
@@ -233,9 +233,13 @@ public class CBEmbeddedSecurityController<T extends ServletAuthApplication>
|
||||
}
|
||||
for (String possibleUserId : List.of(userId, userId.toLowerCase())) {
|
||||
if (isSubjectExists(possibleUserId)) {
|
||||
log.info("User already exist : " + possibleUserId);
|
||||
setUserAuthRole(connection, possibleUserId, authRole);
|
||||
enableUser(connection, possibleUserId, true, null, null);
|
||||
if (getSubjectType(possibleUserId) == SMSubjectType.team) {
|
||||
log.error("Cannot import user '%s': a team with this name already exists.".formatted(possibleUserId));
|
||||
} else {
|
||||
log.info("User already exist : " + possibleUserId);
|
||||
setUserAuthRole(connection, possibleUserId, authRole);
|
||||
enableUser(connection, possibleUserId, true, null, null);
|
||||
}
|
||||
continue outer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import type { DialogComponentProps } from '@cloudbeaver/core-dialogs';
|
||||
|
||||
import { observable } from 'mobx';
|
||||
import { EXPORT_IMAGE_FORMATS, type ExportImageFormat } from './ExportImageFormats.js';
|
||||
import { useTranslate } from '../localization/useTranslate.js';
|
||||
import { useObservableRef } from '../useObservableRef.js';
|
||||
import { CommonDialogBody } from '../CommonDialog/CommonDialog/CommonDialogBody.js';
|
||||
import { CommonDialogFooter } from '../CommonDialog/CommonDialog/CommonDialogFooter.js';
|
||||
import { CommonDialogHeader } from '../CommonDialog/CommonDialog/CommonDialogHeader.js';
|
||||
import { CommonDialogWrapper } from '../CommonDialog/CommonDialog/CommonDialogWrapper.js';
|
||||
import { Container } from '../Containers/Container.js';
|
||||
import { ErrorMessage } from '../ErrorMessage.js';
|
||||
import { FieldCheckbox } from '../FormControls/Checkboxes/FieldCheckbox.js';
|
||||
import { InputField } from '../FormControls/InputField/InputField.js';
|
||||
import { useErrorDetails } from '../useErrorDetails.js';
|
||||
import { Select } from '../FormControls/Select.js';
|
||||
import { Button } from '../Button.js';
|
||||
|
||||
export interface IExportImageOptions {
|
||||
format: ExportImageFormat;
|
||||
transparent: boolean;
|
||||
fileName: string;
|
||||
}
|
||||
|
||||
export interface ExportImagePayload {
|
||||
export: (options: IExportImageOptions) => Promise<void>;
|
||||
defaultFileName: string;
|
||||
}
|
||||
|
||||
interface State extends IExportImageOptions {
|
||||
error: Error | null;
|
||||
}
|
||||
|
||||
export const ExportImageDialog = observer<DialogComponentProps<ExportImagePayload, null>>(function ExportImageDialog(props) {
|
||||
const translate = useTranslate();
|
||||
const state = useObservableRef<State>(
|
||||
() => ({
|
||||
format: 'SVG',
|
||||
transparent: false,
|
||||
fileName: props.payload.defaultFileName,
|
||||
error: null,
|
||||
}),
|
||||
{
|
||||
format: observable.ref,
|
||||
transparent: observable.ref,
|
||||
fileName: observable.ref,
|
||||
error: observable.ref,
|
||||
},
|
||||
false,
|
||||
);
|
||||
const errorDetails = useErrorDetails(state.error);
|
||||
|
||||
async function onExportHandler() {
|
||||
try {
|
||||
await props.payload.export(state);
|
||||
props.resolveDialog();
|
||||
} catch (error: any) {
|
||||
state.error = error;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<CommonDialogWrapper size="small">
|
||||
<CommonDialogHeader title="core_blocks_export_image_dialog_title" onReject={props.rejectDialog} />
|
||||
<CommonDialogBody>
|
||||
<Container>
|
||||
<Container small gap>
|
||||
<InputField state={state} name="fileName">
|
||||
{translate('ui_name')}
|
||||
</InputField>
|
||||
<Select items={EXPORT_IMAGE_FORMATS} keySelector={value => value} valueSelector={value => value} state={state} name="format">
|
||||
{translate('core_blocks_export_image_dialog_format')}
|
||||
</Select>
|
||||
<FieldCheckbox state={state} name="transparent" label={translate('core_blocks_export_image_dialog_transparent_background')} />
|
||||
</Container>
|
||||
</Container>
|
||||
{errorDetails.name && (
|
||||
<ErrorMessage text={errorDetails.message || errorDetails.name} hasDetails={errorDetails.hasDetails} onShowDetails={errorDetails.open} />
|
||||
)}
|
||||
</CommonDialogBody>
|
||||
<CommonDialogFooter className="tw:justify-end tw:items-center tw:gap-[24px]">
|
||||
<Button type="button" variant="secondary" onClick={props.rejectDialog}>
|
||||
{translate('app_shared_inlineEditor_dialog_cancel')}
|
||||
</Button>
|
||||
<Button type="button" onClick={onExportHandler}>
|
||||
{translate('ui_export')}
|
||||
</Button>
|
||||
</CommonDialogFooter>
|
||||
</CommonDialogWrapper>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 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 { importLazyComponent } from '../importLazyComponent.js';
|
||||
|
||||
export const ExportImageDialog = importLazyComponent(() => import('./ExportImageDialog.js').then(m => m.ExportImageDialog));
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
export type ExportImageFormat = 'SVG' | 'PNG';
|
||||
|
||||
export const EXPORT_IMAGE_FORMATS: ExportImageFormat[] = ['SVG', 'PNG'];
|
||||
@@ -19,6 +19,8 @@ export { default as ConfirmationDialogStyles } from './CommonDialog/Confirmation
|
||||
export * from './CommonDialog/ConfirmationDialogDelete.js';
|
||||
export * from './CommonDialog/RenameDialog.js';
|
||||
export * from './CommonDialog/DialogsPortal.js';
|
||||
export * from './ExportImageDialog/ExportImageDialogLazy.js';
|
||||
export * from './ExportImageDialog/ExportImageFormats.js';
|
||||
|
||||
export * from './ErrorDetailsDialog/ErrorDetailsDialog.js';
|
||||
|
||||
|
||||
@@ -13,4 +13,7 @@ export default [
|
||||
['core_blocks_exception_message_error_title', 'Unable to display content'],
|
||||
['core_blocks_exception_message_error_message', 'Something went wrong.'],
|
||||
['core_blocks_custom_input_validation_error', 'Field value is not valid'],
|
||||
['core_blocks_export_image_dialog_title', 'Export Image'],
|
||||
['core_blocks_export_image_dialog_format', 'File format'],
|
||||
['core_blocks_export_image_dialog_transparent_background', 'Transparent background'],
|
||||
];
|
||||
|
||||
@@ -12,4 +12,7 @@ export default [
|
||||
['core_blocks_exception_message_error_title', "Impossible d'afficher le contenu"],
|
||||
['core_blocks_exception_message_error_message', 'Quelque chose a mal tourné.'],
|
||||
['core_blocks_custom_input_validation_error', "La valeur du champ n'est pas valide"],
|
||||
['core_blocks_export_image_dialog_title', "Exporter l'image"],
|
||||
['core_blocks_export_image_dialog_format', 'Format de fichier'],
|
||||
['core_blocks_export_image_dialog_transparent_background', 'Fond transparent'],
|
||||
];
|
||||
|
||||
@@ -7,4 +7,7 @@ export default [
|
||||
['core_blocks_object_property_info_form_empty_placeholder', 'Properties empty'],
|
||||
['core_blocks_properties_table_item_remove', 'Remove property'],
|
||||
['core_blocks_custom_input_validation_error', 'Field value is not valid'],
|
||||
['core_blocks_export_image_dialog_title', 'Esporta immagine'],
|
||||
['core_blocks_export_image_dialog_format', 'Formato file'],
|
||||
['core_blocks_export_image_dialog_transparent_background', 'Sfondo trasparente'],
|
||||
];
|
||||
|
||||
@@ -10,4 +10,7 @@ export default [
|
||||
['combobox_no_results_placeholder', 'Нет совпадений'],
|
||||
['combobox_select_placeholder', 'Выберите опцию'],
|
||||
['core_blocks_custom_input_validation_error', 'Значение не валидно'],
|
||||
['core_blocks_export_image_dialog_title', 'Экспорт изображения'],
|
||||
['core_blocks_export_image_dialog_format', 'Формат файла'],
|
||||
['core_blocks_export_image_dialog_transparent_background', 'Прозрачный фон'],
|
||||
];
|
||||
|
||||
@@ -13,4 +13,7 @@ export default [
|
||||
['core_blocks_exception_message_error_title', 'Không thể hiển thị nội dung'],
|
||||
['core_blocks_exception_message_error_message', 'Đã xảy ra lỗi.'],
|
||||
['core_blocks_custom_input_validation_error', 'Giá trị trường không hợp lệ'],
|
||||
['core_blocks_export_image_dialog_title', 'Xuất hình ảnh'],
|
||||
['core_blocks_export_image_dialog_format', 'Định dạng tệp'],
|
||||
['core_blocks_export_image_dialog_transparent_background', 'Nền trong suốt'],
|
||||
];
|
||||
|
||||
@@ -13,4 +13,7 @@ export default [
|
||||
['core_blocks_exception_message_error_title', '无法显示内容'],
|
||||
['core_blocks_exception_message_error_message', '出现错误.'],
|
||||
['core_blocks_custom_input_validation_error', '字段值无效'],
|
||||
['core_blocks_export_image_dialog_title', '导出图像'],
|
||||
['core_blocks_export_image_dialog_format', '文件格式'],
|
||||
['core_blocks_export_image_dialog_transparent_background', '透明背景'],
|
||||
];
|
||||
|
||||
@@ -12,7 +12,7 @@ module.exports = [
|
||||
'joint',
|
||||
'backbone',
|
||||
'lodash',
|
||||
'canvg',
|
||||
'modern-screenshot',
|
||||
'dagre',
|
||||
'svg-pathdata',
|
||||
'@emotion',
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"fast-deep-equal": "^3",
|
||||
"md5": "^2",
|
||||
"mobx": "^6",
|
||||
"modern-screenshot": "^4",
|
||||
"tslib": "^2",
|
||||
"underscore": "^1",
|
||||
"uuid": "^13",
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 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 * as modernScreenshot from 'modern-screenshot';
|
||||
import type { Options } from 'modern-screenshot';
|
||||
import { download } from './download.js';
|
||||
import { isImageBroken } from './isBrokenImage.js';
|
||||
|
||||
export type { Options as IScreenshotOptions };
|
||||
|
||||
const BROKEN_IMAGE_ERROR_MESSAGE = 'Something went wrong. please try select another file format';
|
||||
|
||||
export async function downloadSvg<T extends Node>(element: T, options: Options, fileName: string): Promise<void> {
|
||||
const svg = await modernScreenshot.domToForeignObjectSvg<T>(element, options);
|
||||
const svgData = new XMLSerializer().serializeToString(svg);
|
||||
const blob = new Blob(['<?xml version="1.0" standalone="no"?>\r\n', svgData], { type: 'image/svg+xml;charset=utf-8' });
|
||||
const uri = URL.createObjectURL(blob);
|
||||
const isBrokenImage = await isImageBroken(uri);
|
||||
|
||||
if (isBrokenImage) {
|
||||
throw new Error(BROKEN_IMAGE_ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
download(blob, `${fileName}.svg`);
|
||||
}
|
||||
|
||||
export async function downloadPng<T extends Node>(element: T, options: Options, fileName: string): Promise<void> {
|
||||
const png = await modernScreenshot.domToPng(element, options);
|
||||
const isBrokenImage = await isImageBroken(png);
|
||||
|
||||
if (isBrokenImage) {
|
||||
throw new Error(BROKEN_IMAGE_ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
download(png, `${fileName}.png`);
|
||||
}
|
||||
@@ -89,3 +89,4 @@ export * from './bindFunctions.js';
|
||||
export * from './getDomainFromUrl.js';
|
||||
export * from './isNumber.js';
|
||||
export * from './getSubjectDifferences.js';
|
||||
export * from './downloadImage.js';
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
export function isImageBroken(imageUrl: string): Promise<boolean> {
|
||||
return new Promise(resolve => {
|
||||
const img = new Image();
|
||||
|
||||
img.onload = () => {
|
||||
resolve(false);
|
||||
};
|
||||
|
||||
img.onerror = () => {
|
||||
resolve(true);
|
||||
};
|
||||
|
||||
img.src = imageUrl;
|
||||
});
|
||||
}
|
||||
@@ -223,12 +223,12 @@ export const ReactCodemirror = observer<IReactCodeMirrorProps, IEditorRef>(
|
||||
}
|
||||
|
||||
if (transaction.changes) {
|
||||
view.dispatch({ changes: transaction.changes });
|
||||
view.dispatch({ changes: transaction.changes, annotations: transaction.annotations });
|
||||
isInitialScriptOpening.current = false;
|
||||
}
|
||||
|
||||
if (transaction.selection) {
|
||||
view.dispatch({ selection: transaction.selection });
|
||||
view.dispatch({ selection: transaction.selection, annotations: transaction.annotations });
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2025 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -13,6 +13,7 @@ import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataMo
|
||||
|
||||
export interface IDataValuePanelOptions {
|
||||
dataFormat: ResultDataFormat[];
|
||||
isTextPresentation?: boolean;
|
||||
}
|
||||
|
||||
export interface IDataValuePanelProps {
|
||||
|
||||
@@ -22,6 +22,9 @@ import ValuePanelTab from './shared/ValuePanelTab.module.css';
|
||||
import { IDatabaseDataSelectAction } from '../../DatabaseDataModel/Actions/IDatabaseDataSelectAction.js';
|
||||
import { IDatabaseDataResultAction } from '../../DatabaseDataModel/Actions/IDatabaseDataResultAction.js';
|
||||
import { IDatabaseDataMetadataAction } from '../../DatabaseDataModel/Actions/IDatabaseDataMetadataAction.js';
|
||||
import { IDatabaseDataFormatAction } from '../../DatabaseDataModel/Actions/IDatabaseDataFormatAction.js';
|
||||
import type { IGridDataKey } from '../../DatabaseDataModel/Actions/Grid/IGridDataKey.js';
|
||||
import { useContentType } from '../../ValuePanelPresentation/TextValue/useContentType.js';
|
||||
|
||||
const tabListRegistry: StyleRegistry = [[TabStyles, { mode: 'append', styles: [ValuePanelTab] }]];
|
||||
|
||||
@@ -35,7 +38,7 @@ export const ValuePanel: DataPresentationComponent = observer(function ValuePane
|
||||
const selectAction = model.source.tryGetAction(resultIndex, IDatabaseDataSelectAction);
|
||||
const dataResultAction = model.source.tryGetAction(resultIndex, IDatabaseDataResultAction);
|
||||
const metadataAction = model.source.getAction(resultIndex, IDatabaseDataMetadataAction);
|
||||
const activeElements = selectAction?.getActiveElements();
|
||||
const activeElements = selectAction?.getActiveElements() as IGridDataKey[] | undefined;
|
||||
let elementKey: string | null = null;
|
||||
const style = useS(styles);
|
||||
|
||||
@@ -58,13 +61,14 @@ export const ValuePanel: DataPresentationComponent = observer(function ValuePane
|
||||
);
|
||||
|
||||
const displayed = service.getDisplayed({ dataFormat, model, resultIndex });
|
||||
let currentTabId = state.currentTabId;
|
||||
|
||||
const hasCurrentTabCells = currentTabId && displayed.some(tab => tab.key === currentTabId);
|
||||
|
||||
if (displayed.length > 0 && !hasCurrentTabCells) {
|
||||
currentTabId = displayed[0]!.key;
|
||||
}
|
||||
const currentTabId = useContentType({
|
||||
model: model as any,
|
||||
currentContentType: state.currentTabId,
|
||||
elementKey: activeElements && activeElements.length > 0 ? activeElements[0] : undefined,
|
||||
formatAction: model.source.tryGetAction(resultIndex, IDatabaseDataFormatAction),
|
||||
displayed,
|
||||
});
|
||||
|
||||
return (
|
||||
<TabsState
|
||||
|
||||
+2
@@ -9,6 +9,8 @@
|
||||
.tabList {
|
||||
composes: theme-background-secondary theme-border-color-background theme-text-on-secondary from global;
|
||||
position: relative;
|
||||
overflow: auto;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
|
||||
+7
-50
@@ -8,10 +8,10 @@
|
||||
import { observable } from 'mobx';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
||||
import { ActionIconButton, Container, Group, Loader, s, SContext, type StyleRegistry, useS, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { ActionIconButton, Container, Group, Loader, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { type TabContainerPanelComponent, TabList, TabsState, TabStyles, useTabLocalState } from '@cloudbeaver/core-ui';
|
||||
import { type TabContainerPanelComponent, useTabLocalState } from '@cloudbeaver/core-ui';
|
||||
|
||||
import { ResultSetDataContentAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetDataContentAction.js';
|
||||
import { DataViewerService } from '../../DataViewerService.js';
|
||||
@@ -19,21 +19,16 @@ import { isResultSetDataModel } from '../../ResultSet/isResultSetDataModel.js';
|
||||
import type { IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService.js';
|
||||
import { getDefaultLineWrapping } from './getDefaultLineWrapping.js';
|
||||
import { isTextValueReadonly } from './isTextValueReadonly.js';
|
||||
import styles from './shared/TextValuePresentation.module.css';
|
||||
import TextValuePresentationTab from './shared/TextValuePresentationTab.module.css';
|
||||
import { TextValueEditor } from './TextValueEditor.js';
|
||||
import { TextValuePresentationService } from './TextValuePresentationService.js';
|
||||
import { TextValueTruncatedMessage } from './TextValueTruncatedMessage.js';
|
||||
import { useAutoContentType } from './useAutoContentType.js';
|
||||
import { useTextValueGetter } from './useTextValueGetter.js';
|
||||
import { IDatabaseDataSelectAction } from '../../DatabaseDataModel/Actions/IDatabaseDataSelectAction.js';
|
||||
import { IDatabaseDataFormatAction } from '../../DatabaseDataModel/Actions/IDatabaseDataFormatAction.js';
|
||||
import { IDatabaseDataEditAction } from '../../DatabaseDataModel/Actions/IDatabaseDataEditAction.js';
|
||||
import { GridSelectAction } from '../../DatabaseDataModel/Actions/Grid/GridSelectAction.js';
|
||||
|
||||
const tabRegistry: StyleRegistry = [[TabStyles, { mode: 'append', styles: [TextValuePresentationTab] }]];
|
||||
|
||||
export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelProps> = observer(function TextValuePresentation({
|
||||
tabId,
|
||||
model: unknownModel,
|
||||
resultIndex,
|
||||
dataFormat,
|
||||
@@ -44,9 +39,7 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
|
||||
}
|
||||
const translate = useTranslate();
|
||||
const notificationService = useService(NotificationService);
|
||||
const textValuePresentationService = useService(TextValuePresentationService);
|
||||
const dataViewerService = useService(DataViewerService);
|
||||
const style = useS(styles, TextValuePresentationTab);
|
||||
const selectAction = model.source.getAction(resultIndex, IDatabaseDataSelectAction, GridSelectAction);
|
||||
const formatAction = model.source.getAction(resultIndex, IDatabaseDataFormatAction);
|
||||
const activeElements = selectAction.getActiveElements();
|
||||
@@ -57,33 +50,21 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
|
||||
const state = useTabLocalState(() =>
|
||||
observable({
|
||||
lineWrapping: null as boolean | null,
|
||||
currentContentType: null as string | null,
|
||||
|
||||
setContentType(contentType: string | null) {
|
||||
this.currentContentType = contentType;
|
||||
},
|
||||
setLineWrapping(lineWrapping: boolean | null) {
|
||||
this.lineWrapping = lineWrapping;
|
||||
},
|
||||
}),
|
||||
);
|
||||
const contentType = useAutoContentType({
|
||||
dataFormat,
|
||||
model,
|
||||
resultIndex,
|
||||
currentContentType: state.currentContentType,
|
||||
elementKey: firstSelectedCell,
|
||||
formatAction,
|
||||
});
|
||||
|
||||
const textValueGetter = useTextValueGetter({
|
||||
contentAction,
|
||||
editAction,
|
||||
formatAction,
|
||||
dataFormat,
|
||||
contentType,
|
||||
contentType: tabId,
|
||||
elementKey: firstSelectedCell,
|
||||
});
|
||||
const autoLineWrapping = getDefaultLineWrapping(contentType);
|
||||
const autoLineWrapping = getDefaultLineWrapping(tabId);
|
||||
const lineWrapping = state.lineWrapping ?? autoLineWrapping;
|
||||
const isReadonly = isTextValueReadonly({ model, resultIndex, contentAction, cell: firstSelectedCell, formatAction, editAction });
|
||||
const canSave = firstSelectedCell && contentAction.isDownloadable(firstSelectedCell) && dataViewerService.canExportData;
|
||||
@@ -106,40 +87,16 @@ export const TextValuePresentation: TabContainerPanelComponent<IDataValuePanelPr
|
||||
}
|
||||
}
|
||||
|
||||
function selectTabHandler(tabId: string) {
|
||||
// currentContentType may be selected automatically we don't want to change state in this case
|
||||
if (tabId !== contentType) {
|
||||
state.setContentType(tabId);
|
||||
}
|
||||
}
|
||||
|
||||
function toggleLineWrappingHandler() {
|
||||
state.setLineWrapping(!lineWrapping);
|
||||
}
|
||||
|
||||
return (
|
||||
<Container vertical gap dense overflow>
|
||||
<Container keepSize center overflow>
|
||||
<Container keepSize>
|
||||
<TabsState
|
||||
dataFormat={dataFormat}
|
||||
resultIndex={resultIndex}
|
||||
container={textValuePresentationService.tabs}
|
||||
currentTabId={contentType}
|
||||
model={model}
|
||||
lazy
|
||||
onChange={tab => selectTabHandler(tab.tabId)}
|
||||
>
|
||||
<SContext registry={tabRegistry}>
|
||||
<TabList className={s(style, { tabList: true, textValuePresentationTab: true, underline: true })} underline />
|
||||
</SContext>
|
||||
</TabsState>
|
||||
</Container>
|
||||
</Container>
|
||||
<Loader suspense>
|
||||
<Group overflow maximum box>
|
||||
<TextValueEditor
|
||||
contentType={contentType}
|
||||
contentType={tabId}
|
||||
lineWrapping={lineWrapping}
|
||||
readonly={isReadonly}
|
||||
valueGetter={textValueGetter}
|
||||
|
||||
+58
-63
@@ -1,87 +1,82 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2024 DBeaver Corp and others
|
||||
* Copyright (C) 2020-2025 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 React from 'react';
|
||||
|
||||
import { importLazyComponent } from '@cloudbeaver/core-blocks';
|
||||
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import { ResultDataFormat } from '@cloudbeaver/core-sdk';
|
||||
|
||||
import { isResultSetDataSource } from '../../ResultSet/ResultSetDataSource.js';
|
||||
import { DataValuePanelService } from '../../TableViewer/ValuePanel/DataValuePanelService.js';
|
||||
import { DataValuePanelService, type IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService.js';
|
||||
import { isBlobPresentationAvailable } from './isTextValuePresentationAvailable.js';
|
||||
import { TextValuePresentationService } from './TextValuePresentationService.js';
|
||||
|
||||
const TextValuePresentation = importLazyComponent(() => import('./TextValuePresentation.js').then(module => module.TextValuePresentation));
|
||||
|
||||
@injectable(() => [TextValuePresentationService, DataValuePanelService])
|
||||
const PRESENTATIONS = [
|
||||
{
|
||||
key: 'text/plain',
|
||||
name: 'data_viewer_presentation_value_text_title',
|
||||
},
|
||||
{
|
||||
key: 'text/html',
|
||||
name: 'data_viewer_presentation_value_text_html_title',
|
||||
},
|
||||
{
|
||||
key: 'text/xml',
|
||||
name: 'data_viewer_presentation_value_text_xml_title',
|
||||
},
|
||||
{
|
||||
key: 'application/json',
|
||||
name: 'data_viewer_presentation_value_text_json_title',
|
||||
},
|
||||
{
|
||||
key: 'application/octet-stream;type=hex',
|
||||
name: 'data_viewer_presentation_value_text_hex_title',
|
||||
isHidden: (context: IDataValuePanelProps | undefined) => !isBlobPresentationAvailable(context),
|
||||
},
|
||||
{
|
||||
key: 'application/octet-stream;type=base64',
|
||||
name: 'data_viewer_presentation_value_text_base64_title',
|
||||
isHidden: (context: IDataValuePanelProps | undefined) => !isBlobPresentationAvailable(context),
|
||||
},
|
||||
];
|
||||
|
||||
@injectable(() => [DataValuePanelService])
|
||||
export class TextValuePresentationBootstrap extends Bootstrap {
|
||||
constructor(
|
||||
private readonly textValuePresentationService: TextValuePresentationService,
|
||||
private readonly dataValuePanelService: DataValuePanelService,
|
||||
) {
|
||||
constructor(private readonly dataValuePanelService: DataValuePanelService) {
|
||||
super();
|
||||
}
|
||||
|
||||
override register(): void {
|
||||
this.dataValuePanelService.add({
|
||||
key: 'text-presentation',
|
||||
options: {
|
||||
dataFormat: [ResultDataFormat.Resultset],
|
||||
},
|
||||
name: 'data_viewer_presentation_value_text_title',
|
||||
order: Number.MAX_SAFE_INTEGER,
|
||||
panel: () => TextValuePresentation,
|
||||
isHidden(_, props) {
|
||||
return !props || !props.model.source.hasResult(props.resultIndex) || !isResultSetDataSource(props.model.source);
|
||||
},
|
||||
});
|
||||
for (const presentation of PRESENTATIONS) {
|
||||
this.dataValuePanelService.add({
|
||||
key: presentation.key,
|
||||
options: {
|
||||
dataFormat: [ResultDataFormat.Resultset],
|
||||
isTextPresentation: true,
|
||||
},
|
||||
name: presentation.name,
|
||||
order: Number.MAX_SAFE_INTEGER,
|
||||
panel: () => TextValuePresentation,
|
||||
isHidden: (_, props) => {
|
||||
if (!props || !props.model.source.hasResult(props.resultIndex) || !isResultSetDataSource(props.model.source)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
this.textValuePresentationService.add({
|
||||
key: 'text/plain',
|
||||
name: 'data_viewer_presentation_value_text_plain_title',
|
||||
order: Number.MAX_SAFE_INTEGER,
|
||||
panel: () => React.Fragment,
|
||||
});
|
||||
this.textValuePresentationService.add({
|
||||
key: 'text/html',
|
||||
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),
|
||||
});
|
||||
if (!presentation.isHidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.textValuePresentationService.add({
|
||||
key: 'application/octet-stream;type=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: 'application/octet-stream;type=base64',
|
||||
name: 'data_viewer_presentation_value_text_base64_title',
|
||||
order: Number.MAX_SAFE_INTEGER,
|
||||
panel: () => React.Fragment,
|
||||
isHidden: (_, context) => !isBlobPresentationAvailable(context),
|
||||
});
|
||||
return presentation.isHidden({
|
||||
dataFormat: props.dataFormat,
|
||||
model: props.model,
|
||||
resultIndex: props.resultIndex,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-34
@@ -1,34 +0,0 @@
|
||||
/*
|
||||
* 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 { injectable } from '@cloudbeaver/core-di';
|
||||
import { type ITabInfo, type ITabInfoOptions, TabsContainer } from '@cloudbeaver/core-ui';
|
||||
|
||||
import { type IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel.js';
|
||||
import { ResultSetDataSource } from '../../ResultSet/ResultSetDataSource.js';
|
||||
import type { IDataValuePanelOptions, IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService.js';
|
||||
|
||||
export interface ITextValuePanelProps extends Omit<IDataValuePanelProps, 'model'> {
|
||||
model: IDatabaseDataModel<ResultSetDataSource>;
|
||||
}
|
||||
|
||||
@injectable()
|
||||
export class TextValuePresentationService {
|
||||
readonly tabs: TabsContainer<ITextValuePanelProps, IDataValuePanelOptions>;
|
||||
|
||||
constructor() {
|
||||
this.tabs = new TabsContainer('Value presentation');
|
||||
}
|
||||
|
||||
get(tabId: string): ITabInfo<ITextValuePanelProps, IDataValuePanelOptions> | undefined {
|
||||
return this.tabs.getTabInfo(tabId);
|
||||
}
|
||||
|
||||
add(tabInfo: ITabInfoOptions<ITextValuePanelProps, IDataValuePanelOptions>): void {
|
||||
this.tabs.add(tabInfo);
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -9,9 +9,9 @@ import { isResultSetBinaryValue } from '@dbeaver/result-set-api';
|
||||
import { isResultSetBlobValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetBlobValue.js';
|
||||
import { ResultSetSelectAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetSelectAction.js';
|
||||
import { ResultSetViewAction } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetViewAction.js';
|
||||
import { type ITextValuePanelProps } from './TextValuePresentationService.js';
|
||||
import type { IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService.js';
|
||||
|
||||
export function isBlobPresentationAvailable(context: ITextValuePanelProps | undefined): boolean {
|
||||
export function isBlobPresentationAvailable(context: IDataValuePanelProps | undefined): boolean {
|
||||
const source = context?.model.source;
|
||||
if (!context || !source?.hasResult(context.resultIndex)) {
|
||||
return true;
|
||||
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
.tabList {
|
||||
composes: theme-border-color-background theme-background-background from global;
|
||||
overflow: auto;
|
||||
border-radius: var(--theme-group-element-radius);
|
||||
}
|
||||
-18
@@ -1,18 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
.tab {
|
||||
composes: theme-ripple theme-background-surface theme-text-text-primary-on-light from global;
|
||||
}
|
||||
|
||||
.textValuePresentationTab.underline .tab {
|
||||
border-bottom: 0;
|
||||
|
||||
&:global([aria-selected='false']) {
|
||||
border-bottom: 0 !important;
|
||||
}
|
||||
}
|
||||
+17
-27
@@ -5,25 +5,23 @@
|
||||
* 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 { ITabInfo } from '@cloudbeaver/core-ui';
|
||||
import { isResultSetContentValue } from '@dbeaver/result-set-api';
|
||||
|
||||
import { isResultSetBlobValue } from '../../DatabaseDataModel/Actions/ResultSet/isResultSetBlobValue.js';
|
||||
import type { IResultSetValue } from '../../DatabaseDataModel/Actions/ResultSet/ResultSetFormatAction.js';
|
||||
import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel.js';
|
||||
import { ResultSetDataSource } from '../../ResultSet/ResultSetDataSource.js';
|
||||
import { TextValuePresentationService } from './TextValuePresentationService.js';
|
||||
import type { IDatabaseDataFormatAction } from '../../DatabaseDataModel/Actions/IDatabaseDataFormatAction.js';
|
||||
import type { IGridDataKey } from '../../DatabaseDataModel/Actions/Grid/IGridDataKey.js';
|
||||
import { isResultSetDataModel } from '../../ResultSet/isResultSetDataModel.js';
|
||||
import type { IDatabaseDataSource } from '../../DatabaseDataModel/IDatabaseDataSource.js';
|
||||
import type { IDataValuePanelOptions, IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService.js';
|
||||
|
||||
interface Args {
|
||||
resultIndex: number;
|
||||
model: IDatabaseDataModel<ResultSetDataSource>;
|
||||
dataFormat: ResultDataFormat | null;
|
||||
currentContentType: string | null;
|
||||
interface UseAutoContentTypeArgs {
|
||||
model: IDatabaseDataModel<IDatabaseDataSource>;
|
||||
elementKey?: IGridDataKey;
|
||||
formatAction: IDatabaseDataFormatAction;
|
||||
formatAction?: IDatabaseDataFormatAction;
|
||||
displayed: Array<ITabInfo<IDataValuePanelProps, IDataValuePanelOptions>>;
|
||||
}
|
||||
|
||||
const DEFAULT_CONTENT_TYPE = 'text/plain';
|
||||
@@ -55,24 +53,16 @@ function preprocessDefaultContentType(contentType: string | null | undefined) {
|
||||
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,
|
||||
});
|
||||
export function useAutoContentType({ displayed, formatAction, model, elementKey }: UseAutoContentTypeArgs): string | null {
|
||||
const isAllTextType = displayed.every(tab => tab.options?.isTextPresentation);
|
||||
const isAutoType = isAllTextType && formatAction && isResultSetDataModel(model);
|
||||
|
||||
if (!isAutoType) {
|
||||
return null;
|
||||
}
|
||||
|
||||
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;
|
||||
return preprocessDefaultContentType(contentValueType);
|
||||
}
|
||||
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2025 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 { ITabInfo } from '@cloudbeaver/core-ui';
|
||||
import type { IDatabaseDataModel } from '../../DatabaseDataModel/IDatabaseDataModel.js';
|
||||
import type { IDatabaseDataFormatAction } from '../../DatabaseDataModel/Actions/IDatabaseDataFormatAction.js';
|
||||
import type { IGridDataKey } from '../../DatabaseDataModel/Actions/Grid/IGridDataKey.js';
|
||||
import type { IDatabaseDataSource } from '../../DatabaseDataModel/IDatabaseDataSource.js';
|
||||
import type { IDataValuePanelOptions, IDataValuePanelProps } from '../../TableViewer/ValuePanel/DataValuePanelService.js';
|
||||
import { useAutoContentType } from './useAutoContentType.js';
|
||||
|
||||
interface UseContentTypeArgs {
|
||||
model: IDatabaseDataModel<IDatabaseDataSource>;
|
||||
currentContentType: string | null;
|
||||
elementKey?: IGridDataKey;
|
||||
formatAction?: IDatabaseDataFormatAction;
|
||||
displayed: Array<ITabInfo<IDataValuePanelProps, IDataValuePanelOptions>>;
|
||||
}
|
||||
|
||||
const DEFAULT_CONTENT_TYPE = 'text/plain';
|
||||
|
||||
export function useContentType({ model, formatAction, currentContentType, elementKey, displayed }: UseContentTypeArgs): string {
|
||||
const autoContentType = useAutoContentType({ displayed, formatAction, model, elementKey });
|
||||
if (displayed.length === 0) {
|
||||
return currentContentType || DEFAULT_CONTENT_TYPE;
|
||||
}
|
||||
|
||||
currentContentType = currentContentType || autoContentType;
|
||||
const hasCurrentTab = currentContentType && displayed.some(tab => tab.key === currentContentType);
|
||||
|
||||
if (!hasCurrentTab) {
|
||||
currentContentType = currentContentType || displayed[0]!.key;
|
||||
}
|
||||
|
||||
return currentContentType || DEFAULT_CONTENT_TYPE;
|
||||
}
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import { Bootstrap, conditional, Dependency, external, ModuleRegistry, proxy } from '@cloudbeaver/core-di';
|
||||
import { TextValuePresentationBootstrap } from './ValuePanelPresentation/TextValue/TextValuePresentationBootstrap.js';
|
||||
import { TextValuePresentationService } from './ValuePanelPresentation/TextValue/TextValuePresentationService.js';
|
||||
import { ImageValuePresentationBootstrap } from './ValuePanelPresentation/ImageValue/ImageValuePresentationBootstrap.js';
|
||||
import { BooleanValuePresentationBootstrap } from './ValuePanelPresentation/BooleanValue/BooleanValuePresentationBootstrap.js';
|
||||
import { TableViewerStorageService } from './TableViewer/TableViewerStorageService.js';
|
||||
@@ -72,7 +71,6 @@ export default ModuleRegistry.add({
|
||||
.addSingleton(Dependency, proxy(DataViewerSettingsService))
|
||||
.addSingleton(DataPresentationService)
|
||||
.addSingleton(TextValuePresentationBootstrap)
|
||||
.addSingleton(TextValuePresentationService)
|
||||
.addSingleton(ImageValuePresentationBootstrap)
|
||||
.addSingleton(BooleanValuePresentationBootstrap)
|
||||
.addSingleton(TableViewerStorageService)
|
||||
|
||||
+11
-3
@@ -2230,6 +2230,7 @@ __metadata:
|
||||
fast-deep-equal: "npm:^3"
|
||||
md5: "npm:^2"
|
||||
mobx: "npm:^6"
|
||||
modern-screenshot: "npm:^4"
|
||||
rimraf: "npm:^6"
|
||||
tslib: "npm:^2"
|
||||
typescript: "npm:^5"
|
||||
@@ -12354,13 +12355,13 @@ __metadata:
|
||||
linkType: hard
|
||||
|
||||
"happy-dom@npm:^20":
|
||||
version: 20.0.0
|
||||
resolution: "happy-dom@npm:20.0.0"
|
||||
version: 20.0.2
|
||||
resolution: "happy-dom@npm:20.0.2"
|
||||
dependencies:
|
||||
"@types/node": "npm:^20.0.0"
|
||||
"@types/whatwg-mimetype": "npm:^3.0.2"
|
||||
whatwg-mimetype: "npm:^3.0.0"
|
||||
checksum: 10c0/a205da48f3c98ae2b2bcb73c8f673fea66296cd2fc081065fcd5fa42b4411546c1b8f7c26ef2ca5adf0bdd1ced9aff4f429a3a4f5e6bce7724eec2747bef1a49
|
||||
checksum: 10c0/d4449e087cb7566fb493f9e6ee99221c64177732bf944ec3e2f464033107832c637e201e3e78e81a8b35871d41aa510806f3f1a2f18b924fc6120226f337a845
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -15160,6 +15161,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"modern-screenshot@npm:^4":
|
||||
version: 4.6.6
|
||||
resolution: "modern-screenshot@npm:4.6.6"
|
||||
checksum: 10c0/0632cdd89db1a7c0b2d8e78d3e05daca77a62d14ee172f6480da3b2de97fd37938c6ed71041271014a601fe2bcb470d01a78c10456ef7ac59a8674b82c64d0a1
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ms@npm:^2.1.3":
|
||||
version: 2.1.3
|
||||
resolution: "ms@npm:2.1.3"
|
||||
|
||||
Reference in New Issue
Block a user