KEYCLOAK-9644: Implement Nav and Headers using PF4 React
This commit is contained in:
parent
81a37d3496
commit
2736dd9d61
16 changed files with 2743 additions and 219 deletions
|
@ -7,7 +7,7 @@
|
|||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
|
||||
<script>
|
||||
var authUrl = '${authUrl}';
|
||||
var baseUrl = '${baseUrl}';
|
||||
|
@ -68,13 +68,6 @@
|
|||
<!-- TODO: We should save these css and js into variables and then load in
|
||||
main.ts for better performance. These might be loaded twice.
|
||||
-->
|
||||
<#if properties.styles?has_content>
|
||||
<#list properties.styles?split(' ') as style>
|
||||
<link href="${resourceUrl}/${style}" rel="stylesheet"/>
|
||||
</#list>
|
||||
<a href="../../../../../../../../keycloak-quickstarts/app-profile-jee-html5/src/main/webapp/index.html"></a>
|
||||
</#if>
|
||||
|
||||
<#if properties.scripts?has_content>
|
||||
<#list properties.scripts?split(' ') as script>
|
||||
<script type="text/javascript" src="${resourceUrl}/${script}"></script>
|
||||
|
@ -113,22 +106,38 @@
|
|||
<div id="main_react_container"></div>
|
||||
|
||||
<div id="welcomeScreen" style="display:none">
|
||||
<div class="pf-c-background-image">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="pf-c-background-image__filter" width="0" height="0">
|
||||
<filter id="image_overlay" width="">
|
||||
<feColorMatrix type="matrix" values="1 0 0 0 0
|
||||
1 0 0 0 0
|
||||
1 0 0 0 0
|
||||
0 0 0 1 0" />
|
||||
<feComponentTransfer color-interpolation-filters="sRGB" result="duotone">
|
||||
<feFuncR type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncR>
|
||||
<feFuncG type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncG>
|
||||
<feFuncB type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncB>
|
||||
<feFuncA type="table" tableValues="0 1"></feFuncA>
|
||||
</feComponentTransfer>
|
||||
</filter>
|
||||
</svg>
|
||||
</div>
|
||||
<#if properties.styles?has_content>
|
||||
<#list properties.styles?split(' ') as style>
|
||||
<link href="${resourceUrl}/${style}" rel="stylesheet"/>
|
||||
</#list>
|
||||
</#if>
|
||||
<style>
|
||||
.pf-c-background-image {
|
||||
--pf-c-background-image--BackgroundImage: url('${resourceUrl}/node_modules/@patternfly/patternfly/assets/images/pfbg_576.jpg');
|
||||
--pf-c-background-image--BackgroundImage-2x: url('${resourceUrl}/node_modules/@patternfly/patternfly/assets/images/pfbg_576@2x.jpg');
|
||||
--pf-c-background-image--BackgroundImage--sm: url('${resourceUrl}/node_modules/@patternfly/patternfly/assets/images/pfbg_768.jpg');
|
||||
--pf-c-background-image--BackgroundImage--sm-2x: url('${resourceUrl}/node_modules/@patternfly/patternfly/assets/images/pfbg_768@2x.jpg');
|
||||
--pf-c-background-image--BackgroundImage--lg: url('${resourceUrl}/node_modules/@patternfly/patternfly/assets/images/pfbg_1200.jpg');
|
||||
--pf-c-background-image--Filter: url('${resourceUrl}/node_modules/@patternfly/patternfly/assets/images/background-filter.svg#image_overlay');
|
||||
}
|
||||
</style>
|
||||
|
||||
<div class="pf-c-background-image">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="pf-c-background-image__filter" width="0" height="0">
|
||||
<filter id="image_overlay" width="">
|
||||
<feColorMatrix type="matrix" values="1 0 0 0 0
|
||||
1 0 0 0 0
|
||||
1 0 0 0 0
|
||||
0 0 0 1 0" />
|
||||
<feComponentTransfer color-interpolation-filters="sRGB" result="duotone">
|
||||
<feFuncR type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncR>
|
||||
<feFuncG type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncG>
|
||||
<feFuncB type="table" tableValues="0.086274509803922 0.43921568627451"></feFuncB>
|
||||
<feFuncA type="table" tableValues="0 1"></feFuncA>
|
||||
</feComponentTransfer>
|
||||
</filter>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="pf-c-page" id="page-layout-default-nav">
|
||||
<header role="banner" class="pf-c-page__header">
|
||||
<div class="pf-c-page__header-brand">
|
||||
|
|
|
@ -15,15 +15,15 @@
|
|||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {Route, Link} from 'react-router-dom';
|
||||
import {Route} from 'react-router-dom';
|
||||
|
||||
import * as moment from 'moment';
|
||||
|
||||
import {KeycloakService} from './keycloak-service/keycloak.service';
|
||||
|
||||
import {Logout} from './widgets/Logout';
|
||||
import {Msg} from './widgets/Msg';
|
||||
import {Referrer} from './page/Referrer';
|
||||
import {PageNav} from './PageNav';
|
||||
import {PageToolbar} from './PageToolbar';
|
||||
import {Background} from './Background';
|
||||
|
||||
import {AccountPage} from './content/account-page/AccountPage';
|
||||
import {PasswordPage} from './content/password-page/PasswordPage';
|
||||
|
@ -34,57 +34,73 @@ import {ApplicationsPage} from './content/applications-page/ApplicationsPage';
|
|||
import {MyResourcesPage} from './content/my-resources-page/MyResourcesPage';
|
||||
import {ExtensionPages} from './content/extensions/ExtensionPages';
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
Brand,
|
||||
Page,
|
||||
PageHeader,
|
||||
PageSection,
|
||||
PageSidebar,
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
declare function toggleReact(): void;
|
||||
declare function isWelcomePage(): boolean;
|
||||
|
||||
declare const locale: string;
|
||||
declare const resourceUrl: string;
|
||||
|
||||
const pFlyImages = resourceUrl + '/node_modules/@patternfly/patternfly/assets/images/';
|
||||
const brandImg = resourceUrl + '/app/assets/img/keycloak-logo-min.png';
|
||||
const avatarImg = pFlyImages + 'img_avatar.svg';
|
||||
|
||||
export interface AppProps {};
|
||||
|
||||
export class App extends React.Component<AppProps> {
|
||||
private kcSvc: KeycloakService = KeycloakService.Instance;
|
||||
|
||||
|
||||
public constructor(props: AppProps) {
|
||||
super(props);
|
||||
console.log('Called into App constructor');
|
||||
toggleReact();
|
||||
}
|
||||
|
||||
|
||||
public render(): React.ReactNode {
|
||||
toggleReact();
|
||||
|
||||
|
||||
// check login
|
||||
if (!this.kcSvc.authenticated() && !isWelcomePage()) {
|
||||
this.kcSvc.login();
|
||||
}
|
||||
|
||||
|
||||
// globally set up locale for date formatting
|
||||
moment.locale(locale);
|
||||
|
||||
|
||||
const Header = (
|
||||
<PageHeader
|
||||
logo={<Brand src={brandImg} alt="Patternfly Logo" />}
|
||||
toolbar={<PageToolbar/>}
|
||||
avatar={<Avatar src={avatarImg} alt="Avatar image" />}
|
||||
showNavToggle
|
||||
/>
|
||||
);
|
||||
|
||||
const Sidebar = <PageSidebar nav={<PageNav/>} />;
|
||||
|
||||
return (
|
||||
<span>
|
||||
<Referrer/>
|
||||
<nav>
|
||||
<Link to="/app/account" className="pf-c-button pf-m-primary" type="button"><Msg msgKey="account"/></Link>
|
||||
<Link to="/app/password" className="pf-c-button pf-m-primary" type="button"><Msg msgKey="password"/></Link>
|
||||
<Link to="/app/authenticator" className="pf-c-button pf-m-primary" type="button"><Msg msgKey="authenticator"/></Link>
|
||||
<Link to="/app/device-activity" className="pf-c-button pf-m-primary" type="button"><Msg msgKey="device-activity"/></Link>
|
||||
<Link to="/app/linked-accounts" className="pf-c-button pf-m-primary" type="button"><Msg msgKey="linkedAccountsHtmlTitle"/></Link>
|
||||
<Link to="/app/applications" className="pf-c-button pf-m-primary" type="button"><Msg msgKey="applications"/></Link>
|
||||
<Link to="/app/my-resources" className="pf-c-button pf-m-primary" type="button"><Msg msgKey="myResources"/></Link>
|
||||
{ExtensionPages.Links}
|
||||
<Logout/>
|
||||
<Route path='/app/account' component={AccountPage}/>
|
||||
<Route path='/app/password' component={PasswordPage}/>
|
||||
<Route path='/app/authenticator' component={AuthenticatorPage}/>
|
||||
<Route path='/app/device-activity' component={DeviceActivityPage}/>
|
||||
<Route path='/app/linked-accounts' component={LinkedAccountsPage}/>
|
||||
<Route path='/app/applications' component={ApplicationsPage}/>
|
||||
<Route path='/app/my-resources' component={MyResourcesPage}/>
|
||||
{ExtensionPages.Routes}
|
||||
</nav>
|
||||
|
||||
</span>
|
||||
<React.Fragment>
|
||||
<Background/>
|
||||
<Page header={Header} sidebar={Sidebar} isManagedSidebar>
|
||||
<PageSection>
|
||||
<Route path='/app/account' component={AccountPage} />
|
||||
<Route path='/app/password' component={PasswordPage} />
|
||||
<Route path='/app/authenticator' component={AuthenticatorPage} />
|
||||
<Route path='/app/device-activity' component={DeviceActivityPage} />
|
||||
<Route path='/app/linked-accounts' component={LinkedAccountsPage} />
|
||||
<Route path='/app/applications' component={ApplicationsPage} />
|
||||
<Route path='/app/my-resources' component={MyResourcesPage} />
|
||||
{ExtensionPages.Routes}
|
||||
</PageSection>
|
||||
</Page>
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2019 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import {BackgroundImageSrc, BackgroundImage} from '@patternfly/react-core';
|
||||
|
||||
declare const resourceUrl: string;
|
||||
|
||||
const pFlyImages = resourceUrl + '/node_modules/@patternfly/patternfly/assets/images/';
|
||||
|
||||
const bgImages = {
|
||||
[BackgroundImageSrc.xs]: pFlyImages + 'pfbg_576.jpg',
|
||||
[BackgroundImageSrc.xs2x]: pFlyImages + 'pfbg_576@2x.jpg',
|
||||
[BackgroundImageSrc.sm]: pFlyImages + 'pfbg_768.jpg',
|
||||
[BackgroundImageSrc.sm2x]: pFlyImages + 'pfbg_768@2x.jpg',
|
||||
[BackgroundImageSrc.lg]: pFlyImages + 'pfbg_1200.jpg',
|
||||
[BackgroundImageSrc.filter]: pFlyImages + 'background-filter.svg#image_overlay'
|
||||
};
|
||||
|
||||
interface BackgroundProps {}
|
||||
export class Background extends React.Component<BackgroundProps> {
|
||||
|
||||
public constructor(props: BackgroundProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
<BackgroundImage src={bgImages} />
|
||||
);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,81 @@
|
|||
/*
|
||||
* Copyright 2019 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import {Nav, NavExpandable, NavList, NavItem} from '@patternfly/react-core';
|
||||
|
||||
import {Msg} from './widgets/Msg';
|
||||
import {ExtensionPages} from './content/extensions/ExtensionPages';
|
||||
|
||||
export interface PageNavProps {
|
||||
}
|
||||
|
||||
interface PageNavState {
|
||||
activeGroup: string | number;
|
||||
activeItem: string | number;
|
||||
}
|
||||
|
||||
export class PageNav extends React.Component<PageNavProps, PageNavState> {
|
||||
|
||||
public constructor(props: PageNavProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
activeGroup: '',
|
||||
activeItem: 'grp-0_itm-0'
|
||||
};
|
||||
}
|
||||
|
||||
private onNavSelect = (groupId: number, itemId: number): void => {
|
||||
this.setState({
|
||||
activeItem: itemId,
|
||||
activeGroup: groupId
|
||||
});
|
||||
};
|
||||
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
<Nav onSelect={this.onNavSelect} aria-label="Nav">
|
||||
<NavList>
|
||||
<NavItem to="#/app/account" itemId="grp-0_itm-0" isActive={this.state.activeItem === 'grp-0_itm-0'}>
|
||||
{Msg.localize("account")}
|
||||
</NavItem>
|
||||
<NavExpandable title="Account Security" groupId="grp-1" isActive={this.state.activeGroup === 'grp-1'}>
|
||||
<NavItem to="#/app/password" groupId="grp-1" itemId="grp-1_itm-1" isActive={this.state.activeItem === 'grp-1_itm-1'}>
|
||||
{Msg.localize("password")}
|
||||
</NavItem>
|
||||
<NavItem to="#/app/authenticator" groupId="grp-1" itemId="grp-1_itm-2" isActive={this.state.activeItem === 'grp-1_itm-2'}>
|
||||
{Msg.localize("authenticator")}
|
||||
</NavItem>
|
||||
<NavItem to="#/app/device-activity" groupId="grp-1" itemId="grp-1_itm-3" isActive={this.state.activeItem === 'grp-1_itm-3'}>
|
||||
{Msg.localize("device-activity")}
|
||||
</NavItem>
|
||||
<NavItem to="#/app/linked-accounts" groupId="grp-1" itemId="grp-1_itm-4" isActive={this.state.activeItem === 'grp-1_itm-4'}>
|
||||
{Msg.localize("linkedAccountsHtmlTitle")}
|
||||
</NavItem>
|
||||
</NavExpandable>
|
||||
<NavItem to="#/app/applications" itemId="grp-2_itm-0" isActive={this.state.activeItem === 'grp-2_itm-0'}>
|
||||
{Msg.localize("applications")}
|
||||
</NavItem>
|
||||
<NavItem to="#/app/my-resources" itemId="grp-3_itm-0" isActive={this.state.activeItem === 'grp-3_itm-0'}>
|
||||
{Msg.localize("myResources")}
|
||||
</NavItem>
|
||||
{ExtensionPages.Links}
|
||||
</NavList>
|
||||
</Nav>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Copyright 2019 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import {Dropdown, KebabToggle, Toolbar, ToolbarGroup, ToolbarItem} from '@patternfly/react-core';
|
||||
|
||||
import {ReferrerDropdownItem} from './widgets/ReferrerDropdownItem';
|
||||
import {ReferrerLink} from './widgets/ReferrerLink';
|
||||
import {Features} from './widgets/features';
|
||||
import {LocaleNav,LocaleDropdown} from './widgets/LocaleSelectors';
|
||||
import {LogoutButton,LogoutDropdownItem} from './widgets/Logout';
|
||||
|
||||
declare const referrerName: string;
|
||||
declare const features: Features;
|
||||
|
||||
interface PageToolbarProps {}
|
||||
interface PageToolbarState {isKebabDropdownOpen: boolean}
|
||||
export class PageToolbar extends React.Component<PageToolbarProps, PageToolbarState> {
|
||||
private hasReferrer: boolean = typeof referrerName !== 'undefined';
|
||||
|
||||
public constructor(props: PageToolbarProps) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
isKebabDropdownOpen: false,
|
||||
};
|
||||
}
|
||||
|
||||
private onKebabDropdownToggle = (isKebabDropdownOpen: boolean) => {
|
||||
this.setState({
|
||||
isKebabDropdownOpen
|
||||
});
|
||||
};
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const kebabDropdownItems = [];
|
||||
if (this.hasReferrer) {
|
||||
kebabDropdownItems.push(
|
||||
<ReferrerDropdownItem key='referrerDropdownItem'/>
|
||||
)
|
||||
}
|
||||
|
||||
if (features.isInternationalizationEnabled) {
|
||||
kebabDropdownItems.push(<LocaleNav key='kebabLocaleNav'/>);
|
||||
}
|
||||
|
||||
kebabDropdownItems.push(<LogoutDropdownItem key='LogoutDropdownItem'/>);
|
||||
|
||||
return (
|
||||
<Toolbar>
|
||||
{this.hasReferrer &&
|
||||
<ToolbarGroup key='referrerGroup'>
|
||||
<ToolbarItem className="pf-m-icons pf-screen-reader" key='referrer'>
|
||||
<ReferrerLink/>
|
||||
</ToolbarItem>
|
||||
</ToolbarGroup>
|
||||
}
|
||||
|
||||
<ToolbarGroup key='secondGroup'>
|
||||
{features.isInternationalizationEnabled &&
|
||||
<ToolbarItem className="pf-m-icons pf-screen-reader" key='locale'>
|
||||
<LocaleDropdown/>
|
||||
</ToolbarItem>
|
||||
}
|
||||
|
||||
<ToolbarItem className="pf-m-icons pf-screen-reader" key='logout'>
|
||||
<LogoutButton/>
|
||||
</ToolbarItem>
|
||||
|
||||
<ToolbarItem key='kebab' className="pf-m-mobile">
|
||||
<Dropdown
|
||||
isPlain
|
||||
position="right"
|
||||
toggle={<KebabToggle onToggle={this.onKebabDropdownToggle} />}
|
||||
isOpen={this.state.isKebabDropdownOpen}
|
||||
dropdownItems={kebabDropdownItems}
|
||||
/>
|
||||
</ToolbarItem>
|
||||
</ToolbarGroup>
|
||||
</Toolbar>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -18,7 +18,7 @@ import * as React from 'react';
|
|||
import {AxiosResponse} from 'axios';
|
||||
|
||||
import {AccountServiceClient} from '../../account-service/account.service';
|
||||
import {Features} from '../../page/features';
|
||||
import {Features} from '../../widgets/features';
|
||||
import {Msg} from '../../widgets/Msg';
|
||||
|
||||
declare const features: Features;
|
||||
|
|
|
@ -15,7 +15,8 @@
|
|||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {Route, Link} from 'react-router-dom';
|
||||
import {Route} from 'react-router-dom';
|
||||
import {NavItem} from '@patternfly/react-core';
|
||||
|
||||
export interface PageDef {
|
||||
path: string;
|
||||
|
@ -30,10 +31,10 @@ export class ExtensionPages { // extends React.Component<ExtensionPagesProps> {
|
|||
public static get Links(): React.ReactNode {
|
||||
if (typeof extensionPages === 'undefined') return (<span/>);
|
||||
|
||||
const links: React.ReactElement<Link>[] = extensionPages.map((page: PageDef) =>
|
||||
<Link key={page.path} to={'/app/' + page.path} className="btn btn-primary btn-lg btn-sign" type="button">{page.label}</Link>
|
||||
const links: React.ReactElement[] = extensionPages.map((page: PageDef, index: number) =>
|
||||
<NavItem key={page.path} to={'#/app/' + page.path} itemId={'ext-' + index} type="button">{page.label}</NavItem>
|
||||
);
|
||||
return (<span>{links}</span>);
|
||||
return (<React.Fragment>{links}</React.Fragment>);
|
||||
}
|
||||
|
||||
public static get Routes(): React.ReactNode {
|
||||
|
@ -42,7 +43,7 @@ export class ExtensionPages { // extends React.Component<ExtensionPagesProps> {
|
|||
const routes: React.ReactElement<Route>[] = extensionPages.map((page) =>
|
||||
<Route key={page.path} path={'/app/' + page.path} component={page.component}/>
|
||||
);
|
||||
return (<span>{routes}</span>);
|
||||
return (<React.Fragment>{routes}</React.Fragment>);
|
||||
}
|
||||
|
||||
};
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright 2019 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {withRouter, RouteComponentProps} from 'react-router-dom';
|
||||
|
||||
import {Msg} from './Msg';
|
||||
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
DropdownToggle,
|
||||
Nav,
|
||||
NavExpandable,
|
||||
NavItem,
|
||||
NavList
|
||||
} from '@patternfly/react-core';
|
||||
|
||||
declare const locale: string;
|
||||
declare const baseUrl: string;
|
||||
declare const referrer: string;
|
||||
declare const referrerUri: string
|
||||
|
||||
interface AvailableLocale {
|
||||
locale: string;
|
||||
label: string;
|
||||
};
|
||||
declare const availableLocales: [AvailableLocale];
|
||||
// remove entry for current locale
|
||||
const availLocales = availableLocales.filter((availableLocale: AvailableLocale) => availableLocale.locale !== locale);
|
||||
|
||||
let referrerFragment = '';
|
||||
if ((typeof referrer !== 'undefined') &&
|
||||
(typeof referrerUri !== 'undefined')) {
|
||||
referrerFragment = '&referrer=' + referrer + '&referrer_uri=' + encodeURIComponent(referrerUri);
|
||||
}
|
||||
|
||||
interface LocaleKebabItemProps extends RouteComponentProps {}
|
||||
interface LocaleKebabItemState {activeGroup: string; activeItem: string}
|
||||
class LocaleKebabItem extends React.Component<LocaleKebabItemProps, LocaleKebabItemState> {
|
||||
public constructor(props: LocaleKebabItemProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
activeGroup: 'locale-group',
|
||||
activeItem: ''
|
||||
};
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const appPath = this.props.location.pathname;
|
||||
const localeNavItems = availLocales.map((availableLocale: AvailableLocale) => {
|
||||
const url = baseUrl + '?kc_locale=' + availableLocale.locale + referrerFragment + '#' + appPath;
|
||||
return (<NavItem key={availableLocale.locale} to={url}>
|
||||
{availableLocale.label}
|
||||
</NavItem> );
|
||||
});
|
||||
|
||||
return (
|
||||
<Nav>
|
||||
<NavList>
|
||||
<NavExpandable title={Msg.localize('locale_' + locale)} isActive={false} groupId="locale-group">
|
||||
{localeNavItems}
|
||||
</NavExpandable>
|
||||
</NavList>
|
||||
</Nav>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
interface LocaleDropdownComponentProps extends RouteComponentProps {}
|
||||
interface LocaleDropdownComponentState {isDropdownOpen: boolean}
|
||||
class LocaleDropdownComponent extends React.Component<LocaleDropdownComponentProps, LocaleDropdownComponentState> {
|
||||
public constructor(props: LocaleDropdownComponentProps) {
|
||||
super(props);
|
||||
this.state = {isDropdownOpen: false};
|
||||
}
|
||||
|
||||
private onDropdownToggle = (isDropdownOpen: boolean) => {
|
||||
this.setState({
|
||||
isDropdownOpen
|
||||
});
|
||||
};
|
||||
|
||||
private onDropdownSelect = () => {
|
||||
this.setState({
|
||||
isDropdownOpen: !this.state.isDropdownOpen
|
||||
});
|
||||
};
|
||||
|
||||
public render(): React.ReactNode {
|
||||
const appPath = this.props.location.pathname;
|
||||
const localeDropdownItems = availLocales.map((availableLocale: AvailableLocale) => {
|
||||
const url = baseUrl + '?kc_locale=' + availableLocale.locale + referrerFragment + '#' + appPath;
|
||||
return (<DropdownItem key={availableLocale.locale} href={url}>
|
||||
{availableLocale.label}
|
||||
</DropdownItem> );
|
||||
});
|
||||
|
||||
return (
|
||||
<Dropdown
|
||||
isPlain
|
||||
position="right"
|
||||
onSelect={this.onDropdownSelect}
|
||||
isOpen={this.state.isDropdownOpen}
|
||||
toggle={<DropdownToggle onToggle={this.onDropdownToggle}><Msg msgKey={'locale_' + locale}/></DropdownToggle>}
|
||||
dropdownItems={localeDropdownItems}
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export const LocaleDropdown = withRouter(LocaleDropdownComponent);
|
||||
export const LocaleNav = withRouter(LocaleKebabItem);
|
|
@ -15,29 +15,34 @@
|
|||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {Link} from 'react-router-dom';
|
||||
|
||||
import {Msg} from './Msg';
|
||||
import {KeycloakService} from '../keycloak-service/keycloak.service';
|
||||
|
||||
import {Button, DropdownItem} from '@patternfly/react-core';
|
||||
|
||||
declare const baseUrl: string;
|
||||
|
||||
export interface LogoutProps {
|
||||
function handleLogout(): void {
|
||||
KeycloakService.Instance.logout(baseUrl);
|
||||
}
|
||||
|
||||
export class Logout extends React.Component<LogoutProps> {
|
||||
|
||||
public constructor(props: LogoutProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
private handleLogout(): void {
|
||||
KeycloakService.Instance.logout(baseUrl);
|
||||
}
|
||||
|
||||
interface LogoutProps {}
|
||||
export class LogoutButton extends React.Component<LogoutProps> {
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
<Link to="/" className="pf-c-button pf-m-primary" type="button" onClick={this.handleLogout}><Msg msgKey="doSignOut"/></Link>
|
||||
<Button onClick={handleLogout}><Msg msgKey="doSignOut"/></Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface LogoutDropdownItemProps {}
|
||||
export class LogoutDropdownItem extends React.Component<LogoutDropdownItemProps> {
|
||||
public render(): React.ReactNode {
|
||||
return (
|
||||
<DropdownItem key="logout" onClick={handleLogout}>
|
||||
{Msg.localize('doSignOut')}
|
||||
</DropdownItem>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -30,23 +30,27 @@ export class Msg extends React.Component<MsgProps> {
|
|||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
let message: string = l18nMsg[this.props.msgKey];
|
||||
if (message === undefined) message = this.props.msgKey;
|
||||
return (
|
||||
<React.Fragment>{Msg.localize(this.props.msgKey, this.props.params)}</React.Fragment>
|
||||
);
|
||||
}
|
||||
|
||||
public static localize(msgKey: string, params?: string[]): string {
|
||||
let message: string = l18nMsg[msgKey];
|
||||
if (message === undefined) message = msgKey;
|
||||
|
||||
if (this.props.params !== undefined) {
|
||||
this.props.params.forEach((value: string, index: number) => {
|
||||
if (params !== undefined) {
|
||||
params.forEach((value: string, index: number) => {
|
||||
value = this.processParam(value);
|
||||
message = message.replace('{{param_'+ index + '}}', value);
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<span>{message}</span>
|
||||
);
|
||||
return message;
|
||||
}
|
||||
|
||||
// if the param has Freemarker syntax, try to look up its value
|
||||
private processParam(param: string): string {
|
||||
private static processParam(param: string): string {
|
||||
if (!(param.startsWith('${') && param.endsWith('}'))) return param;
|
||||
|
||||
// remove Freemarker syntax
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright 2018 Red Hat, Inc. and/or its affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
import {Msg} from '../widgets/Msg';
|
||||
|
||||
import {DropdownItem} from '@patternfly/react-core';
|
||||
import {ArrowIcon} from '@patternfly/react-icons';
|
||||
|
||||
declare const referrerName: string;
|
||||
declare const referrerUri: string;
|
||||
|
||||
export interface ReferrerDropdownItemProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2018 Red Hat Inc.
|
||||
*/
|
||||
export class ReferrerDropdownItem extends React.Component<ReferrerDropdownItemProps> {
|
||||
|
||||
public constructor(props: ReferrerDropdownItemProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
|
||||
return (
|
||||
<DropdownItem href={referrerUri}>
|
||||
<ArrowIcon /> {Msg.localize('backTo', [referrerName])}
|
||||
</DropdownItem>
|
||||
);
|
||||
}
|
||||
};
|
|
@ -17,29 +17,28 @@
|
|||
import * as React from 'react';
|
||||
|
||||
import {Msg} from '../widgets/Msg';
|
||||
|
||||
import {ArrowIcon} from '@patternfly/react-icons';
|
||||
|
||||
declare const referrerName: string;
|
||||
declare const referrerUri: string;
|
||||
|
||||
export interface ReferrerProps {
|
||||
export interface ReferrerLinkProps {
|
||||
}
|
||||
|
||||
/**
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2018 Red Hat Inc.
|
||||
*/
|
||||
export class Referrer extends React.Component<ReferrerProps> {
|
||||
export class ReferrerLink extends React.Component<ReferrerLinkProps> {
|
||||
|
||||
public constructor(props: ReferrerProps) {
|
||||
public constructor(props: ReferrerLinkProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
public render(): React.ReactNode {
|
||||
if (typeof referrerName === "undefined") return null;
|
||||
|
||||
return (
|
||||
<a className="nav-item-iconic" href={referrerUri}>
|
||||
<span className="pficon-arrow"></span>
|
||||
<Msg msgKey="backTo" params={[referrerName]}/>
|
||||
<a href={referrerUri}>
|
||||
<ArrowIcon/> <Msg msgKey="backTo" params={[referrerName]}/>
|
||||
</a>
|
||||
);
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -11,18 +11,19 @@
|
|||
"author": "Stan Silvert",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@patternfly/patternfly": "^1.0.227",
|
||||
"@patternfly/patternfly": "^1.0.248",
|
||||
"@patternfly/react-core": "^2.9.2",
|
||||
"axios": "^0.18.0",
|
||||
"moment": "^2.22.2",
|
||||
"react": "^16.5.2",
|
||||
"react-dom": "^16.5.2",
|
||||
"react": "^16.8.5",
|
||||
"react-dom": "^16.8.5",
|
||||
"react-router-dom": "^4.3.1",
|
||||
"systemjs": "^0.20.17",
|
||||
"systemjs-plugin-babel": "0.0.25"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^16.4.14",
|
||||
"@types/react-dom": "^16.0.8",
|
||||
"@types/react": "^16.8.8",
|
||||
"@types/react-dom": "^16.8.3",
|
||||
"@types/react-router-dom": "^4.3.1",
|
||||
"@typescript-eslint/eslint-plugin": "^1.4.2",
|
||||
"@typescript-eslint/parser": "^1.4.2",
|
||||
|
|
|
@ -18,13 +18,29 @@
|
|||
'react-dom': 'npm:react-dom/umd/react-dom.development.js',
|
||||
'react-router-dom': 'npm:react-router-dom/umd/react-router-dom.js',
|
||||
|
||||
'@patternfly': 'npm:@patternfly',
|
||||
'react-styles': "react-styles/loader.js",
|
||||
//'patternfly-react-core-button': 'patternflyreactcore:js/components/Button/index.js',
|
||||
//'@patternfly/patternfly': 'npm:@patternfly/patternfly',
|
||||
'@patternfly/patternfly': 'npm:@patternfly/react-core/dist/umd/@patternfly/patternfly',
|
||||
'@patternfly/react-core': 'npm:@patternfly/react-core/dist/umd/index.js',
|
||||
'@patternfly/react-styles': 'npm:@patternfly/react-styles/dist/umd/index.js',
|
||||
'@patternfly/react-icons': 'npm:@patternfly/react-icons/dist/umd/index.js',
|
||||
'@patternfly/react-tokens': 'npm:@patternfly/react-tokens/dist/umd/index.js',
|
||||
'emotion': 'npm:emotion/dist/emotion.umd.min.js',
|
||||
'camel-case': 'npm:camel-case/camel-case.js',
|
||||
'upper-case': 'npm:upper-case/upper-case.js',
|
||||
'no-case': 'npm:no-case/no-case.js',
|
||||
'lower-case': 'npm:lower-case/lower-case.js',
|
||||
'prop-types': 'npm:prop-types/prop-types.min.js',
|
||||
'exenv': 'npm:exenv/index.js',
|
||||
'focus-trap': 'npm:focus-trap/dist/focus-trap.min.js',
|
||||
'focus-trap-react': 'npm:focus-trap-react/dist/focus-trap-react.js',
|
||||
'@tippy.js/react': 'npm:@tippy.js/react/dist/Tippy.min.js',
|
||||
'tippy.js': 'npm:tippy.js/dist/tippy.min.js',
|
||||
|
||||
'moment': 'npm:moment/min/moment-with-locales.min.js',
|
||||
|
||||
'axios': 'npm:axios/dist/axios.min.js',
|
||||
|
||||
'history': 'npm:history/umd/history.min.js',
|
||||
},
|
||||
|
||||
bundles: {
|
||||
|
@ -50,11 +66,99 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
|
||||
rxjs: {
|
||||
defaultExtension: false
|
||||
},
|
||||
'npm:@patternfly/react-core/dist/umd/components': {
|
||||
main: './index.js',
|
||||
defaultExtension: 'js',
|
||||
map: {
|
||||
'./Alert': './Alert/index.js',
|
||||
'./AboutModal': './AboutModal/index.js',
|
||||
'./ApplicationLauncher': './ApplicationLauncher/index.js',
|
||||
'./Avatar': './Avatar/index.js',
|
||||
'./Backdrop': './Backdrop/index.js',
|
||||
'./BackgroundImage': './BackgroundImage/index.js',
|
||||
'./Badge': './Badge/index.js',
|
||||
'./Brand': './Brand/index.js',
|
||||
'./Breadcrumb': './Breadcrumb/index.js',
|
||||
'./Button': './Button/index.js',
|
||||
'./Card': './Card/index.js',
|
||||
'./Checkbox': './Checkbox/index.js',
|
||||
'./ChipGroup': './ChipGroup/index.js',
|
||||
'./ContextSelector': './ContextSelector/index.js',
|
||||
'./DataList': './DataList/index.js',
|
||||
'./Dropdown': './Dropdown/index.js',
|
||||
'./EmptyState': './EmptyState/index.js',
|
||||
'./Form': './Form/index.js',
|
||||
'./FormSelect': './FormSelect/index.js',
|
||||
'./InputGroup': './InputGroup/index.js',
|
||||
'./Label': './Label/index.js',
|
||||
'./List': './List/index.js',
|
||||
'./LoginPage': './LoginPage/index.js',
|
||||
'./Modal': './Modal/index.js',
|
||||
'./Nav': './Nav/index.js',
|
||||
'./Page': './Page/index.js',
|
||||
'./Popover': './Popover/index.js',
|
||||
'./Progress': './Progress/index.js',
|
||||
'./Pagination': './Pagination/index.js',
|
||||
'./Radio': './Radio/index.js',
|
||||
'./Select': './Select/index.js',
|
||||
'./Switch': './Switch/index.js',
|
||||
'./Tabs': './Tabs/index.js',
|
||||
'./Text': './Text/index.js',
|
||||
'./TextArea': './TextArea/index.js',
|
||||
'./TextInput': './TextInput/index.js',
|
||||
'./Title': './Title/index.js',
|
||||
'./Tooltip': './Tooltip/index.js',
|
||||
'./Wizard': './Wizard/index.js',
|
||||
'./Bullseye': './Bullseye/index.js',
|
||||
'./Gallery': './Gallery/index.js',
|
||||
'./Grid': './Grid/index.js',
|
||||
'./Level': './Level/index.js',
|
||||
'./Split': './Split/index.js',
|
||||
'./Stack': './Stack/index.js',
|
||||
'./Toolbar': './Toolbar/index.js',
|
||||
}
|
||||
},
|
||||
|
||||
'npm:@patternfly/react-core/dist/umd/styles': {
|
||||
main: './index.js',
|
||||
defaultExtension: 'js',
|
||||
},
|
||||
'npm:@patternfly/react-core/dist/umd/helpers': {
|
||||
main: './index.js',
|
||||
defaultExtension: 'js',
|
||||
},
|
||||
'npm:@patternfly/react-core/dist/umd/layouts': {
|
||||
main: './index.js',
|
||||
defaultExtension: 'js',
|
||||
map: {
|
||||
'./Bullseye': './Bullseye/index.js',
|
||||
'./Gallery': './Gallery/index.js',
|
||||
'./Level': './Level/index.js',
|
||||
'./Grid': './Grid/index.js',
|
||||
'./Stack': './Stack/index.js',
|
||||
'./Split': './Split/index.js',
|
||||
'./Toolbar': './Toolbar/index.js',
|
||||
}
|
||||
},
|
||||
'npm:@patternfly/react-core/dist/umd/@patternfly/patternfly/utilities/Accessibility': {
|
||||
defaultExtension: 'js',
|
||||
map: {
|
||||
'./accessibility.css': './accessibility.css.js'
|
||||
}
|
||||
},
|
||||
'npm:@patternfly/react-icons/dist/umd': {
|
||||
main: './index.js',
|
||||
defaultExtension: 'js',
|
||||
},
|
||||
'npm:@patternfly/react-styles/dist/umd': {
|
||||
defaultExtension: 'js',
|
||||
},
|
||||
'npm:no-case/vendor': {
|
||||
defaultExtension: 'js',
|
||||
},
|
||||
}
|
||||
});
|
||||
})(this);
|
||||
|
|
Loading…
Reference in a new issue