CB-3214 suspense (#1527)

* CB-3214 feat: enable strict mode for development

* CB-3214 refactor: loaders
This commit is contained in:
Alexey
2023-03-07 18:14:30 +03:00
committed by GitHub
parent 1f553fe315
commit b5bef7f226
37 changed files with 469 additions and 361 deletions
@@ -9,7 +9,7 @@
import { observer } from 'mobx-react-lite';
import styled, { css } from 'reshadow';
import { splitStyles, Split, ResizerControls, Pane, useSplitUserState, useStyles, Loader } from '@cloudbeaver/core-blocks';
import { splitStyles, Split, ResizerControls, Pane, useSplitUserState, useStyles, Loader, getComputed } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { LeftBarPanelService, SideBarPanel, SideBarPanelService } from '@cloudbeaver/core-ui';
@@ -38,11 +38,8 @@ export const Main = observer(function Main() {
const splitMainState = useSplitUserState('main');
const splitRightState = useSplitUserState('main-right');
const activeSideBars = sideBarPanelService.tabsContainer.getDisplayed();
const activeLeftBars = leftBarPanelService.tabsContainer.getDisplayed();
const sideBarDisabled = activeSideBars.length === 0;
const leftBarDisabled = activeLeftBars.length === 0;
const sideBarDisabled = getComputed(() => sideBarPanelService.tabsContainer.getDisplayed().length === 0);
const leftBarDisabled = getComputed(() => leftBarPanelService.tabsContainer.getDisplayed().length === 0);
return styled(styles)(
<Loader suspense>
@@ -242,7 +242,10 @@ export const Loader = observer<Props>(function Loader({
return renderWrappedChildren();
}
if (exception && !loading && !hideException) {
if (exception && !loading) {
if (hideException) {
return null;
}
return styled(style)(
<ExceptionMessage
exception={exception}
@@ -8,7 +8,6 @@
import { observer } from 'mobx-react-lite';
import { Loader } from '../Loader/Loader';
import type { PlaceholderContainer, PlaceholderElement } from './PlaceholderContainer';
type Props<T extends Record<string, any>> = T & {
@@ -35,8 +34,8 @@ export const Placeholder = observer(function Placeholder<T extends Record<string
}
return (
<Loader suspense>
<>
{elements.map(({ id, component: Component }) => <Component key={id} {...(rest as any as T)} />)}
</Loader>
</>
);
});
@@ -183,6 +183,20 @@ export const PropertyItem = observer<Props>(function PropertyItem({
>
{propertyValue}
</ShadowInput>
{edited && !isDeletable && (
<property-remove title={translate('core_blocks_properties_table_item_reset')}>
<button type="button" onClick={handleRevert}>
<IconOrImage icon="/icons/data_revert_all_sm.svg" viewBox="0 0 16 16" />
</button>
</property-remove>
)}
{isDeletable && (
<property-remove title={translate('core_blocks_properties_table_item_remove')}>
<button type="button" onClick={handleRemove}>
<Icon name="reject" viewBox="0 0 11 11" />
</button>
</property-remove>
)}
{(!readOnly && property.validValues && property.validValues.length > 0) && (
<property-select>
<PropertyValueSelector
@@ -197,20 +211,6 @@ export const PropertyItem = observer<Props>(function PropertyItem({
</property-select>
)}
</property-value>
{edited && !isDeletable && (
<property-remove title={translate('core_blocks_properties_table_item_reset')}>
<button type="button" onClick={handleRevert}>
<IconOrImage icon="/icons/data_revert_all_sm.svg" viewBox="0 0 16 16" />
</button>
</property-remove>
)}
{isDeletable && (
<property-remove title={translate('core_blocks_properties_table_item_remove')}>
<button type="button" onClick={handleRemove}>
<Icon name="reject" viewBox="0 0 11 11" />
</button>
</property-remove>
)}
</property-item>
);
});
@@ -6,7 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { computed, observable, toJS, untracked, when } from 'mobx';
import { action, computed, observable, reaction, toJS, untracked, when } from 'mobx';
import { useEffect, useContext, useState } from 'react';
import { IServiceConstructor, useService } from '@cloudbeaver/core-di';
@@ -271,6 +271,7 @@ export function useResource<
}
},
}), {
load: action,
resourceException: computed,
exception: observable.ref,
loadingPromise: observable.ref,
@@ -323,9 +324,9 @@ export function useResource<
}
// React Suspense block
if (refObj.loadingPromise) {
throw refObj.loadingPromise;
}
// if (refObj.loadingPromise) {
// throw refObj.loadingPromise;
// }
if (this.loading) {
throw this.resource.waitLoad();
@@ -395,30 +396,24 @@ export function useResource<
}, { preloaded });
useEffect(() => {
let prevData: any;
const disposeDataUpdate = when(
() => {
try {
if (result.isError()) {
return false;
}
result.data;
return true;
} catch {
return false;
}
const disposeDataUpdate = reaction(
() => result.data,
(data, prev) => {
actions?.onData?.(data as any, resource, prev as any);
},
() => {
const newData = result.data;
refObj.actions?.onData?.(newData, resource, prevData);
prevData = newData;
{
onError: () => {},
fireImmediately: true,
}
);
const disposeErrorUpdate = when(
() => result.isError(),
() => {
const disposeErrorUpdate = reaction(
() => result.exception,
exception => {
if (!result.isError()) {
return;
}
if (propertiesRef.errorContext) {
const errors = Array.isArray(result.exception) ? result.exception : [result.exception];
const errors = Array.isArray(exception) ? exception : [exception];
for (const error of errors) {
if (error) {
@@ -426,7 +421,10 @@ export function useResource<
}
}
}
refObj.actions?.onError?.(result.exception);
actions?.onError?.(exception);
},
{
fireImmediately: true,
}
);
return () => {
@@ -8,6 +8,6 @@
import { computed } from 'mobx';
export function getComputed<T>(action: () => T): T {
return computed(action).get();
export function getComputed<T>(action: () => T, equals?: (a: T, b: T) => boolean): T {
return computed(action, { equals }).get();
}
@@ -6,7 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { StrictMode, Suspense } from 'react';
import { Suspense } from 'react';
import { createRoot, Root } from 'react-dom/client';
import styled from 'reshadow';
@@ -52,7 +52,6 @@ export function renderLayout(serviceInjector: IServiceInjector): IRender {
height: 100vh;
}
`(
// <StrictMode>
<AppContext app={serviceInjector}>
<ErrorBoundary root>
<Suspense fallback={<Loader />}>
@@ -60,7 +59,6 @@ export function renderLayout(serviceInjector: IServiceInjector): IRender {
</Suspense>
</ErrorBoundary>
</AppContext>
// </StrictMode>
));
},
renderError(exception?: any) {
+6 -2
View File
@@ -6,7 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { createContext } from 'react';
import { createContext, StrictMode } from 'react';
import type { IServiceInjector } from './IApp';
import { useAppLoadingScreen } from './useAppLoadingScreen';
@@ -19,5 +19,9 @@ interface Props {
export const AppContext: React.FC<React.PropsWithChildren<Props>> = function AppContext({ app, children }) {
useAppLoadingScreen();
return <appContext.Provider value={app}>{children}</appContext.Provider>;
return (
//<StrictMode> // problems with TabState when empty -> displayed state switch
<appContext.Provider value={app}>{children}</appContext.Provider>
//</StrictMode>
);
};
+22 -7
View File
@@ -13,7 +13,7 @@ import { useTabState } from 'reakit/Tab';
import { useExecutor, useObjectRef, useObservableRef } from '@cloudbeaver/core-blocks';
import { Executor, ExecutorInterrupter } from '@cloudbeaver/core-executor';
import { MetadataMap, MetadataValueGetter } from '@cloudbeaver/core-utils';
import { isNull, isUndefined, MetadataMap, MetadataValueGetter } from '@cloudbeaver/core-utils';
import type { ITabData, ITabsContainer } from './TabsContainer/ITabsContainer';
import { TabsContext, ITabsContext } from './TabsContext';
@@ -96,12 +96,21 @@ export const TabsState = observer(function TabsState<T = Record<string, any>>({
tabList,
});
if (currentTabId !== undefined && currentTabId !== null) {
if (
!isNull(currentTabId)
&& !isUndefined(currentTabId)
) {
state.selectedId = currentTabId;
dynamic.selectedId = currentTabId;
}
if (displayed.length > 0 && dynamic.selectedId && selectedId) {
if (
displayed.length > 0
&& !isNull(dynamic.selectedId)
&& !isUndefined(dynamic.selectedId)
&& !isNull(selectedId)
&& !isUndefined(selectedId)
) {
const tabExists = displayed.includes(dynamic.selectedId);
if (!tabExists) {
@@ -134,7 +143,10 @@ export const TabsState = observer(function TabsState<T = Record<string, any>>({
});
useEffect(() => {
if (currentTabId !== undefined && currentTabId !== null) {
if (
!isNull(currentTabId)
&& !isUndefined(currentTabId)
) {
return;
}
@@ -145,13 +157,16 @@ export const TabsState = observer(function TabsState<T = Record<string, any>>({
}, [currentTabId, state.selectedId]);
useEffect(() => {
if (state.selectedId) {
if (
!isNull(state.selectedId)
&& !isUndefined(state.selectedId)
) {
openExecutor.execute({
tabId: state.selectedId!,
tabId: state.selectedId,
props,
});
}
}, []);
}, [!isNull(state.selectedId) && !isUndefined(state.selectedId)]);
const value = useObservableRef<ITabsContext<T>>(() => ({
getTabInfo(tabId: string) {
@@ -11,7 +11,7 @@ import { useContext } from 'react';
import styled from 'reshadow';
import { AdministrationSettingsService } from '@cloudbeaver/core-administration';
import { BASE_CONTAINERS_STYLES, FormContext, GroupTitle, Loader, PlaceholderComponent, Switch, useResource, useTranslate, useStyles } from '@cloudbeaver/core-blocks';
import { BASE_CONTAINERS_STYLES, FormContext, GroupTitle, PlaceholderComponent, Switch, useResource, useTranslate, useStyles } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { FeaturesResource } from '@cloudbeaver/core-root';
import type { IConfigurationPlaceholderProps } from '@cloudbeaver/plugin-administration';
@@ -20,16 +20,21 @@ export const ServerConfigurationFeaturesForm: PlaceholderComponent<IConfiguratio
state: { serverConfig },
configurationWizard,
}) {
const administrationSettingsService = useService(AdministrationSettingsService);
const features = useResource(ServerConfigurationFeaturesForm, FeaturesResource, undefined);
const translate = useTranslate();
const styles = useStyles(BASE_CONTAINERS_STYLES);
const formContext = useContext(FormContext);
if (formContext === null) {
throw new Error('Form state should be provided');
}
const administrationSettingsService = useService(AdministrationSettingsService);
const features = useResource(
ServerConfigurationFeaturesForm,
FeaturesResource,
configurationWizard ? null : undefined
);
const translate = useTranslate();
const styles = useStyles(BASE_CONTAINERS_STYLES);
if (features.data.length === 0 || configurationWizard) {
return null;
}
@@ -37,26 +42,20 @@ export const ServerConfigurationFeaturesForm: PlaceholderComponent<IConfiguratio
return styled(styles)(
<>
<GroupTitle>{translate('administration_configuration_wizard_configuration_services_group')}</GroupTitle>
<Loader state={features} inline>
{() => styled(styles)(
<>
{features.data.map(feature => (
<Switch
key={feature.id}
value={feature.id}
name="enabledFeatures"
state={serverConfig}
description={feature.description}
disabled={administrationSettingsService.isBase(feature.id)}
mod={['primary']}
small
>
{feature.label}
</Switch>
))}
</>
)}
</Loader>
{features.data.map(feature => (
<Switch
key={feature.id}
value={feature.id}
name="enabledFeatures"
state={serverConfig}
description={feature.description}
disabled={administrationSettingsService.isBase(feature.id)}
mod={['primary']}
small
>
{feature.label}
</Switch>
))}
</>
);
});
@@ -36,6 +36,10 @@ const styles = css`
white-space: pre-wrap;
line-height: 2;
}
Loader {
height: 100%;
}
`;
export const ServerConfigurationPage: AdministrationItemContentComponent = observer(function ServerConfigurationPage({
@@ -118,27 +122,29 @@ export const ServerConfigurationPage: AdministrationItemContentComponent = obser
)}
<Loader state={service}>
{() => styled(style)(
<Container wrap gap grid medium>
<ServerConfigurationInfoForm state={service.state} />
<Group form gap>
<GroupTitle>{translate('administration_configuration_wizard_configuration_plugins')}</GroupTitle>
<ServerConfigurationConfigurationForm serverConfig={service.state.serverConfig} />
<ServerConfigurationNavigatorViewForm configs={service.state} />
<ServerConfigurationFeaturesForm state={service.state} configurationWizard={configurationWizard} />
<Loader suspense>
<Container wrap gap grid medium>
<ServerConfigurationInfoForm state={service.state} />
<Group form gap>
<GroupTitle>{translate('administration_configuration_wizard_configuration_plugins')}</GroupTitle>
<ServerConfigurationConfigurationForm serverConfig={service.state.serverConfig} />
<ServerConfigurationNavigatorViewForm configs={service.state} />
<ServerConfigurationFeaturesForm state={service.state} configurationWizard={configurationWizard} />
<Placeholder
container={service.pluginsContainer}
configurationWizard={configurationWizard}
state={service.state}
/>
</Group>
<Placeholder
container={service.pluginsContainer}
container={service.configurationContainer}
configurationWizard={configurationWizard}
state={service.state}
/>
</Group>
<Placeholder
container={service.configurationContainer}
configurationWizard={configurationWizard}
state={service.state}
/>
<ServerConfigurationSecurityForm serverConfig={service.state.serverConfig} />
<ServerConfigurationDriversForm serverConfig={service.state.serverConfig} />
</Container>
<ServerConfigurationSecurityForm serverConfig={service.state.serverConfig} />
<ServerConfigurationDriversForm serverConfig={service.state.serverConfig} />
</Container>
</Loader>
)}
</Loader>
</ColoredContainer>
@@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite';
import { useEffect } from 'react';
import styled, { css } from 'reshadow';
import { Placeholder, useObjectRef, useExecutor, BASE_CONTAINERS_STYLES, IconOrImage, useTranslate, useStyles } from '@cloudbeaver/core-blocks';
import { Placeholder, useObjectRef, useExecutor, BASE_CONTAINERS_STYLES, IconOrImage, useTranslate, useStyles, Loader } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import type { AdminAuthProviderConfiguration } from '@cloudbeaver/core-sdk';
import { TabsState, TabList, UNDERLINE_TAB_STYLES, TabPanelList, BASE_TAB_STYLES } from '@cloudbeaver/core-ui';
@@ -149,7 +149,9 @@ export const AuthConfigurationForm = observer<Props>(function AuthConfigurationF
<TabList style={style} disabled={false} />
</configuration-top-bar-tabs>
<configuration-top-bar-actions>
<Placeholder container={service.actionsContainer} state={state} onCancel={onCancel} />
<Loader suspense inline hideMessage hideException>
<Placeholder container={service.actionsContainer} state={state} onCancel={onCancel} />
</Loader>
</configuration-top-bar-actions>
</configuration-top-bar>
<content-box>
@@ -12,7 +12,7 @@ import styled, { css, use } from 'reshadow';
import { AuthProvidersResource } from '@cloudbeaver/core-authentication';
import {
TableItem, TableColumnValue, TableItemSelect, TableItemExpand,
Placeholder, StaticImage, useResource, FieldCheckbox
Placeholder, StaticImage, useResource, FieldCheckbox, Loader
} from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import type { AdminAuthProviderConfiguration } from '@cloudbeaver/core-sdk';
@@ -65,7 +65,9 @@ export const AuthConfiguration = observer<Props>(function AuthConfiguration({ co
<FieldCheckbox checked={configuration.disabled} disabled />
</TableColumnValue>
<TableColumnValue flex {...use({ gap: true })}>
<Placeholder container={service.configurationDetailsPlaceholder} configuration={configuration} />
<Loader suspense small inline hideMessage>
<Placeholder container={service.configurationDetailsPlaceholder} configuration={configuration} />
</Loader>
</TableColumnValue>
</TableItem>
);
@@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite';
import React, { useContext } from 'react';
import styled from 'reshadow';
import { AuthProviderService, AuthProvidersResource, AuthSettingsService, AUTH_PROVIDER_LOCAL_ID } from '@cloudbeaver/core-authentication';
import { AuthProvider, AuthProviderService, AuthProvidersResource, AuthSettingsService, AUTH_PROVIDER_LOCAL_ID } from '@cloudbeaver/core-authentication';
import { BASE_CONTAINERS_STYLES, FormContext, Group, GroupTitle, Loader, PlaceholderComponent, Switch, useExecutor, useResource, useTranslate, useStyles } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { CachedMapAllKey } from '@cloudbeaver/core-sdk';
@@ -33,8 +33,8 @@ export const AuthenticationProviders: PlaceholderComponent<IConfigurationPlaceho
throw new Error('Form state should be provided');
}
const providerList = providers.resource.values.filter(provider => {
if (configurationWizard && provider.configurable) {
const providerList = providers.data.filter<AuthProvider>((provider): provider is AuthProvider => {
if (configurationWizard && provider?.configurable) {
return false;
}
@@ -89,56 +89,51 @@ export const AuthenticationProviders: PlaceholderComponent<IConfigurationPlaceho
{translate('administration_configuration_wizard_configuration_anonymous_access')}
</Switch>
) : null}
<Loader state={providers} inline>
{() => styled(styles)(
<>
{providerList.map(provider => {
const links = authProviderService.getServiceDescriptionLinks(provider);
let disabled = provider.requiredFeatures.some(feat => !serverConfig.enabledFeatures?.includes(feat));
const tooltip = disabled ? `Following services need to be enabled: "${provider.requiredFeatures.join(', ')}"` : '';
if (
!localProvider
{providerList.map(provider => {
const links = authProviderService.getServiceDescriptionLinks(provider);
let disabled = provider.requiredFeatures.some(feat => !serverConfig.enabledFeatures?.includes(feat));
const tooltip = disabled ? `Following services need to be enabled: "${provider.requiredFeatures.join(', ')}"` : '';
if (
!localProvider
&& primaryProvider?.id === provider.id
&& serverConfig.enabledAuthProviders?.length === 1
&& serverConfig.enabledAuthProviders.includes(provider.id)
) {
disabled = true;
}
) {
disabled = true;
}
if (provider.private || (configurationWizard && (disabled || provider.id !== AUTH_PROVIDER_LOCAL_ID))) {
return null;
}
if (provider.private || (configurationWizard && (disabled || provider.id !== AUTH_PROVIDER_LOCAL_ID))) {
return null;
}
return (
<Switch
key={provider.id}
id={`authProvider_${provider.id}`}
title={tooltip}
value={provider.id}
name="enabledAuthProviders"
state={serverConfig}
description={(
<>
{provider.description}
{links.map(link => {
const Description = link.description();
return <Description key={link.id} configurationWizard={configurationWizard} />;
})}
</>
)}
mod={['primary']}
disabled={disabled}
small
autoHide
>
{provider.label}
</Switch>
);
})}
</>
)}
</Loader>
return (
<Switch
key={provider.id}
id={`authProvider_${provider.id}`}
title={tooltip}
value={provider.id}
name="enabledAuthProviders"
state={serverConfig}
description={(
<>
{provider.description}
{links.map(link => {
const Description = link.description();
return <Description key={link.id} configurationWizard={configurationWizard} />;
})}
</>
)}
mod={['primary']}
disabled={disabled}
small
autoHide
>
{provider.label}
</Switch>
);
})}
</Group>
{configurationWizard && localProvider && (
<ServerConfigurationAdminForm serverConfig={serverConfig} />
@@ -0,0 +1,46 @@
/*
* 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 { UserMetaParametersResource } from '@cloudbeaver/core-authentication';
import { BASE_CONTAINERS_STYLES, Container, ObjectPropertyInfoForm, useResource, useStyles } from '@cloudbeaver/core-blocks';
import type { UserInfo } from '@cloudbeaver/core-sdk';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
interface Props {
user: UserInfo;
className?: string;
style?: ComponentStyle;
}
export const MetaParameterInfoForm = observer<Props>(function MetaParameterInfoForm({
user,
className,
style,
}) {
const userMetaParameters = useResource(MetaParameterInfoForm, UserMetaParametersResource, undefined);
const styles = useStyles(style, BASE_CONTAINERS_STYLES);
if (!userMetaParameters.data.length) {
return null;
}
return styled(styles)(
<Container className={className} wrap gap>
<ObjectPropertyInfoForm
state={user.metaParameters}
properties={userMetaParameters.data}
category={null}
disabled
readOnly
/>
</Container>
);
});
@@ -9,14 +9,12 @@
import { observer } from 'mobx-react-lite';
import styled from 'reshadow';
import { UserMetaParametersResource } from '@cloudbeaver/core-authentication';
import { TabPanel } from '@cloudbeaver/core-ui';
import { BASE_CONTAINERS_STYLES, ColoredContainer, Container, Group, InputField, Loader, ObjectPropertyInfoForm, useResource, useTranslate, useStyles } from '@cloudbeaver/core-blocks';
import { useStyles } from '@cloudbeaver/core-blocks';
import type { UserInfo } from '@cloudbeaver/core-sdk';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import { TabPanel } from '@cloudbeaver/core-ui';
import { AuthProvidersList } from '../AuthProviders/ConfigurationsList';
import { MetaParametersForm } from './MetaParametersForm';
interface Props {
user: UserInfo;
@@ -29,66 +27,11 @@ export const MetaParameterInfoPanel = observer<Props>(function MetaParameterInfo
className,
style,
}) {
const userMetaParameters = useResource(MetaParameterInfoPanel, UserMetaParametersResource, undefined);
const styles = useStyles(style, BASE_CONTAINERS_STYLES);
const translate = useTranslate();
const styles = useStyles(style);
return styled(styles)(
<TabPanel tabId='info' className={className}>
<ColoredContainer wrap overflow parent gap>
<Container medium gap>
<Group form gap>
<Container wrap gap>
<InputField
type="text"
name="userId"
minLength={1}
state={user}
mod='surface'
disabled
readOnly
required
tiny
fill
>
{translate('plugin_user_profile_info_id')}
</InputField>
<InputField
type="text"
name="displayName"
minLength={1}
state={user}
mod='surface'
disabled
readOnly
required
tiny
fill
>
{translate('plugin_user_profile_info_displayName')}
</InputField>
</Container>
<Loader state={userMetaParameters} inline>
{() => userMetaParameters.data.length > 0 && styled(styles)(
<Container wrap gap>
<ObjectPropertyInfoForm
state={user.metaParameters}
properties={userMetaParameters.data}
category={null}
disabled
readOnly
/>
</Container>
)}
</Loader>
</Group>
<Group box medium overflow>
<AuthProvidersList user={user} providers={user.linkedAuthProviders} />
</Group>
</Container>
</ColoredContainer>
<MetaParametersForm user={user} />
</TabPanel>
);
});
@@ -9,9 +9,9 @@
import { observer } from 'mobx-react-lite';
import styled from 'reshadow';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import { Tab } from '@cloudbeaver/core-ui';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
interface Props {
className?: string;
@@ -0,0 +1,76 @@
/*
* 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 { BASE_CONTAINERS_STYLES, ColoredContainer, Container, Group, InputField, useTranslate, useStyles } from '@cloudbeaver/core-blocks';
import type { UserInfo } from '@cloudbeaver/core-sdk';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
import { AuthProvidersList } from '../AuthProviders/ConfigurationsList';
import { MetaParameterInfoForm } from './MetaParameterInfoForm';
interface Props {
user: UserInfo;
className?: string;
style?: ComponentStyle;
}
export const MetaParametersForm = observer<Props>(function MetaParametersForm({
user,
className,
style,
}) {
const styles = useStyles(style, BASE_CONTAINERS_STYLES);
const translate = useTranslate();
return styled(styles)(
<ColoredContainer wrap overflow parent gap>
<Container medium gap>
<Group form gap>
<Container wrap gap>
<InputField
type="text"
name="userId"
minLength={1}
state={user}
mod='surface'
disabled
readOnly
required
tiny
fill
>
{translate('plugin_user_profile_info_id')}
</InputField>
<InputField
type="text"
name="displayName"
minLength={1}
state={user}
mod='surface'
disabled
readOnly
required
tiny
fill
>
{translate('plugin_user_profile_info_displayName')}
</InputField>
</Container>
<MetaParameterInfoForm user={user} />
</Group>
<Group box medium overflow>
<AuthProvidersList user={user} providers={user.linkedAuthProviders} />
</Group>
</Container>
</ColoredContainer>
);
});
@@ -12,7 +12,7 @@ import styled, { css } from 'reshadow';
import {
BASE_CONTAINERS_STYLES, ColoredContainer, Container, getComputed, Group,
InfoItem, Loader, TextPlaceholder, useResource, useStyles, useTranslate
InfoItem, Loader, TextPlaceholder, useAutoLoad, useResource, useStyles, useTranslate
} from '@cloudbeaver/core-blocks';
import { Connection, ConnectionInfoProjectKey, ConnectionInfoResource, DBDriverResource, isCloudConnection } from '@cloudbeaver/core-connections';
import type { TLocalizationToken } from '@cloudbeaver/core-localization';
@@ -60,7 +60,7 @@ export const GrantedConnections: TabContainerPanelComponent<ITeamFormProps> = ob
.map(project => project.id)
);
const dbDriverResource = useResource(
useResource(
GrantedConnections,
DBDriverResource,
CachedMapAllKey,
@@ -80,11 +80,7 @@ export const GrantedConnections: TabContainerPanelComponent<ITeamFormProps> = ob
.filter(connection => state.state.grantedSubjects.includes(connection.id))
);
useEffect(() => {
if (selected && !loaded) {
state.load();
}
});
useAutoLoad(state, selected && !loaded);
if (!selected) {
return null;
@@ -103,7 +99,7 @@ export const GrantedConnections: TabContainerPanelComponent<ITeamFormProps> = ob
}
return styled(style)(
<Loader state={[connectionsLoader, dbDriverResource, projects, state.state]}>
<Loader state={[state.state]}>
{() => styled(style)(
<ColoredContainer parent gap vertical>
{!connections.length ? (
@@ -13,12 +13,12 @@ import { useObservableRef } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { useTabState } from '@cloudbeaver/core-ui';
import { isArraysEqual } from '@cloudbeaver/core-utils';
import { ILoadableState, isArraysEqual } from '@cloudbeaver/core-utils';
import type { TeamFormMode } from '../ITeamFormProps';
import type { IGrantedConnectionsTabState } from './IGrantedConnectionsTabState';
interface State {
interface State extends ILoadableState {
state: IGrantedConnectionsTabState;
changed: boolean;
edit: () => void;
@@ -36,6 +36,15 @@ export function useGrantedConnections(team: TeamInfo, mode: TeamFormMode): Reado
get changed() {
return !isArraysEqual(this.state.initialGrantedSubjects, this.state.grantedSubjects);
},
isLoading() {
return this.state.loading;
},
isLoaded() {
return this.state.loaded;
},
isError() {
return false;
},
edit() {
this.state.editing = !this.state.editing;
},
@@ -6,15 +6,13 @@
* you may not use this file except in compliance with the License.
*/
import { computed } from 'mobx';
import { observer } from 'mobx-react-lite';
import { useEffect, useMemo } from 'react';
import styled, { css } from 'reshadow';
import { UsersResource } from '@cloudbeaver/core-authentication';
import { AdminUser, UsersResource } from '@cloudbeaver/core-authentication';
import {
BASE_CONTAINERS_STYLES, ColoredContainer, Container, Group,
InfoItem, Loader, TextPlaceholder, useResource, useStyles, useTranslate
BASE_CONTAINERS_STYLES, ColoredContainer, Container, getComputed, Group,
InfoItem, Loader, TextPlaceholder, useAutoLoad, useResource, useStyles, useTranslate
} from '@cloudbeaver/core-blocks';
import { CachedMapAllKey } from '@cloudbeaver/core-sdk';
import { TabContainerPanelComponent, useTab } from '@cloudbeaver/core-ui';
@@ -51,24 +49,20 @@ export const GrantedUsers: TabContainerPanelComponent<ITeamFormProps> = observer
const state = useGrantedUsers(formState.config, formState.mode);
const { selected } = useTab(tabId);
const users = useResource(GrantedUsers, UsersResource, selected ? CachedMapAllKey : null);
const users = useResource(GrantedUsers, UsersResource, CachedMapAllKey, { active: selected });
const grantedUsers = useMemo(() => computed(() => users.resource.values
.filter(user => state.state.grantedUsers.includes(user.userId))
), [state.state.grantedUsers, users.resource]);
const grantedUsers = getComputed(() => users.data
.filter<AdminUser>((user): user is AdminUser => !!user && state.state.grantedUsers.includes(user.userId))
);
useEffect(() => {
if (selected && !state.state.loaded) {
state.load();
}
}, [selected, state.state.loaded]);
useAutoLoad(state, selected && !state.state.loaded);
if (!selected) {
return null;
}
return styled(style)(
<Loader state={[users, state.state]}>
<Loader state={[state.state]}>
{() => styled(style)(
<ColoredContainer parent gap vertical>
{!users.resource.values.length ? (
@@ -80,7 +74,7 @@ export const GrantedUsers: TabContainerPanelComponent<ITeamFormProps> = observer
{formState.mode === 'edit' && state.changed && <InfoItem info='ui_save_reminder' />}
<Container gap overflow>
<GrantedUserList
grantedUsers={grantedUsers.get()}
grantedUsers={grantedUsers}
disabled={formState.disabled}
onEdit={state.edit}
onRevoke={state.revoke}
@@ -13,12 +13,12 @@ import { useObservableRef } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { useTabState } from '@cloudbeaver/core-ui';
import { isArraysEqual } from '@cloudbeaver/core-utils';
import { ILoadableState, isArraysEqual } from '@cloudbeaver/core-utils';
import type { TeamFormMode } from '../ITeamFormProps';
import type { IGrantedUsersTabState } from './IGrantedUsersTabState';
interface State {
interface State extends ILoadableState {
state: IGrantedUsersTabState;
changed: boolean;
edit: () => void;
@@ -36,6 +36,15 @@ export function useGrantedUsers(team: TeamInfo, mode: TeamFormMode): Readonly<St
get changed() {
return !isArraysEqual(this.state.initialGrantedUsers, this.state.grantedUsers);
},
isLoading() {
return this.state.loading;
},
isLoaded() {
return this.state.loaded;
},
isError() {
return false;
},
edit() {
this.state.editing = !this.state.editing;
},
@@ -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 { observer } from 'mobx-react-lite';
import styled from 'reshadow';
import { TeamMetaParametersResource } from '@cloudbeaver/core-authentication';
import { BASE_CONTAINERS_STYLES, Group, useTranslate, useStyles, useResource, GroupTitle, ObjectPropertyInfoForm } from '@cloudbeaver/core-blocks';
import type { ITeamFormState } from '../ITeamFormProps';
interface IProps {
state: ITeamFormState;
}
export const TeamMetaParameters = observer<IProps>(function TeamMetaParameters({
state,
}) {
const teamMetaParameters = useResource(TeamMetaParameters, TeamMetaParametersResource, undefined);
const translate = useTranslate();
const style = useStyles(BASE_CONTAINERS_STYLES);
if (teamMetaParameters.data.length > 0) {
return null;
}
return styled(style)(
<Group small gap vertical overflow>
<GroupTitle keepSize>{translate('authentication_team_meta_parameters')}</GroupTitle>
<ObjectPropertyInfoForm
state={state.config.metaParameters}
properties={teamMetaParameters.data}
disabled={state.disabled}
keepSize
tiny
/>
</Group>
);
});
@@ -10,13 +10,13 @@ import { observer } from 'mobx-react-lite';
import { useRef } from 'react';
import styled, { css } from 'reshadow';
import { TeamMetaParametersResource } from '@cloudbeaver/core-authentication';
import { BASE_CONTAINERS_STYLES, ColoredContainer, Group, InputField, SubmittingForm, Textarea, useTranslate, useStyles, useResource, Loader, GroupTitle, ObjectPropertyInfoForm } from '@cloudbeaver/core-blocks';
import { ServerConfigResource } from '@cloudbeaver/core-root';
import type { TabContainerPanelComponent } from '@cloudbeaver/core-ui';
import type { ITeamFormProps } from '../ITeamFormProps';
import { Permissions } from './Permissions';
import { TeamMetaParameters } from './TeamMetaParameters';
const styles = css`
SubmittingForm {
@@ -28,13 +28,10 @@ const styles = css`
export const TeamOptions: TabContainerPanelComponent<ITeamFormProps> = observer(function TeamOptions({
state,
}) {
const formRef = useRef<HTMLFormElement>(null);
const translate = useTranslate();
const serverConfigResource = useResource(TeamOptions, ServerConfigResource, undefined);
const teamMetaParameters = useResource(TeamOptions, TeamMetaParametersResource, undefined);
const style = useStyles(BASE_CONTAINERS_STYLES, styles);
const formRef = useRef<HTMLFormElement>(null);
const translate = useTranslate();
const edit = state.mode === 'edit';
return styled(style)(
@@ -74,20 +71,7 @@ export const TeamOptions: TabContainerPanelComponent<ITeamFormProps> = observer(
</Textarea>
</Group>
{!serverConfigResource.resource.distributed && <Permissions state={state} />}
<Loader state={teamMetaParameters} inline>
{() => teamMetaParameters.data.length > 0 && styled(style)(
<Group small gap vertical overflow>
<GroupTitle keepSize>{translate('authentication_team_meta_parameters')}</GroupTitle>
<ObjectPropertyInfoForm
state={state.config.metaParameters}
properties={teamMetaParameters.data}
disabled={state.disabled}
keepSize
tiny
/>
</Group>
)}
</Loader>
<TeamMetaParameters state={state} />
</ColoredContainer>
</SubmittingForm>
);
@@ -11,7 +11,7 @@ import { useEffect } from 'react';
import styled, { css } from 'reshadow';
import type { TeamInfo } from '@cloudbeaver/core-authentication';
import { Placeholder, useObjectRef, useExecutor, BASE_CONTAINERS_STYLES, IconOrImage, useTranslate, useStyles } from '@cloudbeaver/core-blocks';
import { Placeholder, useObjectRef, useExecutor, BASE_CONTAINERS_STYLES, IconOrImage, useTranslate, useStyles, Loader } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { TabsState, TabList, UNDERLINE_TAB_STYLES, TabPanelList, BASE_TAB_STYLES } from '@cloudbeaver/core-ui';
@@ -150,7 +150,9 @@ export const TeamForm = observer<Props>(function TeamForm({
<TabList style={style} disabled={false} />
</team-top-bar-tabs>
<team-top-bar-actions>
<Placeholder container={service.actionsContainer} state={state} onCancel={onCancel} />
<Loader suspense inline hideMessage hideException>
<Placeholder container={service.actionsContainer} state={state} onCancel={onCancel} />
</Loader>
</team-top-bar-actions>
</team-top-bar>
<content-box>
@@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite';
import styled, { css, use } from 'reshadow';
import type { TeamInfo } from '@cloudbeaver/core-authentication';
import { TableItem, TableColumnValue, TableItemSelect, TableItemExpand, Placeholder, useStyles } from '@cloudbeaver/core-blocks';
import { TableItem, TableColumnValue, TableItemSelect, TableItemExpand, Placeholder, useStyles, Loader } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
@@ -53,7 +53,9 @@ export const Team = observer<Props>(function Team({ team }) {
<TableColumnValue title={team.teamName} ellipsis>{team.teamName || ''}</TableColumnValue>
<TableColumnValue title={team.description} ellipsis>{team.description || ''}</TableColumnValue>
<TableColumnValue flex {...use({ gap: true })}>
<Placeholder container={service.teamDetailsInfoPlaceholder} team={team} />
<Loader suspense small inline hideMessage>
<Placeholder container={service.teamDetailsInfoPlaceholder} team={team} />
</Loader>
</TableColumnValue>
</TableItem>
);
@@ -11,7 +11,7 @@ import styled, { css, use } from 'reshadow';
import { AdminUser, UsersResource } from '@cloudbeaver/core-authentication';
import {
TableItem, TableColumnValue, TableItemSelect, TableItemExpand, Placeholder, Checkbox, useTranslate
TableItem, TableColumnValue, TableItemSelect, TableItemExpand, Placeholder, Checkbox, useTranslate, Loader
} from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
@@ -76,7 +76,9 @@ export const User = observer<Props>(function User({ user, displayAuthRole, selec
/>
</TableColumnValue>
<TableColumnValue flex {...use({ gap: true })}>
<Placeholder container={usersAdministrationService.userDetailsInfoPlaceholder} user={user} />
<Loader suspense small inline hideMessage>
<Placeholder container={usersAdministrationService.userDetailsInfoPlaceholder} user={user} />
</Loader>
</TableColumnValue>
</TableItem>
);
@@ -184,21 +184,17 @@ export const AuthDialog: DialogComponent<IAuthOptions, null> = observer(function
</TabList>
)}
<SubmittingForm {...use({ form: !federate })} onSubmit={login}>
<Loader state={dialogData.loadingState}>
{() => federate
? (
<ConfigurationsList
activeProvider={state.activeProvider}
activeConfiguration={state.activeConfiguration}
providers={dialogData.configurations}
onAuthorize={(provider, configuration) => {
state.setActiveConfiguration(provider, configuration);
}}
onClose={rejectDialog}
/>
)
: renderForm(state.activeProvider)}
</Loader>
{federate ? (
<ConfigurationsList
activeProvider={state.activeProvider}
activeConfiguration={state.activeConfiguration}
providers={dialogData.configurations}
onAuthorize={(provider, configuration) => {
state.setActiveConfiguration(provider, configuration);
}}
onClose={rejectDialog}
/>
) : renderForm(state.activeProvider)}
</SubmittingForm>
</CommonDialogBody>
{!federate && (
@@ -26,7 +26,6 @@ interface IData {
destroyed: boolean;
configure: boolean;
adminPageActive: boolean;
loadingState: ILoadableState;
providers: AuthProvider[];
configurations: AuthProvider[];
@@ -52,7 +51,7 @@ export function useAuthDialogState(accessRequest: boolean, providerId: string |
const primaryId = authProvidersResource.resource.getPrimary();
const adminPageActive = administrationScreenService.isAdministrationPageActive;
const providers = authProvidersResource.resource.values
const providers = authProvidersResource.data
.filter(notEmptyProvider)
.sort(compareProviders);
@@ -129,7 +128,6 @@ export function useAuthDialogState(accessRequest: boolean, providerId: string |
exception: null,
authenticating: false,
destroyed: false,
loadingState: authProvidersResource,
get configure(): boolean {
if (state.activeProvider) {
@@ -197,7 +195,7 @@ export function useAuthDialogState(accessRequest: boolean, providerId: string |
}
function notEmptyProvider(obj: any): obj is AuthProvider {
return typeof obj === 'object';
return !!obj && typeof obj === 'object';
}
function compareProviders(providerA: AuthProvider, providerB: AuthProvider): number {
@@ -10,7 +10,7 @@ import { observer } from 'mobx-react-lite';
import styled, { css } from 'reshadow';
import {
TableItem, TableColumnValue, TableItemSelect, TableItemExpand, StaticImage, Placeholder
TableItem, TableColumnValue, TableItemSelect, TableItemExpand, StaticImage, Placeholder, Loader
} from '@cloudbeaver/core-blocks';
import { DatabaseConnection, DBDriverResource, IConnectionInfoParams } from '@cloudbeaver/core-connections';
import { useService } from '@cloudbeaver/core-di';
@@ -65,10 +65,12 @@ export const Connection = observer<Props>(function Connection({ connectionKey, c
{projectName !== undefined && (
<TableColumnValue title={projectName ?? ''} expand ellipsis>{projectName}</TableColumnValue>)}
<TableColumnValue flex>
<Placeholder
container={connectionsAdministrationService.connectionDetailsPlaceholder}
connection={connection}
/>
<Loader suspense small inline hideMessage>
<Placeholder
container={connectionsAdministrationService.connectionDetailsPlaceholder}
connection={connection}
/>
</Loader>
</TableColumnValue>
</TableItem>
);
@@ -175,7 +175,9 @@ export const ConnectionForm = observer<Props>(function ConnectionForm({
<TabList style={style} disabled={state.disabled} />
</connection-top-bar-tabs>
<connection-top-bar-actions>
<Placeholder container={service.actionsContainer} state={state} onCancel={onCancel} />
<Loader suspense inline hideMessage hideException>
<Placeholder container={service.actionsContainer} state={state} onCancel={onCancel} />
</Loader>
</connection-top-bar-actions>
</connection-top-bar>
<content-box>
@@ -6,7 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { computed, observable } from 'mobx';
import { computed, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react-lite';
import { useMemo, useState } from 'react';
import styled, { css } from 'reshadow';
@@ -64,21 +64,23 @@ export const DriverProperties: TabContainerPanelComponent<IConnectionFormProps>
DriverProperties,
DBDriverResource,
{ key: (selected && formState.config.driverId) || null, includes: ['includeDriverProperties'] as const },
{
active: selected && !!formState.config.driverId,
onData: driver => {
for (const key of Object.keys(formState.config.properties)) {
if (driver.driverProperties.some(property => property.id === key)
|| state.propertiesList.some(property => property.key === key)) {
continue;
}
state.add(key, formState.config.properties[key]);
}
},
}
);
runInAction(() => {
if (driver.data) {
for (const key of Object.keys(formState.config.properties)) {
if (
driver.data.driverProperties.some(property => property.id === key)
|| state.propertiesList.some(property => property.key === key)
) {
continue;
}
state.add(key, formState.config.properties[key]);
}
}
});
const joinedProperties = useMemo(() => computed<IProperty[]>(() => ([
...state.propertiesList,
...(driver.data?.driverProperties
@@ -96,21 +98,17 @@ export const DriverProperties: TabContainerPanelComponent<IConnectionFormProps>
])), [driver.data]);
return styled(style)(
<Loader state={driver}>
{() => styled(style)(
<ColoredContainer parent>
<Group box keepSize large>
<PropertiesTable
properties={joinedProperties.get()}
propertiesState={formState.config.properties}
readOnly={formState.readonly}
filterable
onAdd={state.add}
onRemove={state.remove}
/>
</Group>
</ColoredContainer>
)}
</Loader>
<ColoredContainer parent>
<Group box keepSize large>
<PropertiesTable
properties={joinedProperties.get()}
propertiesState={formState.config.properties}
readOnly={formState.readonly}
filterable
onAdd={state.add}
onRemove={state.remove}
/>
</Group>
</ColoredContainer>
);
});
@@ -99,17 +99,6 @@ export const Options: TabContainerPanelComponent<IConnectionFormProps> = observe
const optionsHook = useOptions(state);
const { credentialsSavingEnabled } = useAdministrationSettings();
const handleDriverSelect = useCallback(async (value?: string, name?: string, prev?: string) => {
if (!value) {
return;
}
const prevDriver = prev ? await driverMap.resource.load(prev) : undefined;
const newDriver = await driverMap.resource.load(value);
optionsHook.setDefaults(newDriver, prevDriver);
}, []);
const handleAuthModelSelect = useCallback(async (value?: string, name?: string, prev?: string) => {
const model = applicableAuthModels.find(model => model?.id === value);
@@ -125,10 +114,8 @@ export const Options: TabContainerPanelComponent<IConnectionFormProps> = observe
DBDriverResource,
{ key: config.driverId || null, includes: ['includeProviderProperties'] as const },
{
onData: (data, resource, prevData) => {
if (data.id !== prevData?.id) {
optionsHook.setDefaults(data);
}
onData: (data, resource, prevDriver) => {
optionsHook.setDefaults(data, prevDriver);
},
}
);
@@ -222,7 +209,6 @@ export const Options: TabContainerPanelComponent<IConnectionFormProps> = observe
loading={driverMap.isLoading()}
tiny
fill
onSelect={handleDriverSelect}
>
{translate('connections_connection_driver')}
</Combobox>
@@ -9,10 +9,9 @@
import { observer } from 'mobx-react-lite';
import styled, { css } from 'reshadow';
import { BASE_CONTAINERS_STYLES, Group, Placeholder, PlaceholderElement, useStyles } from '@cloudbeaver/core-blocks';
import { BASE_CONTAINERS_STYLES, Group, Loader, Placeholder, PlaceholderElement, useStyles } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { ElementsTreeSettingsService, IElementsTreeSettingsProps } from './ElementsTreeSettingsService';
const expandStyles = css`
@@ -47,12 +46,14 @@ export const NavigationTreeSettings = observer<Props>(function NavigationTreeSet
return styled(styles)(
<settings className={className}>
<Group keepSize form gap dense>
<Placeholder
container={elementsTreeSettingsService.placeholder}
elements={elements}
tree={tree}
style={style}
/>
<Loader suspense>
<Placeholder
container={elementsTreeSettingsService.placeholder}
elements={elements}
tree={tree}
style={style}
/>
</Loader>
</Group>
</settings>
);
@@ -48,9 +48,7 @@ export const VirtualFolderPanel: NavNodeTransformViewComponent = observer(functi
});
useEffect(() => {
untracked(() => {
navNodeViewService.logDuplicates(nodeId, duplicates);
});
navNodeViewService.logDuplicates(nodeId, duplicates);
});
const objects = dbObject.data
@@ -52,9 +52,7 @@ export const ObjectPropertyTable = observer<ObjectPropertyTableProps>(function O
});
useEffect(() => {
untracked(() => {
navNodeViewService.logDuplicates(objectId, duplicates);
});
navNodeViewService.logDuplicates(objectId, duplicates);
});
const objects = dbObject.data.filter(object => nodes.includes(object?.id ?? '')) as DBObject[];
@@ -10,12 +10,13 @@ import { observer } from 'mobx-react-lite';
import { useMemo } from 'react';
import styled, { css } from 'reshadow';
import { useResource, useUserData } from '@cloudbeaver/core-blocks';
import { getComputed, useResource, useUserData } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { NavNodeInfoResource, NavTreeResource, ProjectsNavNodeService } from '@cloudbeaver/core-navigation-tree';
import { ProjectInfoResource, ProjectsService } from '@cloudbeaver/core-projects';
import { IResourceManagerParams, ResourceManagerResource, RESOURCES_NODE_PATH } from '@cloudbeaver/core-resource-manager';
import { resourceKeyList } from '@cloudbeaver/core-sdk';
import { isArraysEqual } from '@cloudbeaver/core-utils';
import { CaptureView } from '@cloudbeaver/core-view';
import { NavigationTreeService, ElementsTreeLoader, IElementsTreeSettings, createElementsTreeSettings, validateElementsTreeSettings, getNavigationTreeUserSettingsId } from '@cloudbeaver/plugin-navigation-tree';
@@ -79,9 +80,13 @@ export const ResourceManagerTree: React.FC<Props> = observer(function ResourceMa
const resourceManagerService = useService(ResourceManagerService);
const navTreeResource = useService(NavTreeResource);
const key: IResourceManagerParams[] = projectsService.activeProjectIds.map(id => ({ projectId: id }));
const key = getComputed<IResourceManagerParams[]>(
() => projectsService.activeProjects.map(project => ({ projectId: project.id })),
isArraysEqual
);
useResource(ResourceManagerTree, ResourceManagerResource, resourceKeyList(key));
const settings = useUserData<IElementsTreeSettings>(
getNavigationTreeUserSettingsId(root),
createElementsTreeSettings,
@@ -166,7 +171,6 @@ export const ResourceManagerTree: React.FC<Props> = observer(function ResourceMa
const settingsElements = useMemo(() => ([ProjectsSettingsPlaceholderElement]), []);
return styled(styles)(
<CaptureView view={navTreeService}>
<ResourceManagerTreeCaptureViewContext resourceTypeId={resourceTypeId} />