From 043204002fff9dc97b954cafab06234c22132e17 Mon Sep 17 00:00:00 2001 From: Alexey Potsetsuev Date: Thu, 21 Aug 2025 19:15:30 +0800 Subject: [PATCH] dbeaver/pro#5902 fix: settings resolution (#3673) Co-authored-by: mr-anton-t <42037741+mr-anton-t@users.noreply.github.com> Co-authored-by: Daria Marutkina <125263541+dariamarutkina@users.noreply.github.com> --- .../src/SettingsUserBootstrap.ts | 8 +++---- .../src/SettingsProviderService.ts | 13 ++++++++-- .../src/SettingsResolverSource.ts | 24 ++++++++++++------- .../src/UserSettingsResolverService.ts | 17 +++++++++++++ webapp/packages/core-settings/src/index.ts | 1 + webapp/packages/core-settings/src/manifest.ts | 3 ++- .../core-theming/src/SystemThemeService.ts | 9 +++++++ .../src/SettingsPanel/Setting.tsx | 4 ++-- .../src/SettingsPanel/SettingField.tsx | 4 ++-- .../src/SettingsPanel/Settings.tsx | 4 ++-- .../src/SettingsPanel/SettingsGroup.tsx | 4 ++-- .../src/SettingsPanel/SettingsList.tsx | 4 ++-- .../src/UserProfileSettings.tsx | 6 ++--- 13 files changed, 73 insertions(+), 28 deletions(-) create mode 100644 webapp/packages/core-settings/src/UserSettingsResolverService.ts diff --git a/webapp/packages/core-settings-user/src/SettingsUserBootstrap.ts b/webapp/packages/core-settings-user/src/SettingsUserBootstrap.ts index cdd1b51608..6a636ef7ab 100644 --- a/webapp/packages/core-settings-user/src/SettingsUserBootstrap.ts +++ b/webapp/packages/core-settings-user/src/SettingsUserBootstrap.ts @@ -1,26 +1,26 @@ /* * 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 { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { SESSION_SETTINGS_LAYER } from '@cloudbeaver/core-root'; -import { SettingsResolverService } from '@cloudbeaver/core-settings'; +import { UserSettingsResolverService } from '@cloudbeaver/core-settings'; import { UserSettingsService } from './UserSettingsService.js'; @injectable() export class SettingsUserBootstrap extends Bootstrap { constructor( - private readonly settingsResolverService: SettingsResolverService, + private readonly userSettingsResolverService: UserSettingsResolverService, private readonly userSettingsService: UserSettingsService, ) { super(); } override register(): void { - this.settingsResolverService.addResolver(SESSION_SETTINGS_LAYER, this.userSettingsService); + this.userSettingsResolverService.addResolver(SESSION_SETTINGS_LAYER, this.userSettingsService); } } diff --git a/webapp/packages/core-settings/src/SettingsProviderService.ts b/webapp/packages/core-settings/src/SettingsProviderService.ts index 29fd427da1..74b70d991f 100644 --- a/webapp/packages/core-settings/src/SettingsProviderService.ts +++ b/webapp/packages/core-settings/src/SettingsProviderService.ts @@ -12,15 +12,24 @@ import { schema } from '@cloudbeaver/core-utils'; import { SettingsProvider } from './SettingsProvider.js'; import { SettingsResolverService } from './SettingsResolverService.js'; +import { UserSettingsResolverService } from './UserSettingsResolverService.js'; +import { SettingsResolverSource } from './SettingsResolverSource.js'; @injectable() export class SettingsProviderService { get schema(): schema.ZodObject { return this.mergedSchema; } + get settingsResolver(): SettingsResolverSource { + return this.resolver; + } private mergedSchema: schema.ZodObject; - constructor(private readonly settingsResolverService: SettingsResolverService) { + private resolver: SettingsResolverSource; + constructor(settingsResolverService: SettingsResolverService, userSettingsResolverService: UserSettingsResolverService) { this.mergedSchema = schema.object({}); + this.resolver = new SettingsResolverSource(); + + this.resolver.add(settingsResolverService, userSettingsResolverService); makeObservable(this, { mergedSchema: observable.ref, @@ -29,7 +38,7 @@ export class SettingsProviderService { createSettings(schema: TSchema): SettingsProvider { this.mergedSchema = this.mergedSchema.merge(schema); - const settings = new SettingsProvider(this.settingsResolverService, schema); + const settings = new SettingsProvider(this.resolver, schema); return settings; } } diff --git a/webapp/packages/core-settings/src/SettingsResolverSource.ts b/webapp/packages/core-settings/src/SettingsResolverSource.ts index 9dc01b7064..0ae3749e23 100644 --- a/webapp/packages/core-settings/src/SettingsResolverSource.ts +++ b/webapp/packages/core-settings/src/SettingsResolverSource.ts @@ -15,41 +15,49 @@ import type { ISettingChangeData, ISettingsSource } from './ISettingsSource.js'; import type { ISettingsLayer } from './SettingsLayer.js'; import { isEditableSettingsSource, type IEditableSettingsSource } from './IEditableSettingsSource.js'; -type SettingsSource = ISettingsSource | IEditableSettingsSource; +type SettingsSourceUnion = ISettingsSource | IEditableSettingsSource; interface ISettingsSourcesLayer { layer: ISettingsLayer; - sources: SettingsSource[]; + sources: SettingsSourceUnion[]; } export class SettingsResolverSource implements ISettingsResolverSource { readonly onChange: ISyncExecutor; - protected get sources(): SettingsSource[] { - return this.layers + protected get sources(): SettingsSourceUnion[] { + return [...this.layers, ...this.resolvers.map(r => r.layers).flat()] .slice() .sort((a, b) => a.layer.level - b.layer.level) .flatMap(layer => layer.sources) .reverse(); } protected layers: ISettingsSourcesLayer[]; + protected resolvers: SettingsResolverSource[]; private updating: boolean; constructor() { this.onChange = new SyncExecutor(); this.layers = []; + this.resolvers = []; this.updating = false; - makeObservable(this, { + makeObservable(this, { layers: observable.shallow, sources: computed, update: action, + resolvers: observable.shallow, }); } - hasResolver(layer: ISettingsLayer, resolver: SettingsSource): boolean { + add(...resolvers: SettingsResolverSource[]): this { + this.resolvers.push(...resolvers); + return this; + } + + hasResolver(layer: ISettingsLayer, resolver: SettingsSourceUnion): boolean { return this.tryGetLayerSources(layer)?.sources.includes(resolver) || false; } - removeResolver(layer: ISettingsLayer, resolver: SettingsSource): void { + removeResolver(layer: ISettingsLayer, resolver: SettingsSourceUnion): void { const layerSources = this.getLayerSources(layer); const index = layerSources.sources.indexOf(resolver); @@ -60,7 +68,7 @@ export class SettingsResolverSource implements ISettingsResolverSource { } } - addResolver(layer: ISettingsLayer, ...resolvers: SettingsSource[]): void { + addResolver(layer: ISettingsLayer, ...resolvers: SettingsSourceUnion[]): void { if (resolvers.some(this.hasResolver.bind(this, layer))) { return; } diff --git a/webapp/packages/core-settings/src/UserSettingsResolverService.ts b/webapp/packages/core-settings/src/UserSettingsResolverService.ts new file mode 100644 index 0000000000..f5790b10e5 --- /dev/null +++ b/webapp/packages/core-settings/src/UserSettingsResolverService.ts @@ -0,0 +1,17 @@ +/* + * 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 { injectable } from '@cloudbeaver/core-di'; + +import { SettingsResolverSource } from './SettingsResolverSource.js'; + +@injectable() +export class UserSettingsResolverService extends SettingsResolverSource { + constructor() { + super(); + } +} diff --git a/webapp/packages/core-settings/src/index.ts b/webapp/packages/core-settings/src/index.ts index b4ea35f491..ae7caf63a7 100644 --- a/webapp/packages/core-settings/src/index.ts +++ b/webapp/packages/core-settings/src/index.ts @@ -14,6 +14,7 @@ export * from './ISettingsResolverSource.js'; export * from './IEditableSettingsSource.js'; export * from './ISettingsSource.js'; export * from './SettingsLayer.js'; +export * from './UserSettingsResolverService.js'; export * from './SettingsResolverService.js'; export * from './SettingsProvider.js'; export * from './SettingsProviderService.js'; diff --git a/webapp/packages/core-settings/src/manifest.ts b/webapp/packages/core-settings/src/manifest.ts index c99ec45428..355f1d8231 100644 --- a/webapp/packages/core-settings/src/manifest.ts +++ b/webapp/packages/core-settings/src/manifest.ts @@ -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. @@ -15,6 +15,7 @@ export const coreSettingsManifest: PluginManifest = { providers: [ () => import('./SettingsManager/SettingsManagerService.js').then(m => m.SettingsManagerService), () => import('./SettingsResolverService.js').then(m => m.SettingsResolverService), + () => import('./UserSettingsResolverService.js').then(m => m.UserSettingsResolverService), () => import('./LocaleService.js').then(m => m.LocaleService), () => import('./SettingsProviderService.js').then(m => m.SettingsProviderService), ], diff --git a/webapp/packages/core-theming/src/SystemThemeService.ts b/webapp/packages/core-theming/src/SystemThemeService.ts index 2cafef2fff..5f5bafae91 100644 --- a/webapp/packages/core-theming/src/SystemThemeService.ts +++ b/webapp/packages/core-theming/src/SystemThemeService.ts @@ -61,6 +61,15 @@ export class SystemThemeService extends Bootstrap { } return undefined; }, + getEditedValue(key) { + if (key === 'core.theming.theme') { + return 'system'; + } + return undefined; + }, + isEdited(key) { + return false; + }, }); } diff --git a/webapp/packages/plugin-settings-panel/src/SettingsPanel/Setting.tsx b/webapp/packages/plugin-settings-panel/src/SettingsPanel/Setting.tsx index f6083f2e55..0844a7251c 100644 --- a/webapp/packages/plugin-settings-panel/src/SettingsPanel/Setting.tsx +++ b/webapp/packages/plugin-settings-panel/src/SettingsPanel/Setting.tsx @@ -9,11 +9,11 @@ import { observer } from 'mobx-react-lite'; import { Link, useTranslate } from '@cloudbeaver/core-blocks'; import { clsx } from '@dbeaver/ui-kit'; -import { SettingsResolverSource, type ISettingDescription, type IEditableSettingsSource } from '@cloudbeaver/core-settings'; +import { type ISettingDescription, type IEditableSettingsSource, type ISettingsResolverSource } from '@cloudbeaver/core-settings'; import { SettingField } from './SettingField.js'; interface Props { - resolver: SettingsResolverSource; + resolver: ISettingsResolverSource; source: IEditableSettingsSource; setting: ISettingDescription; displayRestore?: boolean; diff --git a/webapp/packages/plugin-settings-panel/src/SettingsPanel/SettingField.tsx b/webapp/packages/plugin-settings-panel/src/SettingsPanel/SettingField.tsx index 927f81708e..c21a8a0901 100644 --- a/webapp/packages/plugin-settings-panel/src/SettingsPanel/SettingField.tsx +++ b/webapp/packages/plugin-settings-panel/src/SettingsPanel/SettingField.tsx @@ -11,16 +11,16 @@ import { useService } from '@cloudbeaver/core-di'; import { ESettingsValueType, SettingsProviderService, - SettingsResolverSource, type ISettingDescription, type IEditableSettingsSource, + type ISettingsResolverSource, } from '@cloudbeaver/core-settings'; import { schemaValidationError } from '@cloudbeaver/core-utils'; import { isNotNullDefined } from '@dbeaver/js-helpers'; import { observer } from 'mobx-react-lite'; interface Props { - resolver: SettingsResolverSource; + resolver: ISettingsResolverSource; source: IEditableSettingsSource; setting: ISettingDescription; } diff --git a/webapp/packages/plugin-settings-panel/src/SettingsPanel/Settings.tsx b/webapp/packages/plugin-settings-panel/src/SettingsPanel/Settings.tsx index 1bba7e5416..a45d217100 100644 --- a/webapp/packages/plugin-settings-panel/src/SettingsPanel/Settings.tsx +++ b/webapp/packages/plugin-settings-panel/src/SettingsPanel/Settings.tsx @@ -9,7 +9,7 @@ import { useId, useState } from 'react'; import { observer } from 'mobx-react-lite'; import { Container, Filter, getComputed, Group, s, TextPlaceholder, useTranslate } from '@cloudbeaver/core-blocks'; -import { type IEditableSettingsSource, ROOT_SETTINGS_GROUP, SettingsGroup, SettingsResolverSource } from '@cloudbeaver/core-settings'; +import { type IEditableSettingsSource, type ISettingsResolverSource, ROOT_SETTINGS_GROUP, SettingsGroup } from '@cloudbeaver/core-settings'; import { useTreeData, useTreeFilter } from '@cloudbeaver/plugin-navigation-tree'; import { SyncExecutor } from '@cloudbeaver/core-executor'; @@ -20,7 +20,7 @@ import { SettingsList } from './SettingsList.js'; import { useSettings } from './useSettings.js'; export interface ISettingsProps { - resolver: SettingsResolverSource; + resolver: ISettingsResolverSource; source: IEditableSettingsSource; accessor?: string[]; hideGroupsSettingsLimit?: number; diff --git a/webapp/packages/plugin-settings-panel/src/SettingsPanel/SettingsGroup.tsx b/webapp/packages/plugin-settings-panel/src/SettingsPanel/SettingsGroup.tsx index d545784ba8..828d71696b 100644 --- a/webapp/packages/plugin-settings-panel/src/SettingsPanel/SettingsGroup.tsx +++ b/webapp/packages/plugin-settings-panel/src/SettingsPanel/SettingsGroup.tsx @@ -11,8 +11,8 @@ import { getComputed, Group, GroupTitle, useExecutor, useTranslate } from '@clou import type { IEditableSettingsSource, ISettingDescription, + ISettingsResolverSource, SettingsGroup as SettingsGroupType, - SettingsResolverSource, } from '@cloudbeaver/core-settings'; import { isArraysEqual } from '@cloudbeaver/core-utils'; import type { ITreeFilter } from '@cloudbeaver/plugin-navigation-tree'; @@ -27,7 +27,7 @@ import { getSettingGroupId } from './getSettingGroupId.js'; interface Props { settingsId: string; group: SettingsGroupType; - resolver: SettingsResolverSource; + resolver: ISettingsResolverSource; source: IEditableSettingsSource; settings: Map[]>; treeFilter: ITreeFilter; diff --git a/webapp/packages/plugin-settings-panel/src/SettingsPanel/SettingsList.tsx b/webapp/packages/plugin-settings-panel/src/SettingsPanel/SettingsList.tsx index d2f0fcb154..c1ea691f21 100644 --- a/webapp/packages/plugin-settings-panel/src/SettingsPanel/SettingsList.tsx +++ b/webapp/packages/plugin-settings-panel/src/SettingsPanel/SettingsList.tsx @@ -9,9 +9,9 @@ import { observer } from 'mobx-react-lite'; import { Group, s, TextPlaceholder, useTranslate } from '@cloudbeaver/core-blocks'; import { - SettingsResolverSource, type IEditableSettingsSource, type ISettingDescription, + type ISettingsResolverSource, type SettingsGroup as SettingsGroupType, } from '@cloudbeaver/core-settings'; import type { ITreeData, ITreeFilter } from '@cloudbeaver/plugin-navigation-tree'; @@ -27,7 +27,7 @@ interface Props { treeData: ITreeData; treeFilter: ITreeFilter; source: IEditableSettingsSource; - resolver: SettingsResolverSource; + resolver: ISettingsResolverSource; settings: Map[]>; groupSelectExecutor: ISyncExecutor; groupsHidden?: boolean; diff --git a/webapp/packages/plugin-user-profile-settings/src/UserProfileSettings.tsx b/webapp/packages/plugin-user-profile-settings/src/UserProfileSettings.tsx index fd00872335..5374080fac 100644 --- a/webapp/packages/plugin-user-profile-settings/src/UserProfileSettings.tsx +++ b/webapp/packages/plugin-user-profile-settings/src/UserProfileSettings.tsx @@ -12,13 +12,13 @@ import { useService } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import { UserSettingsService } from '@cloudbeaver/core-settings-user'; import { Settings } from '@cloudbeaver/plugin-settings-panel'; -import { SettingsResolverService } from '@cloudbeaver/core-settings'; +import { SettingsProviderService } from '@cloudbeaver/core-settings'; const clientScope = ['client']; export const UserProfileSettings = observer(function UserProfileSettings() { const translate = useTranslate(); - const settingsResolverService = useService(SettingsResolverService); + const settingsProviderService = useService(SettingsProviderService); const userSettingsService = useService(UserSettingsService); const notificationService = useService(NotificationService); @@ -72,7 +72,7 @@ export const UserProfileSettings = observer(function UserProfileSettings() { - + );