Blacklist functionality
Some API improvements
This commit is contained in:
parent
c343e4efd3
commit
9203b5cee7
40 changed files with 726 additions and 398 deletions
8
client/src/lib/bootstrap-components.js
vendored
8
client/src/lib/bootstrap-components.js
vendored
|
|
@ -35,14 +35,19 @@ class DismissibleAlert extends Component {
|
|||
class Icon extends Component {
|
||||
static propTypes = {
|
||||
icon: PropTypes.string.isRequired,
|
||||
family: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
className: PropTypes.string
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
family: 'glyphicon'
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
|
||||
return <span className={'glyphicon glyphicon-' + props.icon + (props.className ? ' ' + props.className : '')} title={props.title}></span>;
|
||||
return <span className={`${props.family} ${props.family}-${props.icon}` + (props.className ? ' ' + props.className : '')} title={props.title}></span>;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -129,6 +134,7 @@ class ActionLink extends Component {
|
|||
async onClick(evt) {
|
||||
if (this.props.onClickAsync) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
await this.props.onClickAsync(evt);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,13 +10,10 @@ import { withPageHelpers } from './page'
|
|||
import { withErrorHandling, withAsyncErrorHandler } from './error-handling';
|
||||
import { TreeTable, TreeSelectMode } from './tree';
|
||||
import { Table, TableSelectMode } from './table';
|
||||
import { Button } from "./bootstrap-components";
|
||||
import {Button, Icon} from "./bootstrap-components";
|
||||
|
||||
import brace from 'brace';
|
||||
import AceEditor from 'react-ace';
|
||||
import 'brace/mode/javascript';
|
||||
import 'brace/mode/json';
|
||||
import 'brace/mode/handlebars';
|
||||
import 'brace/theme/github';
|
||||
|
||||
import DayPicker from 'react-day-picker';
|
||||
|
|
@ -42,7 +39,8 @@ class Form extends Component {
|
|||
static propTypes = {
|
||||
stateOwner: PropTypes.object.isRequired,
|
||||
onSubmitAsync: PropTypes.func,
|
||||
format: PropTypes.string
|
||||
format: PropTypes.string,
|
||||
noStatus: PropTypes.bool
|
||||
}
|
||||
|
||||
static childContextTypes = {
|
||||
|
|
@ -60,7 +58,7 @@ class Form extends Component {
|
|||
const t = this.props.t;
|
||||
|
||||
const owner = this.props.stateOwner;
|
||||
|
||||
|
||||
evt.preventDefault();
|
||||
|
||||
if (this.props.onSubmitAsync) {
|
||||
|
|
@ -94,10 +92,10 @@ class Form extends Component {
|
|||
<fieldset disabled={owner.isFormDisabled()}>
|
||||
{props.children}
|
||||
</fieldset>
|
||||
{statusMessageText &&
|
||||
<AlignedRow htmlId="form-status-message">
|
||||
<p className={`alert alert-${statusMessageSeverity} ${styles.formStatus}`} role="alert">{statusMessageText}</p>
|
||||
</AlignedRow>
|
||||
{!props.noStatus && statusMessageText &&
|
||||
<AlignedRow htmlId="form-status-message">
|
||||
<p className={`alert alert-${statusMessageSeverity} ${styles.formStatus}`} role="alert">{statusMessageText}</p>
|
||||
</AlignedRow>
|
||||
}
|
||||
</form>
|
||||
);
|
||||
|
|
@ -107,18 +105,46 @@ class Form extends Component {
|
|||
|
||||
class Fieldset extends Component {
|
||||
static propTypes = {
|
||||
label: PropTypes.string
|
||||
id: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
}
|
||||
|
||||
static contextTypes = {
|
||||
formStateOwner: PropTypes.object.isRequired
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
const owner = this.context.formStateOwner;
|
||||
const id = this.props.id;
|
||||
const htmlId = 'form_' + id;
|
||||
|
||||
const className = id ? owner.addFormValidationClass('', id) : '';
|
||||
|
||||
let helpBlock = null;
|
||||
if (this.props.help) {
|
||||
helpBlock = <div className="help-block" id={htmlId + '_help'}>{this.props.help}</div>;
|
||||
}
|
||||
|
||||
let validationBlock = null;
|
||||
if (id) {
|
||||
const validationMsg = id && owner.getFormValidationMessage(id);
|
||||
if (validationMsg) {
|
||||
validationBlock = <div className="help-block" id={htmlId + '_help_validation'}>{validationMsg}</div>;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<fieldset>
|
||||
<fieldset className={className}>
|
||||
{props.label ? <legend>{props.label}</legend> : null}
|
||||
{props.children}
|
||||
<div className="fieldset-content">
|
||||
{props.children}
|
||||
{helpBlock}
|
||||
{validationBlock}
|
||||
</div>
|
||||
</fieldset>
|
||||
);
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -411,6 +437,7 @@ class TextArea extends Component {
|
|||
}
|
||||
|
||||
|
||||
@translate()
|
||||
class DatePicker extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
|
@ -426,7 +453,9 @@ class DatePicker extends Component {
|
|||
help: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
format: PropTypes.string,
|
||||
birthday: PropTypes.bool,
|
||||
dateFormat: PropTypes.string
|
||||
dateFormat: PropTypes.string,
|
||||
formatDate: PropTypes.func,
|
||||
parseDate: PropTypes.func
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
|
|
@ -447,7 +476,12 @@ class DatePicker extends Component {
|
|||
const owner = this.context.formStateOwner;
|
||||
const id = this.props.id;
|
||||
const props = this.props;
|
||||
owner.updateFormValue(id, props.birthday ? formatBirthday(props.dateFormat, date) : formatDate(props.dateFormat, date));
|
||||
|
||||
if (props.formatDate) {
|
||||
owner.updateFormValue(id, props.formatDate(date));
|
||||
} else {
|
||||
owner.updateFormValue(id, props.birthday ? formatBirthday(props.dateFormat, date) : formatDate(props.dateFormat, date));
|
||||
}
|
||||
|
||||
this.setState({
|
||||
opened: false
|
||||
|
|
@ -459,6 +493,7 @@ class DatePicker extends Component {
|
|||
const owner = this.context.formStateOwner;
|
||||
const id = this.props.id;
|
||||
const htmlId = 'form_' + id;
|
||||
const t = props.t;
|
||||
|
||||
function BirthdayPickerCaption({ date, localeUtils, onChange }) {
|
||||
const months = localeUtils.getMonths();
|
||||
|
|
@ -472,7 +507,15 @@ class DatePicker extends Component {
|
|||
let selectedDate, captionElement, fromMonth, toMonth, placeholder;
|
||||
const selectedDateStr = owner.getFormValue(id) || '';
|
||||
if (props.birthday) {
|
||||
selectedDate = parseBirthday(props.dateFormat, selectedDateStr);
|
||||
if (props.parseDate) {
|
||||
selectedDate = props.parseDate(selectedDateStr);
|
||||
if (selectedDate) {
|
||||
selectedDate = moment(selectedDate).set('year', birthdayYear).toDate();
|
||||
}
|
||||
} else {
|
||||
selectedDate = parseBirthday(props.dateFormat, selectedDateStr);
|
||||
}
|
||||
|
||||
if (!selectedDate) {
|
||||
selectedDate = moment().set('year', birthdayYear).toDate();
|
||||
}
|
||||
|
|
@ -483,7 +526,12 @@ class DatePicker extends Component {
|
|||
placeholder = getBirthdayFormatString(props.dateFormat);
|
||||
|
||||
} else {
|
||||
selectedDate = parseDate(props.dateFormat, selectedDateStr);
|
||||
if (props.parseDate) {
|
||||
selectedDate = props.parseDate(selectedDateStr);
|
||||
} else {
|
||||
selectedDate = parseDate(props.dateFormat, selectedDateStr);
|
||||
}
|
||||
|
||||
if (!selectedDate) {
|
||||
selectedDate = moment().toDate();
|
||||
}
|
||||
|
|
@ -495,19 +543,19 @@ class DatePicker extends Component {
|
|||
<div>
|
||||
<div className="input-group">
|
||||
<input type="text" value={selectedDateStr} placeholder={placeholder} id={htmlId} className="form-control" aria-describedby={htmlId + '_help'} onChange={evt => owner.updateFormValue(id, evt.target.value)}/>
|
||||
<span className="input-group-addon" onClick={::this.toggleDayPicker}><span className="glyphicon glyphicon-th"></span></span>
|
||||
<span className="input-group-addon" onClick={::this.toggleDayPicker}><Icon icon="calendar" title={t('Open calendar')}/></span>
|
||||
</div>
|
||||
{this.state.opened &&
|
||||
<div className={styles.dayPickerWrapper}>
|
||||
<DayPicker
|
||||
onDayClick={date => this.daySelected(date)}
|
||||
selectedDays={selectedDate}
|
||||
initialMonth={selectedDate}
|
||||
fromMonth={fromMonth}
|
||||
toMonth={toMonth}
|
||||
captionElement={captionElement}
|
||||
/>
|
||||
</div>
|
||||
<div className={styles.dayPickerWrapper}>
|
||||
<DayPicker
|
||||
onDayClick={date => this.daySelected(date)}
|
||||
selectedDays={selectedDate}
|
||||
initialMonth={selectedDate}
|
||||
fromMonth={fromMonth}
|
||||
toMonth={toMonth}
|
||||
captionElement={captionElement}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
);
|
||||
|
|
@ -729,7 +777,7 @@ class TableSelect extends Component {
|
|||
</span>
|
||||
</div>
|
||||
<div className={styles.tableSelectTable + (this.state.open ? '' : ' ' + styles.tableSelectTableHidden)}>
|
||||
<Table ref={node => this.table = node} data={props.data} dataUrl={props.dataUrl} columns={props.columns} selectMode={props.selectMode} selectionAsArray={this.props.selectionAsArray} withHeader={props.withHeader} selection={owner.getFormValue(id)} onSelectionDataAsync={::this.onSelectionDataAsync} onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
|
||||
<Table ref={node => this.table = node} data={props.data} dataUrl={props.dataUrl} columns={props.columns} selectMode={props.selectMode} selectionAsArray={this.props.selectionAsArray} withHeader={props.withHeader} selectionKeyIndex={props.selectionKeyIndex} selection={owner.getFormValue(id)} onSelectionDataAsync={::this.onSelectionDataAsync} onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -737,7 +785,7 @@ class TableSelect extends Component {
|
|||
return wrapInput(id, htmlId, owner, props.format, '', props.label, props.help,
|
||||
<div>
|
||||
<div>
|
||||
<Table ref={node => this.table = node} data={props.data} dataUrl={props.dataUrl} columns={props.columns} selectMode={props.selectMode} selectionAsArray={this.props.selectionAsArray} withHeader={props.withHeader} selection={owner.getFormValue(id)} onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
|
||||
<Table ref={node => this.table = node} data={props.data} dataUrl={props.dataUrl} columns={props.columns} selectMode={props.selectMode} selectionAsArray={this.props.selectionAsArray} withHeader={props.withHeader} selectionKeyIndex={props.selectionKeyIndex} selection={owner.getFormValue(id)} onSelectionChangedAsync={::this.onSelectionChangedAsync}/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
|
@ -938,6 +986,7 @@ function withForm(target) {
|
|||
delete data.hash;
|
||||
|
||||
if (mutator) {
|
||||
// FIXME - change the interface such that if the mutator is provided, it is supposed to return which fields to keep in the form
|
||||
mutator(data);
|
||||
}
|
||||
|
||||
|
|
@ -951,6 +1000,7 @@ function withForm(target) {
|
|||
const data = this.getFormValues();
|
||||
|
||||
if (mutator) {
|
||||
// FIXME - change the interface such that the mutator is supposed to create the object to be submitted
|
||||
mutator(data);
|
||||
}
|
||||
|
||||
|
|
@ -1152,6 +1202,17 @@ function withForm(target) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (error instanceof interoperableErrors.NamespaceNotFoundError) {
|
||||
this.disableForm();
|
||||
this.setFormStatusMessage('danger',
|
||||
<span>
|
||||
<strong>{t('Your updates cannot be saved.')}</strong>{' '}
|
||||
{t('It seems that someone else has deleted the target namespace in the meantime. Refresh your page to start anew with fresh data. Please note that your changes will be lost.')}
|
||||
</span>
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (error instanceof interoperableErrors.NotFoundError) {
|
||||
this.disableForm();
|
||||
this.setFormStatusMessage('danger',
|
||||
|
|
|
|||
156
client/src/lib/page-common.js
Normal file
156
client/src/lib/page-common.js
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
'use strict';
|
||||
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import {withRouter} from "react-router";
|
||||
import {withErrorHandling} from "./error-handling";
|
||||
import axios from "../lib/axios";
|
||||
|
||||
function needsResolve(route, nextRoute, match, nextMatch) {
|
||||
const resolve = route.resolve;
|
||||
const nextResolve = nextRoute.resolve;
|
||||
|
||||
if (Object.keys(resolve).length === Object.keys(nextResolve).length) {
|
||||
for (const key in resolve) {
|
||||
if (!(key in nextResolve) ||
|
||||
resolve[key](match.params) !== nextResolve[key](nextMatch.params)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async function resolve(route, match) {
|
||||
const keys = Object.keys(route.resolve);
|
||||
|
||||
const promises = keys.map(key => {
|
||||
const url = route.resolve[key](match.params);
|
||||
if (url) {
|
||||
return axios.get(url);
|
||||
} else {
|
||||
return Promise.resolve({data: null});
|
||||
}
|
||||
});
|
||||
const resolvedArr = await Promise.all(promises);
|
||||
|
||||
const resolved = {};
|
||||
for (let idx = 0; idx < keys.length; idx++) {
|
||||
resolved[keys[idx]] = resolvedArr[idx].data;
|
||||
}
|
||||
|
||||
return resolved;
|
||||
}
|
||||
|
||||
function getRoutes(urlPrefix, resolve, parents, structure, navs, primaryMenuComponent, secondaryMenuComponent) {
|
||||
let routes = [];
|
||||
for (let routeKey in structure) {
|
||||
const entry = structure[routeKey];
|
||||
|
||||
let path = urlPrefix + routeKey;
|
||||
let pathWithParams = path;
|
||||
|
||||
if (entry.extraParams) {
|
||||
pathWithParams = pathWithParams + '/' + entry.extraParams.join('/');
|
||||
}
|
||||
|
||||
let entryResolve;
|
||||
if (entry.resolve) {
|
||||
entryResolve = Object.assign({}, resolve, entry.resolve);
|
||||
} else {
|
||||
entryResolve = resolve;
|
||||
}
|
||||
|
||||
let navKeys;
|
||||
const entryNavs = [];
|
||||
if (entry.navs) {
|
||||
navKeys = Object.keys(entry.navs);
|
||||
|
||||
for (const navKey of navKeys) {
|
||||
const nav = entry.navs[navKey];
|
||||
|
||||
entryNavs.push({
|
||||
title: nav.title,
|
||||
visible: nav.visible,
|
||||
link: nav.link,
|
||||
externalLink: nav.externalLink
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const route = {
|
||||
path: (pathWithParams === '' ? '/' : pathWithParams),
|
||||
panelComponent: entry.panelComponent,
|
||||
panelRender: entry.panelRender,
|
||||
primaryMenuComponent: entry.primaryMenuComponent || primaryMenuComponent,
|
||||
secondaryMenuComponent: entry.secondaryMenuComponent || secondaryMenuComponent,
|
||||
title: entry.title,
|
||||
link: entry.link,
|
||||
resolve: entryResolve,
|
||||
parents,
|
||||
navs: [...navs, ...entryNavs]
|
||||
};
|
||||
|
||||
routes.push(route);
|
||||
|
||||
const childrenParents = [...parents, route];
|
||||
|
||||
if (entry.navs) {
|
||||
for (let navKeyIdx = 0; navKeyIdx < navKeys.length; navKeyIdx++) {
|
||||
const navKey = navKeys[navKeyIdx];
|
||||
const nav = entry.navs[navKey];
|
||||
|
||||
const childNavs = [...entryNavs];
|
||||
childNavs[navKeyIdx] = Object.assign({}, childNavs[navKeyIdx], { active: true });
|
||||
|
||||
routes = routes.concat(getRoutes(path + '/', entryResolve, childrenParents, { [navKey]: nav }, childNavs, route.primaryMenuComponent, route.secondaryMenuComponent));
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.children) {
|
||||
routes = routes.concat(getRoutes(path + '/', entryResolve, childrenParents, entry.children, entryNavs, route.primaryMenuComponent, route.secondaryMenuComponent));
|
||||
}
|
||||
}
|
||||
|
||||
return routes;
|
||||
}
|
||||
|
||||
function withPageHelpers(target) {
|
||||
target = withErrorHandling(target);
|
||||
|
||||
const inst = target.prototype;
|
||||
|
||||
const contextTypes = target.contextTypes || {};
|
||||
|
||||
contextTypes.sectionContent = PropTypes.object.isRequired;
|
||||
|
||||
target.contextTypes = contextTypes;
|
||||
|
||||
inst.setFlashMessage = function(severity, text) {
|
||||
return this.context.sectionContent.setFlashMessage(severity, text);
|
||||
};
|
||||
|
||||
inst.navigateTo = function(path) {
|
||||
return this.context.sectionContent.navigateTo(path);
|
||||
}
|
||||
|
||||
inst.navigateBack = function() {
|
||||
return this.context.sectionContent.navigateBack();
|
||||
}
|
||||
|
||||
inst.navigateToWithFlashMessage = function(path, severity, text) {
|
||||
return this.context.sectionContent.navigateToWithFlashMessage(path, severity, text);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
export {
|
||||
needsResolve,
|
||||
resolve,
|
||||
getRoutes,
|
||||
withPageHelpers
|
||||
};
|
||||
|
|
@ -1,17 +1,16 @@
|
|||
'use strict';
|
||||
|
||||
import React, { Component } from 'react';
|
||||
import { translate } from 'react-i18next';
|
||||
import PropTypes from 'prop-types';
|
||||
import { withRouter } from 'react-router';
|
||||
import {BrowserRouter as Router, Route, Link, Switch, Redirect} from 'react-router-dom'
|
||||
import { withErrorHandling, withAsyncErrorHandler } from './error-handling';
|
||||
import interoperableErrors from '../../../shared/interoperable-errors';
|
||||
import { DismissibleAlert, Button } from './bootstrap-components';
|
||||
import mailtrainConfig from 'mailtrainConfig';
|
||||
import axios from '../lib/axios';
|
||||
import React, {Component} from "react";
|
||||
import {translate} from "react-i18next";
|
||||
import PropTypes from "prop-types";
|
||||
import {withRouter} from "react-router";
|
||||
import {BrowserRouter as Router, Link, Redirect, Route, Switch} from "react-router-dom";
|
||||
import {withAsyncErrorHandler, withErrorHandling} from "./error-handling";
|
||||
import interoperableErrors from "../../../shared/interoperable-errors";
|
||||
import {Button, DismissibleAlert} from "./bootstrap-components";
|
||||
import mailtrainConfig from "mailtrainConfig";
|
||||
import styles from "./styles.scss";
|
||||
|
||||
import {getRoutes, needsResolve, resolve, withPageHelpers} from "./page-common";
|
||||
|
||||
class Breadcrumb extends Component {
|
||||
static propTypes = {
|
||||
|
|
@ -163,28 +162,42 @@ class RouteContent extends Component {
|
|||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
async resolve() {
|
||||
const route = this.props.route;
|
||||
|
||||
const keys = Object.keys(route.resolve);
|
||||
|
||||
if (keys.length > 0) {
|
||||
const promises = keys.map(key => axios.get(route.resolve[key](this.props.match.params)));
|
||||
const resolvedArr = await Promise.all(promises);
|
||||
|
||||
const resolved = {};
|
||||
for (let idx = 0; idx < keys.length; idx++) {
|
||||
resolved[keys[idx]] = resolvedArr[idx].data;
|
||||
}
|
||||
|
||||
async resolve(props) {
|
||||
if (Object.keys(props.route.resolve).length === 0) {
|
||||
this.setState({
|
||||
resolved
|
||||
resolved: {}
|
||||
});
|
||||
|
||||
} else {
|
||||
this.setState({
|
||||
resolved: null
|
||||
});
|
||||
|
||||
const resolved = await resolve(props.route, props.match);
|
||||
|
||||
if (!this.disregardResolve) { // This is to prevent the warning about setState on discarded component when we immediatelly redirect.
|
||||
this.setState({
|
||||
resolved
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.resolve();
|
||||
this.resolve(this.props);
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
}
|
||||
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.match.params !== nextProps.match.params && needsResolve(this.props.route, nextProps.route, this.props.match, nextProps.match)) {
|
||||
this.resolve(nextProps);
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.disregardResolve = true; // This is to prevent the warning about setState on discarded component when we immediatelly redirect.
|
||||
}
|
||||
|
||||
render() {
|
||||
|
|
@ -193,7 +206,7 @@ class RouteContent extends Component {
|
|||
const params = this.props.match.params;
|
||||
const resolved = this.state.resolved;
|
||||
|
||||
if (!route.render && !route.component && route.link) {
|
||||
if (!route.panelRender && !route.panelComponent && route.link) {
|
||||
let link;
|
||||
if (typeof route.link === 'function') {
|
||||
link = route.link(params);
|
||||
|
|
@ -211,11 +224,11 @@ class RouteContent extends Component {
|
|||
resolved
|
||||
};
|
||||
|
||||
let component;
|
||||
if (route.render) {
|
||||
component = route.render(compProps);
|
||||
} else if (route.component) {
|
||||
component = React.createElement(route.component, compProps, null);
|
||||
let panel;
|
||||
if (route.panelComponent) {
|
||||
panel = React.createElement(route.panelComponent, compProps);
|
||||
} else if (route.panelRender) {
|
||||
panel = route.panelRender(compProps);
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
@ -226,7 +239,7 @@ class RouteContent extends Component {
|
|||
<SecondaryNavBar className="visible-xs" route={route} params={params} resolved={resolved}/>
|
||||
</div>
|
||||
{this.props.flashMessage}
|
||||
{component}
|
||||
{panel}
|
||||
</div>
|
||||
);
|
||||
} else {
|
||||
|
|
@ -291,14 +304,6 @@ class SectionContent extends Component {
|
|||
};
|
||||
}
|
||||
|
||||
getFlashMessageText() {
|
||||
return this.state.flashMessageText;
|
||||
}
|
||||
|
||||
getFlashMessageSeverity() {
|
||||
return this.state.flashMessageSeverity;
|
||||
}
|
||||
|
||||
setFlashMessage(severity, text) {
|
||||
this.setState({
|
||||
flashMessageText: text,
|
||||
|
|
@ -346,77 +351,6 @@ class SectionContent extends Component {
|
|||
})
|
||||
}
|
||||
|
||||
getRoutes(urlPrefix, resolve, parents, structure, navs) {
|
||||
let routes = [];
|
||||
for (let routeKey in structure) {
|
||||
const entry = structure[routeKey];
|
||||
|
||||
let path = urlPrefix + routeKey;
|
||||
let pathWithParams = path;
|
||||
|
||||
if (entry.extraParams) {
|
||||
pathWithParams = pathWithParams + '/' + entry.extraParams.join('/');
|
||||
}
|
||||
|
||||
let entryResolve;
|
||||
if (entry.resolve) {
|
||||
entryResolve = Object.assign({}, resolve, entry.resolve);
|
||||
} else {
|
||||
entryResolve = resolve;
|
||||
}
|
||||
|
||||
let navKeys;
|
||||
const entryNavs = [];
|
||||
if (entry.navs) {
|
||||
navKeys = Object.keys(entry.navs);
|
||||
|
||||
for (const navKey of navKeys) {
|
||||
const nav = entry.navs[navKey];
|
||||
|
||||
entryNavs.push({
|
||||
title: nav.title,
|
||||
visible: nav.visible,
|
||||
link: nav.link,
|
||||
externalLink: nav.externalLink
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const route = {
|
||||
path: (pathWithParams === '' ? '/' : pathWithParams),
|
||||
component: entry.component,
|
||||
render: entry.render,
|
||||
title: entry.title,
|
||||
link: entry.link,
|
||||
resolve: entryResolve,
|
||||
parents,
|
||||
navs: [...navs, ...entryNavs]
|
||||
};
|
||||
|
||||
routes.push(route);
|
||||
|
||||
const childrenParents = [...parents, route];
|
||||
|
||||
if (entry.navs) {
|
||||
for (let navKeyIdx = 0; navKeyIdx < navKeys.length; navKeyIdx++) {
|
||||
const navKey = navKeys[navKeyIdx];
|
||||
const nav = entry.navs[navKey];
|
||||
|
||||
const childNavs = [...entryNavs];
|
||||
childNavs[navKeyIdx] = Object.assign({}, childNavs[navKeyIdx], { active: true });
|
||||
|
||||
routes = routes.concat(this.getRoutes(path + '/', entryResolve, childrenParents, { [navKey]: nav }, childNavs));
|
||||
}
|
||||
}
|
||||
|
||||
if (entry.children) {
|
||||
routes = routes.concat(this.getRoutes(path + '/', entryResolve, childrenParents, entry.children, entryNavs));
|
||||
}
|
||||
}
|
||||
|
||||
return routes;
|
||||
}
|
||||
|
||||
renderRoute(route) {
|
||||
let flashMessage;
|
||||
if (this.state.flashMessageText) {
|
||||
|
|
@ -429,7 +363,7 @@ class SectionContent extends Component {
|
|||
}
|
||||
|
||||
render() {
|
||||
let routes = this.getRoutes('', {}, [], this.props.structure, []);
|
||||
let routes = getRoutes('', {}, [], this.props.structure, [], null, null);
|
||||
|
||||
return (
|
||||
<Switch>{routes.map(x => this.renderRoute(x))}</Switch>
|
||||
|
|
@ -526,44 +460,6 @@ class DropdownLink extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
function withPageHelpers(target) {
|
||||
target = withErrorHandling(target);
|
||||
|
||||
const inst = target.prototype;
|
||||
|
||||
const contextTypes = target.contextTypes || {};
|
||||
|
||||
contextTypes.sectionContent = PropTypes.object.isRequired;
|
||||
|
||||
target.contextTypes = contextTypes;
|
||||
|
||||
inst.getFlashMessageText = function() {
|
||||
return this.context.sectionContent.getFlashMessageText();
|
||||
};
|
||||
|
||||
inst.getFlashMessageSeverity = function() {
|
||||
return this.context.sectionContent.getFlashMessageSeverity();
|
||||
};
|
||||
|
||||
inst.setFlashMessage = function(severity, text) {
|
||||
return this.context.sectionContent.setFlashMessage(severity, text);
|
||||
};
|
||||
|
||||
inst.navigateTo = function(path) {
|
||||
return this.context.sectionContent.navigateTo(path);
|
||||
}
|
||||
|
||||
inst.navigateBack = function() {
|
||||
return this.context.sectionContent.navigateBack();
|
||||
}
|
||||
|
||||
inst.navigateToWithFlashMessage = function(path, severity, text) {
|
||||
return this.context.sectionContent.navigateToWithFlashMessage(path, severity, text);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
function requiresAuthenticatedUser(target) {
|
||||
const comp1 = withPageHelpers(target);
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,10 @@ class Table extends Component {
|
|||
}
|
||||
|
||||
updateSelectInfo() {
|
||||
if (!this.jqSelectInfo) {
|
||||
return; // If the table is updated very quickly after mounting, the datatable may not be initialized yet.
|
||||
}
|
||||
|
||||
const t = this.props.t;
|
||||
|
||||
const count = this.selectionMap.size;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue