From 4842cba903ffcbc8a38ea6b2d774d6b99cd395ff Mon Sep 17 00:00:00 2001 From: sergeyteleshev Date: Tue, 25 Jun 2024 17:17:40 +0200 Subject: [PATCH] CB-5181 Display User Notification Pop-up When Copy/Paste Functionality is Disabled by Administrator (#2730) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * CB-5181 adds copy event handler for data viewer (cells, value panel) * CB-5181 makes edit/copy settings independent from each other * CB-5181 adds disable copy case for read state JsonLine (pre) * CB-5181 changes message text for popup * CB-5181 changes message text for popup 2 * СB-5181 fixes rus locale for copy message --------- Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com> --- .../src/IReactCodemirrorProps.ts | 2 +- .../src/ReactCodemirror.tsx | 8 ++--- .../src/DataGrid/useGridSelectedCellsCopy.ts | 4 +++ .../src/DataViewerService.ts | 6 +--- .../TextValue/TextValueEditor.tsx | 7 ++-- .../packages/plugin-data-viewer/src/index.ts | 1 + .../plugin-data-viewer/src/locales/de.ts | 2 ++ .../plugin-data-viewer/src/locales/en.ts | 2 ++ .../plugin-data-viewer/src/locales/fr.ts | 33 +++++++++++-------- .../plugin-data-viewer/src/locales/it.ts | 2 ++ .../plugin-data-viewer/src/locales/ru.ts | 2 ++ .../plugin-data-viewer/src/locales/zh.ts | 2 ++ .../src/useDataViewerCopyHandler.ts | 31 +++++++++++++++++ 13 files changed, 73 insertions(+), 29 deletions(-) create mode 100644 webapp/packages/plugin-data-viewer/src/useDataViewerCopyHandler.ts diff --git a/webapp/packages/plugin-codemirror6/src/IReactCodemirrorProps.ts b/webapp/packages/plugin-codemirror6/src/IReactCodemirrorProps.ts index 519d3aed3b..73dc02c305 100644 --- a/webapp/packages/plugin-codemirror6/src/IReactCodemirrorProps.ts +++ b/webapp/packages/plugin-codemirror6/src/IReactCodemirrorProps.ts @@ -22,7 +22,7 @@ export interface IReactCodeMirrorProps extends React.PropsWithChildren { getValue?: () => string; extensions?: Map; readonly?: boolean; - disableCopy?: boolean; + copyEventHandler?: (event: ClipboardEvent) => boolean; autoFocus?: boolean; onChange?: (value: string, update: ViewUpdate) => void; onCursorChange?: (selection: SelectionRange, update: ViewUpdate) => void; diff --git a/webapp/packages/plugin-codemirror6/src/ReactCodemirror.tsx b/webapp/packages/plugin-codemirror6/src/ReactCodemirror.tsx index 985d973804..d31afbecaa 100644 --- a/webapp/packages/plugin-codemirror6/src/ReactCodemirror.tsx +++ b/webapp/packages/plugin-codemirror6/src/ReactCodemirror.tsx @@ -31,7 +31,7 @@ export const ReactCodemirror = observer( incomingValue, extensions = new Map(), readonly, - disableCopy, + copyEventHandler, autoFocus, onChange, onCursorChange, @@ -45,11 +45,9 @@ export const ReactCodemirror = observer( const eventHandlers = useMemo( () => EditorView.domEventHandlers({ - copy() { - return disableCopy; - }, + copy: copyEventHandler, }), - [disableCopy], + [copyEventHandler], ); extensions = useCodemirrorExtensions(extensions, [readOnlyFacet, eventHandlers]); const [container, setContainer] = useState(null); diff --git a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useGridSelectedCellsCopy.ts b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useGridSelectedCellsCopy.ts index a9f0949395..d3028b6c45 100644 --- a/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useGridSelectedCellsCopy.ts +++ b/webapp/packages/plugin-data-spreadsheet-new/src/DataGrid/useGridSelectedCellsCopy.ts @@ -17,6 +17,7 @@ import { IResultSetElementKey, ResultSetDataKeysUtils, ResultSetSelectAction, + useDataViewerCopyHandler, } from '@cloudbeaver/plugin-data-viewer'; import type { IDataGridSelectionContext } from './DataGridSelection/DataGridSelectionContext'; @@ -71,6 +72,7 @@ export function useGridSelectedCellsCopy( ) { const dataViewerService = useService(DataViewerService); const props = useObjectRef({ tableData, selectionContext, resultSetSelectAction }); + const copyEventHandler = useDataViewerCopyHandler(); const onKeydownHandler = useCallback((event: React.KeyboardEvent) => { if ((event.ctrlKey || event.metaKey) && event.nativeEvent.code === EVENT_KEY_CODE.C) { @@ -98,6 +100,8 @@ export function useGridSelectedCellsCopy( copyToClipboard(value); } } + + copyEventHandler(event); } }, []); diff --git a/webapp/packages/plugin-data-viewer/src/DataViewerService.ts b/webapp/packages/plugin-data-viewer/src/DataViewerService.ts index 1eb2cc78b3..0a93eeb119 100644 --- a/webapp/packages/plugin-data-viewer/src/DataViewerService.ts +++ b/webapp/packages/plugin-data-viewer/src/DataViewerService.ts @@ -14,11 +14,7 @@ import { DataViewerSettingsService } from './DataViewerSettingsService'; @injectable() export class DataViewerService { get canCopyData() { - if (this.sessionPermissionsResource.has(EAdminPermission.admin)) { - return true; - } - - return !this.dataViewerSettingsService.disableCopyData; + return this.sessionPermissionsResource.has(EAdminPermission.admin) || !this.dataViewerSettingsService.disableCopyData; } constructor( diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValueEditor.tsx b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValueEditor.tsx index dd5ab7d803..06b90a6824 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValueEditor.tsx +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValueEditor.tsx @@ -8,10 +8,9 @@ import { observer } from 'mobx-react-lite'; import { useMemo } from 'react'; -import { useService } from '@cloudbeaver/core-di'; import { EditorLoader, useCodemirrorExtensions } from '@cloudbeaver/plugin-codemirror6'; -import { DataViewerService } from '../../DataViewerService'; +import { useDataViewerCopyHandler } from '../../useDataViewerCopyHandler'; import { getTypeExtension } from './getTypeExtension'; interface Props { @@ -27,7 +26,7 @@ export const TextValueEditor = observer(function TextValueEditor({ conten const typeExtension = useMemo(() => getTypeExtension(contentType!) ?? [], [contentType]); const extensions = useCodemirrorExtensions(undefined, typeExtension); - const dataViewerService = useService(DataViewerService); + const copyEventHandler = useDataViewerCopyHandler(); return ( (function TextValueEditor({ conten lineWrapping={lineWrapping} readonly={readonly} extensions={extensions} - disableCopy={!dataViewerService.canCopyData} + copyEventHandler={copyEventHandler} onChange={onChange} /> ); diff --git a/webapp/packages/plugin-data-viewer/src/index.ts b/webapp/packages/plugin-data-viewer/src/index.ts index 858fd6cf94..0d86dd135c 100644 --- a/webapp/packages/plugin-data-viewer/src/index.ts +++ b/webapp/packages/plugin-data-viewer/src/index.ts @@ -85,5 +85,6 @@ export * from './ResultSetDataSource'; export * from './DataPresentationService'; export * from './DataViewerDataChangeConfirmationService'; export * from './ValuePanelPresentation/BooleanValue/isBooleanValuePresentationAvailable'; +export * from './useDataViewerCopyHandler'; export * from './DataViewerSettingsService'; export * from './DATA_EDITOR_SETTINGS_GROUP'; diff --git a/webapp/packages/plugin-data-viewer/src/locales/de.ts b/webapp/packages/plugin-data-viewer/src/locales/de.ts index fd84a940c7..4815fcebe4 100644 --- a/webapp/packages/plugin-data-viewer/src/locales/de.ts +++ b/webapp/packages/plugin-data-viewer/src/locales/de.ts @@ -34,6 +34,8 @@ export default [ ['data_viewer_script_preview_error_title', 'Kann das Skript nicht bekommen'], ['data_viewer_total_count_tooltip', 'Totalzahl erhalten'], ['data_viewer_model_not_loaded', 'Das Tabellenmodell ist nicht geladen'], + ['data_viewer_copy_not_allowed', 'An ability to copy data is disabled'], + ['data_viewer_copy_not_allowed_message', 'If this was unexpected, contact the administrator'], ['settings_data_editor', 'Dateneditor'], ['settings_data_editor_disable_edit_name', 'Bearbeiten deaktivieren'], ['settings_data_editor_disable_edit_description', 'Deaktivieren Sie die Bearbeitung von Daten in Data Viewer für Nicht-Admin-Benutzer'], diff --git a/webapp/packages/plugin-data-viewer/src/locales/en.ts b/webapp/packages/plugin-data-viewer/src/locales/en.ts index 67e4d9a449..0cdc7815a6 100644 --- a/webapp/packages/plugin-data-viewer/src/locales/en.ts +++ b/webapp/packages/plugin-data-viewer/src/locales/en.ts @@ -57,6 +57,8 @@ export default [ ['data_viewer_total_count_canceled_message', 'Statement was cancelled due to user request'], ['data_viewer_total_count_failed', 'Failed to get total count'], ['data_viewer_model_not_loaded', 'Table model is not loaded'], + ['data_viewer_copy_not_allowed', 'An ability to copy data is disabled'], + ['data_viewer_copy_not_allowed_message', 'If this was unexpected, contact the administrator'], ['settings_data_editor', 'Data Editor'], ['settings_data_editor_disable_edit_name', 'Disable Edit'], ['settings_data_editor_disable_edit_description', 'Disable editing of data in Data Viewer for non-admin users'], diff --git a/webapp/packages/plugin-data-viewer/src/locales/fr.ts b/webapp/packages/plugin-data-viewer/src/locales/fr.ts index c618ff5eff..571d40f4bf 100644 --- a/webapp/packages/plugin-data-viewer/src/locales/fr.ts +++ b/webapp/packages/plugin-data-viewer/src/locales/fr.ts @@ -8,7 +8,7 @@ export default [ ['plugin_data_viewer_data_viewer_settings_group', 'Affichage des données'], ['table_header_sql_expression', 'Entrez une expression SQL pour filtrer les résultats, par exemple column_name=10'], - ['table_header_sql_expression_not_supported', 'Le filtrage des données n\'est pas supporté'], + ['table_header_sql_expression_not_supported', "Le filtrage des données n'est pas supporté"], ['data_viewer_tab_title', 'Données'], ['data_viewer_value_edit', 'Modifier'], ['data_viewer_value_apply', 'Appliquer'], @@ -20,16 +20,16 @@ export default [ ['data_viewer_statistics_updated_rows', 'Lignes mises à jour :'], ['data_viewer_action_refresh', 'Actualiser'], ['data_viewer_action_auto_refresh', 'Actualisation automatique'], - ['data_viewer_action_auto_refresh_stop', 'Arrêter l\'actualisation automatique'], + ['data_viewer_action_auto_refresh_stop', "Arrêter l'actualisation automatique"], ['data_viewer_action_edit_delete', 'Supprimer la sélection'], ['data_viewer_action_edit_add', 'Ajouter'], ['data_viewer_action_edit_add_copy', 'Dupliquer'], ['data_viewer_action_edit_revert', 'Annuler la sélection'], ['data_viewer_result_edited_title', 'Enregistrer les modifications'], ['data_viewer_result_edited_message', 'Le jeu de résultats a été modifié. Voulez-vous enregistrer les modifications dans la base de données ?'], - ['data_viewer_data_save_error_title', 'Erreur lors de l\'enregistrement des modifications'], - ['data_viewer_auto_refresh_settings', 'Paramètres d\'actualisation automatique'], - ['data_viewer_auto_refresh_settings_stop_on_error', 'Arrêter en cas d\'erreur'], + ['data_viewer_data_save_error_title', "Erreur lors de l'enregistrement des modifications"], + ['data_viewer_auto_refresh_settings', "Paramètres d'actualisation automatique"], + ['data_viewer_auto_refresh_settings_stop_on_error', "Arrêter en cas d'erreur"], ['data_viewer_presentation_value_title', 'Valeur'], ['data_viewer_presentation_value_text_line_wrapping_wrap', 'Retour à la ligne'], ['data_viewer_presentation_value_text_line_wrapping_no_wrap', 'Ne pas revenir à la ligne'], @@ -42,25 +42,30 @@ export default [ ['data_viewer_presentation_value_text_base64_title', 'Base64'], ['data_viewer_presentation_value_image_title', 'Image'], ['data_viewer_presentation_value_image_fit', 'Ajuster à la fenêtre'], - ['data_viewer_presentation_value_image_original_size', 'Taille d\'origine'], - ['data_viewer_presentation_value_boolean_placeholder', 'Impossible d\'afficher la valeur actuelle en tant que booléen'], + ['data_viewer_presentation_value_image_original_size', "Taille d'origine"], + ['data_viewer_presentation_value_boolean_placeholder', "Impossible d'afficher la valeur actuelle en tant que booléen"], ['data_viewer_presentation_value_content_truncated_placeholder', 'La valeur a été tronquée en raison de la'], ['data_viewer_presentation_value_content_download_error', 'Échec du téléchargement'], ['data_viewer_presentation_value_content_paste_error', 'Impossible de charger le texte complet'], ['data_viewer_script_preview', 'Script'], ['data_viewer_script_preview_dialog_title', 'Aperçu des modifications'], - ['data_viewer_script_preview_error_title', 'Impossible d\'obtenir le script'], + ['data_viewer_script_preview_error_title', "Impossible d'obtenir le script"], ['data_viewer_refresh_result_set', 'Actualiser le jeu de résultats'], ['data_viewer_total_count_tooltip', 'Obtenir le compte total'], ['data_viewer_total_count_canceled_title', 'Total annulé'], - ['data_viewer_total_count_canceled_message', 'La déclaration a été annulée à la demande de l\'utilisateur'], - ['data_viewer_total_count_failed', 'Échec de l\'obtention du compte total'], - ['data_viewer_model_not_loaded', 'Le modèle de la table n\'est pas chargé'], + ['data_viewer_total_count_canceled_message', "La déclaration a été annulée à la demande de l'utilisateur"], + ['data_viewer_total_count_failed', "Échec de l'obtention du compte total"], + ['data_viewer_model_not_loaded', "Le modèle de la table n'est pas chargé"], + ['data_viewer_copy_not_allowed', 'An ability to copy data is disabled'], + ['data_viewer_copy_not_allowed_message', 'If this was unexpected, contact the administrator'], ['settings_data_editor', 'Éditeur de données'], - ['settings_data_editor_disable_edit_name', 'Désactiver l\'édition'], - ['settings_data_editor_disable_edit_description', 'Désactiver l\'édition des données dans le Data Viewer pour les utilisateurs non administrateurs'], + ['settings_data_editor_disable_edit_name', "Désactiver l'édition"], + ['settings_data_editor_disable_edit_description', "Désactiver l'édition des données dans le Data Viewer pour les utilisateurs non administrateurs"], ['settings_data_editor_disable_data_copy_name', 'Désactiver la copie'], - ['settings_data_editor_disable_data_copy_description', 'Désactiver la copie des données dans le Data Viewer pour les utilisateurs non administrateurs'], + [ + 'settings_data_editor_disable_data_copy_description', + 'Désactiver la copie des données dans le Data Viewer pour les utilisateurs non administrateurs', + ], ['settings_data_editor_fetch_min_name', 'Taille de récupération minimale'], ['settings_data_editor_fetch_min_description', 'Nombre minimal de lignes à récupérer'], ['settings_data_editor_fetch_max_name', 'Taille de récupération maximale'], diff --git a/webapp/packages/plugin-data-viewer/src/locales/it.ts b/webapp/packages/plugin-data-viewer/src/locales/it.ts index 8a8272b794..47ba104bf3 100644 --- a/webapp/packages/plugin-data-viewer/src/locales/it.ts +++ b/webapp/packages/plugin-data-viewer/src/locales/it.ts @@ -50,6 +50,8 @@ export default [ ['data_viewer_total_count_canceled_message', 'Statement was cancelled due to user request'], ['data_viewer_total_count_failed', 'Failed to get total count'], ['data_viewer_model_not_loaded', 'Table model is not loaded'], + ['data_viewer_copy_not_allowed', 'An ability to copy data is disabled'], + ['data_viewer_copy_not_allowed_message', 'If this was unexpected, contact the administrator'], ['settings_data_editor', 'Data Editor'], ['settings_data_editor_disable_edit_name', 'Disable Edit'], ['settings_data_editor_disable_edit_description', 'Disable editing of data in Data Viewer for non-admin users'], diff --git a/webapp/packages/plugin-data-viewer/src/locales/ru.ts b/webapp/packages/plugin-data-viewer/src/locales/ru.ts index c06c0acb5b..8aa7cad446 100644 --- a/webapp/packages/plugin-data-viewer/src/locales/ru.ts +++ b/webapp/packages/plugin-data-viewer/src/locales/ru.ts @@ -51,6 +51,8 @@ export default [ ['data_viewer_total_count_canceled_message', 'Запрос был отменен пользователем'], ['data_viewer_total_count_failed', 'Не удалось получить количество записей'], ['data_viewer_model_not_loaded', 'Не удалось загрузить модель таблицы'], + ['data_viewer_copy_not_allowed', 'Копирование отключено'], + ['data_viewer_copy_not_allowed_message', 'Если это не ожидаемо, обратитесь к администратору'], ['settings_data_editor', 'Редактор данных'], ['settings_data_editor_disable_edit_name', 'Отключить редактирование'], ['settings_data_editor_disable_edit_description', 'Отключить редактирование данных для пользователей без прав администратора'], diff --git a/webapp/packages/plugin-data-viewer/src/locales/zh.ts b/webapp/packages/plugin-data-viewer/src/locales/zh.ts index b11bcbe296..3e81a500b5 100644 --- a/webapp/packages/plugin-data-viewer/src/locales/zh.ts +++ b/webapp/packages/plugin-data-viewer/src/locales/zh.ts @@ -57,6 +57,8 @@ export default [ ['data_viewer_total_count_canceled_title', 'Total count canceled'], ['data_viewer_total_count_canceled_message', 'Statement was cancelled due to user request'], ['data_viewer_model_not_loaded', 'Table model is not loaded'], + ['data_viewer_copy_not_allowed', 'An ability to copy data is disabled'], + ['data_viewer_copy_not_allowed_message', 'If this was unexpected, contact the administrator'], ['settings_data_editor', 'Data Editor'], ['settings_data_editor_disable_edit_name', 'Disable Edit'], ['settings_data_editor_disable_edit_description', 'Disable editing of data in Data Viewer for non-admin users'], diff --git a/webapp/packages/plugin-data-viewer/src/useDataViewerCopyHandler.ts b/webapp/packages/plugin-data-viewer/src/useDataViewerCopyHandler.ts new file mode 100644 index 0000000000..29644d0e3e --- /dev/null +++ b/webapp/packages/plugin-data-viewer/src/useDataViewerCopyHandler.ts @@ -0,0 +1,31 @@ +/* + * 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 React from 'react'; + +import { useService } from '@cloudbeaver/core-di'; +import { NotificationService } from '@cloudbeaver/core-events'; + +import { DataViewerService } from './DataViewerService'; + +export function useDataViewerCopyHandler() { + const notificationService = useService(NotificationService); + const dataViewerService = useService(DataViewerService); + + return function (event?: ClipboardEvent | React.KeyboardEvent | React.ClipboardEvent) { + if (!dataViewerService.canCopyData) { + event?.preventDefault(); + + notificationService.logError({ + title: 'data_viewer_copy_not_allowed', + message: 'data_viewer_copy_not_allowed_message', + }); + } + + return !dataViewerService.canCopyData; + }; +}