mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
Merge remote-tracking branch 'origin/CB-2344-projects-administration' into CB-2344-projects-administration
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { computed, makeObservable, runInAction } from 'mobx';
|
||||
import { computed, makeObservable, observable, runInAction } from 'mobx';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { SyncExecutor, ISyncExecutor, ITask, AutoRunningTask, whileTask } from '@cloudbeaver/core-executor';
|
||||
@@ -253,7 +253,7 @@ UserInfoIncludes
|
||||
|
||||
protected resetIncludes(): void {
|
||||
const metadata = this.getMetadata();
|
||||
metadata.includes = [...this.defaultIncludes];
|
||||
metadata.includes = observable([...this.defaultIncludes]);
|
||||
}
|
||||
|
||||
getIncludes(key: void): string[] {
|
||||
|
||||
@@ -13,6 +13,7 @@ import styled from 'reshadow';
|
||||
|
||||
import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { ErrorBoundary } from '../ErrorBoundary';
|
||||
import { useObjectRef } from '../useObjectRef';
|
||||
import { MenuPanel } from './MenuPanel';
|
||||
import { menuPanelStyles } from './menuPanelStyles';
|
||||
@@ -25,6 +26,7 @@ interface IMenuProps extends Omit<React.ButtonHTMLAttributes<any>, 'style'> {
|
||||
style?: ComponentStyle;
|
||||
disclosure?: boolean;
|
||||
placement?: MenuInitialState['placement'];
|
||||
submenu?: boolean;
|
||||
modal?: boolean;
|
||||
visible?: boolean;
|
||||
rtl?: boolean;
|
||||
@@ -48,6 +50,7 @@ export const Menu = observer<IMenuProps, HTMLButtonElement>(forwardRef(function
|
||||
getHasBindings,
|
||||
onVisibleSwitch,
|
||||
modal,
|
||||
submenu,
|
||||
rtl,
|
||||
...props
|
||||
}, ref) {
|
||||
@@ -64,18 +67,49 @@ export const Menu = observer<IMenuProps, HTMLButtonElement>(forwardRef(function
|
||||
propsRef.onVisibleSwitch?.(menu.visible);
|
||||
}, [menu.visible]);
|
||||
|
||||
let menuVisible = menu.visible;
|
||||
|
||||
if (panelAvailable === false) {
|
||||
menuVisible = false;
|
||||
}
|
||||
|
||||
|
||||
if (React.isValidElement(children) && disclosure) {
|
||||
return styled(styles)(
|
||||
<ErrorBoundary>
|
||||
<MenuStateContext.Provider value={menu}>
|
||||
<MenuButton ref={ref} {...menu} visible={menuVisible} {...props} {...children.props}>
|
||||
{disclosureProps => React.cloneElement(children, { ...disclosureProps, ...children.props })}
|
||||
</MenuButton>
|
||||
<MenuPanel
|
||||
label={label}
|
||||
menu={menu}
|
||||
style={style}
|
||||
rtl={rtl}
|
||||
submenu={submenu}
|
||||
panelAvailable={panelAvailable}
|
||||
hasBindings={hasBindings}
|
||||
getHasBindings={getHasBindings}
|
||||
>
|
||||
{items}
|
||||
</MenuPanel>
|
||||
</MenuStateContext.Provider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<ErrorBoundary>
|
||||
<MenuStateContext.Provider value={menu}>
|
||||
<MenuButton ref={ref} {...menu} {...props} {...children.props}>
|
||||
{disclosureProps => React.cloneElement(children, { ...disclosureProps, ...children.props })}
|
||||
<MenuButton ref={ref} {...menu} visible={menuVisible} {...props}>
|
||||
<box>{children}</box>
|
||||
</MenuButton>
|
||||
<MenuPanel
|
||||
label={label}
|
||||
menu={menu}
|
||||
style={style}
|
||||
rtl={rtl}
|
||||
submenu={submenu}
|
||||
panelAvailable={panelAvailable}
|
||||
hasBindings={hasBindings}
|
||||
getHasBindings={getHasBindings}
|
||||
@@ -83,25 +117,6 @@ export const Menu = observer<IMenuProps, HTMLButtonElement>(forwardRef(function
|
||||
{items}
|
||||
</MenuPanel>
|
||||
</MenuStateContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<MenuStateContext.Provider value={menu}>
|
||||
<MenuButton ref={ref} {...menu} {...props}>
|
||||
<box>{children}</box>
|
||||
</MenuButton>
|
||||
<MenuPanel
|
||||
label={label}
|
||||
menu={menu}
|
||||
style={style}
|
||||
rtl={rtl}
|
||||
panelAvailable={panelAvailable}
|
||||
hasBindings={hasBindings}
|
||||
getHasBindings={getHasBindings}
|
||||
>
|
||||
{items}
|
||||
</MenuPanel>
|
||||
</MenuStateContext.Provider>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
}));
|
||||
@@ -22,6 +22,7 @@ interface IMenuItemElementProps {
|
||||
icon?: string | React.ReactNode;
|
||||
menu?: boolean;
|
||||
loading?: boolean;
|
||||
panelAvailable?: boolean;
|
||||
style?: ComponentStyle;
|
||||
}
|
||||
|
||||
@@ -31,6 +32,7 @@ export const MenuItemElement = observer<IMenuItemElementProps>(function MenuItem
|
||||
binding,
|
||||
icon,
|
||||
menu,
|
||||
panelAvailable,
|
||||
loading = false,
|
||||
style = [],
|
||||
}) {
|
||||
@@ -52,7 +54,7 @@ export const MenuItemElement = observer<IMenuItemElementProps>(function MenuItem
|
||||
</menu-item-binding>
|
||||
<menu-item-content>
|
||||
{loading && <Loader small fullSize />}
|
||||
{menu && !loading && <Icon name="arrow" viewBox="0 0 16 16" />}
|
||||
{panelAvailable !== false && menu && !loading && <Icon name="arrow" viewBox="0 0 16 16" />}
|
||||
</menu-item-content>
|
||||
</menu-panel-item>
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import styled, { use } from 'reshadow';
|
||||
|
||||
import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming';
|
||||
|
||||
import { ErrorBoundary } from '../ErrorBoundary';
|
||||
import { getComputed } from '../getComputed';
|
||||
import { MenuEmptyItem } from './MenuEmptyItem';
|
||||
import { menuPanelStyles } from './menuPanelStyles';
|
||||
@@ -26,6 +27,7 @@ export interface IMenuPanelProps {
|
||||
getHasBindings?: () => boolean;
|
||||
children: React.ReactNode | (() => React.ReactNode);
|
||||
rtl?: boolean;
|
||||
submenu?: boolean;
|
||||
style?: ComponentStyle;
|
||||
className?: string;
|
||||
}
|
||||
@@ -59,13 +61,15 @@ export const MenuPanel = observer<IMenuPanelProps>(function MenuPanel({
|
||||
}
|
||||
|
||||
return styled(styles)(
|
||||
<Menu {...menu} aria-label={label} className={className} visible={panelAvailable}>
|
||||
<menu-box dir={rtl ? 'rtl' : undefined} {...use({ hasBindings })}>
|
||||
{Children.count(renderedChildren) === 0 && (
|
||||
<MenuEmptyItem style={style} />
|
||||
)}
|
||||
{renderedChildren}
|
||||
</menu-box>
|
||||
</Menu>
|
||||
<ErrorBoundary>
|
||||
<Menu {...menu} aria-label={label} className={className} visible={panelAvailable}>
|
||||
<menu-box dir={rtl ? 'rtl' : undefined} {...use({ hasBindings })}>
|
||||
{Children.count(renderedChildren) === 0 && (
|
||||
<MenuEmptyItem style={style} />
|
||||
)}
|
||||
{renderedChildren}
|
||||
</menu-box>
|
||||
</Menu>
|
||||
</ErrorBoundary>
|
||||
);
|
||||
});
|
||||
@@ -39,6 +39,9 @@ export const menuPanelStyles = css`
|
||||
outline: none;
|
||||
z-index: 999;
|
||||
}
|
||||
MenuPanel[submenu] {
|
||||
margin-top: -12px;
|
||||
}
|
||||
menu-box {
|
||||
composes: theme-typography--body2 theme-elevation-z5 from global;
|
||||
min-width: 140px;
|
||||
@@ -58,6 +61,12 @@ export const menuPanelStyles = css`
|
||||
}
|
||||
}
|
||||
|
||||
Menu[modal] menu-box {
|
||||
overflow: auto;
|
||||
max-height: 80vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
MenuItem, MenuItemCheckbox, MenuItemRadio {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -109,6 +118,7 @@ export const menuPanelStyles = css`
|
||||
align-items: center;
|
||||
height: 30px;
|
||||
padding: 0 4px;
|
||||
|
||||
& menu-item-text, & menu-item-binding {
|
||||
display: block;
|
||||
padding: 0 4px;
|
||||
@@ -145,4 +155,13 @@ export const menuPanelStyles = css`
|
||||
width: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
Menu[rtl] {
|
||||
& menu-item-text {
|
||||
text-align: right;
|
||||
}
|
||||
& menu-panel-item menu-item-content Icon {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -14,6 +14,7 @@ import { NotificationService } from '@cloudbeaver/core-events';
|
||||
import { CachedResourceIncludeArgs, CachedMapResource, CachedMapResourceGetter, ResourceKey, CachedMapResourceValue, CachedMapResourceKey, CachedMapResourceArguments, CachedMapResourceLoader, ResourceKeyList, CachedMapResourceListGetter, isResourceKeyList } from '@cloudbeaver/core-sdk';
|
||||
import { isArraysEqual } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { getComputed } from './getComputed';
|
||||
import type { ILoadableState } from './Loader/ILoadableState';
|
||||
import { useObservableRef } from './useObservableRef';
|
||||
|
||||
@@ -277,6 +278,7 @@ export function useMapResource<
|
||||
preloaded: computed,
|
||||
exception: observable.ref,
|
||||
loading: observable.ref,
|
||||
resource: observable.ref,
|
||||
}, {
|
||||
exceptionObserved: false,
|
||||
resource,
|
||||
@@ -364,20 +366,22 @@ export function useMapResource<
|
||||
loading: computed,
|
||||
}, false);
|
||||
|
||||
// TODO: getComputed skips update somehow ...
|
||||
const outdated = (
|
||||
!result.loading
|
||||
&& (result.outdated || !result.loaded)
|
||||
);
|
||||
|
||||
const preloaded = refObj.preloaded; // make mobx subscription
|
||||
const canLoad = getComputed(() => (
|
||||
(
|
||||
!keyRef.actual
|
||||
|| result.outdated
|
||||
|| !result.loaded
|
||||
)
|
||||
&& refObj.preloaded
|
||||
&& keyRef.key !== null
|
||||
&& (
|
||||
result.exception === null
|
||||
|| (Array.isArray(result.exception) && !result.exception.some(Boolean))
|
||||
)
|
||||
));
|
||||
|
||||
useEffect(() => {
|
||||
if (!preloaded || (!outdated && keyRef.actual) || keyRef.key === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.exception === null || (Array.isArray(result.exception) && !result.exception.some(Boolean))) {
|
||||
if (canLoad) {
|
||||
(refObj as any)[loadFunctionName]();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -74,6 +74,10 @@ module.exports = (env, argv) => merge(commonConfig(env, argv), {
|
||||
|
||||
minimizer: [new TerserPlugin({
|
||||
extractComments: /Copyright \(C\)/i,
|
||||
terserOptions: {
|
||||
keep_classnames: true,
|
||||
keep_fnames: true,
|
||||
},
|
||||
})],
|
||||
},
|
||||
plugins: [
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { computed, makeObservable } from 'mobx';
|
||||
import { computed, makeObservable, runInAction } from 'mobx';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { EPermission, SessionPermissionsResource, ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
@@ -37,7 +37,7 @@ export class DBDriverResource extends CachedMapResource<string, DBDriver, Driver
|
||||
super();
|
||||
permissionsResource.require(this, EPermission.public);
|
||||
|
||||
this.serverConfigResource.onDataOutdated.addHandler(this.markOutdated.bind(this));
|
||||
this.serverConfigResource.onDataOutdated.addHandler(() => this.markOutdated());
|
||||
|
||||
makeObservable(this, {
|
||||
enabledDrivers: computed,
|
||||
@@ -69,19 +69,21 @@ export class DBDriverResource extends CachedMapResource<string, DBDriver, Driver
|
||||
includeDriverParameters: false,
|
||||
includeDriverProperties: false,
|
||||
includeProviderProperties: false,
|
||||
...this.getIncludesMap(driverId, includes),
|
||||
...this.getIncludesMap(driverId, (all ? this.defaultIncludes : includes)),
|
||||
});
|
||||
|
||||
if (all) {
|
||||
this.resetIncludes();
|
||||
this.data.clear();
|
||||
}
|
||||
|
||||
if (driverId && !drivers.some(driver => driver.id === driverId)) {
|
||||
throw new Error('Driver is not found');
|
||||
}
|
||||
|
||||
this.updateDriver(...drivers);
|
||||
runInAction(() => {
|
||||
if (all) {
|
||||
const removedDrivers = this.keys.filter(key => !drivers.some(driver => driver.id === key));
|
||||
this.delete(resourceKeyList(removedDrivers));
|
||||
}
|
||||
|
||||
this.updateDriver(...drivers);
|
||||
});
|
||||
});
|
||||
|
||||
return this.data;
|
||||
@@ -91,6 +93,6 @@ export class DBDriverResource extends CachedMapResource<string, DBDriver, Driver
|
||||
const keys = resourceKeyList(drivers.map(driver => driver.id));
|
||||
|
||||
const oldDriver = this.get(keys);
|
||||
this.set(keys, oldDriver.map((oldDriver, i) => ({ ...oldDriver, ...drivers[i] })));
|
||||
this.set(keys, oldDriver.map((oldDriver, i) => (Object.assign(oldDriver ?? {}, drivers[i]))));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,12 @@
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { flat } from '@cloudbeaver/core-utils';
|
||||
|
||||
import { Bootstrap } from './Bootstrap';
|
||||
import { Dependency } from './Dependency';
|
||||
import type { DIContainer } from './DIContainer';
|
||||
import type { IServiceCollection, IServiceInjector } from './IApp';
|
||||
import type { IServiceCollection, IServiceConstructor, IServiceInjector } from './IApp';
|
||||
import { IDiWrapper, inversifyWrapper } from './inversifyWrapper';
|
||||
import type { PluginManifest } from './PluginManifest';
|
||||
|
||||
@@ -24,6 +26,14 @@ export class App {
|
||||
this.getServiceCollection().addServiceByClass(App, this);
|
||||
}
|
||||
|
||||
getPlugins(): PluginManifest[] {
|
||||
return [...this.plugins];
|
||||
}
|
||||
|
||||
getServices(): IServiceConstructor<any>[] {
|
||||
return flat(this.plugins.map(plugin => plugin.providers));
|
||||
}
|
||||
|
||||
registerChildContainer(container: DIContainer): void {
|
||||
this.diWrapper.registerChildContainer(container);
|
||||
}
|
||||
@@ -42,41 +52,33 @@ export class App {
|
||||
|
||||
// first phase register all dependencies
|
||||
registerServices(): void {
|
||||
for (const plugin of this.plugins) {
|
||||
if (plugin.providers.length) {
|
||||
plugin.providers.forEach(provider => {
|
||||
// console.log('provider', provider.name);
|
||||
this.diWrapper.collection.addServiceByClass(provider);
|
||||
});
|
||||
}
|
||||
for (const service of this.getServices()) {
|
||||
// console.log('provider', provider.name);
|
||||
this.diWrapper.collection.addServiceByClass(service);
|
||||
}
|
||||
}
|
||||
|
||||
async initializeServices(): Promise<void> {
|
||||
for (const plugin of this.plugins) {
|
||||
for (const service of plugin.providers) {
|
||||
if (service.prototype instanceof Bootstrap) {
|
||||
const serviceInstance = this.diWrapper.injector.getServiceByClass<Bootstrap>(service);
|
||||
for (const service of this.getServices()) {
|
||||
if (service.prototype instanceof Bootstrap) {
|
||||
const serviceInstance = this.diWrapper.injector.getServiceByClass<Bootstrap>(service);
|
||||
|
||||
if ('register' in serviceInstance) {
|
||||
await serviceInstance.register();
|
||||
}
|
||||
} else if (service.prototype instanceof Dependency) {
|
||||
this.diWrapper.injector.getServiceByClass<Bootstrap>(service);
|
||||
if ('register' in serviceInstance) {
|
||||
await serviceInstance.register();
|
||||
}
|
||||
} else if (service.prototype instanceof Dependency) {
|
||||
this.diWrapper.injector.getServiceByClass<Bootstrap>(service);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async loadServices(): Promise<void> {
|
||||
for (const plugin of this.plugins) {
|
||||
for (const service of plugin.providers) {
|
||||
if (service.prototype instanceof Bootstrap) {
|
||||
const serviceInstance = this.diWrapper.injector.getServiceByClass<Bootstrap>(service);
|
||||
for (const service of this.getServices()) {
|
||||
if (service.prototype instanceof Bootstrap) {
|
||||
const serviceInstance = this.diWrapper.injector.getServiceByClass<Bootstrap>(service);
|
||||
|
||||
if ('load' in serviceInstance) {
|
||||
await serviceInstance.load();
|
||||
}
|
||||
if ('load' in serviceInstance) {
|
||||
await serviceInstance.load();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -366,25 +366,21 @@ export abstract class CachedMapResource<
|
||||
}
|
||||
|
||||
includes(param: ResourceKey<TKey>, key: ResourceKey<TKey>): boolean {
|
||||
if (param === key) {
|
||||
if
|
||||
(
|
||||
this.isAliasEqual(param, key)
|
||||
|| (ResourceKeyUtils.isEmpty(param) && ResourceKeyUtils.isEmpty(key))
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isAlias(param) || this.isAlias(key)) {
|
||||
return this.isAliasEqual(param, key);
|
||||
}
|
||||
|
||||
if (ResourceKeyUtils.isEmpty(param) || ResourceKeyUtils.isEmpty(key)) {
|
||||
return ResourceKeyUtils.isEmpty(param) && ResourceKeyUtils.isEmpty(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
param = ResourceKeyUtils.mapKey(param, this.getKeyRef.bind(this));
|
||||
key = ResourceKeyUtils.mapKey(key, this.getKeyRef.bind(this));
|
||||
|
||||
if (param === key) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ResourceKeyUtils.includes(param, key, this.isKeyEqual);
|
||||
}
|
||||
|
||||
@@ -454,6 +450,7 @@ export abstract class CachedMapResource<
|
||||
if (key === undefined) {
|
||||
key = ResourceKeyUtils.join(resourceKeyList(this.keys), ...this.loadedKeys.map(key => this.transformParam(key)));
|
||||
this.loadedKeys = [];
|
||||
this.resetIncludes();
|
||||
} else {
|
||||
if (this.isAlias(key)) {
|
||||
const index = this.loadedKeys.findIndex(loadedKey => this.isAliasEqual(key!, loadedKey));
|
||||
@@ -466,10 +463,10 @@ export abstract class CachedMapResource<
|
||||
key = this.transformParam(key);
|
||||
}
|
||||
|
||||
runInAction(() => ResourceKeyUtils.forEach(key!, key => {
|
||||
ResourceKeyUtils.forEach(key, key => {
|
||||
const metadata = this.getMetadata(key);
|
||||
metadata.outdated = true;
|
||||
}));
|
||||
});
|
||||
|
||||
this.onDataOutdated.execute(key);
|
||||
}
|
||||
|
||||
@@ -98,7 +98,10 @@ export abstract class CachedResource<
|
||||
this.spy(this.onDataError, 'onDataError');
|
||||
}
|
||||
|
||||
makeObservable<CachedResource<TData, TParam, TKey, TContext>, 'loader' | 'loadedKeys'>(this, {
|
||||
makeObservable<
|
||||
CachedResource<TData, TParam, TKey, TContext>,
|
||||
'loader' | 'loadedKeys' | 'commitIncludes' | 'resetIncludes' | 'markOutdatedSync'
|
||||
>(this, {
|
||||
loadedKeys: observable,
|
||||
data: observable,
|
||||
loader: action,
|
||||
@@ -107,6 +110,9 @@ export abstract class CachedResource<
|
||||
markDataError: action,
|
||||
markOutdated: action,
|
||||
markUpdated: action,
|
||||
commitIncludes: action,
|
||||
markOutdatedSync: action,
|
||||
resetIncludes: action,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -360,7 +366,7 @@ export abstract class CachedResource<
|
||||
break transform;
|
||||
}
|
||||
} else {
|
||||
if (this.includes(alias.param, param)) {
|
||||
if (alias.param === param) {
|
||||
param = alias.getAlias(param);
|
||||
deep++;
|
||||
// eslint-disable-next-line no-labels
|
||||
@@ -406,7 +412,7 @@ export abstract class CachedResource<
|
||||
|
||||
protected resetIncludes(): void {
|
||||
for (const metadata of this.metadata.values()) {
|
||||
metadata.includes = [...this.defaultIncludes];
|
||||
metadata.includes = observable([...this.defaultIncludes]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -441,12 +447,16 @@ export abstract class CachedResource<
|
||||
}
|
||||
|
||||
isAliasEqual(param: TParam, second: TParam): boolean {
|
||||
if (param === second) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.paramAliases.some(alias => {
|
||||
if ('getter' in alias && alias.getter) {
|
||||
return alias.param(param) && alias.param(second);
|
||||
} else {
|
||||
return alias.param === param && alias.param === second;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -455,14 +465,10 @@ export abstract class CachedResource<
|
||||
}
|
||||
|
||||
protected includes(param: TParam, second: TParam): boolean {
|
||||
if (param === second) {
|
||||
if (this.isAliasEqual(param, second)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this.isAlias(param) || this.isAlias(second)) {
|
||||
return this.isAliasEqual(param, second);
|
||||
}
|
||||
|
||||
param = this.transformParam(param);
|
||||
second = this.transformParam(second);
|
||||
return this.isKeyEqual(param, second);
|
||||
|
||||
@@ -188,6 +188,10 @@ export const ResourceKeyUtils: ResourceKeyUtils = {
|
||||
key: ResourceKey<TKey>,
|
||||
isEqual = (keyA: TKey, keyB: TKey) => keyA === keyB
|
||||
): boolean {
|
||||
if (param === key) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isResourceKeyList(param)) {
|
||||
return param.includes(key);
|
||||
}
|
||||
|
||||
@@ -100,6 +100,7 @@ export const ContextMenu = observer<IContextMenuProps, HTMLButtonElement>(forwar
|
||||
menuData={menuData}
|
||||
rtl={rtl}
|
||||
menu={menu.current}
|
||||
modal={modal}
|
||||
style={style}
|
||||
onItemClose={handlers.handleItemClose}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
/*
|
||||
* 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 type { MenuStateReturn } from 'reakit/ts';
|
||||
|
||||
import type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
import type { IMenuData, IMenuItem } from '@cloudbeaver/core-view';
|
||||
|
||||
export interface IContextMenuItemProps {
|
||||
item: IMenuItem;
|
||||
menu: MenuStateReturn;
|
||||
menuData: IMenuData;
|
||||
style?: ComponentStyle;
|
||||
onClick: () => void;
|
||||
}
|
||||
@@ -8,18 +8,17 @@
|
||||
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import React from 'react';
|
||||
import { MenuItem, MenuItemCheckbox, MenuItemRadio, MenuStateReturn } from 'reakit/Menu';
|
||||
import { MenuItem, MenuItemCheckbox, MenuItemRadio } from 'reakit/Menu';
|
||||
import styled, { use } from 'reshadow';
|
||||
|
||||
import { Checkbox, MenuItemElement, menuPanelStyles, Radio } from '@cloudbeaver/core-blocks';
|
||||
import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
import type { IMenuActionItem } from '@cloudbeaver/core-view';
|
||||
|
||||
interface IMenuActionElementProps {
|
||||
import type { IContextMenuItemProps } from './IContextMenuItemProps';
|
||||
|
||||
interface IMenuActionElementProps extends IContextMenuItemProps {
|
||||
item: IMenuActionItem;
|
||||
menu: MenuStateReturn;
|
||||
style?: ComponentStyle;
|
||||
onClick: () => void;
|
||||
}
|
||||
|
||||
export const MenuActionElement = observer<IMenuActionElementProps>(function MenuActionElement({
|
||||
|
||||
@@ -13,7 +13,7 @@ import styled, { use } from 'reshadow';
|
||||
|
||||
import { MenuItemElement, menuPanelStyles } from '@cloudbeaver/core-blocks';
|
||||
import { ComponentStyle, joinStyles, useStyles } from '@cloudbeaver/core-theming';
|
||||
import { IMenuItem, IMenuData, MenuSubMenuItem, MenuSeparatorItem, MenuActionItem, MenuBaseItem } from '@cloudbeaver/core-view';
|
||||
import { IMenuItem, IMenuData, MenuSubMenuItem, MenuSeparatorItem, MenuActionItem, MenuBaseItem, MenuCustomItem } from '@cloudbeaver/core-view';
|
||||
|
||||
import { MenuActionElement } from './MenuActionElement';
|
||||
import { SubMenuElement } from './SubMenuElement';
|
||||
@@ -23,13 +23,14 @@ export interface IMenuItemRendererProps extends Omit<React.ButtonHTMLAttributes<
|
||||
item: IMenuItem;
|
||||
menuData: IMenuData;
|
||||
menu: MenuStateReturn; // from reakit useMenuState
|
||||
modal?: boolean;
|
||||
rtl?: boolean;
|
||||
onItemClose?: () => void;
|
||||
style?: ComponentStyle;
|
||||
}
|
||||
|
||||
export const MenuItemRenderer = observer<IMenuItemRendererProps>(function MenuItemRenderer({
|
||||
item, rtl, menuData, menu, onItemClose, style,
|
||||
item, modal, rtl, menuData, menu, onItemClose, style,
|
||||
}) {
|
||||
const styles = useStyles(menuPanelStyles, style);
|
||||
const onClick = useCallback(() => {
|
||||
@@ -40,21 +41,36 @@ export const MenuItemRenderer = observer<IMenuItemRendererProps>(function MenuIt
|
||||
}
|
||||
}, [item, onItemClose]);
|
||||
|
||||
if (item instanceof MenuCustomItem) {
|
||||
const CustomMenuItem = item.getComponent();
|
||||
|
||||
return styled(styles)(
|
||||
<CustomMenuItem
|
||||
item={item}
|
||||
menu={menu}
|
||||
menuData={menuData}
|
||||
style={style}
|
||||
onClick={onClick}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (item instanceof MenuSubMenuItem) {
|
||||
return styled(styles)(
|
||||
<MenuItem
|
||||
{...menu}
|
||||
{...{ as: SubMenuElement }}
|
||||
{...use({ hidden: item.hidden })}
|
||||
id={item.id}
|
||||
aria-label={item.menu.label}
|
||||
itemRenderer={MenuItemRenderer}
|
||||
rtl={rtl}
|
||||
menuRtl={rtl}
|
||||
menuData={menuData}
|
||||
menuModal={modal}
|
||||
subMenu={item}
|
||||
style={style}
|
||||
onItemClose={onItemClose}
|
||||
onClick={onClick}
|
||||
{...{ as: SubMenuElement }}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -68,6 +84,7 @@ export const MenuItemRenderer = observer<IMenuItemRendererProps>(function MenuIt
|
||||
<MenuActionElement
|
||||
item={item}
|
||||
menu={menu}
|
||||
menuData={menuData}
|
||||
style={style}
|
||||
onClick={onClick}
|
||||
/>
|
||||
|
||||
@@ -13,7 +13,7 @@ import styled from 'reshadow';
|
||||
import { getComputed, IMenuState, Menu, MenuItemElement, menuPanelStyles, useObjectRef } from '@cloudbeaver/core-blocks';
|
||||
import { useService } from '@cloudbeaver/core-di';
|
||||
import { ComponentStyle, useStyles } from '@cloudbeaver/core-theming';
|
||||
import { DATA_CONTEXT_MENU_NESTED, IMenuData, IMenuSubMenuItem, MenuActionItem, MenuService, useMenu } from '@cloudbeaver/core-view';
|
||||
import { DATA_CONTEXT_MENU_NESTED, DATA_CONTEXT_SUBMENU_ITEM, IMenuData, IMenuSubMenuItem, MenuActionItem, MenuService, useMenu } from '@cloudbeaver/core-view';
|
||||
|
||||
import type { IMenuItemRendererProps } from './MenuItemRenderer';
|
||||
|
||||
@@ -21,7 +21,8 @@ interface ISubMenuElementProps extends Omit<React.ButtonHTMLAttributes<any>, 'st
|
||||
menuData: IMenuData;
|
||||
subMenu: IMenuSubMenuItem;
|
||||
itemRenderer: React.FC<IMenuItemRendererProps>;
|
||||
rtl?: boolean;
|
||||
menuModal?: boolean;
|
||||
menuRtl?: boolean;
|
||||
onItemClose?: () => void;
|
||||
style?: ComponentStyle;
|
||||
}
|
||||
@@ -32,7 +33,8 @@ export const SubMenuElement = observer<ISubMenuElementProps, HTMLButtonElement>(
|
||||
subMenu,
|
||||
itemRenderer,
|
||||
style,
|
||||
rtl,
|
||||
menuModal: modal,
|
||||
menuRtl: rtl,
|
||||
onItemClose,
|
||||
...rest
|
||||
},
|
||||
@@ -43,6 +45,7 @@ export const SubMenuElement = observer<ISubMenuElementProps, HTMLButtonElement>(
|
||||
const styles = useStyles(menuPanelStyles, style);
|
||||
const subMenuData = useMenu({ menu: subMenu.menu, context: menuData.context });
|
||||
subMenuData.context.set(DATA_CONTEXT_MENU_NESTED, true);
|
||||
subMenuData.context.set(DATA_CONTEXT_SUBMENU_ITEM, subMenu);
|
||||
|
||||
const handler = menuService.getHandler(subMenuData.context);
|
||||
const hidden = getComputed(() => handler?.isHidden?.(subMenuData.context));
|
||||
@@ -72,6 +75,7 @@ export const SubMenuElement = observer<ISubMenuElementProps, HTMLButtonElement>(
|
||||
const loading = getComputed(() => handler?.isLoading?.(subMenuData.context));
|
||||
const disabled = getComputed(() => handler?.isDisabled?.(subMenuData.context));
|
||||
const MenuItemRenderer = itemRenderer;
|
||||
const panelAvailable = subMenuData.available && !loading;
|
||||
|
||||
return styled(styles)(
|
||||
<Menu
|
||||
@@ -84,22 +88,28 @@ export const SubMenuElement = observer<ISubMenuElementProps, HTMLButtonElement>(
|
||||
item={item}
|
||||
menuData={subMenuData}
|
||||
menu={menu.current}
|
||||
rtl={rtl}
|
||||
modal={modal}
|
||||
style={style}
|
||||
onItemClose={handlers.handleItemClose}
|
||||
/>
|
||||
))}
|
||||
rtl={rtl}
|
||||
modal={modal}
|
||||
style={style}
|
||||
panelAvailable={subMenuData.available && !loading}
|
||||
placement={rtl ? 'left-start' : 'right-start'}
|
||||
panelAvailable={panelAvailable}
|
||||
disabled={disabled}
|
||||
getHasBindings={handlers.hasBindings}
|
||||
submenu
|
||||
onVisibleSwitch={handlers.handleVisibleSwitch}
|
||||
{...rest}
|
||||
>
|
||||
<MenuItemElement
|
||||
label={subMenu.menu.label}
|
||||
icon={subMenu.menu.icon}
|
||||
tooltip={subMenu.menu.tooltip}
|
||||
label={subMenu.label ?? subMenu.menu.label}
|
||||
icon={subMenu.icon ?? subMenu.menu.icon}
|
||||
tooltip={subMenu.tooltip ?? subMenu.menu.tooltip}
|
||||
panelAvailable={panelAvailable}
|
||||
loading={loading}
|
||||
style={style}
|
||||
menu
|
||||
|
||||
@@ -31,7 +31,7 @@ export class TabsBootstrap extends Bootstrap {
|
||||
this.actionService.addHandler({
|
||||
id: 'tabs-base-handler',
|
||||
isActionApplicable: (context, action) => {
|
||||
const menu = context.find(DATA_CONTEXT_MENU, MENU_TAB);
|
||||
const menu = context.hasValue(DATA_CONTEXT_MENU, MENU_TAB);
|
||||
const state = context.tryGet(DATA_CONTEXT_TABS_CONTEXT);
|
||||
const tab = context.tryGet(DATA_CONTEXT_TAB_ID);
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ export * from './Clipboard/ClipboardBootstrap';
|
||||
export * from './Clipboard/ClipboardService';
|
||||
|
||||
export * from './ContextMenu/ContextMenu';
|
||||
export * from './ContextMenu/IContextMenuItemProps';
|
||||
export * from './ContextMenu/MenuBar/MenuBar';
|
||||
export * from './ContextMenu/MenuBar/MENU_BAR_DEFAULT_STYLES';
|
||||
export * from './ContextMenu/MenuBar/MENU_BAR_SMALL_STYLES';
|
||||
|
||||
@@ -15,7 +15,7 @@ import type { DeleteVersionedContextCallback, IDataContext } from './IDataContex
|
||||
import type { IDataContextProvider } from './IDataContextProvider';
|
||||
|
||||
export class DataContext implements IDataContext {
|
||||
private readonly map: Map<DataContextGetter<any>, any>;
|
||||
readonly map: Map<DataContextGetter<any>, any>;
|
||||
private readonly versions: MetadataMap<DataContextGetter<any>, number>;
|
||||
fallback?: IDataContextProvider;
|
||||
|
||||
@@ -40,7 +40,7 @@ export class DataContext implements IDataContext {
|
||||
return this.map.has(context) || this.fallback?.has(context) || false;
|
||||
}
|
||||
|
||||
find<T>(context: DataContextGetter<T>, value: T): boolean {
|
||||
hasValue<T>(context: DataContextGetter<T>, value: T): boolean {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
let provider: IDataContextProvider = this;
|
||||
|
||||
@@ -57,6 +57,25 @@ export class DataContext implements IDataContext {
|
||||
}
|
||||
}
|
||||
|
||||
find<T>(context: DataContextGetter<T>, predicate: (value: T) => boolean): T | undefined {
|
||||
// eslint-disable-next-line @typescript-eslint/no-this-alias
|
||||
let provider: IDataContextProvider = this;
|
||||
|
||||
while (true) {
|
||||
const value = provider.map.get(context);
|
||||
|
||||
if (predicate(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (provider.fallback) {
|
||||
provider = provider.fallback;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set<T>(context: DataContextGetter<T>, value: T): DeleteVersionedContextCallback {
|
||||
const data = this.map.get(context);
|
||||
let version = this.versions.get(context);
|
||||
|
||||
@@ -15,6 +15,10 @@ export class DynamicDataContext implements IDataContext {
|
||||
fallback: IDataContext;
|
||||
contexts: Map<DataContextGetter<any>, DeleteVersionedContextCallback>;
|
||||
|
||||
get map() {
|
||||
return this.fallback.map;
|
||||
}
|
||||
|
||||
constructor(fallback: IDataContext) {
|
||||
this.fallback = fallback;
|
||||
this.contexts = new Map();
|
||||
@@ -56,8 +60,12 @@ export class DynamicDataContext implements IDataContext {
|
||||
return this.fallback.get(context);
|
||||
}
|
||||
|
||||
find <T>(context: DataContextGetter<T>, value: T): boolean {
|
||||
return this.fallback.find(context, value);
|
||||
hasValue <T>(context: DataContextGetter<T>, value: T): boolean {
|
||||
return this.fallback.hasValue(context, value);
|
||||
}
|
||||
|
||||
find<T>(context: DataContextGetter<T>, predicate: (value: T) => boolean): T | undefined {
|
||||
return this.fallback.find(context, predicate);
|
||||
}
|
||||
|
||||
tryGet <T>(context: DataContextGetter<T>): T | undefined {
|
||||
|
||||
@@ -10,8 +10,10 @@ import type { DataContextGetter } from './DataContextGetter';
|
||||
|
||||
export interface IDataContextProvider {
|
||||
fallback?: IDataContextProvider;
|
||||
readonly map: Map<DataContextGetter<any>, any>;
|
||||
has: (context: DataContextGetter<any>) => boolean;
|
||||
get: <T>(context: DataContextGetter<T>) => T;
|
||||
find: <T>(context: DataContextGetter<T>, value: T) => boolean;
|
||||
find: <T>(context: DataContextGetter<T>, predicate: (item: T) => boolean) => T | undefined;
|
||||
hasValue: <T>(context: DataContextGetter<T>, value: T) => boolean;
|
||||
tryGet: <T>(context: DataContextGetter<T>) => T | undefined;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
/*
|
||||
* 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 { createDataContext } from '../DataContext/createDataContext';
|
||||
import type { IMenuSubMenuItem } from './MenuItem/IMenuSubMenuItem';
|
||||
|
||||
export const DATA_CONTEXT_SUBMENU_ITEM = createDataContext<IMenuSubMenuItem>('submenu-item');
|
||||
@@ -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 type { ComponentStyle } from '@cloudbeaver/core-theming';
|
||||
|
||||
import type { IMenuItem } from './IMenuItem';
|
||||
|
||||
interface IMenuCustomItemCommonProperties<TExtraProps = unknown> {
|
||||
hidden?: boolean;
|
||||
disabled?: boolean;
|
||||
getExtraProps?: () => TExtraProps;
|
||||
}
|
||||
|
||||
export type ICustomMenuItemComponent<TExtraProps = unknown> = React.FC<ICustomMenuItemProps<TExtraProps> & TExtraProps>;
|
||||
|
||||
export interface ICustomMenuItemProps<TExtraProps = unknown> {
|
||||
item: IMenuCustomItem<TExtraProps>;
|
||||
style?: ComponentStyle;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export interface IMenuCustomItemOptions<TExtraProps = unknown>
|
||||
extends IMenuCustomItemCommonProperties<TExtraProps> {
|
||||
id: string;
|
||||
getComponent: () => ICustomMenuItemComponent<TExtraProps>;
|
||||
}
|
||||
|
||||
export interface IMenuCustomItem<TExtraProps = unknown>
|
||||
extends IMenuItem, IMenuCustomItemCommonProperties<TExtraProps> {
|
||||
getComponent?: () => ICustomMenuItemComponent<TExtraProps>;
|
||||
}
|
||||
@@ -15,5 +15,8 @@ export interface IMenuSubMenuEvents extends IMenuItemEvents {
|
||||
|
||||
export interface IMenuSubMenuItem extends IMenuItem {
|
||||
menu: IMenu;
|
||||
label?: string;
|
||||
icon?: string;
|
||||
tooltip?: string;
|
||||
events?: IMenuSubMenuEvents;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
/*
|
||||
* 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 type { ICustomMenuItemComponent, IMenuCustomItem, IMenuCustomItemOptions } from './IMenuCustomItem';
|
||||
import type { IMenuItemEvents } from './IMenuItem';
|
||||
import { MenuItem } from './MenuItem';
|
||||
|
||||
interface IMenuCustomItemPropertyGetters<TExtraProps = unknown> {
|
||||
getExtraProps?: () => TExtraProps;
|
||||
isDisabled?: () => boolean;
|
||||
}
|
||||
|
||||
export class MenuCustomItem<TExtraProps = unknown> extends MenuItem implements IMenuCustomItem<TExtraProps> {
|
||||
private readonly isDisabled?: () => boolean;
|
||||
|
||||
readonly hidden?: boolean;
|
||||
readonly getComponent: () => ICustomMenuItemComponent<TExtraProps>;
|
||||
readonly getExtraProps?: () => TExtraProps;
|
||||
|
||||
get disabled(): boolean {
|
||||
return this.isDisabled?.() ?? false;
|
||||
}
|
||||
|
||||
constructor(
|
||||
options: IMenuCustomItemOptions<TExtraProps>,
|
||||
events?: IMenuItemEvents,
|
||||
getters?: IMenuCustomItemPropertyGetters<TExtraProps>,
|
||||
) {
|
||||
super(options.id, events);
|
||||
this.getComponent = options.getComponent;
|
||||
this.isDisabled = getters?.isDisabled;
|
||||
this.getExtraProps = getters?.getExtraProps;
|
||||
}
|
||||
}
|
||||
@@ -12,15 +12,24 @@ import { MenuItem } from './MenuItem';
|
||||
|
||||
export class MenuSubMenuItem extends MenuItem implements IMenuSubMenuItem {
|
||||
readonly menu: IMenu;
|
||||
readonly label?: string;
|
||||
readonly icon?: string;
|
||||
readonly tooltip?: string;
|
||||
readonly events?: IMenuSubMenuEvents;
|
||||
readonly hidden: boolean;
|
||||
|
||||
constructor(
|
||||
menu: IMenu,
|
||||
label?: string,
|
||||
icon?: string,
|
||||
tooltip?: string,
|
||||
events?: IMenuSubMenuEvents
|
||||
) {
|
||||
super(menu.id);
|
||||
this.menu = menu;
|
||||
this.label = label;
|
||||
this.icon = icon;
|
||||
this.tooltip = tooltip;
|
||||
this.hidden = false;
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ function filterApplicable(contexts: IDataContextProvider): (creator: IMenuItemsC
|
||||
}
|
||||
|
||||
if (creator.menus) {
|
||||
const applicable = creator.menus.some(menu => contexts.find(DATA_CONTEXT_MENU, menu));
|
||||
const applicable = creator.menus.some(menu => contexts.hasValue(DATA_CONTEXT_MENU, menu));
|
||||
|
||||
if (!applicable) {
|
||||
return false;
|
||||
|
||||
@@ -30,10 +30,12 @@ export * from './DataContext/useDataContext';
|
||||
export * from './DataContext/useDynamicDataContext';
|
||||
export * from './Menu/MenuItem/IMenuActionItem';
|
||||
export * from './Menu/MenuItem/IMenuBaseItem';
|
||||
export * from './Menu/MenuItem/IMenuCustomItem';
|
||||
export * from './Menu/MenuItem/IMenuItem';
|
||||
export * from './Menu/MenuItem/IMenuSubMenuItem';
|
||||
export * from './Menu/MenuItem/MenuActionItem';
|
||||
export * from './Menu/MenuItem/MenuBaseItem';
|
||||
export * from './Menu/MenuItem/MenuCustomItem';
|
||||
export * from './Menu/MenuItem/MenuGroupItem';
|
||||
export * from './Menu/MenuItem/MenuLazyItem';
|
||||
export * from './Menu/MenuItem/MenuSeparatorItem';
|
||||
@@ -42,6 +44,7 @@ export * from './Menu/createMenu';
|
||||
export * from './Menu/DATA_CONTEXT_MENU_LOCAL';
|
||||
export * from './Menu/DATA_CONTEXT_MENU_NESTED';
|
||||
export * from './Menu/DATA_CONTEXT_MENU';
|
||||
export * from './Menu/DATA_CONTEXT_SUBMENU_ITEM';
|
||||
export * from './Menu/IMenu';
|
||||
export * from './Menu/menuExtractActions';
|
||||
export * from './Menu/MenuService';
|
||||
|
||||
@@ -14,7 +14,7 @@ import { TemplateConnectionsResource } from './TemplateConnectionsResource';
|
||||
|
||||
export const connectionTemplate: PluginManifest = {
|
||||
info: {
|
||||
name: 'Connections plugin',
|
||||
name: 'Template Connections plugin',
|
||||
},
|
||||
|
||||
providers: [
|
||||
|
||||
+3
-3
@@ -11,10 +11,10 @@ import { observer } from 'mobx-react-lite';
|
||||
import { useMemo, useState } from 'react';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { TabContainerPanelComponent, useTab } from '@cloudbeaver/core-ui';
|
||||
import { BASE_CONTAINERS_STYLES, ColoredContainer, Group, IProperty, Loader, PropertiesTable, useMapResource } from '@cloudbeaver/core-blocks';
|
||||
import { DBDriverResource } from '@cloudbeaver/core-connections';
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
import { TabContainerPanelComponent, useTab } from '@cloudbeaver/core-ui';
|
||||
import { uuid } from '@cloudbeaver/core-utils';
|
||||
|
||||
import type { IConnectionFormProps } from '../IConnectionFormProps';
|
||||
@@ -66,10 +66,10 @@ export const DriverProperties: TabContainerPanelComponent<IConnectionFormProps>
|
||||
DBDriverResource,
|
||||
{ key: (selected && formState.config.driverId) || null, includes: ['includeDriverProperties'] },
|
||||
{
|
||||
isActive: () => selected && !!formState.config.driverId,
|
||||
active: selected && !!formState.config.driverId,
|
||||
onData: driver => {
|
||||
for (const key of Object.keys(formState.config.properties)) {
|
||||
if (driver.driverProperties?.some(property => property.id === key)
|
||||
if (driver.driverProperties.some(property => property.id === key)
|
||||
|| state.propertiesList.some(property => property.key === key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@ export class TableHeaderService extends Bootstrap {
|
||||
this.actionService.addHandler({
|
||||
id: 'table-header-menu-base-handler',
|
||||
isActionApplicable(context, action) {
|
||||
const menu = context.find(DATA_CONTEXT_MENU, DATA_VIEWER_DATA_MODEL_TOOLS_MENU);
|
||||
const menu = context.hasValue(DATA_CONTEXT_MENU, DATA_VIEWER_DATA_MODEL_TOOLS_MENU);
|
||||
const model = context.tryGet(DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL);
|
||||
const resultIndex = context.tryGet(DATA_CONTEXT_DATA_VIEWER_DATABASE_DATA_MODEL_RESULT_INDEX);
|
||||
|
||||
|
||||
+2
-2
@@ -42,7 +42,7 @@ export class ConnectionSchemaManagerBootstrap extends Bootstrap {
|
||||
|
||||
this.menuService.setHandler({
|
||||
id: 'connection-selector-base',
|
||||
isApplicable: context => context.find(DATA_CONTEXT_MENU, MENU_CONNECTION_SELECTOR),
|
||||
isApplicable: context => context.hasValue(DATA_CONTEXT_MENU, MENU_CONNECTION_SELECTOR),
|
||||
isLoading: () => this.connectionSelectorLoading,
|
||||
isDisabled: () => (
|
||||
!this.connectionSchemaManagerService.isConnectionChangeable
|
||||
@@ -104,7 +104,7 @@ export class ConnectionSchemaManagerBootstrap extends Bootstrap {
|
||||
|
||||
this.menuService.setHandler({
|
||||
id: 'connection-data-container-selector-base',
|
||||
isApplicable: context => context.find(DATA_CONTEXT_MENU, MENU_CONNECTION_DATA_CONTAINER_SELECTOR),
|
||||
isApplicable: context => context.hasValue(DATA_CONTEXT_MENU, MENU_CONNECTION_DATA_CONTAINER_SELECTOR),
|
||||
isDisabled: () => (
|
||||
!this.connectionSchemaManagerService.currentConnection?.connected
|
||||
|| this.connectionSelectorLoading
|
||||
|
||||
+8
-6
@@ -27,15 +27,17 @@ import { MENU_CONNECTION_SELECTOR } from '../MENU_CONNECTION_SELECTOR';
|
||||
|
||||
const menuStyles = css`
|
||||
Menu {
|
||||
max-height: 400px;
|
||||
overflow: auto;
|
||||
& menu-box {
|
||||
max-height: 80vh;
|
||||
overflow: auto;
|
||||
}
|
||||
& menu-panel-item {
|
||||
overflow-x: hidden;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
& menu-item-text {
|
||||
max-width: 400px;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
max-width: 400px;
|
||||
overflow-x: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -31,7 +31,7 @@ export class DDLViewerFooterService {
|
||||
this.actionsService.addHandler({
|
||||
id: 'ddl-viewer-footer-base-handler',
|
||||
isActionApplicable(context, action) {
|
||||
const menu = context.find(DATA_CONTEXT_MENU, MENU_DDL_VIEWER_FOOTER);
|
||||
const menu = context.hasValue(DATA_CONTEXT_MENU, MENU_DDL_VIEWER_FOOTER);
|
||||
const node = context.tryGet(DATA_CONTEXT_DDL_VIEWER_NODE);
|
||||
|
||||
if (!menu || !node) {
|
||||
|
||||
@@ -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*
|
||||
@@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "@cloudbeaver/plugin-devtools",
|
||||
"sideEffects": false,
|
||||
"version": "0.1.0",
|
||||
"description": "",
|
||||
"license": "Apache-2.0",
|
||||
"main": "lib/index.js",
|
||||
"module": "lib/index.js",
|
||||
"types": "src/index.ts",
|
||||
"scripts": {
|
||||
"prebuild": "rimraf lib/*",
|
||||
"build": "webpack --config ../../configs/webpack.plugin.config.js",
|
||||
"postbuild": "tsc",
|
||||
"lint": "eslint ./src/ --ext .ts,.tsx",
|
||||
"lint-fix": "eslint ./src/ --ext .ts,.tsx --fix"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@cloudbeaver/core-administration": "~0.1.0",
|
||||
"@cloudbeaver/core-app": "~0.1.0",
|
||||
"@cloudbeaver/core-authentication": "~0.1.0",
|
||||
"@cloudbeaver/core-blocks": "~0.1.0",
|
||||
"@cloudbeaver/core-connections": "~0.1.0",
|
||||
"@cloudbeaver/core-di": "~0.1.0",
|
||||
"@cloudbeaver/core-dialogs": "~0.1.0",
|
||||
"@cloudbeaver/core-events": "~0.1.0",
|
||||
"@cloudbeaver/core-executor": "~0.1.0",
|
||||
"@cloudbeaver/core-extensions": "~0.1.0",
|
||||
"@cloudbeaver/core-localization": "~0.1.0",
|
||||
"@cloudbeaver/core-notifications": "~0.1.0",
|
||||
"@cloudbeaver/core-plugin": "~0.1.0",
|
||||
"@cloudbeaver/core-product": "~0.1.0",
|
||||
"@cloudbeaver/core-root": "~0.1.0",
|
||||
"@cloudbeaver/core-routing": "~0.1.0",
|
||||
"@cloudbeaver/core-sdk": "~0.1.0",
|
||||
"@cloudbeaver/core-settings": "~0.1.0",
|
||||
"@cloudbeaver/core-theming": "~0.1.0",
|
||||
"@cloudbeaver/core-ui": "~0.1.0",
|
||||
"@cloudbeaver/core-utils": "~0.1.0",
|
||||
"@cloudbeaver/core-view": "~0.1.0",
|
||||
"@cloudbeaver/plugin-top-app-bar": "~0.1.0",
|
||||
"@cloudbeaver/plugin-user-profile": "~0.1.0",
|
||||
"mobx": "~6.x.x",
|
||||
"mobx-react-lite": "~3.x.x",
|
||||
"react": "~18.x.x",
|
||||
"reshadow": "~0.x.x"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2021 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { createDataContext } from '@cloudbeaver/core-view';
|
||||
|
||||
export const DATA_CONTEXT_MENU_SEARCH = createDataContext<string>('menu-local', '');
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* 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 type { IContextMenuItemProps } from '@cloudbeaver/core-ui';
|
||||
import { MenuCustomItem } from '@cloudbeaver/core-view';
|
||||
|
||||
import { SearchResourceMenuItemComponent } from './SearchResourceMenuItemComponent';
|
||||
|
||||
export class SearchResourceMenuItem extends MenuCustomItem<IContextMenuItemProps> {
|
||||
constructor() {
|
||||
super({
|
||||
id: 'search-resource',
|
||||
getComponent: () => SearchResourceMenuItemComponent,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 { observer } from 'mobx-react-lite';
|
||||
import styled, { css } from 'reshadow';
|
||||
|
||||
import { useStyles } from '@cloudbeaver/core-theming';
|
||||
import type { IContextMenuItemProps } from '@cloudbeaver/core-ui';
|
||||
import type { ICustomMenuItemComponent } from '@cloudbeaver/core-view';
|
||||
|
||||
import { DATA_CONTEXT_MENU_SEARCH } from './DATA_CONTEXT_MENU_SEARCH';
|
||||
|
||||
const styles = css`
|
||||
search-box {
|
||||
padding: 8px 12px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SearchResourceMenuItemComponent: ICustomMenuItemComponent<IContextMenuItemProps> = observer(function SearchResourceMenuItemComponent({
|
||||
item,
|
||||
menu,
|
||||
onClick,
|
||||
menuData,
|
||||
className,
|
||||
style,
|
||||
}) {
|
||||
const value = menuData.context.tryGet(DATA_CONTEXT_MENU_SEARCH) ?? '';
|
||||
function handleChange(value: string) {
|
||||
menuData.context.set(DATA_CONTEXT_MENU_SEARCH, value);
|
||||
}
|
||||
|
||||
return styled(useStyles(style, styles))(
|
||||
<search-box dir="ltr" className={className}>
|
||||
<input
|
||||
name='search'
|
||||
placeholder='Search for resource...'
|
||||
value={value}
|
||||
autoComplete="off"
|
||||
onChange={event => handleChange(event.target.value)}
|
||||
/>
|
||||
</search-box>
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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 { makeObservable, observable } from 'mobx';
|
||||
|
||||
import { injectable } from '@cloudbeaver/core-di';
|
||||
import { LocalStorageSaveService } from '@cloudbeaver/core-settings';
|
||||
|
||||
interface IDevToolsSettings {
|
||||
enabled: boolean;
|
||||
}
|
||||
|
||||
const DEVTOOLS = 'devtools';
|
||||
|
||||
@injectable()
|
||||
export class DevToolsService {
|
||||
get isEnabled(): boolean {
|
||||
return this.settings.enabled;
|
||||
}
|
||||
|
||||
private readonly settings: IDevToolsSettings;
|
||||
|
||||
constructor(
|
||||
private readonly autoSaveService: LocalStorageSaveService,
|
||||
) {
|
||||
this.settings = {
|
||||
enabled: false,
|
||||
};
|
||||
|
||||
makeObservable<this, 'settings'>(this, {
|
||||
settings: observable,
|
||||
});
|
||||
this.autoSaveService.withAutoSave(this.settings, DEVTOOLS);
|
||||
}
|
||||
|
||||
switch() {
|
||||
this.settings.enabled = !this.settings.enabled;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
/*
|
||||
* 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 { EAdminPermission } from '@cloudbeaver/core-administration';
|
||||
import { App, Bootstrap, DIService, injectable, IServiceConstructor } from '@cloudbeaver/core-di';
|
||||
import { PermissionsService, ServerConfigResource } from '@cloudbeaver/core-root';
|
||||
import { CachedResource } from '@cloudbeaver/core-sdk';
|
||||
import { ActionService, DATA_CONTEXT_MENU, DATA_CONTEXT_SUBMENU_ITEM, MenuBaseItem, MenuService } from '@cloudbeaver/core-view';
|
||||
import { TOP_NAV_BAR_SETTINGS_MENU } from '@cloudbeaver/plugin-settings-menu';
|
||||
import { MainMenuService } from '@cloudbeaver/plugin-top-app-bar';
|
||||
import { MENU_USER_PROFILE } from '@cloudbeaver/plugin-user-profile';
|
||||
|
||||
import { ACTION_DEVTOOLS } from './actions/ACTION_DEVTOOLS';
|
||||
import { DATA_CONTEXT_MENU_SEARCH } from './ContextMenu/DATA_CONTEXT_MENU_SEARCH';
|
||||
import { SearchResourceMenuItem } from './ContextMenu/SearchResourceMenuItem';
|
||||
import { DevToolsService } from './DevToolsService';
|
||||
import { MENU_DEVTOOLS } from './menu/MENU_DEVTOOLS';
|
||||
import { MENU_PLUGIN } from './menu/MENU_PLUGIN';
|
||||
import { MENU_PLUGINS } from './menu/MENU_PLUGINS';
|
||||
import { MENU_RESOURCE } from './menu/MENU_RESOURCE';
|
||||
import { MENU_RESOURCES } from './menu/MENU_RESOURCES';
|
||||
import { PluginSubMenuItem } from './menu/PluginSubMenuItem';
|
||||
import { ResourceSubMenuItem } from './menu/ResourceSubMenuItem';
|
||||
|
||||
@injectable()
|
||||
export class PluginBootstrap extends Bootstrap {
|
||||
constructor(
|
||||
private readonly app: App,
|
||||
private readonly diService: DIService,
|
||||
private readonly menuService: MenuService,
|
||||
private readonly actionService: ActionService,
|
||||
private readonly mainMenuService: MainMenuService,
|
||||
private readonly devToolsService: DevToolsService,
|
||||
private readonly permissionsService: PermissionsService,
|
||||
private readonly serverConfigResource: ServerConfigResource
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
register(): void {
|
||||
this.menuService.addCreator({
|
||||
isApplicable: context => {
|
||||
if (!this.permissionsService.has(EAdminPermission.admin)) {
|
||||
return false;
|
||||
}
|
||||
return context.get(DATA_CONTEXT_MENU) === TOP_NAV_BAR_SETTINGS_MENU;
|
||||
},
|
||||
getItems: (context, items) => [
|
||||
ACTION_DEVTOOLS,
|
||||
...items,
|
||||
],
|
||||
});
|
||||
|
||||
|
||||
this.actionService.addHandler({
|
||||
id: 'devtools',
|
||||
isActionApplicable: (context, action) => [
|
||||
ACTION_DEVTOOLS,
|
||||
].includes(action),
|
||||
isChecked: (context, action) => {
|
||||
|
||||
switch (action) {
|
||||
case ACTION_DEVTOOLS: {
|
||||
return this.devToolsService.isEnabled;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
handler: (context, action) => {
|
||||
switch (action) {
|
||||
case ACTION_DEVTOOLS: {
|
||||
this.devToolsService.switch();
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
this.menuService.addCreator({
|
||||
isApplicable: context => {
|
||||
if (!this.devToolsService.isEnabled) {
|
||||
return false;
|
||||
}
|
||||
return context.get(DATA_CONTEXT_MENU) === MENU_USER_PROFILE;
|
||||
},
|
||||
getItems: (context, items) => [
|
||||
MENU_DEVTOOLS,
|
||||
...items,
|
||||
],
|
||||
});
|
||||
|
||||
this.menuService.addCreator({
|
||||
isApplicable: context => context.get(DATA_CONTEXT_MENU) === MENU_DEVTOOLS,
|
||||
getItems: (context, items) => {
|
||||
const search = context.tryGet(DATA_CONTEXT_MENU_SEARCH);
|
||||
|
||||
if (search) {
|
||||
|
||||
return [
|
||||
new SearchResourceMenuItem(),
|
||||
...this.getResources(this.app
|
||||
.getServices()
|
||||
.filter(service => service.name.toLocaleLowerCase().includes(search.toLocaleLowerCase()))),
|
||||
];
|
||||
}
|
||||
|
||||
return [
|
||||
new SearchResourceMenuItem(),
|
||||
MENU_PLUGINS,
|
||||
...items,
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
this.menuService.addCreator({
|
||||
isApplicable: context => context.get(DATA_CONTEXT_MENU) === MENU_PLUGINS,
|
||||
getItems: (context, items) => [
|
||||
...this.app
|
||||
.getPlugins()
|
||||
.sort((a, b) => a.info.name.localeCompare(b.info.name))
|
||||
.map(plugin => new PluginSubMenuItem(plugin)),
|
||||
...items,
|
||||
],
|
||||
});
|
||||
|
||||
this.menuService.addCreator({
|
||||
isApplicable: context => {
|
||||
if (context.get(DATA_CONTEXT_MENU) !== MENU_PLUGIN) {
|
||||
return false;
|
||||
}
|
||||
const item = context.tryGet(DATA_CONTEXT_SUBMENU_ITEM);
|
||||
|
||||
if (item instanceof PluginSubMenuItem) {
|
||||
return item.plugin.providers.some(provider => provider.prototype instanceof CachedResource);
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
getItems: (_, items) => [
|
||||
MENU_RESOURCES,
|
||||
...items,
|
||||
],
|
||||
});
|
||||
|
||||
this.menuService.addCreator({
|
||||
isApplicable: context => (
|
||||
context.get(DATA_CONTEXT_MENU) === MENU_RESOURCES
|
||||
&& context.has(DATA_CONTEXT_SUBMENU_ITEM)
|
||||
),
|
||||
getItems: (context, items) => {
|
||||
const item = context.find(DATA_CONTEXT_SUBMENU_ITEM, item => item instanceof PluginSubMenuItem);
|
||||
|
||||
if (!item) {
|
||||
return items;
|
||||
}
|
||||
|
||||
const plugin = this.app
|
||||
.getPlugins()
|
||||
.find(plugin => plugin.info.name === item.id);
|
||||
|
||||
if (!plugin) {
|
||||
return items;
|
||||
}
|
||||
|
||||
return [
|
||||
...this.getResources(plugin.providers),
|
||||
...items,
|
||||
];
|
||||
},
|
||||
});
|
||||
|
||||
this.menuService.addCreator({
|
||||
isApplicable: context => (
|
||||
context.get(DATA_CONTEXT_MENU) === MENU_RESOURCE
|
||||
&& context.get(DATA_CONTEXT_SUBMENU_ITEM) instanceof ResourceSubMenuItem
|
||||
),
|
||||
getItems: (context, items) => {
|
||||
const item = context.get(DATA_CONTEXT_SUBMENU_ITEM) as ResourceSubMenuItem;
|
||||
|
||||
return [
|
||||
new MenuBaseItem(
|
||||
{
|
||||
id: 'markOutdated',
|
||||
label: 'Mark outdated',
|
||||
tooltip: 'Outdate resource',
|
||||
},
|
||||
{
|
||||
onSelect: () => {
|
||||
const instance = this.diService.serviceInjector.getServiceByClass<CachedResource<any, any>>(
|
||||
item.resource
|
||||
);
|
||||
instance.markOutdated(undefined);
|
||||
},
|
||||
}
|
||||
),
|
||||
...items,
|
||||
];
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
load(): void | Promise<void> {
|
||||
}
|
||||
|
||||
private getResources(providers: IServiceConstructor<any>[]): ResourceSubMenuItem[] {
|
||||
return providers
|
||||
.filter(service => service.prototype instanceof CachedResource)
|
||||
.sort((a, b) => a.name.localeCompare(b.name))
|
||||
.map(resource => new ResourceSubMenuItem(resource));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { createAction } from '@cloudbeaver/core-view';
|
||||
|
||||
export const ACTION_DEVTOOLS = createAction('devtools', {
|
||||
type: 'checkbox',
|
||||
label: 'DevTools',
|
||||
tooltip: 'Show DevTools menu',
|
||||
});
|
||||
@@ -0,0 +1,3 @@
|
||||
import { devToolsPlugin } from './manifest';
|
||||
|
||||
export default devToolsPlugin;
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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 type { PluginManifest } from '@cloudbeaver/core-di';
|
||||
|
||||
import { DevToolsService } from './DevToolsService';
|
||||
import { PluginBootstrap } from './PluginBootstrap';
|
||||
|
||||
export const devToolsPlugin: PluginManifest = {
|
||||
info: {
|
||||
name: 'DevTools plugin',
|
||||
},
|
||||
providers: [
|
||||
PluginBootstrap,
|
||||
DevToolsService,
|
||||
],
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { createMenu } from '@cloudbeaver/core-view';
|
||||
|
||||
export const MENU_DEVTOOLS = createMenu(
|
||||
'devtools',
|
||||
'DevTools'
|
||||
);
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { createMenu } from '@cloudbeaver/core-view';
|
||||
|
||||
export const MENU_PLUGIN = createMenu(
|
||||
'plugin',
|
||||
'Plugin'
|
||||
);
|
||||
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { createMenu } from '@cloudbeaver/core-view';
|
||||
|
||||
export const MENU_PLUGINS = createMenu(
|
||||
'plugins',
|
||||
'Plugins'
|
||||
);
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { createMenu } from '@cloudbeaver/core-view';
|
||||
|
||||
export const MENU_RESOURCE = createMenu(
|
||||
'resource',
|
||||
'Resource',
|
||||
undefined,
|
||||
'Resource actions'
|
||||
);
|
||||
@@ -0,0 +1,16 @@
|
||||
/*
|
||||
* CloudBeaver - Cloud Database Manager
|
||||
* Copyright (C) 2020-2022 DBeaver Corp and others
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0.
|
||||
* you may not use this file except in compliance with the License.
|
||||
*/
|
||||
|
||||
import { createMenu } from '@cloudbeaver/core-view';
|
||||
|
||||
export const MENU_RESOURCES = createMenu(
|
||||
'resources-list',
|
||||
'Resources',
|
||||
undefined,
|
||||
'List of registered resources'
|
||||
);
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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 type { PluginManifest } from '@cloudbeaver/core-di';
|
||||
import { MenuSubMenuItem } from '@cloudbeaver/core-view';
|
||||
|
||||
import { MENU_PLUGIN } from './MENU_PLUGIN';
|
||||
|
||||
export class PluginSubMenuItem extends MenuSubMenuItem {
|
||||
readonly plugin: PluginManifest;
|
||||
|
||||
constructor(plugin: PluginManifest) {
|
||||
super(MENU_PLUGIN, plugin.info.name);
|
||||
|
||||
this.plugin = plugin;
|
||||
|
||||
Object.assign(this, {
|
||||
id: plugin.info.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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 type { IServiceConstructor } from '@cloudbeaver/core-di';
|
||||
import { MenuSubMenuItem } from '@cloudbeaver/core-view';
|
||||
|
||||
import { MENU_RESOURCE } from './MENU_RESOURCE';
|
||||
|
||||
export class ResourceSubMenuItem extends MenuSubMenuItem {
|
||||
readonly resource: IServiceConstructor<any>;
|
||||
|
||||
constructor(resource: IServiceConstructor<any>) {
|
||||
super(MENU_RESOURCE, resource.name);
|
||||
|
||||
this.resource = resource;
|
||||
|
||||
Object.assign(this, {
|
||||
id: resource.name,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "src",
|
||||
"outDir": "lib",
|
||||
"composite": true
|
||||
},
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"exclude": [
|
||||
"**/node_modules",
|
||||
"**/.*/"
|
||||
],
|
||||
"references": [{ "path": "../*" }]
|
||||
}
|
||||
@@ -14,7 +14,7 @@ import { SqlEditorTabService } from './SqlEditorTabService';
|
||||
|
||||
export const sqlEditorTabPluginManifest: PluginManifest = {
|
||||
info: {
|
||||
name: 'Sql Editor Plugin',
|
||||
name: 'Sql Editor Navigation Tab Plugin',
|
||||
},
|
||||
|
||||
providers: [
|
||||
|
||||
@@ -58,7 +58,7 @@ export class SqlGeneratorsBootstrap extends Bootstrap {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !context.find(DATA_CONTEXT_MENU, MENU_SQL_GENERATORS) && !context.has(DATA_CONTEXT_MENU_NESTED);
|
||||
return !context.hasValue(DATA_CONTEXT_MENU, MENU_SQL_GENERATORS) && !context.has(DATA_CONTEXT_MENU_NESTED);
|
||||
},
|
||||
getItems: (context, items) => [
|
||||
...items,
|
||||
|
||||
@@ -14,6 +14,7 @@ import spreadsheetNew from '@cloudbeaver/plugin-data-spreadsheet-new';
|
||||
import dataViewer from '@cloudbeaver/plugin-data-viewer';
|
||||
import datasourceContextSwitch from '@cloudbeaver/plugin-datasource-context-switch';
|
||||
import ddlViewer from '@cloudbeaver/plugin-ddl-viewer';
|
||||
import devTools from '@cloudbeaver/plugin-devtools';
|
||||
import gisViewer from '@cloudbeaver/plugin-gis-viewer';
|
||||
import help from '@cloudbeaver/plugin-help';
|
||||
import localization from '@cloudbeaver/plugin-localization';
|
||||
@@ -42,6 +43,7 @@ import versionUpdate from '@cloudbeaver/plugin-version-update-administration';
|
||||
import { defaultProductManifest } from './manifest';
|
||||
|
||||
const PLUGINS: PluginManifest[] = [
|
||||
devTools,
|
||||
administration,
|
||||
spreadsheetNew,
|
||||
authentication,
|
||||
|
||||
Reference in New Issue
Block a user