From 19df7862114bcd51610319e8dd5f4494904d9f0e Mon Sep 17 00:00:00 2001 From: Wroud Date: Tue, 23 Aug 2022 22:26:59 +0300 Subject: [PATCH 1/9] fix(plugin-blocks): CB-2030 pass persistent prop to snackbar --- .../packages/core-blocks/src/Snackbars/ActionSnackbar.tsx | 2 +- .../packages/core-blocks/src/Snackbars/ProcessSnackbar.tsx | 2 +- webapp/packages/core-blocks/src/Snackbars/Snackbar.tsx | 4 +++- .../src/Snackbars/SnackbarMarkups/SnackbarWrapper.tsx | 6 +++--- .../src/NotificationsItem/NotificationsItem.tsx | 1 + .../src/ExportNotification/ExportNotification.tsx | 2 +- .../packages/plugin-sql-editor/src/SqlEditor/useTools.tsx | 1 - 7 files changed, 10 insertions(+), 8 deletions(-) diff --git a/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx b/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx index f1be92dfaa..0117356d7c 100644 --- a/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx +++ b/webapp/packages/core-blocks/src/Snackbars/ActionSnackbar.tsx @@ -29,7 +29,7 @@ export const ActionSnackbar: NotificationComponent = observ const translate = useTranslate(); return ( - notification.close(false)}> + notification.close(false)}> diff --git a/webapp/packages/core-blocks/src/Snackbars/ProcessSnackbar.tsx b/webapp/packages/core-blocks/src/Snackbars/ProcessSnackbar.tsx index 4ae244dd98..8e4419374b 100644 --- a/webapp/packages/core-blocks/src/Snackbars/ProcessSnackbar.tsx +++ b/webapp/packages/core-blocks/src/Snackbars/ProcessSnackbar.tsx @@ -45,7 +45,7 @@ export const ProcessSnackbar: NotificationComponent = observer(function P return ( notification.close(false)} > diff --git a/webapp/packages/core-blocks/src/Snackbars/Snackbar.tsx b/webapp/packages/core-blocks/src/Snackbars/Snackbar.tsx index 07a42af1a1..a58d4833dc 100644 --- a/webapp/packages/core-blocks/src/Snackbars/Snackbar.tsx +++ b/webapp/packages/core-blocks/src/Snackbars/Snackbar.tsx @@ -23,6 +23,7 @@ interface SnackbarProps { type: ENotificationType; message?: string; title: string; + persistent?: boolean; closeDelay: number; disableShowDetails?: boolean; time: number; @@ -35,6 +36,7 @@ export const Snackbar = observer(function Snackbar({ type, message, title, + persistent, closeDelay = 0, disableShowDetails, onClose, @@ -46,7 +48,7 @@ export const Snackbar = observer(function Snackbar({ useStateDelay(closeDelay > 0, closeDelay, onClose); return ( - onClose(false)}> + onClose(false)}> diff --git a/webapp/packages/core-blocks/src/Snackbars/SnackbarMarkups/SnackbarWrapper.tsx b/webapp/packages/core-blocks/src/Snackbars/SnackbarMarkups/SnackbarWrapper.tsx index 3db37d09d9..b141023bb3 100644 --- a/webapp/packages/core-blocks/src/Snackbars/SnackbarMarkups/SnackbarWrapper.tsx +++ b/webapp/packages/core-blocks/src/Snackbars/SnackbarMarkups/SnackbarWrapper.tsx @@ -51,13 +51,13 @@ const SNACKBAR_WRAPPER_STYLES = css` interface Props { closing?: boolean; - unclosable?: boolean; + persistent?: boolean; onClose?: () => void; className?: string; } export const SnackbarWrapper: React.FC> = function SnackbarWrapper({ - closing = false, unclosable, onClose, children, className, + closing = false, persistent, onClose, children, className, }) { const styles = useStyles(SNACKBAR_WRAPPER_STYLES); const [mounted, setMounted] = useState(false); @@ -69,7 +69,7 @@ export const SnackbarWrapper: React.FC> = functio return styled(styles)( {children} - {!unclosable && onClose && ( + {!persistent && onClose && ( )} diff --git a/webapp/packages/core-notifications/src/NotificationsItem/NotificationsItem.tsx b/webapp/packages/core-notifications/src/NotificationsItem/NotificationsItem.tsx index 153f2c494e..c8c773bb80 100644 --- a/webapp/packages/core-notifications/src/NotificationsItem/NotificationsItem.tsx +++ b/webapp/packages/core-notifications/src/NotificationsItem/NotificationsItem.tsx @@ -30,6 +30,7 @@ export const NotificationsItem = observer(function Notification({ notific (function ExportNotification({ const { title, status, message } = controller.status; return styled(useStyles(styles))( - + diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditor/useTools.tsx b/webapp/packages/plugin-sql-editor/src/SqlEditor/useTools.tsx index 87d2d2fdc0..9640f2b9a4 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditor/useTools.tsx +++ b/webapp/packages/plugin-sql-editor/src/SqlEditor/useTools.tsx @@ -82,7 +82,6 @@ export function useTools(state: ISqlEditorTabState): Readonly { this.notificationService.logInfo({ title: 'sql_editor_upload_script_max_size_title', message: `Max size: ${maxSize}KB\nFile size: ${size}KB`, - persistent: true, }); return false; From 862665a419da1155c764757c746b02e8f743f899 Mon Sep 17 00:00:00 2001 From: Wroud Date: Tue, 23 Aug 2022 22:28:09 +0300 Subject: [PATCH 2/9] fix(plugin-codemirror): CB-2030 read-only state --- webapp/packages/plugin-codemirror/src/CodeEditor.tsx | 4 +++- webapp/packages/plugin-codemirror/src/ICodeEditorProps.ts | 1 - .../src/SqlEditor/SQLCodeEditor/SQLCodeEditor.tsx | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/webapp/packages/plugin-codemirror/src/CodeEditor.tsx b/webapp/packages/plugin-codemirror/src/CodeEditor.tsx index ed5e32b194..4312c1d4ec 100644 --- a/webapp/packages/plugin-codemirror/src/CodeEditor.tsx +++ b/webapp/packages/plugin-codemirror/src/CodeEditor.tsx @@ -28,7 +28,7 @@ import { SqlEditorStyles } from './theme'; import { useAutoFormat } from './useAutoFormat'; export const CodeEditor = observer(function CodeEditor(props) { - const { readonly, autoFormat, className, editorDidMount } = props; + const { autoFormat, className, editorDidMount } = props; const formatter = useAutoFormat(props.options?.mode); const wrapperRef = useRef(null); @@ -63,6 +63,8 @@ export const CodeEditor = observer(function CodeEditor(props) } }, [editorDidMount]); + const readonly = props.options?.readOnly ?? false; + return styled(useStyles(SqlEditorStyles))( From 30380c7b24ac98e97dc96181f82d888ff827cd8c Mon Sep 17 00:00:00 2001 From: Wroud Date: Tue, 23 Aug 2022 22:35:58 +0300 Subject: [PATCH 3/9] feat(plugin-root): CB-2030 offline mode notification --- .../core-root/src/NetworkStateService.ts | 10 ++-- .../packages/plugin-root/src/LocaleService.ts | 36 ++++++++++++++ .../NetworkStateNotificationService.ts | 48 +++++++++++++++++++ webapp/packages/plugin-root/src/locales/en.ts | 4 ++ webapp/packages/plugin-root/src/locales/it.ts | 4 ++ webapp/packages/plugin-root/src/locales/ru.ts | 4 ++ webapp/packages/plugin-root/src/locales/zh.ts | 4 ++ webapp/packages/plugin-root/src/manifest.ts | 4 ++ 8 files changed, 110 insertions(+), 4 deletions(-) create mode 100644 webapp/packages/plugin-root/src/LocaleService.ts create mode 100644 webapp/packages/plugin-root/src/NetworkStateNotification/NetworkStateNotificationService.ts create mode 100644 webapp/packages/plugin-root/src/locales/en.ts create mode 100644 webapp/packages/plugin-root/src/locales/it.ts create mode 100644 webapp/packages/plugin-root/src/locales/ru.ts create mode 100644 webapp/packages/plugin-root/src/locales/zh.ts diff --git a/webapp/packages/core-root/src/NetworkStateService.ts b/webapp/packages/core-root/src/NetworkStateService.ts index 49b22dca2b..b1d18e65d3 100644 --- a/webapp/packages/core-root/src/NetworkStateService.ts +++ b/webapp/packages/core-root/src/NetworkStateService.ts @@ -24,15 +24,17 @@ export class NetworkStateService extends Bootstrap { private networkState: boolean; constructor( - private graphQLService: GraphQLService + private readonly graphQLService: GraphQLService ) { super(); - makeObservable(this, { - networkState: observable, - }); this.networkState = true; this.networkStateExecutor = new Executor(); + this.networkStateExecutor.setInitialDataGetter(() => this.state); + + makeObservable(this, { + networkState: observable, + }); } register(): void { diff --git a/webapp/packages/plugin-root/src/LocaleService.ts b/webapp/packages/plugin-root/src/LocaleService.ts new file mode 100644 index 0000000000..90c761184a --- /dev/null +++ b/webapp/packages/plugin-root/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; + } + } +} \ No newline at end of file diff --git a/webapp/packages/plugin-root/src/NetworkStateNotification/NetworkStateNotificationService.ts b/webapp/packages/plugin-root/src/NetworkStateNotification/NetworkStateNotificationService.ts new file mode 100644 index 0000000000..5d7a9824d4 --- /dev/null +++ b/webapp/packages/plugin-root/src/NetworkStateNotification/NetworkStateNotificationService.ts @@ -0,0 +1,48 @@ +/* + * 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 { INotification, NotificationService } from '@cloudbeaver/core-events'; +import { NetworkStateService } from '@cloudbeaver/core-root'; + + +@injectable() +export class NetworkStateNotificationService extends Bootstrap { + private activeNotification: INotification | null; + + constructor( + private readonly notificationService: NotificationService, + private readonly networkStateService: NetworkStateService + ) { + super(); + this.activeNotification = null; + } + + register(): void { + this.networkStateService.networkStateExecutor.addHandler(this.handleNetworkStateChange.bind(this)); + } + + load(): void | Promise { } + + private handleNetworkStateChange(state: boolean): void { + if (!state) { + if (this.activeNotification) { + return; + } + + this.activeNotification = this.notificationService.logInfo({ + title: 'plugin_root_network_state_title', + message: 'plugin_root_network_state_description', + persistent: true, + }); + } else { + this.activeNotification?.close(true); + this.activeNotification = null; + } + } +} diff --git a/webapp/packages/plugin-root/src/locales/en.ts b/webapp/packages/plugin-root/src/locales/en.ts new file mode 100644 index 0000000000..f5ef171cdf --- /dev/null +++ b/webapp/packages/plugin-root/src/locales/en.ts @@ -0,0 +1,4 @@ +export default [ + ['plugin_root_network_state_title', 'Offline mode'], + ['plugin_root_network_state_description', 'Application now in offline mode, some features are disabled'], +]; \ No newline at end of file diff --git a/webapp/packages/plugin-root/src/locales/it.ts b/webapp/packages/plugin-root/src/locales/it.ts new file mode 100644 index 0000000000..f5ef171cdf --- /dev/null +++ b/webapp/packages/plugin-root/src/locales/it.ts @@ -0,0 +1,4 @@ +export default [ + ['plugin_root_network_state_title', 'Offline mode'], + ['plugin_root_network_state_description', 'Application now in offline mode, some features are disabled'], +]; \ No newline at end of file diff --git a/webapp/packages/plugin-root/src/locales/ru.ts b/webapp/packages/plugin-root/src/locales/ru.ts new file mode 100644 index 0000000000..8056d70e20 --- /dev/null +++ b/webapp/packages/plugin-root/src/locales/ru.ts @@ -0,0 +1,4 @@ +export default [ + ['plugin_root_network_state_title', 'Автономный режим'], + ['plugin_root_network_state_description', 'Приложение в автономном режиме, некоторый функционал не доступен'], +]; \ No newline at end of file diff --git a/webapp/packages/plugin-root/src/locales/zh.ts b/webapp/packages/plugin-root/src/locales/zh.ts new file mode 100644 index 0000000000..49a52a5d07 --- /dev/null +++ b/webapp/packages/plugin-root/src/locales/zh.ts @@ -0,0 +1,4 @@ +export default [ + ['plugin_root_network_state_title', 'Offline mode'], + ['plugin_root_network_state_description', 'Application now in offline mode, some features are disabled'], +]; diff --git a/webapp/packages/plugin-root/src/manifest.ts b/webapp/packages/plugin-root/src/manifest.ts index 4ae168f8bd..3a4d6dab14 100644 --- a/webapp/packages/plugin-root/src/manifest.ts +++ b/webapp/packages/plugin-root/src/manifest.ts @@ -8,6 +8,8 @@ import type { PluginManifest } from '@cloudbeaver/core-di'; +import { LocaleService } from './LocaleService'; +import { NetworkStateNotificationService } from './NetworkStateNotification/NetworkStateNotificationService'; import { PluginBootstrap } from './PluginBootstrap'; import { SessionExpiredDialogService } from './SessionExpireDialog/SessionExpiredDialogService'; import { SessionExpireWarningDialogService } from './SessionExpireWarningDialog/SessionExpireWarningDialogService'; @@ -15,8 +17,10 @@ import { SessionExpireWarningDialogService } from './SessionExpireWarningDialog/ export const rootPlugin: PluginManifest = { info: { name: 'Root plugin' }, providers: [ + LocaleService, PluginBootstrap, SessionExpiredDialogService, SessionExpireWarningDialogService, + NetworkStateNotificationService, ], }; \ No newline at end of file From 69121eb1a4ee5a4ac56830e072cddeb6016e76a6 Mon Sep 17 00:00:00 2001 From: Wroud Date: Tue, 23 Aug 2022 22:39:14 +0300 Subject: [PATCH 4/9] feat(plugin-sql-editor): CB-2030 add isReadonly for ISqlDataSource --- .../src/ResourceSqlDataSource.ts | 13 +++++++++++++ .../src/ResourceSqlDataSourceBootstrap.ts | 7 ++++++- .../src/SqlDataSource/BaseSqlDataSource.ts | 4 ++++ .../src/SqlDataSource/ISqlDataSource.ts | 1 + .../LocalStorage/LocalStorageSqlDataSource.ts | 4 ++++ .../src/SqlDataSource/MemorySqlDataSource.ts | 4 ++++ .../plugin-sql-editor/src/SqlEditor/useSqlEditor.ts | 1 + 7 files changed, 33 insertions(+), 1 deletion(-) diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/ResourceSqlDataSource.ts b/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/ResourceSqlDataSource.ts index e6773084fe..237270e574 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/ResourceSqlDataSource.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/ResourceSqlDataSource.ts @@ -15,6 +15,10 @@ import { BaseSqlDataSource, ESqlDataSourceFeatures } from '@cloudbeaver/plugin-s import type { IResourceNodeInfo, IResourceSqlDataSourceState } from './IResourceSqlDataSourceState'; +interface IResourceInfo { + isReadonly?: (dataSource: ResourceSqlDataSource) => boolean; +} + interface IResourceActions { rename(dataSource: ResourceSqlDataSource, nodeId: string, name: string): Promise; read(dataSource: ResourceSqlDataSource, nodeId: string): Promise; @@ -53,6 +57,7 @@ export class ResourceSqlDataSource extends BaseSqlDataSource { private _script: string; private saved: boolean; private actions?: IResourceActions; + private info?: IResourceInfo; private lastAction?: () => Promise; private readonly state: IResourceSqlDataSourceState; @@ -85,6 +90,10 @@ export class ResourceSqlDataSource extends BaseSqlDataSource { }); } + isReadonly(): boolean { + return !this.isLoaded() || this.info?.isReadonly?.(this) === true; + } + isOutdated(): boolean { return this.nodeInfo !== undefined && super.isOutdated(); } @@ -108,6 +117,10 @@ export class ResourceSqlDataSource extends BaseSqlDataSource { this.actions = actions; } + setInfo(info?: IResourceInfo): void { + this.info = info; + } + setName(name: string | null): void { this.rename(name); } diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/ResourceSqlDataSourceBootstrap.ts b/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/ResourceSqlDataSourceBootstrap.ts index e103c06e8f..d6c1990218 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/ResourceSqlDataSourceBootstrap.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/ResourceSqlDataSourceBootstrap.ts @@ -13,7 +13,7 @@ import { Bootstrap, injectable } from '@cloudbeaver/core-di'; import { CommonDialogService, ConfirmationDialog, DialogueStateResult } from '@cloudbeaver/core-dialogs'; import { NotificationService } from '@cloudbeaver/core-events'; import { NavTreeResource, NavNodeInfoResource, INavNodeMoveData, INavNodeRenameData } from '@cloudbeaver/core-navigation-tree'; -import { WindowEventsService } from '@cloudbeaver/core-root'; +import { NetworkStateService, WindowEventsService } from '@cloudbeaver/core-root'; import { ResourceKey, resourceKeyList, ResourceKeyUtils } from '@cloudbeaver/core-sdk'; import { LocalStorageSaveService } from '@cloudbeaver/core-settings'; import { throttle } from '@cloudbeaver/core-utils'; @@ -34,6 +34,7 @@ export class ResourceSqlDataSourceBootstrap extends Bootstrap { private readonly dataSourceStateState = new Map(); constructor( + private readonly networkStateService: NetworkStateService, private readonly sqlDataSourceService: SqlDataSourceService, private readonly commonDialogService: CommonDialogService, private readonly navResourceNodeService: NavResourceNodeService, @@ -111,6 +112,10 @@ export class ResourceSqlDataSourceBootstrap extends Bootstrap { write: this.write.bind(this), }); + dataSource.setInfo({ + isReadonly: dataSource => !this.networkStateService.state, + }); + return dataSource; }, onDestroy: (_, editorId) => this.deleteState(editorId), diff --git a/webapp/packages/plugin-sql-editor/src/SqlDataSource/BaseSqlDataSource.ts b/webapp/packages/plugin-sql-editor/src/SqlDataSource/BaseSqlDataSource.ts index 1a037571fc..cae0ec0170 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlDataSource/BaseSqlDataSource.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlDataSource/BaseSqlDataSource.ts @@ -46,6 +46,10 @@ export abstract class BaseSqlDataSource implements ISqlDataSource { abstract setName(name: string | null): void; abstract setExecutionContext(executionContext?: IConnectionExecutionContextInfo | undefined): void; + isReadonly(): boolean { + return true; + } + isOutdated(): boolean { return this.outdated; } diff --git a/webapp/packages/plugin-sql-editor/src/SqlDataSource/ISqlDataSource.ts b/webapp/packages/plugin-sql-editor/src/SqlDataSource/ISqlDataSource.ts index 044e4a685a..5568db5333 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlDataSource/ISqlDataSource.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlDataSource/ISqlDataSource.ts @@ -20,6 +20,7 @@ export interface ISqlDataSource extends ILoadableState { readonly onSetScript: ISyncExecutor; readonly features: ESqlDataSourceFeatures[]; + isReadonly(): boolean; isOutdated(): boolean; markOutdated(): void; markUpdated(): void; diff --git a/webapp/packages/plugin-sql-editor/src/SqlDataSource/LocalStorage/LocalStorageSqlDataSource.ts b/webapp/packages/plugin-sql-editor/src/SqlDataSource/LocalStorage/LocalStorageSqlDataSource.ts index b0f2133196..c7917b2d34 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlDataSource/LocalStorage/LocalStorageSqlDataSource.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlDataSource/LocalStorage/LocalStorageSqlDataSource.ts @@ -43,6 +43,10 @@ export class LocalStorageSqlDataSource extends BaseSqlDataSource { }); } + isReadonly(): boolean { + return false; + } + setName(name: string | null): void { this.state.name = name ?? undefined; } diff --git a/webapp/packages/plugin-sql-editor/src/SqlDataSource/MemorySqlDataSource.ts b/webapp/packages/plugin-sql-editor/src/SqlDataSource/MemorySqlDataSource.ts index 585b94b11c..98c6566b15 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlDataSource/MemorySqlDataSource.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlDataSource/MemorySqlDataSource.ts @@ -52,6 +52,10 @@ export class MemorySqlDataSource extends BaseSqlDataSource { }); } + isReadonly(): boolean { + return false; + } + setScript(script: string): void { this._script = script; super.setScript(script); diff --git a/webapp/packages/plugin-sql-editor/src/SqlEditor/useSqlEditor.ts b/webapp/packages/plugin-sql-editor/src/SqlEditor/useSqlEditor.ts index 44286de4d6..19c363af76 100644 --- a/webapp/packages/plugin-sql-editor/src/SqlEditor/useSqlEditor.ts +++ b/webapp/packages/plugin-sql-editor/src/SqlEditor/useSqlEditor.ts @@ -95,6 +95,7 @@ export function useSqlEditor(state: ISqlEditorTabState): ISQLEditorData { this.executingScript || this.readonlyState || !!this.dataSource?.isOutdated() + || !!this.dataSource?.isReadonly() ); }, From 178bb7e6e878ac79ec19640a91a751b84e7a6484 Mon Sep 17 00:00:00 2001 From: Wroud Date: Tue, 23 Aug 2022 22:52:44 +0300 Subject: [PATCH 5/9] fix(plugin-data-viewer): CB-2030 code editor props --- .../ValuePanelPresentation/TextValue/TextValuePresentation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentation.tsx b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentation.tsx index cb13757ff8..4c18b84816 100644 --- a/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentation.tsx +++ b/webapp/packages/plugin-data-viewer/src/ValuePanelPresentation/TextValue/TextValuePresentation.tsx @@ -190,12 +190,12 @@ export const TextValuePresentation: TabContainerPanelComponent Date: Tue, 23 Aug 2022 22:58:55 +0300 Subject: [PATCH 6/9] fix(plugin-sql-editor-navigation-tab-resource): CB-2030 remove unused arg --- .../src/ResourceSqlDataSourceBootstrap.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/ResourceSqlDataSourceBootstrap.ts b/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/ResourceSqlDataSourceBootstrap.ts index d6c1990218..3715a06766 100644 --- a/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/ResourceSqlDataSourceBootstrap.ts +++ b/webapp/packages/plugin-sql-editor-navigation-tab-resource/src/ResourceSqlDataSourceBootstrap.ts @@ -113,7 +113,7 @@ export class ResourceSqlDataSourceBootstrap extends Bootstrap { }); dataSource.setInfo({ - isReadonly: dataSource => !this.networkStateService.state, + isReadonly: () => !this.networkStateService.state, }); return dataSource; From 43f5fad27616247811931d8d3a4713f89fc52d0a Mon Sep 17 00:00:00 2001 From: Wroud Date: Wed, 24 Aug 2022 11:52:34 +0300 Subject: [PATCH 7/9] feat(plugin-navigation-tree): CB-2453 projects for nav tree --- .../ElementsTree/NavigationNodeComponent.ts | 4 + .../NavigationTreeNode/NavigationNode.tsx | 17 +-- .../NavigationNodeDragged.tsx | 8 +- .../NavigationNodeElement.tsx | 1 + .../src/NavigationTree/NavigationTree.tsx | 7 +- .../NavigationNodeProjectControl.tsx | 104 ++++++++++++++++++ ...navigationTreeProjectsRendererRenderer.tsx | 67 +++++++++++ 7 files changed, 198 insertions(+), 10 deletions(-) create mode 100644 webapp/packages/plugin-navigation-tree/src/NavigationTree/ProjectsRenderer/NavigationNodeProjectControl.tsx create mode 100644 webapp/packages/plugin-navigation-tree/src/NavigationTree/ProjectsRenderer/navigationTreeProjectsRendererRenderer.tsx diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationNodeComponent.ts b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationNodeComponent.ts index c8f2064332..0b07f34691 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationNodeComponent.ts +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationNodeComponent.ts @@ -9,6 +9,7 @@ import type React from 'react'; import type { NavNode } from '@cloudbeaver/core-navigation-tree'; +import type { ComponentStyle } from '@cloudbeaver/core-theming'; export type NavTreeNodeComponent = React.FC<{ @@ -33,14 +34,17 @@ export type NavTreeControlComponent = React.FC; export type NavigationNodeComponent = React.FC<{ node: NavNode; component: NavTreeNodeComponent; + control?: NavTreeControlComponent | undefined; path: string[]; dragging?: boolean; expanded?: boolean; className?: string; + style?: ComponentStyle; }>; export type NavigationNodeRendererComponent = React.FC<{ nodeId: string; + path: string[]; component: NavTreeNodeComponent; expanded?: boolean; dragging?: boolean; diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode.tsx b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode.tsx index 06335a41b6..d44dbb20ce 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode.tsx +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNode.tsx @@ -12,6 +12,7 @@ import styled, { use, css } from 'reshadow'; import { getComputed, TreeNode } from '@cloudbeaver/core-blocks'; import { useService } from '@cloudbeaver/core-di'; import { NavNodeManagerService, DATA_CONTEXT_NAV_NODE, DATA_CONTEXT_NAV_NODES } from '@cloudbeaver/core-navigation-tree'; +import { useStyles } from '@cloudbeaver/core-theming'; import { useDNDData } from '@cloudbeaver/core-ui'; import { useDataContext } from '@cloudbeaver/core-view'; @@ -21,7 +22,7 @@ import { NavigationNodeControl } from './NavigationNode/NavigationNodeControl'; import { NavigationNodeNested } from './NavigationNode/NavigationNodeNested'; import { useNavigationNode } from './useNavigationNode'; -const style = css` +const styles = css` TreeNode[|hovered] ::before { opacity: 0.16; } @@ -31,8 +32,10 @@ export const NavigationNode: NavigationNodeComponent = observer(function Navigat node, component, path, - expanded: expandedExternal, + control: externalControl, + expanded: externalExpanded, className, + style, }) { const navNodeManagerService = useService(NavNodeManagerService); const { @@ -73,10 +76,10 @@ export const NavigationNode: NavigationNodeComponent = observer(function Navigat context.set(DATA_CONTEXT_NAV_NODE, node); context.set(DATA_CONTEXT_NAV_NODES, getSelected); - const Control = control || NavigationNodeControl; + const Control = control || externalControl || NavigationNodeControl; if (leaf || empty) { - expandedExternal = false; + externalExpanded = false; } function setRef(refObj: HTMLDivElement | null) { @@ -87,7 +90,7 @@ export const NavigationNode: NavigationNodeComponent = observer(function Navigat const hasNodes = getComputed(() => !!dndBox.state.context && dndBox.state.canDrop && dndBox.state.isOverCurrent); - return styled(style)( + return styled(useStyles(style, styles))( - {(expanded || expandedExternal) && ( + {(expanded || externalExpanded) && ( contextRef.context?.control); - const Control = control || NavigationNodeControl; + const Control = control || externalControl || NavigationNodeControl; - return ( + return styled(useStyles(style))( {/* */} diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNodeElement.tsx b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNodeElement.tsx index a29cf23d9b..750253d841 100644 --- a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNodeElement.tsx +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ElementsTree/NavigationTreeNode/NavigationNodeElement.tsx @@ -37,6 +37,7 @@ export const NavigationNodeElement: NavTreeNodeComponent = observer(function Nav return ( navigationTreeDuplicateFilter(navNodeViewService), [navNodeViewService]); + const projectsRendererRenderer = useMemo( + () => navigationTreeProjectsRendererRenderer(navNodeInfoResource), + [navNodeViewService] + ); if (!isEnabled) { return null; @@ -101,7 +106,7 @@ export const NavigationTree = observer(function NavigationTree() { root={root} localState={navTreeService.treeState} filters={[duplicateFilter, connectionGroupFilter]} - renderers={[navigationTreeConnectionGroupRenderer]} + renderers={[projectsRendererRenderer, navigationTreeConnectionGroupRenderer]} emptyPlaceholder={() => styled(navigationTreeStyles)(
diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ProjectsRenderer/NavigationNodeProjectControl.tsx b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ProjectsRenderer/NavigationNodeProjectControl.tsx new file mode 100644 index 0000000000..6c2d95e1f9 --- /dev/null +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ProjectsRenderer/NavigationNodeProjectControl.tsx @@ -0,0 +1,104 @@ +/* + * 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 { observer } from 'mobx-react-lite'; +import React, { forwardRef, useContext, useState } from 'react'; +import styled, { css, use } from 'reshadow'; + +import { getComputed, TreeNodeContext, TreeNodeControl, TreeNodeExpand, TreeNodeName, TREE_NODE_STYLES, useObjectRef } from '@cloudbeaver/core-blocks'; +import { useService } from '@cloudbeaver/core-di'; +import { EventContext, EventStopPropagationFlag } from '@cloudbeaver/core-events'; +import { NavNodeInfoResource, type INodeActions } from '@cloudbeaver/core-navigation-tree'; + +import { ElementsTreeContext } from '../ElementsTree/ElementsTreeContext'; +import type { NavTreeControlComponent, NavTreeControlProps } from '../ElementsTree/NavigationNodeComponent'; +import { NavigationNodeEditor } from '../ElementsTree/NavigationTreeNode/NavigationNode/NavigationNodeEditor'; +import { TreeNodeMenu } from '../ElementsTree/NavigationTreeNode/TreeNodeMenu/TreeNodeMenu'; + +const styles = css` + TreeNodeControl { + transition: opacity 0.3s ease; + opacity: 1; + + &[|outdated] { + opacity: 0.5; + } + } + TreeNodeControl:hover > portal, + TreeNodeControl:global([aria-selected=true]) > portal, + portal:focus-within { + visibility: visible; + } + portal { + position: relative; + } + TreeNodeName { + composes: theme-text-text-hint-on-light theme-typography--caption from global; + height: 100%; + max-width: 250px; + overflow: hidden; + text-overflow: ellipsis; + } + portal { + box-sizing: border-box; + margin-left: auto !important; + margin-right: 16px !important; + visibility: hidden; + } + name-box { + overflow: hidden; + text-overflow: ellipsis; + } +`; + +export const NavigationNodeProjectControl: NavTreeControlComponent = observer(forwardRef(function NavigationNodeControl({ + node, + dndElement, + dndPlaceholder, +}, ref) { + const treeNodeContext = useContext(TreeNodeContext); + const treeContext = useContext(ElementsTreeContext); + const navNodeInfoResource = useService(NavNodeInfoResource); + const outdated = getComputed(() => navNodeInfoResource.isOutdated(node.id) && !treeNodeContext.loading); + const selected = treeNodeContext.selected; + + const [editing, setEditing] = useState(false); + + const nodeActions = useObjectRef({ + rename: () => { + setEditing(true); + }, + }); + + function handlePortalClick(event: React.MouseEvent) { + EventContext.set(event, EventStopPropagationFlag); + treeNodeContext.select(); + } + + function onClickHandler(event: React.MouseEvent) { + treeNodeContext.select(event.ctrlKey || event.metaKey); + } + + return styled(TREE_NODE_STYLES, styles)( + + + + {editing ? ( + setEditing(false)} /> + ) : ( + {node.name} + )} + + {!editing && !dndPlaceholder && ( + + + + )} + + ); +})); diff --git a/webapp/packages/plugin-navigation-tree/src/NavigationTree/ProjectsRenderer/navigationTreeProjectsRendererRenderer.tsx b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ProjectsRenderer/navigationTreeProjectsRendererRenderer.tsx new file mode 100644 index 0000000000..4b3bf515b1 --- /dev/null +++ b/webapp/packages/plugin-navigation-tree/src/NavigationTree/ProjectsRenderer/navigationTreeProjectsRendererRenderer.tsx @@ -0,0 +1,67 @@ +/* + * 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 { observer } from 'mobx-react-lite'; +import styled from 'reshadow'; + +import { TreeNodeNestedMessage, TREE_NODE_STYLES } from '@cloudbeaver/core-blocks'; +import { Translate } from '@cloudbeaver/core-localization'; +import type { NavNodeInfoResource } from '@cloudbeaver/core-navigation-tree'; +import { NAV_NODE_TYPE_PROJECT } from '@cloudbeaver/core-projects'; + +import { useNode } from '../../NodesManager/useNode'; +import type { NavigationNodeRendererComponent } from '../ElementsTree/NavigationNodeComponent'; +import { NavigationNodeRenderer } from '../ElementsTree/NavigationTreeNode/NavigationNodeRenderer'; +import type { IElementsTreeCustomRenderer } from '../ElementsTree/useElementsTree'; +import { NavigationNodeProjectControl } from './NavigationNodeProjectControl'; + +export function navigationTreeProjectsRendererRenderer( + navNodeInfoResource: NavNodeInfoResource +): IElementsTreeCustomRenderer { + + return nodeId => { + const node = navNodeInfoResource.get(nodeId); + + if (node?.nodeType === NAV_NODE_TYPE_PROJECT) { + return ProjectRenderer; + } + + return undefined; + }; +} + +const ProjectRenderer: NavigationNodeRendererComponent = observer(function ManageableGroup({ + nodeId, + path, + dragging, + component, + className, + expanded, +}) { + const { node } = useNode(nodeId); + + if (!node) { + return styled(TREE_NODE_STYLES)( + + + + ); + } + + return ( + + ); +}); From bf77ff639f8b2fa982ae8d93ed9fdea74d86b480 Mon Sep 17 00:00:00 2001 From: serge-rider Date: Thu, 25 Aug 2022 12:26:56 +0200 Subject: [PATCH 8/9] Fix debug config --- server/product/web-server/CloudbeaverServer.product.launch | 3 --- 1 file changed, 3 deletions(-) diff --git a/server/product/web-server/CloudbeaverServer.product.launch b/server/product/web-server/CloudbeaverServer.product.launch index c7943aa377..f2752415aa 100644 --- a/server/product/web-server/CloudbeaverServer.product.launch +++ b/server/product/web-server/CloudbeaverServer.product.launch @@ -88,7 +88,6 @@ - @@ -97,8 +96,6 @@ - - From d4b805fddcc6bec821cec1c201ea4f9d0e4cb894 Mon Sep 17 00:00:00 2001 From: Wroud Date: Thu, 25 Aug 2022 13:38:15 +0300 Subject: [PATCH 9/9] fix: remove node's path dependency --- webapp/packages/core-blocks/src/Icon.test.tsx | 8 ++--- .../core-cli/configs/webpack.config.js | 2 +- .../core-utils/src/GlobalConstants.ts | 10 +++--- webapp/packages/core-utils/src/index.ts | 1 + webapp/packages/core-utils/src/pathJoin.ts | 34 +++++++++++++++++++ 5 files changed, 45 insertions(+), 10 deletions(-) create mode 100644 webapp/packages/core-utils/src/pathJoin.ts diff --git a/webapp/packages/core-blocks/src/Icon.test.tsx b/webapp/packages/core-blocks/src/Icon.test.tsx index e8cb556439..ba40ab41d9 100644 --- a/webapp/packages/core-blocks/src/Icon.test.tsx +++ b/webapp/packages/core-blocks/src/Icon.test.tsx @@ -15,19 +15,19 @@ test('icons.svg#name', () => { (globalThis as any)._ROOT_URI_ = undefined; render(); - expect(screen.getByTestId('icon').querySelector('use')).toHaveAttribute('href', '\\icons\\icons.svg#test'); + expect(screen.getByTestId('icon').querySelector('use')).toHaveAttribute('href', '/icons/icons.svg#test'); }); test('/image.jpg', () => { (globalThis as any)._ROOT_URI_ = undefined; render(); - expect(screen.getByTestId('icon').querySelector('use')).toHaveAttribute('href', '\\image.jpg'); + expect(screen.getByTestId('icon').querySelector('use')).toHaveAttribute('href', '/image.jpg'); }); test('{_ROOT_URI_}/icons.svg#name', () => { - (globalThis as any)._ROOT_URI_ = '\\path\\'; + (globalThis as any)._ROOT_URI_ = '/path/'; render(); - expect(screen.getByTestId('icon').querySelector('use')).toHaveAttribute('href', '\\path\\icons\\icons.svg#test'); + expect(screen.getByTestId('icon').querySelector('use')).toHaveAttribute('href', '/path/icons/icons.svg#test'); }); \ No newline at end of file diff --git a/webapp/packages/core-cli/configs/webpack.config.js b/webapp/packages/core-cli/configs/webpack.config.js index 06262a08c8..64992aca71 100644 --- a/webapp/packages/core-cli/configs/webpack.config.js +++ b/webapp/packages/core-cli/configs/webpack.config.js @@ -123,7 +123,7 @@ module.exports = (env, argv) => { // "react/jsx-dev-runtime.js": "react/jsx-dev-runtime" // }, fallback: { - path: require.resolve('path-browserify'), + // path: require.resolve('path-browserify'), }, plugins: [ PnpWebpackPlugin, diff --git a/webapp/packages/core-utils/src/GlobalConstants.ts b/webapp/packages/core-utils/src/GlobalConstants.ts index 9aecb2debb..8bb94743f8 100644 --- a/webapp/packages/core-utils/src/GlobalConstants.ts +++ b/webapp/packages/core-utils/src/GlobalConstants.ts @@ -6,7 +6,7 @@ * you may not use this file except in compliance with the License. */ -import path from 'path'; +import { pathJoin } from './pathJoin'; declare const _VERSION_: string | undefined; declare const _DEV_: boolean | undefined; @@ -27,19 +27,19 @@ export const GlobalConstants = { if (_ROOT_URI_ === '{ROOT_URI}') { return defaultURI; } - return path.join(_ROOT_URI_ ?? defaultURI, '/'); + return pathJoin(_ROOT_URI_ ?? defaultURI, '/'); }, get serviceURI(): string { - return path.join(this.rootURI, 'api'); + return pathJoin(this.rootURI, 'api'); }, absoluteRootUrl(...parts: string[]): string { - return path.join(this.rootURI, ...parts); + return pathJoin(this.rootURI, ...parts); }, absoluteServiceUrl(...parts: string[]): string { - return path.join(this.serviceURI, ...parts); + return pathJoin(this.serviceURI, ...parts); }, absoluteUrl(...parts: string[]): string { diff --git a/webapp/packages/core-utils/src/index.ts b/webapp/packages/core-utils/src/index.ts index 63bbbc6e88..a4ec9351de 100644 --- a/webapp/packages/core-utils/src/index.ts +++ b/webapp/packages/core-utils/src/index.ts @@ -23,6 +23,7 @@ export * from './md5'; export * from './MetadataMap'; export * from './OrderedMap'; export * from './parseJSONFlat'; +export * from './pathJoin'; export * from './PriorityQueue'; export * from './replaceMiddle'; export * from './TextTools'; diff --git a/webapp/packages/core-utils/src/pathJoin.ts b/webapp/packages/core-utils/src/pathJoin.ts new file mode 100644 index 0000000000..d5821cbb5e --- /dev/null +++ b/webapp/packages/core-utils/src/pathJoin.ts @@ -0,0 +1,34 @@ +/* + * 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. + */ + +// Joins path segments. Preserves initial "/" and resolves ".." and "." +// Does not support using ".." to go above/outside the root. +// This means that join("foo", "../../bar") will not resolve to "../bar" +export function pathJoin(...segments: string[]): string { + // Split the inputs into a list of path commands. + let parts: string[] = []; + for (const segment of segments) { + parts = parts.concat(segment.split('/')); + } + + // Interpret the path commands to get the new resolved path. + const newParts = []; + for (const part of parts) { + // Remove leading and trailing slashes + // Also remove "." segments + if (!part || part === '.') {continue;} + // Interpret ".." to pop the last segment + if (part === '..') {newParts.pop();} + // Push new path segments. + else {newParts.push(part);} + } + // Preserve the initial slash if there was one. + if (parts[0] === '') {newParts.unshift('');} + // Turn back into a single string path. + return newParts.join('/') || (newParts.length ? '/' : '.'); +} \ No newline at end of file