Merge pull request #151 from dbeaver/feat-configuration-wizard

Feat configuration wizard
This commit is contained in:
Serge Rider
2020-08-31 12:12:33 +03:00
committed by GitHub
27 changed files with 352 additions and 130 deletions
@@ -7,7 +7,7 @@
*/
import { injectable, Bootstrap } from '@cloudbeaver/core-di';
import { PermissionsService } from '@cloudbeaver/core-root';
import { PermissionsService, ServerConfigResource } from '@cloudbeaver/core-root';
import { ScreenService, RouterState } from '@cloudbeaver/core-routing';
import { AdministrationItemService } from '../AdministrationItem/AdministrationItemService';
@@ -23,7 +23,8 @@ export class AdministrationScreenServiceBootstrap extends Bootstrap {
private screenService: ScreenService,
private permissionsService: PermissionsService,
private administrationItemService: AdministrationItemService,
private administrationScreenService: AdministrationScreenService
private administrationScreenService: AdministrationScreenService,
private serverConfigResource: ServerConfigResource
) {
super();
this.permissionsService.onUpdate.subscribe(this.handleActivate.bind(this));
@@ -87,7 +88,14 @@ export class AdministrationScreenServiceBootstrap extends Bootstrap {
});
}
load(): void | Promise<void> { }
async load() {
const config = await this.serverConfigResource.load(null);
if (config?.configurationMode) {
this.administrationScreenService.configurationWizard = true;
this.administrationScreenService.navigateToRoot();
}
}
private async handleCanActivate(toState: RouterState, fromState: RouterState) {
if (!toState.params?.item) {
@@ -103,16 +111,11 @@ export class AdministrationScreenServiceBootstrap extends Bootstrap {
}
private async handleActivate() {
if (!this.permissionsService.has(EAdminPermission.admin)) {
if (await !this.permissionsService.hasAsync(EAdminPermission.admin)) {
this.screenService.navigateToRoot();
return;
}
// FIXME: for development purposes only, must be removed
if (this.screenService.isActive(AdministrationScreenService.setupName)) {
this.administrationScreenService.configurationWizard = true;
}
if (this.administrationScreenService.activeItem) {
await this.administrationItemService.activate(
this.administrationScreenService.activeItem,
@@ -9,6 +9,7 @@
import { observable, computed } from 'mobx';
import { injectable } from '@cloudbeaver/core-di';
import { SessionResource, ServerConfigResource } from '@cloudbeaver/core-root';
import { ScreenService } from '@cloudbeaver/core-routing';
import { LocalStorageSaveService } from '@cloudbeaver/core-settings';
@@ -83,7 +84,9 @@ export class ConfigurationWizardService {
private administrationItemService: AdministrationItemService,
private administrationScreenService: AdministrationScreenService,
private autoSaveService: LocalStorageSaveService,
private screenService: ScreenService
private screenService: ScreenService,
private sessionResource: SessionResource,
private serverConfigResource: ServerConfigResource
) {
this.state = {
finishedSteps: [],
@@ -168,6 +171,8 @@ export class ConfigurationWizardService {
await step.configurationWizardOptions.onConfigurationFinish();
}
}
await this.serverConfigResource.update();
await this.sessionResource.update();
this.administrationScreenService.clearItemsState();
this.state.finishedSteps = [];
this.administrationScreenService.configurationWizard = false;
@@ -74,8 +74,8 @@ export class ConfigurationWizardPagesBootstrapService extends Bootstrap {
}
try {
await this.graphQLService.gql.configureServer({ configuration: state.serverConfig });
await this.graphQLService.gql.setDefaultNavigatorSettings({ settings: state.navigatorConfig });
await this.graphQLService.gql.configureServer({ configuration: state.serverConfig });
} catch (exception) {
this.notificationService.logException(exception, 'Can\'t save server configuration');
@@ -10,7 +10,7 @@ import { observer } from 'mobx-react';
import styled, { css } from 'reshadow';
import {
SubmittingForm, InputGroup, InputField, Checkbox, useFocus
SubmittingForm, InputGroup, InputField, Checkbox, useFocus, Switch
} from '@cloudbeaver/core-blocks';
import { useTranslate } from '@cloudbeaver/core-localization';
import { ServerConfigInput, NavigatorSettingsInput } from '@cloudbeaver/core-sdk';
@@ -49,19 +49,19 @@ const boxStyles = css`
flex-wrap: wrap;
}
box-element {
min-width: 450px;
width: 450px;
}
`;
type Props = {
serverConfig: ServerConfigInput;
navigatorConfig: NavigatorSettingsInput;
onChange: () => void;
onSave: () => void;
}
export const WelcomeConfigForm = observer(function WelcomeConfigForm({
serverConfig,
navigatorConfig,
onChange,
onSave,
}: Props) {
const translate = useTranslate();
@@ -78,6 +78,7 @@ export const WelcomeConfigForm = observer(function WelcomeConfigForm({
name='serverName'
state={serverConfig}
mod='surface'
required
>
{translate('administration_configuration_wizard_welcome_server_name')}
</InputField>
@@ -90,6 +91,7 @@ export const WelcomeConfigForm = observer(function WelcomeConfigForm({
name="adminName"
state={serverConfig}
mod='surface'
required
>
{translate('administration_configuration_wizard_welcome_admin_name')}
</InputField>
@@ -98,6 +100,7 @@ export const WelcomeConfigForm = observer(function WelcomeConfigForm({
name="adminPassword"
state={serverConfig}
mod='surface'
required
>
{translate('administration_configuration_wizard_welcome_admin_password')}
</InputField>
@@ -105,89 +108,36 @@ export const WelcomeConfigForm = observer(function WelcomeConfigForm({
<InputGroup>{translate('administration_configuration_wizard_welcome_plugins')}</InputGroup>
</group>
<group as="div">
<Checkbox
<Switch
name="anonymousAccessEnabled"
state={serverConfig}
checkboxLabel={translate('administration_configuration_wizard_welcome_anonymous_access')}
mod='surface'
/>
description={translate('administration_configuration_wizard_welcome_anonymous_access_description')}
mod={['primary']}
disabled={!serverConfig.authenticationEnabled}
>
{translate('administration_configuration_wizard_welcome_anonymous_access')}
</Switch>
</group>
<group as="div">
<Checkbox
<Switch
name="authenticationEnabled"
state={serverConfig}
checkboxLabel={translate('administration_configuration_wizard_welcome_authentication')}
mod='surface'
/>
description={translate('administration_configuration_wizard_welcome_authentication_description')}
onChange={onChange}
mod={['primary']}
>
{translate('administration_configuration_wizard_welcome_authentication')}
</Switch>
</group>
<group as="div">
<Checkbox
<Switch
name="customConnectionsEnabled"
state={serverConfig}
checkboxLabel={translate('administration_configuration_wizard_welcome_custom_connections')}
mod='surface'
/>
</group>
</box-element>
<box-element as='div' hidden>
<group as="div">
<InputGroup>{translate('administration_configuration_wizard_welcome_navigator')}</InputGroup>
</group>
<group as="div">
<Checkbox
name="hideFolders"
state={navigatorConfig}
checkboxLabel={translate('administration_configuration_wizard_welcome_navigator_hide_folders')}
mod='surface'
/>
</group>
<group as="div">
<Checkbox
name="hideSchemas"
state={navigatorConfig}
checkboxLabel={translate('administration_configuration_wizard_welcome_navigator_hide_schemas')}
mod='surface'
/>
</group>
<group as="div">
<Checkbox
name="hideVirtualModel"
state={navigatorConfig}
checkboxLabel={translate('administration_configuration_wizard_welcome_navigator_hide_virtual_model')}
mod='surface'
/>
</group>
<group as="div">
<Checkbox
name="mergeEntities"
state={navigatorConfig}
checkboxLabel={translate('administration_configuration_wizard_welcome_navigator_merge_entities')}
mod='surface'
/>
</group>
<group as="div">
<Checkbox
name="showOnlyEntities"
state={navigatorConfig}
checkboxLabel={translate('administration_configuration_wizard_welcome_navigator_show_only_entities')}
mod='surface'
/>
</group>
<group as="div">
<Checkbox
name="showSystemObjects"
state={navigatorConfig}
checkboxLabel={translate('administration_configuration_wizard_welcome_navigator_show_system_objects')}
mod='surface'
/>
</group>
<group as="div">
<Checkbox
name="showUtilityObjects"
state={navigatorConfig}
checkboxLabel={translate('administration_configuration_wizard_welcome_navigator_show_utility_objects')}
mod='surface'
/>
description={translate('administration_configuration_wizard_welcome_custom_connections_description')}
mod={['primary']}
>
{translate('administration_configuration_wizard_welcome_custom_connections')}
</Switch>
</group>
</box-element>
</box>
@@ -54,7 +54,7 @@ export const WelcomePage = observer(function WelcomePage() {
<p>Here you can configure something...</p>
<WelcomeConfigForm
serverConfig={controller.state.serverConfig}
navigatorConfig={controller.state.navigatorConfig}
onChange={controller.onChange}
onSave={controller.finish}
/>
</layout-grid-cell>
@@ -27,7 +27,7 @@ export class WelcomePageController {
serverName: 'CloudBeaver',
adminName: 'admin',
adminPassword: '',
anonymousAccessEnabled: false,
anonymousAccessEnabled: true,
authenticationEnabled: false,
customConnectionsEnabled: false,
},
@@ -43,5 +43,11 @@ export class WelcomePageController {
}));
}
onChange = () => {
if (!this.state.serverConfig.authenticationEnabled) {
this.state.serverConfig.anonymousAccessEnabled = true;
}
}
finish = () => this.configurationWizardService.next()
}
@@ -2,18 +2,21 @@ export default [
['administration_menu_enter', 'Administration'],
['administration_menu_back', 'Back to app'],
['administration_configuration_wizard_title', 'Initial Server Configuration'],
['administration_configuration_wizard_welcome', 'Welcome'],
['administration_configuration_wizard_welcome_plugins', 'Plugins'],
['administration_configuration_wizard_welcome_step_description', 'Welcome'],
['administration_configuration_wizard_welcome', 'Server configuration'],
['administration_configuration_wizard_welcome_plugins', 'Configuration'],
['administration_configuration_wizard_welcome_step_description', 'Server configuration'],
['administration_configuration_wizard_welcome_server_name', 'Server Name'],
['administration_configuration_wizard_welcome_admin', 'Admin Credentials'],
['administration_configuration_wizard_welcome_admin_name', 'Name'],
['administration_configuration_wizard_welcome_admin_password', 'Password'],
['administration_configuration_wizard_welcome_anonymous_access', 'Anonymous Access'],
['administration_configuration_wizard_welcome_authentication', 'Authentication'],
['administration_configuration_wizard_welcome_custom_connections', 'Custom Connections'],
['administration_configuration_wizard_finish', 'Finalization'],
['administration_configuration_wizard_finish_step_description', 'Finalization'],
['administration_configuration_wizard_welcome_admin', 'Administrator'],
['administration_configuration_wizard_welcome_admin_name', 'Admin user name'],
['administration_configuration_wizard_welcome_admin_password', 'Admin password'],
['administration_configuration_wizard_welcome_anonymous_access', 'Allow anonymous access'],
['administration_configuration_wizard_welcome_anonymous_access_description', 'Allows to work with CloudBeaver without user authentication'],
['administration_configuration_wizard_welcome_authentication', 'Enable users authentication'],
['administration_configuration_wizard_welcome_authentication_description', 'Allows users to authenticate. Otherwise only anonymous access is enabled'],
['administration_configuration_wizard_welcome_custom_connections', 'Enable custom connections'],
['administration_configuration_wizard_welcome_custom_connections_description', 'Allows users to create custom connections. Otherwise only administrator can create connections'],
['administration_configuration_wizard_finish', 'Confirmation'],
['administration_configuration_wizard_finish_step_description', 'Confirmation'],
['administration_configuration_wizard_welcome_navigator', 'Navigator'],
['administration_configuration_wizard_welcome_navigator_hide_folders', 'Hide Folders'],
['administration_configuration_wizard_welcome_navigator_hide_schemas', 'Hide Schemas'],
@@ -44,6 +44,7 @@ export const InputField: InputFieldType = observer(function InputField(
) {
const {
name,
required,
state,
children,
className,
@@ -65,13 +66,14 @@ export const InputField: InputFieldType = observer(function InputField(
return styled(useStyles(baseFormControlStyles))(
<field as="div" className={className}>
<field-label as='label'>{children}</field-label>
<field-label as='label'>{children} {required && '*'}</field-label>
<input
{...rest}
onChange={handleChange}
name={name}
value={value}
{...use({ mod })}
required={required}
/>
</field>
);
@@ -6,21 +6,30 @@
* you may not use this file except in compliance with the License.
*/
import styled, { use, css } from 'reshadow';
import styled, { css } from 'reshadow';
import { useStyles } from '@cloudbeaver/core-theming';
import { useStyles, composes } from '@cloudbeaver/core-theming';
import { baseFormControlStyles } from './baseFormControlStyles';
const styles = css`
field-label {
composes: theme-typography--body2 from global;
margin-left: 150px;
text-align: left;
font-weight: initial;
}
`;
const styles = composes(
css`
field-label {
composes: theme-border-color-background from global;
}
`,
css`
field-label {
composes: theme-typography--body2 from global;
margin-left: 150px;
text-align: left;
font-weight: initial;
padding-bottom: 8px;
border-bottom: solid 1px #dedede;
width: 100%;
}
`
);
type Props = React.PropsWithChildren<{
className?: string;
}>
@@ -0,0 +1,154 @@
/*
* cloudbeaver - Cloud Database Manager
* Copyright (C) 2020 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
import { observer } from 'mobx-react';
import { useCallback } from 'react';
import styled, { css } from 'reshadow';
import { useStyles, composes } from '@cloudbeaver/core-theming';
import { baseFormControlStyles } from './baseFormControlStyles';
const switchStyles = composes(
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;
}
`,
css`
field label {
width: auto;
}
`
);
const switchMod = {
primary: composes(
css`
switch-control {
composes: theme-switch_primary from global;
}
`
),
};
const switchState = {
disabled: composes(
css`
switch-control {
composes: theme-switch--disabled from global;
}
`
),
checked: composes(
css`
switch-control {
composes: theme-switch--checked from global;
}
`
),
};
type BaseProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'type' | 'value' | 'checked'> & {
mod?: (keyof typeof switchMod)[];
label?: string;
description?: string;
}
type ControlledProps = BaseProps & {
checked?: boolean;
onChange?(value: boolean, name?: string): any;
state?: never;
}
type ObjectProps<TKey extends keyof TState, TState> = BaseProps & {
name: TKey;
state: TState;
onChange?(value: boolean, name: TKey): any;
checked?: never;
}
type SwitchType = {
(props: ControlledProps): JSX.Element;
<TKey extends keyof TState, TState>(props: ObjectProps<TKey, TState>): JSX.Element;
}
export const Switch: SwitchType = observer(function Switch(props: ControlledProps | ObjectProps<any, any>) {
const {
name,
id,
label,
description,
state,
className,
children,
onChange,
mod = [],
disabled,
...rest
} = props;
const handleChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
if (state) {
state[name] = event.target.checked;
}
if (onChange) {
onChange(event.target.checked, name);
}
}, [state, name, onChange]);
const checked = state ? state[name] : props.checked;
return styled(useStyles(
baseFormControlStyles,
switchStyles,
...mod.map(mod => switchMod[mod]),
disabled && switchState.disabled,
checked && switchState.checked
))(
<field as="div" className={className}>
<field-label as="div">{children}</field-label>
<switch-control as='div'>
<switch-control-track as='div' />
<switch-control-underlay as='div' >
<switch-control-thumb as='div' />
<switch-input
as='input'
{...rest}
type="checkbox"
id={id}
role="switch"
onChange={handleChange}
aria-checked={checked}
checked={checked}
disabled={disabled}
/>
</switch-control-underlay>
</switch-control>
<label htmlFor={id}>{label}</label>
<field-description as='div'>{description}</field-description>
</field>
);
});
@@ -11,6 +11,7 @@ import { css } from 'reshadow';
export const baseFormControlStyles = css`
field {
display: flex;
flex-wrap: wrap;
flex: auto;
box-sizing: border-box;
align-items: center;
@@ -33,4 +34,11 @@ export const baseFormControlStyles = css`
line-height: 16px;
font-weight: 500;
}
field-description {
composes: theme-typography--caption from global;
flex-basis: 100%;
padding-top: 8px;
padding-left: 150px;
}
`;
+1
View File
@@ -20,6 +20,7 @@ export * from './FormControls/InputGroup';
export * from './FormControls/Radio';
export * from './FormControls/ShadowInput';
export * from './FormControls/SubmittingForm';
export * from './FormControls/Switch';
export * from './FormControls/Textarea';
export * from './ErrorMessage';
export * from './IconButton';
@@ -42,6 +42,7 @@ export class AppBootstrap {
) { }
register(): void | Promise<void> {
this.routerService.start();
this.exceptionsCatcherService.subscribe();
this.sessionExpireService.subscribe();
@@ -60,6 +61,5 @@ export class AppBootstrap {
async doAfterPluginsInit() {
// todo this should be moved to the NavigationTabs component creation phase but now it leads to bugs
await this.navigationTabsService.restoreTabs();
this.routerService.start();
}
}
@@ -14,12 +14,12 @@ import { Translate } from '@cloudbeaver/core-localization';
import { useStyles } from '@cloudbeaver/core-theming';
export function ConnectionsDrawerItem({
item, onSelect, style, disabled,
item, onSelect, style, disabled, configurationWizard,
}: AdministrationItemDrawerProps) {
return styled(useStyles(...style))(
<Tab tabId={item.name} onOpen={() => onSelect(item.name)} disabled={disabled}>
<TabIcon icon='/icons/connection.svg' />
<TabTitle><Translate token='connections_administration_item'/></TabTitle>
<TabTitle><Translate token={configurationWizard ? 'connections_administration_configuration_wizard_step_title' : 'connections_administration_item'}/></TabTitle>
</Tab>
);
}
@@ -1,7 +1,8 @@
export default [
['connections_administration_item', 'Connection Management'],
['connections_administration_new_connection', 'New connection'],
['connections_administration_configuration_wizard_step_description', 'Configuring connections...'],
['connections_administration_configuration_wizard_step_title', 'Database connections'],
['connections_administration_configuration_wizard_step_description', 'Database connections'],
['connections_connection_edit_authentication', 'Authentication'],
['connections_connection_edit_access', 'Access'],
['connections_connection_edit_access_load_failed', 'Connection access loading failed'],
+4 -2
View File
@@ -7,7 +7,7 @@
*/
import {
useContext, useEffect, useRef, useState, useMemo,
useContext, useEffect, useRef, useMemo,
} from 'react';
import { appContext } from './AppContext';
@@ -35,7 +35,9 @@ export function useController<T>(ctor: IServiceConstructor<T>, ...args: any[]):
controller.init(...args);
}
controllerRef.current = controller;
}, [...args]);
}, [...args, args.length]);
/* we put dynamic array length as the dependency because of preact bug,
otherwise useMemo will not be triggered on array change */
useEffect(() => () => {
if (isDestructibleController(controllerRef.current)) {
@@ -21,6 +21,10 @@ export class ServerConfigResource extends CachedDataResource<ServerConfig | null
return !!this.data;
}
async update() {
await this.refresh(null);
}
protected async loader(key: null): Promise<ServerConfig> {
const { serverConfig } = await this.graphQLService.gql.serverConfig();
@@ -30,6 +30,10 @@ export class SessionResource extends CachedDataResource<SessionState | null, nul
return !!this.data;
}
async update() {
await this.refresh(null);
}
protected async loader(key: null): Promise<SessionState> {
const { session } = await this.graphQLService.gql.openSession();
@@ -6,8 +6,13 @@ query serverConfig {
supportsCustomConnections
supportsConnectionBrowser
supportsWorkspaces
anonymousAccessEnabled
authenticationEnabled
configurationMode
developmentMode
supportedLanguages {
isoCode
displayName
+3 -1
View File
@@ -1271,7 +1271,7 @@ export type ReadSessionLogQuery = { log: Array<Pick<LogEntry, 'time' | 'type' |
export type ServerConfigQueryVariables = Exact<{ [key: string]: never }>;
export type ServerConfigQuery = { serverConfig: (
Pick<ServerConfig, 'name' | 'version' | 'productConfiguration' | 'supportsCustomConnections' | 'supportsConnectionBrowser' | 'supportsWorkspaces' | 'anonymousAccessEnabled' | 'authenticationEnabled'>
Pick<ServerConfig, 'name' | 'version' | 'productConfiguration' | 'supportsCustomConnections' | 'supportsConnectionBrowser' | 'supportsWorkspaces' | 'anonymousAccessEnabled' | 'authenticationEnabled' | 'configurationMode' | 'developmentMode'>
& { supportedLanguages: Array<Pick<ServerLanguage, 'isoCode' | 'displayName' | 'nativeName'>>; defaultNavigatorSettings: Pick<NavigatorSettings, 'showSystemObjects' | 'showUtilityObjects' | 'showOnlyEntities' | 'mergeEntities' | 'hideFolders' | 'hideSchemas' | 'hideVirtualModel'> }
); };
@@ -2117,6 +2117,8 @@ export const ServerConfigDocument = `
supportsWorkspaces
anonymousAccessEnabled
authenticationEnabled
configurationMode
developmentMode
supportedLanguages {
isoCode
displayName
+2 -1
View File
@@ -20,7 +20,8 @@
"@material/radio": "^4.0.0",
"@material/ripple": "^4.0.0",
"@material/theme": "^4.0.0",
"@material/typography": "^4.0.0"
"@material/typography": "^4.0.0",
"@material/switch": "^4.0.0"
},
"peerDependencies": {
"@cloudbeaver/core-di": "^0.1.0",
@@ -20,6 +20,7 @@
@import 'tag';
@import 'button';
@import 'radio';
@import 'switch';
@import 'tabs';
@import 'table';
@import 'placeholder';
@@ -0,0 +1,44 @@
/*
* cloudbeaver - Cloud Database Manager
* Copyright (C) 2020 DBeaver Corp and others
*
* Licensed under the Apache License, Version 2.0.
* you may not use this file except in compliance with the License.
*/
@import '@material/switch/mixins';
@mixin switch-core-styles($query: mdc-feature-all()) {
@include mdc-switch-core-styles($query);
.theme-switch {
composes: mdc-switch;
}
.theme-switch_native-control {
composes: mdc-switch__native-control;
}
.theme-switch__track {
composes: mdc-switch__track;
}
.theme-switch__thumb-underlay {
composes: mdc-switch__thumb-underlay;
}
.theme-switch__thumb {
composes: mdc-switch__thumb;
}
.theme-switch--checked {
composes: mdc-switch--checked;
}
.theme-switch--disabled {
composes: mdc-switch--disabled;
}
.theme-switch_primary {
$mdc-switch-baseline-theme-color: primary;
@include mdc-switch-toggled-on-track-color($mdc-switch-baseline-theme-color, $query);
@include mdc-switch-toggled-on-thumb-color($mdc-switch-baseline-theme-color, $query);
@include mdc-switch-toggled-off-track-color($mdc-switch-toggled-off-track-color, $query);
@include mdc-switch-toggled-off-thumb-color($mdc-switch-toggled-off-thumb-color, $query);
}
}
@@ -16,6 +16,7 @@
@include table;
@include button-core-styles;
@include radio-core-styles;
@include switch-core-styles;
@include form-controls;
@include placeholder;
@include delimiter($mdc-theme-secondary);
@@ -16,6 +16,7 @@
@include table;
@include button-core-styles;
@include radio-core-styles;
@include switch-core-styles;
@include form-controls;
@include placeholder;
@include delimiter($mdc-theme-secondary);
@@ -26,29 +26,29 @@ export type Style = BaseStyles | ThemeSelector
* @param componentStyles styles array
*/
export function useStyles(
...componentStyles: Array<Style | boolean>
...componentStyles: Array<Style | boolean | undefined>
) {
// todo do you understand that we store ALL STYLES in each component that uses this hook?
const [loadedStyles, setLoadedStyles] = useState<BaseStyles[]>([]);
const themeService = useService(ThemeService);
const currentThemeId = useObserver(() => themeService.currentThemeId);
const filteredStyles = componentStyles.filter(Boolean) as Array<Style>;
useMemo(() => {
Promise
.all(
componentStyles.filter(Boolean).map(
style => ((typeof style === 'object' || style instanceof Composes) ? style : (style as ThemeSelector)(currentThemeId))
)
)
.all(filteredStyles.map(style => (
(typeof style === 'object' || style instanceof Composes) ? style : style(currentThemeId)
)))
.then(styles => setLoadedStyles(flat(styles)));
}, [currentThemeId]);
}, [currentThemeId, ...filteredStyles, filteredStyles.length]);
const styles = useMemo(() => {
const themeStyles = themeService.getThemeStyles(currentThemeId);
return applyComposes([...themeStyles, ...loadedStyles]);
}, [currentThemeId, ...loadedStyles]);
}, [currentThemeId, ...loadedStyles, loadedStyles.length]);
/* we put dynamic array length as the dependency because of preact bug,
otherwise useMemo will not be triggered on array change */
return create(styles); // todo this method is called in each rerender
}
+15
View File
@@ -2256,6 +2256,21 @@
dependencies:
"@material/feature-targeting" "^4.0.0"
"@material/switch@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@material/switch/-/switch-4.0.0.tgz#5c5da30389f48f4f5245eb9f325cbc0e5f6bf2d4"
integrity sha512-90fvz0bYgeGQNBD14rdTOI/dvAPANwDQLIodOpE/r2bOZjge1K0rHSAm+TVwNCnrrM9HGmG8Ew0sIeEIj3jMMw==
dependencies:
"@material/animation" "^4.0.0"
"@material/base" "^4.0.0"
"@material/dom" "^4.0.0"
"@material/elevation" "^4.0.0"
"@material/feature-targeting" "^4.0.0"
"@material/ripple" "^4.0.0"
"@material/rtl" "^4.0.0"
"@material/theme" "^4.0.0"
tslib "^1.9.3"
"@material/theme@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@material/theme/-/theme-4.0.0.tgz#8de388fcdbae20fd09b02a3bdef3049bf2f623a8"