diff --git a/webapp/packages/core-app/src/TopNavBar/AdministrationTopAppBarBootstrapService.ts b/webapp/packages/core-app/src/TopNavBar/AdministrationTopAppBarBootstrapService.ts index ba4bf6c297..47011a7fd6 100644 --- a/webapp/packages/core-app/src/TopNavBar/AdministrationTopAppBarBootstrapService.ts +++ b/webapp/packages/core-app/src/TopNavBar/AdministrationTopAppBarBootstrapService.ts @@ -10,7 +10,6 @@ import { AdministrationTopAppBarService, WizardTopAppBarService } from '@cloudbe import { injectable, Bootstrap } from '@cloudbeaver/core-di'; import { Logo } from './Logo'; -import { SettingsMenu } from './SettingsMenu/SettingsMenu'; @injectable() export class AdministrationTopAppBarBootstrapService extends Bootstrap { @@ -23,9 +22,8 @@ export class AdministrationTopAppBarBootstrapService extends Bootstrap { register(): void | Promise { this.administrationTopAppBarService.placeholder.add(Logo, 0); - this.administrationTopAppBarService.placeholder.add(SettingsMenu, 4); this.wizardTopAppBarService.placeholder.add(Logo, 0); } - load(): void | Promise {} -} + load(): void | Promise { } +} \ No newline at end of file diff --git a/webapp/packages/core-app/src/TopNavBar/SettingsMenu/SettingsMenuService.ts b/webapp/packages/core-app/src/TopNavBar/SettingsMenu/SettingsMenuService.ts deleted file mode 100644 index e7693380d6..0000000000 --- a/webapp/packages/core-app/src/TopNavBar/SettingsMenu/SettingsMenuService.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2021 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 { IComputedMenuItemOptions, IMenuPanel, StaticMenu } from '@cloudbeaver/core-dialogs'; -import { LocalizationService } from '@cloudbeaver/core-localization'; -import { ServerConfigResource } from '@cloudbeaver/core-root'; -import { ThemeService } from '@cloudbeaver/core-theming'; - -@injectable() -export class SettingsMenuService extends Bootstrap { - static settingsMenuToken = 'settingsMenu'; - - private menu = new StaticMenu(); - private langMenuToken = 'langMenu'; - private themeMenuToken = 'themeMenu'; - - constructor( - private localizationService: LocalizationService, - private themeService: ThemeService, - private serverConfigResource: ServerConfigResource, - ) { - super(); - } - - register(): void | Promise { - this.menu.addRootPanel(SettingsMenuService.settingsMenuToken); - } - - async load(): Promise { - this.addThemes(); - await this.addLocales(); - } - - getMenu(): IMenuPanel { - return this.menu.getMenu(SettingsMenuService.settingsMenuToken); - } - - addMenuItem(panelId: string, options: IComputedMenuItemOptions): void { - this.menu.addMenuItem(panelId, options); - } - - private addThemes() { - this.addMenuItem( - SettingsMenuService.settingsMenuToken, - { - id: this.themeMenuToken, - order: 1, - title: 'app_shared_settingsMenu_theme', - isPanel: true, - } - ); - - this.themeService.themes.forEach(theme => { - this.addMenuItem( - this.themeMenuToken, - { - id: theme.id, - title: theme.name, - isDisabled: () => theme.id === this.themeService.currentThemeId, - onClick: () => this.themeService.changeThemeAsync(theme.id), - } - ); - }); - } - - private async addLocales() { - const config = await this.serverConfigResource.load(); - - if (!config) { - return; - } - - this.addMenuItem( - SettingsMenuService.settingsMenuToken, - { - id: this.langMenuToken, - order: 2, - title: 'app_shared_settingsMenu_lang', - isPanel: true, - } - ); - - config.supportedLanguages.forEach(lang => { - this.addMenuItem( - this.langMenuToken, - { - id: lang.isoCode, - title: lang.nativeName, - isDisabled: () => lang.isoCode === this.localizationService.getCurrentLanguage(), - onClick: () => this.localizationService.changeLocaleAsync(lang.isoCode), - } - ); - }); - } -} diff --git a/webapp/packages/core-app/src/TopNavBar/TopNavBar.tsx b/webapp/packages/core-app/src/TopNavBar/TopNavBar.tsx index 88049e68f5..3e5e3f0334 100644 --- a/webapp/packages/core-app/src/TopNavBar/TopNavBar.tsx +++ b/webapp/packages/core-app/src/TopNavBar/TopNavBar.tsx @@ -11,7 +11,7 @@ import { useService } from '@cloudbeaver/core-di'; import { TopNavService } from './TopNavBarService'; -export function TopNavBar() { +export const TopNavBar = function TopNavBar() { const topNavBarService = useService(TopNavService); return ( @@ -19,4 +19,4 @@ export function TopNavBar() { ); -} +}; \ No newline at end of file diff --git a/webapp/packages/core-app/src/TopNavBar/TopNavBarService.ts b/webapp/packages/core-app/src/TopNavBar/TopNavBarService.ts index 75004f17d6..4a228999c1 100644 --- a/webapp/packages/core-app/src/TopNavBar/TopNavBarService.ts +++ b/webapp/packages/core-app/src/TopNavBar/TopNavBarService.ts @@ -13,7 +13,6 @@ import { ConnectionSelector } from './ConnectionSchemaManager/ConnectionSelector import { Fill } from './Fill'; import { Logo } from './Logo'; import { MainMenu } from './MainMenu/MainMenu'; -import { SettingsMenu } from './SettingsMenu/SettingsMenu'; @injectable() export class TopNavService { @@ -24,6 +23,5 @@ export class TopNavService { this.placeholder.add(MainMenu, 1); this.placeholder.add(ConnectionSelector, 2); this.placeholder.add(Fill, 3); - this.placeholder.add(SettingsMenu, 4); } } diff --git a/webapp/packages/core-app/src/index.ts b/webapp/packages/core-app/src/index.ts index fae9087366..c8b1f4a56b 100644 --- a/webapp/packages/core-app/src/index.ts +++ b/webapp/packages/core-app/src/index.ts @@ -2,7 +2,6 @@ export * from './CoreSettingsService'; export * from './AppScreen/AppScreenService'; export * from './TopNavBar/MainMenu/MainMenuService'; -export * from './TopNavBar/SettingsMenu/SettingsMenuService'; export * from './TopNavBar/ConnectionSchemaManager/ConnectionSchemaManagerService'; export * from './TopNavBar/TopNavBarService'; export * from './TopNavBar/AdministrationTopAppBarBootstrapService'; @@ -59,7 +58,6 @@ export * from './useAppVersion'; // components export * from './Body'; -export * from './TopNavBar/SettingsMenu/SettingsMenu'; export * from './TopNavBar/Logo'; // Interfaces diff --git a/webapp/packages/core-bootstrap/src/manifest.ts b/webapp/packages/core-bootstrap/src/manifest.ts index 222c456ffe..ca3757866e 100644 --- a/webapp/packages/core-bootstrap/src/manifest.ts +++ b/webapp/packages/core-bootstrap/src/manifest.ts @@ -31,7 +31,6 @@ import { ConnectionSchemaManagerService, NavigationTabsService, NavNodeContextMenuService, - SettingsMenuService, LogViewerService, LogViewerBootstrap, TopNavService, @@ -156,7 +155,6 @@ export const coreManifests: PluginManifest[] = [ DBObjectResource, NotificationService, SessionService, - SettingsMenuService, SettingsService, ThemeSettingsService, ThemeService, diff --git a/webapp/packages/core-localization/src/LocalizationService.ts b/webapp/packages/core-localization/src/LocalizationService.ts index f2ba4247d0..0792bbe013 100644 --- a/webapp/packages/core-localization/src/LocalizationService.ts +++ b/webapp/packages/core-localization/src/LocalizationService.ts @@ -10,7 +10,7 @@ import { action, observable, makeObservable } from 'mobx'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; -import { ServerService, SessionResource } from '@cloudbeaver/core-root'; +import { ServerConfigResource, SessionResource } from '@cloudbeaver/core-root'; import type { ServerLanguage } from '@cloudbeaver/core-sdk'; import { SettingsService } from '@cloudbeaver/core-settings'; @@ -38,10 +38,10 @@ export class LocalizationService extends Bootstrap { private localeProviders: ILocaleProvider[] = []; constructor( - private notificationService: NotificationService, - private sessionResource: SessionResource, - private serverService: ServerService, - private settingsService: SettingsService + private readonly notificationService: NotificationService, + private readonly sessionResource: SessionResource, + private readonly serverConfigResource: ServerConfigResource, + private readonly settingsService: SettingsService ) { super(); @@ -133,7 +133,7 @@ export class LocalizationService extends Bootstrap { } private async setLocale(key: string) { - const config = await this.serverService.config.load(); + const config = await this.serverConfigResource.load(); if (!config) { throw new Error('Can\'t get server settings'); diff --git a/webapp/packages/core-localization/src/locales/en.ts b/webapp/packages/core-localization/src/locales/en.ts index 9bfd3c316f..8b2353867c 100644 --- a/webapp/packages/core-localization/src/locales/en.ts +++ b/webapp/packages/core-localization/src/locales/en.ts @@ -59,6 +59,7 @@ export const defaultENLocale = [ ['ui_changes_will_be_lost', 'Your changes will be lost'], ['ui_continue', 'Continue'], ['ui_authentication', 'Authentication'], + ['ui_settings', 'Settings'], ['root_permission_denied', "You don't have permissions"], ['root_permission_no_permission', "You don't have permission for this action"], diff --git a/webapp/packages/core-localization/src/locales/it.ts b/webapp/packages/core-localization/src/locales/it.ts index 75d42b51ff..c76a0b7a67 100644 --- a/webapp/packages/core-localization/src/locales/it.ts +++ b/webapp/packages/core-localization/src/locales/it.ts @@ -38,6 +38,7 @@ export const defaultITLocale = [ ['ui_no_items_placeholder', 'Non ci sono ancora elementi.'], ['ui_search_no_result_placeholder', 'Nessun risultato trovato.'], ['ui_save_reminder', 'Ci sono modifiche non salvate.'], + ['ui_settings', 'Settings'], ['root_permission_denied', 'Non hai i permessi'], ['app_root_session_expire_warning_title', 'La sessione sta per scadere'], diff --git a/webapp/packages/core-localization/src/locales/ru.ts b/webapp/packages/core-localization/src/locales/ru.ts index b7e51f9fd6..2edc05a81f 100644 --- a/webapp/packages/core-localization/src/locales/ru.ts +++ b/webapp/packages/core-localization/src/locales/ru.ts @@ -57,6 +57,7 @@ export const defaultRULocale = [ ['ui_changes_will_be_lost', 'Ваши изменения будут потеряны'], ['ui_continue', 'Продолжить'], ['ui_authentication', 'Аутентификация'], + ['ui_settings', 'Настройки'], ['root_permission_denied', 'Отказано в доступе'], ['root_permission_no_permission', 'У вас нет разрешения на это действие'], diff --git a/webapp/packages/core-localization/src/locales/zh.ts b/webapp/packages/core-localization/src/locales/zh.ts index 9d45db8441..d72b365e60 100644 --- a/webapp/packages/core-localization/src/locales/zh.ts +++ b/webapp/packages/core-localization/src/locales/zh.ts @@ -1,73 +1,74 @@ export const defaultZHLocale = [ - ['name', 'zh'], - ['ui_tag_new', '新建'], - ['ui_light_theme', '明亮'], - ['ui_dark_theme', '黑暗'], - ['ui_stepper_back', '返回'], - ['ui_stepper_next', '下一步'], - ['ui_stepper_finish', '完成'], - ['ui_processing_loading', '加载中...'], - ['ui_processing_cancel', '取消'], - ['ui_processing_canceling', '取消中...'], - ['ui_processing_retry', '重试'], - ['ui_processing_ok', '好'], - ['ui_processing_create', '创建'], - ['ui_processing_save', '保存'], - ['ui_configure', '配置'], - ['ui_apply', '应用'], - ['ui_error_message', '错误:'], - ['ui_error_close', '关闭'], - ['ui_close', '关闭'], - ['ui_errors_details', '详情'], - ['ui_search', '搜索...'], - ['ui_delete', '删除'], - ['ui_add', '添加'], - ['ui_refresh', '刷新'], - ['ui_data_saving_error', '保存出错'], - ['ui_data_delete_confirmation', '删除确认'], - ['ui_no_matches_placeholder', '您的搜索没有返回匹配项。'], - ['ui_information', '信息'], - ['ui_clipboard', '剪贴板'], - ['ui_copy_to_clipboard', '复制'], - ['ui_copy_to_clipboard_copied', '已复制'], - ['ui_copy_to_clipboard_failed_to_copy', '复制失败'], - ['ui_clipboard_access_denied_title', '剪贴板访问被拒绝'], - ['ui_clipboard_access_denied_message', '您需要授予对剪贴板的访问权限才能使用某些应用功能'], - ['ui_reveal_password', '展示或隐藏密码'], - ['ui_page_not_found', '找不到页面'], - ['ui_revoke', '撤销'], - ['ui_grant', '授予'], - ['ui_edit', '编辑'], - ['ui_open_in_tab', '在选项卡中打开'], - ['ui_rename', '重命名'], - ['ui_rename_processing', '重命名中...'], - ['ui_name', '名称'], - ['ui_cant_delete_item', "此项目不能删除"], - ['ui_no_items_placeholder', '还没有项目'], - ['ui_search_no_result_placeholder', '没有找到结果'], - ['ui_save_reminder', '您有未保存的更改'], - ['ui_yes', '是'], - ['ui_no', '否'], - ['ui_select_all', '选择所有'], - ['ui_reconnect', '重新连接'], - ['ui_forget', '忘记'], - ['ui_set', '设置'], - ['ui_are_you_sure', '您确定吗?'], - ['ui_options', '选项'], - ['ui_changes_might_be_lost', '您的更改可能会丢失'], - ['ui_file', '文件'], - ['ui_changes_will_be_lost', '您的更改将丢失'], - ['ui_continue', '继续'], - ['ui_authentication', '认证'], + ['name', 'zh'], + ['ui_tag_new', '新建'], + ['ui_light_theme', '明亮'], + ['ui_dark_theme', '黑暗'], + ['ui_stepper_back', '返回'], + ['ui_stepper_next', '下一步'], + ['ui_stepper_finish', '完成'], + ['ui_processing_loading', '加载中...'], + ['ui_processing_cancel', '取消'], + ['ui_processing_canceling', '取消中...'], + ['ui_processing_retry', '重试'], + ['ui_processing_ok', '好'], + ['ui_processing_create', '创建'], + ['ui_processing_save', '保存'], + ['ui_configure', '配置'], + ['ui_apply', '应用'], + ['ui_error_message', '错误:'], + ['ui_error_close', '关闭'], + ['ui_close', '关闭'], + ['ui_errors_details', '详情'], + ['ui_search', '搜索...'], + ['ui_delete', '删除'], + ['ui_add', '添加'], + ['ui_refresh', '刷新'], + ['ui_data_saving_error', '保存出错'], + ['ui_data_delete_confirmation', '删除确认'], + ['ui_no_matches_placeholder', '您的搜索没有返回匹配项。'], + ['ui_information', '信息'], + ['ui_clipboard', '剪贴板'], + ['ui_copy_to_clipboard', '复制'], + ['ui_copy_to_clipboard_copied', '已复制'], + ['ui_copy_to_clipboard_failed_to_copy', '复制失败'], + ['ui_clipboard_access_denied_title', '剪贴板访问被拒绝'], + ['ui_clipboard_access_denied_message', '您需要授予对剪贴板的访问权限才能使用某些应用功能'], + ['ui_reveal_password', '展示或隐藏密码'], + ['ui_page_not_found', '找不到页面'], + ['ui_revoke', '撤销'], + ['ui_grant', '授予'], + ['ui_edit', '编辑'], + ['ui_open_in_tab', '在选项卡中打开'], + ['ui_rename', '重命名'], + ['ui_rename_processing', '重命名中...'], + ['ui_name', '名称'], + ['ui_cant_delete_item', "此项目不能删除"], + ['ui_no_items_placeholder', '还没有项目'], + ['ui_search_no_result_placeholder', '没有找到结果'], + ['ui_save_reminder', '您有未保存的更改'], + ['ui_yes', '是'], + ['ui_no', '否'], + ['ui_select_all', '选择所有'], + ['ui_reconnect', '重新连接'], + ['ui_forget', '忘记'], + ['ui_set', '设置'], + ['ui_are_you_sure', '您确定吗?'], + ['ui_options', '选项'], + ['ui_changes_might_be_lost', '您的更改可能会丢失'], + ['ui_file', '文件'], + ['ui_changes_will_be_lost', '您的更改将丢失'], + ['ui_continue', '继续'], + ['ui_authentication', '认证'], + ['ui_settings', 'Settings'], - ['root_permission_denied', "您没有权限"], - ['root_permission_no_permission', "您没有权限执行此操作"], - ['app_root_session_expire_warning_title', '会话即将过期'], - ['app_root_session_expire_warning_message', '您的会话将在5分钟内到期。要继续工作,请关闭此弹出窗口。'], - ['app_root_session_expire_warning_button', '我在'], - ['app_root_session_expired_title', '会话已过期'], - ['app_root_session_expired_message', '会话已过期。您想重新加载吗?'], - ['app_root_session_expired_reload', '重新加载'], - ['app_root_quota_exceeded', '超出配额'], - ['core_eventsLog_dbeaverErrorDetails', '错误详情'], - ]; + ['root_permission_denied', "您没有权限"], + ['root_permission_no_permission', "您没有权限执行此操作"], + ['app_root_session_expire_warning_title', '会话即将过期'], + ['app_root_session_expire_warning_message', '您的会话将在5分钟内到期。要继续工作,请关闭此弹出窗口。'], + ['app_root_session_expire_warning_button', '我在'], + ['app_root_session_expired_title', '会话已过期'], + ['app_root_session_expired_message', '会话已过期。您想重新加载吗?'], + ['app_root_session_expired_reload', '重新加载'], + ['app_root_quota_exceeded', '超出配额'], + ['core_eventsLog_dbeaverErrorDetails', '错误详情'], +]; diff --git a/webapp/packages/core-theming/src/ThemeService.ts b/webapp/packages/core-theming/src/ThemeService.ts index af582d8fdc..9da9539987 100644 --- a/webapp/packages/core-theming/src/ThemeService.ts +++ b/webapp/packages/core-theming/src/ThemeService.ts @@ -18,8 +18,7 @@ import { DbeaverError, NotificationService } from '@cloudbeaver/core-events'; import { SettingsService } from '@cloudbeaver/core-settings'; import { themes } from './themes'; -import { defaultThemeSettings } from './ThemeSettingsService'; -import { ThemeSettingsService } from './ThemeSettingsService'; +import { defaultThemeSettings, ThemeSettingsService } from './ThemeSettingsService'; import type { ClassCollection } from './themeUtils'; const COMMON_STYLES: any[] = []; @@ -82,11 +81,10 @@ export class ThemeService extends Bootstrap { }); } - register(): void { - this.loadAllThemes(); - } + register(): void { } async load(): Promise { + this.loadAllThemes(); this.setCurrentThemeId(this.defaultThemeId); this.settingsService.registerSettings(this.settings, THEME_SETTINGS_KEY); await this.changeThemeAsync(this.currentThemeId); diff --git a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.tsx b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.tsx index 2a395712eb..de867e2098 100644 --- a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.tsx +++ b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.tsx @@ -8,21 +8,29 @@ import { observer } from 'mobx-react-lite'; import { useCallback } from 'react'; -import { MenuSeparator } from 'reakit'; +import { MenuInitialState, MenuSeparator } from 'reakit'; import styled, { use } from 'reshadow'; +import { useService } from '@cloudbeaver/core-di'; import { useStyles, ComponentStyle } from '@cloudbeaver/core-theming'; -import { IMenuActionItem, IMenuData, IMenuItem, MenuActionItem, MenuBaseItem, MenuSeparatorItem } from '@cloudbeaver/core-view'; +import { DATA_CONTEXT_MENU_NESTED, IMenuActionItem, IMenuData, IMenuItem, MenuActionItem, MenuBaseItem, MenuSeparatorItem, MenuService, MenuSubMenuItem, useMenu } from '@cloudbeaver/core-view'; +import { ContextMenu } from '../MenuTrigger'; import { MenuBarItem } from './MenuBarItem'; +interface INestedMenuSettings extends MenuInitialState { + onVisibleSwitch?: (visible: boolean) => void; +} + interface IMenuBarProps extends Omit, 'style'> { menu: IMenuData; + nestedMenuSettings?: INestedMenuSettings; style?: ComponentStyle; } export const MenuBar = observer(function MenuBar({ menu, + nestedMenuSettings, style, ...props }, ref) { @@ -40,6 +48,7 @@ export const MenuBar = observer(function MenuBar( key={item.id} item={item} menuData={menu} + nestedMenuSettings={nestedMenuSettings} style={style} /> ))} @@ -50,16 +59,33 @@ export const MenuBar = observer(function MenuBar( interface IMenuBarElementProps { item: IMenuItem; menuData: IMenuData; + nestedMenuSettings?: INestedMenuSettings; style?: ComponentStyle; } -const MenuBarElement = observer(function MenuBarElement({ item, style }) { +const MenuBarElement = observer(function MenuBarElement({ + item, + menuData, + nestedMenuSettings, + style +}) { const styles = useStyles(style); const onClick = useCallback(() => { item.events?.onSelect?.(); }, [item]); + if (item instanceof MenuSubMenuItem) { + return styled(styles)( + + ); + } + if (item instanceof MenuSeparatorItem) { return styled(styles)( @@ -127,3 +153,48 @@ const MenuBarAction = observer(function MenuBarAction({ ite /> ); }); + +interface ISubMenuItemProps { + item: MenuSubMenuItem; + menuData: IMenuData; + nestedMenuSettings?: INestedMenuSettings; + style?: ComponentStyle; +} + +const SubMenuItem = observer(function SubmenuItem({ + item, + menuData, + nestedMenuSettings, + style +}) { + const styles = useStyles(style); + const menuService = useService(MenuService); + const subMenuData = useMenu(item.menu, menuData.context); + + const handler = menuService.getHandler(menuData.context); + const loading = handler?.isLoading?.(menuData.context); + const disabled = handler?.isDisabled?.(menuData.context); + + subMenuData.context.set(DATA_CONTEXT_MENU_NESTED, true); + + return styled(styles)( + + + + ); +}); \ No newline at end of file diff --git a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBarItem.tsx b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBarItem.tsx index d14d2af035..a28363cae2 100644 --- a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBarItem.tsx +++ b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBarItem.tsx @@ -22,22 +22,23 @@ interface Props extends Omit(function MenuBarItem({ +export const MenuBarItem = observer(function MenuBarItem({ label, loading = false, icon, viewBox = '0 0 24 24', style = [], ...rest -}) { +}, ref) { const translate = useTranslate(); loading = useStateDelay(loading, 100); + const title = translate(rest.title); return styled(useStyles(style))( - + {loading && } {!loading && icon && } {label && } ); -}); +}, { forwardRef: true }); diff --git a/webapp/packages/core-ui/src/ContextMenu/MenuPanelItem.tsx b/webapp/packages/core-ui/src/ContextMenu/MenuPanelItem.tsx index 28af6f7a05..55eeef8e43 100644 --- a/webapp/packages/core-ui/src/ContextMenu/MenuPanelItem.tsx +++ b/webapp/packages/core-ui/src/ContextMenu/MenuPanelItem.tsx @@ -40,7 +40,7 @@ export const MenuPanelItem = observer(function MenuPanelItem loading = useStateDelay(loading, 100); return styled(useStyles(menuPanelStyles, style))( - + {typeof icon === 'string' ? : icon} diff --git a/webapp/packages/core-view/src/Menu/MenuItem/MenuBaseItem.ts b/webapp/packages/core-view/src/Menu/MenuItem/MenuBaseItem.ts index 9735b906ab..281c74eaf0 100644 --- a/webapp/packages/core-view/src/Menu/MenuItem/MenuBaseItem.ts +++ b/webapp/packages/core-view/src/Menu/MenuItem/MenuBaseItem.ts @@ -11,20 +11,27 @@ import type { IMenuItemEvents } from './IMenuItem'; import { MenuItem } from './MenuItem'; export class MenuBaseItem extends MenuItem implements IMenuBaseItem { + private readonly isDisabled?: () => boolean; + readonly label: string; readonly icon?: string; readonly tooltip?: string; readonly hidden?: boolean; - readonly disabled?: boolean; + + get disabled(): boolean { + return this.isDisabled?.() ?? false; + } constructor( id: string, label: string, tooltip?: string, - events?: IMenuItemEvents + events?: IMenuItemEvents, + isDisabled?: () => boolean ) { super(id, events); this.label = label; this.tooltip = tooltip; + this.isDisabled = isDisabled; } } diff --git a/webapp/packages/plugin-administration/src/PluginBootstrap.ts b/webapp/packages/plugin-administration/src/PluginBootstrap.ts index 9c5c08677c..1bcaa3e5f3 100644 --- a/webapp/packages/plugin-administration/src/PluginBootstrap.ts +++ b/webapp/packages/plugin-administration/src/PluginBootstrap.ts @@ -7,45 +7,67 @@ */ import { EAdminPermission, AdministrationScreenService } from '@cloudbeaver/core-administration'; -import { SettingsMenuService } from '@cloudbeaver/core-app'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { PermissionsService } from '@cloudbeaver/core-root'; import { ScreenService } from '@cloudbeaver/core-routing'; +import { DATA_CONTEXT_MENU, MenuBaseItem, MenuService } from '@cloudbeaver/core-view'; +import { TOP_NAV_BAR_SETTINGS_MENU } from '@cloudbeaver/plugin-settings-menu'; @injectable() export class PluginBootstrap extends Bootstrap { constructor( - private settingsMenuService: SettingsMenuService, - private permissionsService: PermissionsService, - private screenService: ScreenService, - private administrationScreenService: AdministrationScreenService, + private readonly permissionsService: PermissionsService, + private readonly screenService: ScreenService, + private readonly administrationScreenService: AdministrationScreenService, + private readonly menuService: MenuService ) { super(); } register(): void { - this.settingsMenuService.addMenuItem( - SettingsMenuService.settingsMenuToken, - { - id: 'administrationMenuEnter', - order: 0, - isHidden: () => !this.permissionsService.has(EAdminPermission.admin) - || this.screenService.isActive(AdministrationScreenService.screenName), - title: 'administration_menu_enter', - onClick: () => this.administrationScreenService.navigateToRoot(), + this.menuService.addCreator({ + isApplicable: (context) => context.get(DATA_CONTEXT_MENU) === TOP_NAV_BAR_SETTINGS_MENU, + getItems: (context, items) => { + const administrationScreen = this.screenService.isActive(AdministrationScreenService.screenName); + + if (this.permissionsService.has(EAdminPermission.admin) && !administrationScreen) { + return [ + ...items, + new MenuBaseItem( + 'administrationMenuEnter', + 'administration_menu_enter', + 'administration_menu_enter', + { onSelect: () => this.administrationScreenService.navigateToRoot() } + ) + ] + } + + if (administrationScreen) { + return [ + ...items, + new MenuBaseItem( + 'administrationMenuBack', + 'administration_menu_back', + 'administration_menu_back', + { onSelect: () => this.screenService.navigateToRoot() } + ) + ] + } + + return items; + }, + orderItems: (context, items) => { + const index = items.findIndex(item => item.id === 'administrationMenuBack' || item.id === 'administrationMenuEnter'); + + if (index > -1) { + const item = items.splice(index, 1); + items.unshift(item[0]); + } + + return items; } - ); - this.settingsMenuService.addMenuItem( - SettingsMenuService.settingsMenuToken, - { - id: 'administrationMenuBack', - order: 0, - isHidden: () => !this.screenService.isActive(AdministrationScreenService.screenName), - title: 'administration_menu_back', - onClick: () => this.screenService.navigateToRoot(), - } - ); + }); } - load(): void {} + load(): void { } } diff --git a/webapp/packages/plugin-authentication/src/PluginBootstrap.ts b/webapp/packages/plugin-authentication/src/PluginBootstrap.ts index b77d099f44..f777714c4c 100644 --- a/webapp/packages/plugin-authentication/src/PluginBootstrap.ts +++ b/webapp/packages/plugin-authentication/src/PluginBootstrap.ts @@ -6,10 +6,11 @@ * you may not use this file except in compliance with the License. */ -import { SettingsMenuService } from '@cloudbeaver/core-app'; import { AuthInfoService } from '@cloudbeaver/core-authentication'; import { injectable, Bootstrap } from '@cloudbeaver/core-di'; import { ServerConfigResource } from '@cloudbeaver/core-root'; +import { DATA_CONTEXT_MENU, MenuBaseItem, MenuService } from '@cloudbeaver/core-view'; +import { TOP_NAV_BAR_SETTINGS_MENU } from '@cloudbeaver/plugin-settings-menu'; import { AuthenticationService } from './AuthenticationService'; import { AuthDialogService } from './Dialog/AuthDialogService'; @@ -21,33 +22,52 @@ export class PluginBootstrap extends Bootstrap { private readonly authDialogService: AuthDialogService, private readonly authenticationService: AuthenticationService, private readonly authInfoService: AuthInfoService, - private readonly settingsMenuService: SettingsMenuService, + private readonly menuService: MenuService, ) { super(); } register(): void { - this.settingsMenuService.addMenuItem( - SettingsMenuService.settingsMenuToken, - { - id: 'login', - order: 0, - isHidden: () => this.serverConfigResource.enabledAuthProviders.length === 0 || !!this.authInfoService.userInfo, - title: 'authentication_login', - onClick: () => this.authDialogService.showLoginForm(false, null, true), - } - ); + this.menuService.addCreator({ + isApplicable: (context) => context.get(DATA_CONTEXT_MENU) === TOP_NAV_BAR_SETTINGS_MENU, + getItems: (context, items) => { + if (this.serverConfigResource.enabledAuthProviders.length > 0 && !this.authInfoService.userInfo) { + return [ + ...items, + new MenuBaseItem( + 'login', + 'authentication_login', + 'authentication_login', + { onSelect: () => this.authDialogService.showLoginForm(false, null, true) } + ) + ] + }; - this.settingsMenuService.addMenuItem( - SettingsMenuService.settingsMenuToken, - { - id: 'logout', - order: Number.MAX_SAFE_INTEGER, - isHidden: () => !this.authInfoService.userInfo, - title: 'authentication_logout', - onClick: this.authenticationService.logout.bind(this.authenticationService), + if (this.authInfoService.userInfo) { + return [ + ...items, + new MenuBaseItem( + 'logout', + 'authentication_logout', + 'authentication_logout', + { onSelect: this.authenticationService.logout.bind(this.authenticationService) } + ) + ] + } + + return items; + }, + orderItems: (context, items) => { + const index = items.findIndex(item => item.id === 'logout' || item.id === 'login') + + if (index > -1) { + const item = items.splice(index, 1); + items.push(item[0]); + } + + return items; } - ); + }); } load(): void | Promise { } diff --git a/webapp/packages/plugin-localization/.gitignore b/webapp/packages/plugin-localization/.gitignore new file mode 100644 index 0000000000..15bc16c7c3 --- /dev/null +++ b/webapp/packages/plugin-localization/.gitignore @@ -0,0 +1,17 @@ +# dependencies +/node_modules + +# testing +/coverage + +# production +/lib + +# misc +.DS_Store +.env* + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/webapp/packages/plugin-localization/package.json b/webapp/packages/plugin-localization/package.json new file mode 100644 index 0000000000..928e338498 --- /dev/null +++ b/webapp/packages/plugin-localization/package.json @@ -0,0 +1,46 @@ +{ + "name": "@cloudbeaver/plugin-localization", + "sideEffects": false, + "version": "0.1.0", + "description": "", + "license": "Apache-2.0", + "main": "lib/index.js", + "module": "lib/index.js", + "types": "src/index.ts", + "scripts": { + "prebuild": "rimraf lib/*", + "build": "webpack --config ../../configs/webpack.plugin.config.js", + "postbuild": "tsc", + "lint": "eslint ./src/ --ext .ts,.tsx", + "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix" + }, + "peerDependencies": { + "@cloudbeaver/core-administration": "^0.1.0", + "@cloudbeaver/core-app": "^0.1.0", + "@cloudbeaver/core-authentication": "^0.1.0", + "@cloudbeaver/core-blocks": "^0.1.0", + "@cloudbeaver/core-connections": "^0.1.0", + "@cloudbeaver/core-di": "^0.1.0", + "@cloudbeaver/core-dialogs": "^0.1.0", + "@cloudbeaver/core-events": "^0.1.0", + "@cloudbeaver/core-executor": "^0.1.0", + "@cloudbeaver/core-extensions": "^0.1.0", + "@cloudbeaver/core-localization": "^0.1.0", + "@cloudbeaver/core-notifications": "^0.1.0", + "@cloudbeaver/core-plugin": "^0.1.0", + "@cloudbeaver/core-product": "^0.1.0", + "@cloudbeaver/core-root": "^0.1.0", + "@cloudbeaver/core-routing": "^0.1.0", + "@cloudbeaver/core-sdk": "^0.1.0", + "@cloudbeaver/core-settings": "^0.1.0", + "@cloudbeaver/core-theming": "^0.1.0", + "@cloudbeaver/core-ui": "^0.1.0", + "@cloudbeaver/core-utils": "^0.1.0", + "@cloudbeaver/core-view": "^0.1.0", + "@cloudbeaver/plugin-settings-menu": "^0.1.0", + "react": "^17.x.x", + "mobx": "^6.x.x", + "mobx-react-lite": "^3.x.x", + "reshadow": "^0.x.x" + } +} \ No newline at end of file diff --git a/webapp/packages/plugin-localization/src/LOCALIZATION_MENU.ts b/webapp/packages/plugin-localization/src/LOCALIZATION_MENU.ts new file mode 100644 index 0000000000..7959940007 --- /dev/null +++ b/webapp/packages/plugin-localization/src/LOCALIZATION_MENU.ts @@ -0,0 +1,11 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 LOCALIZATION_MENU = createMenu('localization-menu', 'app_shared_settingsMenu_lang'); diff --git a/webapp/packages/plugin-localization/src/PluginBootstrap.ts b/webapp/packages/plugin-localization/src/PluginBootstrap.ts new file mode 100644 index 0000000000..21c2774290 --- /dev/null +++ b/webapp/packages/plugin-localization/src/PluginBootstrap.ts @@ -0,0 +1,77 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { ServerConfigResource } from '@cloudbeaver/core-root'; +import { LocalizationService } from '@cloudbeaver/core-localization'; +import { DATA_CONTEXT_MENU, MenuBaseItem, MenuService } from '@cloudbeaver/core-view'; +import { TOP_NAV_BAR_SETTINGS_MENU } from '@cloudbeaver/plugin-settings-menu'; + +import { LOCALIZATION_MENU } from './LOCALIZATION_MENU'; + +@injectable() +export class PluginBootstrap extends Bootstrap { + constructor( + private readonly localizationService: LocalizationService, + private readonly menuService: MenuService, + private readonly serverConfigResource: ServerConfigResource + ) { + super(); + } + + register(): void | Promise { + this.menuService.setHandler({ + id: 'localization-menu', + isApplicable: (context) => context.get(DATA_CONTEXT_MENU) === LOCALIZATION_MENU, + isLoading: () => this.serverConfigResource.isLoading(), + handler: () => this.serverConfigResource.load(), + }) + + this.menuService.addCreator({ + isApplicable: context => { + return context.get(DATA_CONTEXT_MENU) === TOP_NAV_BAR_SETTINGS_MENU && !!this.serverConfigResource.data?.supportedLanguages.length + }, + getItems(context, items) { + return [ + ...items, + LOCALIZATION_MENU + ] + }, + }); + + this.menuService.addCreator({ + isApplicable: context => context.get(DATA_CONTEXT_MENU) === LOCALIZATION_MENU, + getItems: (context, items) => { + const supportedLanguages = this.serverConfigResource.data?.supportedLanguages; + + if (!supportedLanguages?.length) { + return items; + } + + const languages = supportedLanguages.map(lang => { + const label = lang.nativeName || lang.isoCode; + + return new MenuBaseItem( + lang.isoCode, + label, + label, + { onSelect: () => this.localizationService.changeLocaleAsync(lang.isoCode) }, + () => this.localizationService.getCurrentLanguage() === lang.isoCode + ) + }) + + return [ + ...items, + ...languages + ] + }, + }); + } + + load(): void | Promise { } +} \ No newline at end of file diff --git a/webapp/packages/plugin-localization/src/index.ts b/webapp/packages/plugin-localization/src/index.ts new file mode 100644 index 0000000000..e327083077 --- /dev/null +++ b/webapp/packages/plugin-localization/src/index.ts @@ -0,0 +1,5 @@ +import { localizationPlugin } from './manifest'; + +export default localizationPlugin; + +export * from './LOCALIZATION_MENU'; \ No newline at end of file diff --git a/webapp/packages/plugin-localization/src/manifest.ts b/webapp/packages/plugin-localization/src/manifest.ts new file mode 100644 index 0000000000..c6a50f309c --- /dev/null +++ b/webapp/packages/plugin-localization/src/manifest.ts @@ -0,0 +1,17 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { PluginManifest } from '@cloudbeaver/core-di'; +import { PluginBootstrap } from './PluginBootstrap'; + +export const localizationPlugin: PluginManifest = { + info: { name: 'Localization plugin' }, + providers: [ + PluginBootstrap, + ], +}; \ No newline at end of file diff --git a/webapp/packages/plugin-localization/tsconfig.json b/webapp/packages/plugin-localization/tsconfig.json new file mode 100644 index 0000000000..9098ccfdca --- /dev/null +++ b/webapp/packages/plugin-localization/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "lib", + "composite": true + }, + "include": [ + "src" + ], + "exclude": [ + "**/node_modules", + "**/.*/" + ], +} \ No newline at end of file diff --git a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Item.tsx b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Item.tsx index 8ef2f962e0..a8a404e4d1 100644 --- a/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Item.tsx +++ b/webapp/packages/plugin-object-viewer/src/ObjectPropertiesPage/ObjectPropertyTable/Item.tsx @@ -54,6 +54,12 @@ const itemStyles = css` menu-icon { padding-right: 8px; } + menu-name { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + max-width: 500px; + } TableColumnValue:not(:hover):not([|menuOpened]) { & Icon { opacity: 0; @@ -185,7 +191,7 @@ const ItemName = observer(function ItemName({ )} - {name} + {name} ); diff --git a/webapp/packages/plugin-product/src/ProductBootstrap.ts b/webapp/packages/plugin-product/src/ProductBootstrap.ts index 1466f90290..822316aec3 100644 --- a/webapp/packages/plugin-product/src/ProductBootstrap.ts +++ b/webapp/packages/plugin-product/src/ProductBootstrap.ts @@ -6,36 +6,40 @@ * you may not use this file except in compliance with the License. */ -import { SettingsMenuService } from '@cloudbeaver/core-app'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { CommonDialogService } from '@cloudbeaver/core-dialogs'; +import { DATA_CONTEXT_MENU, MenuBaseItem, MenuService } from '@cloudbeaver/core-view'; import { ServerConfigResource } from '@cloudbeaver/core-root'; +import { TOP_NAV_BAR_SETTINGS_MENU } from '@cloudbeaver/plugin-settings-menu'; import { ProductInfoDialog } from './ProductInfoDialog'; @injectable() export class ProductBootstrap extends Bootstrap { - private productInfoMenuToken = 'productInfoMenu'; - constructor( - private settingsMenuService: SettingsMenuService, - private serverConfigResource: ServerConfigResource, - private commonDialogService: CommonDialogService + private readonly serverConfigResource: ServerConfigResource, + private readonly commonDialogService: CommonDialogService, + private readonly menuService: MenuService, ) { super(); } register(): void | Promise { - this.settingsMenuService.addMenuItem( - SettingsMenuService.settingsMenuToken, - { - id: this.productInfoMenuToken, - order: 3, - title: 'app_product_info', - isHidden: () => !this.serverConfigResource.data?.productInfo, - onClick: async () => await this.commonDialogService.open(ProductInfoDialog, null), - } - ); + this.menuService.addCreator({ + isApplicable: (context) => { + return context.get(DATA_CONTEXT_MENU) === TOP_NAV_BAR_SETTINGS_MENU && !!this.serverConfigResource.data?.productInfo + }, + getItems: (context, items) => [ + ...items, + new MenuBaseItem( + 'productInfo', + 'app_product_info', + 'app_product_info', + { onSelect: () => this.commonDialogService.open(ProductInfoDialog, null) } + ) + ] + + }); } load(): void | Promise { } diff --git a/webapp/packages/plugin-settings-menu-administration/.gitignore b/webapp/packages/plugin-settings-menu-administration/.gitignore new file mode 100644 index 0000000000..15bc16c7c3 --- /dev/null +++ b/webapp/packages/plugin-settings-menu-administration/.gitignore @@ -0,0 +1,17 @@ +# dependencies +/node_modules + +# testing +/coverage + +# production +/lib + +# misc +.DS_Store +.env* + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/webapp/packages/plugin-settings-menu-administration/package.json b/webapp/packages/plugin-settings-menu-administration/package.json new file mode 100644 index 0000000000..305bdf6bd5 --- /dev/null +++ b/webapp/packages/plugin-settings-menu-administration/package.json @@ -0,0 +1,46 @@ +{ + "name": "@cloudbeaver/plugin-settings-menu-administration", + "sideEffects": false, + "version": "0.1.0", + "description": "", + "license": "Apache-2.0", + "main": "lib/index.js", + "module": "lib/index.js", + "types": "src/index.ts", + "scripts": { + "prebuild": "rimraf lib/*", + "build": "webpack --config ../../configs/webpack.plugin.config.js", + "postbuild": "tsc", + "lint": "eslint ./src/ --ext .ts,.tsx", + "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix" + }, + "peerDependencies": { + "@cloudbeaver/core-administration": "^0.1.0", + "@cloudbeaver/core-app": "^0.1.0", + "@cloudbeaver/core-authentication": "^0.1.0", + "@cloudbeaver/core-blocks": "^0.1.0", + "@cloudbeaver/core-connections": "^0.1.0", + "@cloudbeaver/core-di": "^0.1.0", + "@cloudbeaver/core-dialogs": "^0.1.0", + "@cloudbeaver/core-events": "^0.1.0", + "@cloudbeaver/core-executor": "^0.1.0", + "@cloudbeaver/core-extensions": "^0.1.0", + "@cloudbeaver/core-localization": "^0.1.0", + "@cloudbeaver/core-notifications": "^0.1.0", + "@cloudbeaver/core-plugin": "^0.1.0", + "@cloudbeaver/core-product": "^0.1.0", + "@cloudbeaver/core-root": "^0.1.0", + "@cloudbeaver/core-routing": "^0.1.0", + "@cloudbeaver/core-sdk": "^0.1.0", + "@cloudbeaver/core-settings": "^0.1.0", + "@cloudbeaver/core-theming": "^0.1.0", + "@cloudbeaver/core-ui": "^0.1.0", + "@cloudbeaver/core-utils": "^0.1.0", + "@cloudbeaver/core-view": "^0.1.0", + "@cloudbeaver/plugin-settings-menu": "^0.1.0", + "react": "^17.x.x", + "mobx": "^6.x.x", + "mobx-react-lite": "^3.x.x", + "reshadow": "^0.x.x" + } +} \ No newline at end of file diff --git a/webapp/packages/plugin-settings-menu-administration/src/PluginBootstrap.ts b/webapp/packages/plugin-settings-menu-administration/src/PluginBootstrap.ts new file mode 100644 index 0000000000..e934053e09 --- /dev/null +++ b/webapp/packages/plugin-settings-menu-administration/src/PluginBootstrap.ts @@ -0,0 +1,26 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { AdministrationTopAppBarService } from '@cloudbeaver/core-administration'; +import { Bootstrap, injectable } from '@cloudbeaver/core-di'; +import { SettingsMenu } from '@cloudbeaver/plugin-settings-menu'; + +@injectable() +export class PluginBootstrap extends Bootstrap { + constructor( + private readonly administrationTopAppBarService: AdministrationTopAppBarService + ) { + super(); + } + + register(): void | Promise { + this.administrationTopAppBarService.placeholder.add(SettingsMenu, 5); + } + + load(): void | Promise { } +} \ No newline at end of file diff --git a/webapp/packages/plugin-settings-menu-administration/src/index.ts b/webapp/packages/plugin-settings-menu-administration/src/index.ts new file mode 100644 index 0000000000..382e631fc7 --- /dev/null +++ b/webapp/packages/plugin-settings-menu-administration/src/index.ts @@ -0,0 +1,3 @@ +import { settingsMenuAdministrationPlugin } from './manifest'; + +export default settingsMenuAdministrationPlugin; \ No newline at end of file diff --git a/webapp/packages/plugin-settings-menu-administration/src/manifest.ts b/webapp/packages/plugin-settings-menu-administration/src/manifest.ts new file mode 100644 index 0000000000..c2cc10eb0a --- /dev/null +++ b/webapp/packages/plugin-settings-menu-administration/src/manifest.ts @@ -0,0 +1,17 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { PluginManifest } from '@cloudbeaver/core-di'; +import { PluginBootstrap } from './PluginBootstrap'; + +export const settingsMenuAdministrationPlugin: PluginManifest = { + info: { name: 'Settings menu administration plugin' }, + providers: [ + PluginBootstrap, + ], +}; \ No newline at end of file diff --git a/webapp/packages/plugin-settings-menu-administration/tsconfig.json b/webapp/packages/plugin-settings-menu-administration/tsconfig.json new file mode 100644 index 0000000000..9098ccfdca --- /dev/null +++ b/webapp/packages/plugin-settings-menu-administration/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "lib", + "composite": true + }, + "include": [ + "src" + ], + "exclude": [ + "**/node_modules", + "**/.*/" + ], +} \ No newline at end of file diff --git a/webapp/packages/plugin-settings-menu/.gitignore b/webapp/packages/plugin-settings-menu/.gitignore new file mode 100644 index 0000000000..15bc16c7c3 --- /dev/null +++ b/webapp/packages/plugin-settings-menu/.gitignore @@ -0,0 +1,17 @@ +# dependencies +/node_modules + +# testing +/coverage + +# production +/lib + +# misc +.DS_Store +.env* + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/webapp/packages/plugin-settings-menu/package.json b/webapp/packages/plugin-settings-menu/package.json new file mode 100644 index 0000000000..846928b787 --- /dev/null +++ b/webapp/packages/plugin-settings-menu/package.json @@ -0,0 +1,45 @@ +{ + "name": "@cloudbeaver/plugin-settings-menu", + "sideEffects": false, + "version": "0.1.0", + "description": "", + "license": "Apache-2.0", + "main": "lib/index.js", + "module": "lib/index.js", + "types": "src/index.ts", + "scripts": { + "prebuild": "rimraf lib/*", + "build": "webpack --config ../../configs/webpack.plugin.config.js", + "postbuild": "tsc", + "lint": "eslint ./src/ --ext .ts,.tsx", + "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix" + }, + "peerDependencies": { + "@cloudbeaver/core-administration": "^0.1.0", + "@cloudbeaver/core-app": "^0.1.0", + "@cloudbeaver/core-authentication": "^0.1.0", + "@cloudbeaver/core-blocks": "^0.1.0", + "@cloudbeaver/core-connections": "^0.1.0", + "@cloudbeaver/core-di": "^0.1.0", + "@cloudbeaver/core-dialogs": "^0.1.0", + "@cloudbeaver/core-events": "^0.1.0", + "@cloudbeaver/core-executor": "^0.1.0", + "@cloudbeaver/core-extensions": "^0.1.0", + "@cloudbeaver/core-localization": "^0.1.0", + "@cloudbeaver/core-notifications": "^0.1.0", + "@cloudbeaver/core-plugin": "^0.1.0", + "@cloudbeaver/core-product": "^0.1.0", + "@cloudbeaver/core-root": "^0.1.0", + "@cloudbeaver/core-routing": "^0.1.0", + "@cloudbeaver/core-sdk": "^0.1.0", + "@cloudbeaver/core-settings": "^0.1.0", + "@cloudbeaver/core-theming": "^0.1.0", + "@cloudbeaver/core-ui": "^0.1.0", + "@cloudbeaver/core-utils": "^0.1.0", + "@cloudbeaver/core-view": "^0.1.0", + "react": "^17.x.x", + "mobx": "^6.x.x", + "mobx-react-lite": "^3.x.x", + "reshadow": "^0.x.x" + } +} \ No newline at end of file diff --git a/webapp/packages/plugin-settings-menu/src/PluginBootstrap.ts b/webapp/packages/plugin-settings-menu/src/PluginBootstrap.ts new file mode 100644 index 0000000000..1dd1eb45ac --- /dev/null +++ b/webapp/packages/plugin-settings-menu/src/PluginBootstrap.ts @@ -0,0 +1,26 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { TopNavService } from '@cloudbeaver/core-app'; +import { Bootstrap, injectable } from '@cloudbeaver/core-di'; +import { SettingsMenu } from './SettingsMenu/SettingsMenu'; + +@injectable() +export class PluginBootstrap extends Bootstrap { + constructor( + private readonly topNavService: TopNavService + ) { + super(); + } + + register(): void | Promise { + this.topNavService.placeholder.add(SettingsMenu, 5); + } + + load(): void | Promise { } +} \ No newline at end of file diff --git a/webapp/packages/core-app/src/TopNavBar/SettingsMenu/SettingsMenu.tsx b/webapp/packages/plugin-settings-menu/src/SettingsMenu/SettingsMenu.tsx similarity index 58% rename from webapp/packages/core-app/src/TopNavBar/SettingsMenu/SettingsMenu.tsx rename to webapp/packages/plugin-settings-menu/src/SettingsMenu/SettingsMenu.tsx index ff4b750637..90d3de89a7 100644 --- a/webapp/packages/core-app/src/TopNavBar/SettingsMenu/SettingsMenu.tsx +++ b/webapp/packages/plugin-settings-menu/src/SettingsMenu/SettingsMenu.tsx @@ -9,21 +9,27 @@ import { observer } from 'mobx-react-lite'; import styled from 'reshadow'; -import { Icon } from '@cloudbeaver/core-blocks'; -import { useService } from '@cloudbeaver/core-di'; -import { MenuTrigger } from '@cloudbeaver/core-dialogs'; import { useStyles } from '@cloudbeaver/core-theming'; +import { ContextMenu } from '@cloudbeaver/core-ui'; +import { useMenu } from '@cloudbeaver/core-view'; +import { Icon } from '@cloudbeaver/core-blocks'; +import { topMenuStyles } from '@cloudbeaver/core-app'; -import { topMenuStyles } from '../shared/topMenuStyles'; -import { SettingsMenuService } from './SettingsMenuService'; import { settingsMenuStyles } from './settingsMenuStyles'; +import { TOP_NAV_BAR_SETTINGS_MENU } from './TOP_NAV_BAR_SETTINGS_MENU'; export const SettingsMenu = observer(function SettingsMenu() { - const settingsMenuService = useService(SettingsMenuService); + const menu = useMenu(TOP_NAV_BAR_SETTINGS_MENU); return styled(useStyles(settingsMenuStyles))( - + - + ); -}); +}); \ No newline at end of file diff --git a/webapp/packages/plugin-settings-menu/src/SettingsMenu/TOP_NAV_BAR_SETTINGS_MENU.ts b/webapp/packages/plugin-settings-menu/src/SettingsMenu/TOP_NAV_BAR_SETTINGS_MENU.ts new file mode 100644 index 0000000000..d5d1edf4df --- /dev/null +++ b/webapp/packages/plugin-settings-menu/src/SettingsMenu/TOP_NAV_BAR_SETTINGS_MENU.ts @@ -0,0 +1,11 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 TOP_NAV_BAR_SETTINGS_MENU = createMenu('top-nav-bar-settings', 'ui_settings', 'settings', 'ui_settings'); \ No newline at end of file diff --git a/webapp/packages/core-app/src/TopNavBar/SettingsMenu/settingsMenuStyles.ts b/webapp/packages/plugin-settings-menu/src/SettingsMenu/settingsMenuStyles.ts similarity index 97% rename from webapp/packages/core-app/src/TopNavBar/SettingsMenu/settingsMenuStyles.ts rename to webapp/packages/plugin-settings-menu/src/SettingsMenu/settingsMenuStyles.ts index d986965784..3eb50231e1 100644 --- a/webapp/packages/core-app/src/TopNavBar/SettingsMenu/settingsMenuStyles.ts +++ b/webapp/packages/plugin-settings-menu/src/SettingsMenu/settingsMenuStyles.ts @@ -9,7 +9,7 @@ import { css } from 'reshadow'; export const settingsMenuStyles = css` - MenuTrigger { + ContextMenu { padding: 0 16px !important; height: 48px; cursor: pointer; @@ -33,4 +33,4 @@ export const settingsMenuStyles = css` transform: rotate(90deg); } } -`; +`; \ No newline at end of file diff --git a/webapp/packages/plugin-settings-menu/src/index.ts b/webapp/packages/plugin-settings-menu/src/index.ts new file mode 100644 index 0000000000..72d060e4c2 --- /dev/null +++ b/webapp/packages/plugin-settings-menu/src/index.ts @@ -0,0 +1,6 @@ +import { settingsMenuPlugin } from './manifest'; + +export default settingsMenuPlugin; + +export * from './SettingsMenu/SettingsMenu'; +export * from './SettingsMenu/TOP_NAV_BAR_SETTINGS_MENU'; \ No newline at end of file diff --git a/webapp/packages/plugin-settings-menu/src/manifest.ts b/webapp/packages/plugin-settings-menu/src/manifest.ts new file mode 100644 index 0000000000..2c8a65935e --- /dev/null +++ b/webapp/packages/plugin-settings-menu/src/manifest.ts @@ -0,0 +1,17 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { PluginManifest } from '@cloudbeaver/core-di'; +import { PluginBootstrap } from './PluginBootstrap'; + +export const settingsMenuPlugin: PluginManifest = { + info: { name: 'Settings menu plugin' }, + providers: [ + PluginBootstrap, + ], +}; \ No newline at end of file diff --git a/webapp/packages/plugin-settings-menu/tsconfig.json b/webapp/packages/plugin-settings-menu/tsconfig.json new file mode 100644 index 0000000000..9098ccfdca --- /dev/null +++ b/webapp/packages/plugin-settings-menu/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "lib", + "composite": true + }, + "include": [ + "src" + ], + "exclude": [ + "**/node_modules", + "**/.*/" + ], +} \ No newline at end of file diff --git a/webapp/packages/plugin-theme/.gitignore b/webapp/packages/plugin-theme/.gitignore new file mode 100644 index 0000000000..15bc16c7c3 --- /dev/null +++ b/webapp/packages/plugin-theme/.gitignore @@ -0,0 +1,17 @@ +# dependencies +/node_modules + +# testing +/coverage + +# production +/lib + +# misc +.DS_Store +.env* + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/webapp/packages/plugin-theme/package.json b/webapp/packages/plugin-theme/package.json new file mode 100644 index 0000000000..4a0383fccb --- /dev/null +++ b/webapp/packages/plugin-theme/package.json @@ -0,0 +1,46 @@ +{ + "name": "@cloudbeaver/plugin-theme", + "sideEffects": false, + "version": "0.1.0", + "description": "", + "license": "Apache-2.0", + "main": "lib/index.js", + "module": "lib/index.js", + "types": "src/index.ts", + "scripts": { + "prebuild": "rimraf lib/*", + "build": "webpack --config ../../configs/webpack.plugin.config.js", + "postbuild": "tsc", + "lint": "eslint ./src/ --ext .ts,.tsx", + "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix" + }, + "peerDependencies": { + "@cloudbeaver/core-administration": "^0.1.0", + "@cloudbeaver/core-app": "^0.1.0", + "@cloudbeaver/core-authentication": "^0.1.0", + "@cloudbeaver/core-blocks": "^0.1.0", + "@cloudbeaver/core-connections": "^0.1.0", + "@cloudbeaver/core-di": "^0.1.0", + "@cloudbeaver/core-dialogs": "^0.1.0", + "@cloudbeaver/core-events": "^0.1.0", + "@cloudbeaver/core-executor": "^0.1.0", + "@cloudbeaver/core-extensions": "^0.1.0", + "@cloudbeaver/core-localization": "^0.1.0", + "@cloudbeaver/core-notifications": "^0.1.0", + "@cloudbeaver/core-plugin": "^0.1.0", + "@cloudbeaver/core-product": "^0.1.0", + "@cloudbeaver/core-root": "^0.1.0", + "@cloudbeaver/core-routing": "^0.1.0", + "@cloudbeaver/core-sdk": "^0.1.0", + "@cloudbeaver/core-settings": "^0.1.0", + "@cloudbeaver/core-theming": "^0.1.0", + "@cloudbeaver/core-ui": "^0.1.0", + "@cloudbeaver/core-utils": "^0.1.0", + "@cloudbeaver/core-view": "^0.1.0", + "@cloudbeaver/plugin-settings-menu": "^0.1.0", + "react": "^17.x.x", + "mobx": "^6.x.x", + "mobx-react-lite": "^3.x.x", + "reshadow": "^0.x.x" + } +} \ No newline at end of file diff --git a/webapp/packages/plugin-theme/src/PluginBootstrap.ts b/webapp/packages/plugin-theme/src/PluginBootstrap.ts new file mode 100644 index 0000000000..34903f2b69 --- /dev/null +++ b/webapp/packages/plugin-theme/src/PluginBootstrap.ts @@ -0,0 +1,62 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { ThemeService } from '@cloudbeaver/core-theming'; +import { DATA_CONTEXT_MENU, MenuBaseItem, MenuService } from '@cloudbeaver/core-view'; +import { TOP_NAV_BAR_SETTINGS_MENU } from '@cloudbeaver/plugin-settings-menu'; + +import { THEME_MENU } from './THEME_MENU'; + +@injectable() +export class PluginBootstrap extends Bootstrap { + constructor( + private readonly menuService: MenuService, + private readonly themeService: ThemeService + ) { + super(); + } + + register(): void | Promise { + this.menuService.addCreator({ + isApplicable: (context) => { + return context.get(DATA_CONTEXT_MENU) === TOP_NAV_BAR_SETTINGS_MENU && this.themeService.themes.length > 0; + }, + getItems(context, items) { + return [ + ...items, + THEME_MENU + ] + }, + }); + + this.menuService.addCreator({ + isApplicable(context) { + return context.get(DATA_CONTEXT_MENU) === THEME_MENU + }, + getItems: (context, items) => { + const themes = this.themeService.themes.map(theme => new MenuBaseItem( + theme.id, + theme.name, + theme.name, + { + onSelect: () => this.themeService.changeThemeAsync(theme.id) + }, + () => this.themeService.currentThemeId === theme.id, + )) + + return [ + ...items, + ...themes + ] + }, + }); + } + + load(): void | Promise { } +} \ No newline at end of file diff --git a/webapp/packages/plugin-theme/src/THEME_MENU.ts b/webapp/packages/plugin-theme/src/THEME_MENU.ts new file mode 100644 index 0000000000..97e217147d --- /dev/null +++ b/webapp/packages/plugin-theme/src/THEME_MENU.ts @@ -0,0 +1,11 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 THEME_MENU = createMenu('theme-menu', 'app_shared_settingsMenu_theme'); diff --git a/webapp/packages/plugin-theme/src/index.ts b/webapp/packages/plugin-theme/src/index.ts new file mode 100644 index 0000000000..eb4cbd399e --- /dev/null +++ b/webapp/packages/plugin-theme/src/index.ts @@ -0,0 +1,5 @@ +import { themePlugin } from './manifest'; + +export default themePlugin; + +export * from './THEME_MENU'; \ No newline at end of file diff --git a/webapp/packages/plugin-theme/src/manifest.ts b/webapp/packages/plugin-theme/src/manifest.ts new file mode 100644 index 0000000000..37a68924f3 --- /dev/null +++ b/webapp/packages/plugin-theme/src/manifest.ts @@ -0,0 +1,17 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 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 { PluginManifest } from '@cloudbeaver/core-di'; +import { PluginBootstrap } from './PluginBootstrap'; + +export const themePlugin: PluginManifest = { + info: { name: 'Theme plugin' }, + providers: [ + PluginBootstrap, + ], +}; \ No newline at end of file diff --git a/webapp/packages/plugin-theme/tsconfig.json b/webapp/packages/plugin-theme/tsconfig.json new file mode 100644 index 0000000000..9098ccfdca --- /dev/null +++ b/webapp/packages/plugin-theme/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "lib", + "composite": true + }, + "include": [ + "src" + ], + "exclude": [ + "**/node_modules", + "**/.*/" + ], +} \ No newline at end of file diff --git a/webapp/packages/product-default/package.json b/webapp/packages/product-default/package.json index e640c479c7..fda7e3e2e7 100644 --- a/webapp/packages/product-default/package.json +++ b/webapp/packages/product-default/package.json @@ -22,6 +22,8 @@ "@cloudbeaver/core-bootstrap": "0.1.0", "@cloudbeaver/core-di": "0.1.0", "@cloudbeaver/core-product": "0.1.0", + "@cloudbeaver/plugin-theme": "0.1.0", + "@cloudbeaver/plugin-localization": "0.1.0", "@cloudbeaver/plugin-administration": "0.1.0", "@cloudbeaver/plugin-authentication": "0.1.0", "@cloudbeaver/plugin-authentication-administration": "0.1.0", @@ -35,7 +37,9 @@ "@cloudbeaver/plugin-sql-editor": "0.1.0", "@cloudbeaver/plugin-sql-editor-navigation-tab": "0.1.0", "@cloudbeaver/plugin-sql-editor-screen": "0.1.0", - "@cloudbeaver/plugin-version-update-administration": "0.1.0" + "@cloudbeaver/plugin-version-update-administration": "0.1.0", + "@cloudbeaver/plugin-settings-menu": "0.1.0", + "@cloudbeaver/plugin-settings-menu-administration": "0.1.0" }, "product": { "name": "CloudBeaver Community" diff --git a/webapp/packages/product-default/src/index.ts b/webapp/packages/product-default/src/index.ts index 5c2fe71ee2..d0e21384ba 100644 --- a/webapp/packages/product-default/src/index.ts +++ b/webapp/packages/product-default/src/index.ts @@ -5,6 +5,8 @@ import type { PluginManifest } from '@cloudbeaver/core-di'; import administration from '@cloudbeaver/plugin-administration'; import authentication from '@cloudbeaver/plugin-authentication'; import authenticationAdministration from '@cloudbeaver/plugin-authentication-administration'; +import theme from '@cloudbeaver/plugin-theme'; +import localization from '@cloudbeaver/plugin-localization'; import connectionCustom from '@cloudbeaver/plugin-connection-custom'; import connectionTemplate from '@cloudbeaver/plugin-connection-template'; import connectionPlugin from '@cloudbeaver/plugin-connections'; @@ -22,6 +24,8 @@ import sqlEditorScreen from '@cloudbeaver/plugin-sql-editor-screen'; import userProfile from '@cloudbeaver/plugin-user-profile'; import userProfileAdministration from '@cloudbeaver/plugin-user-profile-administration'; import versionUpdate from '@cloudbeaver/plugin-version-update-administration'; +import settingsMenu from '@cloudbeaver/plugin-settings-menu'; +import settingsMenuAdministration from '@cloudbeaver/plugin-settings-menu-administration'; import { defaultProductManifest } from './manifest'; @@ -30,6 +34,8 @@ const PLUGINS: PluginManifest[] = [ spreadsheetNew, authentication, authenticationAdministration, + theme, + localization, connectionCustom, connectionTemplate, dataExport, @@ -47,6 +53,8 @@ const PLUGINS: PluginManifest[] = [ connectionAdministration, versionUpdate, productPlugin, + settingsMenu, + settingsMenuAdministration, ]; bootstrap(PLUGINS);