Chore/prettier (#1706)

* chore: add prettier

* chore: format code
This commit is contained in:
Alexey
2023-05-28 02:42:14 +03:00
committed by GitHub
parent 1985df12e2
commit 2b25b3ac8e
1784 changed files with 23654 additions and 26796 deletions
-2
View File
@@ -12,8 +12,6 @@ module.exports = {
parserOptions: {
ecmaVersion: 2019,
sourceType: 'module',
tsconfigRootDir: __dirname,
project: './tsconfig.eslint.json',
ecmaFeatures: {
jsx: true,
},
+4 -2
View File
@@ -27,13 +27,15 @@
"mobx-react-lite": "~3.4.2",
"msw": "^1.1.0",
"path-browserify": "~1.0.1",
"prettier": "^2.8.8",
"react": "~18.2.0",
"react-dom": "~18.2.0",
"reakit": "~1.3.11",
"reflect-metadata": "~0.1.13",
"reshadow": "~0.0.1",
"reshadow": "^0.0.1",
"rimraf": "~4.3.1",
"typescript": "4.9.5"
},
"dependencies": {}
"dependencies": {},
"prettier": "@cloudbeaver/prettier-config"
}
@@ -5,17 +5,14 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observable, makeObservable } from 'mobx';
import { makeObservable, observable } from 'mobx';
import { injectable } from '@cloudbeaver/core-di';
import { Executor, IExecutor, IExecutorHandler } from '@cloudbeaver/core-executor';
import type { RouterState } from '@cloudbeaver/core-routing';
import { filterConfigurationWizard } from './filterConfigurationWizard';
import {
IAdministrationItem, IAdministrationItemOptions, IAdministrationItemSubItem, AdministrationItemType
} from './IAdministrationItem';
import { AdministrationItemType, IAdministrationItem, IAdministrationItemOptions, IAdministrationItemSubItem } from './IAdministrationItem';
import type { IAdministrationItemRoute } from './IAdministrationItemRoute';
import { orderAdministrationItems } from './orderAdministrationItems';
@@ -49,20 +46,29 @@ export class AdministrationItemService {
this.deActivationTask = new Executor();
this.activationTask
.addHandler(() => { this.itemActivating = true; })
.addHandler(() => {
this.itemActivating = true;
})
.addHandler(this.activateHandler)
.addPostHandler(() => { this.itemActivating = false; });
.addPostHandler(() => {
this.itemActivating = false;
});
this.deActivationTask
.addHandler(() => { this.itemDeactivating = true; })
.addHandler(() => {
this.itemDeactivating = true;
})
.addHandler(this.deActivateHandler)
.addPostHandler(() => { this.itemDeactivating = false; });
.addPostHandler(() => {
this.itemDeactivating = false;
});
}
getUniqueItems(configurationWizard: boolean): IAdministrationItem[] {
const items: IAdministrationItem[] = [];
const orderedByPriority = this.items.slice()
const orderedByPriority = this.items
.slice()
.sort((a, b) => {
if (a.name !== b.name) {
return a.name.localeCompare(b.name);
@@ -88,10 +94,9 @@ export class AdministrationItemService {
}
getActiveItems(configurationWizard: boolean): IAdministrationItem[] {
return this.getUniqueItems(configurationWizard).filter(item =>
filterHiddenAdministrationItem(configurationWizard)(item)
&& filterConfigurationWizard(configurationWizard)(item)
).sort(orderAdministrationItems(configurationWizard));
return this.getUniqueItems(configurationWizard)
.filter(item => filterHiddenAdministrationItem(configurationWizard)(item) && filterConfigurationWizard(configurationWizard)(item))
.sort(orderAdministrationItems(configurationWizard));
}
getDefaultItem(configurationWizard: boolean): string | null {
@@ -140,11 +145,10 @@ export class AdministrationItemService {
create(options: IAdministrationItemOptions): void {
const type = options.type ?? AdministrationItemType.Administration;
const existedIndex = this.items.findIndex(item => item.name === options.name && (
item.type === type
|| item.type === AdministrationItemType.Default
|| type === AdministrationItemType.Default
));
const existedIndex = this.items.findIndex(
item =>
item.name === options.name && (item.type === type || item.type === AdministrationItemType.Default || type === AdministrationItemType.Default),
);
if (!options.replace && existedIndex !== -1) {
throw new Error(`Administration item "${options.name}" already exists in the same visibility scope`);
@@ -159,21 +163,11 @@ export class AdministrationItemService {
this.items.push(item);
}
async activate(
screen: IAdministrationItemRoute,
configurationWizard: boolean,
outside: boolean,
outsideAdminPage: boolean
): Promise<void> {
async activate(screen: IAdministrationItemRoute, configurationWizard: boolean, outside: boolean, outsideAdminPage: boolean): Promise<void> {
await this.activationTask.execute({ screen, configurationWizard, outside, outsideAdminPage });
}
async deActivate(
screen: IAdministrationItemRoute,
configurationWizard: boolean,
outside: boolean,
outsideAdminPage: boolean
): Promise<void> {
async deActivate(screen: IAdministrationItemRoute, configurationWizard: boolean, outside: boolean, outsideAdminPage: boolean): Promise<void> {
await this.deActivationTask.execute({ screen, configurationWizard, outside, outsideAdminPage });
}
@@ -181,7 +175,7 @@ export class AdministrationItemService {
screen: IAdministrationItemRoute,
toScreen: IAdministrationItemRoute | null,
configurationWizard: boolean,
outside: boolean
outside: boolean,
): Promise<boolean> {
const item = this.getItem(screen.item, configurationWizard);
let nextItem = null;
@@ -207,11 +201,7 @@ export class AdministrationItemService {
return true;
}
async canActivate(
screen: IAdministrationItemRoute,
configurationWizard: boolean,
outside: boolean
): Promise<boolean> {
async canActivate(screen: IAdministrationItemRoute, configurationWizard: boolean, outside: boolean): Promise<boolean> {
const item = this.getItem(screen.item, configurationWizard);
if (!item) {
return false;
@@ -231,12 +221,7 @@ export class AdministrationItemService {
return true;
}
private activateHandler: IExecutorHandler<IActivationData> = async ({
screen,
configurationWizard,
outside,
outsideAdminPage,
}) => {
private activateHandler: IExecutorHandler<IActivationData> = async ({ screen, configurationWizard, outside, outsideAdminPage }) => {
let lastItem = 0;
while (true) {
const items = this.getActiveItems(configurationWizard);
@@ -271,12 +256,7 @@ export class AdministrationItemService {
}
};
private deActivateHandler: IExecutorHandler<IActivationData> = async ({
screen,
configurationWizard,
outside,
outsideAdminPage,
}) => {
private deActivateHandler: IExecutorHandler<IActivationData> = async ({ screen, configurationWizard, outside, outsideAdminPage }) => {
const item = this.getItem(screen.item, configurationWizard);
if (!item) {
return;
@@ -5,7 +5,6 @@
* 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 { IRouteParams } from './IRouteParams';
@@ -13,7 +12,7 @@ import type { IRouteParams } from './IRouteParams';
export enum AdministrationItemType {
Default,
Administration,
ConfigurationWizard
ConfigurationWizard,
}
export interface IAdministrationItemReplaceOptions {
@@ -44,29 +43,15 @@ export type AdministrationItemSubContentProps = AdministrationItemContentProps &
};
export type AdministrationItemSubContentComponent = React.FunctionComponent<AdministrationItemSubContentProps>;
export type AdministrationItemEvent = (
configurationWizard: boolean,
outside: boolean,
outsideAdminPage: boolean
) => Promise<void> | void;
export type AdministrationItemCanActivateEvent = (
configurationWizard: boolean,
administration: boolean,
) => Promise<boolean> | boolean;
export type AdministrationItemEvent = (configurationWizard: boolean, outside: boolean, outsideAdminPage: boolean) => Promise<void> | void;
export type AdministrationItemCanActivateEvent = (configurationWizard: boolean, administration: boolean) => Promise<boolean> | boolean;
export type AdministrationItemCanDeActivateEvent = (
configurationWizard: boolean,
administration: boolean,
nextAdministrationItem: IAdministrationItem | null,
) => Promise<boolean> | boolean;
export type AdministrationItemSubEvent = (
param: string | null,
configurationWizard: boolean,
outside: boolean
) => Promise<void> | void;
export type AdministrationItemSubCanActivateEvent = (
param: string | null,
configurationWizard: boolean
) => Promise<boolean> | boolean;
export type AdministrationItemSubEvent = (param: string | null, configurationWizard: boolean, outside: boolean) => Promise<void> | void;
export type AdministrationItemSubCanActivateEvent = (param: string | null, configurationWizard: boolean) => Promise<boolean> | boolean;
export interface IAdministrationItemSubItem {
name: string;
@@ -5,11 +5,9 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { AdministrationItemType, IAdministrationItem } from './IAdministrationItem';
export function filterConfigurationWizard(configurationWizard: boolean) {
return (item: IAdministrationItem) => (configurationWizard
? item.type !== AdministrationItemType.Administration
: item.type !== AdministrationItemType.ConfigurationWizard);
return (item: IAdministrationItem) =>
configurationWizard ? item.type !== AdministrationItemType.Administration : item.type !== AdministrationItemType.ConfigurationWizard;
}
@@ -5,17 +5,12 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { IAdministrationItem } from './IAdministrationItem';
export function orderAdministrationItems(configuration: boolean) {
return (itemA: IAdministrationItem, itemB: IAdministrationItem): number => {
if (configuration) {
return (
itemA.configurationWizardOptions?.order ?? itemA.order
) - (
itemB.configurationWizardOptions?.order ?? itemB.order
);
return (itemA.configurationWizardOptions?.order ?? itemA.order) - (itemB.configurationWizardOptions?.order ?? itemB.order);
}
return itemA.order - itemB.order;
};
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { LocalizationService } from '@cloudbeaver/core-localization';
@@ -19,7 +18,7 @@ export class AdministrationLocaleService extends Bootstrap {
this.localizationService.addProvider(this.provider.bind(this));
}
load(): void | Promise<void> { }
load(): void | Promise<void> {}
private async provider(locale: string) {
switch (locale) {
@@ -5,15 +5,14 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { computed, observable, makeObservable } from 'mobx';
import { computed, makeObservable, observable } from 'mobx';
import { EAdminPermission } from '@cloudbeaver/core-authentication';
import { injectable } from '@cloudbeaver/core-di';
import { NotificationService } from '@cloudbeaver/core-events';
import { IExecutor, Executor } from '@cloudbeaver/core-executor';
import { SessionPermissionsResource, PermissionsService, ServerConfigResource } from '@cloudbeaver/core-root';
import { ScreenService, RouterState } from '@cloudbeaver/core-routing';
import { Executor, IExecutor } from '@cloudbeaver/core-executor';
import { PermissionsService, ServerConfigResource, SessionPermissionsResource } from '@cloudbeaver/core-root';
import { RouterState, ScreenService } from '@cloudbeaver/core-routing';
import { LocalStorageSaveService } from '@cloudbeaver/core-settings';
import { GlobalConstants } from '@cloudbeaver/core-utils';
@@ -73,7 +72,7 @@ export class AdministrationScreenService {
private readonly administrationItemService: AdministrationItemService,
private readonly autoSaveService: LocalStorageSaveService,
private readonly serverConfigResource: ServerConfigResource,
private readonly notificationService: NotificationService
private readonly notificationService: NotificationService,
) {
this.info = getDefaultAdministrationScreenInfo();
this.itemState = new Map();
@@ -146,27 +145,17 @@ export class AdministrationScreenService {
this.getRouteName(itemName, item?.defaultSub, item?.defaultParam),
itemName,
item?.defaultSub,
item?.defaultParam
item?.defaultParam,
);
}
navigateToItemSub(item: string, sub: string, param?: string): void {
this.screenService.navigateToScreen(
this.getRouteName(item, sub, param),
item,
sub,
param
);
this.screenService.navigateToScreen(this.getRouteName(item, sub, param), item, sub, param);
}
getItemState<T>(name: string): T | undefined;
getItemState<T>(name: string, defaultState: () => T, update?: boolean, validate?: (state: T) => boolean): T;
getItemState<T>(
name: string,
defaultState?: () => T,
update?: boolean,
validate?: (state: T) => boolean
): T | undefined {
getItemState<T>(name: string, defaultState?: () => T, update?: boolean, validate?: (state: T) => boolean): T | undefined {
if (!this.serverConfigResource.isLoaded()) {
throw new Error('Administration screen getItemState can be used only after server configuration loaded');
}
@@ -193,8 +182,8 @@ export class AdministrationScreenService {
isAdministrationRouteActive(routeName: string): boolean {
return (
this.screenService.isActive(routeName, AdministrationScreenService.screenName)
|| this.screenService.isActive(routeName, AdministrationScreenService.setupName)
this.screenService.isActive(routeName, AdministrationScreenService.screenName) ||
this.screenService.isActive(routeName, AdministrationScreenService.setupName)
);
}
@@ -207,16 +196,10 @@ export class AdministrationScreenService {
const screen = this.getScreen(state);
if (screen) {
await this.administrationItemService.deActivate(
screen,
this.isConfigurationMode,
screen.item !== toScreen?.item,
toScreen === null
);
await this.administrationItemService.deActivate(screen, this.isConfigurationMode, screen.item !== toScreen?.item, toScreen === null);
}
if (this.isConfigurationMode
&& !this.screenService.isActive(nextState.name, AdministrationScreenService.setupName)) {
if (this.isConfigurationMode && !this.screenService.isActive(nextState.name, AdministrationScreenService.setupName)) {
this.navigateToRoot();
}
}
@@ -233,12 +216,7 @@ export class AdministrationScreenService {
return true;
}
return this.administrationItemService.canDeActivate(
screen,
toScreen,
this.isConfigurationMode,
screen.item !== toScreen?.item
);
return this.administrationItemService.canDeActivate(screen, toScreen, this.isConfigurationMode, screen.item !== toScreen?.item);
}
async handleCanActivate(toState: RouterState, fromState: RouterState): Promise<boolean> {
@@ -252,11 +230,7 @@ export class AdministrationScreenService {
return false;
}
return this.administrationItemService.canActivate(
screen,
this.isConfigurationMode,
screen.item !== fromScreen?.item
);
return this.administrationItemService.canActivate(screen, this.isConfigurationMode, screen.item !== fromScreen?.item);
}
async handleActivate(state: RouterState, prevState?: RouterState): Promise<void> {
@@ -269,21 +243,16 @@ export class AdministrationScreenService {
const screen = this.getScreen(state);
const fromScreen = this.getScreen(prevState);
if (screen) {
await this.administrationItemService.activate(
screen,
this.isConfigurationMode,
screen.item !== fromScreen?.item,
fromScreen === null
);
await this.administrationItemService.activate(screen, this.isConfigurationMode, screen.item !== fromScreen?.item, fromScreen === null);
}
}
private validateState() {
if (
this.info.workspaceId !== this.serverConfigResource.workspaceId
|| this.info.configurationMode !== this.isConfigurationMode
|| this.info.serverVersion !== this.serverConfigResource.serverVersion
|| this.info.version !== GlobalConstants.version
this.info.workspaceId !== this.serverConfigResource.workspaceId ||
this.info.configurationMode !== this.isConfigurationMode ||
this.info.serverVersion !== this.serverConfigResource.serverVersion ||
this.info.version !== GlobalConstants.version
) {
this.clearItemsState();
this.info.workspaceId = this.serverConfigResource.workspaceId;
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { computed, makeObservable } from 'mobx';
import { injectable } from '@cloudbeaver/core-di';
@@ -20,11 +19,9 @@ import { AdministrationScreenService } from '../AdministrationScreenService';
@injectable()
export class ConfigurationWizardService {
get steps(): IAdministrationItem[] {
return this.administrationItemService.getUniqueItems(true)
.filter(item =>
filterConfigurationWizard(true)(item)
&& filterHiddenAdministrationItem(true)(item)
)
return this.administrationItemService
.getUniqueItems(true)
.filter(item => filterConfigurationWizard(true)(item) && filterHiddenAdministrationItem(true)(item))
.sort(orderAdministrationItems(true));
}
@@ -33,9 +30,7 @@ export class ConfigurationWizardService {
}
get finishedSteps(): IAdministrationItem[] {
return this.steps.filter(step => (
step.configurationWizardOptions?.isDone && step.configurationWizardOptions.isDone()
));
return this.steps.filter(step => step.configurationWizardOptions?.isDone && step.configurationWizardOptions.isDone());
}
get currentStepIndex(): number {
@@ -48,8 +43,7 @@ export class ConfigurationWizardService {
get canFinish(): boolean {
return this.steps.every(step => {
if (step.configurationWizardOptions?.isDone
&& !step.configurationWizardOptions?.isDone()) {
if (step.configurationWizardOptions?.isDone && !step.configurationWizardOptions?.isDone()) {
return false;
}
@@ -78,7 +72,7 @@ export class ConfigurationWizardService {
constructor(
private administrationItemService: AdministrationItemService,
private administrationScreenService: AdministrationScreenService,
private notificationService: NotificationService
private notificationService: NotificationService,
) {
makeObservable(this, {
steps: computed,
@@ -144,10 +138,7 @@ export class ConfigurationWizardService {
if (this.currentStepIndex + 1 < this.steps.length) {
if (this.nextStep) {
this.administrationScreenService.navigateTo(
this.nextStep.name,
this.nextStep.configurationWizardOptions?.defaultRoute
);
this.administrationScreenService.navigateTo(this.nextStep.name, this.nextStep.configurationWizardOptions?.defaultRoute);
}
} else {
await this.finish();
@@ -166,8 +157,7 @@ export class ConfigurationWizardService {
}
private getStep(name: string) {
return this.administrationItemService.getUniqueItems(true)
.find(step => filterConfigurationWizard(true)(step) && step.name === name);
return this.administrationItemService.getUniqueItems(true).find(step => filterConfigurationWizard(true)(step) && step.name === name);
}
private async finish() {
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import '@testing-library/jest-dom';
import { mockAuthentication } from '@cloudbeaver/core-authentication/mocks/mockAuthentication';
@@ -21,10 +20,7 @@ import { AdministrationSettings, AdministrationSettingsService } from './Adminis
const endpoint = createGQLEndpoint();
const app = createApp();
const server = mockGraphQL(
...mockAppInit(endpoint),
...mockAuthentication(endpoint)
);
const server = mockGraphQL(...mockAppInit(endpoint), ...mockAuthentication(endpoint));
beforeAll(() => app.init());
@@ -42,11 +38,9 @@ test('Read settings', async () => {
const settings = app.injector.getServiceByClass(AdministrationSettingsService);
const config = app.injector.getServiceByClass(ServerConfigResource);
server.use(
endpoint.query('serverConfig', mockServerConfig(equalConfig)),
);
server.use(endpoint.query('serverConfig', mockServerConfig(equalConfig)));
await config.refresh();
expect(settings.settings.getValue('baseFeatures')).toEqual(testValue);
});
});
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@cloudbeaver/core-di';
import { PluginManagerService, PluginSettings } from '@cloudbeaver/core-plugin';
@@ -1,4 +1,3 @@
/*
* CloudBeaver - Cloud Database Manager
* Copyright (C) 2022 DBeaver Corp and others
@@ -6,13 +5,12 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { css } from 'reshadow';
export const ADMINISTRATION_TOOLS_PANEL_STYLES = css`
ToolsPanel {
composes: theme-background-surface theme-text-on-surface theme-border-color-background from global;
border-bottom: solid 1px;
flex: 0 0 auto;
}
`;
ToolsPanel {
composes: theme-background-surface theme-text-on-surface theme-border-color-background from global;
border-bottom: solid 1px;
flex: 0 0 auto;
}
`;
@@ -5,23 +5,31 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@cloudbeaver/core-di';
import { SessionDataResource } from '@cloudbeaver/core-root';
import { GraphQLService, CachedMapResource, CachedMapAllKey, AdminPermissionInfoFragment, AdminObjectGrantInfoFragment, ResourceKey, resourceKeyList } from '@cloudbeaver/core-sdk';
import {
AdminObjectGrantInfoFragment,
AdminPermissionInfoFragment,
CachedMapAllKey,
CachedMapResource,
GraphQLService,
ResourceKey,
resourceKeyList,
} from '@cloudbeaver/core-sdk';
export type PermissionInfo = AdminPermissionInfoFragment;
export type AdminObjectGrantInfo = AdminObjectGrantInfoFragment;
@injectable()
export class PermissionsResource extends CachedMapResource<string, PermissionInfo> {
constructor(
private readonly graphQLService: GraphQLService,
sessionDataResource: SessionDataResource
) {
constructor(private readonly graphQLService: GraphQLService, sessionDataResource: SessionDataResource) {
super();
this.sync(sessionDataResource, () => {}, () => CachedMapAllKey);
this.sync(
sessionDataResource,
() => {},
() => CachedMapAllKey,
);
}
protected async loader(): Promise<Map<string, PermissionInfo>> {
@@ -5,7 +5,6 @@
* 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 { AdministrationItemService } from './AdministrationItem/AdministrationItemService';
@@ -15,7 +14,6 @@ import { ConfigurationWizardService } from './AdministrationScreen/Configuration
import { AdministrationSettingsService } from './AdministrationSettingsService';
import { PermissionsResource } from './PermissionsResource';
export const manifest: PluginManifest = {
info: {
name: 'Core Administration',
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { LocalizationService } from '@cloudbeaver/core-localization';
@@ -19,7 +18,7 @@ export class AppLocaleService extends Bootstrap {
this.localizationService.addProvider(this.provider.bind(this));
}
load(): void | Promise<void> { }
load(): void | Promise<void> {}
private async provider(locale: string) {
switch (locale) {
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { memo } from 'react';
import { Loader, Placeholder } from '@cloudbeaver/core-blocks';
@@ -5,8 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable, Bootstrap } from '@cloudbeaver/core-di';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { Executor, IExecutor } from '@cloudbeaver/core-executor';
import { ScreenService } from '@cloudbeaver/core-routing';
@@ -17,9 +16,7 @@ import { AppScreenService } from './AppScreenService';
export class AppScreenBootstrap extends Bootstrap {
readonly activation: IExecutor;
constructor(
private readonly screenService: ScreenService
) {
constructor(private readonly screenService: ScreenService) {
super();
this.activation = new Executor();
}
@@ -30,9 +27,11 @@ export class AppScreenBootstrap extends Bootstrap {
routes: [{ name: AppScreenService.screenName, path: '/' }],
component: AppScreen,
root: true,
onActivate: async () => { await this.activation.execute(); },
onActivate: async () => {
await this.activation.execute();
},
});
}
load(): void | Promise<void> { }
load(): void | Promise<void> {}
}
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { PlaceholderContainer } from '@cloudbeaver/core-blocks';
import { injectable } from '@cloudbeaver/core-di';
@@ -5,11 +5,10 @@
* 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 { splitStyles, Split, ResizerControls, Pane, useSplitUserState, useStyles, Loader, getComputed } from '@cloudbeaver/core-blocks';
import { getComputed, Loader, Pane, ResizerControls, Split, splitStyles, useSplitUserState, useStyles } from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { LeftBarPanelService, SideBarPanel, SideBarPanelService } from '@cloudbeaver/core-ui';
@@ -44,30 +43,20 @@ export const Main = observer(function Main() {
return styled(styles)(
<Loader suspense>
<space as="main">
<Split
{...splitMainState}
sticky={30}
mode={leftBarDisabled ? 'minimize' : splitMainState.mode}
disable={leftBarDisabled}
>
<Pane basis='250px' main>
<Split {...splitMainState} sticky={30} mode={leftBarDisabled ? 'minimize' : splitMainState.mode} disable={leftBarDisabled}>
<Pane basis="250px" main>
<Loader suspense>
<SideBarPanel container={leftBarPanelService.tabsContainer} />
</Loader>
</Pane>
<ResizerControls />
<Pane>
<Split
{...splitRightState}
mode={sideBarDisabled ? 'minimize' : splitRightState.mode}
disable={sideBarDisabled}
sticky={30}
>
<Split {...splitRightState} mode={sideBarDisabled ? 'minimize' : splitRightState.mode} disable={sideBarDisabled} sticky={30}>
<Pane>
<RightArea />
</Pane>
<ResizerControls />
<Pane basis='250px' main>
<Pane basis="250px" main>
<Loader suspense>
<SideBarPanel container={sideBarPanelService.tabsContainer} />
</Loader>
@@ -76,6 +65,6 @@ export const Main = observer(function Main() {
</Pane>
</Split>
</space>
</Loader>
</Loader>,
);
});
@@ -5,42 +5,41 @@
* 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 {
Loader,
Pane,
ResizerControls,
SlideBox,
SlideElement,
slideBoxStyles,
SlideElement,
SlideOverlay,
Split,
splitHorizontalStyles,
splitStyles,
SlideOverlay,
useStyles,
useSplitUserState,
Loader
useStyles,
} from '@cloudbeaver/core-blocks';
import { useService } from '@cloudbeaver/core-di';
import { OptionsPanelService } from '@cloudbeaver/core-ui';
import { NavigationTabsBar } from '@cloudbeaver/plugin-navigation-tabs';
import { ToolsPanelService, ToolsPanel } from '@cloudbeaver/plugin-tools-panel';
import { ToolsPanel, ToolsPanelService } from '@cloudbeaver/plugin-tools-panel';
const styles = css`
Pane {
composes: theme-background-surface theme-text-on-surface from global;
display: flex;
overflow: auto;
}
Loader {
height: 100%;
}
SlideBox {
flex: 1;
}
`;
Pane {
composes: theme-background-surface theme-text-on-surface from global;
display: flex;
overflow: auto;
}
Loader {
height: 100%;
}
SlideBox {
flex: 1;
}
`;
interface Props {
className?: string;
@@ -64,21 +63,14 @@ export const RightArea = observer<Props>(function RightArea({ className }) {
</Loader>
</SlideElement>
<SlideElement>
<Split
{...splitState}
sticky={30}
split="horizontal"
mode={toolsDisabled ? 'minimize' : splitState.mode}
disable={toolsDisabled}
keepRatio
>
<Split {...splitState} sticky={30} split="horizontal" mode={toolsDisabled ? 'minimize' : splitState.mode} disable={toolsDisabled} keepRatio>
<Pane>
<Loader suspense>
<NavigationTabsBar />
</Loader>
</Pane>
<ResizerControls />
<Pane basis='30%' main>
<Pane basis="30%" main>
<Loader suspense>
<ToolsPanel container={toolsPanelService.tabsContainer} />
</Loader>
@@ -86,6 +78,6 @@ export const RightArea = observer<Props>(function RightArea({ className }) {
</Split>
<SlideOverlay onClick={() => optionsPanelService.close()} />
</SlideElement>
</SlideBox>
</SlideBox>,
);
});
+15 -18
View File
@@ -5,9 +5,8 @@
* 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 { useRef, useLayoutEffect } from 'react';
import { useLayoutEffect, useRef } from 'react';
import styled, { css } from 'reshadow';
import { Loader, useResource, useStyles } from '@cloudbeaver/core-blocks';
@@ -23,18 +22,18 @@ import { DNDProvider } from '@cloudbeaver/core-ui';
import { useAppVersion } from '@cloudbeaver/plugin-version';
const bodyStyles = css`
theme {
composes: theme-background-surface theme-text-on-surface theme-typography from global;
height: 100vh;
display: flex;
padding: 0 !important; /* fix additional padding with modal reakit menu */
flex-direction: column;
overflow: hidden;
}
Loader {
height: 100vh;
}
`;
theme {
composes: theme-background-surface theme-text-on-surface theme-typography from global;
height: 100vh;
display: flex;
padding: 0 !important; /* fix additional padding with modal reakit menu */
flex-direction: column;
overflow: hidden;
}
Loader {
height: 100vh;
}
`;
export const Body = observer(function Body() {
// const serverConfigLoader = useResource(Body, ServerConfigResource, undefined);
@@ -61,13 +60,11 @@ export const Body = observer(function Body() {
<DNDProvider>
<Loader suspense>
<theme ref={ref} className={`theme-${themeService.currentTheme.id}`}>
<Loader suspense>
{Screen && <Screen {...screenService.routerService.params} />}
</Loader>
<Loader suspense>{Screen && <Screen {...screenService.routerService.params} />}</Loader>
<DialogsPortal />
<Notifications />
</theme>
</Loader>
</DNDProvider>
</DNDProvider>,
);
});
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@cloudbeaver/core-di';
import { PluginManagerService, PluginSettings } from '@cloudbeaver/core-plugin';
+1 -1
View File
@@ -9,4 +9,4 @@ export * from './AppLocaleService';
export * from './Body';
// Interfaces
export * from './manifest';
export * from './manifest';
+1 -4
View File
@@ -5,10 +5,7 @@ export default [
['app_shared_inlineEditor_dialog_title', 'Edit mode'],
['app_shared_inlineEditor_dialog_apply', 'Apply'],
['app_shared_inlineEditor_dialog_cancel', 'Cancel'],
[
'app_shared_navigationTabsBar_placeholder',
'There are no objects to show. Double click on an object in the navigation tree to open it.',
],
['app_shared_navigationTabsBar_placeholder', 'There are no objects to show. Double click on an object in the navigation tree to open it.'],
['app_shared_sql_generators_panel_title', 'Generate SQL'],
['app_shared_sql_generators_dialog_title', 'Generated SQL'],
];
+1 -4
View File
@@ -5,8 +5,5 @@ export default [
['app_shared_inlineEditor_dialog_title', 'Modalità modifica'],
['app_shared_inlineEditor_dialog_apply', 'Applica'],
['app_shared_inlineEditor_dialog_cancel', 'Cancella'],
[
'app_shared_navigationTabsBar_placeholder',
'Non ci sono oggetti da mostrare. Fai doppio click su un oggetto per aprirlo.',
],
['app_shared_navigationTabsBar_placeholder', 'Non ci sono oggetti da mostrare. Fai doppio click su un oggetto per aprirlo.'],
];
+1 -4
View File
@@ -5,10 +5,7 @@ export default [
['app_shared_inlineEditor_dialog_title', '编辑模式'],
['app_shared_inlineEditor_dialog_apply', '应用'],
['app_shared_inlineEditor_dialog_cancel', '取消'],
[
'app_shared_navigationTabsBar_placeholder',
'没有要显示的对象。双击导航树中的对象将其打开。',
],
['app_shared_navigationTabsBar_placeholder', '没有要显示的对象。双击导航树中的对象将其打开。'],
['app_shared_sql_generators_panel_title', '生成SQL'],
['app_shared_sql_generators_dialog_title', '生成的SQL'],
];
+1 -8
View File
@@ -5,7 +5,6 @@
* 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 { AppLocaleService } from './AppLocaleService';
@@ -13,16 +12,10 @@ import { AppScreenBootstrap } from './AppScreen/AppScreenBootstrap';
import { AppScreenService } from './AppScreen/AppScreenService';
import { CoreSettingsService } from './CoreSettingsService';
export const coreAppManifest: PluginManifest = {
info: {
name: 'Core App',
},
providers: [
AppScreenService,
AppScreenBootstrap,
CoreSettingsService,
AppLocaleService,
],
providers: [AppScreenService, AppScreenBootstrap, CoreSettingsService, AppLocaleService],
};
@@ -5,13 +5,10 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { graphql } from 'msw';
import { mockGetActiveUser } from './resolvers/mockGetActiveUser';
export function mockAuthentication(endpoint: ReturnType<typeof graphql.link>) {
return [
endpoint.query('getActiveUser', mockGetActiveUser),
];
}
return [endpoint.query('getActiveUser', mockGetActiveUser)];
}
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { GraphQLContext, GraphQLRequest, ResponseComposition } from 'msw';
import type { GetActiveUserQuery, GetActiveUserQueryVariables } from '@cloudbeaver/core-sdk';
@@ -13,11 +12,11 @@ import type { GetActiveUserQuery, GetActiveUserQueryVariables } from '@cloudbeav
export function mockGetActiveUser(
req: GraphQLRequest<GetActiveUserQueryVariables>,
res: ResponseComposition<GetActiveUserQuery>,
ctx: GraphQLContext<GetActiveUserQuery>
ctx: GraphQLContext<GetActiveUserQuery>,
) {
return res(
ctx.data({
'user': null as unknown as undefined,
user: null as unknown as undefined,
}),
);
}
@@ -5,8 +5,7 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable, Bootstrap } from '@cloudbeaver/core-di';
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { Executor, ExecutorInterrupter, IExecutor } from '@cloudbeaver/core-executor';
import { ServerConfigResource } from '@cloudbeaver/core-root';
import { CachedDataResourceKey, CachedResource, getCachedDataResourceLoaderState } from '@cloudbeaver/core-sdk';
@@ -19,11 +18,7 @@ export class AppAuthService extends Bootstrap {
get authenticated(): boolean {
const user = this.userInfoResource.data;
return (
this.serverConfigResource.anonymousAccessEnabled
|| this.serverConfigResource.configurationMode
|| user !== null
);
return this.serverConfigResource.anonymousAccessEnabled || this.serverConfigResource.configurationMode || user !== null;
}
get loaders(): ILoadableState[] {
@@ -35,10 +30,7 @@ export class AppAuthService extends Bootstrap {
readonly auth: IExecutor<boolean>;
constructor(
private readonly serverConfigResource: ServerConfigResource,
private readonly userInfoResource: UserInfoResource,
) {
constructor(private readonly serverConfigResource: ServerConfigResource, private readonly userInfoResource: UserInfoResource) {
super();
this.auth = new Executor();
this.userInfoResource.onDataUpdate.addHandler(this.authUser.bind(this));
@@ -46,7 +38,7 @@ export class AppAuthService extends Bootstrap {
requireAuthentication<T = CachedDataResourceKey<UserInfoResource>>(
resource: CachedResource<any, any, T, any, any>,
map?: (param: T | undefined) => T
map?: (param: T | undefined) => T,
): this {
resource
.preloadResource(this.userInfoResource, () => {})
@@ -61,14 +53,12 @@ export class AppAuthService extends Bootstrap {
async isAuthNeeded(): Promise<boolean> {
const config = await this.serverConfigResource.load();
if (!config) {
throw new Error('Can\'t configure Authentication');
throw new Error("Can't configure Authentication");
}
const user = await this.userInfoResource.load();
return !this.serverConfigResource.configurationMode
&& !this.serverConfigResource.anonymousAccessEnabled
&& user === null;
return !this.serverConfigResource.configurationMode && !this.serverConfigResource.anonymousAccessEnabled && user === null;
}
async authUser(): Promise<boolean> {
@@ -79,7 +69,7 @@ export class AppAuthService extends Bootstrap {
return state;
}
register(): void { }
register(): void {}
load(): void { }
load(): void {}
}
@@ -5,20 +5,26 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@cloudbeaver/core-di';
import { SessionPermissionsResource, SessionDataResource } from '@cloudbeaver/core-root';
import { AuthProviderConfigurationParametersFragment, CachedMapResource, GetAuthProviderConfigurationParametersQueryVariables, GraphQLService, isResourceAlias, ResourceKey, ResourceKeyUtils } from '@cloudbeaver/core-sdk';
import { SessionDataResource, SessionPermissionsResource } from '@cloudbeaver/core-root';
import {
AuthProviderConfigurationParametersFragment,
CachedMapResource,
GetAuthProviderConfigurationParametersQueryVariables,
GraphQLService,
isResourceAlias,
ResourceKey,
ResourceKeyUtils,
} from '@cloudbeaver/core-sdk';
import { EAdminPermission } from './EAdminPermission';
@injectable()
export class AuthConfigurationParametersResource
extends CachedMapResource<
export class AuthConfigurationParametersResource extends CachedMapResource<
string,
AuthProviderConfigurationParametersFragment[],
GetAuthProviderConfigurationParametersQueryVariables
> {
> {
constructor(
private readonly graphQLService: GraphQLService,
private readonly sessionDataResource: SessionDataResource,
@@ -30,9 +36,7 @@ export class AuthConfigurationParametersResource
permissionsResource.require(this, EAdminPermission.admin);
}
protected async loader(
key: ResourceKey<string>
): Promise<Map<string, AuthProviderConfigurationParametersFragment[]>> {
protected async loader(key: ResourceKey<string>): Promise<Map<string, AuthProviderConfigurationParametersFragment[]>> {
if (isResourceAlias(key)) {
throw new Error('Aliases not supported by this resource.');
}
@@ -5,12 +5,23 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { runInAction } from 'mobx';
import { injectable } from '@cloudbeaver/core-di';
import { SessionPermissionsResource } from '@cloudbeaver/core-root';
import { AdminAuthProviderConfiguration, CachedMapAllKey, CachedMapResource, GetAuthProviderConfigurationsQueryVariables, GraphQLService, isResourceAlias, ResourceKey, ResourceKeyList, resourceKeyList, ResourceKeySimple, ResourceKeyUtils } from '@cloudbeaver/core-sdk';
import {
AdminAuthProviderConfiguration,
CachedMapAllKey,
CachedMapResource,
GetAuthProviderConfigurationsQueryVariables,
GraphQLService,
isResourceAlias,
ResourceKey,
ResourceKeyList,
resourceKeyList,
ResourceKeySimple,
ResourceKeyUtils,
} from '@cloudbeaver/core-sdk';
import type { AuthProviderConfiguration } from './AuthProvidersResource';
import { EAdminPermission } from './EAdminPermission';
@@ -22,17 +33,11 @@ export type AuthConfiguration = AdminAuthProviderConfiguration;
type NewConfiguration = AuthConfiguration & { [NEW_CONFIGURATION_SYMBOL]: boolean; timestamp: number };
@injectable()
export class AuthConfigurationsResource
extends CachedMapResource<string, AuthConfiguration, GetAuthProviderConfigurationsQueryVariables> {
constructor(
private readonly graphQLService: GraphQLService,
permissionsResource: SessionPermissionsResource,
) {
export class AuthConfigurationsResource extends CachedMapResource<string, AuthConfiguration, GetAuthProviderConfigurationsQueryVariables> {
constructor(private readonly graphQLService: GraphQLService, permissionsResource: SessionPermissionsResource) {
super(() => new Map(), []);
permissionsResource
.require(this, EAdminPermission.admin)
.outdateResource(this);
permissionsResource.require(this, EAdminPermission.admin).outdateResource(this);
}
async saveConfiguration(config: AuthConfiguration): Promise<AuthConfiguration> {
@@ -111,15 +116,11 @@ export class AuthConfigurationsResource
}
}
function isNewConfiguration(
configuration: AuthConfiguration | NewConfiguration
): configuration is NewConfiguration {
function isNewConfiguration(configuration: AuthConfiguration | NewConfiguration): configuration is NewConfiguration {
return (configuration as NewConfiguration)[NEW_CONFIGURATION_SYMBOL];
}
export function compareAuthConfigurations(
a: AuthConfiguration, b: AuthConfiguration
): number {
export function compareAuthConfigurations(a: AuthConfiguration, b: AuthConfiguration): number {
if (isNewConfiguration(a) && isNewConfiguration(b)) {
return b.timestamp - a.timestamp;
}
@@ -5,14 +5,13 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@cloudbeaver/core-di';
import { type ITask, AutoRunningTask } from '@cloudbeaver/core-executor';
import { AutoRunningTask, type ITask } from '@cloudbeaver/core-executor';
import { WindowsService } from '@cloudbeaver/core-routing';
import { AuthInfo, AuthStatus, UserInfo } from '@cloudbeaver/core-sdk';
import { uuid } from '@cloudbeaver/core-utils';
import { AuthProvidersResource, AuthProviderConfiguration } from './AuthProvidersResource';
import { AuthProviderConfiguration, AuthProvidersResource } from './AuthProvidersResource';
import { type ILoginOptions, UserInfoResource } from './UserInfoResource';
export interface IUserAuthConfiguration {
@@ -36,14 +35,10 @@ export class AuthInfoService {
for (const token of tokens) {
if (token.authConfiguration) {
const provider = this.authProvidersResource.values.find(
provider => provider.id === token.authProvider
);
const provider = this.authProvidersResource.values.find(provider => provider.id === token.authProvider);
if (provider) {
const configuration = provider.configurations?.find(
configuration => configuration.id === token.authConfiguration
);
const configuration = provider.configurations?.find(configuration => configuration.id === token.authConfiguration);
if (configuration) {
result.push({ providerId: provider.id, configuration });
@@ -58,13 +53,13 @@ export class AuthInfoService {
constructor(
private readonly userInfoResource: UserInfoResource,
private readonly authProvidersResource: AuthProvidersResource,
private readonly windowsService: WindowsService
) {
}
private readonly windowsService: WindowsService,
) {}
login(providerId: string, options: ILoginOptions): ITask<UserInfo | null> {
return new AutoRunningTask(async () => await this.userInfoResource.login(providerId, options))
.then(authInfo => this.federatedAuthentication(providerId, options, authInfo));
return new AutoRunningTask(async () => await this.userInfoResource.login(providerId, options)).then(authInfo =>
this.federatedAuthentication(providerId, options, authInfo),
);
}
async logout(): Promise<void> {
@@ -74,7 +69,7 @@ export class AuthInfoService {
private federatedAuthentication(
providerId: string,
options: ILoginOptions,
{ redirectLink, authId, authStatus }: AuthInfo
{ redirectLink, authId, authStatus }: AuthInfo,
): ITask<UserInfo | null> {
let window: Window | null = null;
let id = providerId;
@@ -101,16 +96,19 @@ export class AuthInfoService {
}
}
return new AutoRunningTask(() => {
if (authId && authStatus === AuthStatus.InProgress) {
return this.userInfoResource.finishFederatedAuthentication(authId, options.linkUser);
}
return new AutoRunningTask(
() => {
if (authId && authStatus === AuthStatus.InProgress) {
return this.userInfoResource.finishFederatedAuthentication(authId, options.linkUser);
}
return AutoRunningTask.resolve(this.userInfoResource.data);
}, () => {
if (window) {
this.windowsService.close(window);
}
});
return AutoRunningTask.resolve(this.userInfoResource.data);
},
() => {
if (window) {
this.windowsService.close(window);
}
},
);
}
}
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@cloudbeaver/core-di';
import { Executor, IExecutor } from '@cloudbeaver/core-executor';
import { md5, uuid } from '@cloudbeaver/core-utils';
@@ -39,9 +38,7 @@ export class AuthProviderService {
private readonly serviceDescriptionLinker: IServiceDescriptionLink[]; // TODO: probably should be replaced by PlaceholderContainer
constructor(
private readonly authProvidersResource: AuthProvidersResource
) {
constructor(private readonly authProvidersResource: AuthProvidersResource) {
this.requestAuthProvider = new Executor();
this.serviceDescriptionLinker = [];
}
@@ -5,12 +5,21 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { computed, makeObservable, runInAction } from 'mobx';
import { injectable } from '@cloudbeaver/core-di';
import { ServerConfigResource } from '@cloudbeaver/core-root';
import { GraphQLService, CachedMapResource, ResourceKey, resourceKeyList, ResourceKeyUtils, CachedMapAllKey, AuthProviderInfoFragment, AuthProviderConfigurationInfoFragment, ResourceKeySimple } from '@cloudbeaver/core-sdk';
import {
AuthProviderConfigurationInfoFragment,
AuthProviderInfoFragment,
CachedMapAllKey,
CachedMapResource,
GraphQLService,
ResourceKey,
resourceKeyList,
ResourceKeySimple,
ResourceKeyUtils,
} from '@cloudbeaver/core-sdk';
import { AuthConfigurationsResource } from './AuthConfigurationsResource';
import { AuthSettingsService } from './AuthSettingsService';
@@ -28,11 +37,15 @@ export class AuthProvidersResource extends CachedMapResource<string, AuthProvide
private readonly authSettingsService: AuthSettingsService,
private readonly graphQLService: GraphQLService,
private readonly serverConfigResource: ServerConfigResource,
private readonly authConfigurationsResource: AuthConfigurationsResource
private readonly authConfigurationsResource: AuthConfigurationsResource,
) {
super();
this.sync(serverConfigResource, () => {}, () => CachedMapAllKey);
this.sync(
serverConfigResource,
() => {},
() => CachedMapAllKey,
);
this.authConfigurationsResource.onItemUpdate.addHandler(this.updateConfigurations.bind(this));
this.authConfigurationsResource.onItemDelete.addHandler(this.deleteConfigurations.bind(this));
@@ -42,7 +55,7 @@ export class AuthProvidersResource extends CachedMapResource<string, AuthProvide
});
}
getConfiguration(providerId: string, configurationId: string): AuthProviderConfiguration | undefined {
getConfiguration(providerId: string, configurationId: string): AuthProviderConfiguration | undefined {
const provider = this.get(providerId);
if (provider) {
@@ -108,9 +121,7 @@ export class AuthProvidersResource extends CachedMapResource<string, AuthProvide
private updateConfigurations(key: ResourceKeySimple<string>) {
const configurations = this.authConfigurationsResource.get(ResourceKeyUtils.toList(key));
const providerIds = resourceKeyList(
configurations.filter(Boolean).map(configuration => configuration!.providerId)
);
const providerIds = resourceKeyList(configurations.filter(Boolean).map(configuration => configuration!.providerId));
this.markOutdated(providerIds);
}
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@cloudbeaver/core-di';
import { SessionPermissionsResource } from '@cloudbeaver/core-root';
import { CachedDataResource, GraphQLService } from '@cloudbeaver/core-sdk';
@@ -14,15 +13,10 @@ import { EAdminPermission } from './EAdminPermission';
@injectable()
export class AuthRolesResource extends CachedDataResource<string[]> {
constructor(
private readonly graphQLService: GraphQLService,
sessionPermissionsResource: SessionPermissionsResource
) {
constructor(private readonly graphQLService: GraphQLService, sessionPermissionsResource: SessionPermissionsResource) {
super(() => []);
sessionPermissionsResource
.require(this, EAdminPermission.admin)
.outdateResource(this);
sessionPermissionsResource.require(this, EAdminPermission.admin).outdateResource(this);
}
protected async loader(): Promise<string[]> {
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import '@testing-library/jest-dom';
import { mockAuthentication } from '@cloudbeaver/core-authentication/mocks/mockAuthentication';
@@ -21,10 +20,7 @@ import { AuthSettings, AuthSettingsService } from './AuthSettingsService';
const endpoint = createGQLEndpoint();
const app = createApp();
const server = mockGraphQL(
...mockAppInit(endpoint),
...mockAuthentication(endpoint)
);
const server = mockGraphQL(...mockAppInit(endpoint), ...mockAuthentication(endpoint));
beforeAll(() => app.init());
@@ -42,13 +38,11 @@ test('Read settings', async () => {
const settings = app.injector.getServiceByClass(AuthSettingsService);
const config = app.injector.getServiceByClass(ServerConfigResource);
server.use(
endpoint.query('serverConfig', mockServerConfig(equalConfig)),
);
server.use(endpoint.query('serverConfig', mockServerConfig(equalConfig)));
await config.refresh();
expect(settings.settings.getValue('baseAuthProvider')).toBe('sd');
expect(settings.settings.getValue('primaryAuthProvider')).toBe('sd');
expect(settings.settings.getValue('disableAnonymousAccess')).toBe(true);
});
});
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@cloudbeaver/core-di';
import { PluginManagerService, PluginSettings } from '@cloudbeaver/core-plugin';
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { UserInfo } from '@cloudbeaver/core-sdk';
import { createDataContext } from '@cloudbeaver/core-view';
@@ -7,5 +7,5 @@
*/
export enum EAdminPermission {
admin = 'admin'
admin = 'admin',
}
@@ -12,4 +12,4 @@ export enum ELMRole {
DATA_MANAGER = 'DATA_MANAGER',
EDITOR = 'EDITOR',
VIEWER = 'VIEWER',
}
}
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@cloudbeaver/core-di';
import { SessionResource } from '@cloudbeaver/core-root';
import { CachedDataResource, GraphQLService, ObjectPropertyInfo } from '@cloudbeaver/core-sdk';
@@ -14,13 +13,14 @@ export type TeamMetaParameter = ObjectPropertyInfo;
@injectable()
export class TeamMetaParametersResource extends CachedDataResource<TeamMetaParameter[]> {
constructor(
private readonly graphQLService: GraphQLService,
sessionResource: SessionResource,
) {
constructor(private readonly graphQLService: GraphQLService, sessionResource: SessionResource) {
super(() => []);
this.sync(sessionResource, () => { }, () => { });
this.sync(
sessionResource,
() => {},
() => {},
);
}
protected async loader(): Promise<TeamMetaParameter[]> {
@@ -5,15 +5,11 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@cloudbeaver/core-di';
import { TeamsResource } from './TeamsResource';
@injectable()
export class TeamsManagerService {
constructor(
readonly teams: TeamsResource
) {
}
constructor(readonly teams: TeamsResource) {}
}
@@ -5,9 +5,20 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@cloudbeaver/core-di';
import { GraphQLService, CachedMapResource, ResourceKey, ResourceKeyUtils, AdminTeamInfoFragment, AdminConnectionGrantInfo, CachedMapAllKey, GetTeamsListQueryVariables, ResourceKeySimple, resourceKeyList, isResourceAlias } from '@cloudbeaver/core-sdk';
import {
AdminConnectionGrantInfo,
AdminTeamInfoFragment,
CachedMapAllKey,
CachedMapResource,
GetTeamsListQueryVariables,
GraphQLService,
isResourceAlias,
ResourceKey,
resourceKeyList,
ResourceKeySimple,
ResourceKeyUtils,
} from '@cloudbeaver/core-sdk';
import { isArraysEqual } from '@cloudbeaver/core-utils';
const NEW_TEAM_SYMBOL = Symbol('new-team');
@@ -23,13 +34,7 @@ export class TeamsResource extends CachedMapResource<string, TeamInfo, TeamResou
super();
}
async createTeam({
teamId,
teamPermissions,
teamName,
description,
metaParameters,
}: TeamInfo): Promise<TeamInfo> {
async createTeam({ teamId, teamPermissions, teamName, description, metaParameters }: TeamInfo): Promise<TeamInfo> {
const response = await this.graphQLService.sdk.createTeam({
teamId,
teamName,
@@ -52,13 +57,7 @@ export class TeamsResource extends CachedMapResource<string, TeamInfo, TeamResou
return this.get(teamId)!;
}
async updateTeam({
teamId,
teamPermissions,
teamName,
description,
metaParameters,
}: TeamInfo): Promise<TeamInfo> {
async updateTeam({ teamId, teamPermissions, teamName, description, metaParameters }: TeamInfo): Promise<TeamInfo> {
const { team } = await this.graphQLService.sdk.updateTeam({
teamId,
teamName,
@@ -105,9 +104,7 @@ export class TeamsResource extends CachedMapResource<string, TeamInfo, TeamResou
return;
}
const {
permissions: newPermissions,
} = await this.graphQLService.sdk.setSubjectPermissions({ subjectId, permissions });
const { permissions: newPermissions } = await this.graphQLService.sdk.setSubjectPermissions({ subjectId, permissions });
if (team) {
team.teamPermissions = newPermissions.map(permission => permission.id);
@@ -121,10 +118,7 @@ export class TeamsResource extends CachedMapResource<string, TeamInfo, TeamResou
await this.graphQLService.sdk.saveTeamMetaParameters({ teamId, parameters });
}
protected async loader(
originalKey: ResourceKey<string>,
includes?: string[]
): Promise<Map<string, TeamInfo>> {
protected async loader(originalKey: ResourceKey<string>, includes?: string[]): Promise<Map<string, TeamInfo>> {
const all = this.isAlias(originalKey, CachedMapAllKey);
const teamsList: TeamInfo[] = [];
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { LocalizationService } from '@cloudbeaver/core-localization';
import { SessionResource } from '@cloudbeaver/core-root';
@@ -18,12 +17,11 @@ const USER_APP_LANGUAGE = 'app.language';
@injectable()
export class UserConfigurationBootstrap extends Bootstrap {
constructor(
private readonly userInfoResource: UserInfoResource,
private readonly themeService: ThemeService,
private readonly localizationService: LocalizationService,
private readonly sessionResource: SessionResource
private readonly sessionResource: SessionResource,
) {
super();
this.userInfoResource.onDataUpdate.addHandler(() => {
@@ -58,10 +56,9 @@ export class UserConfigurationBootstrap extends Bootstrap {
});
}
register(): void { }
register(): void {}
async load(): Promise<void> {
await this.userInfoResource.load();
}
}
}
@@ -5,7 +5,6 @@
* 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';
@@ -19,10 +18,7 @@ export class UserDataService {
private readonly userData: Map<string, Record<string, any>>;
private readonly tempData: TempMap<string, Record<string, any>>;
constructor(
private readonly userInfoResource: UserInfoResource,
private readonly autoSaveService: LocalStorageSaveService,
) {
constructor(private readonly userInfoResource: UserInfoResource, private readonly autoSaveService: LocalStorageSaveService) {
this.userData = new Map();
makeObservable<this, 'userData'>(this, {
@@ -31,11 +27,7 @@ export class UserDataService {
this.tempData = new TempMap(this.userData);
this.autoSaveService.withAutoSave(
'user_data',
this.userData,
() => new Map()
);
this.autoSaveService.withAutoSave('user_data', this.userData, () => new Map());
}
getUserData<T extends Record<any, any>>(key: string, defaultValue: () => T, validate?: (data: T) => boolean): T {
@@ -47,10 +39,7 @@ export class UserDataService {
const data = this.tempData.get(userId)!;
if (
!(key in data)
|| validate?.(data[key]) === false
) {
if (!(key in data) || validate?.(data[key]) === false) {
data[key] = observable(defaultValue());
}
@@ -5,13 +5,22 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { computed, makeObservable, runInAction } from 'mobx';
import { injectable } from '@cloudbeaver/core-di';
import { SyncExecutor, ISyncExecutor, ITask, AutoRunningTask, whileTask } from '@cloudbeaver/core-executor';
import { AutoRunningTask, ISyncExecutor, ITask, SyncExecutor, whileTask } from '@cloudbeaver/core-executor';
import { SessionDataResource, SessionResource } from '@cloudbeaver/core-root';
import { AuthInfo, AuthStatus, CachedDataResource, GetActiveUserQueryVariables, GraphQLService, ResourceKeySimple, ResourceKeyUtils, UserAuthToken, UserInfo } from '@cloudbeaver/core-sdk';
import {
AuthInfo,
AuthStatus,
CachedDataResource,
GetActiveUserQueryVariables,
GraphQLService,
ResourceKeySimple,
ResourceKeyUtils,
UserAuthToken,
UserInfo,
} from '@cloudbeaver/core-sdk';
import { AUTH_PROVIDER_LOCAL_ID } from './AUTH_PROVIDER_LOCAL_ID';
import { AuthProviderService } from './AuthProviderService';
@@ -27,11 +36,7 @@ export interface ILoginOptions {
}
@injectable()
export class UserInfoResource extends CachedDataResource<
UserInfo | null,
void,
UserInfoIncludes
> {
export class UserInfoResource extends CachedDataResource<UserInfo | null, void, UserInfoIncludes> {
readonly onUserChange: ISyncExecutor<string>;
get authRole(): ELMRole | undefined {
@@ -46,13 +51,17 @@ UserInfoIncludes
private readonly graphQLService: GraphQLService,
private readonly authProviderService: AuthProviderService,
private readonly sessionResource: SessionResource,
private readonly sessionDataResource: SessionDataResource
private readonly sessionDataResource: SessionDataResource,
) {
super(() => null, undefined, ['customIncludeOriginDetails', 'includeConfigurationParameters']);
this.onUserChange = new SyncExecutor();
this.sync(sessionResource, () => {}, () => {});
this.sync(
sessionResource,
() => {},
() => {},
);
makeObservable(this, {
parametersAvailable: computed,
@@ -77,15 +86,10 @@ UserInfoIncludes
}
// TODO: will be changed due wrong origin in authTokens
return (
this.data.authTokens.some(token => token.authProvider === providerId)
);
return this.data.authTokens.some(token => token.authProvider === providerId);
}
async login(
provider: string,
{ credentials, configurationId, linkUser }: ILoginOptions
): Promise<AuthInfo> {
async login(provider: string, { credentials, configurationId, linkUser }: ILoginOptions): Promise<AuthInfo> {
let processedCredentials: Record<string, any> | undefined;
if (credentials) {
@@ -106,7 +110,7 @@ UserInfoIncludes
this.resetIncludes();
this.markOutdated();
} else {
this.data.authTokens.push(...authInfo.userTokens as UserAuthToken[]);
this.data.authTokens.push(...(authInfo.userTokens as UserAuthToken[]));
}
this.sessionDataResource.markOutdated();
@@ -118,49 +122,49 @@ UserInfoIncludes
finishFederatedAuthentication(authId: string, linkUser?: boolean): ITask<UserInfo | null> {
let activeTask: ITask<AuthInfo> | undefined;
return new AutoRunningTask<UserInfo | null>(() => this.performUpdate(
undefined,
[],
async () => {
activeTask = whileTask<AuthInfo>(
authInfo => {
if (authInfo.authStatus === AuthStatus.Success) {
return true;
} else if (authInfo.authStatus === AuthStatus.Error) {
throw new Error('Authentication error');
return new AutoRunningTask<UserInfo | null>(
() =>
this.performUpdate(undefined, [], async () => {
activeTask = whileTask<AuthInfo>(
authInfo => {
if (authInfo.authStatus === AuthStatus.Success) {
return true;
} else if (authInfo.authStatus === AuthStatus.Error) {
throw new Error('Authentication error');
}
return false;
},
async () => {
const { authInfo } = await this.graphQLService.sdk.getAuthStatus({
authId,
linkUser,
customIncludeOriginDetails: true,
});
return authInfo as AuthInfo;
},
1000,
);
const authInfo = await activeTask;
if (authInfo.userTokens && authInfo.authStatus === AuthStatus.Success) {
if (this.data === null) {
this.resetIncludes();
this.setData(await this.loader());
} else {
this.data.authTokens.push(...(authInfo.userTokens as UserAuthToken[]));
}
return false;
},
async () => {
const { authInfo } = await this.graphQLService.sdk.getAuthStatus({
authId,
linkUser,
customIncludeOriginDetails: true,
});
return authInfo as AuthInfo;
},
1000
);
const authInfo = await activeTask;
if (authInfo.userTokens && authInfo.authStatus === AuthStatus.Success) {
if (this.data === null) {
this.resetIncludes();
this.setData(await this.loader());
} else {
this.data.authTokens.push(...authInfo.userTokens as UserAuthToken[]);
this.sessionDataResource.markOutdated();
}
this.sessionDataResource.markOutdated();
}
return this.data;
}
), () => {
activeTask?.cancel();
});
return this.data;
}),
() => {
activeTask?.cancel();
},
);
}
async logout(provider?: string, configuration?: string): Promise<void> {
@@ -230,10 +234,7 @@ UserInfoIncludes
return this.data?.configurationParameters[key];
}
protected async loader(
key: void,
includes?: ReadonlyArray<string>,
): Promise<UserInfo | null> {
protected async loader(key: void, includes?: ReadonlyArray<string>): Promise<UserInfo | null> {
const { user } = await this.graphQLService.sdk.getActiveUser({
...this.getDefaultIncludes(),
...this.getIncludesMap(key, includes),
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { injectable } from '@cloudbeaver/core-di';
import { ExecutorInterrupter } from '@cloudbeaver/core-executor';
import { SessionResource } from '@cloudbeaver/core-root';
@@ -14,7 +13,7 @@ import { CachedDataResource, GraphQLService, UserConnectionAuthPropertiesFragmen
import { UserInfoResource } from './UserInfoResource';
export type UserMetaParameter = UserConnectionAuthPropertiesFragment;
export interface IUserMetaParameterOptions{
export interface IUserMetaParameterOptions {
id: string;
displayName: string;
description?: string;
@@ -23,17 +22,15 @@ export interface IUserMetaParameterOptions{
@injectable()
export class UserMetaParametersResource extends CachedDataResource<UserMetaParameter[]> {
constructor(
private readonly graphQLService: GraphQLService,
sessionResource: SessionResource,
userInfoResource: UserInfoResource
) {
constructor(private readonly graphQLService: GraphQLService, sessionResource: SessionResource, userInfoResource: UserInfoResource) {
super(() => []);
this.sync(sessionResource, () => {}, () => {});
this
.preloadResource(userInfoResource, () => {})
.before(ExecutorInterrupter.interrupter(() => userInfoResource.data === null));
this.sync(
sessionResource,
() => {},
() => {},
);
this.preloadResource(userInfoResource, () => {}).before(ExecutorInterrupter.interrupter(() => userInfoResource.data === null));
}
async add(options: IUserMetaParameterOptions): Promise<UserMetaParameter> {
@@ -5,25 +5,24 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { runInAction } from 'mobx';
import { injectable } from '@cloudbeaver/core-di';
import { ServerConfigResource, SessionPermissionsResource } from '@cloudbeaver/core-root';
import {
GraphQLService,
CachedMapResource,
ResourceKey,
AdminConnectionGrantInfo,
AdminUserInfoFragment,
AdminUserInfo,
ResourceKeyUtils,
GetUsersListQueryVariables,
AdminUserInfoFragment,
CachedMapAllKey,
resourceKeyList,
CachedMapResource,
GetUsersListQueryVariables,
GraphQLService,
isResourceAlias,
isResourceKeyAlias,
ResourceKey,
resourceKeyList,
ResourceKeySimple,
isResourceAlias
ResourceKeyUtils,
} from '@cloudbeaver/core-sdk';
import { AUTH_PROVIDER_LOCAL_ID } from './AUTH_PROVIDER_LOCAL_ID';
@@ -56,13 +55,11 @@ export class UsersResource extends CachedMapResource<string, AdminUser, UserReso
private readonly serverConfigResource: ServerConfigResource,
private readonly authProviderService: AuthProviderService,
private readonly authInfoService: AuthInfoService,
sessionPermissionsResource: SessionPermissionsResource
sessionPermissionsResource: SessionPermissionsResource,
) {
super();
sessionPermissionsResource
.require(this, EAdminPermission.admin)
.outdateResource(this);
sessionPermissionsResource.require(this, EAdminPermission.admin).outdateResource(this);
}
isNew(id: string): boolean {
@@ -79,10 +76,12 @@ export class UsersResource extends CachedMapResource<string, AdminUser, UserReso
grantedConnections: [],
configurationParameters: {},
metaParameters: {},
origins: [{
type: AUTH_PROVIDER_LOCAL_ID,
displayName: 'Local',
}],
origins: [
{
type: AUTH_PROVIDER_LOCAL_ID,
displayName: 'Local',
},
],
linkedAuthProviders: [AUTH_PROVIDER_LOCAL_ID],
enabled: true,
authRole: this.serverConfigResource.data?.defaultAuthRole ?? undefined,
@@ -103,15 +102,7 @@ export class UsersResource extends CachedMapResource<string, AdminUser, UserReso
await this.graphQLService.sdk.saveUserMetaParameters({ userId, parameters });
}
async create({
userId,
teams,
credentials,
metaParameters,
grantedConnections,
enabled,
authRole,
}: UserCreateOptions): Promise<AdminUser> {
async create({ userId, teams, credentials, metaParameters, grantedConnections, enabled, authRole }: UserCreateOptions): Promise<AdminUser> {
const { user } = await this.graphQLService.sdk.createUser({
userId,
enabled,
@@ -129,7 +120,7 @@ export class UsersResource extends CachedMapResource<string, AdminUser, UserReso
await this.setConnections(userId, grantedConnections);
await this.setMetaParameters(userId, metaParameters);
const user = await this.refresh(userId) as unknown as AdminUserNew;
const user = (await this.refresh(userId)) as unknown as AdminUserNew;
user[NEW_USER_SYMBOL] = true;
} catch (exception: any) {
this.delete(userId);
@@ -191,7 +182,7 @@ export class UsersResource extends CachedMapResource<string, AdminUser, UserReso
async delete(key: ResourceKeySimple<string>): Promise<void> {
await ResourceKeyUtils.forEachAsync(key, async key => {
if (this.isActiveUser(key)) {
throw new Error('You can\'t delete current logged user');
throw new Error("You can't delete current logged user");
}
await this.graphQLService.sdk.deleteUser({ userId: key });
super.delete(key);
@@ -214,10 +205,7 @@ export class UsersResource extends CachedMapResource<string, AdminUser, UserReso
return this.authInfoService.userInfo?.userId === userId;
}
protected async loader(
originalKey: ResourceKey<string>,
includes?: string[]
): Promise<Map<string, AdminUser>> {
protected async loader(originalKey: ResourceKey<string>, includes?: string[]): Promise<Map<string, AdminUser>> {
const all = this.isAlias(originalKey, CachedMapAllKey);
const usersList: AdminUser[] = [];
@@ -18,4 +18,4 @@ export * from './UserInfoResource';
export * from './UserMetaParametersResource';
export * from './UsersResource';
export * from './TeamMetaParametersResource';
export * from './EAdminPermission';
export * from './EAdminPermission';
@@ -5,7 +5,6 @@
* 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 { AppAuthService } from './AppAuthService';
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type React from 'react';
import styled, { css } from 'reshadow';
@@ -18,13 +17,14 @@ const style = css`
background-color: #2a7cb4;
padding: 5px 16px;
border-radius: 4px;
letter-spacing: .08929em;
font-size: .875rem;
letter-spacing: 0.08929em;
font-size: 0.875rem;
text-transform: uppercase;
cursor: pointer;
outline: none;
border: none;
&:hover, &:focus {
&:hover,
&:focus {
opacity: 0.8;
}
&:active {
@@ -44,5 +44,9 @@ export const AppRefreshButton: React.FC<IProps> = function AppRefreshButton({ cl
app.start();
}
return styled(style)(<button className={className} onClick={refresh}>Refresh</button>);
return styled(style)(
<button className={className} onClick={refresh}>
Refresh
</button>,
);
};
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { Bootstrap, injectable } from '@cloudbeaver/core-di';
import { LocalizationService } from '@cloudbeaver/core-localization';
+76 -78
View File
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observable } from 'mobx';
import { observer } from 'mobx-react-lite';
import styled, { css, use } from 'reshadow';
@@ -18,84 +17,83 @@ import { useObservableRef } from './useObservableRef';
import { useStyles } from './useStyles';
const buttonStyles = css`
button-label {
composes: theme-button__label from global;
button-label {
composes: theme-button__label from global;
}
button-icon {
composes: theme-button__icon from global;
}
ripple {
composes: theme-button_ripple from global;
}
Button {
composes: theme-button from global;
display: flex;
& IconOrImage {
width: 100%;
}
button-icon {
composes: theme-button__icon from global;
&[disabled] IconOrImage {
opacity: 0.5;
}
ripple {
composes: theme-button_ripple from global;
& Loader,
& button-label {
transition: opacity cubic-bezier(0.4, 0, 0.2, 1) 0.3s;
}
Button {
composes: theme-button from global;
display: flex;
& IconOrImage {
width: 100%;
}
& Loader {
position: absolute;
opacity: 0 !important;
}
&[disabled] IconOrImage {
opacity: 0.5;
}
& Loader, & button-label {
transition: opacity cubic-bezier(0.4, 0.0, 0.2, 1) 0.3s;
}
& button-label {
opacity: 1;
}
&[|loading] {
& Loader {
position: absolute;
opacity: 0 !important;
opacity: 1 !important;
}
& button-label {
opacity: 1;
}
&[|loading] {
& Loader {
opacity: 1 !important;
}
& button-label {
opacity: 0;
}
}
&[href] {
text-decoration: none !important;
opacity: 0;
}
}
`;
&[href] {
text-decoration: none !important;
}
}
`;
const buttonMod = {
raised: css`
Button {
composes: theme-button_raised from global;
}
`,
`,
unelevated: css`
Button {
composes: theme-button_unelevated from global;
}
`,
`,
outlined: css`
Button {
composes: theme-button_outlined from global;
}
`,
`,
secondary: css`
Button {
composes: theme-button_secondary from global;
}
`,
`,
};
type ButtonProps = (
React.ButtonHTMLAttributes<HTMLButtonElement | HTMLAnchorElement>
& React.LinkHTMLAttributes<HTMLLinkElement | HTMLButtonElement>
& React.HTMLAttributes<HTMLDivElement>
) & {
type ButtonProps = (React.ButtonHTMLAttributes<HTMLButtonElement | HTMLAnchorElement> &
React.LinkHTMLAttributes<HTMLLinkElement | HTMLButtonElement> &
React.HTMLAttributes<HTMLDivElement>) & {
loading?: boolean;
icon?: string;
viewBox?: string;
@@ -105,9 +103,7 @@ type ButtonProps = (
href?: string;
target?: '_blank' | '_self' | '_parent' | '_top';
loader?: boolean;
onClick?: React.MouseEventHandler<
HTMLButtonElement | HTMLAnchorElement | HTMLLinkElement | HTMLDivElement
> | (() => Promise<any>);
onClick?: React.MouseEventHandler<HTMLButtonElement | HTMLAnchorElement | HTMLLinkElement | HTMLDivElement> | (() => Promise<any>);
download?: boolean;
};
@@ -126,24 +122,29 @@ export const Button = observer<ButtonProps>(function Button({
className,
...rest
}) {
const state = useObservableRef(() => ({
loading: false,
}), {
loading: observable.ref,
}, {
click(e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement | HTMLLinkElement | HTMLDivElement>) {
const returnValue = onClick?.(e);
if (returnValue instanceof Promise) {
if (loader) {
this.loading = true;
returnValue.finally(() => {
this.loading = false;
});
}
}
const state = useObservableRef(
() => ({
loading: false,
}),
{
loading: observable.ref,
},
}, ['click']);
{
click(e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement | HTMLLinkElement | HTMLDivElement>) {
const returnValue = onClick?.(e);
if (returnValue instanceof Promise) {
if (loader) {
this.loading = true;
returnValue.finally(() => {
this.loading = false;
});
}
}
},
},
['click'],
);
loading = state.loading || loading;
@@ -153,18 +154,15 @@ export const Button = observer<ButtonProps>(function Button({
const Button = tag;
return styled(useStyles(styles, buttonStyles, ...(mod || []).map(mod => buttonMod[mod])))(
<Button
{...rest}
type={type}
disabled={disabled}
{...use({ loading })}
className={className}
onClick={state.click}
>
<Button {...rest} type={type} disabled={disabled} {...use({ loading })} className={className} onClick={state.click}>
<ripple />
{icon && <button-icon><IconOrImage icon={icon} viewBox={viewBox} /></button-icon>}
<button-label as='span'>{children}</button-label>
{icon && (
<button-icon>
<IconOrImage icon={icon} viewBox={viewBox} />
</button-icon>
)}
<button-label as="span">{children}</button-label>
<Loader small />
</Button>
</Button>,
);
});
+30 -31
View File
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import styled, { css } from 'reshadow';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
@@ -22,40 +21,40 @@ interface Props {
}
const styles = css`
main {
position: relative;
display: flex;
align-items: center;
padding: 8px;
}
before {
margin-right: 16px;
width: 24px;
height: 24px;
flex-shrink: 0;
}
after {
margin-left: 16px;
flex-shrink: 0;
}
info {
composes: theme-typography--body2 from global;
flex: 1;
line-height: 1.4;
display: flex;
flex-direction: column;
font-weight: 500;
}
description {
composes: theme-typography--caption from global;
line-height: 1.2;
}
main {
position: relative;
display: flex;
align-items: center;
padding: 8px;
}
before {
margin-right: 16px;
width: 24px;
height: 24px;
flex-shrink: 0;
}
after {
margin-left: 16px;
flex-shrink: 0;
}
info {
composes: theme-typography--body2 from global;
flex: 1;
line-height: 1.4;
display: flex;
flex-direction: column;
font-weight: 500;
}
description {
composes: theme-typography--caption from global;
line-height: 1.2;
}
`;
const RIPPLE_STYLES = css`
cell {
composes: theme-ripple from global;
}
}
`;
export const Cell: React.FC<React.PropsWithChildren<Props>> = function Cell({
@@ -77,6 +76,6 @@ export const Cell: React.FC<React.PropsWithChildren<Props>> = function Cell({
</info>
<after>{after}</after>
</main>
</cell>
</cell>,
);
};
+7 -10
View File
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import styled, { css } from 'reshadow';
interface Props {
@@ -13,19 +12,17 @@ interface Props {
}
const styles = css`
code-container {
composes: theme-background-secondary theme-text-on-secondary from global;
padding: 16px;
border-radius: var(--theme-group-element-radius);
}
code-container {
composes: theme-background-secondary theme-text-on-secondary from global;
padding: 16px;
border-radius: var(--theme-group-element-radius);
}
`;
export const Code: React.FC<React.PropsWithChildren<Props>> = function Code({ children, className }) {
return styled(styles)(
<code-container className={className}>
<code>
{children}
</code>
</code-container>
<code>{children}</code>
</code-container>,
);
};
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { LoadingError } from '@cloudbeaver/core-utils';
export interface IComplexLoaderData<T> {
@@ -65,7 +64,7 @@ export function createComplexLoader<T>(loader: () => Promise<T>): IComplexLoader
this.data = await this.promise;
return this.data;
} catch (cause: any) {
this.error = new LoadingError(() => this.refresh(), 'Can\'t load element', { cause });
this.error = new LoadingError(() => this.refresh(), "Can't load element", { cause });
throw this.error;
}
},
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { ConnectionImageWithMaskSvg } from './ConnectionImageWithMaskSvg';
import { ConnectionMark } from './ConnectionMark';
@@ -19,9 +18,7 @@ interface Props {
className?: string;
}
export const ConnectionImageWithMask: React.FC<Props> = (
{ icon, connected, maskId, size, markerRadius, paddingSize, className }
) => (
export const ConnectionImageWithMask: React.FC<Props> = ({ icon, connected, maskId, size, markerRadius, paddingSize, className }) => (
<>
<ConnectionImageWithMaskSvg
icon={icon}
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { GlobalConstants, isValidUrl } from '@cloudbeaver/core-utils';
interface Props {
@@ -18,9 +17,7 @@ interface Props {
className?: string;
}
export const ConnectionImageWithMaskSvg: React.FC<Props> = (
{ icon, connected, maskId, size = 16, markerRadius = 4, paddingSize = 0, className }
) => {
export const ConnectionImageWithMaskSvg: React.FC<Props> = ({ icon, connected, maskId, size = 16, markerRadius = 4, paddingSize = 0, className }) => {
if (!icon) {
return null;
}
@@ -42,7 +39,14 @@ export const ConnectionImageWithMaskSvg: React.FC<Props> = (
<mask id={maskId}>
<rect fill="#fff" x="0" y="0" width={size} height={size} />
<circle fill="#000" cx={circleParams.coordinate} cy={circleParams.coordinate} r={circleParams.radius} />
<rect fill="#000" x={rectParams.coordinate} y={rectParams.coordinate} width={rectParams.size} height={rectParams.size} mask={`url(#${maskId})`} />
<rect
fill="#000"
x={rectParams.coordinate}
y={rectParams.coordinate}
width={rectParams.size}
height={rectParams.size}
mask={`url(#${maskId})`}
/>
</mask>
<image xlinkHref={url} width={size} height={size} mask={connected ? `url(#${maskId})` : undefined} />
</svg>
@@ -5,26 +5,25 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import styled, { css, use } from 'reshadow';
const styles = css`
status {
composes: theme-background-positive from global;
position: absolute;
opacity: 0;
transition: opacity 0.3s ease;
bottom: 0;
right: 0;
box-sizing: border-box;
width: 8px;
height: 8px;
border-radius: 50%;
status {
composes: theme-background-positive from global;
position: absolute;
opacity: 0;
transition: opacity 0.3s ease;
bottom: 0;
right: 0;
box-sizing: border-box;
width: 8px;
height: 8px;
border-radius: 50%;
&[|connected] {
opacity: 1;
}
&[|connected] {
opacity: 1;
}
}
`;
interface Props {
@@ -33,7 +32,5 @@ interface Props {
}
export const ConnectionMark: React.FC<Props> = function ConnectionMark({ connected, className }) {
return styled(styles)(
<status {...use({ connected })} className={className} />
);
};
return styled(styles)(<status {...use({ connected })} className={className} />);
};
@@ -5,217 +5,234 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { css } from 'reshadow';
export const BASE_CONTAINERS_STYLES = css`
Group {
composes: theme-background-surface theme-text-on-surface from global;
Group {
composes: theme-background-surface theme-text-on-surface from global;
}
ColoredContainer {
composes: theme-background-secondary theme-text-on-secondary from global;
}
GroupSubTitle {
composes: theme-text-text-hint-on-light from global;
}
Container,
ColoredContainer,
Group {
display: flex;
flex-direction: row;
align-content: baseline;
position: relative;
&[hideEmpty]:empty {
display: none;
}
ColoredContainer {
composes: theme-background-secondary theme-text-on-secondary from global;
}
GroupSubTitle {
composes: theme-text-text-hint-on-light from global;
}
Container, ColoredContainer, Group {
display: flex;
flex-direction: row;
align-content: baseline;
position: relative;
&[hideEmpty]:empty {
display: none;
&[vertical] {
flex-direction: column;
align-content: stretch;
& > :global(*) {
flex-basis: 0 !important;
}
&[vertical] {
flex-direction: column;
align-content: stretch;
& > :global(*) {
flex-basis: 0 !important;
}
& > [keepSize] {
flex-basis: auto !important;
}
}
&[baseline] {
align-items: baseline;
}
&[flexStart]{
align-items: flex-start;
}
&[center] {
align-items: center;
justify-content: center;
align-content: center;
}
&[wrap] {
flex-wrap: wrap;
}
&[overflow] {
overflow: auto;
}
&[parent] {
padding: 24px;
&[compact] {
padding: 16px;
}
&[dense] {
padding: 8px;
}
}
&[gap] {
gap: 24px;
&[compact] {
gap: 16px;
}
&[dense] {
gap: 8px;
}
& > [keepSize] {
flex-basis: auto !important;
}
}
Group {
align-content: baseline;
box-sizing: border-box;
&[baseline] {
align-items: baseline;
}
&[flexStart] {
align-items: flex-start;
}
&[center] {
align-items: center;
justify-content: center;
align-content: center;
}
&[wrap] {
flex-wrap: wrap;
}
&[overflow] {
overflow: auto;
}
&[parent] {
padding: 24px;
border-radius: var(--theme-group-element-radius);
&[compact] {
padding: 16px;
}
&[dense] {
padding: 8px;
}
&[form] > :global(*) {
margin-right: 25%;
}
&[box] {
padding: 0;
overflow: hidden;
}
&[box="no-overflow"] {
padding: 0;
overflow: initial;
}
&[center] {
margin: 0 auto;
}
}
Container, ColoredContainer, Group {
&[grid] {
display: grid;
&[gap] {
gap: 24px;
&[compact] {
gap: 16px;
}
/* increase css specificity */
&[grid]:nth-child(n) {
flex-basis: unset;
max-width: unset;
}
&[grid][tiny] {
grid-template-columns: repeat(auto-fit, minmax(140px, max-content));
}
&[grid][small] {
grid-template-columns: repeat(auto-fit, minmax(260px, max-content));
}
&[grid][medium] {
grid-template-columns: repeat(auto-fit, minmax(460px, max-content));
}
&[grid][large] {
grid-template-columns: repeat(auto-fit, minmax(800px, max-content));
&[dense] {
gap: 8px;
}
}
}
Container, ColoredContainer, Group {
flex-wrap: wrap;
Group {
align-content: baseline;
box-sizing: border-box;
padding: 24px;
border-radius: var(--theme-group-element-radius);
&[dense] {
padding: 8px;
}
&[form] > :global(*) {
margin-right: 25%;
}
&[box] {
padding: 0;
overflow: hidden;
}
&[box='no-overflow'] {
padding: 0;
overflow: initial;
}
&[center] {
margin: 0 auto;
}
}
Container,
ColoredContainer,
Group {
&[grid] {
display: grid;
}
/* increase css specificity */
&[grid]:nth-child(n) {
flex-basis: unset;
max-width: unset;
}
&[grid][tiny] {
grid-template-columns: repeat(auto-fit, minmax(140px, max-content));
}
&[grid][small] {
grid-template-columns: repeat(auto-fit, minmax(260px, max-content));
}
&[grid][medium] {
grid-template-columns: repeat(auto-fit, minmax(460px, max-content));
}
&[grid][large] {
grid-template-columns: repeat(auto-fit, minmax(800px, max-content));
}
}
Container,
ColoredContainer,
Group {
flex-wrap: wrap;
flex: 1 1 100%;
& > :global(*) {
flex: 1 1 100%;
& > :global(*) {
flex: 1 1 100%;
}
&[keepSize], & > [keepSize] {
flex-grow: 0;
flex-basis: 0;
flex-basis: auto; /* test for layout */
}
&[tiny], & > [tiny], &[grid][tiny] > * {
flex-basis: 140px;
max-width: 210px;
}
&[small], & > [small], &[grid][small] > * {
flex-basis: 260px;
max-width: 390px;
}
&[medium], & > [medium], &[grid][medium] > * {
flex-basis: 460px;
max-width: 640px;
}
&[large], & > [large], &[grid][large] > * {
flex-basis: 800px;
max-width: 800px;
}
&[maximum], & > [maximum], &[grid][maximum] > * {
max-width: 100%;
}
&[fill], & > [fill] {
max-width: none;
}
}
GroupItem {
min-width: min-content;
&[keepSize],
& > [keepSize] {
flex-grow: 0;
flex-basis: 0;
flex-basis: auto; /* test for layout */
}
GroupTitle {
composes: theme-typography--body2 from global;
font-weight: 400;
margin: 0;
text-transform: uppercase;
opacity: 0.9;
&[tiny],
& > [tiny],
&[grid][tiny] > * {
flex-basis: 140px;
max-width: 210px;
}
Group[box] > GroupTitle {
padding: 24px;
&[small],
& > [small],
&[grid][small] > * {
flex-basis: 260px;
max-width: 390px;
}
GroupSubTitle {
composes: theme-typography--caption from global;
display: block;
text-transform: none;
&[medium],
& > [medium],
&[grid][medium] > * {
flex-basis: 460px;
max-width: 640px;
}
GroupClose {
width: 18px;
height: 18px;
cursor: pointer;
display: flex;
position: absolute;
right: 24px;
margin-right: 0 !important;
&[large],
& > [large],
&[grid][large] > * {
flex-basis: 800px;
max-width: 800px;
}
`;
&[maximum],
& > [maximum],
&[grid][maximum] > * {
max-width: 100%;
}
&[fill],
& > [fill] {
max-width: none;
}
}
GroupItem {
min-width: min-content;
}
GroupTitle {
composes: theme-typography--body2 from global;
font-weight: 400;
margin: 0;
text-transform: uppercase;
opacity: 0.9;
}
Group[box] > GroupTitle {
padding: 24px;
}
GroupSubTitle {
composes: theme-typography--caption from global;
display: block;
text-transform: none;
}
GroupClose {
width: 18px;
height: 18px;
cursor: pointer;
display: flex;
position: absolute;
right: 24px;
margin-right: 0 !important;
}
`;
@@ -5,13 +5,15 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { forwardRef } from 'react';
import { filterContainerFakeProps } from './filterContainerFakeProps';
import type { IContainerProps } from './IContainerProps';
export const ColoredContainer = forwardRef<HTMLDivElement, IContainerProps & React.HTMLAttributes<HTMLDivElement>>(function ColoredContainer(props, ref) {
export const ColoredContainer = forwardRef<HTMLDivElement, IContainerProps & React.HTMLAttributes<HTMLDivElement>>(function ColoredContainer(
props,
ref,
) {
const divProps = filterContainerFakeProps(props);
return <div ref={ref} {...divProps} />;
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { forwardRef } from 'react';
import { filterContainerFakeProps } from './filterContainerFakeProps';
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { forwardRef } from 'react';
import { filterContainerFakeProps } from './filterContainerFakeProps';
@@ -17,12 +16,7 @@ interface Props extends IContainerProps {
box?: boolean | 'no-overflow';
}
export const Group = forwardRef<HTMLDivElement, Props & React.HTMLAttributes<HTMLDivElement>>(function Group({
form,
center,
box,
...rest
}, ref) {
export const Group = forwardRef<HTMLDivElement, Props & React.HTMLAttributes<HTMLDivElement>>(function Group({ form, center, box, ...rest }, ref) {
const divProps = filterContainerFakeProps(rest);
return <div ref={ref} {...divProps} />;
@@ -5,16 +5,16 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { Icon } from '../Icon';
interface IProps {
onClick?: () => void;
}
export const GroupClose: React.FC<IProps & React.HTMLAttributes<HTMLDivElement>> = function GroupClose({
onClick,
...rest
}) {
return <div {...rest}><Icon name="cross" viewBox="0 0 16 16" onClick={onClick} /></div>;
export const GroupClose: React.FC<IProps & React.HTMLAttributes<HTMLDivElement>> = function GroupClose({ onClick, ...rest }) {
return (
<div {...rest}>
<Icon name="cross" viewBox="0 0 16 16" onClick={onClick} />
</div>
);
};
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { filterLayoutFakeProps } from './filterLayoutFakeProps';
import type { ILayoutSizeProps } from './ILayoutSizeProps';
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { filterLayoutFakeProps } from './filterLayoutFakeProps';
import type { ILayoutSizeProps } from './ILayoutSizeProps';
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { filterLayoutFakeProps } from './filterLayoutFakeProps';
import type { ILayoutSizeProps } from './ILayoutSizeProps';
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { ILayoutSizeProps } from './ILayoutSizeProps';
export interface IContainerProps extends ILayoutSizeProps {
@@ -5,26 +5,12 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { filterLayoutFakeProps } from './filterLayoutFakeProps';
import type { IContainerProps } from './IContainerProps';
export function filterContainerFakeProps<T extends IContainerProps>(props: T): Omit<T, keyof IContainerProps> {
const {
hideEmpty,
flexStart,
baseline,
center,
vertical,
wrap,
overflow,
parent,
gap,
grid,
dense,
compact,
...rest
} = filterLayoutFakeProps(props);
const { hideEmpty, flexStart, baseline, center, vertical, wrap, overflow, parent, gap, grid, dense, compact, ...rest } =
filterLayoutFakeProps(props);
return rest as Omit<T, keyof IContainerProps>;
}
@@ -5,20 +5,10 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type { ILayoutSizeProps } from './ILayoutSizeProps';
export function filterLayoutFakeProps<T extends ILayoutSizeProps>(props: T): Omit<T, keyof ILayoutSizeProps> {
const {
keepSize,
tiny,
small,
medium,
large,
maximum,
fill,
...rest
} = props;
const { keepSize, tiny, small, medium, large, maximum, fill, ...rest } = props;
return rest;
}
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import type React from 'react';
import styled, { css, use } from 'reshadow';
@@ -51,14 +50,7 @@ interface Props {
styles?: ComponentStyle;
}
export const DisplayError: React.FC<React.PropsWithChildren<Props>> = function DisplayError({
root,
children,
error,
errorInfo,
className,
styles,
}) {
export const DisplayError: React.FC<React.PropsWithChildren<Props>> = function DisplayError({ root, children, error, errorInfo, className, styles }) {
const stack = errorInfo?.componentStack || error?.stack;
return styled(useStyles(style, styles))(
@@ -76,6 +68,6 @@ export const DisplayError: React.FC<React.PropsWithChildren<Props>> = function D
</details>
)}
</error-inner-block>
</error>
</error>,
);
};
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import React, { ErrorInfo } from 'react';
import styled, { css } from 'reshadow';
@@ -43,15 +42,9 @@ interface IState {
exceptions: IErrorData[];
}
export class ErrorBoundary
extends React.Component<React.PropsWithChildren<Props>, IState>
implements IExceptionContext {
export class ErrorBoundary extends React.Component<React.PropsWithChildren<Props>, IState> implements IExceptionContext {
get canRefresh(): boolean {
return (
!!this.props.remount
|| !!this.props.onRefresh
|| this.state.exceptions.some(error => errorOf(error.error, LoadingError))
);
return !!this.props.remount || !!this.props.onRefresh || this.state.exceptions.some(error => errorOf(error.error, LoadingError));
}
constructor(props: Props) {
super(props);
@@ -69,12 +62,15 @@ export class ErrorBoundary
if (state.exceptions.some(data => data.error === error)) {
return state;
}
return ({
exceptions: [...state.exceptions, {
error,
errorInfo,
}],
});
return {
exceptions: [
...state.exceptions,
{
error,
errorInfo,
},
],
};
});
}
@@ -84,16 +80,18 @@ export class ErrorBoundary
for (const errorData of this.state.exceptions) {
if (root) {
return styled(style)(
<DisplayError
className={className}
root={root}
error={errorData.error}
styles={styles}
errorInfo={errorData.errorInfo}
>
{onClose && <action><Button onClick={onClose}>Close</Button></action>}
{this.canRefresh && <action><Button onClick={this.refresh}>Refresh</Button></action>}
</DisplayError>
<DisplayError className={className} root={root} error={errorData.error} styles={styles} errorInfo={errorData.errorInfo}>
{onClose && (
<action>
<Button onClick={onClose}>Close</Button>
</action>
)}
{this.canRefresh && (
<action>
<Button onClick={this.refresh}>Refresh</Button>
</action>
)}
</DisplayError>,
);
} else {
return (
@@ -110,11 +108,7 @@ export class ErrorBoundary
}
}
return (
<ErrorContext.Provider value={this}>
{children}
</ErrorContext.Provider>
);
return <ErrorContext.Provider value={this}>{children}</ErrorContext.Provider>;
}
private refresh() {
@@ -5,11 +5,10 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { createContext } from 'react';
export interface IExceptionContext {
catch(exception: Error): void;
}
export const ErrorContext = createContext<IExceptionContext | null>(null);
export const ErrorContext = createContext<IExceptionContext | null>(null);
@@ -5,8 +5,8 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import '@testing-library/jest-dom';
import { screen } from '@testing-library/react';
import { mockAuthentication } from '@cloudbeaver/core-authentication/mocks/mockAuthentication';
import { createApp } from '@cloudbeaver/core-cli/tests/utils/createApp';
@@ -14,21 +14,17 @@ import { renderInApp } from '@cloudbeaver/core-cli/tests/utils/renderInApp';
import { createGQLEndpoint } from '@cloudbeaver/core-root/mocks/createGQLEndpoint';
import { mockAppInit } from '@cloudbeaver/core-root/mocks/mockAppInit';
import { mockGraphQL } from '@cloudbeaver/core-root/mocks/mockGraphQL';
import { screen } from '@testing-library/react';
import { ErrorMessage } from './ErrorMessage';
const endpoint = createGQLEndpoint();
const app = createApp();
mockGraphQL(
...mockAppInit(endpoint),
...mockAuthentication(endpoint)
);
mockGraphQL(...mockAppInit(endpoint), ...mockAuthentication(endpoint));
beforeAll(() => app.init());
test('icons.svg#name', () => {
renderInApp(<ErrorMessage text='error' />, app);
renderInApp(<ErrorMessage text="error" />, app);
expect(screen.getByText('error')).not.toBeNull();
});
});
@@ -5,7 +5,6 @@
* 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';
@@ -20,7 +19,7 @@ const styles = css`
align-items: center;
border-radius: var(--theme-group-element-radius);
height: 50px;
padding: 8px 12px;
padding: 8px 12px;
}
IconOrImage {
@@ -52,27 +51,20 @@ interface Props {
onShowDetails?: () => void;
}
export const ErrorMessage = observer<Props>(function ErrorMessage({
text,
className,
hasDetails,
onShowDetails,
}) {
export const ErrorMessage = observer<Props>(function ErrorMessage({ text, className, hasDetails, onShowDetails }) {
const translate = useTranslate();
return styled(styles)(
<message className={className}>
<IconOrImage icon="/icons/error_icon_sm.svg" />
<message-body title={text}>
{text}
</message-body>
<message-body title={text}>{text}</message-body>
<message-actions>
{hasDetails && (
<Button type='button' mod={['outlined']} onClick={onShowDetails}>
<Button type="button" mod={['outlined']} onClick={onShowDetails}>
{translate('ui_errors_details')}
</Button>
)}
</message-actions>
</message>
</message>,
);
});
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react-lite';
import styled, { css, use } from 'reshadow';
@@ -116,7 +115,15 @@ interface Props {
}
export const ExceptionMessage = observer<Props>(function ExceptionMessage({
name, message, exception = null, icon, inline, className, styles, onRetry, onClose,
name,
message,
exception = null,
icon,
inline,
className,
styles,
onRetry,
onClose,
}) {
const translate = useTranslate();
const error = useErrorDetails(exception);
@@ -146,12 +153,12 @@ export const ExceptionMessage = observer<Props>(function ExceptionMessage({
<error-message>{message}</error-message>
<error-actions>
{error.hasDetails && (
<Button type='button' mod={['outlined']} disabled={error.isOpen} onClick={error.open}>
<Button type="button" mod={['outlined']} disabled={error.isOpen} onClick={error.open}>
{translate('ui_errors_details')}
</Button>
)}
{onRetry && (
<Button type='button' mod={['unelevated']} onClick={onRetry}>
<Button type="button" mod={['unelevated']} onClick={onRetry}>
{translate('ui_processing_retry')}
</Button>
)}
@@ -164,6 +171,6 @@ export const ExceptionMessage = observer<Props>(function ExceptionMessage({
)}
</>
)}
</error>
</error>,
);
});
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { css } from 'reshadow';
export const EXPANDABLE_FORM_STYLES = css`
@@ -16,4 +15,4 @@ export const EXPANDABLE_FORM_STYLES = css`
text-transform: uppercase;
opacity: 0.9;
}
`;
`;
@@ -5,7 +5,6 @@
* 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 { forwardRef, ReactNode, useImperativeHandle } from 'react';
import { Disclosure, DisclosureContent, DisclosureStateReturn, useDisclosureState } from 'reakit';
@@ -60,28 +59,24 @@ const styles = css`
}
`;
export const Expandable = observer<Props, ExpandableState>(forwardRef(function Expandable({
label,
defaultExpanded,
disabled,
children,
style,
}, ref) {
const disclosure = useDisclosureState({ visible: defaultExpanded ?? false });
export const Expandable = observer<Props, ExpandableState>(
forwardRef(function Expandable({ label, defaultExpanded, disabled, children, style }, ref) {
const disclosure = useDisclosureState({ visible: defaultExpanded ?? false });
useImperativeHandle(ref, () => disclosure);
useImperativeHandle(ref, () => disclosure);
return styled(useStyles(styles, style))(
<>
<Disclosure {...disclosure} disabled={disabled}>
<expand-icon {...use({ expanded: disclosure.visible })}>
<IconOrImage icon='arrow' />
</expand-icon>
<expand-label as='h2'>{label}</expand-label>
</Disclosure>
<DisclosureContent {...disclosure}>
<>{children}</>
</DisclosureContent>
</>
);
}));
return styled(useStyles(styles, style))(
<>
<Disclosure {...disclosure} disabled={disabled}>
<expand-icon {...use({ expanded: disclosure.visible })}>
<IconOrImage icon="arrow" />
</expand-icon>
<expand-label as="h2">{label}</expand-label>
</Disclosure>
<DisclosureContent {...disclosure}>
<>{children}</>
</DisclosureContent>
</>,
);
}),
);
@@ -5,20 +5,14 @@
* 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 { FolderExplorerContext, IFolderExplorerContext } from './FolderExplorerContext';
interface Props{
interface Props {
state: IFolderExplorerContext;
}
export const FolderExplorer = observer<React.PropsWithChildren<Props>>(function FolderExplorer({
state,
children,
}) {
return (
<FolderExplorerContext.Provider value={state}>{children}</FolderExplorerContext.Provider>
);
export const FolderExplorer = observer<React.PropsWithChildren<Props>>(function FolderExplorer({ state, children }) {
return <FolderExplorerContext.Provider value={state}>{children}</FolderExplorerContext.Provider>;
});
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { createContext } from 'react';
export interface IFolderExplorerOptions {
@@ -5,7 +5,6 @@
* 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 { useContext } from 'react';
import styled from 'reshadow';
@@ -20,11 +19,7 @@ interface Props {
className?: string;
}
export const FolderExplorerPath = observer<Props>(function FolderExplorerPath({
getName,
canSkip,
className,
}) {
export const FolderExplorerPath = observer<Props>(function FolderExplorerPath({ getName, canSkip, className }) {
const context = useContext(FolderExplorerContext);
if (!context) {
@@ -44,32 +39,12 @@ export const FolderExplorerPath = observer<Props>(function FolderExplorerPath({
const path = context.state.fullPath.slice(0, i);
const skipFolder = !canSkip || canSkip(folder);
if (
i === 0
|| i === context.state.fullPath.length - 1
|| !skipFolder
|| context.state.fullPath.length < 5
) {
if (i === 0 || i === context.state.fullPath.length - 1 || !skipFolder || context.state.fullPath.length < 5) {
if (skip) {
pathElements.push(
<FolderName
key={i - 1}
path={path}
title={skipTitle}
short
/>
);
pathElements.push(<FolderName key={i - 1} path={path} title={skipTitle} short />);
}
pathElements.push(
<FolderName
key={i}
folder={folder}
path={path}
last={i === context.state.fullPath.length - 1}
getName={getName}
/>
);
pathElements.push(<FolderName key={i} folder={folder} path={path} last={i === context.state.fullPath.length - 1} getName={getName} />);
skip = false;
skipTitle = '';
continue;
@@ -79,15 +54,11 @@ export const FolderExplorerPath = observer<Props>(function FolderExplorerPath({
if (skipTitle !== '') {
skipTitle += ' > ';
}
skipTitle += (getName?.(folder) || folder);
skipTitle += getName?.(folder) || folder;
skip = true;
continue;
}
}
return styled(folderExplorerStyles)(
<folder-explorer-path className={className}>
{pathElements}
</folder-explorer-path>
);
return styled(folderExplorerStyles)(<folder-explorer-path className={className}>{pathElements}</folder-explorer-path>);
});
@@ -5,7 +5,6 @@
* 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 { useContext } from 'react';
import styled from 'reshadow';
@@ -36,14 +35,7 @@ interface ShortProps extends BaseProps {
short: boolean;
}
export const FolderName = observer<FolderProps | ShortProps>(function FolderName({
folder,
path,
title,
short,
last,
getName,
}) {
export const FolderName = observer<FolderProps | ShortProps>(function FolderName({ folder, path, title, short, last, getName }) {
const context = useContext(FolderExplorerContext);
if (!context) {
@@ -71,10 +63,8 @@ export const FolderName = observer<FolderProps | ShortProps>(function FolderName
<Icon name="arrow" viewBox="0 0 16 16" />
</folder-explorer-path-element-arrow>
<folder-explorer-path-element-name>
{last
? name
: <Link onClick={() => context.open(path, folder!)}>{name}</Link>}
{last ? name : <Link onClick={() => context.open(path, folder!)}>{name}</Link>}
</folder-explorer-path-element-name>
</folder-explorer-path-element>
</folder-explorer-path-element>,
);
});
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { css } from 'reshadow';
export const folderExplorerStyles = css`
@@ -33,8 +32,8 @@ export const folderExplorerStyles = css`
text-overflow: ellipsis;
padding: 0 4px;
}
folder-explorer-path-element:first-child folder-explorer-path-element-arrow {
display: none
display: none;
}
`;
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { action, observable } from 'mobx';
import { useContext, useMemo } from 'react';
@@ -23,40 +22,42 @@ export function useFolderExplorer(root: string, options: IFolderExplorerOptions
fullPath: [root],
folder: root,
}),
() => { },
data => (
typeof data === 'object'
&& typeof data.folder === 'string'
&& Array.isArray(data.path)
&& Array.isArray(data.fullPath)
)
() => {},
data => typeof data === 'object' && typeof data.folder === 'string' && Array.isArray(data.path) && Array.isArray(data.fullPath),
);
useMemo(action(() => {
if (!options.saveState) {
userState.folder = root;
userState.fullPath = [root];
userState.path = [];
}
}), [userState]);
useMemo(
action(() => {
if (!options.saveState) {
userState.folder = root;
userState.fullPath = [root];
userState.path = [];
}
}),
[userState],
);
const data = useObservableRef<IFolderExplorerContext>(() => ({
root,
options,
open(path: string[], folder: string) {
this.state.path = path.slice();
this.state.fullPath = [...path, folder];
this.state.folder = folder;
const data = useObservableRef<IFolderExplorerContext>(
() => ({
root,
options,
open(path: string[], folder: string) {
this.state.path = path.slice();
this.state.fullPath = [...path, folder];
this.state.folder = folder;
},
}),
{
root: observable,
state: observable.ref,
options: observable.ref,
open: action.bound,
},
}), {
root: observable,
state: observable.ref,
options: observable.ref,
open: action.bound,
}, {
state: userState,
root,
});
{
state: userState,
root,
},
);
return context || data;
}
@@ -5,51 +5,51 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { css } from 'reshadow';
export const BASE_DROPDOWN_STYLES = css`
MenuItem {
composes: theme-ripple from global;
}
MenuItem {
composes: theme-ripple from global;
}
Menu {
composes: theme-text-on-surface theme-background-surface theme-typography--caption theme-elevation-z3 from global;
Menu {
composes: theme-text-on-surface theme-background-surface theme-typography--caption theme-elevation-z3 from global;
display: flex;
flex-direction: column;
max-height: 300px;
overflow: auto;
outline: none;
z-index: 999;
border-radius: var(--theme-form-element-radius);
& MenuItem {
background: transparent;
display: flex;
flex-direction: column;
max-height: 300px;
overflow: auto;
flex-direction: row;
align-items: center;
padding: 8px 12px;
text-align: left;
outline: none;
z-index: 999;
border-radius: var(--theme-form-element-radius);
color: inherit;
cursor: pointer;
gap: 8px;
& MenuItem {
background: transparent;
display: flex;
flex-direction: row;
align-items: center;
padding: 8px 12px;
text-align: left;
outline: none;
color: inherit;
cursor: pointer;
gap: 8px;
& item-icon,
& item-title {
position: relative;
}
& item-icon, & item-title {
position: relative;
& item-icon {
width: 16px;
height: 16px;
overflow: hidden;
flex-shrink: 0;
& IconOrImage {
width: 100%;
height: 100%;
}
& item-icon {
width: 16px;
height: 16px;
overflow: hidden;
flex-shrink: 0;
& IconOrImage {
width: 100%;
height: 100%;
}
}
}
}
`;
}
`;
@@ -5,7 +5,6 @@
* 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 type { ComponentStyle } from '@cloudbeaver/core-theming';
@@ -23,12 +22,16 @@ export interface CheckboxBaseProps {
style?: ComponentStyle;
}
export type CheckboxInputProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'type' | 'value' | 'defaultValue' | 'checked' | 'defaultChecked' | 'style'> & ILayoutSizeProps & {
value?: string;
defaultValue?: string;
defaultChecked?: boolean;
label?: string;
};
export type CheckboxInputProps = Omit<
React.InputHTMLAttributes<HTMLInputElement>,
'onChange' | 'type' | 'value' | 'defaultValue' | 'checked' | 'defaultChecked' | 'style'
> &
ILayoutSizeProps & {
value?: string;
defaultValue?: string;
defaultChecked?: boolean;
label?: string;
};
export interface ICheckboxControlledProps extends CheckboxInputProps {
state?: never;
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { useLayoutEffect, useRef } from 'react';
import styled, { css } from 'reshadow';
@@ -37,55 +36,55 @@ const checkboxStyles = css`
checkbox-ripple {
composes: theme-checkbox__ripple from global;
}
checkbox-container {
display: flex;
align-items: center;
}
checkbox-label {
composes: theme-typography--body2 from global;
cursor: pointer;
}
`;
checkbox-container {
display: flex;
align-items: center;
}
checkbox-label {
composes: theme-typography--body2 from global;
cursor: pointer;
}
`;
const checkboxMod: Record<CheckboxMod, any> = {
primary: css`
checkbox {
composes: theme-checkbox_primary from global;
}
`,
checkbox {
composes: theme-checkbox_primary from global;
}
`,
surface: css`
checkbox {
composes: theme-checkbox_surface from global;
}
`,
checkbox {
composes: theme-checkbox_surface from global;
}
`,
small: css`
checkbox {
composes: theme-checkbox_small from global;
checkbox {
composes: theme-checkbox_small from global;
}
checkbox-container {
& checkbox {
width: 14px;
height: 14px;
}
checkbox-container {
& checkbox {
width: 14px;
height: 14px;
}
& checkbox-background {
width: 14px;
height: 14px;
}
& checkbox-background {
width: 14px;
height: 14px;
}
`,
}
`,
};
const checkboxState = {
disabled: css`
checkbox {
composes: theme-checkbox--disabled from global;
}
`,
checkbox {
composes: theme-checkbox--disabled from global;
}
`,
checked: css`
checkbox {
composes: theme-checkbox--checked from global;
}
`,
checkbox {
composes: theme-checkbox--checked from global;
}
`,
};
interface ICheckboxMarkupProps extends Omit<React.InputHTMLAttributes<HTMLInputElement>, 'style'> {
@@ -97,7 +96,16 @@ interface ICheckboxMarkupProps extends Omit<React.InputHTMLAttributes<HTMLInputE
}
export const CheckboxMarkup: React.FC<ICheckboxMarkupProps> = function CheckboxMarkup({
id, label, indeterminate, className, title, mod = ['primary'], ripple = true, style, readOnly, ...rest
id,
label,
indeterminate,
className,
title,
mod = ['primary'],
ripple = true,
style,
readOnly,
...rest
}) {
const checkboxRef = useRef<HTMLInputElement>(null);
@@ -113,30 +121,25 @@ export const CheckboxMarkup: React.FC<ICheckboxMarkupProps> = function CheckboxM
...(mod || []).map(mod => checkboxMod[mod]),
rest.disabled && checkboxState.disabled,
rest.checked && checkboxState.checked,
style
)
style,
),
)(
<checkbox-container className={className} title={title}>
<checkbox>
<checkbox-input
ref={checkboxRef}
as='input'
type='checkbox'
{...rest}
disabled={rest.disabled || readOnly}
id={id || rest.name}
/>
<checkbox-input ref={checkboxRef} as="input" type="checkbox" {...rest} disabled={rest.disabled || readOnly} id={id || rest.name} />
<checkbox-background>
<checkbox-checkmark as='svg' viewBox='0 0 24 24'>
<checkbox-checkmark-path as='path' fill='none' d='M1.73,12.91 8.1,19.28 22.79,4.59' />
<checkbox-checkmark as="svg" viewBox="0 0 24 24">
<checkbox-checkmark-path as="path" fill="none" d="M1.73,12.91 8.1,19.28 22.79,4.59" />
</checkbox-checkmark>
<checkbox-mixedmark />
</checkbox-background>
{ripple && (
<checkbox-ripple />
)}
{ripple && <checkbox-ripple />}
</checkbox>
{label && (id || rest.name) && <checkbox-label as='label' htmlFor={id || rest.name}>{label}</checkbox-label>}
</checkbox-container>
{label && (id || rest.name) && (
<checkbox-label as="label" htmlFor={id || rest.name}>
{label}
</checkbox-label>
)}
</checkbox-container>,
);
};
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import styled, { css } from 'reshadow';
import { useStyles } from '../../useStyles';
@@ -30,7 +29,7 @@ const style = css`
}
}
field-label {
composes: theme-typography--body2 from global;
composes: theme-typography--body2 from global;
}
Checkbox[disabled] + field-label {
cursor: auto;
@@ -51,13 +50,9 @@ export const FieldCheckbox: CheckboxType = function FieldCheckbox({
return styled(styles)(
<field className={className}>
<Checkbox {...(rest as CheckboxBaseProps & ICheckboxControlledProps)} />
<field-label
htmlFor={rest.id || rest.name}
title={rest.title}
as="label"
>
<field-label htmlFor={rest.id || rest.name} title={rest.title} as="label">
{children}
</field-label>
</field>
</field>,
);
};
@@ -5,12 +5,9 @@
* 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 { filterLayoutFakeProps } from '../../Containers/filterLayoutFakeProps';
import { useStyles } from '../../useStyles';
import { baseFormControlStyles, baseValidFormControlStyles } from '../baseFormControlStyles';
@@ -19,72 +16,72 @@ import type { ICheckboxControlledProps, ICheckboxObjectProps } from './Checkbox'
import { useCheckboxState } from './useCheckboxState';
const switchStyles = css`
switch-control {
composes: theme-switch from global;
}
switch-control-track {
composes: theme-switch__track from global;
}
switch-input {
composes: theme-switch_native-control from global;
}
switch-control-underlay {
composes: theme-switch__thumb-underlay from global;
}
switch-control-thumb {
composes: theme-switch__thumb from global;
}
radio-ripple {
composes: theme-radio_ripple from global;
}
switch-body {
display: flex;
align-items: center;
}
switch-body {
composes: theme-typography--body1 from global;
}
switch-body field-label {
cursor: pointer;
user-select: none;
display: block;
padding-left: 18px;
min-width: 50px;
white-space: pre-wrap;
font-weight: 500;
}
`;
switch-control {
composes: theme-switch from global;
}
switch-control-track {
composes: theme-switch__track from global;
}
switch-input {
composes: theme-switch_native-control from global;
}
switch-control-underlay {
composes: theme-switch__thumb-underlay from global;
}
switch-control-thumb {
composes: theme-switch__thumb from global;
}
radio-ripple {
composes: theme-radio_ripple from global;
}
switch-body {
display: flex;
align-items: center;
}
switch-body {
composes: theme-typography--body1 from global;
}
switch-body field-label {
cursor: pointer;
user-select: none;
display: block;
padding-left: 18px;
min-width: 50px;
white-space: pre-wrap;
font-weight: 500;
}
`;
const switchMod = {
primary: css`
switch-control {
composes: theme-switch_primary from global;
}
`,
switch-control {
composes: theme-switch_primary from global;
}
`,
dense: css`
switch-body {
composes: theme-switch_dense from global;
}
field-label {
composes: theme-typography--body2 from global;
}
switch-body field-label {
font-weight: initial;
}
`,
switch-body {
composes: theme-switch_dense from global;
}
field-label {
composes: theme-typography--body2 from global;
}
switch-body field-label {
font-weight: initial;
}
`,
};
const switchState = {
disabled: css`
switch-control {
composes: theme-switch--disabled mdc-switch--disabled from global;
}
`,
switch-control {
composes: theme-switch--disabled mdc-switch--disabled from global;
}
`,
checked: css`
switch-control {
composes: theme-switch--checked mdc-switch--checked from global;
}
`,
switch-control {
composes: theme-switch--checked mdc-switch--checked from global;
}
`,
};
interface IBaseProps {
@@ -130,7 +127,7 @@ export const Switch: SwitchType = observer(function Switch({
switchStyles,
...mod.map(mod => switchMod[mod]),
disabled && switchState.disabled,
checkboxState.checked && switchState.checked
checkboxState.checked && switchState.checked,
);
if (autoHide && !isControlPresented(name, state)) {
@@ -145,7 +142,7 @@ export const Switch: SwitchType = observer(function Switch({
<switch-control-underlay>
<switch-control-thumb />
<switch-input
as='input'
as="input"
{...rest}
type="checkbox"
id={id || value || name}
@@ -157,9 +154,11 @@ export const Switch: SwitchType = observer(function Switch({
/>
</switch-control-underlay>
</switch-control>
<field-label as="label" htmlFor={id || value || name}>{children}</field-label>
<field-label as="label" htmlFor={id || value || name}>
{children}
</field-label>
</switch-body>
{description && <field-description>{description}</field-description>}
</field>
</field>,
);
});
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { useContext, useState } from 'react';
import { useObjectRef } from '../../useObjectRef';
@@ -19,16 +18,16 @@ export type CheckboxStateOptions<TKey extends string> = {
checked: boolean | undefined;
defaultChecked: boolean | undefined;
} & (
{
state: undefined;
name: string | undefined;
onChange: CheckboxOnChangeEvent<string | undefined> | undefined;
}
| {
state: Record<TKey, boolean | undefined | null | string | string[]> | undefined;
name: TKey;
onChange: CheckboxOnChangeEvent<TKey> | undefined;
}
state: undefined;
name: string | undefined;
onChange: CheckboxOnChangeEvent<string | undefined> | undefined;
}
| {
state: Record<TKey, boolean | undefined | null | string | string[]> | undefined;
name: TKey;
onChange: CheckboxOnChangeEvent<TKey> | undefined;
}
);
interface ICheckboxState {
@@ -57,35 +56,39 @@ export function useCheckboxState<TKey extends string>(options: CheckboxStateOpti
}
}
return useObjectRef<ICheckboxState>(() => ({
checked,
change(event: React.ChangeEvent<HTMLInputElement>) {
const { state, name, value, onChange, count, context } = optionsRef;
const checked = event.target.checked;
return useObjectRef<ICheckboxState>(
() => ({
checked,
change(event: React.ChangeEvent<HTMLInputElement>) {
const { state, name, value, onChange, count, context } = optionsRef;
const checked = event.target.checked;
if (state !== undefined && name !== undefined) {
const currentState = state[name as TKey];
if (state !== undefined && name !== undefined) {
const currentState = state[name as TKey];
if (typeof value === 'string') {
if (Array.isArray(currentState)) {
const elementIndex = currentState.indexOf(value);
if (checked && elementIndex === -1) {
currentState.push(value);
} else if (elementIndex !== -1) {
currentState.splice(elementIndex, 1);
if (typeof value === 'string') {
if (Array.isArray(currentState)) {
const elementIndex = currentState.indexOf(value);
if (checked && elementIndex === -1) {
currentState.push(value);
} else if (elementIndex !== -1) {
currentState.splice(elementIndex, 1);
}
} else {
state[name as TKey] = value;
}
} else {
state[name as TKey] = value;
state[name as TKey] = checked;
}
} else {
state[name as TKey] = checked;
}
}
onChange?.(checked, name as TKey);
context?.change(checked, name);
onChange?.(checked, name as TKey);
context?.change(checked, name);
refresh(count + 1);
},
}), { checked }, ['change']);
refresh(count + 1);
},
}),
{ checked },
['change'],
);
}
@@ -5,10 +5,9 @@
* 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 { useLayoutEffect, useCallback, useState, useRef, useContext, useEffect } from 'react';
import { useMenuState, Menu, MenuItem, MenuButton } from 'reakit/Menu';
import { useCallback, useContext, useEffect, useLayoutEffect, useRef, useState } from 'react';
import { Menu, MenuButton, MenuItem, useMenuState } from 'reakit/Menu';
import styled, { css, use } from 'reshadow';
import { filterLayoutFakeProps } from '../Containers/filterLayoutFakeProps';
@@ -23,131 +22,134 @@ import { baseFormControlStyles, baseValidFormControlStyles } from './baseFormCon
import { FormContext } from './FormContext';
const styles = css`
field[|inline] {
display: flex;
align-items: center;
field[|inline] {
display: flex;
align-items: center;
& field-label {
padding-right: 8px;
padding-bottom: 0;
}
& field-label {
padding-right: 8px;
padding-bottom: 0;
}
field input {
margin: 0;
}
field input {
margin: 0;
}
field-label {
display: block;
padding-bottom: 10px;
composes: theme-typography--body1 from global;
font-weight: 500;
}
input {
padding-right: 24px !important;
}
MenuButton {
position: absolute;
right: 0;
background: transparent;
outline: none;
display: flex;
align-items: center;
height: 100%;
padding: 0 8px 0 0;
cursor: pointer;
&:hover,
&:focus {
opacity: 0.7;
}
field-label {
display: block;
padding-bottom: 10px;
composes: theme-typography--body1 from global;
font-weight: 500;
}
input {
padding-right: 24px !important;
}
MenuButton {
position: absolute;
right: 0;
}
MenuItem {
composes: theme-ripple from global;
}
Menu {
composes: theme-text-on-surface theme-background-surface theme-typography--caption theme-elevation-z3 from global;
display: flex;
flex-direction: column;
max-height: 300px;
overflow: auto;
outline: none;
z-index: 999;
border-radius: var(--theme-form-element-radius);
& MenuItem {
background: transparent;
outline: none;
display: flex;
flex-direction: row;
align-items: center;
height: 100%;
padding: 0 8px 0 0;
padding: 8px 12px;
text-align: left;
outline: none;
color: inherit;
cursor: pointer;
&:hover, &:focus {
opacity: 0.7;
gap: 8px;
& item-icon,
& item-title {
position: relative;
}
}
MenuItem {
composes: theme-ripple from global;
}
Menu {
composes: theme-text-on-surface theme-background-surface theme-typography--caption theme-elevation-z3 from global;
display: flex;
flex-direction: column;
max-height: 300px;
overflow: auto;
outline: none;
z-index: 999;
border-radius: var(--theme-form-element-radius);
& item-icon {
width: 16px;
height: 16px;
overflow: hidden;
flex-shrink: 0;
& MenuItem {
background: transparent;
display: flex;
flex-direction: row;
align-items: center;
padding: 8px 12px;
text-align: left;
outline: none;
color: inherit;
cursor: pointer;
gap: 8px;
& item-icon, & item-title {
position: relative;
}
& item-icon {
width: 16px;
height: 16px;
overflow: hidden;
flex-shrink: 0;
& IconOrImage {
width: 100%;
height: 100%;
}
& IconOrImage {
width: 100%;
height: 100%;
}
}
}
Icon {
}
Icon {
height: 16px;
display: block;
}
MenuButton Icon[|focus] {
transform: rotate(180deg);
}
input-box {
flex: 1;
position: relative;
display: flex;
align-items: center;
& input-icon {
position: absolute;
left: 0;
width: 16px;
height: 16px;
display: block;
}
MenuButton Icon[|focus] {
transform: rotate(180deg);
}
input-box {
flex: 1;
position: relative;
display: flex;
align-items: center;
margin-left: 12px;
& input-icon {
position: absolute;
left: 0;
width: 16px;
height: 16px;
margin-left: 12px;
& IconOrImage {
width: 100%;
height: 100%;
}
& IconOrImage {
width: 100%;
height: 100%;
}
&:not(:empty) + input {
padding-left: 34px !important;
}
&:not(:empty) + input {
padding-left: 34px !important;
}
}
`;
}
`;
type BaseProps<TKey, TValue> = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'onSelect' | 'name' | 'value' | 'defaultValue'> & ILayoutSizeProps & {
propertyName?: string;
items: TValue[];
searchable?: boolean;
defaultValue?: TKey;
loading?: boolean;
description?: string;
keySelector?: (item: TValue, index: number) => TKey;
valueSelector?: (item: TValue) => string;
titleSelector?: (item: TValue) => string | undefined;
iconSelector?: (item: TValue) => string | React.ReactElement | undefined;
isDisabled?: (item: TValue) => boolean;
onSwitch?: (state: boolean) => void;
inline?: boolean;
};
type BaseProps<TKey, TValue> = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'onSelect' | 'name' | 'value' | 'defaultValue'> &
ILayoutSizeProps & {
propertyName?: string;
items: TValue[];
searchable?: boolean;
defaultValue?: TKey;
loading?: boolean;
description?: string;
keySelector?: (item: TValue, index: number) => TKey;
valueSelector?: (item: TValue) => string;
titleSelector?: (item: TValue) => string | undefined;
iconSelector?: (item: TValue) => string | React.ReactElement | undefined;
isDisabled?: (item: TValue) => boolean;
onSwitch?: (state: boolean) => void;
inline?: boolean;
};
type ControlledProps<TKey, TValue> = BaseProps<TKey, TValue> & {
name?: string;
@@ -191,7 +193,7 @@ export const Combobox: ComboboxType = observer(function Combobox({
iconSelector,
titleSelector,
isDisabled,
onChange = () => { },
onChange = () => {},
onSelect,
onSwitch,
...rest
@@ -217,9 +219,7 @@ export const Combobox: ComboboxType = observer(function Combobox({
const [searchValue, setSearchValue] = useState<string | null>(null);
const filteredItems = getComputed(() => {
const result = items.filter(
item => !searchValue || valueSelector(item).toUpperCase().includes(searchValue.toUpperCase())
);
const result = items.filter(item => !searchValue || valueSelector(item).toUpperCase().includes(searchValue.toUpperCase()));
if (isDisabled) {
return result.sort((a, b) => Number(isDisabled(a)) - Number(isDisabled(b)));
@@ -252,61 +252,73 @@ export const Combobox: ComboboxType = observer(function Combobox({
}
}
const handleChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
onChange(value, name);
setSearchValue(value);
}, [name, onChange]);
const handleChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const value = event.target.value;
onChange(value, name);
setSearchValue(value);
},
[name, onChange],
);
const handleSelect = useCallback((id: any) => {
id = id ?? value ?? '';
const changed = id !== value;
const handleSelect = useCallback(
(id: any) => {
id = id ?? value ?? '';
const changed = id !== value;
menu.hide();
if (state && changed) {
state[name] = id;
}
if (onSelect && changed) {
onSelect(id, name, value);
}
if (context && changed) {
context.change(id, name);
}
setSearchValue(null);
}, [value, state, name, menu, context, onSelect]);
const matchItems = useCallback((input?: boolean) => {
if (searchValue === null) {
return;
}
if (filteredItems.length === 0) {
setSearchValue(null);
return;
}
const filteredItemIndex = items.indexOf(filteredItems[0]);
if (filteredItems.length === 1) {
handleSelect(keySelector(filteredItems[0], filteredItemIndex));
return;
}
if (filteredItems.length > 0) {
if (input) {
handleSelect(keySelector(filteredItems[0], filteredItemIndex));
} else {
setSearchValue(null);
menu.hide();
if (state && changed) {
state[name] = id;
}
}
}, [items, filteredItems, keySelector, handleSelect, searchValue]);
if (onSelect && changed) {
onSelect(id, name, value);
}
if (context && changed) {
context.change(id, name);
}
setSearchValue(null);
},
[value, state, name, menu, context, onSelect],
);
const handleKeyDown = useCallback((event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') {
event.preventDefault();
matchItems(true);
}
}, [matchItems]);
const matchItems = useCallback(
(input?: boolean) => {
if (searchValue === null) {
return;
}
if (filteredItems.length === 0) {
setSearchValue(null);
return;
}
const filteredItemIndex = items.indexOf(filteredItems[0]);
if (filteredItems.length === 1) {
handleSelect(keySelector(filteredItems[0], filteredItemIndex));
return;
}
if (filteredItems.length > 0) {
if (input) {
handleSelect(keySelector(filteredItems[0], filteredItemIndex));
} else {
setSearchValue(null);
}
}
},
[items, filteredItems, keySelector, handleSelect, searchValue],
);
const handleKeyDown = useCallback(
(event: React.KeyboardEvent<HTMLInputElement>) => {
if (event.key === 'Enter') {
event.preventDefault();
matchItems(true);
}
},
[matchItems],
);
useEffect(() => {
if (inputRef === document.activeElement) {
@@ -353,16 +365,15 @@ export const Combobox: ComboboxType = observer(function Combobox({
return styled(useStyles(baseFormControlStyles, baseValidFormControlStyles, styles))(
<field className={className} {...use({ inline })}>
{children && <field-label title={title}>{children}{rest.required && ' *'}</field-label>}
{children && (
<field-label title={title}>
{children}
{rest.required && ' *'}
</field-label>
)}
<input-box>
{(icon || loading) && (
<input-icon>
{loading ? (
<Loader small fullSize />
) : (
typeof icon === 'string' ? <IconOrImage icon={icon} /> : icon
)}
</input-icon>
<input-icon>{loading ? <Loader small fullSize /> : typeof icon === 'string' ? <IconOrImage icon={icon} /> : icon}</input-icon>
)}
<input
ref={setInputRef}
@@ -391,13 +402,12 @@ export const Combobox: ComboboxType = observer(function Combobox({
// unstable_initialFocusRef={ref}
modal
>
{!filteredItems.length
? (
<MenuItem id='placeholder' disabled {...menu}>
{translate('combobox_no_results_placeholder')}
</MenuItem>
)
: (filteredItems.map((item, index) => {
{!filteredItems.length ? (
<MenuItem id="placeholder" disabled {...menu}>
{translate('combobox_no_results_placeholder')}
</MenuItem>
) : (
filteredItems.map((item, index) => {
const icon = iconSelector?.(item);
const title = titleSelector?.(item);
const disabled = isDisabled?.(item);
@@ -406,28 +416,21 @@ export const Combobox: ComboboxType = observer(function Combobox({
<MenuItem
key={keySelector(item, index)}
id={keySelector(item, index)}
type='button'
type="button"
title={title}
{...menu}
disabled={disabled}
onClick={event => handleSelect(event.currentTarget.id)}
>
{iconSelector && (
<item-icon>
{icon && typeof icon === 'string' ? <IconOrImage icon={icon} /> : icon}
</item-icon>
)}
{iconSelector && <item-icon>{icon && typeof icon === 'string' ? <IconOrImage icon={icon} /> : icon}</item-icon>}
<item-value>{valueSelector(item)}</item-value>
</MenuItem>
);
}))}
})
)}
</Menu>
</input-box>
{description && (
<field-description>
{description}
</field-description>
)}
</field>
{description && <field-description>{description}</field-description>}
</field>,
);
});
@@ -5,7 +5,6 @@
* 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 { useCallback, useEffect, useState } from 'react';
import styled, { css, use } from 'reshadow';
@@ -51,10 +50,10 @@ const filterStyles = css`
`;
const toggleModeButtonStyle = css`
IconButton {
composes: theme-background-primary theme-text-on-primary from global;
cursor: pointer;
}
IconButton {
composes: theme-background-primary theme-text-on-primary from global;
cursor: pointer;
}
`;
const innerInputStyle = css`
@@ -108,17 +107,20 @@ export const Filter = observer<ControlledProps | ObjectsProps<any, any>>(functio
const [inputRef, ref] = useFocus<HTMLInputElement>({});
const [toggled, setToggled] = useState(!toggleMode);
const filter = useCallback((value: string | number, name?: string) => {
value = String(value);
const filter = useCallback(
(value: string | number, name?: string) => {
value = String(value);
if (state && name) {
state[name] = value;
}
if (state && name) {
state[name] = value;
}
if (onFilter) {
onFilter(value, name);
}
}, [onFilter, state]);
if (onFilter) {
onFilter(value, name);
}
},
[onFilter, state],
);
const toggle = useCallback(() => {
if (!toggleMode) {
@@ -161,12 +163,7 @@ export const Filter = observer<ControlledProps | ObjectsProps<any, any>>(functio
onKeyDown={onKeyDown}
{...use({ toggled, max })}
/>
<IconButton
name='search'
disabled={disabled}
onClick={toggle}
{...use({ toggled })}
/>
</filter-container>
<IconButton name="search" disabled={disabled} onClick={toggle} {...use({ toggled })} />
</filter-container>,
);
});
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import styled, { css } from 'reshadow';
const styles = css`
@@ -21,9 +20,5 @@ interface Props {
}
export const FormBox: React.FC<React.PropsWithChildren<Props>> = function FormBox({ children, className }) {
return styled(styles)(
<box className={className}>
{children}
</box>
);
return styled(styles)(<box className={className}>{children}</box>);
};
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import styled, { css, use } from 'reshadow';
const styles = css`
@@ -26,8 +25,8 @@ interface Props {
export const FormBoxElement: React.FC<React.PropsWithChildren<Props>> = function FormBoxElement({ children, className, max }) {
return styled(styles)(
<box-element as='div' className={className} {...use({ max })}>
<box-element as="div" className={className} {...use({ max })}>
{children}
</box-element>
</box-element>,
);
};
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { createContext } from 'react';
import type { IExecutor } from '@cloudbeaver/core-executor';
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import styled, { css } from 'reshadow';
import { filterLayoutFakeProps } from '../Containers/filterLayoutFakeProps';
@@ -42,10 +41,8 @@ export const FormFieldDescription: React.FC<React.PropsWithChildren<Props>> = fu
return styled(styles)(
<field title={title} className={className} {...rest}>
{label && <field-label as='label'>{label}</field-label>}
<field-description>
{children}
</field-description>
</field>
{label && <field-label as="label">{label}</field-label>}
<field-description>{children}</field-description>
</field>,
);
};
@@ -5,7 +5,6 @@
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import styled, { css } from 'reshadow';
const styles = css`
@@ -20,9 +19,5 @@ interface Props {
}
export const FormGroup: React.FC<React.PropsWithChildren<Props>> = function FormGroup({ children, className }) {
return styled(styles)(
<group className={className}>
{children}
</group>
);
return styled(styles)(<group className={className}>{children}</group>);
};
@@ -5,10 +5,9 @@
* 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 { forwardRef, useCallback, useContext, useState } from 'react';
import styled, { use, css } from 'reshadow';
import styled, { css, use } from 'reshadow';
import type { ComponentStyle } from '@cloudbeaver/core-theming';
@@ -25,55 +24,57 @@ import { isControlPresented } from './isControlPresented';
import { useCapsLockTracker } from './useCapsLockTracker';
const INPUT_FIELD_STYLES = css`
Icon {
composes: theme-text-on-secondary from global;
}
field-label {
display: block;
composes: theme-typography--body1 from global;
font-weight: 500;
}
field-label:not(:empty) {
padding-bottom: 10px;
}
input-container {
position: relative;
}
loader-container, icon-container {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
width: 24px;
height: 24px;
display: flex;
}
icon-container {
cursor: pointer;
& Icon {
width: 100%;
height: 100%;
}
}
input[disabled] + icon-container {
cursor: auto;
opacity: 0.8;
}
input:not(:only-child) {
padding-right: 32px !important;
Icon {
composes: theme-text-on-secondary from global;
}
field-label {
display: block;
composes: theme-typography--body1 from global;
font-weight: 500;
}
field-label:not(:empty) {
padding-bottom: 10px;
}
input-container {
position: relative;
}
loader-container,
icon-container {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
width: 24px;
height: 24px;
display: flex;
}
icon-container {
cursor: pointer;
& Icon {
width: 100%;
height: 100%;
}
}
input[disabled] + icon-container {
cursor: auto;
opacity: 0.8;
}
input:not(:only-child) {
padding-right: 32px !important;
}
`;
type BaseProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'name' | 'value' | 'style'> & ILayoutSizeProps & {
error?: boolean;
loading?: boolean;
description?: string;
labelTooltip?: string;
mod?: 'surface';
ref?: React.Ref<HTMLInputElement>;
style?: ComponentStyle;
onCustomCopy?: () => void;
};
type BaseProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'name' | 'value' | 'style'> &
ILayoutSizeProps & {
error?: boolean;
loading?: boolean;
description?: string;
labelTooltip?: string;
mod?: 'surface';
ref?: React.Ref<HTMLInputElement>;
style?: ComponentStyle;
onCustomCopy?: () => void;
};
type ControlledProps = BaseProps & {
name?: string;
@@ -100,134 +101,128 @@ interface InputFieldType {
<TKey extends keyof TState, TState>(props: ObjectProps<TKey, TState>): React.ReactElement<any, any> | null;
}
export const InputField: InputFieldType = observer(forwardRef(function InputField({
name,
style,
value: valueControlled,
defaultValue,
required,
state,
mapState,
mapValue,
children,
className,
error,
loading,
description,
labelTooltip,
mod,
fill,
small,
medium,
large,
tiny,
autoHide,
onChange,
onCustomCopy,
...rest
}: ControlledProps | ObjectProps<any, any>, ref: React.Ref<HTMLInputElement>) {
const capsLock = useCapsLockTracker();
const [passwordRevealed, setPasswordRevealed] = useState(false);
const translate = useTranslate();
const styles = useStyles(
baseFormControlStyles,
error ? baseInvalidFormControlStyles : baseValidFormControlStyles,
INPUT_FIELD_STYLES,
style
);
const context = useContext(FormContext);
loading = useStateDelay(loading ?? false, 300);
export const InputField: InputFieldType = observer(
forwardRef(function InputField(
{
name,
style,
value: valueControlled,
defaultValue,
required,
state,
mapState,
mapValue,
children,
className,
error,
loading,
description,
labelTooltip,
mod,
fill,
small,
medium,
large,
tiny,
autoHide,
onChange,
onCustomCopy,
...rest
}: ControlledProps | ObjectProps<any, any>,
ref: React.Ref<HTMLInputElement>,
) {
const capsLock = useCapsLockTracker();
const [passwordRevealed, setPasswordRevealed] = useState(false);
const translate = useTranslate();
const styles = useStyles(baseFormControlStyles, error ? baseInvalidFormControlStyles : baseValidFormControlStyles, INPUT_FIELD_STYLES, style);
const context = useContext(FormContext);
loading = useStateDelay(loading ?? false, 300);
const revealPassword = useCallback(() => {
if (rest.disabled) {
return;
const revealPassword = useCallback(() => {
if (rest.disabled) {
return;
}
setPasswordRevealed(prev => !prev);
}, [rest.disabled]);
const handleChange = useCallback(
(event: React.ChangeEvent<HTMLInputElement>) => {
const value = mapValue?.(event.target.value) ?? event.target.value;
if (state) {
state[name] = value;
}
if (onChange) {
onChange(value, name);
}
if (context) {
context.change(value, name);
}
},
[state, name, context, onChange],
);
const handleBlur = useCombinedHandler(rest.onBlur, capsLock.handleBlur);
const handleKeyDown = useCombinedHandler(rest.onKeyDown, capsLock.handleKeyDown, context?.keyDown);
if (autoHide && !isControlPresented(name, state, defaultValue)) {
return null;
}
setPasswordRevealed(prev => !prev);
}, [rest.disabled]);
let value: any = valueControlled ?? defaultValue ?? undefined;
const handleChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
const value = mapValue?.(event.target.value) ?? event.target.value;
if (state) {
state[name] = value;
if (state && name !== undefined && name in state) {
value = state[name];
}
if (onChange) {
onChange(value, name);
if (mapState) {
value = mapState(value);
}
if (context) {
context.change(value, name);
const showRevealPasswordButton = rest.type === 'password' && !rest.readOnly;
if (showRevealPasswordButton && capsLock.warn) {
description = translate('ui_capslock_on');
}
}, [state, name, context, onChange]);
const handleBlur = useCombinedHandler(rest.onBlur, capsLock.handleBlur);
const handleKeyDown = useCombinedHandler(rest.onKeyDown, capsLock.handleKeyDown, context?.keyDown);
if (autoHide && !isControlPresented(name, state, defaultValue)) {
return null;
}
let value: any = valueControlled ?? defaultValue ?? undefined;
if (state && name !== undefined && name in state) {
value = state[name];
}
if (mapState) {
value = mapState(value);
}
const showRevealPasswordButton = rest.type === 'password' && !rest.readOnly;
if (showRevealPasswordButton && capsLock.warn) {
description = translate('ui_capslock_on');
}
return styled(styles)(
<field className={className} {...use({ small, medium, large, tiny })}>
<field-label title={labelTooltip || rest.title}>{children}{required && ' *'}</field-label>
<input-container>
<input
ref={ref}
{...rest}
type={passwordRevealed ? 'text' : rest.type}
name={name}
value={value ?? ''}
onChange={handleChange}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
{...use({ mod })}
required={required}
/>
{loading && (
<loader-container
title={translate('ui_processing_loading')}
>
<Loader small />
</loader-container>
)}
{showRevealPasswordButton && (
<icon-container
title={translate('ui_reveal_password')}
onClick={revealPassword}
>
<Icon
name={passwordRevealed ? 'password-hide' : 'password-show'}
viewBox='0 0 16 16'
/>
</icon-container>
)}
{onCustomCopy && (
<icon-container title={translate('ui_copy_to_clipboard')} onClick={onCustomCopy}>
<Icon name="copy" viewBox='0 0 32 32' />
</icon-container>
)}
</input-container>
{(description || showRevealPasswordButton) && (
<field-description>
{description}
</field-description>
)}
</field>
);
}));
return styled(styles)(
<field className={className} {...use({ small, medium, large, tiny })}>
<field-label title={labelTooltip || rest.title}>
{children}
{required && ' *'}
</field-label>
<input-container>
<input
ref={ref}
{...rest}
type={passwordRevealed ? 'text' : rest.type}
name={name}
value={value ?? ''}
onChange={handleChange}
onBlur={handleBlur}
onKeyDown={handleKeyDown}
{...use({ mod })}
required={required}
/>
{loading && (
<loader-container title={translate('ui_processing_loading')}>
<Loader small />
</loader-container>
)}
{showRevealPasswordButton && (
<icon-container title={translate('ui_reveal_password')} onClick={revealPassword}>
<Icon name={passwordRevealed ? 'password-hide' : 'password-show'} viewBox="0 0 16 16" />
</icon-container>
)}
{onCustomCopy && (
<icon-container title={translate('ui_copy_to_clipboard')} onClick={onCustomCopy}>
<Icon name="copy" viewBox="0 0 32 32" />
</icon-container>
)}
</input-container>
{(description || showRevealPasswordButton) && <field-description>{description}</field-description>}
</field>,
);
}),
);
@@ -5,7 +5,6 @@
* 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 { ReactNode, useContext, useState } from 'react';
import styled, { css, use } from 'reshadow';
@@ -67,9 +66,7 @@ interface Props<TState> extends ILayoutSizeProps {
mapValue?: (value: string) => string;
}
type InputFileTextContentType = <TState extends Record<string, any>>(
props: Props<TState>
) => React.ReactElement<any, any>;
type InputFileTextContentType = <TState extends Record<string, any>>(props: Props<TState>) => React.ReactElement<any, any>;
export const InputFileTextContent: InputFileTextContentType = observer(function InputFileTextContent({
name,
@@ -97,12 +94,7 @@ export const InputFileTextContent: InputFileTextContentType = observer(function
const [selected, setSelected] = useState<File | null>(null);
const [error, setError] = useState<Error | null>(null);
const styles = useStyles(
INPUT_FILE_FIELD_STYLES,
baseFormControlStyles,
style,
error ? baseInvalidFormControlStyles : baseValidFormControlStyles
);
const styles = useStyles(INPUT_FILE_FIELD_STYLES, baseFormControlStyles, style, error ? baseInvalidFormControlStyles : baseValidFormControlStyles);
const savedExternally = !!fileName && state[name] !== '';
const saved = savedExternally || !!state[name];
@@ -138,10 +130,12 @@ export const InputFileTextContent: InputFileTextContentType = observer(function
const maxFileSizeBytes = maxFileSize * 1024;
if (size > maxFileSizeBytes) {
throw new Error(translate('ui_file_size_exceeds', undefined, {
size: bytesToSize(size),
maxSize: bytesToSize(maxFileSizeBytes),
}));
throw new Error(
translate('ui_file_size_exceeds', undefined, {
size: bytesToSize(size),
maxSize: bytesToSize(maxFileSizeBytes),
}),
);
}
}
@@ -170,21 +164,19 @@ export const InputFileTextContent: InputFileTextContentType = observer(function
return styled(styles)(
<field className={className} {...use({ small, medium, large, tiny })}>
<field-label title={labelTooltip}>{children}{required && ' *'}</field-label>
<field-label title={labelTooltip}>
{children}
{required && ' *'}
</field-label>
<UploadArea title={tooltip} disabled={disabled} accept={accept} reset onChange={handleChange}>
<Button
icon='/icons/import.svg'
tag='div'
mod={['outlined']}
disabled={disabled}
>
<Button icon="/icons/import.svg" tag="div" mod={['outlined']} disabled={disabled}>
{translate('ui_upload_file')}
</Button>
</UploadArea>
<field-description>
{description}
{(selected || saved) && <IconButton disabled={disabled} name='cross' onClick={removeFile} />}
{(selected || saved) && <IconButton disabled={disabled} name="cross" onClick={removeFile} />}
</field-description>
</field>
</field>,
);
});
});

Some files were not shown because too many files have changed in this diff Show More