'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 (
    {content}
    ); } 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 (