CB-4005 add wrap mode (#2081)

* CB-4005 add wrap mode

* CB-4005 save wrap state in user settings

* CB-4005 add tooltips

* CB-4005 review fixes

---------

Co-authored-by: EvgeniaBzzz <139753579+EvgeniaBzzz@users.noreply.github.com>
This commit is contained in:
alex
2023-10-31 17:16:09 +03:00
committed by GitHub
co-authored by EvgeniaBzzz
parent b2eab058c9
commit 0f9e7b650c
21 changed files with 147 additions and 54 deletions
@@ -105,6 +105,7 @@ export default [
['ui_upload_files', 'Upload files'],
['ui_upload_files_duplicate_error', 'Files with the same name already exist'],
['ui_upload_file_fail', 'Failed to upload file'],
['ui_filter', 'Filter'],
['root_permission_denied', "You don't have permissions"],
['root_permission_no_permission', "You don't have permission for this action"],
@@ -89,6 +89,7 @@ export default [
['ui_upload_files', 'Upload files'],
['ui_upload_files_duplicate_error', 'Files with the same name already exist'],
['ui_upload_file_fail', 'Failed to upload file'],
['ui_filter', 'Filter'],
['root_permission_denied', 'Non hai i permessi'],
['app_root_session_expire_warning_title', 'La sessione sta per scadere'],
@@ -101,6 +101,7 @@ export default [
['ui_upload_files', 'Загрузить файлы'],
['ui_upload_files_duplicate_error', 'Файлы с такими именами уже существуют'],
['ui_upload_file_fail', 'Не удалось загрузить файл'],
['ui_filter', 'Фильтр'],
['root_permission_denied', 'Отказано в доступе'],
['root_permission_no_permission', 'У вас нет разрешения на это действие'],
@@ -102,6 +102,7 @@ export default [
['ui_upload_files', 'Upload files'],
['ui_upload_files_duplicate_error', 'Files with the same name already exist'],
['ui_upload_file_fail', 'Failed to upload file'],
['ui_filter', 'Filter'],
['root_permission_denied', '您没有权限'],
['root_permission_no_permission', '您没有权限执行此操作'],
@@ -35,29 +35,23 @@ export const MenuBarItem = observer<Props, HTMLButtonElement>(
const title = translate(rest.title);
return (
<button ref={ref} className={s(styles, { menuBarItem: true, hidden }, className)} {...rest} title={title} aria-label={title}>
<div className={s(styles, { menuBarItemBox: true }, className)}>
<div className={s(styles, { menuBarItemBox: true })}>
{loading ? (
<div className={s(styles, { menuBarItemIcon: true }, className)}>
<Loader className={s(styles, { loader: true }, className)} small fullSize />
<div className={s(styles, { menuBarItemIcon: true })}>
<Loader className={s(styles, { loader: true })} small fullSize />
</div>
) : (
icon && (
<div className={s(styles, { menuBarItemIcon: true }, className)}>
<Loader className={s(styles, { loader: true }, className)} suspense small fullSize>
{typeof icon === 'string' ? (
<IconOrImage className={s(styles, { iconOrImage: true }, className)} icon={icon} viewBox={viewBox} />
) : (
icon
)}
<div className={s(styles, { menuBarItemIcon: true })}>
<Loader className={s(styles, { loader: true })} suspense small fullSize>
{typeof icon === 'string' ? <IconOrImage className={s(styles, { iconOrImage: true })} icon={icon} viewBox={viewBox} /> : icon}
</Loader>
</div>
)
)}
{label && displayLabel && (
<div className={s(styles, { menuBarItemLabel: true }, className)}>{translate(label)}</div>
)}
{label && displayLabel && <div className={s(styles, { menuBarItemLabel: true })}>{translate(label)}</div>}
{displaySubmenuMark && (
<div className={s(styles, { menuBarItemMark: true }, className)}>
<div className={s(styles, { menuBarItemMark: true })}>
<Icon className={s(styles, { icon: true }, className)} name="angle" viewBox="0 0 15 8" />
</div>
)}
@@ -1,3 +1,10 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { createAction } from '@cloudbeaver/core-view';
export const ACTION_SHOW_OUTPUT_LOGS = createAction('action-show_output_logs', {
@@ -1,2 +1,9 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
export const OUTPUT_LOG_TYPES = ['Debug', 'Log', 'Info', 'Notice', 'Warning', 'Error'] as const;
export type IOutputLogType = (typeof OUTPUT_LOG_TYPES)[number];
@@ -7,4 +7,4 @@
*/
import { createMenu } from '@cloudbeaver/core-view';
export const OUTPUT_LOGS_FILTER_MENU = createMenu('output_logs_filter_menu', 'Output Logs');
export const OUTPUT_LOGS_FILTER_MENU = createMenu('output_logs_filter_menu', '', 'filter', 'ui_filter');
@@ -0,0 +1,10 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { createMenu } from '@cloudbeaver/core-view';
export const OUTPUT_LOGS_MENU = createMenu('output_logs_menu', 'Output Logs');
@@ -0,0 +1,10 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2020-2023 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { createMenu } from '@cloudbeaver/core-view';
export const OUTPUT_LOGS_SETTINGS_MENU = createMenu('output_logs_settings_menu', '', '/icons/settings_cog_sm.svg', 'ui_settings');
@@ -1,14 +0,0 @@
.contextMenu {
padding: 0;
height: 24px;
width: 24px;
display: flex;
box-sizing: border-box;
align-items: center;
justify-content: center;
& .icon {
width: 16px;
height: 100%;
}
}
@@ -0,0 +1,3 @@
.menuBar {
align-items: center;
}
@@ -8,23 +8,22 @@
import { observer } from 'mobx-react-lite';
import React, { useEffect } from 'react';
import { Icon, s, useS } from '@cloudbeaver/core-blocks';
import { ContextMenu } from '@cloudbeaver/core-ui';
import { s } from '@cloudbeaver/core-blocks';
import { MenuBar, MenuBarItemStyles } from '@cloudbeaver/core-ui';
import { useMenu } from '@cloudbeaver/core-view';
import { DATA_CONTEXT_SQL_EDITOR_STATE } from '../../DATA_CONTEXT_SQL_EDITOR_STATE';
import type { ISqlEditorTabState } from '../../ISqlEditorTabState';
import { OUTPUT_LOGS_FILTER_MENU } from './OUTPUT_LOGS_FILTER_MENU';
import style from './OutputLogTypesFilterMenu.m.css';
import { OUTPUT_LOGS_MENU } from './OUTPUT_LOGS_MENU';
import styles from './OutputLogsMenu.m.css';
interface Props {
sqlEditorTabState: ISqlEditorTabState;
}
export const OutputLogsFilterMenu = observer<Props>(function OutputLogTypesFilterMenu({ sqlEditorTabState }) {
const styles = useS(style);
export const OutputLogsMenu = observer<Props>(function OutputLogsMenu({ sqlEditorTabState }) {
const menu = useMenu({
menu: OUTPUT_LOGS_FILTER_MENU,
menu: OUTPUT_LOGS_MENU,
});
useEffect(() => {
@@ -32,8 +31,10 @@ export const OutputLogsFilterMenu = observer<Props>(function OutputLogTypesFilte
}, []);
return (
<ContextMenu className={s(styles, { contextMenu: true })} menu={menu} placement="bottom-end" modal>
<Icon className={s(styles, { icon: true })} name="filter" viewBox="0 0 16 16" />
</ContextMenu>
<MenuBar
menu={menu}
nestedMenuSettings={{ modal: true, placement: 'top-start' }}
className={s(styles, { menuBar: true }, MenuBarItemStyles.floating)}
/>
);
});
@@ -7,7 +7,7 @@
*/
import { observer } from 'mobx-react-lite';
import { Container, Group, s, useResource, useS } from '@cloudbeaver/core-blocks';
import { Container, useResource } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { EditorLoader } from '@cloudbeaver/plugin-codemirror6';
@@ -29,11 +29,19 @@ export const OutputLogsPanel = observer<Props>(function SqlOutputLogsPanel({ sql
const state = useOutputLogsPanelState(outputLogs, sqlEditorTabState);
return (
<Container className="theme-background-secondary" parent vertical gap dense>
<OutputLogsToolbar state={state} sqlEditorTabState={sqlEditorTabState}/>
<Group overflow box>
{data && <EditorLoader value={state.resultValue} foldGutter={false} highlightActiveLine={false} lineWrapping readonly />}
</Group>
<Container className="theme-background-secondary" overflow vertical noWrap dense parent gap>
<OutputLogsToolbar state={state} sqlEditorTabState={sqlEditorTabState} />
{data && (
<Container overflow>
<EditorLoader
value={state.resultValue}
foldGutter={false}
lineWrapping={outputLogsService.settings.wrapMode}
highlightActiveLine={false}
readonly
/>
</Container>
)}
</Container>
);
});
@@ -5,6 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { UserDataService } from '@cloudbeaver/core-authentication';
import { injectable } from '@cloudbeaver/core-di';
import type { ISqlEditorTabState } from '../../ISqlEditorTabState';
@@ -13,9 +14,19 @@ import { OUTPUT_LOG_TYPES } from './IOutputLogTypes';
import { OUTPUT_LOGS_TAB_ID } from './OUTPUT_LOGS_TAB_ID';
import type { IOutputLog } from './OutputLogsResource';
const OUTPUT_LOGS_KEY = 'output_logs';
interface ISettings {
wrapMode: boolean;
}
@injectable()
export class OutputLogsService {
constructor(private readonly sqlDataSourceService: SqlDataSourceService) {}
get settings() {
return this.userDataService.getUserData(OUTPUT_LOGS_KEY, getOutputLogsDefaultSettings);
}
constructor(private readonly sqlDataSourceService: SqlDataSourceService, private readonly userDataService: UserDataService) {}
async showOutputLogs(editorState: ISqlEditorTabState): Promise<void> {
this.createOutputLogsTab(editorState);
@@ -28,6 +39,10 @@ export class OutputLogsService {
}
}
toggleWrapMode() {
this.settings.wrapMode = !this.settings.wrapMode;
}
private createOutputLogsTab(state: ISqlEditorTabState) {
const order = Math.max(0, ...state.tabs.map(tab => tab.order + 1));
@@ -52,3 +67,9 @@ export class OutputLogsService {
return events.filter(event => event.contextId === dataSource?.executionContext?.id);
}
}
function getOutputLogsDefaultSettings(): ISettings {
return {
wrapMode: true,
};
}
@@ -11,8 +11,8 @@ import React from 'react';
import { Container, Icon, InputField, s, useS, useTranslate } from '@cloudbeaver/core-blocks';
import type { ISqlEditorTabState } from '../../ISqlEditorTabState';
import { OutputLogsMenu } from './OutputLogsMenu';
import style from './OutputLogsToolbar.m.css';
import { OutputLogsFilterMenu } from './OutputLogTypesFilterMenu';
import type { SqlOutputLogsPanelState } from './useOutputLogsPanelState';
interface Props {
@@ -20,7 +20,7 @@ interface Props {
sqlEditorTabState: ISqlEditorTabState;
}
export const OutputLogsToolbar = observer<Props>(function SqlOutputLogsToolbar({ state, sqlEditorTabState }) {
export const OutputLogsToolbar = observer<Props>(function OutputLogsToolbar({ state, sqlEditorTabState }) {
const styles = useS(style);
const translate = useTranslate();
@@ -38,7 +38,7 @@ export const OutputLogsToolbar = observer<Props>(function SqlOutputLogsToolbar({
onChange={value => state.setSearchValue(value.toString())}
/>
<Container keepSize>
<OutputLogsFilterMenu sqlEditorTabState={sqlEditorTabState} />
<OutputLogsMenu sqlEditorTabState={sqlEditorTabState} />
</Container>
</Container>
);
@@ -6,7 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { ActionService, KeyBindingService, MenuCheckboxItem, MenuService } from '@cloudbeaver/core-view';
import { ActionService, DATA_CONTEXT_MENU, KeyBindingService, MenuCheckboxItem, MenuService } from '@cloudbeaver/core-view';
import { ACTION_SQL_EDITOR_SHOW_OUTPUT } from '../../actions/ACTION_SQL_EDITOR_SHOW_OUTPUT';
import { KEY_BINDING_SQL_EDITOR_SHOW_OUTPUT } from '../../actions/bindings/KEY_BINDING_SQL_EDITOR_SHOW_OUTPUT';
@@ -17,6 +17,8 @@ import { SQL_EDITOR_ACTIONS_MENU } from '../../SqlEditor/SQL_EDITOR_ACTIONS_MENU
import { ACTION_SHOW_OUTPUT_LOGS } from './ACTION_SHOW_OUTPUT_LOGS';
import { OUTPUT_LOG_TYPES } from './IOutputLogTypes';
import { OUTPUT_LOGS_FILTER_MENU } from './OUTPUT_LOGS_FILTER_MENU';
import { OUTPUT_LOGS_MENU } from './OUTPUT_LOGS_MENU';
import { OUTPUT_LOGS_SETTINGS_MENU } from './OUTPUT_LOGS_SETTINGS_MENU';
import { OutputLogsService } from './OutputLogsService';
@injectable()
@@ -32,9 +34,16 @@ export class OutputMenuBootstrap extends Bootstrap {
}
register(): void | Promise<void> {
this.menuService.addCreator({
isApplicable: context => context.get(DATA_CONTEXT_MENU) === OUTPUT_LOGS_MENU,
getItems(context, items) {
return [...items, OUTPUT_LOGS_FILTER_MENU, OUTPUT_LOGS_SETTINGS_MENU];
},
});
this.menuService.addCreator({
menus: [OUTPUT_LOGS_FILTER_MENU],
getItems: context => {
getItems: (context, items) => {
const state = context.tryGet(DATA_CONTEXT_SQL_EDITOR_STATE);
if (!state) {
@@ -42,10 +51,10 @@ export class OutputMenuBootstrap extends Bootstrap {
}
const outputLogsTabState = state?.outputLogsTab;
const items = [];
const result = [];
for (const logType of OUTPUT_LOG_TYPES) {
items.push(
result.push(
new MenuCheckboxItem(
{
id: logType,
@@ -70,7 +79,36 @@ export class OutputMenuBootstrap extends Bootstrap {
);
}
return items;
return [...items, ...result];
},
});
this.menuService.addCreator({
menus: [OUTPUT_LOGS_SETTINGS_MENU],
getItems: (context, items) => {
const state = context.tryGet(DATA_CONTEXT_SQL_EDITOR_STATE);
if (!state) {
return [];
}
const wrapMode = new MenuCheckboxItem(
{
id: 'wrap-mode',
label: 'sql_editor_output_logs_wrap_mode',
tooltip: 'sql_editor_output_logs_wrap_mode',
},
{
onSelect: () => {
this.outputLogsService.toggleWrapMode();
},
},
{
isChecked: () => this.outputLogsService.settings.wrapMode,
},
);
return [...items, wrapMode];
},
});
@@ -10,6 +10,7 @@ export default [
['sql_editor_output_logs_button_tooltip', 'Show server output (Shift + Ctrl + O)'],
['sql_editor_output_logs_tab_title', 'Output'],
['sql_editor_output_logs_input_placeholder', 'Enter a part of a message to search for here'],
['sql_editor_output_logs_wrap_mode', 'Wrap mode'],
['sql_editor_sql_execution_button_tooltip', 'Execute SQL Statement (Ctrl + Enter)'],
['sql_editor_sql_execution_new_tab_button_tooltip', 'Execute SQL Statement in new tab (Ctrl + \\)(Shift + Ctrl + Enter)'],
['sql_editor_sql_execution_script_button_tooltip', 'Execute SQL Script (Alt + X)'],
@@ -10,6 +10,7 @@ export default [
['sql_editor_output_logs_button_tooltip', 'Show server output (Shift + Ctrl + O)'],
['sql_editor_output_logs_tab_title', 'Output'],
['sql_editor_output_logs_input_placeholder', 'Enter a part of a message to search for here'],
['sql_editor_output_logs_wrap_mode', 'Wrap mode'],
['sql_editor_sql_execution_button_tooltip', "Esegui l'istruzione SQL (Ctrl + Enter)"],
['sql_editor_sql_execution_new_tab_button_tooltip', "Esegui l'istruzione SQL in una nuova tab (Ctrl + \\)(Shift + Ctrl + Enter)"],
['sql_editor_sql_execution_script_button_tooltip', 'Esegui lo script SQL (Alt + X)'],
@@ -10,6 +10,7 @@ export default [
['sql_editor_output_logs_button_tooltip', 'Показать вывод сервера (Shift + Ctrl + O)'],
['sql_editor_output_logs_tab_title', 'Вывод логов'],
['sql_editor_output_logs_input_placeholder', 'Введите часть сообщения для поиска'],
['sql_editor_output_logs_wrap_mode', 'Перенос строк'],
['sql_editor_sql_execution_button_tooltip', 'Выполнить SQL Выражение (Ctrl + Enter)'],
['sql_editor_sql_execution_new_tab_button_tooltip', 'Выполнить SQL Выражение в новой вкладке (Ctrl + \\)(Shift + Ctrl + Enter)'],
['sql_editor_sql_execution_script_button_tooltip', 'Исполнить SQL Скрипт (Alt + X)'],
@@ -10,6 +10,7 @@ export default [
['sql_editor_output_logs_button_tooltip', 'Show server output (Shift + Ctrl + O)'],
['sql_editor_output_logs_tab_title', 'Output'],
['sql_editor_output_logs_input_placeholder', 'Enter a part of a message to search for here'],
['sql_editor_output_logs_wrap_mode', 'Wrap mode'],
['sql_editor_sql_execution_button_tooltip', '执行SQL语句(Ctrl + Enter)'],
['sql_editor_sql_execution_new_tab_button_tooltip', '在新选项卡中执行SQL语句(Ctrl + \\)(Shift + Ctrl + Enter)'],
['sql_editor_sql_execution_script_button_tooltip', '执行SQL脚本(Alt + X)'],