Merge pull request #50 from dbeaver/feat-routing

feat(administration): layout
This commit is contained in:
Serge Rider
2020-05-24 16:58:57 +03:00
committed by GitHub
30 changed files with 293 additions and 114 deletions
@@ -6,33 +6,46 @@
* you may not use this file except in compliance with the License.
*/
import { SettingsMenuService } from '@dbeaver/core/app';
import { injectable } from '@dbeaver/core/di';
import { SettingsMenuService, ScreenService, AppScreenService } from '@dbeaver/core/app';
import { injectable, Bootstrap } from '@dbeaver/core/di';
import { PermissionsService } from '@dbeaver/core/root';
import { AdministrationScreenService } from './AdministrationScreen/AdministrationScreenService';
export const ADMINISTRATION_PERMISSION = 'admin';
import { EAdminPermission } from './EAdminPermission';
@injectable()
export class AdministrationMenuService {
static administrationMenuToken = 'administrationMenu';
export class AdministrationMenuService extends Bootstrap {
constructor(
private settingsMenuService: SettingsMenuService,
private permissionsService: PermissionsService,
private screenService: ScreenService,
private administrationScreenService: AdministrationScreenService,
) { }
private appScreenService: AppScreenService
) {
super();
}
register() {
bootstrap() {
this.settingsMenuService.addMenuItem(
SettingsMenuService.settingsMenuToken,
{
id: AdministrationMenuService.administrationMenuToken,
id: 'administrationMenuEnter',
order: 0,
isHidden: () => !this.permissionsService.has(ADMINISTRATION_PERMISSION),
isHidden: () => !this.permissionsService.has(EAdminPermission.admin)
|| this.screenService.isActive(AdministrationScreenService.screenName),
title: 'administration_menu_enter',
onClick: () => this.administrationScreenService.navigateToRoot(),
}
);
this.settingsMenuService.addMenuItem(
SettingsMenuService.settingsMenuToken,
{
id: 'administrationMenuBack',
order: 0,
isHidden: () => !this.screenService.isActive(AdministrationScreenService.screenName),
title: 'administration_menu_back',
onClick: () => this.appScreenService.navigateToRoot(),
}
);
}
}
@@ -6,6 +6,21 @@
* you may not use this file except in compliance with the License.
*/
export function AdministrationScreen() {
return <>Hello</>;
}
import { observer } from 'mobx-react';
import { usePermission } from '@dbeaver/core/root';
import { EAdminPermission } from '../EAdminPermission';
import { AdministrationTopAppBar } from './AdministrationTopAppBar/AdministrationTopAppBar';
export const AdministrationScreen = observer(function AdministrationScreen() {
if (!usePermission(EAdminPermission.admin)) {
return <>You has no permission</>;
}
return (
<>
<AdministrationTopAppBar />
</>
);
});
@@ -6,26 +6,44 @@
* you may not use this file except in compliance with the License.
*/
import { ScreenService, RouterService } from '@dbeaver/core/app';
import { injectable } from '@dbeaver/core/di';
import { ScreenService, RouterService, AppScreenService } from '@dbeaver/core/app';
import { injectable, Bootstrap } from '@dbeaver/core/di';
import { PermissionsService } from '@dbeaver/core/root';
import { EAdminPermission } from '../EAdminPermission';
import { AdministrationScreen } from './AdministrationScreen';
@injectable()
export class AdministrationScreenService {
export class AdministrationScreenService extends Bootstrap {
static screenName = 'administration'
constructor(
private screenService: ScreenService,
private routerService: RouterService,
) {}
private permissionsService: PermissionsService,
private appScreenService: AppScreenService
) {
super();
this.permissionsService.onUpdate.subscribe(this.handleActivate.bind(this));
}
navigateToRoot() {
return this.routerService.router.navigate(AdministrationScreenService.screenName);
this.routerService.router.navigate(AdministrationScreenService.screenName);
}
register() {
this.screenService.add({ name: AdministrationScreenService.screenName, path: '/admin', component: AdministrationScreen });
bootstrap() {
this.screenService.create({
name: AdministrationScreenService.screenName,
path: '/admin',
component: AdministrationScreen,
onActivate: this.handleActivate.bind(this),
});
}
private handleActivate() {
if (!this.permissionsService.has(EAdminPermission.admin)) {
this.appScreenService.navigateToRoot();
}
}
}
@@ -0,0 +1,22 @@
/*
* 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 { Placeholder, TopAppBar } from '@dbeaver/core/blocks';
import { useService } from '@dbeaver/core/di';
import { AdministrationTopAppBarService } from './AdministrationTopAppBarService';
export function AdministrationTopAppBar() {
const administrationTopAppBarService = useService(AdministrationTopAppBarService);
return (
<TopAppBar>
<Placeholder container={administrationTopAppBarService.placeholder} />
</TopAppBar>
);
}
@@ -0,0 +1,24 @@
/*
* 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 { SettingsMenu, Logo } from '@dbeaver/core/app';
import { PlaceholderContainer } from '@dbeaver/core/blocks';
import { injectable } from '@dbeaver/core/di';
import { Fill } from './Fill';
@injectable()
export class AdministrationTopAppBarService {
readonly placeholder = new PlaceholderContainer();
constructor() {
this.placeholder.add(Logo, 0);
this.placeholder.add(Fill, 3);
this.placeholder.add(SettingsMenu, 4);
}
}
@@ -0,0 +1,19 @@
/*
* 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 styled, { css } from 'reshadow';
const styles = css`
fill {
flex: 1;
}
`;
export function Fill() {
return styled(styles)(<fill as='div' />);
}
@@ -1,25 +0,0 @@
/*
* 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 { injectable } from '@dbeaver/core/di';
import { AdministrationMenuService } from './AdministrationMenuService';
import { AdministrationScreenService } from './AdministrationScreen/AdministrationScreenService';
@injectable()
export class Bootstrap {
constructor(
private administrationMenuService: AdministrationMenuService,
private administrationScreenService: AdministrationScreenService,
) { }
bootstrap() {
this.administrationScreenService.register();
this.administrationMenuService.register();
}
}
@@ -0,0 +1,11 @@
/*
* 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.
*/
export enum EAdminPermission {
admin = 'admin'
}
@@ -3,3 +3,5 @@ import { manifest } from './manifest';
export default manifest;
export * from './AdministrationMenuService';
export * from './AdministrationScreen/AdministrationScreenService';
export * from './AdministrationScreen/AdministrationTopAppBar/AdministrationTopAppBarService';
@@ -10,7 +10,7 @@ import { IServiceInjector, PluginManifest } from '@dbeaver/core/di';
import { AdministrationMenuService } from './AdministrationMenuService';
import { AdministrationScreenService } from './AdministrationScreen/AdministrationScreenService';
import { Bootstrap } from './Bootstrap';
import { AdministrationTopAppBarService } from './AdministrationScreen/AdministrationTopAppBar/AdministrationTopAppBarService';
export const manifest: PluginManifest = {
info: {
@@ -20,11 +20,9 @@ export const manifest: PluginManifest = {
providers: [
AdministrationMenuService,
AdministrationScreenService,
AdministrationTopAppBarService,
],
async initialize(services: IServiceInjector) {
services
.resolveServiceByClass(Bootstrap)
.bootstrap();
},
};
+4 -2
View File
@@ -12,10 +12,12 @@
"test": "echo \"Error: no test specified\" && exit 1"
},
"devDependencies": {
"@dbeaver/core": "^0.1.0",
"@dbeaver/builder": "^0.1.0"
"@dbeaver/administration": "^0.1.0",
"@dbeaver/builder": "^0.1.0",
"@dbeaver/core": "^0.1.0"
},
"peerDependencies": {
"@dbeaver/administration": "^0.1.0",
"@dbeaver/core": "^0.1.0"
}
}
@@ -6,6 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { AdministrationTopAppBarService } from '@dbeaver/administration';
import { SettingsMenuService, TopNavService } from '@dbeaver/core/app';
import { injectable } from '@dbeaver/core/di';
import { ServerService } from '@dbeaver/core/root';
@@ -24,6 +25,7 @@ export class AuthMenuService {
private settingsMenuService: SettingsMenuService,
private authenticationService: AuthenticationService,
private topNavService: TopNavService,
private administrationTopAppBarService: AdministrationTopAppBarService,
) { }
register() {
@@ -50,6 +52,7 @@ export class AuthMenuService {
);
this.topNavService.placeholder.add(UserInfo, 4);
this.administrationTopAppBarService.placeholder.add(UserInfo, 4);
}
private async logout() {
@@ -42,6 +42,7 @@ module.exports = {
data_transfer_exporting_sql: 'SQL:',
administration_menu_enter: 'Administration',
administration_menu_back: 'Back to app',
authentication_login_dialog_title: 'Authentication',
authentication_login: 'Login',
@@ -41,6 +41,7 @@ module.exports = {
data_transfer_exporting_sql: 'SQL:',
administration_menu_enter: 'Администрирование',
administration_menu_back: 'Вернуться в приложение',
authentication_login_dialog_title: 'Авторизация',
authentication_login: 'Войти',
@@ -6,9 +6,6 @@
* you may not use this file except in compliance with the License.
*/
import { DialogsPortal } from '@dbeaver/core/dialogs';
import { Notifications } from '../Notifications/Notifications';
import { TopNavBar } from '../TopNavBar/TopNavBar';
import { Main } from './Main';
@@ -18,8 +15,6 @@ export function AppScreen() {
<>
<TopNavBar />
<Main />
<DialogsPortal />
<Notifications />
</>
);
}
@@ -8,17 +8,25 @@
import { injectable } from '@dbeaver/core/di';
import { RouterService } from '../RouterService';
import { ScreenService } from '../Screen/ScreenService';
import { AppScreen } from './AppScreen';
@injectable()
export class AppScreenService {
static screenName = 'app'
constructor(
private screenService: ScreenService
private screenService: ScreenService,
private routerService: RouterService,
) {}
navigateToRoot() {
this.routerService.router.navigate(AppScreenService.screenName);
}
register() {
this.screenService.add({ name: 'app', path: '/', component: AppScreen });
this.screenService.create({ name: AppScreenService.screenName, path: '/', component: AppScreen });
}
}
+4
View File
@@ -10,8 +10,10 @@ import { observer } from 'mobx-react';
import styled, { css } from 'reshadow';
import { useService } from '@dbeaver/core/di';
import { DialogsPortal } from '@dbeaver/core/dialogs';
import { useStyles, useTheme } from '@dbeaver/core/theming';
import { Notifications } from './Notifications/Notifications';
import { ScreenService } from './Screen/ScreenService';
const bodyStyles = css`
@@ -31,6 +33,8 @@ export const Body = observer(function Body() {
return styled(useStyles(bodyStyles))(
<container as="div">
{Screen && <Screen />}
<DialogsPortal />
<Notifications />
</container>
);
});
@@ -28,7 +28,7 @@ export class ScreenService {
this.routerService.subscribe(this.onRouteChange.bind(this));
}
add(screen: IScreen) {
create(screen: IScreen) {
if (this.screens.has(screen.name)) {
return;
}
@@ -37,6 +37,14 @@ export class ScreenService {
this.routerService.router.add({ name: screen.name, path: screen.path });
}
isActive(name: string, strict = false): boolean {
if (strict) {
return this.screen?.name === name;
}
return this.screen?.name.startsWith(name) || false;
}
buildUrl(screen: string) {
return this.routerService.router.buildUrl(screen);
}
@@ -7,37 +7,20 @@
*/
import { observer } from 'mobx-react';
import styled, { css } from 'reshadow';
import { Icon } from '@dbeaver/core/blocks';
import { AppLogo } from '@dbeaver/core/blocks';
import { useService } from '@dbeaver/core/di';
import { ServerService } from '@dbeaver/core/root';
import { AppScreenService } from '../AppScreen/AppScreenService';
declare const version: string; // declared in webpack DefinePlugin // todo move to enviroment?
const styles = css`
logo {
height: 100%;
display: flex;
align-items: center;
margin-right: 16px;
width: 250px;
}
Icon {
height: 24px;
width: auto;
margin-bottom: 2px;
}
`;
export const Logo = observer(function Logo() {
const serverService = useService(ServerService);
const appScreenService = useService(AppScreenService);
const title = `Frontend: ${version}\nBackend: ${serverService.config.data?.version}`;
return styled(styles)(
<logo as="div" title={title}>
<Icon name="logo" viewBox="0 0 361 73" />
</logo>
);
return <AppLogo title={title} onClick={() => appScreenService.navigateToRoot()}/>;
});
@@ -6,21 +6,17 @@
* you may not use this file except in compliance with the License.
*/
import styled from 'reshadow';
import { Placeholder } from '@dbeaver/core/blocks';
import { Placeholder, TopAppBar } from '@dbeaver/core/blocks';
import { useService } from '@dbeaver/core/di';
import { useStyles } from '@dbeaver/core/theming';
import { TopNavService } from './TopNavBarService';
import { topNavBarStyles } from './topNavBarStyles';
export function TopNavBar() {
const topNavBarService = useService(TopNavService);
return styled(useStyles(topNavBarStyles))(
<header>
return (
<TopAppBar>
<Placeholder container={topNavBarService.placeholder} />
</header>
</TopAppBar>
);
}
+2
View File
@@ -44,6 +44,8 @@ export * from './shared/InlineEditor/InlineEditor';
// components
export * from './Body';
export * from './Notifications/NotificationsItem/ErrorDetailsDialog/ErrorDetailsDialog';
export * from './TopNavBar/SettingsMenu/SettingsMenu';
export * from './TopNavBar/Logo';
// Interfaces
export * from './shared/Navigation/INavigator';
@@ -22,7 +22,7 @@ export type PlaceholderElement<T = unknown> = {
order?: number;
}
export class PlaceholderContainer<T = unknown> {
export class PlaceholderContainer<T = any> {
private placeholders = observable<PlaceholderElement<T>>([], { deep: false });
get() {
+19 -18
View File
@@ -1,22 +1,23 @@
export * from './Icons';
export * from './Split';
export * from './StaticImage';
export * from './ComplexLoader';
export * from './Loader/Loader';
export * from './StaticImage';
export * from './Tabs';
export * from './Button';
export * from './Radio';
export * from './InputField';
export * from './ShadowInput';
export * from './ErrorMessage';
export * from './IconOrImage';
export * from './TextPlaceholder';
export * from './useClipboard';
export * from './SubmittingForm';
export * from './SanitizedHTML/SanitizedHTML';
export * from './Icons';
export * from './ItemList';
export * from './PropertiesTable';
export * from './IconButton';
export * from './layout/AppLogo';
export * from './layout/TopAppBar';
export * from './Loader/Loader';
export * from './Placeholder/Placeholder';
export * from './Placeholder/PlaceholderContainer';
export * from './PropertiesTable';
export * from './SanitizedHTML/SanitizedHTML';
export * from './Split';
export * from './Tabs';
export * from './Button';
export * from './ErrorMessage';
export * from './IconButton';
export * from './IconOrImage';
export * from './InputField';
export * from './Radio';
export * from './ShadowInput';
export * from './StaticImage';
export * from './SubmittingForm';
export * from './TextPlaceholder';
export * from './useClipboard';
@@ -0,0 +1,41 @@
/*
* 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 styled, { css } from 'reshadow';
import { Icon } from '../Icons/Icon';
const styles = css`
logo {
height: 100%;
display: flex;
align-items: center;
margin-right: 16px;
width: 250px;
cursor: pointer;
}
Icon {
height: 24px;
width: auto;
margin-bottom: 2px;
}
`;
type Props = {
title: string;
onClick?: () => void;
}
export function AppLogo({ title, onClick }: Props) {
return styled(styles)(
<logo as="div" title={title} onClick={onClick}>
<Icon name="logo" viewBox="0 0 361 73" />
</logo>
);
}
@@ -6,11 +6,11 @@
* you may not use this file except in compliance with the License.
*/
import { css } from 'reshadow';
import styled, { css } from 'reshadow';
import { composes } from '@dbeaver/core/theming';
import { useStyles, composes } from '@dbeaver/core/theming';
export const topNavBarStyles = composes(
export const topAppBarStyles = composes(
css`
header {
composes: theme-background-primary theme-text-on-primary from global;
@@ -27,3 +27,11 @@ export const topNavBarStyles = composes(
}
`
);
export function TopAppBar({ children }: React.PropsWithChildren<{}>) {
return styled(useStyles(topAppBarStyles))(
<header>
{children}
</header>
);
}
+1
View File
@@ -13,6 +13,7 @@ export async function bootstrap(plugins: PluginManifest[]) {
try {
await appBootstrap.init();
await app.initializeServices();
await app.initializePlugins();
await appBootstrap.doAfterPluginsInit();
await app.load();
+13
View File
@@ -6,6 +6,7 @@
* you may not use this file except in compliance with the License.
*/
import { Bootstrap } from './Bootstrap';
import { DIContainer } from './DIContainer';
import { RootContainerService } from './entities/RootContainerService';
import { IServiceCollection, IServiceInjector } from './IApp';
@@ -55,6 +56,18 @@ export class App {
}
}
async initializeServices() {
for (const plugin of this.plugins) {
for (const service of plugin.providers) {
if (service.prototype instanceof Bootstrap) {
const serviceInstance = this.diWrapper.injector.getServiceByClass<Bootstrap>(service);
await serviceInstance.bootstrap();
}
}
}
}
// second phase - run init scripts todo run it based on dependency tree
async initializePlugins() {
for (const plugin of this.plugins) {
+14
View File
@@ -0,0 +1,14 @@
/*
* 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 { injectable } from './injectable';
@injectable()
export abstract class Bootstrap {
abstract bootstrap(): void | Promise<void>;
}
+1
View File
@@ -1,6 +1,7 @@
export * from './IApp';
export * from './App';
export * from './AppContext';
export * from './Bootstrap';
export * from './injectable';
export * from './PluginManifest';
export * from './useService';
@@ -60,11 +60,11 @@ export class ThemeService {
constructor(private notificationService: NotificationService,
private settingsService: SettingsService) {
this.loadAllThemes();
}
async init() {
this.settingsService.registerSettings(this.settings, THEME_SETTINGS_KEY);
await this.loadAllThemesAsync();
await this.changeThemeAsync(this.currentThemeId);
}
@@ -95,7 +95,7 @@ export class ThemeService {
this.settings.currentThemeId = themeId;
}
private async loadAllThemesAsync(): Promise<void> {
private loadAllThemes(): void {
for (const theme of themes) {
this.themeMap.set(theme.id, theme);
}