Basic support for Mosaico templates.

TODO:
- Allow choosing a mosaico template in a mosaico-based template
- Integrate the custom mosaico templates with templates (endpoint for retrieving a mosaico template, replacement of URL_BASE and PLACEHOLDER tags
- Implement support for MJML-based Mosaico templates
- Implement support for MJML-based templates
- Implement support for GrapeJS-based templates
This commit is contained in:
Tomas Bures 2018-04-02 19:05:22 +02:00
parent 7b5642e911
commit 6706d93bc1
21 changed files with 2192 additions and 26 deletions

View file

@ -0,0 +1,192 @@
'use strict';
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { translate, Trans } from 'react-i18next';
import {requiresAuthenticatedUser, withPageHelpers, Title, NavButton} from '../../lib/page'
import { withForm, Form, FormSendMethod, InputField, TextArea, Dropdown, ButtonRow, Button } from '../../lib/form';
import { withErrorHandling, withAsyncErrorHandler } from '../../lib/error-handling';
import { validateNamespace, NamespaceSelect } from '../../lib/namespace';
import {DeleteModalDialog} from "../../lib/modals";
import { versafix } from "../../../../shared/mosaico-templates";
import { getTemplateTypes } from "./helpers";
@translate()
@withForm
@withPageHelpers
@withErrorHandling
@requiresAuthenticatedUser
export default class CUD extends Component {
constructor(props) {
super(props);
this.templateTypes = getTemplateTypes(props.t);
this.state = {};
this.initForm();
}
static propTypes = {
action: PropTypes.string.isRequired,
wizard: PropTypes.string,
entity: PropTypes.object
}
@withAsyncErrorHandler
async loadFormValues() {
await this.getFormValuesFromURL(`/rest/mosaico-templates/${this.props.entity.id}`, data => {
this.templateTypes[data.type].afterLoad(data);
});
}
componentDidMount() {
if (this.props.entity) {
this.getFormValuesFromEntity(this.props.entity, data => {
this.templateTypes[data.type].afterLoad(data);
});
} else {
const wizard = this.props.wizard;
if (wizard === 'versafix') {
this.populateFormValues({
name: '',
description: '',
namespace: mailtrainConfig.user.namespace,
type: 'html',
html: versafix
});
} else {
this.populateFormValues({
name: '',
description: '',
namespace: mailtrainConfig.user.namespace,
type: 'html',
html: ''
});
}
}
}
localValidateFormValues(state) {
const t = this.props.t;
if (!state.getIn(['name', 'value'])) {
state.setIn(['name', 'error'], t('Name must not be empty'));
} else {
state.setIn(['name', 'error'], null);
}
if (!state.getIn(['type', 'value'])) {
state.setIn(['type', 'error'], t('Type must be selected'));
} else {
state.setIn(['type', 'error'], null);
}
validateNamespace(t, state);
}
async submitAndStay() {
await this.formHandleChangedError(async () => await this.doSubmit(true));
}
async submitAndLeave() {
await this.formHandleChangedError(async () => await this.doSubmit(false));
}
async doSubmit(stay) {
const t = this.props.t;
let sendMethod, url;
if (this.props.entity) {
sendMethod = FormSendMethod.PUT;
url = `/rest/mosaico-templates/${this.props.entity.id}`
} else {
sendMethod = FormSendMethod.POST;
url = '/rest/mosaico-templates'
}
this.disableForm();
this.setFormStatusMessage('info', t('Saving ...'));
const submitSuccessful = await this.validateAndSendFormValuesToURL(sendMethod, url, data => {
this.templateTypes[data.type].beforeSave(data);
});
if (submitSuccessful) {
if (stay) {
await this.loadFormValues();
this.enableForm();
this.setFormStatusMessage('success', t('Mosaico template saved'));
} else {
this.navigateToWithFlashMessage('/templates/mosaico', 'success', t('Mosaico template saved'));
}
} else {
this.enableForm();
this.setFormStatusMessage('warning', t('There are errors in the form. Please fix them and submit again.'));
}
}
render() {
const t = this.props.t;
const isEdit = !!this.props.entity;
const canDelete = isEdit && this.props.entity.permissions.includes('delete');
const typeKey = this.getFormValue('type');
let form = null;
if (typeKey) {
form = this.templateTypes[typeKey].getForm(this);
}
const typeOptions = [];
for (const type of ['html', 'mjml']) {
typeOptions.push({
key: type,
label: this.templateTypes.typeName
});
}
return (
<div>
{canDelete &&
<DeleteModalDialog
stateOwner={this}
visible={this.props.action === 'delete'}
deleteUrl={`/rest/templates/mosaico/${this.props.entity.id}`}
cudUrl={`/templates/mosaico/${this.props.entity.id}/edit`}
listUrl="/templates/mosaico"
deletingMsg={t('Deleting mosaico template ...')}
deletedMsg={t('Mosaico template deleted')}/>
}
<Title>{isEdit ? t('Edit Mosaico Template') : t('Create Mosaico Template')}</Title>
<Form stateOwner={this} onSubmitAsync={::this.submitAndLeave}>
<InputField id="name" label={t('Name')}/>
<TextArea id="description" label={t('Description')} help={t('HTML is allowed')}/>
<Dropdown id="type" label={t('Type')} options={typeOptions}/>
<NamespaceSelect/>
{form}
{isEdit ?
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('Save and Stay')} onClickAsync={::this.submitAndStay}/>
<Button type="submit" className="btn-primary" icon="ok" label={t('Save and Leave')}/>
{canDelete &&
<NavButton className="btn-danger" icon="remove" label={t('Delete')} linkTo={`/templates/mosaico/${this.props.entity.id}/delete`}/>
}
</ButtonRow>
:
<ButtonRow>
<Button type="submit" className="btn-primary" icon="ok" label={t('Save')}/>
</ButtonRow>
}
</Form>
</div>
);
}
}