RC1 of confirmation dialogs displayed when one navigates from a page with unsaved changes.

Fixes in Share and UserShare.
This commit is contained in:
Tomas Bures 2019-05-12 10:00:10 +02:00
parent c4b78c4823
commit e064948838
6 changed files with 172 additions and 109 deletions

View file

@ -1099,9 +1099,8 @@ const withForm = createComponentMixin([], [], (TargetClass, InnerClass) => {
data.originalHash = data.hash;
delete data.hash;
const mutator = this.getFormValuesMutator;
if (mutator) {
mutator(data);
if (this.getFormValuesMutator) {
this.getFormValuesMutator(data);
}
this.populateFormValues(data);
@ -1126,9 +1125,8 @@ const withForm = createComponentMixin([], [], (TargetClass, InnerClass) => {
data.originalHash = data.hash;
delete data.hash;
const mutator = this.getFormValuesMutator;
if (mutator) {
const newData = mutator(data);
if (this.getFormValuesMutator) {
const newData = this.getFormValuesMutator(data);
if (newData !== undefined) {
data = newData;
@ -1157,9 +1155,8 @@ const withForm = createComponentMixin([], [], (TargetClass, InnerClass) => {
let data = this.getFormValues();
const mutator = this.submitFormValuesMutator;
if (mutator) {
const newData = mutator(data);
if (this.submitFormValuesMutator) {
const newData = this.submitFormValuesMutator(data);
if (newData !== undefined) {
data = newData;
}

View file

@ -197,11 +197,23 @@ export function tableRestActionDialogInit(owner) {
function _hide(owner, dontRefresh = false) {
owner.tableRestActionDialogData = {};
const refreshTables = owner.tableRestActionDialogData.refreshTables;
owner.setState({ tableRestActionDialogShown: false });
if (!dontRefresh) {
owner.tableRestActionDialogData = {};
if (refreshTables) {
refreshTables();
} else {
owner.table.refresh();
}
} else {
// _hide is called twice: (1) at performing action, and at (2) success. Here we keep the refreshTables
// reference till it is really needed in step #2.
owner.tableRestActionDialogData = { refreshTables };
}
}
export function tableAddDeleteButton(actions, owner, perms, deleteUrl, name, deletingMsg, deletedMsg) {
@ -268,15 +280,20 @@ export function tableAddRestActionButton(actions, owner, action, button, title,
actionData: action.data,
actionInProgressMsg: actionInProgressMsg,
actionDoneMsg: actionDoneMsg,
onErrorAsync: onErrorAsync
onErrorAsync: onErrorAsync,
refreshTables: action.refreshTables
};
owner.setState({
tableRestActionDialogShown: true
});
if (action.refreshTables) {
action.refreshTables();
} else {
owner.table.refresh();
}
}
});
}
}

View file

@ -4,18 +4,16 @@ import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {withTranslation} from '../lib/i18n';
import {requiresAuthenticatedUser, Title, withPageHelpers} from '../lib/page';
import {withAsyncErrorHandler, withErrorHandling} from '../lib/error-handling';
import {Button, ButtonRow, Form, FormSendMethod, TableSelect, withForm, withFormErrorHandlers} from '../lib/form';
import {Table} from '../lib/table';
import axios from '../lib/axios';
import {HTTPMethod} from '../lib/axios';
import mailtrainConfig from 'mailtrainConfig';
import {getUrl} from "../lib/urls";
import {withComponentMixins} from "../lib/decorator-helpers";
import {tableAddRestActionButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../lib/modals";
@withComponentMixins([
withTranslation,
withForm,
withErrorHandling,
withPageHelpers,
requiresAuthenticatedUser
])
@ -23,9 +21,13 @@ export default class Share extends Component {
constructor(props) {
super(props);
this.state = {};
this.initForm({
leaveConfirmation: false
});
tableRestActionDialogInit(this);
}
static propTypes = {
@ -34,19 +36,6 @@ export default class Share extends Component {
entityTypeId: PropTypes.string
}
@withAsyncErrorHandler
async deleteShare(userId) {
const data = {
entityTypeId: this.props.entityTypeId,
entityId: this.props.entity.id,
userId
};
await axios.put(getUrl('rest/shares'), data);
this.sharesTable.refresh();
this.usersTableSelect.refresh();
}
clearShareFields() {
this.populateFormValues({
entityTypeId: this.props.entityTypeId,
@ -116,15 +105,37 @@ export default class Share extends Component {
const autoGenerated = data[4];
if (!autoGenerated) {
actions.push({
label: 'Delete',
action: () => this.deleteShare(data[3])
});
const username = data[0];
const userId = data[3];
tableAddRestActionButton(
actions,
this,
{
method: HTTPMethod.PUT,
url: 'rest/shares',
data: {
entityTypeId: this.props.entityTypeId,
entityId: this.props.entity.id,
userId
},
refreshTables: () => {
this.sharesTable.refresh();
this.usersTableSelect.refresh();
}
},
{ icon: 'trash-alt', label: t('Unshare') },
t('Confirm Unsharing'),
t('Are you sure you want to remove the share to user "{{username}}"?', {username}),
t('Removing share for user "{{username}}"', {username}),
t('Share for user "{{username}}" removed', {username}),
null
);
}
return actions;
}
})
});
let usersLabelIndex = 1;
const usersColumns = [
@ -146,6 +157,7 @@ export default class Share extends Component {
return (
<div>
{tableRestActionDialogRender(this)}
<Title>{this.props.title}</Title>
<h3 className="legend">{t('addUser')}</h3>

View file

@ -4,16 +4,13 @@ import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {withTranslation} from '../lib/i18n';
import {requiresAuthenticatedUser, Title, withPageHelpers} from '../lib/page';
import {withAsyncErrorHandler, withErrorHandling} from '../lib/error-handling';
import {Table} from '../lib/table';
import axios from '../lib/axios';
import {Icon} from "../lib/bootstrap-components";
import {getUrl} from "../lib/urls";
import {HTTPMethod} from '../lib/axios';
import {withComponentMixins} from "../lib/decorator-helpers";
import {tableAddRestActionButton, tableRestActionDialogInit, tableRestActionDialogRender} from "../lib/modals";
@withComponentMixins([
withTranslation,
withErrorHandling,
withPageHelpers,
requiresAuthenticatedUser
])
@ -22,33 +19,19 @@ export default class UserShares extends Component {
super(props);
this.sharesTables = {};
this.state = {};
tableRestActionDialogInit(this);
}
static propTypes = {
user: PropTypes.object
}
@withAsyncErrorHandler
async deleteShare(entityTypeId, entityId) {
const data = {
entityTypeId,
entityId,
userId: this.props.user.id
};
await axios.put(getUrl('rest/shares'), data);
for (const key in this.sharesTables) {
this.sharesTables[key].refresh();
}
}
componentDidMount() {
}
render() {
const t = this.props.t;
const renderSharesTable = (entityTypeId, title) => {
const renderSharesTable = (entityTypeId, title, typeName) => {
const columns = [
{ data: 0, title: t('name') },
{ data: 1, title: t('role') },
@ -59,10 +42,33 @@ export default class UserShares extends Component {
const perms = data[4];
if (!autoGenerated && perms.includes('share')) {
actions.push({
label: <Icon icon="trash-alt" title={t('remove')}/>,
action: () => this.deleteShare(entityTypeId, data[2])
});
const name = data[0];
const entityId = data[2];
tableAddRestActionButton(
actions,
this,
{
method: HTTPMethod.PUT,
url: 'rest/shares',
data: {
entityTypeId,
entityId,
userId: this.props.user.id
},
refreshTables: () => {
for (const key in this.sharesTables) {
this.sharesTables[key].refresh();
}
}
},
{ icon: 'trash-alt', label: t('Unshare') },
t('Confirm Unsharing'),
t('Are you sure you want to remove the sharing of the {{typeName}} "{{name}}"?', {typeName, name}),
t('Removing sharing of the {{typeName}} "{{name}}"', {typeName, name}),
t('Sharing of the {{typeName}} "{{name}}" removed', {typeName, name}),
null
);
}
return actions;
@ -80,17 +86,18 @@ export default class UserShares extends Component {
return (
<div>
{tableRestActionDialogRender(this)}
<Title>{t('sharesForUserUsername', {username: this.props.user.username})}</Title>
{renderSharesTable('namespace', t('namespaces'))}
{renderSharesTable('list', t('lists'))}
{renderSharesTable('template', t('Templates'))}
{renderSharesTable('mosaicoTemplate', t('Mosaico Templates'))}
{renderSharesTable('campaign', t('Campaigns'))}
{renderSharesTable('customForm', t('customForms-1'))}
{renderSharesTable('report', t('reports'))}
{renderSharesTable('reportTemplate', t('reportTemplates'))}
{renderSharesTable('sendConfiguration', t('Send Configurations'))}
{renderSharesTable('namespace', t('namespaces'), t('namespace_lc'))}
{renderSharesTable('list', t('lists'), t('list_lc'))}
{renderSharesTable('template', t('Templates'), t('template_lc'))}
{renderSharesTable('mosaicoTemplate', t('Mosaico Templates'), t('Mosaico template'))}
{renderSharesTable('campaign', t('Campaigns'), t('campaign_lc'))}
{renderSharesTable('customForm', t('customForms-1', t('custom forms')))}
{renderSharesTable('report', t('reports'), t('report_lc'))}
{renderSharesTable('reportTemplate', t('reportTemplates'), t('report template'))}
{renderSharesTable('sendConfiguration', t('Send Configurations'), t('send configuration'))}
</div>
);
}

View file

@ -103,14 +103,19 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
/>
</AlignedRow>,
exportHTMLEditorData: async owner => {
const {html, metadata, model} = await owner.editorNode.exportState();
const state = await owner.editorNode.exportState();
// If the sandbox is still loading, the exportState returns null.
if (state) {
return {
[prefix + 'html']: html,
[prefix + 'html']: state.html,
[prefix + 'mosaicoData']: {
metadata,
model
metadata: state.metadata,
model: state.model
}
};
} else {
return null;
}
},
exportContent: async (owner, contentType) => {
const {html, metadata, model} = await owner.editorNode.exportState();
@ -184,14 +189,19 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
/>
</AlignedRow>,
exportHTMLEditorData: async owner => {
const {html, metadata, model} = await owner.editorNode.exportState();
const state = await owner.editorNode.exportState();
// If the sandbox is still loading, the exportState returns null.
if (state) {
return {
[prefix + 'html']: html,
[prefix + 'html']: state.html,
[prefix + 'mosaicoData']: {
metadata,
model
metadata: state.metadata,
model: state.model
}
};
} else {
return null;
}
},
exportContent: async (owner, contentType) => {
const {html, metadata, model} = await owner.editorNode.exportState();
@ -265,14 +275,19 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
/>
</AlignedRow>,
exportHTMLEditorData: async owner => {
const {html, source, style} = await owner.editorNode.exportState();
const state = await owner.editorNode.exportState();
// If the sandbox is still loading, the exportState returns null.
if (state) {
return {
[prefix + 'html']: html,
[prefix + 'html']: state.html,
[prefix + 'grapesJSData']: {
source,
style
source: state.source,
style: state.style
}
};
} else {
return null;
}
},
exportContent: async (owner, contentType) => {
const {html, source, style} = await owner.editorNode.exportState();
@ -326,13 +341,18 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
/>
</AlignedRow>,
exportHTMLEditorData: async owner => {
const {html, source} = await owner.editorNode.exportState();
const state = await owner.editorNode.exportState();
// If the sandbox is still loading, the exportState returns null.
if (state) {
return {
[prefix + 'html']: html,
[prefix + 'html']: state.html,
[prefix + 'ckeditor4Data']: {
source
source: state.source
}
};
} else {
return null;
}
},
exportContent: async (owner, contentType) => {
const {html, source} = await owner.editorNode.exportState();
@ -401,13 +421,18 @@ export function getTemplateTypes(t, prefix = '', entityTypeId = ResourceType.TEM
/>
</AlignedRow>,
exportHTMLEditorData: async owner => {
const {html, source} = await owner.editorNode.exportState();
const state = await owner.editorNode.exportState();
// If the sandbox is still loading, the exportState returns null.
if (state) {
return {
[prefix + 'html']: html,
[prefix + 'html']: state.html,
[prefix + 'codeEditorData']: {
source
source: state.source
}
};
} else {
return null;
}
},
exportContent: async (owner, contentType) => {
const {html, source} = await owner.editorNode.exportState();

View file

@ -125,6 +125,7 @@
"editRssCampaign": "Edit RSS Campaign",
"editTriggeredCampaign": "Edit Triggered Campaign",
"template": "Template",
"template_lc": "template",
"template_plural": "Templates",
"customContentClonedFromTemplate": "Custom content cloned from template",
"customContentClonedFromAnotherCampaign": "Custom content cloned from another campaign",
@ -147,6 +148,7 @@
"subscribers": "Subscribers",
"description": "Description",
"namespace": "Namespace",
"namespace_lc": "namespace",
"namespace_plural": "Namespaces",
"namespaceFiltering": "Namespace filtering",
"remove": "Remove",
@ -154,6 +156,7 @@
"moveUp": "Move up",
"moveDown": "Move down",
"list": "List",
"list_lc": "list",
"list_plural": "Lists",
"segment": "Segment",
"useAParticularSegment": "Use a particular segment",
@ -170,6 +173,7 @@
"contentSource": "Content source",
"selectingATemplateCreatesACampaign": "Selecting a template creates a campaign specific copy from it.",
"campaign": "Campaign",
"campaign_lc": "campaign",
"campaign_plural": "Campaigns",
"contentOfTheSelectedCampaignWillBeCopied": "Content of the selected campaign will be copied into this campaign.",
"renderUrl": "Render URL",
@ -350,6 +354,7 @@
"itSeemsThatSomeoneElseHasDeletedThe-1": "It seems that someone else has deleted the entity in the meantime.",
"customForms": "Custom forms",
"report": "Report",
"report_lc": "report",
"report_plural": "Reports",
"reportTemplate": "Report template",
"reportTemplate_plural": "Report templates",