Line endings fixed so that we don't have CRLF in Git. Better now than later.
This commit is contained in:
parent
2fe7f82be3
commit
d482d214d9
69 changed files with 6405 additions and 6405 deletions
|
|
@ -1,32 +1,32 @@
|
|||
'use strict';
|
||||
|
||||
import csrfToken from 'csrfToken';
|
||||
import axios from 'axios';
|
||||
import interoperableErrors from '../../../shared/interoperable-errors';
|
||||
|
||||
const axiosInst = axios.create({
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': csrfToken
|
||||
}
|
||||
});
|
||||
|
||||
const axiosWrapper = {
|
||||
get: (...args) => axiosInst.get(...args).catch(error => { throw (error.response && interoperableErrors.deserialize(error.response.data)) || error }),
|
||||
put: (...args) => axiosInst.put(...args).catch(error => { throw (error.response && interoperableErrors.deserialize(error.response.data)) || error }),
|
||||
post: (...args) => axiosInst.post(...args).catch(error => { throw (error.response && interoperableErrors.deserialize(error.response.data)) || error }),
|
||||
delete: (...args) => axiosInst.delete(...args).catch(error => { throw (error.response && interoperableErrors.deserialize(error.response.data)) || error })
|
||||
};
|
||||
|
||||
const HTTPMethod = {
|
||||
GET: axiosWrapper.get,
|
||||
PUT: axiosWrapper.put,
|
||||
POST: axiosWrapper.post,
|
||||
DELETE: axiosWrapper.delete
|
||||
};
|
||||
|
||||
axiosWrapper.method = (method, ...args) => method(...args);
|
||||
|
||||
export default axiosWrapper;
|
||||
export {
|
||||
HTTPMethod
|
||||
'use strict';
|
||||
|
||||
import csrfToken from 'csrfToken';
|
||||
import axios from 'axios';
|
||||
import interoperableErrors from '../../../shared/interoperable-errors';
|
||||
|
||||
const axiosInst = axios.create({
|
||||
headers: {
|
||||
'X-CSRF-TOKEN': csrfToken
|
||||
}
|
||||
});
|
||||
|
||||
const axiosWrapper = {
|
||||
get: (...args) => axiosInst.get(...args).catch(error => { throw (error.response && interoperableErrors.deserialize(error.response.data)) || error }),
|
||||
put: (...args) => axiosInst.put(...args).catch(error => { throw (error.response && interoperableErrors.deserialize(error.response.data)) || error }),
|
||||
post: (...args) => axiosInst.post(...args).catch(error => { throw (error.response && interoperableErrors.deserialize(error.response.data)) || error }),
|
||||
delete: (...args) => axiosInst.delete(...args).catch(error => { throw (error.response && interoperableErrors.deserialize(error.response.data)) || error })
|
||||
};
|
||||
|
||||
const HTTPMethod = {
|
||||
GET: axiosWrapper.get,
|
||||
PUT: axiosWrapper.put,
|
||||
POST: axiosWrapper.post,
|
||||
DELETE: axiosWrapper.delete
|
||||
};
|
||||
|
||||
axiosWrapper.method = (method, ...args) => method(...args);
|
||||
|
||||
export default axiosWrapper;
|
||||
export {
|
||||
HTTPMethod
|
||||
}
|
||||
662
client/src/lib/bootstrap-components.js
vendored
662
client/src/lib/bootstrap-components.js
vendored
|
|
@ -1,331 +1,331 @@
|
|||
'use strict';
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import {withTranslation} from './i18n';
|
||||
import PropTypes
|
||||
from 'prop-types';
|
||||
import {
|
||||
withAsyncErrorHandler,
|
||||
withErrorHandling
|
||||
} from './error-handling';
|
||||
import {withComponentMixins} from "./decorator-helpers";
|
||||
|
||||
@withComponentMixins([
|
||||
withTranslation,
|
||||
withErrorHandling
|
||||
])
|
||||
export class DismissibleAlert extends Component {
|
||||
static propTypes = {
|
||||
severity: PropTypes.string.isRequired,
|
||||
onCloseAsync: PropTypes.func
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
onClose() {
|
||||
if (this.props.onCloseAsync) {
|
||||
this.props.onCloseAsync();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
|
||||
return (
|
||||
<div className={`alert alert-${this.props.severity} alert-dismissible`} role="alert">
|
||||
<button type="button" className="close" aria-label={t('close')} onClick={::this.onClose}><span aria-hidden="true">×</span></button>
|
||||
{this.props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class Icon extends Component {
|
||||
static propTypes = {
|
||||
icon: PropTypes.string.isRequired,
|
||||
family: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
className: PropTypes.string
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
family: 'fas'
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
|
||||
if (props.family === 'fas' || props.family === 'far') {
|
||||
return <i className={`${props.family} fa-${props.icon} ${props.className || ''}`} title={props.title}></i>;
|
||||
} else {
|
||||
console.error(`Icon font family ${props.family} not supported. (icon: ${props.icon}, title: ${props.title})`)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@withComponentMixins([
|
||||
withErrorHandling
|
||||
])
|
||||
export class Button extends Component {
|
||||
static propTypes = {
|
||||
onClickAsync: PropTypes.func,
|
||||
label: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
iconTitle: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
disabled: PropTypes.bool
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
async onClick(evt) {
|
||||
if (this.props.onClickAsync) {
|
||||
evt.preventDefault();
|
||||
await this.props.onClickAsync(evt);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
|
||||
let className = 'btn';
|
||||
if (props.className) {
|
||||
className = className + ' ' + props.className;
|
||||
}
|
||||
|
||||
let type = props.type || 'button';
|
||||
|
||||
let icon;
|
||||
if (props.icon) {
|
||||
icon = <Icon icon={props.icon} title={props.iconTitle}/>
|
||||
}
|
||||
|
||||
let iconSpacer;
|
||||
if (props.icon && props.label) {
|
||||
iconSpacer = ' ';
|
||||
}
|
||||
|
||||
return (
|
||||
<button type={type} className={className} onClick={::this.onClick} title={this.props.title} disabled={this.props.disabled}>{icon}{iconSpacer}{props.label}</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class ButtonDropdown extends Component {
|
||||
static propTypes = {
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
className: PropTypes.string,
|
||||
buttonClassName: PropTypes.string,
|
||||
menuClassName: PropTypes.string
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
|
||||
const className = 'dropdown' + (props.className ? ' ' + props.className : '');
|
||||
const buttonClassName = 'btn dropdown-toggle' + (props.buttonClassName ? ' ' + props.buttonClassName : '');
|
||||
const menuClassName = 'dropdown-menu' + (props.menuClassName ? ' ' + props.menuClassName : '');
|
||||
|
||||
return (
|
||||
<div className="dropdown" className={className}>
|
||||
<button type="button" className={buttonClassName} data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{props.label}
|
||||
</button>
|
||||
<ul className={menuClassName}>
|
||||
{props.children}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@withComponentMixins([
|
||||
withErrorHandling
|
||||
])
|
||||
export class ActionLink extends Component {
|
||||
static propTypes = {
|
||||
onClickAsync: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
href: PropTypes.string
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
async onClick(evt) {
|
||||
if (this.props.onClickAsync) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
await this.props.onClickAsync(evt);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
|
||||
return (
|
||||
<a href={props.href || ''} className={props.className} onClick={::this.onClick}>{props.children}</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class DropdownActionLink extends Component {
|
||||
static propTypes = {
|
||||
onClickAsync: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
disabled: PropTypes.bool
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
|
||||
let clsName = "dropdown-item ";
|
||||
if (props.disabled) {
|
||||
clsName += "disabled ";
|
||||
}
|
||||
|
||||
clsName += props.className;
|
||||
|
||||
return (
|
||||
<ActionLink className={clsName} onClickAsync={props.onClickAsync}>{props.children}</ActionLink>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class DropdownDivider extends Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
|
||||
let className = 'dropdown-divider';
|
||||
if (props.className) {
|
||||
className = className + ' ' + props.className;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@withComponentMixins([
|
||||
withTranslation,
|
||||
withErrorHandling
|
||||
])
|
||||
export class ModalDialog extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const t = props.t;
|
||||
|
||||
this.state = {
|
||||
buttons: this.props.buttons || [ { label: t('close'), className: 'btn-secondary', onClickAsync: null } ]
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
title: PropTypes.string,
|
||||
onCloseAsync: PropTypes.func,
|
||||
onButtonClickAsync: PropTypes.func,
|
||||
buttons: PropTypes.array,
|
||||
hidden: PropTypes.bool,
|
||||
className: PropTypes.string
|
||||
}
|
||||
|
||||
/*
|
||||
this.props.hidden - this is the desired state of the modal
|
||||
this.hidden - this is the actual state of the modal - this is because there is no public API on Bootstrap modal to know whether the modal is shown or not
|
||||
*/
|
||||
|
||||
componentDidMount() {
|
||||
const jqModal = jQuery(this.domModal);
|
||||
|
||||
jqModal.on('shown.bs.modal', () => jqModal.focus());
|
||||
jqModal.on('hide.bs.modal', ::this.onHide);
|
||||
|
||||
this.hidden = this.props.hidden;
|
||||
jqModal.modal({
|
||||
show: !this.props.hidden
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.props.hidden != this.hidden) {
|
||||
const jqModal = jQuery(this.domModal);
|
||||
this.hidden = this.props.hidden;
|
||||
jqModal.modal(this.props.hidden ? 'hide' : 'show');
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// We discard the modal in a hard way (without hiding it). Thus we have to take care of the backgrop too.
|
||||
jQuery('.modal-backdrop').remove();
|
||||
}
|
||||
|
||||
onHide(evt) {
|
||||
// Hide event is emited is both when hidden through user action or through API. We have to let the API
|
||||
// calls through, otherwise the modal would never hide. The user actions, which change the desired state,
|
||||
// are capture, converted to onClose callback and prevented. It's up to the parent to decide whether to
|
||||
// hide the modal or not.
|
||||
if (!this.props.hidden) {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.onClose();
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
async onClose() {
|
||||
if (this.props.onCloseAsync) {
|
||||
await this.props.onCloseAsync();
|
||||
}
|
||||
}
|
||||
|
||||
async onButtonClick(idx) {
|
||||
const buttonSpec = this.state.buttons[idx];
|
||||
if (buttonSpec.onClickAsync) {
|
||||
await buttonSpec.onClickAsync(idx);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
const t = props.t;
|
||||
|
||||
const buttons = [];
|
||||
for (let idx = 0; idx < this.state.buttons.length; idx++) {
|
||||
const buttonSpec = this.state.buttons[idx];
|
||||
const button = <Button key={idx} label={buttonSpec.label} className={buttonSpec.className} onClickAsync={async () => this.onButtonClick(idx)} />
|
||||
buttons.push(button);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={(domElem) => { this.domModal = domElem; }}
|
||||
className={'modal fade' + (props.className ? ' ' + props.className : '')}
|
||||
tabIndex="-1" role="dialog" aria-labelledby="myModalLabel">
|
||||
|
||||
<div className="modal-dialog" role="document">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h4 className="modal-title">{this.props.title}</h4>
|
||||
<button type="button" className="close" aria-label={t('close')} onClick={::this.onClose}><span aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
<div className="modal-body">{this.props.children}</div>
|
||||
<div className="modal-footer">
|
||||
{buttons}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
'use strict';
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import {withTranslation} from './i18n';
|
||||
import PropTypes
|
||||
from 'prop-types';
|
||||
import {
|
||||
withAsyncErrorHandler,
|
||||
withErrorHandling
|
||||
} from './error-handling';
|
||||
import {withComponentMixins} from "./decorator-helpers";
|
||||
|
||||
@withComponentMixins([
|
||||
withTranslation,
|
||||
withErrorHandling
|
||||
])
|
||||
export class DismissibleAlert extends Component {
|
||||
static propTypes = {
|
||||
severity: PropTypes.string.isRequired,
|
||||
onCloseAsync: PropTypes.func
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
onClose() {
|
||||
if (this.props.onCloseAsync) {
|
||||
this.props.onCloseAsync();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
|
||||
return (
|
||||
<div className={`alert alert-${this.props.severity} alert-dismissible`} role="alert">
|
||||
<button type="button" className="close" aria-label={t('close')} onClick={::this.onClose}><span aria-hidden="true">×</span></button>
|
||||
{this.props.children}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export class Icon extends Component {
|
||||
static propTypes = {
|
||||
icon: PropTypes.string.isRequired,
|
||||
family: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
className: PropTypes.string
|
||||
}
|
||||
|
||||
static defaultProps = {
|
||||
family: 'fas'
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
|
||||
if (props.family === 'fas' || props.family === 'far') {
|
||||
return <i className={`${props.family} fa-${props.icon} ${props.className || ''}`} title={props.title}></i>;
|
||||
} else {
|
||||
console.error(`Icon font family ${props.family} not supported. (icon: ${props.icon}, title: ${props.title})`)
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@withComponentMixins([
|
||||
withErrorHandling
|
||||
])
|
||||
export class Button extends Component {
|
||||
static propTypes = {
|
||||
onClickAsync: PropTypes.func,
|
||||
label: PropTypes.string,
|
||||
icon: PropTypes.string,
|
||||
iconTitle: PropTypes.string,
|
||||
className: PropTypes.string,
|
||||
title: PropTypes.string,
|
||||
type: PropTypes.string,
|
||||
disabled: PropTypes.bool
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
async onClick(evt) {
|
||||
if (this.props.onClickAsync) {
|
||||
evt.preventDefault();
|
||||
await this.props.onClickAsync(evt);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
|
||||
let className = 'btn';
|
||||
if (props.className) {
|
||||
className = className + ' ' + props.className;
|
||||
}
|
||||
|
||||
let type = props.type || 'button';
|
||||
|
||||
let icon;
|
||||
if (props.icon) {
|
||||
icon = <Icon icon={props.icon} title={props.iconTitle}/>
|
||||
}
|
||||
|
||||
let iconSpacer;
|
||||
if (props.icon && props.label) {
|
||||
iconSpacer = ' ';
|
||||
}
|
||||
|
||||
return (
|
||||
<button type={type} className={className} onClick={::this.onClick} title={this.props.title} disabled={this.props.disabled}>{icon}{iconSpacer}{props.label}</button>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class ButtonDropdown extends Component {
|
||||
static propTypes = {
|
||||
label: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
className: PropTypes.string,
|
||||
buttonClassName: PropTypes.string,
|
||||
menuClassName: PropTypes.string
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
|
||||
const className = 'dropdown' + (props.className ? ' ' + props.className : '');
|
||||
const buttonClassName = 'btn dropdown-toggle' + (props.buttonClassName ? ' ' + props.buttonClassName : '');
|
||||
const menuClassName = 'dropdown-menu' + (props.menuClassName ? ' ' + props.menuClassName : '');
|
||||
|
||||
return (
|
||||
<div className="dropdown" className={className}>
|
||||
<button type="button" className={buttonClassName} data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
{props.label}
|
||||
</button>
|
||||
<ul className={menuClassName}>
|
||||
{props.children}
|
||||
</ul>
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@withComponentMixins([
|
||||
withErrorHandling
|
||||
])
|
||||
export class ActionLink extends Component {
|
||||
static propTypes = {
|
||||
onClickAsync: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
href: PropTypes.string
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
async onClick(evt) {
|
||||
if (this.props.onClickAsync) {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
|
||||
await this.props.onClickAsync(evt);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
|
||||
return (
|
||||
<a href={props.href || ''} className={props.className} onClick={::this.onClick}>{props.children}</a>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class DropdownActionLink extends Component {
|
||||
static propTypes = {
|
||||
onClickAsync: PropTypes.func,
|
||||
className: PropTypes.string,
|
||||
disabled: PropTypes.bool
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
|
||||
let clsName = "dropdown-item ";
|
||||
if (props.disabled) {
|
||||
clsName += "disabled ";
|
||||
}
|
||||
|
||||
clsName += props.className;
|
||||
|
||||
return (
|
||||
<ActionLink className={clsName} onClickAsync={props.onClickAsync}>{props.children}</ActionLink>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class DropdownDivider extends Component {
|
||||
static propTypes = {
|
||||
className: PropTypes.string
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
|
||||
let className = 'dropdown-divider';
|
||||
if (props.className) {
|
||||
className = className + ' ' + props.className;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={className}/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@withComponentMixins([
|
||||
withTranslation,
|
||||
withErrorHandling
|
||||
])
|
||||
export class ModalDialog extends Component {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
const t = props.t;
|
||||
|
||||
this.state = {
|
||||
buttons: this.props.buttons || [ { label: t('close'), className: 'btn-secondary', onClickAsync: null } ]
|
||||
};
|
||||
}
|
||||
|
||||
static propTypes = {
|
||||
title: PropTypes.string,
|
||||
onCloseAsync: PropTypes.func,
|
||||
onButtonClickAsync: PropTypes.func,
|
||||
buttons: PropTypes.array,
|
||||
hidden: PropTypes.bool,
|
||||
className: PropTypes.string
|
||||
}
|
||||
|
||||
/*
|
||||
this.props.hidden - this is the desired state of the modal
|
||||
this.hidden - this is the actual state of the modal - this is because there is no public API on Bootstrap modal to know whether the modal is shown or not
|
||||
*/
|
||||
|
||||
componentDidMount() {
|
||||
const jqModal = jQuery(this.domModal);
|
||||
|
||||
jqModal.on('shown.bs.modal', () => jqModal.focus());
|
||||
jqModal.on('hide.bs.modal', ::this.onHide);
|
||||
|
||||
this.hidden = this.props.hidden;
|
||||
jqModal.modal({
|
||||
show: !this.props.hidden
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate() {
|
||||
if (this.props.hidden != this.hidden) {
|
||||
const jqModal = jQuery(this.domModal);
|
||||
this.hidden = this.props.hidden;
|
||||
jqModal.modal(this.props.hidden ? 'hide' : 'show');
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
// We discard the modal in a hard way (without hiding it). Thus we have to take care of the backgrop too.
|
||||
jQuery('.modal-backdrop').remove();
|
||||
}
|
||||
|
||||
onHide(evt) {
|
||||
// Hide event is emited is both when hidden through user action or through API. We have to let the API
|
||||
// calls through, otherwise the modal would never hide. The user actions, which change the desired state,
|
||||
// are capture, converted to onClose callback and prevented. It's up to the parent to decide whether to
|
||||
// hide the modal or not.
|
||||
if (!this.props.hidden) {
|
||||
// noinspection JSIgnoredPromiseFromCall
|
||||
this.onClose();
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
|
||||
@withAsyncErrorHandler
|
||||
async onClose() {
|
||||
if (this.props.onCloseAsync) {
|
||||
await this.props.onCloseAsync();
|
||||
}
|
||||
}
|
||||
|
||||
async onButtonClick(idx) {
|
||||
const buttonSpec = this.state.buttons[idx];
|
||||
if (buttonSpec.onClickAsync) {
|
||||
await buttonSpec.onClickAsync(idx);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const props = this.props;
|
||||
const t = props.t;
|
||||
|
||||
const buttons = [];
|
||||
for (let idx = 0; idx < this.state.buttons.length; idx++) {
|
||||
const buttonSpec = this.state.buttons[idx];
|
||||
const button = <Button key={idx} label={buttonSpec.label} className={buttonSpec.className} onClickAsync={async () => this.onButtonClick(idx)} />
|
||||
buttons.push(button);
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={(domElem) => { this.domModal = domElem; }}
|
||||
className={'modal fade' + (props.className ? ' ' + props.className : '')}
|
||||
tabIndex="-1" role="dialog" aria-labelledby="myModalLabel">
|
||||
|
||||
<div className="modal-dialog" role="document">
|
||||
<div className="modal-content">
|
||||
<div className="modal-header">
|
||||
<h4 className="modal-title">{this.props.title}</h4>
|
||||
<button type="button" className="close" aria-label={t('close')} onClick={::this.onClose}><span aria-hidden="true">×</span></button>
|
||||
</div>
|
||||
<div className="modal-body">{this.props.children}</div>
|
||||
<div className="modal-footer">
|
||||
{buttons}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,128 +1,128 @@
|
|||
'use strict';
|
||||
|
||||
import React from "react";
|
||||
|
||||
export function createComponentMixin(contexts, deps, decoratorFn) {
|
||||
return {
|
||||
contexts,
|
||||
deps,
|
||||
decoratorFn
|
||||
};
|
||||
}
|
||||
|
||||
export function withComponentMixins(mixins, delegateFuns) {
|
||||
const mixinsClosure = new Set();
|
||||
for (const mixin of mixins) {
|
||||
mixinsClosure.add(mixin);
|
||||
for (const dep of mixin.deps) {
|
||||
mixinsClosure.add(dep);
|
||||
}
|
||||
}
|
||||
|
||||
const contexts = new Map();
|
||||
for (const mixin of mixinsClosure.values()) {
|
||||
for (const ctx of mixin.contexts) {
|
||||
contexts.set(ctx.propName, ctx.context);
|
||||
}
|
||||
}
|
||||
|
||||
return TargetClass => {
|
||||
const ctors = [];
|
||||
const mixinDelegateFuns = [];
|
||||
|
||||
if (delegateFuns) {
|
||||
mixinDelegateFuns.push(...delegateFuns);
|
||||
}
|
||||
|
||||
function TargetClassWithCtors(props) {
|
||||
if (!new.target) {
|
||||
throw new TypeError();
|
||||
}
|
||||
|
||||
const self = Reflect.construct(TargetClass, [props], new.target);
|
||||
|
||||
for (const ctor of ctors) {
|
||||
ctor(self, props);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
TargetClassWithCtors.prototype = TargetClass.prototype;
|
||||
|
||||
for (const attr in TargetClass) {
|
||||
TargetClassWithCtors[attr] = TargetClass[attr];
|
||||
}
|
||||
|
||||
|
||||
class ComponentMixinsInner extends React.Component {
|
||||
render() {
|
||||
const props = {
|
||||
...this.props,
|
||||
ref: this.props._decoratorInnerInstanceRefFn
|
||||
};
|
||||
delete props._decoratorInnerInstanceRefFn;
|
||||
|
||||
return (
|
||||
<TargetClassWithCtors {...props}/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let DecoratedInner = ComponentMixinsInner;
|
||||
|
||||
for (const mixin of mixinsClosure.values()) {
|
||||
const res = mixin.decoratorFn(DecoratedInner, TargetClassWithCtors);
|
||||
|
||||
if (res.cls) {
|
||||
DecoratedInner = res.cls;
|
||||
}
|
||||
|
||||
if (res.ctor) {
|
||||
ctors.push(res.ctor);
|
||||
}
|
||||
|
||||
if (res.delegateFuns) {
|
||||
mixinDelegateFuns.push(...res.delegateFuns);
|
||||
}
|
||||
}
|
||||
|
||||
class ComponentMixinsOuter extends React.Component {
|
||||
render() {
|
||||
let innerFn = parentProps => {
|
||||
const props = {
|
||||
...parentProps,
|
||||
_decoratorInnerInstanceRefFn: node => this._decoratorInnerInstance = node
|
||||
};
|
||||
|
||||
return <DecoratedInner {...props}/>
|
||||
}
|
||||
|
||||
for (const [propName, Context] of contexts.entries()) {
|
||||
const existingInnerFn = innerFn;
|
||||
innerFn = parentProps => (
|
||||
<Context.Consumer>
|
||||
{
|
||||
value => existingInnerFn({
|
||||
...parentProps,
|
||||
[propName]: value
|
||||
})
|
||||
}
|
||||
</Context.Consumer>
|
||||
);
|
||||
}
|
||||
|
||||
return innerFn(this.props);
|
||||
}
|
||||
}
|
||||
|
||||
for (const fun of mixinDelegateFuns) {
|
||||
ComponentMixinsOuter.prototype[fun] = function (...args) {
|
||||
return this._decoratorInnerInstance[fun](...args);
|
||||
}
|
||||
}
|
||||
|
||||
return ComponentMixinsOuter;
|
||||
};
|
||||
}
|
||||
|
||||
'use strict';
|
||||
|
||||
import React from "react";
|
||||
|
||||
export function createComponentMixin(contexts, deps, decoratorFn) {
|
||||
return {
|
||||
contexts,
|
||||
deps,
|
||||
decoratorFn
|
||||
};
|
||||
}
|
||||
|
||||
export function withComponentMixins(mixins, delegateFuns) {
|
||||
const mixinsClosure = new Set();
|
||||
for (const mixin of mixins) {
|
||||
mixinsClosure.add(mixin);
|
||||
for (const dep of mixin.deps) {
|
||||
mixinsClosure.add(dep);
|
||||
}
|
||||
}
|
||||
|
||||
const contexts = new Map();
|
||||
for (const mixin of mixinsClosure.values()) {
|
||||
for (const ctx of mixin.contexts) {
|
||||
contexts.set(ctx.propName, ctx.context);
|
||||
}
|
||||
}
|
||||
|
||||
return TargetClass => {
|
||||
const ctors = [];
|
||||
const mixinDelegateFuns = [];
|
||||
|
||||
if (delegateFuns) {
|
||||
mixinDelegateFuns.push(...delegateFuns);
|
||||
}
|
||||
|
||||
function TargetClassWithCtors(props) {
|
||||
if (!new.target) {
|
||||
throw new TypeError();
|
||||
}
|
||||
|
||||
const self = Reflect.construct(TargetClass, [props], new.target);
|
||||
|
||||
for (const ctor of ctors) {
|
||||
ctor(self, props);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
TargetClassWithCtors.prototype = TargetClass.prototype;
|
||||
|
||||
for (const attr in TargetClass) {
|
||||
TargetClassWithCtors[attr] = TargetClass[attr];
|
||||
}
|
||||
|
||||
|
||||
class ComponentMixinsInner extends React.Component {
|
||||
render() {
|
||||
const props = {
|
||||
...this.props,
|
||||
ref: this.props._decoratorInnerInstanceRefFn
|
||||
};
|
||||
delete props._decoratorInnerInstanceRefFn;
|
||||
|
||||
return (
|
||||
<TargetClassWithCtors {...props}/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let DecoratedInner = ComponentMixinsInner;
|
||||
|
||||
for (const mixin of mixinsClosure.values()) {
|
||||
const res = mixin.decoratorFn(DecoratedInner, TargetClassWithCtors);
|
||||
|
||||
if (res.cls) {
|
||||
DecoratedInner = res.cls;
|
||||
}
|
||||
|
||||
if (res.ctor) {
|
||||
ctors.push(res.ctor);
|
||||
}
|
||||
|
||||
if (res.delegateFuns) {
|
||||
mixinDelegateFuns.push(...res.delegateFuns);
|
||||
}
|
||||
}
|
||||
|
||||
class ComponentMixinsOuter extends React.Component {
|
||||
render() {
|
||||
let innerFn = parentProps => {
|
||||
const props = {
|
||||
...parentProps,
|
||||
_decoratorInnerInstanceRefFn: node => this._decoratorInnerInstance = node
|
||||
};
|
||||
|
||||
return <DecoratedInner {...props}/>
|
||||
}
|
||||
|
||||
for (const [propName, Context] of contexts.entries()) {
|
||||
const existingInnerFn = innerFn;
|
||||
innerFn = parentProps => (
|
||||
<Context.Consumer>
|
||||
{
|
||||
value => existingInnerFn({
|
||||
...parentProps,
|
||||
[propName]: value
|
||||
})
|
||||
}
|
||||
</Context.Consumer>
|
||||
);
|
||||
}
|
||||
|
||||
return innerFn(this.props);
|
||||
}
|
||||
}
|
||||
|
||||
for (const fun of mixinDelegateFuns) {
|
||||
ComponentMixinsOuter.prototype[fun] = function (...args) {
|
||||
return this._decoratorInnerInstance[fun](...args);
|
||||
}
|
||||
}
|
||||
|
||||
return ComponentMixinsOuter;
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,76 +1,76 @@
|
|||
'use strict';
|
||||
|
||||
import React from "react";
|
||||
import PropTypes from 'prop-types';
|
||||
import {createComponentMixin} from "./decorator-helpers";
|
||||
|
||||
function handleError(that, error) {
|
||||
let errorHandled;
|
||||
if (that.errorHandler) {
|
||||
errorHandled = that.errorHandler(error);
|
||||
}
|
||||
|
||||
if (!errorHandled && that.props.parentErrorHandler) {
|
||||
errorHandled = handleError(that.props.parentErrorHandler, error);
|
||||
}
|
||||
|
||||
if (!errorHandled) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return errorHandled;
|
||||
}
|
||||
|
||||
export const ParentErrorHandlerContext = React.createContext(null);
|
||||
export const withErrorHandling = createComponentMixin([{context: ParentErrorHandlerContext, propName: 'parentErrorHandler'}], [], (TargetClass, InnerClass) => {
|
||||
/* Example of use:
|
||||
this.getFormValuesFromURL(....).catch(error => this.handleError(error));
|
||||
|
||||
It's equivalent to:
|
||||
|
||||
@withAsyncErrorHandler
|
||||
async loadFormValues() {
|
||||
await this.getFormValuesFromURL(...);
|
||||
}
|
||||
*/
|
||||
|
||||
const originalRender = InnerClass.prototype.render;
|
||||
|
||||
InnerClass.prototype.render = function() {
|
||||
return (
|
||||
<ParentErrorHandlerContext.Provider value={this}>
|
||||
{originalRender.apply(this)}
|
||||
</ParentErrorHandlerContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
InnerClass.prototype.handleError = function(error) {
|
||||
handleError(this, error);
|
||||
};
|
||||
|
||||
return {};
|
||||
});
|
||||
|
||||
export function withAsyncErrorHandler(target, name, descriptor) {
|
||||
let fn = descriptor.value;
|
||||
|
||||
descriptor.value = async function () {
|
||||
try {
|
||||
await fn.apply(this, arguments)
|
||||
} catch (error) {
|
||||
handleError(this, error);
|
||||
}
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
export function wrapWithAsyncErrorHandler(self, fn) {
|
||||
return async function () {
|
||||
try {
|
||||
await fn.apply(this, arguments)
|
||||
} catch (error) {
|
||||
handleError(self, error);
|
||||
}
|
||||
};
|
||||
}
|
||||
'use strict';
|
||||
|
||||
import React from "react";
|
||||
import PropTypes from 'prop-types';
|
||||
import {createComponentMixin} from "./decorator-helpers";
|
||||
|
||||
function handleError(that, error) {
|
||||
let errorHandled;
|
||||
if (that.errorHandler) {
|
||||
errorHandled = that.errorHandler(error);
|
||||
}
|
||||
|
||||
if (!errorHandled && that.props.parentErrorHandler) {
|
||||
errorHandled = handleError(that.props.parentErrorHandler, error);
|
||||
}
|
||||
|
||||
if (!errorHandled) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return errorHandled;
|
||||
}
|
||||
|
||||
export const ParentErrorHandlerContext = React.createContext(null);
|
||||
export const withErrorHandling = createComponentMixin([{context: ParentErrorHandlerContext, propName: 'parentErrorHandler'}], [], (TargetClass, InnerClass) => {
|
||||
/* Example of use:
|
||||
this.getFormValuesFromURL(....).catch(error => this.handleError(error));
|
||||
|
||||
It's equivalent to:
|
||||
|
||||
@withAsyncErrorHandler
|
||||
async loadFormValues() {
|
||||
await this.getFormValuesFromURL(...);
|
||||
}
|
||||
*/
|
||||
|
||||
const originalRender = InnerClass.prototype.render;
|
||||
|
||||
InnerClass.prototype.render = function() {
|
||||
return (
|
||||
<ParentErrorHandlerContext.Provider value={this}>
|
||||
{originalRender.apply(this)}
|
||||
</ParentErrorHandlerContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
InnerClass.prototype.handleError = function(error) {
|
||||
handleError(this, error);
|
||||
};
|
||||
|
||||
return {};
|
||||
});
|
||||
|
||||
export function withAsyncErrorHandler(target, name, descriptor) {
|
||||
let fn = descriptor.value;
|
||||
|
||||
descriptor.value = async function () {
|
||||
try {
|
||||
await fn.apply(this, arguments)
|
||||
} catch (error) {
|
||||
handleError(this, error);
|
||||
}
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
export function wrapWithAsyncErrorHandler(self, fn) {
|
||||
return async function () {
|
||||
try {
|
||||
await fn.apply(this, arguments)
|
||||
} catch (error) {
|
||||
handleError(self, error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
'use strict';
|
||||
|
||||
import ellipsize from "ellipsize";
|
||||
|
||||
|
||||
export function ellipsizeBreadcrumbLabel(label) {
|
||||
return ellipsize(label, 40)
|
||||
'use strict';
|
||||
|
||||
import ellipsize from "ellipsize";
|
||||
|
||||
|
||||
export function ellipsizeBreadcrumbLabel(label) {
|
||||
return ellipsize(label, 40)
|
||||
}
|
||||
|
|
@ -1,75 +1,75 @@
|
|||
'use strict';
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import i18n
|
||||
from 'i18next';
|
||||
import {withNamespaces} from "react-i18next";
|
||||
import LanguageDetector
|
||||
from 'i18next-browser-languagedetector';
|
||||
import mailtrainConfig
|
||||
from 'mailtrainConfig';
|
||||
|
||||
import {convertToFake, getLang} from '../../../shared/langs';
|
||||
import {createComponentMixin} from "./decorator-helpers";
|
||||
|
||||
import lang_en_US_common from "../../../locales/en-US/common";
|
||||
import lang_es_ES_common from "../../../locales/es-ES/common";
|
||||
|
||||
|
||||
const resourcesCommon = {
|
||||
'en-US': lang_en_US_common,
|
||||
'es-ES': lang_es_ES_common,
|
||||
'fk-FK': convertToFake(lang_en_US_common)
|
||||
};
|
||||
|
||||
const resources = {};
|
||||
for (const lng of mailtrainConfig.enabledLanguages) {
|
||||
const langDesc = getLang(lng);
|
||||
resources[langDesc.longCode] = {
|
||||
common: resourcesCommon[langDesc.longCode]
|
||||
};
|
||||
}
|
||||
|
||||
i18n
|
||||
.use(LanguageDetector)
|
||||
.init({
|
||||
resources,
|
||||
|
||||
fallbackLng: mailtrainConfig.defaultLanguage,
|
||||
defaultNS: 'common',
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false // not needed for react
|
||||
},
|
||||
|
||||
react: {
|
||||
wait: true
|
||||
},
|
||||
|
||||
detection: {
|
||||
order: ['querystring', 'cookie', 'localStorage', 'navigator'],
|
||||
lookupQuerystring: 'locale',
|
||||
lookupCookie: 'i18nextLng',
|
||||
lookupLocalStorage: 'i18nextLng',
|
||||
caches: ['localStorage', 'cookie']
|
||||
},
|
||||
|
||||
whitelist: mailtrainConfig.enabledLanguages,
|
||||
load: 'currentOnly',
|
||||
|
||||
debug: false
|
||||
});
|
||||
|
||||
|
||||
export default i18n;
|
||||
|
||||
|
||||
export const withTranslation = createComponentMixin([], [], (TargetClass, InnerClass) => {
|
||||
return {
|
||||
cls: withNamespaces()(TargetClass)
|
||||
};
|
||||
});
|
||||
|
||||
export function tMark(key) {
|
||||
return key;
|
||||
}
|
||||
'use strict';
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import i18n
|
||||
from 'i18next';
|
||||
import {withNamespaces} from "react-i18next";
|
||||
import LanguageDetector
|
||||
from 'i18next-browser-languagedetector';
|
||||
import mailtrainConfig
|
||||
from 'mailtrainConfig';
|
||||
|
||||
import {convertToFake, getLang} from '../../../shared/langs';
|
||||
import {createComponentMixin} from "./decorator-helpers";
|
||||
|
||||
import lang_en_US_common from "../../../locales/en-US/common";
|
||||
import lang_es_ES_common from "../../../locales/es-ES/common";
|
||||
|
||||
|
||||
const resourcesCommon = {
|
||||
'en-US': lang_en_US_common,
|
||||
'es-ES': lang_es_ES_common,
|
||||
'fk-FK': convertToFake(lang_en_US_common)
|
||||
};
|
||||
|
||||
const resources = {};
|
||||
for (const lng of mailtrainConfig.enabledLanguages) {
|
||||
const langDesc = getLang(lng);
|
||||
resources[langDesc.longCode] = {
|
||||
common: resourcesCommon[langDesc.longCode]
|
||||
};
|
||||
}
|
||||
|
||||
i18n
|
||||
.use(LanguageDetector)
|
||||
.init({
|
||||
resources,
|
||||
|
||||
fallbackLng: mailtrainConfig.defaultLanguage,
|
||||
defaultNS: 'common',
|
||||
|
||||
interpolation: {
|
||||
escapeValue: false // not needed for react
|
||||
},
|
||||
|
||||
react: {
|
||||
wait: true
|
||||
},
|
||||
|
||||
detection: {
|
||||
order: ['querystring', 'cookie', 'localStorage', 'navigator'],
|
||||
lookupQuerystring: 'locale',
|
||||
lookupCookie: 'i18nextLng',
|
||||
lookupLocalStorage: 'i18nextLng',
|
||||
caches: ['localStorage', 'cookie']
|
||||
},
|
||||
|
||||
whitelist: mailtrainConfig.enabledLanguages,
|
||||
load: 'currentOnly',
|
||||
|
||||
debug: false
|
||||
});
|
||||
|
||||
|
||||
export default i18n;
|
||||
|
||||
|
||||
export const withTranslation = createComponentMixin([], [], (TargetClass, InnerClass) => {
|
||||
return {
|
||||
cls: withNamespaces()(TargetClass)
|
||||
};
|
||||
});
|
||||
|
||||
export function tMark(key) {
|
||||
return key;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,33 +1,33 @@
|
|||
'use strict';
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import {withTranslation} from './i18n';
|
||||
import {TreeTableSelect} from './form';
|
||||
import {withComponentMixins} from "./decorator-helpers";
|
||||
|
||||
|
||||
@withComponentMixins([
|
||||
withTranslation
|
||||
])
|
||||
class NamespaceSelect extends Component {
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
|
||||
return (
|
||||
<TreeTableSelect id="namespace" label={t('namespace')} dataUrl="rest/namespaces-tree"/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function validateNamespace(t, state) {
|
||||
if (!state.getIn(['namespace', 'value'])) {
|
||||
state.setIn(['namespace', 'error'], t('namespacemustBeSelected'));
|
||||
} else {
|
||||
state.setIn(['namespace', 'error'], null);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
NamespaceSelect,
|
||||
validateNamespace
|
||||
'use strict';
|
||||
|
||||
import React, {Component} from 'react';
|
||||
import {withTranslation} from './i18n';
|
||||
import {TreeTableSelect} from './form';
|
||||
import {withComponentMixins} from "./decorator-helpers";
|
||||
|
||||
|
||||
@withComponentMixins([
|
||||
withTranslation
|
||||
])
|
||||
class NamespaceSelect extends Component {
|
||||
render() {
|
||||
const t = this.props.t;
|
||||
|
||||
return (
|
||||
<TreeTableSelect id="namespace" label={t('namespace')} dataUrl="rest/namespaces-tree"/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function validateNamespace(t, state) {
|
||||
if (!state.getIn(['namespace', 'value'])) {
|
||||
state.setIn(['namespace', 'error'], t('namespacemustBeSelected'));
|
||||
} else {
|
||||
state.setIn(['namespace', 'error'], null);
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
NamespaceSelect,
|
||||
validateNamespace
|
||||
};
|
||||
|
|
@ -1,146 +1,146 @@
|
|||
'use strict';
|
||||
|
||||
import React
|
||||
from "react";
|
||||
import {withRouter} from "react-router";
|
||||
import {withErrorHandling} from "./error-handling";
|
||||
import axios
|
||||
from "../lib/axios";
|
||||
import {getUrl} from "./urls";
|
||||
import {createComponentMixin} from "./decorator-helpers";
|
||||
|
||||
export function needsResolve(route, nextRoute, match, nextMatch) {
|
||||
const resolve = route.resolve;
|
||||
const nextResolve = nextRoute.resolve;
|
||||
|
||||
// This compares whether two objects have the same content and returns TRUE if they don't
|
||||
if (Object.keys(resolve).length === Object.keys(nextResolve).length) {
|
||||
for (const key in nextResolve) {
|
||||
if (!(key in resolve) ||
|
||||
resolve[key](match.params) !== nextResolve[key](nextMatch.params)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export 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(getUrl(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;
|
||||
}
|
||||
|
||||
export 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,
|
||||
panelInFullScreen: entry.panelInFullScreen,
|
||||
insideIframe: entry.insideIframe,
|
||||
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;
|
||||
}
|
||||
|
||||
export const SectionContentContext = React.createContext(null);
|
||||
export const withPageHelpers = createComponentMixin([{context: SectionContentContext, propName: 'sectionContent'}], [withErrorHandling], (TargetClass, InnerClass) => {
|
||||
InnerClass.prototype.setFlashMessage = function(severity, text) {
|
||||
return this.props.sectionContent.setFlashMessage(severity, text);
|
||||
};
|
||||
|
||||
InnerClass.prototype.navigateTo = function(path) {
|
||||
return this.props.sectionContent.navigateTo(path);
|
||||
}
|
||||
|
||||
InnerClass.prototype.navigateBack = function() {
|
||||
return this.props.sectionContent.navigateBack();
|
||||
}
|
||||
|
||||
InnerClass.prototype.navigateToWithFlashMessage = function(path, severity, text) {
|
||||
return this.props.sectionContent.navigateToWithFlashMessage(path, severity, text);
|
||||
}
|
||||
|
||||
return {};
|
||||
});
|
||||
'use strict';
|
||||
|
||||
import React
|
||||
from "react";
|
||||
import {withRouter} from "react-router";
|
||||
import {withErrorHandling} from "./error-handling";
|
||||
import axios
|
||||
from "../lib/axios";
|
||||
import {getUrl} from "./urls";
|
||||
import {createComponentMixin} from "./decorator-helpers";
|
||||
|
||||
export function needsResolve(route, nextRoute, match, nextMatch) {
|
||||
const resolve = route.resolve;
|
||||
const nextResolve = nextRoute.resolve;
|
||||
|
||||
// This compares whether two objects have the same content and returns TRUE if they don't
|
||||
if (Object.keys(resolve).length === Object.keys(nextResolve).length) {
|
||||
for (const key in nextResolve) {
|
||||
if (!(key in resolve) ||
|
||||
resolve[key](match.params) !== nextResolve[key](nextMatch.params)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export 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(getUrl(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;
|
||||
}
|
||||
|
||||
export 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,
|
||||
panelInFullScreen: entry.panelInFullScreen,
|
||||
insideIframe: entry.insideIframe,
|
||||
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;
|
||||
}
|
||||
|
||||
export const SectionContentContext = React.createContext(null);
|
||||
export const withPageHelpers = createComponentMixin([{context: SectionContentContext, propName: 'sectionContent'}], [withErrorHandling], (TargetClass, InnerClass) => {
|
||||
InnerClass.prototype.setFlashMessage = function(severity, text) {
|
||||
return this.props.sectionContent.setFlashMessage(severity, text);
|
||||
};
|
||||
|
||||
InnerClass.prototype.navigateTo = function(path) {
|
||||
return this.props.sectionContent.navigateTo(path);
|
||||
}
|
||||
|
||||
InnerClass.prototype.navigateBack = function() {
|
||||
return this.props.sectionContent.navigateBack();
|
||||
}
|
||||
|
||||
InnerClass.prototype.navigateToWithFlashMessage = function(path, severity, text) {
|
||||
return this.props.sectionContent.navigateToWithFlashMessage(path, severity, text);
|
||||
}
|
||||
|
||||
return {};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
'use strict';
|
||||
|
||||
import {getUrl} from "./urls";
|
||||
import axios from "./axios";
|
||||
|
||||
async function checkPermissions(request) {
|
||||
return await axios.post(getUrl('rest/permissions-check'), request);
|
||||
}
|
||||
|
||||
export {
|
||||
checkPermissions
|
||||
'use strict';
|
||||
|
||||
import {getUrl} from "./urls";
|
||||
import axios from "./axios";
|
||||
|
||||
async function checkPermissions(request) {
|
||||
return await axios.post(getUrl('rest/permissions-check'), request);
|
||||
}
|
||||
|
||||
export {
|
||||
checkPermissions
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
'use strict';
|
||||
|
||||
import {getUrl} from "./urls";
|
||||
|
||||
__webpack_public_path__ = getUrl('client/');
|
||||
'use strict';
|
||||
|
||||
import {getUrl} from "./urls";
|
||||
|
||||
__webpack_public_path__ = getUrl('client/');
|
||||
|
|
|
|||
|
|
@ -1,90 +1,90 @@
|
|||
$navbarHeight: 34px;
|
||||
$editorNormalHeight: 800px !default;
|
||||
|
||||
.editor {
|
||||
.host {
|
||||
@if $editorNormalHeight {
|
||||
height: $editorNormalHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editorFullscreen {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
z-index: 1000;
|
||||
background: white;
|
||||
margin-top: $navbarHeight;
|
||||
|
||||
.navbar {
|
||||
margin-top: -$navbarHeight;
|
||||
}
|
||||
|
||||
.host {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background: #f86c6b;
|
||||
width: 100%;
|
||||
height: $navbarHeight;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.navbarLeft {
|
||||
.logo {
|
||||
display: inline-block;
|
||||
height: $navbarHeight;
|
||||
padding: 5px 0 5px 10px;
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
.title {
|
||||
display: inline-block;
|
||||
padding: 5px 0 5px 10px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
float: left;
|
||||
color: white;
|
||||
height: $navbarHeight;
|
||||
}
|
||||
}
|
||||
|
||||
.navbarRight {
|
||||
.btn, .btnDisabled {
|
||||
display: inline-block;
|
||||
padding: 0px 15px;
|
||||
line-height: $navbarHeight;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
font-family: sans-serif;
|
||||
cursor: pointer;
|
||||
|
||||
&, &:not([href]):not([tabindex]) { // This is to override reboot.scss in bootstrap
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #c05454;
|
||||
text-decoration: none;
|
||||
|
||||
&, &:not([href]):not([tabindex]) { // This is to override reboot.scss in bootstrap
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.btnDisabled {
|
||||
cursor: default;
|
||||
|
||||
&, &:not([href]):not([tabindex]) { // This is to override reboot.scss in bootstrap
|
||||
color: #621d1d;
|
||||
}
|
||||
}
|
||||
}
|
||||
$navbarHeight: 34px;
|
||||
$editorNormalHeight: 800px !default;
|
||||
|
||||
.editor {
|
||||
.host {
|
||||
@if $editorNormalHeight {
|
||||
height: $editorNormalHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.editorFullscreen {
|
||||
position: fixed;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
z-index: 1000;
|
||||
background: white;
|
||||
margin-top: $navbarHeight;
|
||||
|
||||
.navbar {
|
||||
margin-top: -$navbarHeight;
|
||||
}
|
||||
|
||||
.host {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.navbar {
|
||||
background: #f86c6b;
|
||||
width: 100%;
|
||||
height: $navbarHeight;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.navbarLeft {
|
||||
.logo {
|
||||
display: inline-block;
|
||||
height: $navbarHeight;
|
||||
padding: 5px 0 5px 10px;
|
||||
filter: brightness(0) invert(1);
|
||||
}
|
||||
|
||||
.title {
|
||||
display: inline-block;
|
||||
padding: 5px 0 5px 10px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
float: left;
|
||||
color: white;
|
||||
height: $navbarHeight;
|
||||
}
|
||||
}
|
||||
|
||||
.navbarRight {
|
||||
.btn, .btnDisabled {
|
||||
display: inline-block;
|
||||
padding: 0px 15px;
|
||||
line-height: $navbarHeight;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
font-family: sans-serif;
|
||||
cursor: pointer;
|
||||
|
||||
&, &:not([href]):not([tabindex]) { // This is to override reboot.scss in bootstrap
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #c05454;
|
||||
text-decoration: none;
|
||||
|
||||
&, &:not([href]):not([tabindex]) { // This is to override reboot.scss in bootstrap
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
||||
.btnDisabled {
|
||||
cursor: default;
|
||||
|
||||
&, &:not([href]):not([tabindex]) { // This is to override reboot.scss in bootstrap
|
||||
color: #621d1d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
$editorNormalHeight: false;
|
||||
@import "sandbox-common";
|
||||
|
||||
.sandbox {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
$editorNormalHeight: false;
|
||||
@import "sandbox-common";
|
||||
|
||||
.sandbox {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
|
@ -1,35 +1,35 @@
|
|||
@import "sandbox-common";
|
||||
|
||||
.sandbox {
|
||||
}
|
||||
|
||||
.aceEditorWithPreview, .aceEditorWithoutPreview, .preview {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.aceEditorWithPreview {
|
||||
border-right: #e8e8e8 solid 2px;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.aceEditorWithoutPreview {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.preview {
|
||||
border-left: #e8e8e8 solid 2px;
|
||||
width: 50%;
|
||||
left: 50%;
|
||||
overflow: hidden;
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0px none;
|
||||
|
||||
body {
|
||||
margin: 0px;
|
||||
}
|
||||
}
|
||||
@import "sandbox-common";
|
||||
|
||||
.sandbox {
|
||||
}
|
||||
|
||||
.aceEditorWithPreview, .aceEditorWithoutPreview, .preview {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.aceEditorWithPreview {
|
||||
border-right: #e8e8e8 solid 2px;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.aceEditorWithoutPreview {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.preview {
|
||||
border-left: #e8e8e8 solid 2px;
|
||||
width: 50%;
|
||||
left: 50%;
|
||||
overflow: hidden;
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0px none;
|
||||
|
||||
body {
|
||||
margin: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,18 +1,18 @@
|
|||
@import "sandbox-common";
|
||||
|
||||
:global .grapesjs-body {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
:global .gjs-editor-cont {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
:global .gjs-devices-c .gjs-devices {
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
:global .gjs-pn-devices-c, :global .gjs-pn-views {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
@import "sandbox-common";
|
||||
|
||||
:global .grapesjs-body {
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
:global .gjs-editor-cont {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
:global .gjs-devices-c .gjs-devices {
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
:global .gjs-pn-devices-c, :global .gjs-pn-views {
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
@import "sandbox-common";
|
||||
|
||||
:global .mo-standalone {
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
@import "sandbox-common";
|
||||
|
||||
:global .mo-standalone {
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,92 +1,92 @@
|
|||
@import "../scss/variables.scss";
|
||||
|
||||
:global {
|
||||
|
||||
.mt-treetable-container .fancytree-container {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.mt-treetable-container span.fancytree-expander {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.mt-treetable-container.mt-treetable-inactivable>table.fancytree-ext-table.fancytree-container>tbody>tr.fancytree-active>td,
|
||||
.mt-treetable-container.mt-treetable-inactivable>table.fancytree-ext-table.fancytree-container>tbody>tr.fancytree-active:hover>td,
|
||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title,
|
||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title:hover,
|
||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active:hover span.fancytree-title,
|
||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node span.fancytree-title:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node span.fancytree-title {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title,
|
||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title:hover {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.mt-treetable-container.mt-treetable-inactivable>table.fancytree-ext-table.fancytree-container>tbody>tr.fancytree-active>td span.fancytree-title,
|
||||
.mt-treetable-container.mt-treetable-inactivable>table.fancytree-ext-table.fancytree-container>tbody>tr.fancytree-active>td span.fancytree-expander,
|
||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title,
|
||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container>tbody>tr.fancytree-active>td {
|
||||
outline: 0px none;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.mt-treetable-container span.fancytree-node span.fancytree-expander:hover {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.mt-treetable-container {
|
||||
padding-top: 9px;
|
||||
padding-bottom: 9px;
|
||||
}
|
||||
|
||||
.mt-treetable-container>table.fancytree-ext-table {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.mt-treetable-container.mt-treetable-noheader>.table>tbody>tr>td {
|
||||
border-top: 0px none;
|
||||
}
|
||||
|
||||
.mt-treetable-container .mt-treetable-title {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.form-group .mt-treetable-container {
|
||||
border: $input-border-width solid $input-border-color;
|
||||
border-radius: $input-border-radius;
|
||||
padding-top: $input-padding-y;
|
||||
padding-bottom: $input-padding-y;
|
||||
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||
-webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
|
||||
-o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
|
||||
transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
|
||||
}
|
||||
|
||||
.form-group .mt-treetable-container.is-valid {
|
||||
border-color: $form-feedback-valid-color;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||
}
|
||||
|
||||
.form-group .mt-treetable-container.is-invalid {
|
||||
border-color: $form-feedback-invalid-color;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||
}
|
||||
|
||||
.mt-treetable-container .table td {
|
||||
padding-top: 3px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
}
|
||||
@import "../scss/variables.scss";
|
||||
|
||||
:global {
|
||||
|
||||
.mt-treetable-container .fancytree-container {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.mt-treetable-container span.fancytree-expander {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.mt-treetable-container.mt-treetable-inactivable>table.fancytree-ext-table.fancytree-container>tbody>tr.fancytree-active>td,
|
||||
.mt-treetable-container.mt-treetable-inactivable>table.fancytree-ext-table.fancytree-container>tbody>tr.fancytree-active:hover>td,
|
||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title,
|
||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title:hover,
|
||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active:hover span.fancytree-title,
|
||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node span.fancytree-title:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node span.fancytree-title {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title,
|
||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title:hover {
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.mt-treetable-container.mt-treetable-inactivable>table.fancytree-ext-table.fancytree-container>tbody>tr.fancytree-active>td span.fancytree-title,
|
||||
.mt-treetable-container.mt-treetable-inactivable>table.fancytree-ext-table.fancytree-container>tbody>tr.fancytree-active>td span.fancytree-expander,
|
||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container span.fancytree-node.fancytree-active span.fancytree-title,
|
||||
.mt-treetable-container.mt-treetable-inactivable .fancytree-container>tbody>tr.fancytree-active>td {
|
||||
outline: 0px none;
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.mt-treetable-container span.fancytree-node span.fancytree-expander:hover {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.mt-treetable-container {
|
||||
padding-top: 9px;
|
||||
padding-bottom: 9px;
|
||||
}
|
||||
|
||||
.mt-treetable-container>table.fancytree-ext-table {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
|
||||
.mt-treetable-container.mt-treetable-noheader>.table>tbody>tr>td {
|
||||
border-top: 0px none;
|
||||
}
|
||||
|
||||
.mt-treetable-container .mt-treetable-title {
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
|
||||
|
||||
.form-group .mt-treetable-container {
|
||||
border: $input-border-width solid $input-border-color;
|
||||
border-radius: $input-border-radius;
|
||||
padding-top: $input-padding-y;
|
||||
padding-bottom: $input-padding-y;
|
||||
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||
-webkit-transition: border-color ease-in-out .15s,-webkit-box-shadow ease-in-out .15s;
|
||||
-o-transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
|
||||
transition: border-color ease-in-out .15s,box-shadow ease-in-out .15s;
|
||||
}
|
||||
|
||||
.form-group .mt-treetable-container.is-valid {
|
||||
border-color: $form-feedback-valid-color;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||
}
|
||||
|
||||
.form-group .mt-treetable-container.is-invalid {
|
||||
border-color: $form-feedback-invalid-color;
|
||||
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||
box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
|
||||
}
|
||||
|
||||
.mt-treetable-container .table td {
|
||||
padding-top: 3px;
|
||||
padding-bottom: 3px;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,60 +1,60 @@
|
|||
'use strict';
|
||||
|
||||
import {anonymousRestrictedAccessToken} from '../../../shared/urls';
|
||||
import {AppType} from '../../../shared/app';
|
||||
import mailtrainConfig from "mailtrainConfig";
|
||||
import i18n from './i18n';
|
||||
|
||||
let restrictedAccessToken = anonymousRestrictedAccessToken;
|
||||
|
||||
function setRestrictedAccessToken(token) {
|
||||
restrictedAccessToken = token;
|
||||
}
|
||||
|
||||
function getTrustedUrl(path) {
|
||||
return mailtrainConfig.trustedUrlBase + (path || '');
|
||||
}
|
||||
|
||||
function getSandboxUrl(path, customRestrictedAccessToken) {
|
||||
const localRestrictedAccessToken = customRestrictedAccessToken || restrictedAccessToken;
|
||||
return mailtrainConfig.sandboxUrlBase + localRestrictedAccessToken + '/' + (path || '');
|
||||
}
|
||||
|
||||
function getPublicUrl(path, opts) {
|
||||
const url = new URL(path || '', mailtrainConfig.publicUrlBase);
|
||||
|
||||
if (opts && opts.withLocale) {
|
||||
url.searchParams.append('locale', i18n.language);
|
||||
}
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function getUrl(path) {
|
||||
if (mailtrainConfig.appType === AppType.TRUSTED) {
|
||||
return getTrustedUrl(path);
|
||||
} else if (mailtrainConfig.appType === AppType.SANDBOXED) {
|
||||
return getSandboxUrl(path);
|
||||
} else if (mailtrainConfig.appType === AppType.PUBLIC) {
|
||||
return getPublicUrl(path);
|
||||
}
|
||||
}
|
||||
|
||||
function getBaseDir() {
|
||||
if (mailtrainConfig.appType === AppType.TRUSTED) {
|
||||
return mailtrainConfig.trustedUrlBaseDir;
|
||||
} else if (mailtrainConfig.appType === AppType.SANDBOXED) {
|
||||
return mailtrainConfig.sandboxUrlBaseDir + restrictedAccessToken;
|
||||
} else if (mailtrainConfig.appType === AppType.PUBLIC) {
|
||||
return mailtrainConfig.publicUrlBaseDir;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
getTrustedUrl,
|
||||
getSandboxUrl,
|
||||
getPublicUrl,
|
||||
getUrl,
|
||||
getBaseDir,
|
||||
setRestrictedAccessToken
|
||||
'use strict';
|
||||
|
||||
import {anonymousRestrictedAccessToken} from '../../../shared/urls';
|
||||
import {AppType} from '../../../shared/app';
|
||||
import mailtrainConfig from "mailtrainConfig";
|
||||
import i18n from './i18n';
|
||||
|
||||
let restrictedAccessToken = anonymousRestrictedAccessToken;
|
||||
|
||||
function setRestrictedAccessToken(token) {
|
||||
restrictedAccessToken = token;
|
||||
}
|
||||
|
||||
function getTrustedUrl(path) {
|
||||
return mailtrainConfig.trustedUrlBase + (path || '');
|
||||
}
|
||||
|
||||
function getSandboxUrl(path, customRestrictedAccessToken) {
|
||||
const localRestrictedAccessToken = customRestrictedAccessToken || restrictedAccessToken;
|
||||
return mailtrainConfig.sandboxUrlBase + localRestrictedAccessToken + '/' + (path || '');
|
||||
}
|
||||
|
||||
function getPublicUrl(path, opts) {
|
||||
const url = new URL(path || '', mailtrainConfig.publicUrlBase);
|
||||
|
||||
if (opts && opts.withLocale) {
|
||||
url.searchParams.append('locale', i18n.language);
|
||||
}
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function getUrl(path) {
|
||||
if (mailtrainConfig.appType === AppType.TRUSTED) {
|
||||
return getTrustedUrl(path);
|
||||
} else if (mailtrainConfig.appType === AppType.SANDBOXED) {
|
||||
return getSandboxUrl(path);
|
||||
} else if (mailtrainConfig.appType === AppType.PUBLIC) {
|
||||
return getPublicUrl(path);
|
||||
}
|
||||
}
|
||||
|
||||
function getBaseDir() {
|
||||
if (mailtrainConfig.appType === AppType.TRUSTED) {
|
||||
return mailtrainConfig.trustedUrlBaseDir;
|
||||
} else if (mailtrainConfig.appType === AppType.SANDBOXED) {
|
||||
return mailtrainConfig.sandboxUrlBaseDir + restrictedAccessToken;
|
||||
} else if (mailtrainConfig.appType === AppType.PUBLIC) {
|
||||
return mailtrainConfig.publicUrlBaseDir;
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
getTrustedUrl,
|
||||
getSandboxUrl,
|
||||
getPublicUrl,
|
||||
getUrl,
|
||||
getBaseDir,
|
||||
setRestrictedAccessToken
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue