mirror of
https://github.com/dbeaver/cloudbeaver.git
synced 2026-09-24 16:04:36 +08:00
@@ -12,8 +12,6 @@ module.exports = {
|
||||
parserOptions: {
|
||||
ecmaVersion: 2019,
|
||||
sourceType: 'module',
|
||||
tsconfigRootDir: __dirname,
|
||||
project: './tsconfig.eslint.json',
|
||||
ecmaFeatures: {
|
||||
jsx: true,
|
||||
},
|
||||
|
||||
+4
-2
@@ -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"
|
||||
}
|
||||
|
||||
+29
-49
@@ -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;
|
||||
|
||||
+2
-4
@@ -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;
|
||||
}
|
||||
|
||||
+1
-6
@@ -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) {
|
||||
|
||||
+19
-50
@@ -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;
|
||||
|
||||
+8
-18
@@ -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>,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -9,4 +9,4 @@ export * from './AppLocaleService';
|
||||
export * from './Body';
|
||||
|
||||
// Interfaces
|
||||
export * from './manifest';
|
||||
export * from './manifest';
|
||||
|
||||
@@ -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'],
|
||||
];
|
||||
|
||||
@@ -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.'],
|
||||
];
|
||||
|
||||
@@ -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'],
|
||||
];
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
@@ -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>,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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>,
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
+9
-5
@@ -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
Reference in New Issue
Block a user