mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-10-29 18:03:45 +08:00
CB-3703 save product configuration from ui (#2334)
* CB-4560 api for saving product configuration * CB-4560 save product config in te * CB-4560 move update product config to mutations * CB-3703 feat: extend base components * CB-3703 feat: settings editor * CB-3703 chore: review fixes * CB-3703 chore: pr review * CB-3703 feat: update server configuration icon * CB-4560 add events * CB-3703 fix: localization * CB-3703 chore: localization --------- Co-authored-by: Alexey <wrouds@gmail.com> Co-authored-by: Evgenia Bezborodova <139753579+EvgeniaBzzz@users.noreply.github.com>
This commit is contained in:
co-authored by
Alexey
Evgenia Bezborodova
parent
11adc7d3d6
commit
fdde4348a2
+1
-5
@@ -5,9 +5,5 @@
|
||||
* 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_OPEN_APP_SETTINGS = createAction('open-app-settings', {
|
||||
label: 'settings_panel',
|
||||
tooltip: 'settings_panel',
|
||||
});
|
||||
export const SETTINGS_GROUP_ID_PREFIX = 'settings-group-';
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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 { observer } from 'mobx-react-lite';
|
||||
|
||||
import { Combobox, FieldCheckbox, InputField, Textarea, useCustomInputValidation, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { ESettingsValueType, type ISettingDescriptionWithScope, PluginManagerService } from '@cloudbeaver/core-plugin';
|
||||
import type { ISettingsSource } from '@cloudbeaver/core-settings';
|
||||
import { schemaValidationError } from '@cloudbeaver/core-utils';
|
||||
|
||||
interface Props {
|
||||
source: ISettingsSource;
|
||||
setting: ISettingDescriptionWithScope;
|
||||
}
|
||||
|
||||
export const Setting = observer<Props>(function Setting({ source, setting }) {
|
||||
const translate = useTranslate();
|
||||
const pluginManagerService = useService(PluginManagerService);
|
||||
|
||||
const defaultValue = pluginManagerService.getSettings(setting.scope)?.getDefaultValue(setting.key);
|
||||
|
||||
const key = `${setting.scope}.${setting.key}`;
|
||||
const value = source.getValue(key) ?? defaultValue ?? '';
|
||||
const name = translate(setting.name);
|
||||
const description = translate(setting.description);
|
||||
const disabled = false;
|
||||
const readOnly = false;
|
||||
const customValidation = useCustomInputValidation(value => {
|
||||
const result = setting.schema.safeParse(value);
|
||||
|
||||
if (result.success) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return schemaValidationError(result.error).toString();
|
||||
});
|
||||
|
||||
function handleChange(value: any) {
|
||||
source.setValue(key, value);
|
||||
}
|
||||
|
||||
if (setting.type === ESettingsValueType.Checkbox) {
|
||||
return (
|
||||
<FieldCheckbox
|
||||
id={key}
|
||||
checked={value}
|
||||
label={name}
|
||||
title={name}
|
||||
caption={description}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
groupGap
|
||||
onChange={handleChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (setting.type === ESettingsValueType.Select) {
|
||||
const options = setting.options?.map(option => ({ ...option, name: translate(option.name) })) || [];
|
||||
return (
|
||||
<Combobox
|
||||
id={key}
|
||||
items={options}
|
||||
keySelector={value => value.id}
|
||||
valueSelector={value => value.name}
|
||||
value={value}
|
||||
title={description}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
tiny
|
||||
onSelect={handleChange}
|
||||
>
|
||||
{name}
|
||||
</Combobox>
|
||||
);
|
||||
}
|
||||
|
||||
if (setting.type === ESettingsValueType.Textarea) {
|
||||
return (
|
||||
<Textarea id={key} title={value} labelTooltip={description} value={value} disabled={disabled} readOnly={readOnly} onChange={handleChange}>
|
||||
{name}
|
||||
</Textarea>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<InputField
|
||||
ref={customValidation}
|
||||
id={key}
|
||||
type="text"
|
||||
title={value}
|
||||
labelTooltip={description}
|
||||
value={value}
|
||||
description={description}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
small
|
||||
onChange={handleChange}
|
||||
>
|
||||
{name}
|
||||
</InputField>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* 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 { observer } from 'mobx-react-lite';
|
||||
import React from 'react';
|
||||
|
||||
import { Container, Form, Group, TextPlaceholder, useForm, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { ROOT_SETTINGS_GROUP } from '@cloudbeaver/core-plugin';
|
||||
import type { ISettingsSource } from '@cloudbeaver/core-settings';
|
||||
import { useTreeData } from '@cloudbeaver/plugin-navigation-tree';
|
||||
|
||||
import { SettingsGroups } from './SettingsGroups/SettingsGroups';
|
||||
import { SettingsList } from './SettingsList';
|
||||
import { useSettings } from './useSettings';
|
||||
|
||||
export interface ISettingsProps {
|
||||
source: ISettingsSource;
|
||||
}
|
||||
|
||||
export const Settings = observer<ISettingsProps>(function Settings({ source }) {
|
||||
const translate = useTranslate();
|
||||
const settings = useSettings();
|
||||
|
||||
const treeData = useTreeData({
|
||||
rootId: ROOT_SETTINGS_GROUP.id,
|
||||
getNode(id) {
|
||||
const group = ROOT_SETTINGS_GROUP.get(id);
|
||||
|
||||
return {
|
||||
name: translate(group!.name),
|
||||
leaf: !group?.subGroups.length,
|
||||
};
|
||||
},
|
||||
getChildren(id) {
|
||||
return (ROOT_SETTINGS_GROUP.get(id)?.subGroups || [])
|
||||
.filter(group => settings.groups.has(group))
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map(group => group.id);
|
||||
},
|
||||
load() {
|
||||
return Promise.resolve();
|
||||
},
|
||||
});
|
||||
|
||||
if (settings.settings.size === 0) {
|
||||
return <TextPlaceholder>{translate('plugin_settings_panel_empty')}</TextPlaceholder>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Container gap overflow noWrap>
|
||||
<Group style={{ height: '100%', minWidth: '240px' }} box keepSize overflow hidden>
|
||||
<SettingsGroups treeData={treeData} />
|
||||
</Group>
|
||||
<Container style={{ height: '100%' }} fill>
|
||||
<SettingsList treeData={treeData} source={source} settings={settings.settings} />
|
||||
</Container>
|
||||
</Container>
|
||||
);
|
||||
});
|
||||
@@ -1,54 +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 { observer } from 'mobx-react-lite';
|
||||
import React from 'react';
|
||||
|
||||
import { Group, GroupTitle, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { PluginManagerService } from '@cloudbeaver/core-plugin';
|
||||
import { SettingsGroupType, SettingsManagerService, SettingsScopeType } from '@cloudbeaver/core-settings';
|
||||
|
||||
import { SettingsInfoForm } from './SettingsInfoForm';
|
||||
|
||||
interface Props {
|
||||
group: SettingsGroupType;
|
||||
}
|
||||
|
||||
export const SettingsGroup = observer<Props>(function SettingsGroup({ group }) {
|
||||
const settingsManagerService = useService(SettingsManagerService);
|
||||
const pluginManagerService = useService(PluginManagerService);
|
||||
const translate = useTranslate();
|
||||
|
||||
function getValue(scope: string, scopeType: SettingsScopeType, key: string) {
|
||||
const settings = pluginManagerService.getSettings(scope, scopeType);
|
||||
return settings?.getValue(key);
|
||||
}
|
||||
|
||||
const settings = settingsManagerService.settings
|
||||
.filter(settingsItem => settingsItem.groupId === group.id)
|
||||
.map(settingsItem => ({
|
||||
...settingsItem,
|
||||
value: getValue(settingsItem.scope, settingsItem.scopeType, settingsItem.key),
|
||||
name: translate(settingsItem.name),
|
||||
description: translate(settingsItem.description),
|
||||
options: settingsItem.options?.map(option => ({ ...option, name: translate(option.name) })),
|
||||
}));
|
||||
|
||||
if (settings.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<Group gap vertical>
|
||||
<GroupTitle keepSize large>
|
||||
{translate(group.name)}
|
||||
</GroupTitle>
|
||||
<SettingsInfoForm fields={settings} readOnly />
|
||||
</Group>
|
||||
);
|
||||
});
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 { observer } from 'mobx-react-lite';
|
||||
import { forwardRef, useContext } from 'react';
|
||||
|
||||
import { TreeNodeControl, TreeNodeExpand, TreeNodeName } from '@cloudbeaver/core-blocks';
|
||||
import { type NodeControlComponent, TreeContext, TreeDataContext } from '@cloudbeaver/plugin-navigation-tree';
|
||||
|
||||
export const GroupNodeControl: NodeControlComponent = observer(
|
||||
forwardRef(function GroupNodeControl({ nodeId }, ref) {
|
||||
const data = useContext(TreeDataContext)!;
|
||||
const tree = useContext(TreeContext)!;
|
||||
|
||||
const node = data.getNode(nodeId);
|
||||
const height = tree.getNodeHeight(nodeId);
|
||||
|
||||
return (
|
||||
<TreeNodeControl ref={ref} style={{ height }}>
|
||||
<TreeNodeExpand leaf={node.leaf} />
|
||||
<TreeNodeName title={node.tooltip}>{node.name}</TreeNodeName>
|
||||
</TreeNodeControl>
|
||||
);
|
||||
}),
|
||||
);
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* 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 { observer } from 'mobx-react-lite';
|
||||
|
||||
import { ITreeData, Tree } from '@cloudbeaver/plugin-navigation-tree';
|
||||
|
||||
import { getSettingGroupId } from '../getSettingGroupId';
|
||||
import { groupNodeRenderer } from './groupNodeRenderer';
|
||||
|
||||
interface Props {
|
||||
treeData: ITreeData;
|
||||
}
|
||||
|
||||
export const SettingsGroups = observer<Props>(function SettingsGroups({ treeData }) {
|
||||
function getNodeHeight(id: string) {
|
||||
return 24;
|
||||
}
|
||||
|
||||
function handleClick(id: string) {
|
||||
document.querySelector('#' + getSettingGroupId(id))?.scrollIntoView();
|
||||
}
|
||||
|
||||
return <Tree data={treeData} getNodeHeight={getNodeHeight} nodeRenderers={[groupNodeRenderer]} onNodeClick={handleClick} />;
|
||||
});
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* 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 { INodeRenderer, Node, NodeComponent } from '@cloudbeaver/plugin-navigation-tree';
|
||||
|
||||
import { GroupNodeControl } from './GroupNodeControl';
|
||||
|
||||
export const groupNodeRenderer: INodeRenderer = () => GroupNodeRenderer;
|
||||
|
||||
const GroupNodeRenderer: NodeComponent = function GroupNodeRenderer(props) {
|
||||
return <Node {...props} controlRenderer={GroupNodeControl} />;
|
||||
};
|
||||
@@ -1,60 +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 { observer } from 'mobx-react-lite';
|
||||
|
||||
import { TextPlaceholder, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import type { FormFieldType } from '@cloudbeaver/core-settings';
|
||||
|
||||
import { SettingsInfoFormField } from './SettingsInfoFormField';
|
||||
|
||||
interface SettingsProps {
|
||||
key: string;
|
||||
type: FormFieldType;
|
||||
disabled?: boolean;
|
||||
value?: any;
|
||||
options?: any[];
|
||||
description?: string;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
interface SettingsInfoFormProps {
|
||||
fields: SettingsProps[];
|
||||
className?: string;
|
||||
readOnly?: boolean;
|
||||
onSelect?: (value: any) => void;
|
||||
onChange?: (value: any) => void;
|
||||
}
|
||||
|
||||
export const SettingsInfoForm = observer<SettingsInfoFormProps>(function SettingsInfoForm({ fields, className, readOnly, onChange, onSelect }) {
|
||||
const translate = useTranslate();
|
||||
|
||||
if (fields.length === 0) {
|
||||
return <TextPlaceholder>{translate('settings_panel_empty_fields')}</TextPlaceholder>;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{fields.map(field => (
|
||||
<SettingsInfoFormField
|
||||
key={field.key}
|
||||
id={field.key}
|
||||
type={field.type}
|
||||
value={field.value}
|
||||
options={field.options}
|
||||
description={field.description}
|
||||
name={field.name}
|
||||
disabled={field.disabled}
|
||||
readOnly={readOnly}
|
||||
className={className}
|
||||
onChange={onChange}
|
||||
onSelect={onSelect}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
@@ -1,102 +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 { observer } from 'mobx-react-lite';
|
||||
|
||||
import { Combobox, FieldCheckbox, InputField, Textarea } from '@cloudbeaver/core-blocks';
|
||||
import { FormFieldType } from '@cloudbeaver/core-settings';
|
||||
|
||||
export interface SettingsInfoFormFieldProps {
|
||||
id: string;
|
||||
type: FormFieldType;
|
||||
disabled?: boolean;
|
||||
value?: any;
|
||||
options?: any[];
|
||||
description?: string;
|
||||
name?: string;
|
||||
className?: string;
|
||||
readOnly?: boolean;
|
||||
onSelect?: (value: any) => void;
|
||||
onChange?: (value: any) => void;
|
||||
}
|
||||
|
||||
export const SettingsInfoFormField = observer<SettingsInfoFormFieldProps>(function SettingsInfoFormField({
|
||||
id,
|
||||
type,
|
||||
value,
|
||||
description,
|
||||
disabled,
|
||||
name,
|
||||
className,
|
||||
readOnly,
|
||||
options,
|
||||
onSelect,
|
||||
onChange,
|
||||
}) {
|
||||
if (type === FormFieldType.Checkbox) {
|
||||
return (
|
||||
<FieldCheckbox id={id} checked={value} title={description} disabled={disabled || readOnly} className={className} tiny onChange={onChange}>
|
||||
{name ?? ''}
|
||||
</FieldCheckbox>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === FormFieldType.Combobox && options !== undefined) {
|
||||
return (
|
||||
<Combobox
|
||||
id={id}
|
||||
items={options}
|
||||
keySelector={value => value.id}
|
||||
valueSelector={value => value.name}
|
||||
value={value}
|
||||
title={description}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
className={className}
|
||||
tiny
|
||||
onSelect={onSelect}
|
||||
>
|
||||
{name ?? ''}
|
||||
</Combobox>
|
||||
);
|
||||
}
|
||||
|
||||
if (type === FormFieldType.Textarea) {
|
||||
return (
|
||||
<Textarea
|
||||
id={id}
|
||||
title={value}
|
||||
labelTooltip={description || name}
|
||||
value={value}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
className={className}
|
||||
onChange={onChange}
|
||||
>
|
||||
{name ?? ''}
|
||||
</Textarea>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<InputField
|
||||
id={id}
|
||||
type="text"
|
||||
title={value}
|
||||
labelTooltip={description || name}
|
||||
value={value}
|
||||
description={description}
|
||||
disabled={disabled}
|
||||
readOnly={readOnly}
|
||||
className={className}
|
||||
small
|
||||
onChange={onChange}
|
||||
>
|
||||
{name ?? ''}
|
||||
</InputField>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
/*
|
||||
* 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 { importLazyComponent } from '@cloudbeaver/core-utils';
|
||||
|
||||
export const Settings = importLazyComponent(() => import('./Settings').then(m => m.Settings));
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* 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 { observer } from 'mobx-react-lite';
|
||||
|
||||
import { Container, Group, GroupTitle, useTranslate } from '@cloudbeaver/core-blocks';
|
||||
import { type ISettingDescriptionWithScope, ROOT_SETTINGS_GROUP, type SettingsGroup } from '@cloudbeaver/core-plugin';
|
||||
import type { ISettingsSource } from '@cloudbeaver/core-settings';
|
||||
import type { ITreeData } from '@cloudbeaver/plugin-navigation-tree';
|
||||
|
||||
import { getSettingGroupId } from './getSettingGroupId';
|
||||
import { Setting } from './Setting';
|
||||
import { useTreeScrollSync } from './useTreeScrollSync';
|
||||
|
||||
interface Props {
|
||||
treeData: ITreeData;
|
||||
source: ISettingsSource;
|
||||
settings: Map<SettingsGroup, ISettingDescriptionWithScope<any>[]>;
|
||||
onSettingsOpen?: (groupId: string) => void;
|
||||
}
|
||||
|
||||
export const SettingsList = observer<Props>(function SettingsList({ treeData, source, settings, onSettingsOpen }) {
|
||||
const translate = useTranslate();
|
||||
const list = [];
|
||||
const groups = [...treeData.getChildren(treeData.rootId)];
|
||||
const ref = useTreeScrollSync(treeData, onSettingsOpen);
|
||||
|
||||
while (groups.length) {
|
||||
const groupId = groups[0];
|
||||
groups.splice(0, 1, ...treeData.getChildren(groupId));
|
||||
|
||||
const group = ROOT_SETTINGS_GROUP.get(groupId)!;
|
||||
const groupSettings = settings.get(group);
|
||||
|
||||
list.push({ group, settings: groupSettings || [] });
|
||||
}
|
||||
|
||||
return (
|
||||
<Container ref={ref} style={{ height: '100%' }} gap overflow>
|
||||
{list.map(({ group, settings }) => (
|
||||
<Group key={group.id} id={getSettingGroupId(group.id)} vertical gap>
|
||||
<GroupTitle>{translate(group.name)}</GroupTitle>
|
||||
{settings.map((setting, i) => (
|
||||
<Setting key={i} source={source} setting={setting} />
|
||||
))}
|
||||
</Group>
|
||||
))}
|
||||
</Container>
|
||||
);
|
||||
});
|
||||
@@ -1,6 +0,0 @@
|
||||
.content {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: auto;
|
||||
}
|
||||
@@ -1,35 +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 { observer } from 'mobx-react-lite';
|
||||
import React from 'react';
|
||||
|
||||
import { ColoredContainer, Container, s, useS } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { SettingsManagerService } from '@cloudbeaver/core-settings';
|
||||
|
||||
import { SettingsGroup } from './SettingsGroup';
|
||||
import styles from './SettingsPanelForm.m.css';
|
||||
|
||||
export const SettingsPanelForm = observer(function SettingsPanelForm() {
|
||||
const style = useS(styles);
|
||||
|
||||
const settingsManagerService = useService(SettingsManagerService);
|
||||
const groups = Array.from(settingsManagerService.groups.values());
|
||||
|
||||
return (
|
||||
<div className={s(style, { content: true })}>
|
||||
<ColoredContainer gap overflow parent>
|
||||
<Container medium gap vertical overflow>
|
||||
{groups.map(group => (
|
||||
<SettingsGroup key={group.id} group={group} />
|
||||
))}
|
||||
</Container>
|
||||
</ColoredContainer>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* 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 { SETTINGS_GROUP_ID_PREFIX } from './SETTINGS_GROUP_ID_PREFIX';
|
||||
|
||||
export function getSettingGroupId(id: string): string {
|
||||
return `${SETTINGS_GROUP_ID_PREFIX}${id}`;
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* 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 { SETTINGS_GROUP_ID_PREFIX } from './SETTINGS_GROUP_ID_PREFIX';
|
||||
|
||||
export function getSettingGroupIdFromElementId(id: string): string {
|
||||
return id.replace(SETTINGS_GROUP_ID_PREFIX, '');
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* 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 { SETTINGS_GROUP_ID_PREFIX } from './SETTINGS_GROUP_ID_PREFIX';
|
||||
|
||||
export function querySettingsGroups(element: HTMLElement): HTMLElement[] {
|
||||
return Array.from(element.querySelectorAll<HTMLElement>(`[id^="${SETTINGS_GROUP_ID_PREFIX}"]`));
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* 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 { computed, observable } from 'mobx';
|
||||
|
||||
import { useObservableRef } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { ISettingDescriptionWithScope, ROOT_SETTINGS_GROUP, SettingsGroup, SettingsManagerService } from '@cloudbeaver/core-plugin';
|
||||
|
||||
interface ISettings {
|
||||
settings: Map<SettingsGroup, ISettingDescriptionWithScope<any>[]>;
|
||||
groups: Set<SettingsGroup>;
|
||||
}
|
||||
|
||||
export function useSettings(): ISettings {
|
||||
const settingsManagerService = useService(SettingsManagerService);
|
||||
|
||||
return useObservableRef(
|
||||
() => ({
|
||||
get settings() {
|
||||
const map = new Map();
|
||||
const settings = this.settingsManagerService.getSettings().sort((a, b) => a.name.localeCompare(b.name));
|
||||
|
||||
for (const setting of settings) {
|
||||
map.set(setting.group, [...(map.get(setting.group) || []), setting]);
|
||||
}
|
||||
|
||||
return map;
|
||||
},
|
||||
get groups() {
|
||||
const groups = new Set(this.settings.keys());
|
||||
|
||||
for (const group of groups) {
|
||||
if (group.parent) {
|
||||
groups.add(group.parent);
|
||||
}
|
||||
}
|
||||
|
||||
return groups;
|
||||
},
|
||||
groupChildren(id: string) {
|
||||
return (ROOT_SETTINGS_GROUP.get(id)?.subGroups || []).filter(group => this.groups.has(group)).sort((a, b) => a.name.localeCompare(b.name));
|
||||
},
|
||||
}),
|
||||
{
|
||||
settings: computed,
|
||||
groups: computed,
|
||||
settingsManagerService: observable.ref,
|
||||
},
|
||||
{ settingsManagerService },
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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 { useEffect, useRef } from 'react';
|
||||
|
||||
import { ROOT_SETTINGS_GROUP } from '@cloudbeaver/core-plugin';
|
||||
import type { ITreeData } from '@cloudbeaver/plugin-navigation-tree';
|
||||
|
||||
import { getSettingGroupIdFromElementId } from './getSettingGroupIdFromElementId';
|
||||
import { querySettingsGroups } from './querySettingsGroups';
|
||||
|
||||
export function useTreeScrollSync(treeData: ITreeData, onSettingsOpen?: (groupId: string) => void): React.RefObject<HTMLDivElement> {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const element = ref.current;
|
||||
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
function syncScroll(e: Event) {
|
||||
const container = e.target as HTMLDivElement;
|
||||
|
||||
const elements = querySettingsGroups(container);
|
||||
let firstVisibleElement: HTMLElement | undefined;
|
||||
|
||||
for (const element of elements) {
|
||||
if (element.offsetTop + element.offsetHeight > container.scrollTop) {
|
||||
firstVisibleElement = element;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (firstVisibleElement) {
|
||||
const groupId = getSettingGroupIdFromElementId(firstVisibleElement.id);
|
||||
let group = ROOT_SETTINGS_GROUP.get(groupId)!;
|
||||
|
||||
treeData.updateAllState({ selected: false, expanded: false });
|
||||
treeData.updateState(groupId, { selected: true });
|
||||
|
||||
while (group.parent && group.parent !== ROOT_SETTINGS_GROUP) {
|
||||
treeData.updateState(group.parent.id, { expanded: true });
|
||||
group = group.parent;
|
||||
}
|
||||
onSettingsOpen?.(groupId);
|
||||
}
|
||||
}
|
||||
|
||||
element.addEventListener('scroll', syncScroll);
|
||||
|
||||
return () => {
|
||||
element.removeEventListener('scroll', syncScroll);
|
||||
};
|
||||
});
|
||||
|
||||
return ref;
|
||||
}
|
||||
@@ -5,42 +5,15 @@
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
import { AuthInfoService } from '@cloudbeaver/core-authentication';
|
||||
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
|
||||
import { OptionsPanelService } from '@cloudbeaver/core-ui';
|
||||
import { ActionService, DATA_CONTEXT_MENU, MenuService } from '@cloudbeaver/core-view';
|
||||
import { TOP_NAV_BAR_SETTINGS_MENU } from '@cloudbeaver/plugin-settings-menu';
|
||||
|
||||
import { ACTION_OPEN_APP_SETTINGS } from './actions/ACTION_OPEN_APP_SETTINGS';
|
||||
import { SettingsPanelForm } from './SettingsPanel/SettingsPanelForm';
|
||||
|
||||
@injectable()
|
||||
export class SettingsPanelPluginBootstrap extends Bootstrap {
|
||||
constructor(
|
||||
private readonly menuService: MenuService,
|
||||
private readonly optionsPanelService: OptionsPanelService,
|
||||
private readonly authInfoService: AuthInfoService,
|
||||
private readonly actionService: ActionService,
|
||||
) {
|
||||
constructor() {
|
||||
super();
|
||||
}
|
||||
|
||||
register(): void | Promise<void> {
|
||||
this.addTopAppMenuItems();
|
||||
}
|
||||
register(): void | Promise<void> {}
|
||||
|
||||
load(): void | Promise<void> {}
|
||||
|
||||
private addTopAppMenuItems() {
|
||||
this.actionService.addHandler({
|
||||
id: 'open-app-settings',
|
||||
isActionApplicable: (context, action) => ACTION_OPEN_APP_SETTINGS === action,
|
||||
handler: () => this.optionsPanelService.open(() => SettingsPanelForm),
|
||||
});
|
||||
|
||||
this.menuService.addCreator({
|
||||
isApplicable: context => context.get(DATA_CONTEXT_MENU) === TOP_NAV_BAR_SETTINGS_MENU && !!this.authInfoService.userInfo,
|
||||
getItems: (context, items) => [...items, ACTION_OPEN_APP_SETTINGS],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import { settingsPanelPlugin } from './manifest';
|
||||
|
||||
export * from './SettingsPanel/SettingsInfoFormField';
|
||||
export * from './SettingsPanel/SettingsInfoForm';
|
||||
|
||||
export * from './SettingsPanel/SettingsLazy';
|
||||
export default settingsPanelPlugin;
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
export default [
|
||||
['settings_panel', 'Settings'],
|
||||
['settings_panel_empty_fields', 'Fields are empty'],
|
||||
];
|
||||
export default [['plugin_settings_panel_empty', 'No settings available']];
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
export default [
|
||||
['settings_panel', 'Settings'],
|
||||
['settings_panel_empty_fields', 'Fields are empty'],
|
||||
];
|
||||
export default [['plugin_settings_panel_empty', 'No settings available']];
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
export default [
|
||||
['settings_panel', 'Настройки'],
|
||||
['settings_panel_empty_fields', 'Поля пустые'],
|
||||
];
|
||||
export default [['plugin_settings_panel_empty', 'Настройки недоступны']];
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
export default [
|
||||
['settings_panel', 'Settings'],
|
||||
['settings_panel_empty_fields', 'Fields are empty'],
|
||||
];
|
||||
export default [['plugin_settings_panel_empty', 'No settings available']];
|
||||
|
||||
Reference in New Issue
Block a user