'use strict';
import React, {Component} from "react";
import i18n, {withTranslation} from './i18n';
import PropTypes from "prop-types";
import {withRouter} from "react-router";
import {BrowserRouter as Router, Link, Route, Switch} from "react-router-dom";
import {withErrorHandling} from "./error-handling";
import interoperableErrors from "../../../shared/interoperable-errors";
import {ActionLink, Button, DismissibleAlert, DropdownActionLink, Icon} from "./bootstrap-components";
import mailtrainConfig from "mailtrainConfig";
import styles from "./styles.scss";
import {getRoutes, renderRoute, Resolver, SectionContentContext, withPageHelpers} from "./page-common";
import {getBaseDir} from "./urls";
import {createComponentMixin, withComponentMixins} from "./decorator-helpers";
import {getLang} from "../../../shared/langs";
export { withPageHelpers }
class Breadcrumb extends Component {
constructor(props) {
super(props);
}
static propTypes = {
route: PropTypes.object.isRequired,
params: PropTypes.object.isRequired,
resolved: PropTypes.object.isRequired
}
renderElement(entry, isActive) {
const params = this.props.params;
let title;
if (typeof entry.title === 'function') {
title = entry.title(this.props.resolved, params);
} else {
title = entry.title;
}
if (isActive) {
return
{title};
} else if (entry.externalLink) {
let externalLink;
if (typeof entry.externalLink === 'function') {
externalLink = entry.externalLink(params);
} else {
externalLink = entry.externalLink;
}
return {title};
} else if (entry.link) {
let link;
if (typeof entry.link === 'function') {
link = entry.link(params);
} else {
link = entry.link;
}
return {title};
} else {
return {title};
}
}
render() {
const route = this.props.route;
const renderedElems = [...route.parents.map(x => this.renderElement(x)), this.renderElement(route, true)];
return ;
}
}
class TertiaryNavBar extends Component {
static propTypes = {
route: PropTypes.object.isRequired,
params: PropTypes.object.isRequired,
resolved: PropTypes.object.isRequired,
className: PropTypes.string
}
renderElement(key, entry) {
const params = this.props.params;
let title;
if (typeof entry.title === 'function') {
title = entry.title(this.props.resolved);
} else {
title = entry.title;
}
let liClassName = 'nav-item';
let linkClassName = 'nav-link';
if (entry.active) {
linkClassName += ' active';
}
if (entry.link) {
let link;
if (typeof entry.link === 'function') {
link = entry.link(params);
} else {
link = entry.link;
}
return {title};
} else if (entry.externalLink) {
let externalLink;
if (typeof entry.externalLink === 'function') {
externalLink = entry.externalLink(params);
} else {
externalLink = entry.externalLink;
}
return {title};
} else {
return {title};
}
}
render() {
const route = this.props.route;
const keys = Object.keys(route.navs);
const renderedElems = [];
for (const key of keys) {
const entry = route.navs[key];
let visible = true;
if (typeof entry.visible === 'function') {
visible = entry.visible(this.props.resolved);
}
if (visible) {
renderedElems.push(this.renderElement(key, entry));
}
}
if (renderedElems.length > 1) {
let className = styles.tertiaryNav + ' nav nav-pills';
if (this.props.className) {
className += ' ' + this.props.className;
}
return ;
} else {
return null;
}
}
}
function getLoadingMessage(t) {
return (
{t('loading')}
);
}
function renderFrameWithContent(t, panelInFullScreen, showSidebar, primaryMenu, secondaryMenu, content) {
if (panelInFullScreen) {
return (
);
} else {
return (
{showSidebar &&
{secondaryMenu}
}
{content}
);
}
}
@withComponentMixins([
withTranslation
])
class PanelRoute extends Component {
constructor(props) {
super(props);
this.state = {
panelInFullScreen: props.route.panelInFullScreen
};
this.sidebarAnimationNodeListener = evt => {
if (evt.propertyName === 'left') {
this.forceUpdate();
}
};
this.setPanelInFullScreen = panelInFullScreen => this.setState({ panelInFullScreen });
}
static propTypes = {
route: PropTypes.object.isRequired,
location: PropTypes.object.isRequired,
match: PropTypes.object.isRequired,
flashMessage: PropTypes.object
}
registerSidebarAnimationListener() {
if (this.sidebarAnimationNode) {
this.sidebarAnimationNode.addEventListener("transitionend", this.sidebarAnimationNodeListener);
}
}
componentDidMount() {
this.registerSidebarAnimationListener();
}
componentDidUpdate(prevProps) {
this.registerSidebarAnimationListener();
}
render() {
const t = this.props.t;
const route = this.props.route;
const params = this.props.match.params;
const showSidebar = !!route.secondaryMenuComponent;
const panelInFullScreen = this.state.panelInFullScreen;
const render = resolved => {
let primaryMenu = null;
let secondaryMenu = null;
let content = null;
if (resolved) {
const compProps = {
match: this.props.match,
location: this.props.location,
resolved,
setPanelInFullScreen: this.setPanelInFullScreen,
panelInFullScreen: this.state.panelInFullScreen
};
let panel;
if (route.panelComponent) {
panel = React.createElement(route.panelComponent, compProps);
} else if (route.panelRender) {
panel = route.panelRender(compProps);
}
if (route.primaryMenuComponent) {
primaryMenu = React.createElement(route.primaryMenuComponent, compProps);
}
if (route.secondaryMenuComponent) {
secondaryMenu = React.createElement(route.secondaryMenuComponent, compProps);
}
const panelContent = (
{this.props.flashMessage}
{panel}
);
if (panelInFullScreen) {
content = panelContent;
} else {
content = (
<>
{panelContent}
>
);
}
} else {
content = getLoadingMessage(t);
}
return renderFrameWithContent(t, panelInFullScreen, showSidebar, primaryMenu, secondaryMenu, content);
};
return ;
}
}
export class BeforeUnloadListeners {
constructor() {
this.listeners = new Set();
}
register(listener) {
this.listeners.add(listener);
}
deregister(listener) {
this.listeners.delete(listener);
}
shouldUnloadBeCancelled() {
for (const lst of this.listeners) {
if (lst.handler()) return true;
}
return false;
}
async shouldUnloadBeCancelledAsync() {
for (const lst of this.listeners) {
if (await lst.handlerAsync()) return true;
}
return false;
}
}
@withRouter
@withComponentMixins([
withTranslation,
withErrorHandling
], ['onNavigationConfirmationDialog'])
export class SectionContent extends Component {
constructor(props) {
super(props);
this.state = {
flashMessageText: ''
};
this.historyUnlisten = props.history.listen((location, action) => {
// noinspection JSIgnoredPromiseFromCall
this.closeFlashMessage();
});
this.beforeUnloadListeners = new BeforeUnloadListeners();
this.beforeUnloadHandler = ::this.onBeforeUnload;
this.historyUnblock = null;
}
static propTypes = {
structure: PropTypes.object.isRequired,
root: PropTypes.string.isRequired
}
onBeforeUnload(event) {
if (this.beforeUnloadListeners.shouldUnloadBeCancelled()) {
event.preventDefault();
event.returnValue = '';
}
}
onNavigationConfirmationDialog(message, callback) {
this.beforeUnloadListeners.shouldUnloadBeCancelledAsync().then(res => {
if (res) {
const allowTransition = window.confirm(message);
callback(allowTransition);
} else {
callback(true);
}
});
}
componentDidMount() {
window.addEventListener('beforeunload', this.beforeUnloadHandler);
this.historyUnblock = this.props.history.block('Changes you made may not be saved. Are you sure you want to leave this page?');
}
componentWillUnmount() {
window.removeEventListener('beforeunload', this.beforeUnloadHandler);
this.historyUnblock();
}
setFlashMessage(severity, text) {
this.setState({
flashMessageText: text,
flashMessageSeverity: severity
});
}
navigateTo(path) {
this.props.history.push(path);
}
navigateBack() {
this.props.history.goBack();
}
navigateToWithFlashMessage(path, severity, text) {
this.props.history.push(path);
this.setFlashMessage(severity, text);
}
ensureAuthenticated() {
if (!mailtrainConfig.isAuthenticated) {
this.navigateTo('/login?next=' + encodeURIComponent(window.location.pathname));
}
}
registerBeforeUnloadHandlers(handlers) {
this.beforeUnloadListeners.register(handlers);
}
deregisterBeforeUnloadHandlers(handlers) {
this.beforeUnloadListeners.deregister(handlers);
}
errorHandler(error) {
if (error instanceof interoperableErrors.NotLoggedInError) {
if (window.location.pathname !== '/login') { // There may be multiple async requests failing at the same time. So we take the pathname only from the first one.
this.navigateTo('/login?next=' + encodeURIComponent(window.location.pathname));
}
} else if (error.response && error.response.data && error.response.data.message) {
console.error(error);
this.navigateToWithFlashMessage(this.props.root, 'danger', error.response.data.message);
} else {
console.error(error);
this.navigateToWithFlashMessage(this.props.root, 'danger', error.message);
}
return true;
}
async closeFlashMessage() {
this.setState({
flashMessageText: ''
});
}
renderRoute(route) {
const t = this.props.t;
const render = props => {
let flashMessage;
if (this.state.flashMessageText) {
flashMessage = {this.state.flashMessageText};
}
return renderRoute(
route,
PanelRoute,
() => renderFrameWithContent(t,false, false, null, null, getLoadingMessage(this.props.t)),
flashMessage,
props
);
};
return
}
render() {
const routes = getRoutes(this.props.structure);
return (
{routes.map(x => this.renderRoute(x))}
);
}
}
@withComponentMixins([
withTranslation
])
export class Section extends Component {
constructor(props) {
super(props);
this.getUserConfirmationHandler = ::this.onGetUserConfirmation;
this.sectionContent = null;
}
static propTypes = {
structure: PropTypes.oneOfType([PropTypes.object, PropTypes.func]).isRequired,
root: PropTypes.string.isRequired
}
onGetUserConfirmation(message, callback) {
this.sectionContent.onNavigationConfirmationDialog(message, callback);
}
render() {
let structure = this.props.structure;
if (typeof structure === 'function') {
structure = structure(this.props.t);
}
return (
this.sectionContent = node} root={this.props.root} structure={structure} />
);
}
}
export class Title extends Component {
render() {
return (
{this.props.children}
);
}
}
export class Toolbar extends Component {
static propTypes = {
className: PropTypes.string,
};
render() {
let className = styles.toolbar + ' ' + styles.buttonRow;
if (this.props.className) {
className += ' ' + this.props.className;
}
return (
{this.props.children}
);
}
}
export class LinkButton extends Component {
static propTypes = {
label: PropTypes.string,
icon: PropTypes.string,
className: PropTypes.string,
to: PropTypes.string
};
render() {
const props = this.props;
return (