diff --git a/webapp/packages/core-app/src/locales/en.ts b/webapp/packages/core-app/src/locales/en.ts index fd7f802d03..6e153148cd 100644 --- a/webapp/packages/core-app/src/locales/en.ts +++ b/webapp/packages/core-app/src/locales/en.ts @@ -1,11 +1,4 @@ export default [ - ['app_shared_connectionMenu_newConnection', 'New connection'], - ['app_shared_connectionMenu_connection', 'Connection'], - ['app_shared_connectionMenu_custom', 'Manual'], - ['app_shared_connectionMenu_disconnect', 'Disconnect All'], - ['app_shared_toolsMenu_tools', 'Tools'], - ['app_shared_toolsMenu_logViewer', 'Log viewer'], - ['app_shared_toolsMenu_logViewer_placeholder', 'Log viewer is not active'], ['app_shared_settingsMenu_config', 'Configuration'], ['app_shared_settingsMenu_theme', 'Theme'], ['app_shared_settingsMenu_lang', 'Language'], diff --git a/webapp/packages/core-app/src/locales/it.ts b/webapp/packages/core-app/src/locales/it.ts index 57bda48d0d..42ff5da27d 100644 --- a/webapp/packages/core-app/src/locales/it.ts +++ b/webapp/packages/core-app/src/locales/it.ts @@ -1,11 +1,4 @@ export default [ - ['app_shared_connectionMenu_newConnection', 'Nuova connessione'], - ['app_shared_connectionMenu_connection', 'Connessione'], - ['app_shared_connectionMenu_custom', 'Manuale'], - ['app_shared_connectionMenu_disconnect', 'Scollegati da tutto'], - ['app_shared_toolsMenu_tools', 'Strumenti'], - ['app_shared_toolsMenu_logViewer', 'Visualizzatore del Log'], - ['app_shared_toolsMenu_logViewer_placeholder', 'Il visualizzatore del log non è attivo'], ['app_shared_settingsMenu_config', 'Configurazione'], ['app_shared_settingsMenu_theme', 'Tema'], ['app_shared_settingsMenu_lang', 'Lingua'], diff --git a/webapp/packages/core-app/src/locales/ru.ts b/webapp/packages/core-app/src/locales/ru.ts index 65641a84ee..2396cc821e 100644 --- a/webapp/packages/core-app/src/locales/ru.ts +++ b/webapp/packages/core-app/src/locales/ru.ts @@ -1,10 +1,4 @@ export default [ - ['app_shared_connectionMenu_connection', 'Подключение'], - ['app_shared_connectionMenu_custom', 'Создать...'], - ['app_shared_connectionMenu_disconnect', 'Отключить все'], - ['app_shared_toolsMenu_tools', 'Инструменты'], - ['app_shared_toolsMenu_logViewer', 'Просмотр лога'], - ['app_shared_toolsMenu_logViewer_placeholder', 'Просмотр логов неактивен'], ['app_shared_settingsMenu_config', 'Настройки'], ['app_shared_settingsMenu_theme', 'Тема'], ['app_shared_settingsMenu_lang', 'Язык'], diff --git a/webapp/packages/core-app/src/locales/zh.ts b/webapp/packages/core-app/src/locales/zh.ts index f3abaf8b5d..6af2aef6f6 100644 --- a/webapp/packages/core-app/src/locales/zh.ts +++ b/webapp/packages/core-app/src/locales/zh.ts @@ -1,11 +1,4 @@ export default [ - ['app_shared_connectionMenu_newConnection', '新连接'], - ['app_shared_connectionMenu_connection', '连接'], - ['app_shared_connectionMenu_custom', '手动'], - ['app_shared_connectionMenu_disconnect', '断开所有连接'], - ['app_shared_toolsMenu_tools', '工具'], - ['app_shared_toolsMenu_logViewer', '日志查看器'], - ['app_shared_toolsMenu_logViewer_placeholder', '日志查看器未激活'], ['app_shared_settingsMenu_config', '配置'], ['app_shared_settingsMenu_theme', '主题'], ['app_shared_settingsMenu_lang', '语言'], diff --git a/webapp/packages/core-authentication/src/DATA_CONTEXT_USER.ts b/webapp/packages/core-authentication/src/DATA_CONTEXT_USER.ts index 97593dd72e..60504d7161 100644 --- a/webapp/packages/core-authentication/src/DATA_CONTEXT_USER.ts +++ b/webapp/packages/core-authentication/src/DATA_CONTEXT_USER.ts @@ -9,4 +9,4 @@ import type { UserInfo } from '@cloudbeaver/core-sdk'; import { createDataContext } from '@cloudbeaver/core-view'; -export const DATA_CONTEXT_USER = createDataContext('user-info', null); +export const DATA_CONTEXT_USER = createDataContext('user-info', () => null); diff --git a/webapp/packages/core-blocks/src/Loader/Loader.tsx b/webapp/packages/core-blocks/src/Loader/Loader.tsx index f272458517..d25a751bbe 100644 --- a/webapp/packages/core-blocks/src/Loader/Loader.tsx +++ b/webapp/packages/core-blocks/src/Loader/Loader.tsx @@ -11,16 +11,14 @@ import { observer } from 'mobx-react-lite'; import { useState, useEffect, useContext, useRef } from 'react'; import styled, { use } from 'reshadow'; - import type { ComponentStyle } from '@cloudbeaver/core-theming'; -import { uuid } from '@cloudbeaver/core-utils'; +import { ILoadableState, uuid } from '@cloudbeaver/core-utils'; import { Button } from '../Button'; import { ExceptionMessage } from '../ExceptionMessage'; import { Translate } from '../localization/Translate'; import { StaticImage } from '../StaticImage'; import { useStyles } from '../useStyles'; -import type { ILoadableState } from './ILoadableState'; import { ILoaderContext, LoaderContext } from './LoaderContext'; import { loaderStyles, overlayStyles } from './loaderStyles'; @@ -276,11 +274,3 @@ export const Loader = observer(function Loader({ ); }); - -export function isContainsException(exception?: Error[] | Error | null): boolean { - if (Array.isArray(exception)) { - return exception.some(Boolean); - } - - return !!exception; -} diff --git a/webapp/packages/core-blocks/src/Loader/LoaderState.ts b/webapp/packages/core-blocks/src/Loader/LoaderState.ts index 80fd529905..ae95d8ba33 100644 --- a/webapp/packages/core-blocks/src/Loader/LoaderState.ts +++ b/webapp/packages/core-blocks/src/Loader/LoaderState.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import type { ILoadableState } from './ILoadableState'; +import type { ILoadableState } from '@cloudbeaver/core-utils'; export type LoaderState = ILoadableState | { loading: boolean; diff --git a/webapp/packages/core-blocks/src/Loader/useAutoLoad.ts b/webapp/packages/core-blocks/src/Loader/useAutoLoad.ts index 0a8b3f0afa..2be1e05518 100644 --- a/webapp/packages/core-blocks/src/Loader/useAutoLoad.ts +++ b/webapp/packages/core-blocks/src/Loader/useAutoLoad.ts @@ -8,18 +8,43 @@ import { useEffect } from 'react'; -import type { ILoadableState } from './ILoadableState'; +import { ILoadableState, isLoadableStateHasException } from '@cloudbeaver/core-utils'; + +import { getComputed } from '../getComputed'; export interface IAutoLoadable extends ILoadableState { load: () => void; } -export function useAutoLoad(state: IAutoLoadable, enabled = true) { - const canBeLoaded = enabled && !state.isLoaded(); +export function useAutoLoad(state: IAutoLoadable | IAutoLoadable[], enabled = true) { + if (!Array.isArray(state)) { + state = [state]; + } + + for (const loader of state as IAutoLoadable[]) { + getComputed( // activate mobx subscriptions + () => ( + ( + !loader.isLoaded() + || loader.isOutdated?.() === true + ) && !isLoadableStateHasException(loader) + ) + ); + } useEffect(() => { - if (canBeLoaded) { - state.load(); + if (!enabled) { + return; + } + + for (const loader of state as IAutoLoadable[]) { + if (isLoadableStateHasException(loader)) { + continue; + } + + if (!loader.isLoaded() || loader.isOutdated?.() === true) { + loader.load(); + } } }); } \ No newline at end of file diff --git a/webapp/packages/core-blocks/src/index.ts b/webapp/packages/core-blocks/src/index.ts index 9cd016638e..decccf2c91 100644 --- a/webapp/packages/core-blocks/src/index.ts +++ b/webapp/packages/core-blocks/src/index.ts @@ -18,7 +18,6 @@ export * from './layout/TopAppBar'; export * from './Loader/Loader'; export * from './Loader/useAutoLoad'; -export * from './Loader/ILoadableState'; export * from './localization/Translate'; export * from './localization/useTranslate'; diff --git a/webapp/packages/core-blocks/src/useDataResource.ts b/webapp/packages/core-blocks/src/useDataResource.ts index cd4e18e0a9..3a3976b8c9 100644 --- a/webapp/packages/core-blocks/src/useDataResource.ts +++ b/webapp/packages/core-blocks/src/useDataResource.ts @@ -11,9 +11,9 @@ import { useEffect, useState } from 'react'; import { IServiceConstructor, useService } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import { CachedDataResource, CachedDataResourceContext, CachedDataResourceGetter, CachedDataResourceParam, CachedResourceData, CachedResourceIncludeArgs, isResourceKeyList } from '@cloudbeaver/core-sdk'; +import type { ILoadableState } from '@cloudbeaver/core-utils'; import { getComputed } from './getComputed'; -import type { ILoadableState } from './Loader/ILoadableState'; import { useObjectRef } from './useObjectRef'; interface KeyWithIncludes { @@ -44,6 +44,7 @@ interface IMapResourceResult< resource: TResource; exception: Error | null; reload: () => void; + load: () => void; } export function useDataResource< @@ -183,6 +184,9 @@ export function useDataResource< setException(null); (refObj as any)[loadFunctionName](true); }, + load: () => { + (refObj as any)[loadFunctionName](); + }, isLoading: () => { if (refObj.key === null) { return false; diff --git a/webapp/packages/core-blocks/src/useMapResource.ts b/webapp/packages/core-blocks/src/useMapResource.ts index 30fcddc9d5..b2eaf0746f 100644 --- a/webapp/packages/core-blocks/src/useMapResource.ts +++ b/webapp/packages/core-blocks/src/useMapResource.ts @@ -12,10 +12,9 @@ import { useEffect, useState } from 'react'; import { IServiceConstructor, useService } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import { CachedResourceIncludeArgs, CachedMapResource, CachedMapResourceGetter, ResourceKey, CachedMapResourceValue, CachedMapResourceKey, CachedMapResourceArguments, CachedMapResourceLoader, ResourceKeyList, CachedMapResourceListGetter, isResourceKeyList } from '@cloudbeaver/core-sdk'; -import { isArraysEqual } from '@cloudbeaver/core-utils'; +import { ILoadableState, isArraysEqual } from '@cloudbeaver/core-utils'; import { getComputed } from './getComputed'; -import type { ILoadableState } from './Loader/ILoadableState'; import { useObservableRef } from './useObservableRef'; interface IActions< @@ -56,6 +55,7 @@ interface IMapResourceState> loaded: boolean; resource: TResource; isOutdated: () => boolean; + load: () => void; reload: () => void; } @@ -355,6 +355,9 @@ export function useMapResource< reload: () => { (refObj as any)[loadFunctionName](true); }, + load: () => { + (refObj as any)[loadFunctionName](); + }, isLoading() { return this.loading; }, diff --git a/webapp/packages/core-blocks/src/usePromiseState.ts b/webapp/packages/core-blocks/src/usePromiseState.ts index 97f9197f55..7233815984 100644 --- a/webapp/packages/core-blocks/src/usePromiseState.ts +++ b/webapp/packages/core-blocks/src/usePromiseState.ts @@ -10,8 +10,8 @@ import { computed, observable } from 'mobx'; import { useEffect } from 'react'; import { Task } from '@cloudbeaver/core-executor'; +import type { ILoadableState } from '@cloudbeaver/core-utils'; -import type { ILoadableState } from './Loader/ILoadableState'; import { useObservableRef } from './useObservableRef'; @@ -40,6 +40,7 @@ export function usePromiseState(promise: Promise | null): ILoadableState { isLoaded(): boolean { return this.promise === null; }, + load() {}, }), { cancel: computed, promise: observable.ref, diff --git a/webapp/packages/core-sdk/src/Resource/CachedDataResource.ts b/webapp/packages/core-sdk/src/Resource/CachedDataResource.ts index 25defb0779..f92ee77a6a 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedDataResource.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedDataResource.ts @@ -8,6 +8,8 @@ import { makeObservable, observable } from 'mobx'; +import type { ILoadableState } from '@cloudbeaver/core-utils'; + import { CachedResource } from './CachedResource'; import type { CachedResourceIncludeArgs, CachedResourceValueIncludes } from './CachedResourceIncludes'; @@ -87,3 +89,35 @@ export abstract class CachedDataResource< return this.data; } } + +export function getCachedDataResourceLoaderState< + TData, + TParam = void, + TKey = TParam, + TContext extends Record | void = void, +>( + resource: CachedDataResource, + param: TParam, + context: ContextArg +): ILoadableState { + return { + get exception() { + return resource.getException(param); + }, + isLoading() { + return resource.isDataLoading(param); + }, + isLoaded() { + return resource.isLoaded(param, context); + }, + isOutdated() { + return resource.isOutdated(param); + }, + load() { + return resource.load(param, context); + }, + reload() { + return resource.refresh(param, context); + }, + }; +} \ No newline at end of file diff --git a/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts b/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts index a282b28e27..a565cd4361 100644 --- a/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts +++ b/webapp/packages/core-sdk/src/Resource/CachedMapResource.ts @@ -9,7 +9,7 @@ import { action, computed, makeObservable, observable } from 'mobx'; import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor'; -import { isArraysEqual, MetadataMap } from '@cloudbeaver/core-utils'; +import { ILoadableState, isArraysEqual, MetadataMap } from '@cloudbeaver/core-utils'; import { CachedResource, CachedResourceKey, ICachedResourceMetadata } from './CachedResource'; import type { CachedResourceIncludeArgs, CachedResourceValueIncludes } from './CachedResourceIncludes'; @@ -475,3 +475,34 @@ export abstract class CachedMapResource< this.onDataOutdated.execute(key); } } + +export function getCachedMapResourceLoaderState< + TKey, + TValue, + TArguments = Record +>( + resource: CachedMapResource, + key: ResourceKey, + includes?: CachedResourceIncludeArgs | undefined +): ILoadableState { + return { + get exception() { + return resource.getException(key); + }, + isLoading() { + return resource.isDataLoading(key); + }, + isLoaded() { + return resource.isLoaded(key, includes); + }, + isOutdated() { + return resource.isOutdated(key); + }, + load() { + return resource.load(key, includes); + }, + reload() { + return resource.refresh(key, includes); + }, + }; +} diff --git a/webapp/packages/core-ui/src/ContextMenu/ContextMenu.tsx b/webapp/packages/core-ui/src/ContextMenu/ContextMenu.tsx index 93ea21b9ee..02998cac78 100644 --- a/webapp/packages/core-ui/src/ContextMenu/ContextMenu.tsx +++ b/webapp/packages/core-ui/src/ContextMenu/ContextMenu.tsx @@ -11,9 +11,8 @@ import { ButtonHTMLAttributes, forwardRef, useRef } from 'react'; import type { MenuInitialState } from 'reakit/Menu'; import styled from 'reshadow'; -import { getComputed, IMenuState, Menu, menuPanelStyles, useObjectRef, useTranslate, useStyles } from '@cloudbeaver/core-blocks'; +import { getComputed, IMenuState, Menu, menuPanelStyles, useObjectRef, useTranslate, useStyles, useAutoLoad } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; - import type { ComponentStyle } from '@cloudbeaver/core-theming'; import { IMenuData, MenuActionItem, MenuService } from '@cloudbeaver/core-view'; @@ -62,6 +61,8 @@ export const ContextMenu = observer(forwar const menu = useRef(); const styles = useStyles(menuPanelStyles, style); + useAutoLoad(menuData.loaders, !lazy); + const handlers = useObjectRef(() => ({ handleItemClose() { menu.current?.hide(); diff --git a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MENU_BAR_DEFAULT_STYLES.ts b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MENU_BAR_DEFAULT_STYLES.ts index c4f8e6148e..e06d9ef733 100644 --- a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MENU_BAR_DEFAULT_STYLES.ts +++ b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MENU_BAR_DEFAULT_STYLES.ts @@ -38,13 +38,13 @@ export const MENU_BAR_DEFAULT_STYLES = css` width: 24px; } - & item-label { + & menu-bar-item-label { display: block; text-transform: uppercase; font-weight: 700; } - & IconOrImage + item-label, & Loader + item-label { + & IconOrImage + menu-bar-item-label, & Loader + menu-bar-item-label { padding-left: 8px } } diff --git a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.tsx b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.tsx index cee63b73b1..2dfb113d8e 100644 --- a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.tsx +++ b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBar.tsx @@ -11,7 +11,7 @@ import { forwardRef, useCallback } from 'react'; import { MenuInitialState, MenuSeparator } from 'reakit'; import styled, { use } from 'reshadow'; -import { useStyles } from '@cloudbeaver/core-blocks'; +import { useAutoLoad, useStyles } from '@cloudbeaver/core-blocks'; import type { ComponentStyle } from '@cloudbeaver/core-theming'; import { DATA_CONTEXT_MENU_NESTED, IMenuActionItem, IMenuData, IMenuItem, MenuActionItem, MenuBaseItem, MenuSeparatorItem, MenuSubMenuItem, useMenu } from '@cloudbeaver/core-view'; @@ -36,6 +36,7 @@ export const MenuBar = observer(forwardRef(functi }, ref) { const styles = useStyles(style); const items = menu.items; + useAutoLoad(menu.loaders); if (!items.length) { return null; @@ -60,6 +61,7 @@ interface IMenuBarElementProps { item: IMenuItem; menuData: IMenuData; nestedMenuSettings?: INestedMenuSettings; + className?: string; style?: ComponentStyle; } @@ -67,6 +69,7 @@ const MenuBarElement = observer(function MenuBarElement({ item, menuData, nestedMenuSettings, + className, style, }) { const styles = useStyles(style); @@ -81,6 +84,7 @@ const MenuBarElement = observer(function MenuBarElement({ item={item} menuData={menuData} style={style} + className={className} nestedMenuSettings={nestedMenuSettings} /> ); @@ -88,7 +92,7 @@ const MenuBarElement = observer(function MenuBarElement({ if (item instanceof MenuSeparatorItem) { return styled(styles)( - + ); } @@ -97,6 +101,7 @@ const MenuBarElement = observer(function MenuBarElement({ ); @@ -112,6 +117,7 @@ const MenuBarElement = observer(function MenuBarElement({ title={item.tooltip} disabled={item.disabled} style={style} + className={className} onClick={onClick} {...use({ hidden: item.hidden })} /> @@ -124,10 +130,11 @@ const MenuBarElement = observer(function MenuBarElement({ interface IMenuBarActionProps { item: IMenuActionItem; style?: ComponentStyle; + className?: string; onClick: () => void; } -const MenuBarAction = observer(function MenuBarAction({ item, style, onClick }) { +const MenuBarAction = observer(function MenuBarAction({ item, style, className, onClick }) { const styles = useStyles(style); const actionInfo = item.action.actionInfo; @@ -148,6 +155,7 @@ const MenuBarAction = observer(function MenuBarAction({ ite disabled={item.disabled} loading={loading} style={style} + className={className} onClick={handleClick} {...use({ hidden: item.hidden })} /> @@ -158,6 +166,7 @@ interface ISubMenuItemProps { item: MenuSubMenuItem; menuData: IMenuData; nestedMenuSettings?: INestedMenuSettings; + className?: string; style?: ComponentStyle; } @@ -165,6 +174,7 @@ const SubMenuItem = observer(function SubmenuItem({ item, menuData, nestedMenuSettings, + className, style, }) { const styles = useStyles(style); @@ -176,6 +186,7 @@ const SubMenuItem = observer(function SubmenuItem({ diff --git a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBarItem.tsx b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBarItem.tsx index 6f40a68caf..4e006c3c9d 100644 --- a/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBarItem.tsx +++ b/webapp/packages/core-ui/src/ContextMenu/MenuBar/MenuBarItem.tsx @@ -37,7 +37,7 @@ export const MenuBarItem = observer(forwardRef(functio {loading && } {!loading && icon && } - {label && {translate(label)}} + {label && {translate(label)}} ); })); diff --git a/webapp/packages/core-utils/src/ILoadableState.ts b/webapp/packages/core-utils/src/ILoadableState.ts new file mode 100644 index 0000000000..b41f485241 --- /dev/null +++ b/webapp/packages/core-utils/src/ILoadableState.ts @@ -0,0 +1,35 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +export interface ILoadableState { + isLoading: () => boolean; + isLoaded: () => boolean; + readonly exception?: (Error | null)[] | Error | null; + load: () => void; + reload?: () => void; + + isOutdated?: () => boolean; + isCancelled?: () => boolean; + cancel?: () => void; +} + +// export function composeLoadableState(...states: ILoadableState[]): ILoadableState { + +// } + +export function isLoadableStateHasException(state: ILoadableState): boolean { + return isContainsException(state.exception); +} + +export function isContainsException(exception?: (Error | null)[] | Error | null): boolean { + if (Array.isArray(exception)) { + return exception.some(Boolean); + } + + return !!exception; +} \ No newline at end of file diff --git a/webapp/packages/core-utils/src/index.ts b/webapp/packages/core-utils/src/index.ts index 12b7e53a4e..f50c93e879 100644 --- a/webapp/packages/core-utils/src/index.ts +++ b/webapp/packages/core-utils/src/index.ts @@ -16,6 +16,7 @@ export * from './flat'; export * from './generateFileName'; export * from './getMIME'; export * from './GlobalConstants'; +export * from './ILoadableState'; export * from './isArraysEqual'; export * from './isPropertiesEqual'; export * from './isValuesEqual'; diff --git a/webapp/packages/core-view/src/Action/ActionItem.ts b/webapp/packages/core-view/src/Action/ActionItem.ts index db1cdfe5c7..4cd676b1ea 100644 --- a/webapp/packages/core-view/src/Action/ActionItem.ts +++ b/webapp/packages/core-view/src/Action/ActionItem.ts @@ -6,6 +6,8 @@ * you may not use this file except in compliance with the License. */ +import { flat } from '@cloudbeaver/core-utils'; + import type { IDataContextProvider } from '../DataContext/IDataContextProvider'; import type { IAction } from './IAction'; import type { IActionHandler } from './IActionHandler'; @@ -17,7 +19,7 @@ export class ActionItem implements IActionItem { readonly action: IAction; readonly handler: IActionHandler; readonly binding: IKeyBindingHandler | null; - private context: IDataContextProvider; + private readonly context: IDataContextProvider; get actionInfo(): IActionInfo { if (this.handler.getActionInfo) { @@ -44,7 +46,10 @@ export class ActionItem implements IActionItem { } isLoading(): boolean { - return this.handler.isLoading?.(this.context, this.action) ?? false; + return ( + this.handler.isLoading?.(this.context, this.action) + || flat([this.handler.getLoader?.(this.context, this.action)]).some(loader => loader?.isLoading()) + ); } isDisabled(): boolean { diff --git a/webapp/packages/core-view/src/Action/IActionHandler.ts b/webapp/packages/core-view/src/Action/IActionHandler.ts index ea0725e650..9a52d654a4 100644 --- a/webapp/packages/core-view/src/Action/IActionHandler.ts +++ b/webapp/packages/core-view/src/Action/IActionHandler.ts @@ -6,6 +6,8 @@ * you may not use this file except in compliance with the License. */ +import type { ILoadableState } from '@cloudbeaver/core-utils'; + import type { IDataContextProvider } from '../DataContext/IDataContextProvider'; import type { IAction } from './IAction'; import type { IActionInfo } from './IActionInfo'; @@ -14,6 +16,7 @@ export interface IActionHandler { id: string; getActionInfo?: (context: IDataContextProvider, action: IAction) => IActionInfo; + getLoader?: (context: IDataContextProvider, action: IAction) => ILoadableState[] | ILoadableState; isChecked?: (context: IDataContextProvider, action: IAction) => boolean; isLoading?: (context: IDataContextProvider, action: IAction) => boolean; diff --git a/webapp/packages/core-view/src/DataContext/DataContext.ts b/webapp/packages/core-view/src/DataContext/DataContext.ts index 6c476cda2e..4ace11be33 100644 --- a/webapp/packages/core-view/src/DataContext/DataContext.ts +++ b/webapp/packages/core-view/src/DataContext/DataContext.ts @@ -36,8 +36,12 @@ export class DataContext implements IDataContext { this.fallback = fallback; } - has(context: DataContextGetter): boolean { - return this.map.has(context) || this.fallback?.has(context) || false; + has(context: DataContextGetter, nested = true): boolean { + if (nested) { + return this.map.has(context) || this.fallback?.has(context) || false; + } + + return this.map.has(context); } hasValue(context: DataContextGetter, value: T): boolean { diff --git a/webapp/packages/core-view/src/DataContext/IDataContextProvider.ts b/webapp/packages/core-view/src/DataContext/IDataContextProvider.ts index 3b0e12e875..1f5a87050d 100644 --- a/webapp/packages/core-view/src/DataContext/IDataContextProvider.ts +++ b/webapp/packages/core-view/src/DataContext/IDataContextProvider.ts @@ -11,7 +11,7 @@ import type { DataContextGetter } from './DataContextGetter'; export interface IDataContextProvider { fallback?: IDataContextProvider; readonly map: Map, any>; - has: (context: DataContextGetter) => boolean; + has: (context: DataContextGetter, nested?: boolean) => boolean; get: (context: DataContextGetter) => T; find: (context: DataContextGetter, predicate: (item: T) => boolean) => T | undefined; hasValue: (context: DataContextGetter, value: T) => boolean; diff --git a/webapp/packages/core-view/src/DataContext/createDataContext.ts b/webapp/packages/core-view/src/DataContext/createDataContext.ts index 82f627e2dc..8e653a01a6 100644 --- a/webapp/packages/core-view/src/DataContext/createDataContext.ts +++ b/webapp/packages/core-view/src/DataContext/createDataContext.ts @@ -8,11 +8,14 @@ import type { DataContextGetter } from './DataContextGetter'; -export function createDataContext(name: string, defaultValue?: T): DataContextGetter { +export function createDataContext( + name: string, + defaultValue?: () => T extends any ? T : undefined +): DataContextGetter { name = `@context/${name}`; const obj = { [name](): T { - return defaultValue as T; + return defaultValue?.() as T; }, }; return obj[name]; diff --git a/webapp/packages/core-view/src/LoadableStateContext/DATA_CONTEXT_LOADABLE_STATE.ts b/webapp/packages/core-view/src/LoadableStateContext/DATA_CONTEXT_LOADABLE_STATE.ts new file mode 100644 index 0000000000..b213859ff2 --- /dev/null +++ b/webapp/packages/core-view/src/LoadableStateContext/DATA_CONTEXT_LOADABLE_STATE.ts @@ -0,0 +1,31 @@ +/* + * 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 { flat, ILoadableState } from '@cloudbeaver/core-utils'; + +import { createDataContext } from '../DataContext/createDataContext'; + +interface ILoadableStateContext { + getState(id: string, defaultState: () => ILoadableState | ILoadableState[]): ILoadableState[]; +} + +export function loadableStateContext(): ILoadableStateContext { + const state = new Map(); + + return { + getState(id, defaultState) { + if (!state.has(id)) { + state.set(id, flat([defaultState()])); + } + + return state.get(id)!; + }, + }; +} + +export const DATA_CONTEXT_LOADABLE_STATE = createDataContext('loadable-state', loadableStateContext); diff --git a/webapp/packages/core-view/src/Menu/DATA_CONTEXT_MENU_LOCAL.ts b/webapp/packages/core-view/src/Menu/DATA_CONTEXT_MENU_LOCAL.ts index 746f32b36e..1ae26106aa 100644 --- a/webapp/packages/core-view/src/Menu/DATA_CONTEXT_MENU_LOCAL.ts +++ b/webapp/packages/core-view/src/Menu/DATA_CONTEXT_MENU_LOCAL.ts @@ -8,4 +8,4 @@ import { createDataContext } from '../DataContext/createDataContext'; -export const DATA_CONTEXT_MENU_LOCAL = createDataContext('menu-local', false); +export const DATA_CONTEXT_MENU_LOCAL = createDataContext('menu-local', () => false); diff --git a/webapp/packages/core-view/src/Menu/DATA_CONTEXT_MENU_NESTED.ts b/webapp/packages/core-view/src/Menu/DATA_CONTEXT_MENU_NESTED.ts index 286f7f9531..5a5d1f19a0 100644 --- a/webapp/packages/core-view/src/Menu/DATA_CONTEXT_MENU_NESTED.ts +++ b/webapp/packages/core-view/src/Menu/DATA_CONTEXT_MENU_NESTED.ts @@ -8,4 +8,4 @@ import { createDataContext } from '../DataContext/createDataContext'; -export const DATA_CONTEXT_MENU_NESTED = createDataContext('menu-nested', false); +export const DATA_CONTEXT_MENU_NESTED = createDataContext('menu-nested', () => false); diff --git a/webapp/packages/core-view/src/Menu/MenuService.ts b/webapp/packages/core-view/src/Menu/MenuService.ts index c77af8d911..c3e303d22c 100644 --- a/webapp/packages/core-view/src/Menu/MenuService.ts +++ b/webapp/packages/core-view/src/Menu/MenuService.ts @@ -7,6 +7,7 @@ */ import { injectable } from '@cloudbeaver/core-di'; +import { flat, ILoadableState } from '@cloudbeaver/core-utils'; import { ActionService } from '../Action/ActionService'; import { isAction } from '../Action/createAction'; @@ -44,11 +45,6 @@ export class MenuService { return null; } - isMenuAvailable(context: IDataContextProvider): boolean { - return this.creators - .some(filterApplicable(context)); - } - addCreator(creator: IMenuItemsCreator): void { this.creators.push(creator); } @@ -70,18 +66,32 @@ export class MenuService { return null; } - getMenu(context: IDataContextProvider): IMenuItem[] { - const applicableCreators = this.creators.filter(filterApplicable(context)); + getMenuItemLoaders(context: IDataContextProvider, itemCreators: MenuCreatorItem[]): ILoadableState[] { + return flat(itemCreators + .map(item => { + if (isAction(item)) { + const handler = this.actionService.getHandler(context, item); - return applicableCreators - .reduce( + return handler?.getLoader?.(context, item) ?? null; + } + + return null; + })) + .filter((item => item !== null) as ((obj: any) => obj is ILoadableState)) + ; + } + + getMenuItemCreators(context: IDataContextProvider): MenuCreatorItem[] { + const creators = this.creators.filter(filterApplicable(context)); + + return creators.reduce( (items, creator) => { if (creator.orderItems) { return creator.orderItems(context, items); } return items; }, - applicableCreators + creators .reduce((items, creator) => creator.getItems(context, items), []) .filter(item => { if (isAction(item)) { @@ -90,7 +100,11 @@ export class MenuService { return true; }) - ) + ); + } + + getMenu(context: IDataContextProvider, itemCreators: MenuCreatorItem[]): IMenuItem[] { + return itemCreators .map(item => { if (isAction(item)) { return this.createActionItem(context, item) as IMenuItem; @@ -132,6 +146,10 @@ function filterApplicable(contexts: IDataContextProvider): (creator: IMenuItemsC return false; } - return creator.isApplicable?.(contexts) ?? true; + if (creator.isApplicable?.(contexts) === false) { + return false; + } + + return true; }; } diff --git a/webapp/packages/core-view/src/Menu/useMenu.ts b/webapp/packages/core-view/src/Menu/useMenu.ts index fbdf72bfb9..ac448fe14c 100644 --- a/webapp/packages/core-view/src/Menu/useMenu.ts +++ b/webapp/packages/core-view/src/Menu/useMenu.ts @@ -10,10 +10,12 @@ import { computed, observable } from 'mobx'; import { useObservableRef } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; +import type { ILoadableState } from '@cloudbeaver/core-utils'; import type { IDataContext } from '../DataContext/IDataContext'; import { DATA_CONTEXT_MENU_LOCAL } from './DATA_CONTEXT_MENU_LOCAL'; import type { IMenu } from './IMenu'; +import type { MenuCreatorItem } from './IMenuItemsCreator'; import type { IMenuItem } from './MenuItem/IMenuItem'; import { MenuService } from './MenuService'; import { useMenuContext } from './useMenuContext'; @@ -22,6 +24,8 @@ export interface IMenuData { menu: IMenu; context: IDataContext; available: boolean; + loaders: ILoadableState[]; + itemCreators: MenuCreatorItem[]; items: IMenuItem[]; } @@ -37,16 +41,24 @@ export function useMenu(options: IMenuOptions): IMenuData { context.set(DATA_CONTEXT_MENU_LOCAL, options.local); - return useObservableRef(() => ({ + const state = useObservableRef(() => ({ context, + get loaders() { + return menuService.getMenuItemLoaders(this.context, this.itemCreators); + }, + get itemCreators() { + return menuService.getMenuItemCreators(this.context); + }, get available() { - return menuService.isMenuAvailable(this.context); + return this.itemCreators.length > 0; }, get items() { - return menuService.getMenu(this.context); + return menuService.getMenu(this.context, this.itemCreators); }, }), { + loaders: computed, available: computed, + itemCreators: computed, items: computed, menu: observable.ref, context: observable.ref, @@ -54,4 +66,6 @@ export function useMenu(options: IMenuOptions): IMenuData { menu: options.menu, context, }); + + return state; } diff --git a/webapp/packages/core-view/src/Menu/useMenuContext.ts b/webapp/packages/core-view/src/Menu/useMenuContext.ts index 15ea486c7e..d8aced78ec 100644 --- a/webapp/packages/core-view/src/Menu/useMenuContext.ts +++ b/webapp/packages/core-view/src/Menu/useMenuContext.ts @@ -10,6 +10,7 @@ import { useContext } from 'react'; import type { IDataContext } from '../DataContext/IDataContext'; import { useDataContext } from '../DataContext/useDataContext'; +import { DATA_CONTEXT_LOADABLE_STATE, loadableStateContext } from '../LoadableStateContext/DATA_CONTEXT_LOADABLE_STATE'; import { CaptureViewContext } from '../View/CaptureViewContext'; import { DATA_CONTEXT_MENU } from './DATA_CONTEXT_MENU'; import type { IMenu } from './IMenu'; @@ -20,5 +21,9 @@ export function useMenuContext(menu: IMenu, _menuContext?: IDataContext): IDataC context.set(DATA_CONTEXT_MENU, menu); + if (context.has(DATA_CONTEXT_LOADABLE_STATE, false)) { + context.set(DATA_CONTEXT_LOADABLE_STATE, loadableStateContext()); + } + return context; } diff --git a/webapp/packages/core-view/src/index.ts b/webapp/packages/core-view/src/index.ts index d5e112ff67..021b864b8d 100644 --- a/webapp/packages/core-view/src/index.ts +++ b/webapp/packages/core-view/src/index.ts @@ -28,6 +28,7 @@ export * from './DataContext/IDataContext'; export * from './DataContext/IDataContextProvider'; export * from './DataContext/useDataContext'; export * from './DataContext/useDynamicDataContext'; +export * from './LoadableStateContext/DATA_CONTEXT_LOADABLE_STATE'; export * from './Menu/MenuItem/IMenuCheckboxItem'; export * from './Menu/MenuItem/IMenuActionItem'; export * from './Menu/MenuItem/IMenuBaseItem'; diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamsTable/TeamsTable.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamsTable/TeamsTable.tsx index b29ca69049..cef296edfc 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamsTable/TeamsTable.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamsTable/TeamsTable.tsx @@ -10,9 +10,8 @@ import { observer } from 'mobx-react-lite'; import styled, { css } from 'reshadow'; import type { TeamInfo } from '@cloudbeaver/core-authentication'; -import { Table, TableHeader, TableColumnHeader, TableBody, TableSelect, Loader, ILoadableState, useTranslate, useStyles } from '@cloudbeaver/core-blocks'; - - +import { Table, TableHeader, TableColumnHeader, TableBody, TableSelect, Loader, useTranslate, useStyles } from '@cloudbeaver/core-blocks'; +import type { ILoadableState } from '@cloudbeaver/core-utils'; import { Team } from './Team'; diff --git a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamsTable/useTeamsTable.tsx b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamsTable/useTeamsTable.tsx index 6c3f74e60b..90f7bac601 100644 --- a/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamsTable/useTeamsTable.tsx +++ b/webapp/packages/plugin-authentication-administration/src/Administration/Users/Teams/TeamsTable/useTeamsTable.tsx @@ -9,12 +9,12 @@ import { computed, observable } from 'mobx'; import { compareTeams, TeamInfo, TeamsResource } from '@cloudbeaver/core-authentication'; -import { ILoadableState, TableState, useMapResource, useObservableRef, useTranslate } from '@cloudbeaver/core-blocks'; +import { TableState, useMapResource, useObservableRef, useTranslate } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { CommonDialogService, ConfirmationDialogDelete, DialogueStateResult } from '@cloudbeaver/core-dialogs'; import { NotificationService } from '@cloudbeaver/core-events'; - import { CachedMapAllKey, resourceKeyList } from '@cloudbeaver/core-sdk'; +import type { ILoadableState } from '@cloudbeaver/core-utils'; interface State { tableState: TableState; diff --git a/webapp/packages/plugin-authentication/src/Dialog/useAuthDialogState.ts b/webapp/packages/plugin-authentication/src/Dialog/useAuthDialogState.ts index c2a08b05d2..fafb40d24e 100644 --- a/webapp/packages/plugin-authentication/src/Dialog/useAuthDialogState.ts +++ b/webapp/packages/plugin-authentication/src/Dialog/useAuthDialogState.ts @@ -11,10 +11,11 @@ import { useEffect } from 'react'; import { AdministrationScreenService } from '@cloudbeaver/core-administration'; import { AuthInfoService, AuthProvider, AuthProviderConfiguration, AuthProvidersResource, IAuthCredentials } from '@cloudbeaver/core-authentication'; -import { ILoadableState, useMapResource, useObservableRef } from '@cloudbeaver/core-blocks'; +import { useMapResource, useObservableRef } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import { CachedMapAllKey } from '@cloudbeaver/core-sdk'; +import type { ILoadableState } from '@cloudbeaver/core-utils'; import { FEDERATED_AUTH } from './FEDERATED_AUTH'; diff --git a/webapp/packages/core-blocks/src/Loader/ILoadableState.ts b/webapp/packages/plugin-connection-custom/src/Actions/ACTION_CONNECTION_CUSTOM.ts similarity index 50% rename from webapp/packages/core-blocks/src/Loader/ILoadableState.ts rename to webapp/packages/plugin-connection-custom/src/Actions/ACTION_CONNECTION_CUSTOM.ts index c6de66c91c..ac23b6c906 100644 --- a/webapp/packages/core-blocks/src/Loader/ILoadableState.ts +++ b/webapp/packages/plugin-connection-custom/src/Actions/ACTION_CONNECTION_CUSTOM.ts @@ -6,12 +6,11 @@ * you may not use this file except in compliance with the License. */ -export interface ILoadableState { - isLoading: () => boolean; - isLoaded: () => boolean; - readonly exception?: Error[] | Error | null; - reload?: () => void; +import { createAction } from '@cloudbeaver/core-view'; - isCancelled?: () => boolean; - cancel?: () => void; -} +export const ACTION_CONNECTION_CUSTOM = createAction( + 'connection-custom', + { + label: 'plugin_connection_custom_action_custom_label', + } +); diff --git a/webapp/packages/plugin-connection-custom/src/CustomConnectionPluginBootstrap.ts b/webapp/packages/plugin-connection-custom/src/CustomConnectionPluginBootstrap.ts index 588f3a0fb4..e6b8e5627f 100644 --- a/webapp/packages/plugin-connection-custom/src/CustomConnectionPluginBootstrap.ts +++ b/webapp/packages/plugin-connection-custom/src/CustomConnectionPluginBootstrap.ts @@ -9,35 +9,57 @@ import { ConnectionsManagerService } from '@cloudbeaver/core-connections'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { CommonDialogService } from '@cloudbeaver/core-dialogs'; -import { ProjectsService } from '@cloudbeaver/core-projects'; -import { MainMenuService, EMainMenu } from '@cloudbeaver/plugin-top-app-bar'; +import { ProjectInfoResource } from '@cloudbeaver/core-projects'; +import { CachedMapAllKey, getCachedMapResourceLoaderState } from '@cloudbeaver/core-sdk'; +import { ActionService, DATA_CONTEXT_LOADABLE_STATE, DATA_CONTEXT_MENU, MenuService } from '@cloudbeaver/core-view'; +import { MENU_CONNECTIONS } from '@cloudbeaver/plugin-connections'; +import { ACTION_CONNECTION_CUSTOM } from './Actions/ACTION_CONNECTION_CUSTOM'; import { CustomConnectionDialog } from './CustomConnection/CustomConnectionDialog'; @injectable() export class CustomConnectionPluginBootstrap extends Bootstrap { constructor( - private readonly mainMenuService: MainMenuService, private readonly commonDialogService: CommonDialogService, - private readonly projectsService: ProjectsService, - private readonly connectionsManagerService: ConnectionsManagerService + private readonly projectInfoResource: ProjectInfoResource, + private readonly menuService: MenuService, + private readonly actionService: ActionService, + private readonly connectionsManagerService: ConnectionsManagerService, ) { super(); } register(): void | Promise { - this.mainMenuService.onConnectionClick.addHandler(() => { - this.projectsService.load(); + this.menuService.addCreator({ + isApplicable: context => context.tryGet(DATA_CONTEXT_MENU) === MENU_CONNECTIONS, + getItems: (context, items) => [ + ...items, + ACTION_CONNECTION_CUSTOM, + ], }); - this.mainMenuService.registerMenuItem(EMainMenu.mainMenuConnectionsPanel, { - id: 'customConnection', - order: 2, - title: 'app_shared_connectionMenu_custom', - isHidden: () => ( - !this.projectsService.activeProjects.some(project => project.canEditDataSources) - ), - onClick: () => this.openConnectionsDialog(), + this.actionService.addHandler({ + id: 'connection-custom', + isActionApplicable: (context, action) => [ + ACTION_CONNECTION_CUSTOM, + ].includes(action), + isHidden: () => this.connectionsManagerService.createConnectionProjects.length === 0, + getLoader: (context, action) => { + const state = context.get(DATA_CONTEXT_LOADABLE_STATE); + + return state.getState( + action.id, + () => getCachedMapResourceLoaderState(this.projectInfoResource, CachedMapAllKey) + ); + }, + handler: async (context, action) => { + switch (action) { + case ACTION_CONNECTION_CUSTOM: { + await this.openConnectionsDialog(); + break; + } + } + }, }); } diff --git a/webapp/packages/plugin-connection-custom/src/locales/en.ts b/webapp/packages/plugin-connection-custom/src/locales/en.ts index ffa3770c38..f0fd6117a1 100644 --- a/webapp/packages/plugin-connection-custom/src/locales/en.ts +++ b/webapp/packages/plugin-connection-custom/src/locales/en.ts @@ -17,4 +17,5 @@ export default [ ['customConnection_test', 'Test Connection'], ['customConnection_create', 'Create'], ['customConnection_create_error', 'Сreate connection error'], + ['plugin_connection_custom_action_custom_label', 'Manual'], ]; diff --git a/webapp/packages/plugin-connection-custom/src/locales/it.ts b/webapp/packages/plugin-connection-custom/src/locales/it.ts index 5db283b8cb..f10362ded3 100644 --- a/webapp/packages/plugin-connection-custom/src/locales/it.ts +++ b/webapp/packages/plugin-connection-custom/src/locales/it.ts @@ -17,4 +17,5 @@ export default [ ['customConnection_test', 'Prova la connessione'], ['customConnection_create', 'Crea'], ['customConnection_create_error', 'Errore di creazione connessione'], + ['plugin_connection_custom_action_custom_label', 'Manuale'], ]; diff --git a/webapp/packages/plugin-connection-custom/src/locales/ru.ts b/webapp/packages/plugin-connection-custom/src/locales/ru.ts index e37ef6baf9..124c9e22f6 100644 --- a/webapp/packages/plugin-connection-custom/src/locales/ru.ts +++ b/webapp/packages/plugin-connection-custom/src/locales/ru.ts @@ -17,4 +17,5 @@ export default [ ['customConnection_test', 'Проверить'], ['customConnection_create', 'Создать'], ['customConnection_create_error', 'Ошибка при создании подключения'], + ['plugin_connection_custom_action_custom_label', 'Создать'], ]; diff --git a/webapp/packages/plugin-connection-custom/src/locales/zh.ts b/webapp/packages/plugin-connection-custom/src/locales/zh.ts index b1aa3ef725..7aa5670419 100644 --- a/webapp/packages/plugin-connection-custom/src/locales/zh.ts +++ b/webapp/packages/plugin-connection-custom/src/locales/zh.ts @@ -17,4 +17,5 @@ export default [ ['customConnection_test', '测试连接'], ['customConnection_create', '创建'], ['customConnection_create_error', '创建连接失败'], + ['plugin_connection_custom_action_custom_label', '手动'], ]; diff --git a/webapp/packages/plugin-connection-search/src/Actions/ACTION_CONNECTION_SEARCH.ts b/webapp/packages/plugin-connection-search/src/Actions/ACTION_CONNECTION_SEARCH.ts new file mode 100644 index 0000000000..ebc7943bfe --- /dev/null +++ b/webapp/packages/plugin-connection-search/src/Actions/ACTION_CONNECTION_SEARCH.ts @@ -0,0 +1,16 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { createAction } from '@cloudbeaver/core-view'; + +export const ACTION_CONNECTION_SEARCH = createAction( + 'connection-search', + { + label: 'plugin_connection_search_action_search_label', + } +); diff --git a/webapp/packages/plugin-connection-search/src/LocaleService.ts b/webapp/packages/plugin-connection-search/src/LocaleService.ts new file mode 100644 index 0000000000..c6a5d9b3c9 --- /dev/null +++ b/webapp/packages/plugin-connection-search/src/LocaleService.ts @@ -0,0 +1,36 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 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 { LocalizationService } from '@cloudbeaver/core-localization'; + +@injectable() +export class LocaleService extends Bootstrap { + constructor(private readonly localizationService: LocalizationService) { + super(); + } + + register(): void | Promise { + this.localizationService.addProvider(this.provider.bind(this)); + } + + load(): void | Promise { } + + private async provider(locale: string) { + switch (locale) { + case 'ru': + return (await import('./locales/ru')).default; + case 'it': + return (await import('./locales/it')).default; + case 'zh': + return (await import('./locales/zh')).default; + default: + return (await import('./locales/en')).default; + } + } +} diff --git a/webapp/packages/plugin-connection-search/src/SearchConnectionPluginBootstrap.ts b/webapp/packages/plugin-connection-search/src/SearchConnectionPluginBootstrap.ts index ddf14f869e..b07bd17a86 100644 --- a/webapp/packages/plugin-connection-search/src/SearchConnectionPluginBootstrap.ts +++ b/webapp/packages/plugin-connection-search/src/SearchConnectionPluginBootstrap.ts @@ -7,39 +7,63 @@ */ import { EAdminPermission } from '@cloudbeaver/core-administration'; +import { ConnectionsManagerService } from '@cloudbeaver/core-connections'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; -import { ProjectsService } from '@cloudbeaver/core-projects'; +import { ProjectInfoResource } from '@cloudbeaver/core-projects'; import { PermissionsService } from '@cloudbeaver/core-root'; -import { MainMenuService, EMainMenu } from '@cloudbeaver/plugin-top-app-bar'; +import { getCachedMapResourceLoaderState, CachedMapAllKey } from '@cloudbeaver/core-sdk'; +import { MenuService, ActionService, DATA_CONTEXT_MENU, DATA_CONTEXT_LOADABLE_STATE } from '@cloudbeaver/core-view'; +import { MENU_CONNECTIONS } from '@cloudbeaver/plugin-connections'; +import { ACTION_CONNECTION_SEARCH } from './Actions/ACTION_CONNECTION_SEARCH'; import { ConnectionSearchService } from './Search/ConnectionSearchService'; @injectable() export class SearchConnectionPluginBootstrap extends Bootstrap { constructor( - private readonly mainMenuService: MainMenuService, - private readonly projectsService: ProjectsService, private readonly permissionsService: PermissionsService, + private readonly projectInfoResource: ProjectInfoResource, private readonly connectionSearchService: ConnectionSearchService, + private readonly connectionsManagerService: ConnectionsManagerService, + private readonly menuService: MenuService, + private readonly actionService: ActionService, ) { super(); } register(): void | Promise { - this.mainMenuService.onConnectionClick.addHandler(() => { - this.projectsService.load(); + this.menuService.addCreator({ + isApplicable: context => context.tryGet(DATA_CONTEXT_MENU) === MENU_CONNECTIONS, + getItems: (context, items) => [ + ...items, + ACTION_CONNECTION_SEARCH, + ], }); - this.mainMenuService.registerMenuItem(EMainMenu.mainMenuConnectionsPanel, { - id: 'searchConnection', - order: 3, - title: 'connections_connection_create_search_database', + this.actionService.addHandler({ + id: 'connection-search', + isActionApplicable: (context, action) => [ + ACTION_CONNECTION_SEARCH, + ].includes(action), isHidden: () => ( - !this.projectsService.activeProjects.some(project => project.canEditDataSources) + this.connectionsManagerService.createConnectionProjects.length === 0 || !this.permissionsService.has(EAdminPermission.admin) ), - onClick: () => { - this.connectionSearchService.open(); + getLoader: (context, action) => { + const state = context.get(DATA_CONTEXT_LOADABLE_STATE); + + return state.getState( + action.id, + () => getCachedMapResourceLoaderState(this.projectInfoResource, CachedMapAllKey) + ); + }, + handler: async (context, action) => { + switch (action) { + case ACTION_CONNECTION_SEARCH: { + this.connectionSearchService.open(); + break; + } + } }, }); } diff --git a/webapp/packages/plugin-connection-search/src/locales/en.ts b/webapp/packages/plugin-connection-search/src/locales/en.ts new file mode 100644 index 0000000000..1e83566e27 --- /dev/null +++ b/webapp/packages/plugin-connection-search/src/locales/en.ts @@ -0,0 +1,3 @@ +export default [ + ['plugin_connection_search_action_search_label', 'Search'], +]; diff --git a/webapp/packages/plugin-connection-search/src/locales/it.ts b/webapp/packages/plugin-connection-search/src/locales/it.ts new file mode 100644 index 0000000000..1e83566e27 --- /dev/null +++ b/webapp/packages/plugin-connection-search/src/locales/it.ts @@ -0,0 +1,3 @@ +export default [ + ['plugin_connection_search_action_search_label', 'Search'], +]; diff --git a/webapp/packages/plugin-connection-search/src/locales/ru.ts b/webapp/packages/plugin-connection-search/src/locales/ru.ts new file mode 100644 index 0000000000..1cb0672ad7 --- /dev/null +++ b/webapp/packages/plugin-connection-search/src/locales/ru.ts @@ -0,0 +1,3 @@ +export default [ + ['plugin_connection_search_action_search_label', 'Поиск'], +]; diff --git a/webapp/packages/plugin-connection-search/src/locales/zh.ts b/webapp/packages/plugin-connection-search/src/locales/zh.ts new file mode 100644 index 0000000000..1e83566e27 --- /dev/null +++ b/webapp/packages/plugin-connection-search/src/locales/zh.ts @@ -0,0 +1,3 @@ +export default [ + ['plugin_connection_search_action_search_label', 'Search'], +]; diff --git a/webapp/packages/plugin-connection-search/src/manifest.ts b/webapp/packages/plugin-connection-search/src/manifest.ts index 14ed2f493b..a2caf8e94d 100644 --- a/webapp/packages/plugin-connection-search/src/manifest.ts +++ b/webapp/packages/plugin-connection-search/src/manifest.ts @@ -1,5 +1,6 @@ import type { PluginManifest } from '@cloudbeaver/core-di'; +import { LocaleService } from './LocaleService'; import { ConnectionSearchService } from './Search/ConnectionSearchService'; import { SearchConnectionPluginBootstrap } from './SearchConnectionPluginBootstrap'; @@ -10,5 +11,6 @@ export const connectionSearchPlugin: PluginManifest = { providers: [ SearchConnectionPluginBootstrap, ConnectionSearchService, + LocaleService, ], }; \ No newline at end of file diff --git a/webapp/packages/plugin-connection-template/src/Actions/ACTION_CONNECTION_TEMPLATE.ts b/webapp/packages/plugin-connection-template/src/Actions/ACTION_CONNECTION_TEMPLATE.ts new file mode 100644 index 0000000000..ed980f873c --- /dev/null +++ b/webapp/packages/plugin-connection-template/src/Actions/ACTION_CONNECTION_TEMPLATE.ts @@ -0,0 +1,16 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { createAction } from '@cloudbeaver/core-view'; + +export const ACTION_CONNECTION_TEMPLATE = createAction( + 'connection-template', + { + label: 'plugin_connection_template_action_connection_template_label', + } +); diff --git a/webapp/packages/plugin-connection-template/src/TemplateConnectionPluginBootstrap.ts b/webapp/packages/plugin-connection-template/src/TemplateConnectionPluginBootstrap.ts index c8eeeff9b5..353fe70736 100644 --- a/webapp/packages/plugin-connection-template/src/TemplateConnectionPluginBootstrap.ts +++ b/webapp/packages/plugin-connection-template/src/TemplateConnectionPluginBootstrap.ts @@ -8,11 +8,13 @@ import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { CommonDialogService } from '@cloudbeaver/core-dialogs'; -import { NotificationService } from '@cloudbeaver/core-events'; -import { ProjectsService } from '@cloudbeaver/core-projects'; +import { ProjectInfoResource, ProjectsService } from '@cloudbeaver/core-projects'; import { PermissionsService, EPermission } from '@cloudbeaver/core-root'; -import { MainMenuService, EMainMenu } from '@cloudbeaver/plugin-top-app-bar'; +import { CachedMapAllKey, getCachedDataResourceLoaderState, getCachedMapResourceLoaderState } from '@cloudbeaver/core-sdk'; +import { MenuService, ActionService, DATA_CONTEXT_MENU, DATA_CONTEXT_LOADABLE_STATE } from '@cloudbeaver/core-view'; +import { MENU_CONNECTIONS } from '@cloudbeaver/plugin-connections'; +import { ACTION_CONNECTION_TEMPLATE } from './Actions/ACTION_CONNECTION_TEMPLATE'; import { ConnectionDialog } from './ConnectionDialog/ConnectionDialog'; import { TemplateConnectionsResource } from './TemplateConnectionsResource'; import { TemplateConnectionsService } from './TemplateConnectionsService'; @@ -20,10 +22,11 @@ import { TemplateConnectionsService } from './TemplateConnectionsService'; @injectable() export class TemplateConnectionPluginBootstrap extends Bootstrap { constructor( - private readonly mainMenuService: MainMenuService, + private readonly menuService: MenuService, + private readonly actionService: ActionService, + private readonly projectInfoResource: ProjectInfoResource, private readonly templateConnectionsResource: TemplateConnectionsResource, private readonly commonDialogService: CommonDialogService, - private readonly notificationService: NotificationService, private readonly permissionsService: PermissionsService, private readonly templateConnectionsService: TemplateConnectionsService, private readonly projectsService: ProjectsService, @@ -32,40 +35,49 @@ export class TemplateConnectionPluginBootstrap extends Bootstrap { } register(): void | Promise { - this.mainMenuService.onConnectionClick.addHandler(this.loadTemplateConnections.bind(this)); - this.mainMenuService.registerMenuItem(EMainMenu.mainMenuConnectionsPanel, { - id: 'mainMenuConnect', - order: 1, - titleGetter: this.getMenuTitle.bind(this), - onClick: this.openConnectionsDialog.bind(this), + this.menuService.addCreator({ + isApplicable: context => context.tryGet(DATA_CONTEXT_MENU) === MENU_CONNECTIONS, + getItems: (context, items) => [ + ...items, + ACTION_CONNECTION_TEMPLATE, + ], + }); + + this.actionService.addHandler({ + id: 'connection-template', + isActionApplicable: (context, action) => [ + ACTION_CONNECTION_TEMPLATE, + ].includes(action), isHidden: () => ( !this.permissionsService.has(EPermission.public) || !this.projectsService.userProject?.canEditDataSources || !this.templateConnectionsService.projectTemplates.length ), + getLoader: (context, action) => { + const state = context.get(DATA_CONTEXT_LOADABLE_STATE); + + return state.getState( + action.id, + () => [ + getCachedMapResourceLoaderState(this.projectInfoResource, CachedMapAllKey), + getCachedDataResourceLoaderState(this.templateConnectionsResource, undefined, undefined), + ] + ); + }, + handler: async (context, action) => { + switch (action) { + case ACTION_CONNECTION_TEMPLATE: { + await this.openConnectionsDialog(); + break; + } + } + }, }); } load(): void | Promise { } - private getMenuTitle(): string { - if (this.templateConnectionsResource.isLoading()) { - return 'ui_processing_loading'; - } - return 'basicConnection_main_menu_item'; - } - private async openConnectionsDialog() { - this.loadTemplateConnections(); await this.commonDialogService.open(ConnectionDialog, null); } - - private async loadTemplateConnections() { - try { - await this.projectsService.load(); - await this.templateConnectionsResource.load(); - } catch (error: any) { - this.notificationService.logException(error, 'Template Connections loading failed'); - } - } } diff --git a/webapp/packages/plugin-connection-template/src/locales/en.ts b/webapp/packages/plugin-connection-template/src/locales/en.ts index 7df2d69d6e..363d17cf17 100644 --- a/webapp/packages/plugin-connection-template/src/locales/en.ts +++ b/webapp/packages/plugin-connection-template/src/locales/en.ts @@ -8,5 +8,5 @@ export default [ ['basicConnection_connectionDialog_passwordPlaceholder', 'password'], ['basicConnection_connectionDialog_connecting', 'Connecting...'], ['basicConnection_connectionDialog_connecting_message', 'Connecting to database...'], - ['basicConnection_main_menu_item', 'From Template'], + ['plugin_connection_template_action_connection_template_label', 'From Template'], ]; diff --git a/webapp/packages/plugin-connections/src/ContextMenu/Actions/ACTION_CONNECTION_DISCONNECT_ALL.ts b/webapp/packages/plugin-connections/src/ContextMenu/Actions/ACTION_CONNECTION_DISCONNECT_ALL.ts new file mode 100644 index 0000000000..7ed3b8c364 --- /dev/null +++ b/webapp/packages/plugin-connections/src/ContextMenu/Actions/ACTION_CONNECTION_DISCONNECT_ALL.ts @@ -0,0 +1,16 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { createAction } from '@cloudbeaver/core-view'; + +export const ACTION_CONNECTION_DISCONNECT_ALL = createAction( + 'connection-disconnect-all', + { + label: 'plugin_connections_action_disconnect_all_label', + } +); diff --git a/webapp/packages/plugin-connections/src/ContextMenu/ConnectionMenuBootstrap.ts b/webapp/packages/plugin-connections/src/ContextMenu/ConnectionMenuBootstrap.ts index 53f58b5f2f..f4f72c2abd 100644 --- a/webapp/packages/plugin-connections/src/ContextMenu/ConnectionMenuBootstrap.ts +++ b/webapp/packages/plugin-connections/src/ContextMenu/ConnectionMenuBootstrap.ts @@ -13,6 +13,7 @@ import { NotificationService } from '@cloudbeaver/core-events'; import { DATA_CONTEXT_NAV_NODE, EObjectFeature, NavNodeManagerService } from '@cloudbeaver/core-navigation-tree'; import { CONNECTION_NAVIGATOR_VIEW_SETTINGS, isNavigatorViewSettingsEqual, NavigatorViewSettings, PermissionsService } from '@cloudbeaver/core-root'; import { ActionService, ACTION_DELETE, DATA_CONTEXT_MENU, DATA_CONTEXT_MENU_NESTED, MenuSeparatorItem, MenuService } from '@cloudbeaver/core-view'; +import { TOP_APP_BAR_MENU } from '@cloudbeaver/plugin-top-app-bar'; import { ConnectionsSettingsService } from '../ConnectionsSettingsService'; import { PublicConnectionFormService } from '../PublicConnectionForm/PublicConnectionFormService'; @@ -23,6 +24,7 @@ import { ACTION_CONNECTION_VIEW_SIMPLE } from './Actions/ACTION_CONNECTION_VIEW_ import { ACTION_CONNECTION_VIEW_SYSTEM_OBJECTS } from './Actions/ACTION_CONNECTION_VIEW_SYSTEM_OBJECTS'; import { DATA_CONTEXT_CONNECTION } from './DATA_CONTEXT_CONNECTION'; import { MENU_CONNECTION_VIEW } from './MENU_CONNECTION_VIEW'; +import { MENU_CONNECTIONS } from './MENU_CONNECTIONS'; @injectable() export class ConnectionMenuBootstrap extends Bootstrap { @@ -41,6 +43,8 @@ export class ConnectionMenuBootstrap extends Bootstrap { } register(): void | Promise { + this.addConnectionsMenuToTopAppBar(); + this.menuService.addCreator({ isApplicable: context => { if ( @@ -225,4 +229,14 @@ export class ConnectionMenuBootstrap extends Bootstrap { this.notificationService.logException(exception); } } + + private addConnectionsMenuToTopAppBar() { + this.menuService.addCreator({ + isApplicable: context => context.tryGet(DATA_CONTEXT_MENU) === TOP_APP_BAR_MENU, + getItems: (context, items) => [ + ...items, + MENU_CONNECTIONS, + ], + }); + } } diff --git a/webapp/packages/plugin-connections/src/ContextMenu/MENU_CONNECTIONS.ts b/webapp/packages/plugin-connections/src/ContextMenu/MENU_CONNECTIONS.ts new file mode 100644 index 0000000000..bc4da2a23e --- /dev/null +++ b/webapp/packages/plugin-connections/src/ContextMenu/MENU_CONNECTIONS.ts @@ -0,0 +1,14 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 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 MENU_CONNECTIONS = createMenu( + 'connections', + 'plugin_connections_menu_connections_label' +); diff --git a/webapp/packages/plugin-connections/src/PluginBootstrap.ts b/webapp/packages/plugin-connections/src/PluginBootstrap.ts index 065f0d5c3d..0ab4a7d91f 100644 --- a/webapp/packages/plugin-connections/src/PluginBootstrap.ts +++ b/webapp/packages/plugin-connections/src/PluginBootstrap.ts @@ -8,28 +8,45 @@ import { ConnectionsManagerService } from '@cloudbeaver/core-connections'; import { Bootstrap, injectable } from '@cloudbeaver/core-di'; -import { EMainMenu, MainMenuService } from '@cloudbeaver/plugin-top-app-bar'; +import { MenuService, ActionService, DATA_CONTEXT_MENU } from '@cloudbeaver/core-view'; + +import { ACTION_CONNECTION_DISCONNECT_ALL } from './ContextMenu/Actions/ACTION_CONNECTION_DISCONNECT_ALL'; +import { MENU_CONNECTIONS } from './ContextMenu/MENU_CONNECTIONS'; @injectable() export class PluginBootstrap extends Bootstrap { constructor( - private readonly mainMenuService: MainMenuService, + private readonly menuService: MenuService, + private readonly actionService: ActionService, private readonly connectionsManagerService: ConnectionsManagerService, ) { super(); } register(): void { - this.mainMenuService.registerMenuItem( - EMainMenu.mainMenuConnectionsPanel, - { - id: 'mainMenuDisconnect', - order: 3, - title: 'app_shared_connectionMenu_disconnect', - onClick: () => this.connectionsManagerService.closeAllConnections(), - isDisabled: () => !this.connectionsManagerService.hasAnyConnection(true), - } - ); + this.menuService.addCreator({ + isApplicable: context => context.tryGet(DATA_CONTEXT_MENU) === MENU_CONNECTIONS, + getItems: (context, items) => [ + ...items, + ACTION_CONNECTION_DISCONNECT_ALL, + ], + }); + + this.actionService.addHandler({ + id: 'connection-base', + isActionApplicable: (context, action) => [ + ACTION_CONNECTION_DISCONNECT_ALL, + ].includes(action), + isDisabled: () => !this.connectionsManagerService.hasAnyConnection(true), + handler: async (context, action) => { + switch (action) { + case ACTION_CONNECTION_DISCONNECT_ALL: { + await this.connectionsManagerService.closeAllConnections(); + break; + } + } + }, + }); } load(): void { } diff --git a/webapp/packages/plugin-connections/src/index.ts b/webapp/packages/plugin-connections/src/index.ts index 0dbc64dcd4..2e81898c3f 100644 --- a/webapp/packages/plugin-connections/src/index.ts +++ b/webapp/packages/plugin-connections/src/index.ts @@ -16,6 +16,7 @@ export * from './ConnectionForm/IConnectionFormProps'; export * from './ConnectionForm/useConnectionFormState'; export * from './ContextMenu/DATA_CONTEXT_CONNECTION'; export * from './ContextMenu/MENU_CONNECTION_VIEW'; +export * from './ContextMenu/MENU_CONNECTIONS'; export * from './PublicConnectionForm/PublicConnectionFormService'; export * from './ConnectionAuthService'; diff --git a/webapp/packages/plugin-connections/src/locales/en.ts b/webapp/packages/plugin-connections/src/locales/en.ts index de50cb894b..7748c20c77 100644 --- a/webapp/packages/plugin-connections/src/locales/en.ts +++ b/webapp/packages/plugin-connections/src/locales/en.ts @@ -12,4 +12,6 @@ export default [ ['plugin_connections_connection_form_host_configuration_invalid', 'Host configuration is not supported'], ['plugin_connections_connection_form_name_invalid', 'Field \'name\' can\'t be empty'], ['connections_public_connection_folder_delete_confirmation', 'You\'re going to delete "{arg:name}". Connections won\'t be deleted. Are you sure?'], + ['plugin_connections_menu_connections_label', 'Connection'], + ['plugin_connections_action_disconnect_all_label', 'Disconnect All'], ]; diff --git a/webapp/packages/plugin-connections/src/locales/it.ts b/webapp/packages/plugin-connections/src/locales/it.ts index 3272924879..69411e5211 100644 --- a/webapp/packages/plugin-connections/src/locales/it.ts +++ b/webapp/packages/plugin-connections/src/locales/it.ts @@ -11,4 +11,6 @@ export default [ ['plugin_connections_connection_form_host_configuration_invalid', 'Host configuration is not supported'], ['plugin_connections_connection_form_name_invalid', 'Field \'name\' can\'t be empty'], ['connections_public_connection_folder_delete_confirmation', 'You\'re going to delete "{arg:name}". Connections won\'t be deleted. Are you sure?'], + ['plugin_connections_menu_connections_label', 'Connessione'], + ['plugin_connections_action_disconnect_all_label', 'Scollegati da tutto'], ]; diff --git a/webapp/packages/plugin-connections/src/locales/ru.ts b/webapp/packages/plugin-connections/src/locales/ru.ts index 350e719817..f349fe92ac 100644 --- a/webapp/packages/plugin-connections/src/locales/ru.ts +++ b/webapp/packages/plugin-connections/src/locales/ru.ts @@ -11,4 +11,6 @@ export default [ ['plugin_connections_connection_form_host_configuration_invalid', 'Конфигурация хоста не поддерживается'], ['plugin_connections_connection_form_name_invalid', 'Поле \'Название\' не может быть пустым'], ['connections_public_connection_folder_delete_confirmation', 'Вы удаляете "{arg:name}". Подключения не будут удалены. Вы уверены?'], + ['plugin_connections_menu_connections_label', 'Подключение'], + ['plugin_connections_action_disconnect_all_label', 'Отключить все'], ]; diff --git a/webapp/packages/plugin-connections/src/locales/zh.ts b/webapp/packages/plugin-connections/src/locales/zh.ts index 6c88549b7a..f68201b2f6 100644 --- a/webapp/packages/plugin-connections/src/locales/zh.ts +++ b/webapp/packages/plugin-connections/src/locales/zh.ts @@ -11,4 +11,6 @@ export default [ ['plugin_connections_connection_form_host_configuration_invalid', 'Host configuration is not supported'], ['plugin_connections_connection_form_name_invalid', 'Field \'name\' can\'t be empty'], ['connections_public_connection_folder_delete_confirmation', 'You\'re going to delete "{arg:name}". Connections won\'t be deleted. Are you sure?'], + ['plugin_connections_menu_connections_label', '连接'], + ['plugin_connections_action_disconnect_all_label', '断开所有连接'], ]; diff --git a/webapp/packages/plugin-data-viewer/src/DataViewerPage/useDataViewerDatabaseDataModel.ts b/webapp/packages/plugin-data-viewer/src/DataViewerPage/useDataViewerDatabaseDataModel.ts index 15af79b9fc..3d00305c66 100644 --- a/webapp/packages/plugin-data-viewer/src/DataViewerPage/useDataViewerDatabaseDataModel.ts +++ b/webapp/packages/plugin-data-viewer/src/DataViewerPage/useDataViewerDatabaseDataModel.ts @@ -9,10 +9,11 @@ import { action, computed, observable } from 'mobx'; import { useEffect } from 'react'; -import { ILoadableState, isContainsException, useMapResource, useObservableRef } from '@cloudbeaver/core-blocks'; +import { useMapResource, useObservableRef } from '@cloudbeaver/core-blocks'; import { ConnectionInfoResource } from '@cloudbeaver/core-connections'; import { useService } from '@cloudbeaver/core-di'; import { NavNodeManagerService } from '@cloudbeaver/core-navigation-tree'; +import { ILoadableState, isContainsException } from '@cloudbeaver/core-utils'; import type { ITab } from '@cloudbeaver/plugin-navigation-tabs'; import type { IObjectViewerTabState } from '@cloudbeaver/plugin-object-viewer'; @@ -60,7 +61,7 @@ export function useDataViewerDatabaseDataModel(tab: ITab) }, async reload() { if (isContainsException(connection.exception)) { - connection.reload?.(); + connection.reload(); } this.init(); }, diff --git a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderMenu.tsx b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderMenu.tsx index f20b630643..e2fe5b28e1 100644 --- a/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderMenu.tsx +++ b/webapp/packages/plugin-data-viewer/src/TableViewer/TableHeader/TableHeaderMenu.tsx @@ -51,13 +51,13 @@ const TABLE_HEADER_MENU_BAR_STYLES = css` width: 16px; } - & item-label { + & menu-bar-item-label { display: block; text-transform: uppercase; font-weight: 700; } - & IconOrImage + item-label, & Loader + item-label { + & IconOrImage + menu-bar-item-label, & Loader + menu-bar-item-label { padding-left: 8px } } diff --git a/webapp/packages/plugin-devtools/src/ContextMenu/DATA_CONTEXT_MENU_SEARCH.ts b/webapp/packages/plugin-devtools/src/ContextMenu/DATA_CONTEXT_MENU_SEARCH.ts index 8448f823a0..4ce283230f 100644 --- a/webapp/packages/plugin-devtools/src/ContextMenu/DATA_CONTEXT_MENU_SEARCH.ts +++ b/webapp/packages/plugin-devtools/src/ContextMenu/DATA_CONTEXT_MENU_SEARCH.ts @@ -8,4 +8,4 @@ import { createDataContext } from '@cloudbeaver/core-view'; -export const DATA_CONTEXT_MENU_SEARCH = createDataContext('menu-local', ''); +export const DATA_CONTEXT_MENU_SEARCH = createDataContext('menu-local', () => ''); diff --git a/webapp/packages/plugin-log-viewer/src/Actions/ACTION_LOG_VIEWER_ENABLE.ts b/webapp/packages/plugin-log-viewer/src/Actions/ACTION_LOG_VIEWER_ENABLE.ts new file mode 100644 index 0000000000..422a7a6ebd --- /dev/null +++ b/webapp/packages/plugin-log-viewer/src/Actions/ACTION_LOG_VIEWER_ENABLE.ts @@ -0,0 +1,17 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { createAction } from '@cloudbeaver/core-view'; + +export const ACTION_LOG_VIEWER_ENABLE = createAction( + 'log-viewer-enable', + { + label: 'plugin_log_viewer_action_enable_label', + type: 'checkbox', + } +); diff --git a/webapp/packages/plugin-log-viewer/src/LocaleService.ts b/webapp/packages/plugin-log-viewer/src/LocaleService.ts new file mode 100644 index 0000000000..ad46427405 --- /dev/null +++ b/webapp/packages/plugin-log-viewer/src/LocaleService.ts @@ -0,0 +1,45 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2019-2022 DBeaver Corp + * + * All Rights Reserved + * + * NOTICE: All information contained herein is, and remains + * the property of DBeaver Corp and its suppliers, if any. + * The intellectual and technical concepts contained + * herein are proprietary to DBeaver Corp and its suppliers + * and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from DBeaver Corp. + */ + +import { Bootstrap, injectable } from '@cloudbeaver/core-di'; +import { LocalizationService } from '@cloudbeaver/core-localization'; + +@injectable() +export class LocaleService extends Bootstrap { + constructor(private readonly localizationService: LocalizationService) { + super(); + } + + register(): void | Promise { + this.localizationService.addProvider(this.provider.bind(this)); + } + + load(): void | Promise { } + + private async provider(locale: string) { + switch (locale) { + case 'ru': + return (await import('./locales/ru')).default; + case 'it': + return (await import('./locales/it')).default; + case 'zh': + return (await import('./locales/zh')).default; + default: + return (await import('./locales/en')).default; + } + } +} diff --git a/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewer.tsx b/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewer.tsx index 20ddc93dbf..79637fe88c 100644 --- a/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewer.tsx +++ b/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewer.tsx @@ -40,7 +40,7 @@ export const LogViewer = observer(function LogViewer() { }, [logViewerState]); if (!logViewerState.isActive) { - return {translate('app_shared_toolsMenu_logViewer_placeholder')}; + return {translate('plugin_log_viewer_placeholder')}; } return styled(style)( diff --git a/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerBootstrap.ts b/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerBootstrap.ts index 59f52196e9..803e3aeb08 100644 --- a/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerBootstrap.ts +++ b/webapp/packages/plugin-log-viewer/src/LogViewer/LogViewerBootstrap.ts @@ -7,9 +7,10 @@ */ import { Bootstrap, injectable } from '@cloudbeaver/core-di'; -import { ToolsPanelService } from '@cloudbeaver/plugin-tools-panel'; -import { MainMenuService, EMainMenu } from '@cloudbeaver/plugin-top-app-bar'; +import { MenuService, ActionService, DATA_CONTEXT_MENU } from '@cloudbeaver/core-view'; +import { MENU_TOOLS, ToolsPanelService } from '@cloudbeaver/plugin-tools-panel'; +import { ACTION_LOG_VIEWER_ENABLE } from '../Actions/ACTION_LOG_VIEWER_ENABLE'; import { LogViewer } from './LogViewer'; import { LogViewerService } from './LogViewerService'; @@ -17,30 +18,43 @@ import { LogViewerService } from './LogViewerService'; @injectable() export class LogViewerBootstrap extends Bootstrap { constructor( - private readonly mainMenuService: MainMenuService, private readonly toolsPanelService: ToolsPanelService, + private readonly menuService: MenuService, + private readonly actionService: ActionService, private readonly logViewerService: LogViewerService ) { super(); } register(): void { - this.mainMenuService.registerMenuItem( - EMainMenu.mainMenuToolsPanel, - { - id: 'openLogViewer', - order: 1, - type: 'checkbox', - isChecked: () => this.logViewerService.isActive, - title: 'app_shared_toolsMenu_logViewer', - onClick: () => this.logViewerService.toggle(), - } - ); + this.menuService.addCreator({ + isApplicable: context => context.tryGet(DATA_CONTEXT_MENU) === MENU_TOOLS, + getItems: (context, items) => [ + ...items, + ACTION_LOG_VIEWER_ENABLE, + ], + }); + + this.actionService.addHandler({ + id: 'log-viewer-base', + isActionApplicable: (context, action) => [ + ACTION_LOG_VIEWER_ENABLE, + ].includes(action), + isChecked: () => this.logViewerService.isActive, + handler: (context, action) => { + switch (action) { + case ACTION_LOG_VIEWER_ENABLE: { + this.logViewerService.toggle(); + break; + } + } + }, + }); this.toolsPanelService.tabsContainer.add({ key: 'log-viewer-tab', order: 0, - name: 'app_shared_toolsMenu_logViewer', + name: 'plugin_log_viewer_action_enable_label', isHidden: () => !this.logViewerService.isActive, onClose: () => this.logViewerService.toggle(), panel: () => LogViewer, diff --git a/webapp/packages/plugin-log-viewer/src/locales/en.ts b/webapp/packages/plugin-log-viewer/src/locales/en.ts new file mode 100644 index 0000000000..b151a91bd1 --- /dev/null +++ b/webapp/packages/plugin-log-viewer/src/locales/en.ts @@ -0,0 +1,4 @@ +export default [ + ['plugin_log_viewer_action_enable_label', 'Log Viewer'], + ['plugin_log_viewer_placeholder', 'Log viewer is not active'], +]; diff --git a/webapp/packages/plugin-log-viewer/src/locales/it.ts b/webapp/packages/plugin-log-viewer/src/locales/it.ts new file mode 100644 index 0000000000..d1cd81df14 --- /dev/null +++ b/webapp/packages/plugin-log-viewer/src/locales/it.ts @@ -0,0 +1,4 @@ +export default [ + ['plugin_log_viewer_action_enable_label', 'Visualizzatore del Log'], + ['plugin_log_viewer_placeholder', 'Il visualizzatore del log non è attivo'], +]; diff --git a/webapp/packages/plugin-log-viewer/src/locales/ru.ts b/webapp/packages/plugin-log-viewer/src/locales/ru.ts new file mode 100644 index 0000000000..99b0c43e74 --- /dev/null +++ b/webapp/packages/plugin-log-viewer/src/locales/ru.ts @@ -0,0 +1,4 @@ +export default [ + ['plugin_log_viewer_action_enable_label', 'Просмотр лога'], + ['plugin_log_viewer_placeholder', 'Просмотр логов неактивен'], +]; diff --git a/webapp/packages/plugin-log-viewer/src/locales/zh.ts b/webapp/packages/plugin-log-viewer/src/locales/zh.ts new file mode 100644 index 0000000000..06f4aade2f --- /dev/null +++ b/webapp/packages/plugin-log-viewer/src/locales/zh.ts @@ -0,0 +1,4 @@ +export default [ + ['plugin_log_viewer_action_enable_label', '日志查看器'], + ['plugin_log_viewer_placeholder', '日志查看器未激活'], +]; diff --git a/webapp/packages/plugin-log-viewer/src/manifest.ts b/webapp/packages/plugin-log-viewer/src/manifest.ts index 3b73c5fabf..964d479d59 100644 --- a/webapp/packages/plugin-log-viewer/src/manifest.ts +++ b/webapp/packages/plugin-log-viewer/src/manifest.ts @@ -8,6 +8,7 @@ import type { PluginManifest } from '@cloudbeaver/core-di'; +import { LocaleService } from './LocaleService'; import { LogViewerBootstrap } from './LogViewer/LogViewerBootstrap'; import { LogViewerService } from './LogViewer/LogViewerService'; import { LogViewerSettingsService } from './LogViewer/LogViewerSettingsService'; @@ -17,6 +18,7 @@ export const logViewerPlugin: PluginManifest = { providers: [ LogViewerBootstrap, LogViewerService, + LocaleService, LogViewerSettingsService, ], }; \ No newline at end of file diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeLoader.tsx b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeLoader.tsx index 17e5dfb09a..cc086e2363 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeLoader.tsx +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeLoader.tsx @@ -9,8 +9,8 @@ import { observer } from 'mobx-react-lite'; import styled, { css } from 'reshadow'; -import { ILoadableState, Loader, Translate, TreeNodeNestedMessage, TREE_NODE_STYLES } from '@cloudbeaver/core-blocks'; - +import { Loader, Translate, TreeNodeNestedMessage, TREE_NODE_STYLES } from '@cloudbeaver/core-blocks'; +import type { ILoadableState } from '@cloudbeaver/core-utils'; import type { IElementsTreeContext } from './ElementsTreeContext'; diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeTools/ElementsTreeToolsMenu.tsx b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeTools/ElementsTreeToolsMenu.tsx index 2fd3ee6947..04112f1a89 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeTools/ElementsTreeToolsMenu.tsx +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/ElementsTreeTools/ElementsTreeToolsMenu.tsx @@ -50,7 +50,7 @@ const MENU_STYLES = css` width: 16px; } - & item-label { + & menu-bar-item-label { display: none; } diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts index c58b46a2c9..272fa0f666 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/useElementsTree.ts @@ -9,7 +9,7 @@ import { action, computed, observable, runInAction } from 'mobx'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { IFolderExplorerContext, ILoadableState, useExecutor, useMapResource, useObjectRef, useObservableRef, useUserData } from '@cloudbeaver/core-blocks'; +import { IFolderExplorerContext, useExecutor, useMapResource, useObjectRef, useObservableRef, useUserData } from '@cloudbeaver/core-blocks'; import { ConnectionInfoResource } from '@cloudbeaver/core-connections'; import { useService } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; @@ -17,7 +17,7 @@ import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor'; import { type NavNode, NavNodeInfoResource, NavTreeResource } from '@cloudbeaver/core-navigation-tree'; import { ProjectInfoResource, ProjectsService } from '@cloudbeaver/core-projects'; import { CachedMapAllKey, ResourceKeyUtils } from '@cloudbeaver/core-sdk'; -import { MetadataMap, throttle } from '@cloudbeaver/core-utils'; +import { ILoadableState, MetadataMap, throttle } from '@cloudbeaver/core-utils'; import type { IElementsTreeAction } from './IElementsTreeAction'; import type { NavigationNodeRendererComponent } from './NavigationNodeComponent'; diff --git a/webapp/packages/plugin-resource-manager/src/Actions/ACTION_RESOURCE_MANAGER.ts b/webapp/packages/plugin-resource-manager/src/Actions/ACTION_RESOURCE_MANAGER.ts new file mode 100644 index 0000000000..4ff8e5013f --- /dev/null +++ b/webapp/packages/plugin-resource-manager/src/Actions/ACTION_RESOURCE_MANAGER.ts @@ -0,0 +1,17 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { createAction } from '@cloudbeaver/core-view'; + +export const ACTION_RESOURCE_MANAGER = createAction( + 'resource-manager-enable', + { + label: 'plugin_resource_manager_action_enable_label', + type: 'checkbox', + } +); diff --git a/webapp/packages/plugin-resource-manager/src/PluginBootstrap.ts b/webapp/packages/plugin-resource-manager/src/PluginBootstrap.ts index 8d739ef712..d45eb997c2 100644 --- a/webapp/packages/plugin-resource-manager/src/PluginBootstrap.ts +++ b/webapp/packages/plugin-resource-manager/src/PluginBootstrap.ts @@ -7,35 +7,61 @@ */ import { Bootstrap, injectable } from '@cloudbeaver/core-di'; +import { ServerConfigResource } from '@cloudbeaver/core-root'; +import { getCachedDataResourceLoaderState } from '@cloudbeaver/core-sdk'; import { SideBarPanelService } from '@cloudbeaver/core-ui'; -import { EMainMenu, MainMenuService } from '@cloudbeaver/plugin-top-app-bar'; +import { ActionService, DATA_CONTEXT_LOADABLE_STATE, DATA_CONTEXT_MENU, MenuService } from '@cloudbeaver/core-view'; +import { MENU_TOOLS } from '@cloudbeaver/plugin-tools-panel'; +import { ACTION_RESOURCE_MANAGER } from './Actions/ACTION_RESOURCE_MANAGER'; import { ResourceManager } from './ResourceManager'; import { ResourceManagerService } from './ResourceManagerService'; @injectable() export class PluginBootstrap extends Bootstrap { constructor( - private readonly mainMenuService: MainMenuService, private readonly resourceManagerService: ResourceManagerService, private readonly sideBarPanelService: SideBarPanelService, + private readonly menuService: MenuService, + private readonly actionService: ActionService, + private readonly serverConfigResource: ServerConfigResource ) { super(); } register(): void | Promise { - this.mainMenuService.registerMenuItem( - EMainMenu.mainMenuToolsPanel, - { - id: 'resourceManagementTrigger', - order: 3, - type: 'checkbox', - title: 'plugin_resource_manager_title', - isHidden: () => !this.resourceManagerService.enabled, - isChecked: () => this.resourceManagerService.active, - onClick: this.resourceManagerService.togglePanel, - } - ); + this.menuService.addCreator({ + isApplicable: context => context.tryGet(DATA_CONTEXT_MENU) === MENU_TOOLS, + getItems: (context, items) => [ + ...items, + ACTION_RESOURCE_MANAGER, + ], + }); + + this.actionService.addHandler({ + id: 'resource-manager-base', + isActionApplicable: (context, action) => [ + ACTION_RESOURCE_MANAGER, + ].includes(action), + isHidden: () => !this.resourceManagerService.enabled, + isChecked: () => this.resourceManagerService.active, + getLoader: (context, action) => { + const state = context.get(DATA_CONTEXT_LOADABLE_STATE); + + return state.getState( + action.id, + () => getCachedDataResourceLoaderState(this.serverConfigResource, undefined, undefined) + ); + }, + handler: (context, action) => { + switch (action) { + case ACTION_RESOURCE_MANAGER: { + this.resourceManagerService.togglePanel(); + break; + } + } + }, + }); this.sideBarPanelService.tabsContainer.add({ key: 'resource-manager-tab', diff --git a/webapp/packages/plugin-resource-manager/src/locales/en.ts b/webapp/packages/plugin-resource-manager/src/locales/en.ts index cbe8c7d433..6849532d37 100644 --- a/webapp/packages/plugin-resource-manager/src/locales/en.ts +++ b/webapp/packages/plugin-resource-manager/src/locales/en.ts @@ -1,4 +1,5 @@ export default [ + ['plugin_resource_manager_action_enable_label', 'Resource manager'], ['plugin_resource_manager_title', 'Resource manager'], ['plugin_resource_manager_no_resources_placeholder', 'There are no resources yet'], ['plugin_resource_manager_script_name', 'Script name'], diff --git a/webapp/packages/plugin-resource-manager/src/locales/it.ts b/webapp/packages/plugin-resource-manager/src/locales/it.ts index f1c2699a0c..8cead0de90 100644 --- a/webapp/packages/plugin-resource-manager/src/locales/it.ts +++ b/webapp/packages/plugin-resource-manager/src/locales/it.ts @@ -1,4 +1,5 @@ export default [ + ['plugin_resource_manager_action_enable_label', 'Resource manager'], ['plugin_resource_manager_title', 'Resource manager'], ['plugin_resource_manager_no_resources_placeholder', 'No resources'], ['plugin_resource_manager_script_name', 'Script name'], diff --git a/webapp/packages/plugin-resource-manager/src/locales/ru.ts b/webapp/packages/plugin-resource-manager/src/locales/ru.ts index 7b0adb74f6..65da496e3e 100644 --- a/webapp/packages/plugin-resource-manager/src/locales/ru.ts +++ b/webapp/packages/plugin-resource-manager/src/locales/ru.ts @@ -1,4 +1,5 @@ export default [ + ['plugin_resource_manager_action_enable_label', 'Управление ресурсами'], ['plugin_resource_manager_title', 'Управление ресурсами'], ['plugin_resource_manager_no_resources_placeholder', 'Нет ресурсов'], ['plugin_resource_manager_script_name', 'Имя скрипта'], diff --git a/webapp/packages/plugin-resource-manager/src/locales/zh.ts b/webapp/packages/plugin-resource-manager/src/locales/zh.ts index 06524054b8..2dc0ba2e09 100644 --- a/webapp/packages/plugin-resource-manager/src/locales/zh.ts +++ b/webapp/packages/plugin-resource-manager/src/locales/zh.ts @@ -1,4 +1,5 @@ export default [ + ['plugin_resource_manager_action_enable_label', '资源管理器'], ['plugin_resource_manager_title', '资源管理器'], ['plugin_resource_manager_no_resources_placeholder', '无资源'], ['plugin_resource_manager_script_name', '脚本名称'], diff --git a/webapp/packages/plugin-sql-editor-navigation-tab/src/ACTION_SQL_EDITOR_NEW.ts b/webapp/packages/plugin-sql-editor-navigation-tab/src/ACTION_SQL_EDITOR_NEW.ts new file mode 100644 index 0000000000..41f0b82b41 --- /dev/null +++ b/webapp/packages/plugin-sql-editor-navigation-tab/src/ACTION_SQL_EDITOR_NEW.ts @@ -0,0 +1,16 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import { createAction } from '@cloudbeaver/core-view'; + +export const ACTION_SQL_EDITOR_NEW = createAction( + 'sql-editor-new', + { + label: 'plugin_sql_editor_navigation_tab_action_sql_editor_new', + } +); diff --git a/webapp/packages/plugin-sql-editor-navigation-tab/src/LocaleService.ts b/webapp/packages/plugin-sql-editor-navigation-tab/src/LocaleService.ts new file mode 100644 index 0000000000..ad46427405 --- /dev/null +++ b/webapp/packages/plugin-sql-editor-navigation-tab/src/LocaleService.ts @@ -0,0 +1,45 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2019-2022 DBeaver Corp + * + * All Rights Reserved + * + * NOTICE: All information contained herein is, and remains + * the property of DBeaver Corp and its suppliers, if any. + * The intellectual and technical concepts contained + * herein are proprietary to DBeaver Corp and its suppliers + * and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from DBeaver Corp. + */ + +import { Bootstrap, injectable } from '@cloudbeaver/core-di'; +import { LocalizationService } from '@cloudbeaver/core-localization'; + +@injectable() +export class LocaleService extends Bootstrap { + constructor(private readonly localizationService: LocalizationService) { + super(); + } + + register(): void | Promise { + this.localizationService.addProvider(this.provider.bind(this)); + } + + load(): void | Promise { } + + private async provider(locale: string) { + switch (locale) { + case 'ru': + return (await import('./locales/ru')).default; + case 'it': + return (await import('./locales/it')).default; + case 'zh': + return (await import('./locales/zh')).default; + default: + return (await import('./locales/en')).default; + } + } +} diff --git a/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorBootstrap.ts b/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorBootstrap.ts index 8d05c35e0d..e4f85435cd 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorBootstrap.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorBootstrap.ts @@ -13,13 +13,14 @@ import type { IExecutorHandler } from '@cloudbeaver/core-executor'; import { ExtensionUtils } from '@cloudbeaver/core-extensions'; import { DATA_CONTEXT_NAV_NODE, EObjectFeature } from '@cloudbeaver/core-navigation-tree'; import { ISessionAction, sessionActionContext, SessionActionService } from '@cloudbeaver/core-root'; -import { ActionService, ACTION_RENAME, DATA_CONTEXT_MENU_NESTED, menuExtractActions, MenuService, ViewService } from '@cloudbeaver/core-view'; -import { DATA_CONTEXT_CONNECTION } from '@cloudbeaver/plugin-connections'; +import { ActionService, ACTION_RENAME, DATA_CONTEXT_MENU, DATA_CONTEXT_MENU_NESTED, menuExtractActions, MenuService, ViewService } from '@cloudbeaver/core-view'; +import { DATA_CONTEXT_CONNECTION, MENU_CONNECTIONS } from '@cloudbeaver/plugin-connections'; import { ConnectionSchemaManagerService } from '@cloudbeaver/plugin-datasource-context-switch'; import { NavigationTabsService } from '@cloudbeaver/plugin-navigation-tabs'; import { DATA_CONTEXT_SQL_EDITOR_STATE, ESqlDataSourceFeatures, getSqlEditorName, LocalStorageSqlDataSource, SqlDataSourceService, SqlEditorService } from '@cloudbeaver/plugin-sql-editor'; -import { MainMenuService } from '@cloudbeaver/plugin-top-app-bar'; +import { TOP_APP_BAR_MENU } from '@cloudbeaver/plugin-top-app-bar'; +import { ACTION_SQL_EDITOR_NEW } from './ACTION_SQL_EDITOR_NEW'; import { ACTION_SQL_EDITOR_OPEN } from './ACTION_SQL_EDITOR_OPEN'; import { DATA_CONTEXT_SQL_EDITOR_TAB } from './DATA_CONTEXT_SQL_EDITOR_TAB'; import { isSessionActionOpenSQLEditor } from './sessionActionOpenSQLEditor'; @@ -30,7 +31,6 @@ import { SqlEditorTabService } from './SqlEditorTabService'; @injectable() export class SqlEditorBootstrap extends Bootstrap { constructor( - private readonly mainMenuService: MainMenuService, private readonly sqlEditorNavigatorService: SqlEditorNavigatorService, private readonly navigationTabsService: NavigationTabsService, private readonly connectionSchemaManagerService: ConnectionSchemaManagerService, @@ -48,14 +48,7 @@ export class SqlEditorBootstrap extends Bootstrap { } register(): void { - this.mainMenuService.registerRootItem( - { - id: 'sql-editor', - title: 'SQL', - order: 2, - onClick: this.openSQLEditor.bind(this), - } - ); + this.registerTopAppBarItem(); this.menuService.addCreator({ isApplicable: context => context.has(DATA_CONTEXT_SQL_EDITOR_STATE) && context.has(DATA_CONTEXT_SQL_EDITOR_TAB), @@ -179,6 +172,48 @@ export class SqlEditorBootstrap extends Bootstrap { load(): void { } + private registerTopAppBarItem() { + this.menuService.addCreator({ + isApplicable: context => context.tryGet(DATA_CONTEXT_MENU) === TOP_APP_BAR_MENU, + getItems: (context, items) => [ + ...items, + ACTION_SQL_EDITOR_NEW, + ], + orderItems: (context, items) => { + let placeIndex = items.indexOf(ACTION_SQL_EDITOR_NEW); + + const actionsOpen = menuExtractActions(items, [ + ACTION_SQL_EDITOR_NEW, + ]); + + const connectionsIndex = items.indexOf(MENU_CONNECTIONS); + + if (connectionsIndex !== -1) { + placeIndex = connectionsIndex + 1; + } + + items.splice(placeIndex, 0, ...actionsOpen); + + return items; + }, + }); + + this.actionService.addHandler({ + id: 'sql-editor-new', + isActionApplicable: (context, action) => [ + ACTION_SQL_EDITOR_NEW, + ].includes(action), + handler: (context, action) => { + switch (action) { + case ACTION_SQL_EDITOR_NEW: { + this.openSQLEditor(); + break; + } + } + }, + }); + } + private openSQLEditor() { let connectionKey: IConnectionInfoParams | undefined; let catalogId: string | undefined; diff --git a/webapp/packages/plugin-sql-editor-navigation-tab/src/locales/en.ts b/webapp/packages/plugin-sql-editor-navigation-tab/src/locales/en.ts new file mode 100644 index 0000000000..c205985613 --- /dev/null +++ b/webapp/packages/plugin-sql-editor-navigation-tab/src/locales/en.ts @@ -0,0 +1,3 @@ +export default [ + ['plugin_sql_editor_navigation_tab_action_sql_editor_new', 'SQL'], +]; diff --git a/webapp/packages/plugin-sql-editor-navigation-tab/src/locales/it.ts b/webapp/packages/plugin-sql-editor-navigation-tab/src/locales/it.ts new file mode 100644 index 0000000000..9f25cb126f --- /dev/null +++ b/webapp/packages/plugin-sql-editor-navigation-tab/src/locales/it.ts @@ -0,0 +1,2 @@ +export default [ +]; diff --git a/webapp/packages/plugin-sql-editor-navigation-tab/src/locales/ru.ts b/webapp/packages/plugin-sql-editor-navigation-tab/src/locales/ru.ts new file mode 100644 index 0000000000..9f25cb126f --- /dev/null +++ b/webapp/packages/plugin-sql-editor-navigation-tab/src/locales/ru.ts @@ -0,0 +1,2 @@ +export default [ +]; diff --git a/webapp/packages/plugin-sql-editor-navigation-tab/src/locales/zh.ts b/webapp/packages/plugin-sql-editor-navigation-tab/src/locales/zh.ts new file mode 100644 index 0000000000..9f25cb126f --- /dev/null +++ b/webapp/packages/plugin-sql-editor-navigation-tab/src/locales/zh.ts @@ -0,0 +1,2 @@ +export default [ +]; diff --git a/webapp/packages/plugin-sql-editor-navigation-tab/src/manifest.ts b/webapp/packages/plugin-sql-editor-navigation-tab/src/manifest.ts index 86b08d8878..720a2dd0a6 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab/src/manifest.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab/src/manifest.ts @@ -8,6 +8,7 @@ import type { PluginManifest } from '@cloudbeaver/core-di'; +import { LocaleService } from './LocaleService'; import { SqlEditorBootstrap } from './SqlEditorBootstrap'; import { SqlEditorNavigatorService } from './SqlEditorNavigatorService'; import { SqlEditorTabService } from './SqlEditorTabService'; @@ -21,5 +22,6 @@ export const sqlEditorTabPluginManifest: PluginManifest = { SqlEditorBootstrap, SqlEditorTabService, SqlEditorNavigatorService, + LocaleService, ], }; diff --git a/webapp/packages/plugin-sql-editor/src/SqlDataSource/ISqlDataSource.ts b/webapp/packages/plugin-sql-editor/src/SqlDataSource/ISqlDataSource.ts index 4541c948bc..505644c498 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlDataSource/ISqlDataSource.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlDataSource/ISqlDataSource.ts @@ -6,9 +6,9 @@ * you may not use this file except in compliance with the License. */ -import type { ILoadableState } from '@cloudbeaver/core-blocks'; import type { IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections'; import type { ISyncExecutor } from '@cloudbeaver/core-executor'; +import type { ILoadableState } from '@cloudbeaver/core-utils'; import type { ESqlDataSourceFeatures } from './ESqlDataSourceFeatures'; diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditor/SQL_EDITOR_ACTIONS_MENU_STYLES.ts b/webapp/packages/plugin-sql-editor/src/SqlEditor/SQL_EDITOR_ACTIONS_MENU_STYLES.ts index 1c98b41936..c09fa9bb93 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditor/SQL_EDITOR_ACTIONS_MENU_STYLES.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlEditor/SQL_EDITOR_ACTIONS_MENU_STYLES.ts @@ -43,13 +43,13 @@ export const SQL_EDITOR_ACTIONS_MENU_STYLES = css` width: 24px; } - & item-label { + & menu-bar-item-label { display: block; text-transform: uppercase; font-weight: 700; } - & IconOrImage + item-label, & Loader + item-label { + & IconOrImage + menu-bar-item-label, & Loader + menu-bar-item-label { padding-left: 8px } } diff --git a/webapp/packages/plugin-tools-panel/package.json b/webapp/packages/plugin-tools-panel/package.json index f718acc2ed..be25b19400 100644 --- a/webapp/packages/plugin-tools-panel/package.json +++ b/webapp/packages/plugin-tools-panel/package.json @@ -38,7 +38,8 @@ "@cloudbeaver/core-utils": "~0.1.0", "@cloudbeaver/core-view": "~0.1.0", "@cloudbeaver/plugin-version": "~0.1.0", - "@cloudbeaver/plugin-settings-menu": "~0.1.0" + "@cloudbeaver/plugin-settings-menu": "~0.1.0", + "@cloudbeaver/plugin-top-app-bar": "~0.1.0" }, "peerDependencies": { "react": "~18.x.x", diff --git a/webapp/packages/plugin-tools-panel/src/LocaleService.ts b/webapp/packages/plugin-tools-panel/src/LocaleService.ts new file mode 100644 index 0000000000..ad46427405 --- /dev/null +++ b/webapp/packages/plugin-tools-panel/src/LocaleService.ts @@ -0,0 +1,45 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2019-2022 DBeaver Corp + * + * All Rights Reserved + * + * NOTICE: All information contained herein is, and remains + * the property of DBeaver Corp and its suppliers, if any. + * The intellectual and technical concepts contained + * herein are proprietary to DBeaver Corp and its suppliers + * and may be covered by U.S. and Foreign Patents, + * patents in process, and are protected by trade secret or copyright law. + * Dissemination of this information or reproduction of this material + * is strictly forbidden unless prior written permission is obtained + * from DBeaver Corp. + */ + +import { Bootstrap, injectable } from '@cloudbeaver/core-di'; +import { LocalizationService } from '@cloudbeaver/core-localization'; + +@injectable() +export class LocaleService extends Bootstrap { + constructor(private readonly localizationService: LocalizationService) { + super(); + } + + register(): void | Promise { + this.localizationService.addProvider(this.provider.bind(this)); + } + + load(): void | Promise { } + + private async provider(locale: string) { + switch (locale) { + case 'ru': + return (await import('./locales/ru')).default; + case 'it': + return (await import('./locales/it')).default; + case 'zh': + return (await import('./locales/zh')).default; + default: + return (await import('./locales/en')).default; + } + } +} diff --git a/webapp/packages/plugin-tools-panel/src/Menu/MENU_TOOLS.ts b/webapp/packages/plugin-tools-panel/src/Menu/MENU_TOOLS.ts new file mode 100644 index 0000000000..067e7fd783 --- /dev/null +++ b/webapp/packages/plugin-tools-panel/src/Menu/MENU_TOOLS.ts @@ -0,0 +1,14 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 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 MENU_TOOLS = createMenu( + 'tools', + 'plugin_tools_panel_menu_title' +); diff --git a/webapp/packages/plugin-tools-panel/src/PluginBootstrap.ts b/webapp/packages/plugin-tools-panel/src/PluginBootstrap.ts index 9a71470d48..6a9ad7224b 100644 --- a/webapp/packages/plugin-tools-panel/src/PluginBootstrap.ts +++ b/webapp/packages/plugin-tools-panel/src/PluginBootstrap.ts @@ -7,14 +7,28 @@ */ import { Bootstrap, injectable } from '@cloudbeaver/core-di'; +import { DATA_CONTEXT_MENU, MenuService } from '@cloudbeaver/core-view'; +import { TOP_APP_BAR_MENU } from '@cloudbeaver/plugin-top-app-bar'; + +import { MENU_TOOLS } from './Menu/MENU_TOOLS'; @injectable() export class PluginBootstrap extends Bootstrap { - constructor() { + constructor( + private readonly menuService: MenuService, + ) { super(); } - register(): void | Promise { } + register(): void { + this.menuService.addCreator({ + isApplicable: context => context.tryGet(DATA_CONTEXT_MENU) === TOP_APP_BAR_MENU, + getItems: (context, items) => [ + ...items, + MENU_TOOLS, + ], + }); + } load(): void | Promise { } } \ No newline at end of file diff --git a/webapp/packages/plugin-tools-panel/src/index.ts b/webapp/packages/plugin-tools-panel/src/index.ts index 9accda035f..419ad25187 100644 --- a/webapp/packages/plugin-tools-panel/src/index.ts +++ b/webapp/packages/plugin-tools-panel/src/index.ts @@ -1,3 +1,4 @@ +export * from './Menu/MENU_TOOLS'; export * from './ToolsPanel/ToolsPanelService'; export * from './ToolsPanel/ToolsPanel'; diff --git a/webapp/packages/plugin-tools-panel/src/locales/en.ts b/webapp/packages/plugin-tools-panel/src/locales/en.ts new file mode 100644 index 0000000000..6179253ae0 --- /dev/null +++ b/webapp/packages/plugin-tools-panel/src/locales/en.ts @@ -0,0 +1,3 @@ +export default [ + ['plugin_tools_panel_menu_title', 'Tools'], +]; diff --git a/webapp/packages/plugin-tools-panel/src/locales/it.ts b/webapp/packages/plugin-tools-panel/src/locales/it.ts new file mode 100644 index 0000000000..81bfffa650 --- /dev/null +++ b/webapp/packages/plugin-tools-panel/src/locales/it.ts @@ -0,0 +1,3 @@ +export default [ + ['plugin_tools_panel_menu_title', 'Strumenti'], +]; diff --git a/webapp/packages/plugin-tools-panel/src/locales/ru.ts b/webapp/packages/plugin-tools-panel/src/locales/ru.ts new file mode 100644 index 0000000000..228965f39c --- /dev/null +++ b/webapp/packages/plugin-tools-panel/src/locales/ru.ts @@ -0,0 +1,3 @@ +export default [ + ['plugin_tools_panel_menu_title', 'Инструменты'], +]; diff --git a/webapp/packages/plugin-tools-panel/src/locales/zh.ts b/webapp/packages/plugin-tools-panel/src/locales/zh.ts new file mode 100644 index 0000000000..54be2eb6d3 --- /dev/null +++ b/webapp/packages/plugin-tools-panel/src/locales/zh.ts @@ -0,0 +1,3 @@ +export default [ + ['plugin_tools_panel_menu_title', '工具'], +]; diff --git a/webapp/packages/plugin-tools-panel/src/manifest.ts b/webapp/packages/plugin-tools-panel/src/manifest.ts index 78c0818c7d..8dde29a8d0 100644 --- a/webapp/packages/plugin-tools-panel/src/manifest.ts +++ b/webapp/packages/plugin-tools-panel/src/manifest.ts @@ -8,6 +8,7 @@ import type { PluginManifest } from '@cloudbeaver/core-di'; +import { LocaleService } from './LocaleService'; import { PluginBootstrap } from './PluginBootstrap'; import { ToolsPanelService } from './ToolsPanel/ToolsPanelService'; @@ -17,5 +18,6 @@ export const toolsPanelPlugin: PluginManifest = { providers: [ ToolsPanelService, PluginBootstrap, + LocaleService, ], }; \ No newline at end of file diff --git a/webapp/packages/plugin-top-app-bar/src/TopNavBar/MainMenu/MainMenu.tsx b/webapp/packages/plugin-top-app-bar/src/TopNavBar/MainMenu/MainMenu.tsx index 03898dfdc7..7de0557a37 100644 --- a/webapp/packages/plugin-top-app-bar/src/TopNavBar/MainMenu/MainMenu.tsx +++ b/webapp/packages/plugin-top-app-bar/src/TopNavBar/MainMenu/MainMenu.tsx @@ -9,10 +9,66 @@ import { observer } from 'mobx-react-lite'; import styled, { css } from 'reshadow'; -import { useService } from '@cloudbeaver/core-di'; +import { MenuBar } from '@cloudbeaver/core-ui'; +import { useMenu } from '@cloudbeaver/core-view'; -import { TopMenuItem } from '../shared/TopMenuItem'; -import { MainMenuService } from './MainMenuService'; +import { topMenuStyles } from '../shared/topMenuStyles'; +import { TOP_APP_BAR_MENU } from './TOP_APP_BAR_MENU'; + +const menuStyles = css` + Menu { + & menu-box { + max-height: 80vh; + overflow: auto; + } + & menu-panel-item { + overflow-x: hidden; + } + & menu-item-text { + max-width: 400px; + overflow-x: hidden; + text-overflow: ellipsis; + } + & menu-trigger-text { + text-transform: uppercase; + font-weight: 500; + } + } +`; + +const menuBarItemStyles = css` + MenuBarElement { + background: none; + + & menu-bar-item-label { + text-transform: uppercase; + font-weight: 500; + } + } +`; + +const removeDisableEffect = css` + Button:disabled, Button[aria-disabled="true"] { + opacity: 1; + } +`; + +const projectsMenu = css` + MenuItem IconOrImage { + background-color: #fff; + padding: 2px; + border-radius: var(--theme-form-element-radius); + } + menu-trigger-icon:not([|loading]) { + background-color: #fff; + border-radius: 4px; + padding: 1px; + + & IconOrImage { + width: 22px; + } + } +`; const styles = css` menu-wrapper { @@ -27,13 +83,15 @@ const styles = css` `; export const MainMenu = observer(function MainMenu() { - const mainMenuService = useService(MainMenuService); + const menu = useMenu({ menu: TOP_APP_BAR_MENU }); return styled(styles)( - {mainMenuService.getMainMenu().map((topItem, i) => ( - - ))} + ); }); diff --git a/webapp/packages/plugin-top-app-bar/src/TopNavBar/MainMenu/MainMenuService.ts b/webapp/packages/plugin-top-app-bar/src/TopNavBar/MainMenu/MainMenuService.ts deleted file mode 100644 index bd535ca40d..0000000000 --- a/webapp/packages/plugin-top-app-bar/src/TopNavBar/MainMenu/MainMenuService.ts +++ /dev/null @@ -1,62 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2022 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 { IComputedMenuItemOptions, StaticMenu } from '@cloudbeaver/core-dialogs'; -import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor'; - -export enum EMainMenu { - 'mainMenuConnectionsPanel' = 'mainMenuConnectionsPanel', - 'mainMenuToolsPanel' = 'mainMenuToolsPanel', -} - -@injectable() -export class MainMenuService { - readonly onConnectionClick: ISyncExecutor; - - private readonly menuOptions = new StaticMenu(); - private readonly mainMenuToken = 'mainMenu'; - - constructor() { - this.onConnectionClick = new SyncExecutor(); - this.menuOptions.addRootPanel(this.mainMenuToken); - - const connectionMenu: IComputedMenuItemOptions = { - id: EMainMenu.mainMenuConnectionsPanel, - order: 1, - title: 'app_shared_connectionMenu_connection', - onClick: () => this.onConnectionClick.execute(), - isPanel: true, - }; - this.registerRootItem(connectionMenu); - - const toolsMenu: IComputedMenuItemOptions = { - id: EMainMenu.mainMenuToolsPanel, - order: 3, - title: 'app_shared_toolsMenu_tools', - isPanel: true, - }; - this.registerRootItem(toolsMenu); - } - - getMainMenu() { - return this.menuOptions.getMenu(this.mainMenuToken).menuItems; - } - - isEmptyMenuPanel(token: string): boolean { - return this.menuOptions.getMenu(token).menuItems.length === 0; - } - - registerMenuItem(panelId: string, options: IComputedMenuItemOptions): void { - this.menuOptions.addMenuItem(panelId, options); - } - - registerRootItem(menuItem: IComputedMenuItemOptions): void { - this.registerMenuItem(this.mainMenuToken, menuItem); - } -} diff --git a/webapp/packages/plugin-top-app-bar/src/TopNavBar/MainMenu/TOP_APP_BAR_MENU.ts b/webapp/packages/plugin-top-app-bar/src/TopNavBar/MainMenu/TOP_APP_BAR_MENU.ts new file mode 100644 index 0000000000..4fd3e3636a --- /dev/null +++ b/webapp/packages/plugin-top-app-bar/src/TopNavBar/MainMenu/TOP_APP_BAR_MENU.ts @@ -0,0 +1,11 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2022 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_APP_BAR_MENU = createMenu('top-app-bar', 'Top App Bar'); diff --git a/webapp/packages/plugin-top-app-bar/src/TopNavBar/shared/topMenuStyles.ts b/webapp/packages/plugin-top-app-bar/src/TopNavBar/shared/topMenuStyles.ts index 6307a5e89e..3d92123371 100644 --- a/webapp/packages/plugin-top-app-bar/src/TopNavBar/shared/topMenuStyles.ts +++ b/webapp/packages/plugin-top-app-bar/src/TopNavBar/shared/topMenuStyles.ts @@ -22,7 +22,15 @@ export const topMenuStyles = css` menu-box menu-panel-item { border-color: #ffffff !important; } - MenuTrigger, Button { + MenuButton, MenuBarElement { + composes: theme-ripple from global; + background: none; + border: none; + outline: none !important; + color: inherit; + cursor: pointer; + } + MenuButton, MenuTrigger, Button, MenuBarElement { height: 100%; padding: 0 16px !important; @@ -41,7 +49,7 @@ export const topMenuStyles = css` & menu-trigger-icon { margin-right: 8px; } - & menu-trigger-text { + & menu-trigger-text, & menu-bar-item-label { max-width: 240px; overflow-x: hidden; text-overflow: ellipsis; @@ -51,6 +59,8 @@ export const topMenuStyles = css` background: #236ea0; }*/ } + MenuButton, + MenuBarElement, MenuItem, MenuItemCheckbox, MenuItemRadio { @@ -62,7 +72,7 @@ export const topMenuStyles = css` } } @media only screen and (max-width: 1200px) { - MenuTrigger, Button { + MenuButton, MenuBarElement, MenuTrigger, Button { padding: 0 8px !important; } } diff --git a/webapp/packages/plugin-top-app-bar/src/index.ts b/webapp/packages/plugin-top-app-bar/src/index.ts index 3ec739bd09..61f6ae2572 100644 --- a/webapp/packages/plugin-top-app-bar/src/index.ts +++ b/webapp/packages/plugin-top-app-bar/src/index.ts @@ -1,4 +1,4 @@ -export * from './TopNavBar/MainMenu/MainMenuService'; +export * from './TopNavBar/MainMenu/TOP_APP_BAR_MENU'; export * from './TopNavBar/shared/TopNavButton'; export * from './TopNavBar/TopNavService'; export * from './TopNavBar/AdministrationTopAppBarBootstrapService'; diff --git a/webapp/packages/plugin-top-app-bar/src/manifest.ts b/webapp/packages/plugin-top-app-bar/src/manifest.ts index 099638b9dd..80585f31be 100644 --- a/webapp/packages/plugin-top-app-bar/src/manifest.ts +++ b/webapp/packages/plugin-top-app-bar/src/manifest.ts @@ -10,7 +10,6 @@ import type { PluginManifest } from '@cloudbeaver/core-di'; import { PluginBootstrap } from './PluginBootstrap'; import { AdministrationTopAppBarBootstrapService } from './TopNavBar/AdministrationTopAppBarBootstrapService'; -import { MainMenuService } from './TopNavBar/MainMenu/MainMenuService'; import { TopNavService } from './TopNavBar/TopNavService'; export const topAppBarPlugin: PluginManifest = { @@ -19,7 +18,6 @@ export const topAppBarPlugin: PluginManifest = { }, providers: [ PluginBootstrap, - MainMenuService, TopNavService, AdministrationTopAppBarBootstrapService, ],