diff --git a/webapp/packages/core-app/package.json b/webapp/packages/core-app/package.json index 97be7f6399..c5df1bdcd0 100644 --- a/webapp/packages/core-app/package.json +++ b/webapp/packages/core-app/package.json @@ -27,6 +27,7 @@ "@cloudbeaver/core-sdk": "^0.1.0", "@cloudbeaver/core-settings": "^0.1.0", "@cloudbeaver/core-theming": "^0.1.0", + "@cloudbeaver/core-ui": "^0.1.0", "@cloudbeaver/core-utils": "^0.1.0", "@cloudbeaver/core-view": "^0.1.0", "@cloudbeaver/core-executor": "^0.1.0", diff --git a/webapp/packages/core-app/src/AppScreen/Main.tsx b/webapp/packages/core-app/src/AppScreen/Main.tsx index 442b76f6a6..c130ff5f1f 100644 --- a/webapp/packages/core-app/src/AppScreen/Main.tsx +++ b/webapp/packages/core-app/src/AppScreen/Main.tsx @@ -42,7 +42,7 @@ const mainStyles = composes( ` ); -export function Main() { +export const Main: React.FC = function Main() { return styled(useStyles(mainStyles, splitStyles))( @@ -56,4 +56,4 @@ export function Main() { ); -} +}; diff --git a/webapp/packages/core-app/src/AppScreen/RightArea.tsx b/webapp/packages/core-app/src/AppScreen/RightArea.tsx index 009c2ad3c9..21205bea17 100644 --- a/webapp/packages/core-app/src/AppScreen/RightArea.tsx +++ b/webapp/packages/core-app/src/AppScreen/RightArea.tsx @@ -10,10 +10,11 @@ import { observer } from 'mobx-react-lite'; import styled, { css } from 'reshadow'; import { - Pane, ResizerControls, Split, splitHorizontalStyles, splitStyles + Pane, ResizerControls, SlideBox, SlideElement, slideBoxStyles, Split, splitHorizontalStyles, splitStyles } from '@cloudbeaver/core-blocks'; -import { useController } from '@cloudbeaver/core-di'; +import { useController, useService } from '@cloudbeaver/core-di'; import { useStyles, composes } from '@cloudbeaver/core-theming'; +import { OptionsPanelService } from '@cloudbeaver/core-ui'; import { NavigationTabsBar } from '../shared/NavigationTabs/NavigationTabsBar'; import { LogViewTab } from '../shared/ToolsPanel/LogViewTab/LogViewTab'; @@ -34,21 +35,36 @@ const styles = composes( Pane:last-child { flex: 0 0 30%; } + SlideBox { + flex: 1; + } + SlideElement { + display: flex; + } ` ); export const RightArea = observer(function RightArea() { const controller = useController(LogViewTabController); + const optionsPanelService = useService(OptionsPanelService); + const OptionsPanel = optionsPanelService.getPanelComponent(); - return styled(useStyles(styles, splitStyles, splitHorizontalStyles))( - - - - - {controller.isActive && } - - - - + return styled(useStyles(styles, splitStyles, splitHorizontalStyles, slideBoxStyles))( + + + + + + + + + + {controller.isActive && } + + + + + + ); }); diff --git a/webapp/packages/core-blocks/src/Loader/Loader.tsx b/webapp/packages/core-blocks/src/Loader/Loader.tsx index a00fc9fbc9..c103dd94b0 100644 --- a/webapp/packages/core-blocks/src/Loader/Loader.tsx +++ b/webapp/packages/core-blocks/src/Loader/Loader.tsx @@ -70,7 +70,12 @@ export const Loader: React.FC = observer(function Loader({ if ('loading' in element) { loading = element.loading; } else if ('isLoading' in element) { - loading = element.isLoading() && (overlay || !element.isLoaded()); + const loaded = element.isLoaded(); + loading = element.isLoading() || !loaded; + + if (!loaded) { + overlay = false; + } } } } diff --git a/webapp/packages/core-blocks/src/Slide/SlideBox.tsx b/webapp/packages/core-blocks/src/Slide/SlideBox.tsx new file mode 100644 index 0000000000..37d6558b12 --- /dev/null +++ b/webapp/packages/core-blocks/src/Slide/SlideBox.tsx @@ -0,0 +1,20 @@ +/* + * 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. + */ + +interface Props { + open?: boolean; + className?: string; +} + +export const SlideBox: React.FC = function SlideBox({ children, className }) { + return ( +
+ {children} +
+ ); +}; diff --git a/webapp/packages/core-blocks/src/Slide/SlideBoxStyles.ts b/webapp/packages/core-blocks/src/Slide/SlideBoxStyles.ts new file mode 100644 index 0000000000..95d7323131 --- /dev/null +++ b/webapp/packages/core-blocks/src/Slide/SlideBoxStyles.ts @@ -0,0 +1,29 @@ +/* + * 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 { css } from 'reshadow'; + +export const slideBoxStyles = css` + SlideBox { + overflow: hidden; + white-space: nowrap; + + & SlideElement { + width: 100%; + height: 100%; + display: inline-block; + vertical-align:top; + transition: transform cubic-bezier(0.4, 0.0, 0.2, 1) 0.6s; + transform: translateX(-100%); + } + + &[open] SlideElement { + transform: translateX(0%); + } + } +`; diff --git a/webapp/packages/core-blocks/src/Slide/SlideElement.tsx b/webapp/packages/core-blocks/src/Slide/SlideElement.tsx new file mode 100644 index 0000000000..915ceb8323 --- /dev/null +++ b/webapp/packages/core-blocks/src/Slide/SlideElement.tsx @@ -0,0 +1,19 @@ +/* + * 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. + */ + +interface Props { + className?: string; +} + +export const SlideElement: React.FC = function SlideElement({ children, className }) { + return ( +
+ {children} +
+ ); +}; diff --git a/webapp/packages/core-blocks/src/Tabs/Tab/TabDefault.tsx b/webapp/packages/core-blocks/src/Tabs/Tab/TabDefault.tsx index 7bd38a6365..2964f46d8c 100644 --- a/webapp/packages/core-blocks/src/Tabs/Tab/TabDefault.tsx +++ b/webapp/packages/core-blocks/src/Tabs/Tab/TabDefault.tsx @@ -13,7 +13,7 @@ import { Translate } from '@cloudbeaver/core-localization'; import { DynamicStyle, useStyles } from '@cloudbeaver/core-theming'; import { TabContext } from '../TabContext'; -import type { ITabData } from '../TabsContext'; +import type { ITabData } from '../TabsContainer/ITabsContainer'; import { Tab } from './Tab'; import { TabIcon } from './TabIcon'; import type { TabProps } from './TabProps'; diff --git a/webapp/packages/core-blocks/src/Tabs/Tab/TabProps.ts b/webapp/packages/core-blocks/src/Tabs/Tab/TabProps.ts index 9b84578dc6..a8a137171d 100644 --- a/webapp/packages/core-blocks/src/Tabs/Tab/TabProps.ts +++ b/webapp/packages/core-blocks/src/Tabs/Tab/TabProps.ts @@ -10,7 +10,7 @@ import type { PropsWithChildren } from 'react'; import type { DynamicStyle } from '@cloudbeaver/core-theming'; -import type { ITabData } from '../TabsContext'; +import type { ITabData } from '../TabsContainer/ITabsContainer'; export type TabProps = PropsWithChildren<{ tabId: string; diff --git a/webapp/packages/core-blocks/src/Tabs/Tab/useTab.ts b/webapp/packages/core-blocks/src/Tabs/Tab/useTab.ts index 5762af9b7c..43a059b9c4 100644 --- a/webapp/packages/core-blocks/src/Tabs/Tab/useTab.ts +++ b/webapp/packages/core-blocks/src/Tabs/Tab/useTab.ts @@ -6,12 +6,13 @@ * you may not use this file except in compliance with the License. */ -import { useContext, useEffect, useRef } from 'react'; +import { useContext, useEffect } from 'react'; import type { IExecutorHandler } from '@cloudbeaver/core-executor'; import { useObjectRef } from '../../useObjectRef'; -import { ITabData, TabsContext } from '../TabsContext'; +import type { ITabData } from '../TabsContainer/ITabsContainer'; +import { TabsContext } from '../TabsContext'; export function useTab( tabId: string, diff --git a/webapp/packages/core-blocks/src/Tabs/TabPanelList.tsx b/webapp/packages/core-blocks/src/Tabs/TabPanelList.tsx index 9b5c12c24d..b1a625a400 100644 --- a/webapp/packages/core-blocks/src/Tabs/TabPanelList.tsx +++ b/webapp/packages/core-blocks/src/Tabs/TabPanelList.tsx @@ -12,7 +12,7 @@ import styled from 'reshadow'; import { DynamicStyle, useStyles } from '@cloudbeaver/core-theming'; import { TabPanel } from './TabPanel'; -import type { ITabInfo } from './TabsContainer'; +import type { ITabInfo } from './TabsContainer/ITabsContainer'; import { TabsContext } from './TabsContext'; interface Props { diff --git a/webapp/packages/core-blocks/src/Tabs/TabsBox/TabsBox.tsx b/webapp/packages/core-blocks/src/Tabs/TabsBox/TabsBox.tsx index 94be3e0f37..c899d1f748 100644 --- a/webapp/packages/core-blocks/src/Tabs/TabsBox/TabsBox.tsx +++ b/webapp/packages/core-blocks/src/Tabs/TabsBox/TabsBox.tsx @@ -29,7 +29,7 @@ export const TabsBox = forwardRef(function TabsBox className, style = [], }: TabsBoxProps, ref) { - return styled(useStyles(...style))( + return styled(useStyles(style))( {tabs && ( diff --git a/webapp/packages/core-blocks/src/Tabs/TabsBox/TabsBoxFromArray.tsx b/webapp/packages/core-blocks/src/Tabs/TabsBox/TabsBoxFromArray.tsx index 98812fcdd1..a96356c634 100644 --- a/webapp/packages/core-blocks/src/Tabs/TabsBox/TabsBoxFromArray.tsx +++ b/webapp/packages/core-blocks/src/Tabs/TabsBox/TabsBoxFromArray.tsx @@ -13,7 +13,7 @@ import { useStyles, Style } from '@cloudbeaver/core-theming'; import { Tab } from '../Tab'; import { TabPanel } from '../TabPanel'; -import type { ITabData } from '../TabsContext'; +import type { ITabData } from '../TabsContainer/ITabsContainer'; import { TabsBox } from './TabsBox'; export interface TabElementProps { diff --git a/webapp/packages/core-blocks/src/Tabs/TabsContainer.ts b/webapp/packages/core-blocks/src/Tabs/TabsContainer/ITabsContainer.ts similarity index 53% rename from webapp/packages/core-blocks/src/Tabs/TabsContainer.ts rename to webapp/packages/core-blocks/src/Tabs/TabsContainer/ITabsContainer.ts index a4487a3f95..3d76ec62c8 100644 --- a/webapp/packages/core-blocks/src/Tabs/TabsContainer.ts +++ b/webapp/packages/core-blocks/src/Tabs/TabsContainer/ITabsContainer.ts @@ -6,12 +6,14 @@ * you may not use this file except in compliance with the License. */ -import { computed, observable, makeObservable } from 'mobx'; - import type { MetadataValueGetter } from '@cloudbeaver/core-utils'; -import type { TabProps } from './Tab/TabProps'; -import type { ITabData } from './TabsContext'; +import type { TabProps } from '../Tab/TabProps'; + +export interface ITabData> { + tabId: string; + props: T; +} export type TabContainerTabComponent> = React.FC; export type TabContainerPanelComponent> = React.FC<{ tabId: string } & TProps>; @@ -42,35 +44,8 @@ export interface ITabInfo< order: number; } -export class TabsContainer, TOptions extends Record = never> { - readonly tabInfoMap: Map>; - - get tabInfoList(): Array> { - return Array.from(this.tabInfoMap.values()) - .sort((a, b) => a.order - b.order); - } - - constructor() { - makeObservable(this, { - tabInfoMap: observable.shallow, - tabInfoList: computed, - }); - - this.tabInfoMap = new Map(); - } - - getDisplayed(props?: TProps): Array> { - return this.tabInfoList.filter(tabInfo => !tabInfo.isHidden?.(tabInfo.key, props)); - } - - add(tabInfo: ITabInfoOptions): void { - if (this.tabInfoMap.has(tabInfo.key)) { - throw new Error('Tab with same key already exists'); - } - - this.tabInfoMap.set(tabInfo.key, { - ...tabInfo, - order: tabInfo.order ?? Number.MAX_SAFE_INTEGER, - }); - } +export interface ITabsContainer, TOptions extends Record = never> { + tabInfoList: Array>; + getTabInfo: (tabId: string) => ITabInfo | undefined; + getDisplayed: (props?: TProps) => Array>; } diff --git a/webapp/packages/core-blocks/src/Tabs/TabsContainer/TabsContainer.ts b/webapp/packages/core-blocks/src/Tabs/TabsContainer/TabsContainer.ts new file mode 100644 index 0000000000..6869b4f4c8 --- /dev/null +++ b/webapp/packages/core-blocks/src/Tabs/TabsContainer/TabsContainer.ts @@ -0,0 +1,49 @@ +/* + * 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 { computed, observable, makeObservable } from 'mobx'; + +import type { ITabInfo, ITabInfoOptions, ITabsContainer } from './ITabsContainer'; + +export class TabsContainer, TOptions extends Record = never> +implements ITabsContainer { + readonly tabInfoMap: Map>; + + get tabInfoList(): Array> { + return Array.from(this.tabInfoMap.values()) + .sort((a, b) => a.order - b.order); + } + + constructor() { + makeObservable(this, { + tabInfoMap: observable.shallow, + tabInfoList: computed, + }); + + this.tabInfoMap = new Map(); + } + + getTabInfo(tabId: string): ITabInfo | undefined { + return this.tabInfoMap.get(tabId); + } + + getDisplayed(props?: TProps): Array> { + return this.tabInfoList.filter(tabInfo => !tabInfo.isHidden?.(tabInfo.key, props)); + } + + add(tabInfo: ITabInfoOptions): void { + if (this.tabInfoMap.has(tabInfo.key)) { + throw new Error('Tab with same key already exists'); + } + + this.tabInfoMap.set(tabInfo.key, { + ...tabInfo, + order: tabInfo.order ?? Number.MAX_SAFE_INTEGER, + }); + } +} diff --git a/webapp/packages/core-blocks/src/Tabs/TabsContext.ts b/webapp/packages/core-blocks/src/Tabs/TabsContext.ts index 06eea8c5a7..68d64daf8f 100644 --- a/webapp/packages/core-blocks/src/Tabs/TabsContext.ts +++ b/webapp/packages/core-blocks/src/Tabs/TabsContext.ts @@ -12,18 +12,13 @@ import type { TabStateReturn } from 'reakit/Tab'; import type { IExecutor } from '@cloudbeaver/core-executor'; import type { MetadataMap } from '@cloudbeaver/core-utils'; -import type { ITabInfo, TabsContainer } from './TabsContainer'; - -export interface ITabData> { - tabId: string; - props: T; -} +import type { ITabData, ITabInfo, ITabsContainer } from './TabsContainer/ITabsContainer'; export interface ITabsContext> { state: TabStateReturn; tabsState: MetadataMap; props: T; - container?: TabsContainer; + container?: ITabsContainer; openExecutor: IExecutor>; closeExecutor: IExecutor>; lazy: boolean; diff --git a/webapp/packages/core-blocks/src/Tabs/TabsState.tsx b/webapp/packages/core-blocks/src/Tabs/TabsState.tsx index 8d7cde7e98..83661f07ef 100644 --- a/webapp/packages/core-blocks/src/Tabs/TabsState.tsx +++ b/webapp/packages/core-blocks/src/Tabs/TabsState.tsx @@ -13,14 +13,14 @@ import { Executor, ExecutorInterrupter, IExecutorHandler } from '@cloudbeaver/co import { MetadataMap } from '@cloudbeaver/core-utils'; import { useObjectRef } from '../useObjectRef'; -import type { TabsContainer } from './TabsContainer'; -import { TabsContext, ITabsContext, ITabData } from './TabsContext'; +import type { ITabData, ITabsContainer } from './TabsContainer/ITabsContainer'; +import { TabsContext, ITabsContext } from './TabsContext'; type Props> = T & React.PropsWithChildren<{ selectedId?: string; orientation?: 'horizontal' | 'vertical'; currentTabId?: string | null; - container?: TabsContainer; + container?: ITabsContainer; localState?: MetadataMap; lazy?: boolean; manual?: boolean; @@ -120,7 +120,7 @@ export function TabsState>({ props: dynamic.props, }), []); - const getTabInfo = useCallback((tabId: string) => dynamic.container?.tabInfoMap.get(tabId), []); + const getTabInfo = useCallback((tabId: string) => dynamic.container?.getTabInfo(tabId), []); const value = useMemo>(() => ({ state, diff --git a/webapp/packages/core-blocks/src/Tabs/index.ts b/webapp/packages/core-blocks/src/Tabs/index.ts index 5e45c1ccd2..28483dbd6b 100644 --- a/webapp/packages/core-blocks/src/Tabs/index.ts +++ b/webapp/packages/core-blocks/src/Tabs/index.ts @@ -4,7 +4,8 @@ export * from './TabList'; export * from './TabPanel'; export * from './TabPanelList'; export * from './TabPanelProps'; -export * from './TabsContainer'; +export * from './TabsContainer/ITabsContainer'; +export * from './TabsContainer/TabsContainer'; export * from './TabsContext'; export * from './TabsState'; export * from './useTabState'; diff --git a/webapp/packages/core-blocks/src/index.ts b/webapp/packages/core-blocks/src/index.ts index 8adbe9dab2..38fec05355 100644 --- a/webapp/packages/core-blocks/src/index.ts +++ b/webapp/packages/core-blocks/src/index.ts @@ -11,6 +11,9 @@ export * from './Placeholder/Placeholder'; export * from './Placeholder/PlaceholderContainer'; export * from './PropertiesTable'; export * from './SanitizedHTML/SanitizedHTML'; +export * from './Slide/SlideBox'; +export * from './Slide/SlideBoxStyles'; +export * from './Slide/SlideElement'; export * from './Split'; export * from './Table'; export * from './Tabs'; diff --git a/webapp/packages/core-bootstrap/package.json b/webapp/packages/core-bootstrap/package.json index f7729d812c..06d6eb9de3 100644 --- a/webapp/packages/core-bootstrap/package.json +++ b/webapp/packages/core-bootstrap/package.json @@ -33,6 +33,7 @@ "@cloudbeaver/core-sdk": "^0.1.0", "@cloudbeaver/core-settings": "^0.1.0", "@cloudbeaver/core-theming": "^0.1.0", + "@cloudbeaver/core-ui": "^0.1.0", "@cloudbeaver/core-utils": "^0.1.0", "@cloudbeaver/core-view": "^0.1.0", "react": "^16.x.x", diff --git a/webapp/packages/core-bootstrap/src/manifest.ts b/webapp/packages/core-bootstrap/src/manifest.ts index 1da8a51691..f3d44ea94b 100644 --- a/webapp/packages/core-bootstrap/src/manifest.ts +++ b/webapp/packages/core-bootstrap/src/manifest.ts @@ -92,6 +92,7 @@ import { RouterService, ScreenService } from '@cloudbeaver/core-routing'; import { EnvironmentService, GraphQLService } from '@cloudbeaver/core-sdk'; import { LocalStorageSaveService, SettingsService } from '@cloudbeaver/core-settings'; import { ThemeService } from '@cloudbeaver/core-theming'; +import { OptionsPanelService } from '@cloudbeaver/core-ui'; import { ActiveViewService } from '@cloudbeaver/core-view'; export const coreManifest: PluginManifest = { @@ -154,6 +155,7 @@ export const coreManifest: PluginManifest = { EnvironmentService, ExceptionsCatcherService, EventsSettingsService, + OptionsPanelService, GraphQLService, LocalStorageSaveService, LocalizationService, diff --git a/webapp/packages/core-connections/src/Administration/ConnectionsResource.ts b/webapp/packages/core-connections/src/Administration/ConnectionsResource.ts index d98a34c517..f7a9596d8f 100644 --- a/webapp/packages/core-connections/src/Administration/ConnectionsResource.ts +++ b/webapp/packages/core-connections/src/Administration/ConnectionsResource.ts @@ -235,6 +235,9 @@ export class ConnectionsResource extends CachedMapResource { + await this.performUpdate(config.connectionId!, [], async () => { + const { connection } = await this.graphQLService.sdk.updateConnection({ + config, + ...this.getDefaultIncludes(), + ...this.getIncludesMap(config.connectionId!), + }); + + this.updateConnection(connection); + }); + return this.get(config.connectionId!)!; + } + async close(id: string): Promise { await this.performUpdate(id, [], async () => { const { connection } = await this.graphQLService.sdk.closeConnection({ diff --git a/webapp/packages/core-connections/src/index.ts b/webapp/packages/core-connections/src/index.ts index 564ba14b23..d728d62733 100644 --- a/webapp/packages/core-connections/src/index.ts +++ b/webapp/packages/core-connections/src/index.ts @@ -5,6 +5,7 @@ export * from './ConnectionForm/OriginInfo/ConnectionOriginInfoTabService'; export * from './ConnectionForm/ConnectionAccess/ConnectionAccessTabService'; export * from './ConnectionForm/ConnectionFormService'; export * from './ConnectionForm/useConnectionFormState'; +export * from './ConnectionForm/ConnectionForm'; export * from './Administration/Connections/ConnectionsAdministration'; export * from './Administration/Connections/ConnectionsAdministrationNavService'; export * from './Administration/Connections/ConnectionsAdministrationService'; diff --git a/webapp/packages/core-executor/src/Executor.ts b/webapp/packages/core-executor/src/Executor.ts index 0c8e618239..72feb4182a 100644 --- a/webapp/packages/core-executor/src/Executor.ts +++ b/webapp/packages/core-executor/src/Executor.ts @@ -14,7 +14,7 @@ import type { IExecutorHandler } from './IExecutorHandler'; import type { IExecutorHandlersCollection } from './IExecutorHandlersCollection'; import { BlockedExecution, TaskScheduler } from './TaskScheduler/TaskScheduler'; -export class Executor implements IExecutor { +export class Executor implements IExecutor { private collection: ExecutorHandlersCollection; private scheduler: TaskScheduler; diff --git a/webapp/packages/core-executor/src/IExecutor.ts b/webapp/packages/core-executor/src/IExecutor.ts index 236678959c..d5068df5b7 100644 --- a/webapp/packages/core-executor/src/IExecutor.ts +++ b/webapp/packages/core-executor/src/IExecutor.ts @@ -10,7 +10,7 @@ import type { IExecutionContext, IExecutionContextProvider } from './IExecutionC import type { IExecutorHandler } from './IExecutorHandler'; import type { IExecutorHandlersCollection } from './IExecutorHandlersCollection'; -export interface IExecutor { +export interface IExecutor { before: (executor: IExecutor, map?: (data: T) => TNext) => this; next: (executor: IExecutor, map?: (data: T) => TNext) => this; execute: ( diff --git a/webapp/packages/core-sdk/src/queries/connections/updateConnection.gql b/webapp/packages/core-sdk/src/queries/connections/updateConnection.gql new file mode 100644 index 0000000000..b20a8eaf9e --- /dev/null +++ b/webapp/packages/core-sdk/src/queries/connections/updateConnection.gql @@ -0,0 +1,12 @@ +mutation updateConnection( + $config: ConnectionConfig! + + $includeOrigin: Boolean!, + $customIncludeOriginDetails: Boolean!, + $includeAuthProperties: Boolean!, + $customIncludeNetworkHandlerCredentials: Boolean! +) { + connection: updateConnection(config: $config) { + ...DatabaseConnection + } +} diff --git a/webapp/packages/core-sdk/src/sdk.ts b/webapp/packages/core-sdk/src/sdk.ts index e8eef1572e..beedab8003 100644 --- a/webapp/packages/core-sdk/src/sdk.ts +++ b/webapp/packages/core-sdk/src/sdk.ts @@ -279,6 +279,7 @@ export interface Mutation { testConnection: ConnectionInfo; testNetworkHandler: NetworkEndpointInfo; touchSession?: Maybe; + updateConnection: ConnectionInfo; updateResultsData?: Maybe; updateResultsDataBatch?: Maybe; } @@ -389,6 +390,10 @@ export interface MutationTestNetworkHandlerArgs { config: NetworkHandlerConfigInput; } +export interface MutationUpdateConnectionArgs { + config: ConnectionConfig; +} + export interface MutationUpdateResultsDataArgs { connectionId: Scalars['ID']; contextId: Scalars['ID']; @@ -1217,6 +1222,16 @@ export type TestNetworkHandlerMutationVariables = Exact<{ export interface TestNetworkHandlerMutation { testNetworkHandler: Pick } +export type UpdateConnectionMutationVariables = Exact<{ + config: ConnectionConfig; + includeOrigin: Scalars['Boolean']; + customIncludeOriginDetails: Scalars['Boolean']; + includeAuthProperties: Scalars['Boolean']; + customIncludeNetworkHandlerCredentials: Scalars['Boolean']; +}>; + +export interface UpdateConnectionMutation { connection: DatabaseConnectionFragment } + export type ExportDataFromContainerQueryVariables = Exact<{ connectionId: Scalars['ID']; containerNodePath: Scalars['ID']; @@ -2045,6 +2060,13 @@ export const TestNetworkHandlerDocument = ` } } `; +export const UpdateConnectionDocument = ` + mutation updateConnection($config: ConnectionConfig!, $includeOrigin: Boolean!, $customIncludeOriginDetails: Boolean!, $includeAuthProperties: Boolean!, $customIncludeNetworkHandlerCredentials: Boolean!) { + connection: updateConnection(config: $config) { + ...DatabaseConnection + } +} + ${DatabaseConnectionFragmentDoc}`; export const ExportDataFromContainerDocument = ` query exportDataFromContainer($connectionId: ID!, $containerNodePath: ID!, $parameters: DataTransferParameters!) { taskInfo: dataTransferExportDataFromContainer( @@ -2603,6 +2625,9 @@ export function getSdk(client: GraphQLClient, withWrapper: SdkFunctionWrapper = testNetworkHandler(variables: TestNetworkHandlerMutationVariables): Promise { return withWrapper(() => client.request(TestNetworkHandlerDocument, variables)); }, + updateConnection(variables: UpdateConnectionMutationVariables): Promise { + return withWrapper(() => client.request(UpdateConnectionDocument, variables)); + }, exportDataFromContainer(variables: ExportDataFromContainerQueryVariables): Promise { return withWrapper(() => client.request(ExportDataFromContainerDocument, variables)); }, diff --git a/webapp/packages/core-ui/.gitignore b/webapp/packages/core-ui/.gitignore new file mode 100644 index 0000000000..99902db863 --- /dev/null +++ b/webapp/packages/core-ui/.gitignore @@ -0,0 +1,21 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# production +/lib + +# misc +.DS_Store +.env* + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/webapp/packages/core-ui/package.json b/webapp/packages/core-ui/package.json new file mode 100644 index 0000000000..f9cff19d30 --- /dev/null +++ b/webapp/packages/core-ui/package.json @@ -0,0 +1,22 @@ +{ + "name": "@cloudbeaver/core-ui", + "sideEffects": false, + "version": "0.1.0", + "description": "", + "license": "Apache-2.0", + "main": "lib/index.js", + "module": "lib/index.js", + "types": "src/index.ts", + "scripts": { + "prebuild": "rimraf lib/*", + "build": "webpack --config ../../configs/webpack.plugin.config.js", + "postbuild": "tsc", + "lint": "eslint ./src/ --ext .ts,.tsx", + "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix" + }, + "peerDependencies": { + "@cloudbeaver/core-di": "^0.1.0", + "@cloudbeaver/core-executor": "^0.1.0", + "mobx": "^5.x.x" + } +} \ No newline at end of file diff --git a/webapp/packages/core-ui/src/Screens/AppScreen/OptionsPanelService.ts b/webapp/packages/core-ui/src/Screens/AppScreen/OptionsPanelService.ts new file mode 100644 index 0000000000..1c44a29f45 --- /dev/null +++ b/webapp/packages/core-ui/src/Screens/AppScreen/OptionsPanelService.ts @@ -0,0 +1,64 @@ +/* + * 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 { makeObservable, observable } from 'mobx'; + +import { injectable } from '@cloudbeaver/core-di'; +import { Executor, ExecutorInterrupter, IExecutor } from '@cloudbeaver/core-executor'; + +@injectable() +export class OptionsPanelService { + active: boolean; + closeTask: IExecutor; + + panelComponent: (() => React.FC) | null; + + constructor() { + makeObservable(this, { + active: observable, + panelComponent: observable, + }); + this.active = false; + this.panelComponent = null; + this.closeTask = new Executor(); + } + + getPanelComponent(): React.FC { + if (this.panelComponent === null) { + return () => null; + } + return this.panelComponent(); + } + + async open(component: () => React.FC): Promise { + if (!(await this.close(component))) { + return; + } + + this.panelComponent = component; + this.active = true; + } + + async close(component: () => React.FC): Promise { + if (component !== this.panelComponent || this.panelComponent === null) { + return true; + } + + const contexts = await this.closeTask.execute(); + + const interrupted = contexts.getContext(ExecutorInterrupter.interruptContext); + + if (interrupted.interrupted) { + return false; + } + + this.panelComponent = null; + this.active = false; + return true; + } +} diff --git a/webapp/packages/core-ui/src/index.ts b/webapp/packages/core-ui/src/index.ts new file mode 100644 index 0000000000..9618c34418 --- /dev/null +++ b/webapp/packages/core-ui/src/index.ts @@ -0,0 +1 @@ +export * from './Screens/AppScreen/OptionsPanelService'; diff --git a/webapp/packages/core-ui/tsconfig.json b/webapp/packages/core-ui/tsconfig.json new file mode 100644 index 0000000000..8cd6b539e3 --- /dev/null +++ b/webapp/packages/core-ui/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["src"], + "exclude": ["**/node_modules", "**/.*/"], +} diff --git a/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/ConnectionForm/ConnectionForm.tsx b/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/ConnectionForm/ConnectionForm.tsx deleted file mode 100644 index a8ad10beb9..0000000000 --- a/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/ConnectionForm/ConnectionForm.tsx +++ /dev/null @@ -1,115 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2021 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ - -import { observer } from 'mobx-react-lite'; -import styled, { css } from 'reshadow'; - -import { useAdministrationSettings } from '@cloudbeaver/core-administration'; -import { - InputField, - useFocus, - ObjectPropertyInfoForm, - InputGroup, - FieldCheckbox, - SubmittingForm, - FormBox, - FormBoxElement, - FormGroup, -} from '@cloudbeaver/core-blocks'; -import type { DBDriver } from '@cloudbeaver/core-connections'; -import { useTranslate } from '@cloudbeaver/core-localization'; -import { useStyles } from '@cloudbeaver/core-theming'; - -import type { IFormController } from './IFormController'; -import { ParametersForm } from './ParametersForm'; - -interface ConnectionFormProps { - driver: DBDriver | null; - controller: IFormController; -} - -const connectionFormStyles = css` - SubmittingForm { - flex: 1; - } - FormBoxElement { - flex: 1; - } - FormBox { - padding: 24px 4px 24px; - } -`; - -export const ConnectionForm = observer(function ConnectionForm({ - driver, - controller, -}: ConnectionFormProps) { - const translate = useTranslate(); - const [focusedRef] = useFocus({ focusFirstChild: true }); - const { credentialsSavingEnabled } = useAdministrationSettings(); - - return styled(useStyles(connectionFormStyles))( - - - - - - {translate('customConnection_custom_name')} - - - {controller.isUrlConnection ? ( - - - {translate('customConnection_url_JDBC')} - - - ) : ( - - )} - {controller.authModel && ( - <> - - {translate('connections_connection_edit_authentication')} - - - {credentialsSavingEnabled && ( - - - - )} - - )} - - - - ); -}); diff --git a/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/ConnectionForm/IFormController.ts b/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/ConnectionForm/IFormController.ts deleted file mode 100644 index 656627252c..0000000000 --- a/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/ConnectionForm/IFormController.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2021 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ - -import { ConnectionConfig, DatabaseAuthModel } from '@cloudbeaver/core-sdk'; - -export interface IFormController { - authModel?: DatabaseAuthModel; - config: ConnectionConfig; - isUrlConnection: boolean; - isConnecting: boolean; - onChange: (value?: unknown, name?: string) => void; -} diff --git a/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/ConnectionForm/ParametersForm.tsx b/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/ConnectionForm/ParametersForm.tsx deleted file mode 100644 index b489aa3548..0000000000 --- a/webapp/packages/plugin-connection-custom/src/CustomConnection/ConnectionFormDialog/ConnectionForm/ParametersForm.tsx +++ /dev/null @@ -1,78 +0,0 @@ -/* - * CloudBeaver - Cloud Database Manager - * Copyright (C) 2020-2021 DBeaver Corp and others - * - * Licensed under the Apache License, Version 2.0. - * you may not use this file except in compliance with the License. - */ - -import { observer } from 'mobx-react-lite'; -import styled, { css, use } from 'reshadow'; - -import { InputField } from '@cloudbeaver/core-blocks'; -import { useTranslate } from '@cloudbeaver/core-localization'; -import { useStyles } from '@cloudbeaver/core-theming'; - -import type { IFormController } from './IFormController'; - -interface ParametersFormProps { - controller: IFormController; - embedded?: boolean; -} - -const parametersFormStyles = css` - sub-label { - composes: theme-typography--caption from global; - line-height: 14px; - }`; - -export const ParametersForm = observer(function ParametersForm({ - controller, - embedded, -}: ParametersFormProps) { - const translate = useTranslate(); - - return styled(useStyles(parametersFormStyles))( - <> - {!embedded && ( - - - - {translate('customConnection_custom_host')} - {translate('customConnection_custom_obligatory')} - - - - - {translate('customConnection_custom_port')} - - - - )} - - - {translate('customConnection_custom_database')} - - - - ); -}); diff --git a/webapp/packages/plugin-connection-template/src/manifest.ts b/webapp/packages/plugin-connection-template/src/manifest.ts index 462c58f5ff..16227f62e9 100644 --- a/webapp/packages/plugin-connection-template/src/manifest.ts +++ b/webapp/packages/plugin-connection-template/src/manifest.ts @@ -14,7 +14,7 @@ import { TemplateConnectionsResource } from './TemplateConnectionsResource'; export const connectionTemplate: PluginManifest = { info: { - name: 'Connection template plugin', + name: 'Connections plugin', }, providers: [ diff --git a/webapp/packages/plugin-connections/.gitignore b/webapp/packages/plugin-connections/.gitignore new file mode 100644 index 0000000000..15bc16c7c3 --- /dev/null +++ b/webapp/packages/plugin-connections/.gitignore @@ -0,0 +1,17 @@ +# dependencies +/node_modules + +# testing +/coverage + +# production +/lib + +# misc +.DS_Store +.env* + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* diff --git a/webapp/packages/plugin-connections/package.json b/webapp/packages/plugin-connections/package.json new file mode 100644 index 0000000000..4cf3f337d9 --- /dev/null +++ b/webapp/packages/plugin-connections/package.json @@ -0,0 +1,40 @@ +{ + "name": "@cloudbeaver/plugin-connections", + "sideEffects": false, + "version": "0.1.0", + "description": "", + "license": "Apache-2.0", + "main": "lib/index.js", + "module": "lib/index.js", + "types": "src/index.ts", + "scripts": { + "prebuild": "rimraf lib/*", + "build": "webpack --config ../../configs/webpack.plugin.config.js", + "postbuild": "tsc", + "lint": "eslint ./src/ --ext .ts,.tsx", + "lint-fix": "eslint ./src/ --ext .ts,.tsx --fix" + }, + "peerDependencies": { + "@cloudbeaver/core-app": "^0.1.0", + "@cloudbeaver/core-blocks": "^0.1.0", + "@cloudbeaver/core-connections": "^0.1.0", + "@cloudbeaver/core-di": "^0.1.0", + "@cloudbeaver/core-dialogs": "^0.1.0", + "@cloudbeaver/core-events": "^0.1.0", + "@cloudbeaver/core-extensions": "^0.1.0", + "@cloudbeaver/core-localization": "^0.1.0", + "@cloudbeaver/core-plugin": "^0.1.0", + "@cloudbeaver/core-product": "^0.1.0", + "@cloudbeaver/core-root": "^0.1.0", + "@cloudbeaver/core-sdk": "^0.1.0", + "@cloudbeaver/core-settings": "^0.1.0", + "@cloudbeaver/core-theming": "^0.1.0", + "@cloudbeaver/core-ui": "^0.1.0", + "@cloudbeaver/core-utils": "^0.1.0", + "react": "^16.x.x", + "rxjs": "^6.x.x", + "mobx": "^5.x.x", + "mobx-react-lite": "^3.x.x", + "reshadow": "^0.x.x" + } +} diff --git a/webapp/packages/plugin-connections/src/PublicConnectionForm/PublicConnectionForm.tsx b/webapp/packages/plugin-connections/src/PublicConnectionForm/PublicConnectionForm.tsx new file mode 100644 index 0000000000..21bed97f06 --- /dev/null +++ b/webapp/packages/plugin-connections/src/PublicConnectionForm/PublicConnectionForm.tsx @@ -0,0 +1,63 @@ +/* + * 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 { observable } from 'mobx'; +import { observer } from 'mobx-react-lite'; +import { useCallback, useState } from 'react'; +import styled from 'reshadow'; +import { css } from 'reshadow'; + +import { Loader, useMapResource } from '@cloudbeaver/core-blocks'; +import { ConnectionInfoResource, IConnectionFormData, IConnectionFormOptions, ConnectionForm } from '@cloudbeaver/core-connections'; +import { useService } from '@cloudbeaver/core-di'; +import { MetadataMap } from '@cloudbeaver/core-utils'; + +import { PublicConnectionFormService } from './PublicConnectionFormService'; + +const styles = css` + Loader { + height: 100%; + } +`; + +export const PublicConnectionForm: React.FC = observer(function PublicConnectionForm() { + const service = useService(PublicConnectionFormService); + const connection = useMapResource(ConnectionInfoResource, { + key: service.connectionId, + includes: ['includeOrigin', 'customIncludeNetworkHandlerCredentials', 'includeAuthProperties', 'customIncludeNetworkHandlerCredentials'], + }); + + const [data] = useState(() => ({ + config: observable({}), + get info() { + return connection.data; + }, + resource: connection.resource, + partsState: new MetadataMap(), + })); + + const [options] = useState(() => ({ + mode: 'edit', + type: 'public', + })); + + const close = useCallback(() => service.close(), []); + + return styled(styles)( + + {() => ( + + )} + + ); +}); diff --git a/webapp/packages/plugin-connections/src/PublicConnectionForm/PublicConnectionFormBootstrap.ts b/webapp/packages/plugin-connections/src/PublicConnectionForm/PublicConnectionFormBootstrap.ts new file mode 100644 index 0000000000..298a27444a --- /dev/null +++ b/webapp/packages/plugin-connections/src/PublicConnectionForm/PublicConnectionFormBootstrap.ts @@ -0,0 +1,48 @@ +/* + * 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 { + NavigationTreeContextMenuService, + EObjectFeature, + NodeManagerUtils, + NavNode +} from '@cloudbeaver/core-app'; +import { Bootstrap, injectable } from '@cloudbeaver/core-di'; +import { ContextMenuService } from '@cloudbeaver/core-dialogs'; + +import { PublicConnectionFormService } from './PublicConnectionFormService'; + +@injectable() +export class PublicConnectionFormBootstrap extends Bootstrap { + constructor( + private readonly contextMenuService: ContextMenuService, + private readonly publicConnectionFormService: PublicConnectionFormService + ) { + super(); + } + + register(): void | Promise { + this.contextMenuService.addMenuItem(this.contextMenuService.getRootMenuToken(), { + id: 'connection-edit', + isPresent(context) { + return context.contextType === NavigationTreeContextMenuService.nodeContextType + && context.data.objectFeatures.includes(EObjectFeature.dataSource); + }, + title: 'Edit', + order: 2, + onClick: context => { + const node = context.data; + const connectionId = NodeManagerUtils.connectionNodeIdToConnectionId(node.id); + this.publicConnectionFormService.open(connectionId); + }, + }); + } + + load(): void | Promise { + } +} diff --git a/webapp/packages/plugin-connections/src/PublicConnectionForm/PublicConnectionFormService.ts b/webapp/packages/plugin-connections/src/PublicConnectionForm/PublicConnectionFormService.ts new file mode 100644 index 0000000000..f9de931161 --- /dev/null +++ b/webapp/packages/plugin-connections/src/PublicConnectionForm/PublicConnectionFormService.ts @@ -0,0 +1,35 @@ +/* + * 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 { injectable } from '@cloudbeaver/core-di'; +import { OptionsPanelService } from '@cloudbeaver/core-ui'; + +import { PublicConnectionForm } from './PublicConnectionForm'; + +const formGetter = () => PublicConnectionForm; + +@injectable() +export class PublicConnectionFormService { + connectionId: string | null; + + constructor( + private readonly optionsPanelService: OptionsPanelService + ) { + this.connectionId = null; + } + + async open(connectionId: string): Promise { + this.connectionId = connectionId; + await this.optionsPanelService.open(formGetter); + } + + async close(): Promise { + this.connectionId = null; + await this.optionsPanelService.close(formGetter); + } +} diff --git a/webapp/packages/plugin-connections/src/index.ts b/webapp/packages/plugin-connections/src/index.ts new file mode 100644 index 0000000000..25b9fa81b7 --- /dev/null +++ b/webapp/packages/plugin-connections/src/index.ts @@ -0,0 +1,3 @@ +import { connectionPlugin } from './manifest'; + +export default connectionPlugin; diff --git a/webapp/packages/plugin-connections/src/manifest.ts b/webapp/packages/plugin-connections/src/manifest.ts new file mode 100644 index 0000000000..4ee6559631 --- /dev/null +++ b/webapp/packages/plugin-connections/src/manifest.ts @@ -0,0 +1,23 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import type { PluginManifest } from '@cloudbeaver/core-di'; + +import { PublicConnectionFormBootstrap } from './PublicConnectionForm/PublicConnectionFormBootstrap'; +import { PublicConnectionFormService } from './PublicConnectionForm/PublicConnectionFormService'; + +export const connectionPlugin: PluginManifest = { + info: { + name: 'Connection template plugin', + }, + + providers: [ + PublicConnectionFormBootstrap, + PublicConnectionFormService, + ], +}; diff --git a/webapp/packages/plugin-connections/tsconfig.json b/webapp/packages/plugin-connections/tsconfig.json new file mode 100644 index 0000000000..8cd6b539e3 --- /dev/null +++ b/webapp/packages/plugin-connections/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["src"], + "exclude": ["**/node_modules", "**/.*/"], +} diff --git a/webapp/packages/product-default/src/index.ts b/webapp/packages/product-default/src/index.ts index 3b12fc1be7..752470bb67 100644 --- a/webapp/packages/product-default/src/index.ts +++ b/webapp/packages/product-default/src/index.ts @@ -5,6 +5,7 @@ import administration from '@cloudbeaver/plugin-administration'; import authentication from '@cloudbeaver/plugin-authentication'; import connectionCustom from '@cloudbeaver/plugin-connection-custom'; import connectionTemplate from '@cloudbeaver/plugin-connection-template'; +import connectionPlugin from '@cloudbeaver/plugin-connections'; import dataExport from '@cloudbeaver/plugin-data-export'; import spreadsheetNew from '@cloudbeaver/plugin-data-spreadsheet-new'; import dataViewer from '@cloudbeaver/plugin-data-viewer'; @@ -27,6 +28,7 @@ const PLUGINS = [ objectViewer, sqlEditor, defaultProductManifest, + connectionPlugin, ]; bootstrap(PLUGINS);