diff --git a/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreen.tsx b/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreen.tsx index 8af5a8435c..b743eb1c95 100644 --- a/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreen.tsx +++ b/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreen.tsx @@ -11,13 +11,15 @@ import { useCallback } from 'react'; import { useService } from '@cloudbeaver/core-di'; import { usePermission } from '@cloudbeaver/core-root'; +import type { ScreenComponent } from '@cloudbeaver/core-routing'; import { Administration } from '../Administration/Administration'; import { EAdminPermission } from '../EAdminPermission'; import { AdministrationScreenService } from './AdministrationScreenService'; import { AdministrationTopAppBar } from './AdministrationTopAppBar/AdministrationTopAppBar'; +import type { IAdministrationScreenParams } from './IAdministrationScreenParams'; -export const AdministrationScreen = observer(function AdministrationScreen() { +export const AdministrationScreen: ScreenComponent = observer(function AdministrationScreen() { const administrationScreenService = useService(AdministrationScreenService); const handleSelect = useCallback( diff --git a/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts b/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts index f87abcb9b6..4a77e3a70e 100644 --- a/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts +++ b/webapp/packages/core-administration/src/AdministrationScreen/AdministrationScreenService.ts @@ -103,9 +103,9 @@ export class AdministrationScreenService { navigateToRoot(): void { if (this.isConfigurationMode) { - this.screenService.navigate(AdministrationScreenService.setupName); + this.screenService.navigateToScreen(AdministrationScreenService.setupName); } else { - this.screenService.navigate(AdministrationScreenService.screenName); + this.screenService.navigateToScreen(AdministrationScreenService.screenName); } } @@ -119,25 +119,25 @@ export class AdministrationScreenService { navigateToItem(item: string): void { if (this.isConfigurationMode) { - this.screenService.navigate(AdministrationScreenService.setupItemRouteName, item); + this.screenService.navigateToScreen(AdministrationScreenService.setupItemRouteName, item); } else { - this.screenService.navigate(AdministrationScreenService.itemRouteName, item); + this.screenService.navigateToScreen(AdministrationScreenService.itemRouteName, item); } } navigateToItemSub(item: string, sub: string, param?: string | null): void { if (!param) { if (this.isConfigurationMode) { - this.screenService.navigate(AdministrationScreenService.setupItemSubRouteName, item, sub); + this.screenService.navigateToScreen(AdministrationScreenService.setupItemSubRouteName, item, sub); } else { - this.screenService.navigate(AdministrationScreenService.itemSubRouteName, item, sub); + this.screenService.navigateToScreen(AdministrationScreenService.itemSubRouteName, item, sub); } return; } if (this.isConfigurationMode) { - this.screenService.navigate(AdministrationScreenService.setupItemSubParamRouteName, item, sub, param); + this.screenService.navigateToScreen(AdministrationScreenService.setupItemSubParamRouteName, item, sub, param); } else { - this.screenService.navigate(AdministrationScreenService.itemSubParamRouteName, item, sub, param); + this.screenService.navigateToScreen(AdministrationScreenService.itemSubParamRouteName, item, sub, param); } } diff --git a/webapp/packages/core-administration/src/AdministrationScreen/IAdministrationScreenParams.ts b/webapp/packages/core-administration/src/AdministrationScreen/IAdministrationScreenParams.ts new file mode 100644 index 0000000000..12a48a7a7d --- /dev/null +++ b/webapp/packages/core-administration/src/AdministrationScreen/IAdministrationScreenParams.ts @@ -0,0 +1,13 @@ +/* + * 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. + */ + +export interface IAdministrationScreenParams { + item?: string; + sub?: string; + param?: string; +} diff --git a/webapp/packages/core-app/src/Body.tsx b/webapp/packages/core-app/src/Body.tsx index 1ed430f838..40060855b4 100644 --- a/webapp/packages/core-app/src/Body.tsx +++ b/webapp/packages/core-app/src/Body.tsx @@ -51,7 +51,7 @@ export const Body = observer(function Body() { {() => styled(style)( <> - {Screen && } + {Screen && } )} diff --git a/webapp/packages/core-app/src/shared/NavigationTabs/NavigationTabsService.ts b/webapp/packages/core-app/src/shared/NavigationTabs/NavigationTabsService.ts index c4954c6b5e..decc68355d 100644 --- a/webapp/packages/core-app/src/shared/NavigationTabs/NavigationTabsService.ts +++ b/webapp/packages/core-app/src/shared/NavigationTabs/NavigationTabsService.ts @@ -14,7 +14,7 @@ import { injectable } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import { ISyncExecutor, SyncExecutor } from '@cloudbeaver/core-executor'; import { LocalStorageSaveService } from '@cloudbeaver/core-settings'; -import { IActiveView, View } from '@cloudbeaver/core-view'; +import { ACTION_OPEN_IN_TAB, IActiveView, View } from '@cloudbeaver/core-view'; import type { ITab } from './ITab'; import { TabHandler, TabHandlerOptions, TabHandlerEvent } from './TabHandler'; @@ -73,6 +73,8 @@ export class NavigationTabsService extends View> { this.onTabSelect = new SyncExecutor(); this.onTabClose = new SyncExecutor(); + this.registerAction(ACTION_OPEN_IN_TAB); + makeObservable(this, { handlers: observable, tabsMap: observable, diff --git a/webapp/packages/core-localization/src/locales/en.ts b/webapp/packages/core-localization/src/locales/en.ts index 205ca1bf02..ce796e3ef3 100644 --- a/webapp/packages/core-localization/src/locales/en.ts +++ b/webapp/packages/core-localization/src/locales/en.ts @@ -38,6 +38,7 @@ export const defaultENLocale = [ ['ui_revoke', 'Revoke'], ['ui_grant', 'Grant'], ['ui_edit', 'Edit'], + ['ui_open_in_tab', 'Open in tab'], ['ui_rename', 'Rename'], ['ui_rename_processing', 'Renaming...'], ['ui_name', 'Name'], diff --git a/webapp/packages/core-localization/src/locales/ru.ts b/webapp/packages/core-localization/src/locales/ru.ts index 4e41fbd2ae..6f7e84c8f6 100644 --- a/webapp/packages/core-localization/src/locales/ru.ts +++ b/webapp/packages/core-localization/src/locales/ru.ts @@ -36,6 +36,7 @@ export const defaultRULocale = [ ['ui_revoke', 'Запретить'], ['ui_grant', 'Разрешить'], ['ui_edit', 'Редактировать'], + ['ui_open_in_tab', 'Открыть во вкладке'], ['ui_rename', 'Переименовать'], ['ui_rename_processing', 'Переименование...'], ['ui_name', 'Название'], diff --git a/webapp/packages/core-routing/src/Screen/IScreen.ts b/webapp/packages/core-routing/src/Screen/IScreen.ts index 3dba5d407f..bb92ba127e 100644 --- a/webapp/packages/core-routing/src/Screen/IScreen.ts +++ b/webapp/packages/core-routing/src/Screen/IScreen.ts @@ -10,14 +10,14 @@ import type { Route, State as RouterState } from 'router5'; export type ScreenRoute = Omit; -export interface IScreen { +export interface IScreen = Record> { name: string; routes: ScreenRoute[]; - component: ScreenComponent; + component: ScreenComponent; root?: boolean; onActivate?: (state: RouterState, prevState?: RouterState) => void | Promise; onDeactivate?: (state: RouterState, nextState: RouterState) => void | Promise; canDeActivate?: (state: RouterState, nextState: RouterState) => boolean | Promise; } -export type ScreenComponent = React.FunctionComponent; +export type ScreenComponent> = React.FunctionComponent; diff --git a/webapp/packages/core-routing/src/Screen/ScreenService.ts b/webapp/packages/core-routing/src/Screen/ScreenService.ts index 1fcb03f7d2..63e5cb7197 100644 --- a/webapp/packages/core-routing/src/Screen/ScreenService.ts +++ b/webapp/packages/core-routing/src/Screen/ScreenService.ts @@ -17,13 +17,13 @@ import type { IScreen, ScreenRoute } from './IScreen'; @injectable() export class ScreenService { - get screen(): IScreen | undefined { + get screen(): IScreen | undefined { return this.getScreenByRoute(this.routerService.route); } readonly routeChange: IExecutor; - private screens = new Map(); + private screens = new Map>(); private routeScreenMap = new Map(); constructor( @@ -47,11 +47,15 @@ export class ScreenService { } } - navigate(screen: string, item?: string, sub?: string, param?: string): void { + navigateToScreen(screen: string, item?: string, sub?: string, param?: string): void { this.routerService.router.navigate(screen, { item, sub, param }); } - create(screen: IScreen): void { + navigate(screen: string, params?: Record) { + this.routerService.router.navigate(screen, params as any); + } + + create>(screen: IScreen): void { if (this.screens.has(screen.name)) { return; } @@ -78,11 +82,11 @@ export class ScreenService { return screen?.name === name; } - buildUrl(screen: string): string { - return this.routerService.router.buildUrl(screen); + buildUrl(screen: string, params?: Record): string { + return this.routerService.router.buildUrl(screen, params); } - getScreenByRoute(route: string): IScreen | undefined { + getScreenByRoute(route: string): IScreen | undefined { const screen = this.routeScreenMap.get(route); if (!screen) { return undefined; diff --git a/webapp/packages/core-view/src/Action/Actions/ACTION_OPEN_IN_TAB.ts b/webapp/packages/core-view/src/Action/Actions/ACTION_OPEN_IN_TAB.ts new file mode 100644 index 0000000000..b0d1c8cb74 --- /dev/null +++ b/webapp/packages/core-view/src/Action/Actions/ACTION_OPEN_IN_TAB.ts @@ -0,0 +1,13 @@ +/* + * 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 { createAction } from '../createAction'; + +export const ACTION_OPEN_IN_TAB = createAction('open-in-tab', { + label: 'ui_open_in_tab', +}); diff --git a/webapp/packages/core-view/src/Action/KeyBinding/Bindings/KEY_BINDING_OPEN_IN_TAB.ts b/webapp/packages/core-view/src/Action/KeyBinding/Bindings/KEY_BINDING_OPEN_IN_TAB.ts new file mode 100644 index 0000000000..a1e76363f2 --- /dev/null +++ b/webapp/packages/core-view/src/Action/KeyBinding/Bindings/KEY_BINDING_OPEN_IN_TAB.ts @@ -0,0 +1,15 @@ +/* + * 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 { createKeyBinding } from '../createKeyBinding'; + +export const KEY_BINDING_OPEN_IN_TAB = createKeyBinding({ + id: 'open-in-tab', + label: 'ui_rename', + keys: 'alt+t', +}); diff --git a/webapp/packages/core-view/src/Action/KeyBinding/KeyBindingService.ts b/webapp/packages/core-view/src/Action/KeyBinding/KeyBindingService.ts index 49d7972994..b125899808 100644 --- a/webapp/packages/core-view/src/Action/KeyBinding/KeyBindingService.ts +++ b/webapp/packages/core-view/src/Action/KeyBinding/KeyBindingService.ts @@ -20,6 +20,13 @@ export class KeyBindingService { this.handlers = new Map(); } + addKeyBindingHandler(handler: IKeyBindingHandler): void { + if (this.handlers.has(handler.id)) { + throw new Error(`Key binding handler with same id (${handler.id}) already exists`); + } + this.handlers.set(handler.id, handler); + } + getKeyBindingHandler(context: IDataContextProvider, action: IAction): IKeyBindingHandler | null { for (const handler of this.handlers.values()) { if (handler.isBindingApplicable(context, action)) { diff --git a/webapp/packages/core-view/src/DataContext/DataContext.ts b/webapp/packages/core-view/src/DataContext/DataContext.ts index 63aa70937b..a2feca0552 100644 --- a/webapp/packages/core-view/src/DataContext/DataContext.ts +++ b/webapp/packages/core-view/src/DataContext/DataContext.ts @@ -6,6 +6,8 @@ * you may not use this file except in compliance with the License. */ +import { makeObservable, observable } from 'mobx'; + import type { DataContextGetter } from './DataContextGetter'; import type { IDataContext } from './IDataContext'; import type { IDataContextProvider } from './IDataContextProvider'; @@ -17,6 +19,11 @@ export class DataContext implements IDataContext { constructor(fallback?: IDataContextProvider) { this.map = new Map(); this.fallback = fallback; + + makeObservable(this, { + map: observable.shallow, + fallback: observable.ref, + }); } setFallBack(fallback?: IDataContextProvider): void { @@ -50,6 +57,12 @@ export class DataContext implements IDataContext { return this; } + delete(context: DataContextGetter): this { + this.map.delete(context); + + return this; + } + get(context: DataContextGetter): T { if (!this.map.has(context)) { const defaultValue = context(); diff --git a/webapp/packages/core-view/src/DataContext/DynamicDataContext.ts b/webapp/packages/core-view/src/DataContext/DynamicDataContext.ts new file mode 100644 index 0000000000..5823e9e5df --- /dev/null +++ b/webapp/packages/core-view/src/DataContext/DynamicDataContext.ts @@ -0,0 +1,61 @@ +/* + * 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 type { DataContextGetter } from './DataContextGetter'; +import type { IDataContext } from './IDataContext'; + +export class DynamicDataContext implements IDataContext { + fallback: IDataContext; + contexts: Array>; + + constructor(fallback: IDataContext) { + this.fallback = fallback; + this.contexts = []; + + makeObservable(this, { + fallback: observable.ref, + }); + } + + setFallBack(fallback: IDataContext): void { + this.fallback = fallback; + } + + set(context: DataContextGetter, value: T): this { + this.contexts.push(context); + this.fallback.set(context, value); + return this; + } + + delete(context: DataContextGetter): this { + this.fallback.delete(context); + return this; + } + + has(context: DataContextGetter): boolean { + return this.fallback.has(context); + } + + get (context: DataContextGetter): T { + return this.fallback.get(context); + } + + find (context: DataContextGetter, value: T): boolean { + return this.fallback.find(context, value); + } + + tryGet (context: DataContextGetter): T | undefined { + return this.fallback.tryGet(context); + } + + flush(): void { + this.contexts = []; + } +} diff --git a/webapp/packages/core-view/src/DataContext/IDataContext.ts b/webapp/packages/core-view/src/DataContext/IDataContext.ts index 7e204728e4..52d21bc768 100644 --- a/webapp/packages/core-view/src/DataContext/IDataContext.ts +++ b/webapp/packages/core-view/src/DataContext/IDataContext.ts @@ -11,4 +11,5 @@ import type { IDataContextProvider } from './IDataContextProvider'; export interface IDataContext extends IDataContextProvider { set: (context: DataContextGetter, value: T) => this; + delete: (context: DataContextGetter) => this; } diff --git a/webapp/packages/core-view/src/DataContext/useDynamicDataContext.ts b/webapp/packages/core-view/src/DataContext/useDynamicDataContext.ts new file mode 100644 index 0000000000..572165669a --- /dev/null +++ b/webapp/packages/core-view/src/DataContext/useDynamicDataContext.ts @@ -0,0 +1,40 @@ +/* + * 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 { action } from 'mobx'; +import { useEffect } from 'react'; + +import { useObjectRef } from '@cloudbeaver/core-blocks'; + +import { DataContext } from './DataContext'; +import type { DataContextGetter } from './DataContextGetter'; +import { DynamicDataContext } from './DynamicDataContext'; +import type { IDataContext } from './IDataContext'; + +export function useDynamicDataContext(context: IDataContext | undefined): IDataContext { + const state = useObjectRef(() => ({ + dynamic: new DynamicDataContext(context || new DataContext()), + contexts: [] as Array>, + })); + + if (context) { + state.dynamic.setFallBack(context); + } + state.contexts = state.dynamic.contexts; + state.dynamic.flush(); + + useEffect(action(() => { + for (const context of state.contexts) { + if (!state.dynamic.contexts.includes(context)) { + state.dynamic.delete(context); + } + } + })); + + return state.dynamic; +} diff --git a/webapp/packages/core-view/src/View/CaptureView.tsx b/webapp/packages/core-view/src/View/CaptureView.tsx index c1036b03b5..0b213ca5e9 100644 --- a/webapp/packages/core-view/src/View/CaptureView.tsx +++ b/webapp/packages/core-view/src/View/CaptureView.tsx @@ -54,7 +54,7 @@ export const CaptureView = observer(function CaptureView({ const handlers = actionItems.reduce((map, item) => ({ ...map, - [item.binding!.id]: item.activate.bind(item.handler, true), + [item.binding!.id]: () => item.activate(true), }), {}); return styled(styles)( diff --git a/webapp/packages/core-view/src/View/CaptureViewContext.ts b/webapp/packages/core-view/src/View/CaptureViewContext.ts index b4d4fa43b2..74c21a2559 100644 --- a/webapp/packages/core-view/src/View/CaptureViewContext.ts +++ b/webapp/packages/core-view/src/View/CaptureViewContext.ts @@ -8,6 +8,6 @@ import { createContext } from 'react'; -import type { IDataContextProvider } from '../DataContext/IDataContextProvider'; +import type { IDataContext } from '../DataContext/IDataContext'; -export const CaptureViewContext = createContext(undefined); +export const CaptureViewContext = createContext(undefined); diff --git a/webapp/packages/core-view/src/View/useCaptureViewContext.ts b/webapp/packages/core-view/src/View/useCaptureViewContext.ts new file mode 100644 index 0000000000..27530feeab --- /dev/null +++ b/webapp/packages/core-view/src/View/useCaptureViewContext.ts @@ -0,0 +1,24 @@ +/* + * 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 { useContext } from 'react'; + +import type { IDataContext } from '../DataContext/IDataContext'; +import { useDynamicDataContext } from '../DataContext/useDynamicDataContext'; +import { CaptureViewContext } from './CaptureViewContext'; + +export function useCaptureViewContext(): IDataContext | undefined { + const context = useContext(CaptureViewContext); + const dynamic = useDynamicDataContext(context); + + if (context === undefined) { + return undefined; + } + + return dynamic; +} diff --git a/webapp/packages/core-view/src/index.ts b/webapp/packages/core-view/src/index.ts index 1071b54de9..701db80fc6 100644 --- a/webapp/packages/core-view/src/index.ts +++ b/webapp/packages/core-view/src/index.ts @@ -1,14 +1,20 @@ export * from './Action/Actions/ACTION_DELETE'; export * from './Action/Actions/ACTION_EXPORT'; +export * from './Action/Actions/ACTION_OPEN_IN_TAB'; export * from './Action/Actions/ACTION_OPEN'; export * from './Action/Actions/ACTION_REFRESH'; export * from './Action/Actions/ACTION_RENAME'; +export * from './Action/KeyBinding/Bindings/KEY_BINDING_OPEN_IN_TAB'; export * from './Action/KeyBinding/KeyBindingService'; export * from './Action/KeyBinding/createKeyBinding'; export * from './Action/ActionService'; export * from './Action/createAction'; -export * from './DataContext/DataContext'; export * from './DataContext/createDataContext'; +export * from './DataContext/DataContext'; +export * from './DataContext/DynamicDataContext'; +export * from './DataContext/IDataContext'; +export * from './DataContext/IDataContextProvider'; +export * from './DataContext/useDynamicDataContext'; export * from './Menu/MenuItem/IMenuActionItem'; export * from './Menu/MenuItem/IMenuBaseItem'; export * from './Menu/MenuItem/IMenuItem'; @@ -32,6 +38,7 @@ export * from './View/CaptureViewContext'; export * from './View/IActiveView'; export * from './View/IView'; export * from './View/useActiveView'; +export * from './View/useCaptureViewContext'; export * from './View/View'; export * from './View/ViewService'; export { manifest as coreViewManifest } from './manifest'; diff --git a/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorPanel.tsx b/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorPanel.tsx index 2a197cdc89..081e6e4280 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorPanel.tsx +++ b/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorPanel.tsx @@ -8,10 +8,12 @@ import type { TabHandlerPanelComponent } from '@cloudbeaver/core-app'; import { useTab } from '@cloudbeaver/core-blocks'; -import { ISqlEditorTabState, SqlEditor } from '@cloudbeaver/plugin-sql-editor'; +import { useCaptureViewContext } from '@cloudbeaver/core-view'; +import { DATA_CONTEXT_SQL_EDITOR_STATE, ISqlEditorTabState, SqlEditor } from '@cloudbeaver/plugin-sql-editor'; export const SqlEditorPanel: TabHandlerPanelComponent = function SqlEditorPanel({ tab }) { const baseTab = useTab(tab.id); + const viewContext = useCaptureViewContext(); // const navigatorService = useService(SqlEditorNavigatorService); // const handleOpen = ({ tabId }: ITabData) => navigatorService.openEditorResult(editorId, tabId); @@ -21,5 +23,7 @@ export const SqlEditorPanel: TabHandlerPanelComponent = func return null; } - return ; + viewContext?.set(DATA_CONTEXT_SQL_EDITOR_STATE, tab.handlerState); + + return ; }; diff --git a/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts b/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts index f73808a863..d9f720ebce 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab/src/SqlEditorTabService.ts @@ -88,16 +88,7 @@ export class SqlEditorTabService extends Bootstrap { return { handlerId: sqlEditorTabHandlerKey, - handlerState: { - query: '', - order, - executionContext: { ...executionContext.context }, - tabs: [], - resultGroups: [], - resultTabs: [], - executionPlanTabs: [], - statisticsTabs: [], - }, + handlerState: this.sqlEditorService.getState(order, executionContext.context), }; } diff --git a/webapp/packages/plugin-sql-editor-screen/.gitignore b/webapp/packages/plugin-sql-editor-screen/.gitignore new file mode 100644 index 0000000000..15bc16c7c3 --- /dev/null +++ b/webapp/packages/plugin-sql-editor-screen/.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-sql-editor-screen/package.json b/webapp/packages/plugin-sql-editor-screen/package.json new file mode 100644 index 0000000000..564400755d --- /dev/null +++ b/webapp/packages/plugin-sql-editor-screen/package.json @@ -0,0 +1,48 @@ +{ + "name": "@cloudbeaver/plugin-sql-editor-screen", + "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/plugin-sql-editor": "^0.1.0", + "@cloudbeaver/plugin-codemirror": "^0.1.0", + "@cloudbeaver/plugin-data-viewer": "^0.1.0", + "@cloudbeaver/core-administration": "^0.1.0", + "@cloudbeaver/core-app": "^0.1.0", + "@cloudbeaver/core-authentication": "^0.1.0", + "@cloudbeaver/core-blocks": "^0.1.0", + "@cloudbeaver/core-connections": "^0.1.0", + "@cloudbeaver/core-di": "^0.1.0", + "@cloudbeaver/core-dialogs": "^0.1.0", + "@cloudbeaver/core-events": "^0.1.0", + "@cloudbeaver/core-executor": "^0.1.0", + "@cloudbeaver/core-extensions": "^0.1.0", + "@cloudbeaver/core-localization": "^0.1.0", + "@cloudbeaver/core-notifications": "^0.1.0", + "@cloudbeaver/core-plugin": "^0.1.0", + "@cloudbeaver/core-product": "^0.1.0", + "@cloudbeaver/core-root": "^0.1.0", + "@cloudbeaver/core-routing": "^0.1.0", + "@cloudbeaver/core-sdk": "^0.1.0", + "@cloudbeaver/core-settings": "^0.1.0", + "@cloudbeaver/core-theming": "^0.1.0", + "@cloudbeaver/core-ui": "^0.1.0", + "@cloudbeaver/core-utils": "^0.1.0", + "@cloudbeaver/core-view": "^0.1.0", + "react": "^16.x.x", + "mobx": "^6.x.x", + "mobx-react-lite": "^3.x.x", + "reshadow": "^0.x.x" + } +} diff --git a/webapp/packages/plugin-sql-editor-screen/src/PluginBootstrap.ts b/webapp/packages/plugin-sql-editor-screen/src/PluginBootstrap.ts new file mode 100644 index 0000000000..38e8fcc553 --- /dev/null +++ b/webapp/packages/plugin-sql-editor-screen/src/PluginBootstrap.ts @@ -0,0 +1,61 @@ +/* + * 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, Bootstrap } from '@cloudbeaver/core-di'; +import { NotificationService } from '@cloudbeaver/core-events'; +import { ActionService, ACTION_OPEN_IN_TAB, KeyBindingService, KEY_BINDING_OPEN_IN_TAB } from '@cloudbeaver/core-view'; +import { DATA_CONTEXT_SQL_EDITOR_STATE } from '@cloudbeaver/plugin-sql-editor'; + +import { SqlEditorScreenService } from './Screen/SqlEditorScreenService'; + +@injectable() +export class PluginBootstrap extends Bootstrap { + constructor( + private readonly actionService: ActionService, + private readonly keyBindingService: KeyBindingService, + private readonly sqlEditorScreenService: SqlEditorScreenService, + private readonly notificationService: NotificationService + ) { + super(); + } + + register(): void | Promise { + this.actionService.addHandler({ + id: 'sql-editor-screen', + isActionApplicable: (contexts, action) => ( + action === ACTION_OPEN_IN_TAB + && contexts.has(DATA_CONTEXT_SQL_EDITOR_STATE) + ), + handler: (context, action) => { }, + }); + this.keyBindingService.addKeyBindingHandler({ + id: 'sql-editor', + binding: KEY_BINDING_OPEN_IN_TAB, + isBindingApplicable: (contexts, action) => action === ACTION_OPEN_IN_TAB, + handler: (contexts, action) => { + const context = contexts.get(DATA_CONTEXT_SQL_EDITOR_STATE); + + if (!context.executionContext) { + this.notificationService.logError({ + title: 'sd', + }); + return; + } + + const url = this.sqlEditorScreenService.createURL({ + connectionId: context.executionContext.connectionId, + contextId: context.executionContext.baseId, + }); + + window.open(url, '_blank')?.focus(); + }, + }); + } + + async load(): Promise { } +} diff --git a/webapp/packages/plugin-sql-editor-screen/src/Screen/ISqlEditorScreenParams.ts b/webapp/packages/plugin-sql-editor-screen/src/Screen/ISqlEditorScreenParams.ts new file mode 100644 index 0000000000..dc09c1ca1d --- /dev/null +++ b/webapp/packages/plugin-sql-editor-screen/src/Screen/ISqlEditorScreenParams.ts @@ -0,0 +1,12 @@ +/* + * 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. + */ + +export interface ISqlEditorScreenParams { + readonly connectionId: string; + readonly contextId: string; +} diff --git a/webapp/packages/plugin-sql-editor-screen/src/Screen/SqlEditorScreen.tsx b/webapp/packages/plugin-sql-editor-screen/src/Screen/SqlEditorScreen.tsx new file mode 100644 index 0000000000..9ff93a1dfd --- /dev/null +++ b/webapp/packages/plugin-sql-editor-screen/src/Screen/SqlEditorScreen.tsx @@ -0,0 +1,57 @@ +/* + * 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 { Loader, TextPlaceholder, useMapResource, useObservableRef } from '@cloudbeaver/core-blocks'; +import { ConnectionExecutionContextResource, ConnectionExecutionContextService, ConnectionInfoResource, IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections'; +import { useService } from '@cloudbeaver/core-di'; +import type { ScreenComponent } from '@cloudbeaver/core-routing'; +import { CachedMapAllKey } from '@cloudbeaver/core-sdk'; +import { ISqlEditorTabState, SqlEditor, SqlEditorService } from '@cloudbeaver/plugin-sql-editor'; + +import type { ISqlEditorScreenParams } from './ISqlEditorScreenParams'; + +export const SqlEditorScreen: ScreenComponent = observer(function SqlEditorScreen({ + connectionId, + contextId, +}) { + const connectionInfoResource = useMapResource( + SqlEditorScreen, + ConnectionInfoResource, + connectionId + ); + const connectionExecutionContextResource = useMapResource( + SqlEditorScreen, + ConnectionExecutionContextResource, + CachedMapAllKey + ); + const sqlEditorService = useService(SqlEditorService); + const connectionExecutionContextService = useService(ConnectionExecutionContextService); + const context = connectionExecutionContextService.get(contextId); + + const state = useObservableRef(() => ({ + state: null as null | ISqlEditorTabState, + setState(contextInfo: IConnectionExecutionContextInfo) { + this.state = sqlEditorService.getState(0, contextInfo); + }, + }), { state: observable }, false); + + if (context?.context && context.context.baseId !== state.state?.executionContext?.baseId) { + state.setState(context.context); + } + + return ( + + {state.state + ? + : Context not found} + + ); +}); diff --git a/webapp/packages/plugin-sql-editor-screen/src/Screen/SqlEditorScreenBootstrap.ts b/webapp/packages/plugin-sql-editor-screen/src/Screen/SqlEditorScreenBootstrap.ts new file mode 100644 index 0000000000..c8a3f7aa9e --- /dev/null +++ b/webapp/packages/plugin-sql-editor-screen/src/Screen/SqlEditorScreenBootstrap.ts @@ -0,0 +1,28 @@ +/* + * 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, Bootstrap } from '@cloudbeaver/core-di'; +import { ScreenService } from '@cloudbeaver/core-routing'; + +import { SqlEditorScreenService } from './SqlEditorScreenService'; + +@injectable() +export class SqlEditorScreenBootstrap extends Bootstrap { + constructor( + private readonly screenService: ScreenService, + private sqlEditorScreenService: SqlEditorScreenService + ) { + super(); + } + + register(): void { + this.screenService.create(this.sqlEditorScreenService.screen); + } + + load(): void | Promise { } +} diff --git a/webapp/packages/plugin-sql-editor-screen/src/Screen/SqlEditorScreenService.ts b/webapp/packages/plugin-sql-editor-screen/src/Screen/SqlEditorScreenService.ts new file mode 100644 index 0000000000..2419ba1ae6 --- /dev/null +++ b/webapp/packages/plugin-sql-editor-screen/src/Screen/SqlEditorScreenService.ts @@ -0,0 +1,37 @@ +/* + * 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 { IScreen, ScreenService } from '@cloudbeaver/core-routing'; + +import type { ISqlEditorScreenParams } from './ISqlEditorScreenParams'; +import { SqlEditorScreen } from './SqlEditorScreen'; + +@injectable() +export class SqlEditorScreenService { + readonly screen: IScreen; + constructor( + private readonly screenService: ScreenService + ) { + this.screen = { + name: 'sql-editor', + routes: [ + { name: 'sql-editor', path: '/sql-editor/:connectionId/:contextId' }, + ], + component: SqlEditorScreen, + }; + } + + createURL(params: ISqlEditorScreenParams): string { + return this.screenService.buildUrl(this.screen.name, params); + } + + navigate(params: ISqlEditorScreenParams): void { + this.screenService.navigate(this.screen.name, params); + } +} diff --git a/webapp/packages/plugin-sql-editor-screen/src/index.ts b/webapp/packages/plugin-sql-editor-screen/src/index.ts new file mode 100644 index 0000000000..ee6c8b0bda --- /dev/null +++ b/webapp/packages/plugin-sql-editor-screen/src/index.ts @@ -0,0 +1,3 @@ +import { sqlEditorPagePluginManifest } from './manifest'; + +export default sqlEditorPagePluginManifest; diff --git a/webapp/packages/plugin-sql-editor-screen/src/manifest.ts b/webapp/packages/plugin-sql-editor-screen/src/manifest.ts new file mode 100644 index 0000000000..12f12696a3 --- /dev/null +++ b/webapp/packages/plugin-sql-editor-screen/src/manifest.ts @@ -0,0 +1,25 @@ +/* + * CloudBeaver - Cloud Database Manager + * Copyright (C) 2020-2021 DBeaver Corp and others + * + * Licensed under the Apache License, Version 2.0. + * you may not use this file except in compliance with the License. + */ + +import type { PluginManifest } from '@cloudbeaver/core-di'; + +import { PluginBootstrap } from './PluginBootstrap'; +import { SqlEditorScreenBootstrap } from './Screen/SqlEditorScreenBootstrap'; +import { SqlEditorScreenService } from './Screen/SqlEditorScreenService'; + +export const sqlEditorPagePluginManifest: PluginManifest = { + info: { + name: 'Sql Editor Page plugin', + }, + + providers: [ + PluginBootstrap, + SqlEditorScreenBootstrap, + SqlEditorScreenService, + ], +}; diff --git a/webapp/packages/plugin-sql-editor-screen/tsconfig.json b/webapp/packages/plugin-sql-editor-screen/tsconfig.json new file mode 100644 index 0000000000..8cd6b539e3 --- /dev/null +++ b/webapp/packages/plugin-sql-editor-screen/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "lib" + }, + "include": ["src"], + "exclude": ["**/node_modules", "**/.*/"], +} diff --git a/webapp/packages/plugin-sql-editor/src/DATA_CONTEXT_SQL_EDITOR_STATE.ts b/webapp/packages/plugin-sql-editor/src/DATA_CONTEXT_SQL_EDITOR_STATE.ts new file mode 100644 index 0000000000..d873f021ec --- /dev/null +++ b/webapp/packages/plugin-sql-editor/src/DATA_CONTEXT_SQL_EDITOR_STATE.ts @@ -0,0 +1,13 @@ +/* + * 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 { createDataContext } from '@cloudbeaver/core-view'; + +import type { ISqlEditorTabState } from './ISqlEditorTabState'; + +export const DATA_CONTEXT_SQL_EDITOR_STATE = createDataContext('sql-editor-state'); diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditor.tsx b/webapp/packages/plugin-sql-editor/src/SqlEditor.tsx index 71f8c95983..e1f330c02c 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditor.tsx +++ b/webapp/packages/plugin-sql-editor/src/SqlEditor.tsx @@ -29,11 +29,10 @@ const viewerStyles = css` `; interface Props { - editorId: string; state: ISqlEditorTabState; } -export const SqlEditor = observer(function SqlEditor({ editorId, state }) { +export const SqlEditor = observer(function SqlEditor({ state }) { const styles = useStyles(splitStyles, splitHorizontalStyles, viewerStyles); return styled(styles)( @@ -43,7 +42,7 @@ export const SqlEditor = observer(function SqlEditor({ editorId, state }) - + ); diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditorService.ts b/webapp/packages/plugin-sql-editor/src/SqlEditorService.ts index 5290d65e64..2acdef4f0c 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditorService.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlEditorService.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import { ConnectionExecutionContextService, ConnectionsManagerService, IConnectionExecutionContext } from '@cloudbeaver/core-connections'; +import { ConnectionExecutionContextService, ConnectionsManagerService, IConnectionExecutionContext, IConnectionExecutionContextInfo } from '@cloudbeaver/core-connections'; import { injectable } from '@cloudbeaver/core-di'; import { NotificationService } from '@cloudbeaver/core-events'; import { GraphQLService, QuerySqlCompletionProposalsQuery } from '@cloudbeaver/core-sdk'; @@ -22,6 +22,20 @@ export class SqlEditorService { ) { } + getState(order: number, contextInfo: IConnectionExecutionContextInfo): ISqlEditorTabState { + return { + + query: '', + order, + executionContext: { ...contextInfo }, + tabs: [], + resultGroups: [], + resultTabs: [], + executionPlanTabs: [], + statisticsTabs: [], + }; + } + async getAutocomplete( connectionId: string, contextId: string, diff --git a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultTabs.tsx b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultTabs.tsx index 5c2b7ea4e5..b91346e252 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultTabs.tsx +++ b/webapp/packages/plugin-sql-editor/src/SqlResultTabs/SqlResultTabs.tsx @@ -51,13 +51,12 @@ const styles = composes( ); interface Props { - editorId: string; state: ISqlEditorTabState; onTabSelect?: (tabId: string) => void; onTabClose?: (tabId: string) => void; } -export const SqlResultTabs = observer(function SqlDataResult({ editorId, state, onTabSelect, onTabClose }) { +export const SqlResultTabs = observer(function SqlDataResult({ state, onTabSelect, onTabClose }) { const style = useStyles(styles); const translate = useTranslate(); const sqlResultTabsService = useService(SqlResultTabsService); diff --git a/webapp/packages/plugin-sql-editor/src/index.ts b/webapp/packages/plugin-sql-editor/src/index.ts index f2eaff5fc7..5ee434eae7 100644 --- a/webapp/packages/plugin-sql-editor/src/index.ts +++ b/webapp/packages/plugin-sql-editor/src/index.ts @@ -1,6 +1,7 @@ import { sqlEditorPluginManifest } from './manifest'; export * from './SqlEditor/SQLCodeEditor/SQLCodeEditorLoader'; export * from './SqlResultTabs/SqlResultTabsService'; +export * from './DATA_CONTEXT_SQL_EDITOR_STATE'; export * from './QueryDataSource'; export * from './SqlDialectInfoService'; export * from './ISqlEditorTabState'; diff --git a/webapp/packages/product-default/package.json b/webapp/packages/product-default/package.json index 2f9a643db2..8c8bacad05 100644 --- a/webapp/packages/product-default/package.json +++ b/webapp/packages/product-default/package.json @@ -31,6 +31,8 @@ "@cloudbeaver/plugin-data-viewer": "0.1.0", "@cloudbeaver/plugin-ddl-viewer": "0.1.0", "@cloudbeaver/plugin-object-viewer": "0.1.0", - "@cloudbeaver/plugin-sql-editor-navigation-tab": "0.1.0" + "@cloudbeaver/plugin-sql-editor": "0.1.0", + "@cloudbeaver/plugin-sql-editor-navigation-tab": "0.1.0", + "@cloudbeaver/plugin-sql-editor-screen": "0.1.0" } } diff --git a/webapp/packages/product-default/src/index.ts b/webapp/packages/product-default/src/index.ts index abb094ae1b..667dd60a33 100644 --- a/webapp/packages/product-default/src/index.ts +++ b/webapp/packages/product-default/src/index.ts @@ -18,6 +18,7 @@ import objectViewer from '@cloudbeaver/plugin-object-viewer'; import productPlugin from '@cloudbeaver/plugin-product'; import sqlEditor from '@cloudbeaver/plugin-sql-editor'; import sqlEditorTab from '@cloudbeaver/plugin-sql-editor-navigation-tab'; +import sqlEditorScreen from '@cloudbeaver/plugin-sql-editor-screen'; import userProfile from '@cloudbeaver/plugin-user-profile'; import { defaultProductManifest } from './manifest'; @@ -36,6 +37,7 @@ const PLUGINS: PluginManifest[] = [ objectViewer, sqlEditor, sqlEditorTab, + sqlEditorScreen, userProfile, defaultProductManifest, connectionPlugin,