Line endings fixed so that we don't have CRLF in Git. Better now than later.

This commit is contained in:
Tomas Bures 2019-03-27 09:49:29 +01:00
parent 2fe7f82be3
commit d482d214d9
69 changed files with 6405 additions and 6405 deletions

View file

@ -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
}

View file

@ -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">&times;</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">&times;</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">&times;</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">&times;</span></button>
</div>
<div className="modal-body">{this.props.children}</div>
<div className="modal-footer">
{buttons}
</div>
</div>
</div>
</div>
);
}
}

View file

@ -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;
};
}

View file

@ -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);
}
};
}

View file

@ -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)
}

View file

@ -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;
}

View file

@ -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
};

View file

@ -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 {};
});

View file

@ -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
}

View file

@ -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/');

View file

@ -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;
}
}
}

View file

@ -1,7 +1,7 @@
$editorNormalHeight: false;
@import "sandbox-common";
.sandbox {
height: 100%;
overflow: hidden;
$editorNormalHeight: false;
@import "sandbox-common";
.sandbox {
height: 100%;
overflow: hidden;
}

View file

@ -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;
}
}
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}
}

View file

@ -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
}