Skip to content

Commit

Permalink
Merge remote-tracking branch 'AlejandroCano/O365' into bootstrap5
Browse files Browse the repository at this point in the history
# Conflicts:
#	Signum.React.Extensions/Mailing/MailingClient.tsx
#	Signum.React.Extensions/Workflow/WorkflowClient.tsx
  • Loading branch information
olmobrutall committed Nov 12, 2021
2 parents 53c1189 + 27cc951 commit 002bcc9
Show file tree
Hide file tree
Showing 19 changed files with 283 additions and 206 deletions.
6 changes: 3 additions & 3 deletions Signum.React.Extensions/Alerts/AlertsClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,10 +52,10 @@ export function start(options: { routes: JSX.Element[], showAlerts?: (typeName:
}));

Operations.addSettings(new EntityOperationSettings(AlertOperation.Delay, {
onClick: (eoc) => chooseDate().then(d => d && eoc.defaultClick(d.toISO())).done(),
onClick: (eoc) => chooseDate().then(d => d && eoc.defaultClick(d.toISO())),
hideOnCanExecute: true,
contextual: { onClick: (coc) => chooseDate().then(d => d && coc.defaultContextualClick(d.toISO())).done() },
contextualFromMany: { onClick: (coc) => chooseDate().then(d => d && coc.defaultContextualClick(d.toISO())).done() },
contextual: { onClick: (coc) => chooseDate().then(d => d && coc.defaultContextualClick(d.toISO())) },
contextualFromMany: { onClick: (coc) => chooseDate().then(d => d && coc.defaultContextualClick(d.toISO())) },
}));

var cellFormatter = new Finder.CellFormatter((cell, ctx) => {
Expand Down
12 changes: 6 additions & 6 deletions Signum.React.Extensions/Dynamic/DynamicTypeClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,24 +32,24 @@ export function start(options: { routes: JSX.Element[] }) {
onClick: eoc => {
(eoc.frame.entityComponent as DynamicTypeComponent).beforeSave();

Operations.API.executeEntity(eoc.entity, eoc.operationInfo.key)
return Operations.API.executeEntity(eoc.entity, eoc.operationInfo.key)
.then(pack => { eoc.frame.onReload(pack); Operations.notifySuccess(); })
.then(() => {
if (AuthClient.isPermissionAuthorized(DynamicPanelPermission.ViewDynamicPanel)) {
MessageModal.show({
return MessageModal.show({
title: DynamicTypeMessage.TypeSaved.niceToString(),
message: DynamicTypeMessage.DynamicType0SucessfullySavedGoToDynamicPanelNow.niceToString(eoc.entity.typeName),
buttons: "yes_no",
style: "success",
icon: "success"
}).then(result => {
if (result == "yes")
if (result == "yes")
window.open(AppContext.toAbsoluteUrl("~/dynamic/panel"));
}).done();
return;
});
}
})
.catch(ifError(ValidationError, e => eoc.frame.setError(e.modelState, "entity")))
.done();
.catch(ifError(ValidationError, e => eoc.frame.setError(e.modelState, "entity")));
},
alternatives: eoc => [],
}));
Expand Down
16 changes: 8 additions & 8 deletions Signum.React.Extensions/Dynamic/DynamicViewClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,33 +64,33 @@ export function start(options: { routes: JSX.Element[] }) {
onClick: ctx => {
(ctx.frame.entityComponent as DynamicViewEntityComponent).beforeSave();
cleanCaches();
ctx.defaultClick();
return ctx.defaultClick();
}
}));

Operations.addSettings(new EntityOperationSettings(DynamicViewOperation.Delete, {
onClick: ctx => {
cleanCaches();
ctx.defaultClick();
return ctx.defaultClick();
},
contextual: { onClick: ctx => { cleanCaches(); ctx.defaultContextualClick(); } },
contextualFromMany: { onClick: ctx => { cleanCaches(); ctx.defaultContextualClick(); } },
contextual: { onClick: ctx => { cleanCaches(); return ctx.defaultContextualClick(); } },
contextualFromMany: { onClick: ctx => { cleanCaches(); return ctx.defaultContextualClick(); } },
}));

Operations.addSettings(new EntityOperationSettings(DynamicViewSelectorOperation.Save, {
onClick: ctx => {
cleanCaches();
ctx.defaultClick();
return ctx.defaultClick();
}
}));

Operations.addSettings(new EntityOperationSettings(DynamicViewSelectorOperation.Delete, {
onClick: ctx => {
cleanCaches();
ctx.defaultClick();
return ctx.defaultClick();
},
contextual: { onClick: ctx => { cleanCaches(); ctx.defaultContextualClick(); } },
contextualFromMany: { onClick: ctx => { cleanCaches(); ctx.defaultContextualClick(); } },
contextual: { onClick: ctx => { cleanCaches(); return ctx.defaultContextualClick(); } },
contextualFromMany: { onClick: ctx => { cleanCaches(); return ctx.defaultContextualClick(); } },
}));

Navigator.setViewDispatcher(new DynamicViewViewDispatcher());
Expand Down
10 changes: 4 additions & 6 deletions Signum.React.Extensions/MachineLearning/PredictorClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,20 +68,18 @@ export function start(options: { routes: JSX.Element[] }) {

Operations.addSettings(new EntityOperationSettings(PredictorOperation.Publish, {
hideOnCanExecute: true,
onClick: eoc => {
onClick: eoc =>
API.publications(eoc.entity.mainQuery.query!.key)
.then(pubs => SelectorModal.chooseElement(pubs, { buttonDisplay: a => symbolNiceName(a), buttonName: a => a.key }))
.then(pps => pps && eoc.defaultClick(pps))
.done();
},
,
contextual: {
onClick: coc => {
onClick: coc =>
Navigator.API.fetch(coc.context.lites[0])
.then(p => API.publications(p.mainQuery.query!.key))
.then(pubs => SelectorModal.chooseElement(pubs, { buttonDisplay: a => symbolNiceName(a), buttonName: a => a.key }))
.then(pps => pps && coc.defaultContextualClick(pps))
.done();
}

}
}));

Expand Down
26 changes: 15 additions & 11 deletions Signum.React.Extensions/Mailing/MailingClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { ajaxPost, ajaxGet } from '@framework/Services';
import { EntitySettings } from '@framework/Navigator'
import * as Navigator from '@framework/Navigator'
import * as Constructor from '@framework/Constructor'
import * as Finder from '@framework/Finder'
import { Lite, Entity, registerToString, JavascriptMessage } from '@framework/Signum.Entities'
import { EntityOperationSettings } from '@framework/Operations'
import { PseudoType, Type, getTypeName } from '@framework/Reflection'
import { PseudoType, Type, getTypeName, isTypeEntity } from '@framework/Reflection'
import * as Operations from '@framework/Operations'
import { EmailMessageEntity, EmailTemplateMessageEmbedded, EmailMasterTemplateEntity, EmailMasterTemplateMessageEmbedded, EmailMessageOperation, EmailPackageEntity, EmailRecipientEmbedded, EmailConfigurationEmbedded, EmailTemplateEntity, AsyncEmailSenderPermission, EmailModelEntity, IEmailOwnerEntity, EmailFromEmbedded, MicrosoftGraphEmbedded } from './Signum.Entities.Mailing'
import { EmailSenderConfigurationEntity, Pop3ConfigurationEntity, Pop3ReceptionEntity, EmailAddressEmbedded } from './Signum.Entities.Mailing'
Expand Down Expand Up @@ -61,17 +62,20 @@ export function start(options: {

Operations.addSettings(new EntityOperationSettings(EmailMessageOperation.CreateEmailFromTemplate, {
onClick: (ctx) => {

var promise: Promise<string | undefined> = ctx.entity.model ? API.getConstructorType(ctx.entity.model) : Promise.resolve(undefined);
promise

Finder.find({ queryName: ctx.entity.query!.key }).then(lite => {
if (!lite)
return;
Navigator.API.fetch(lite).then(entity =>
ctx.defaultClick(entity))
.done();
}).done();
return promise.then(ct => {
if (!ct || isTypeEntity(ct))
return Finder.find({ queryName: ctx.entity.query!.key }).then(lite => {
if (!lite)
return;
return Navigator.API.fetch(lite).then(entity => ctx.defaultClick(entity));
});
else {
var s = settings[ct];
var promise = (s?.createFromTemplate ? s.createFromTemplate(ctx.entity) : Constructor.constructPack(ct).then(a => a && Navigator.view(a)));
return promise.then(model => model && ctx.defaultClick(model));
}
});
}
}));

Expand Down
2 changes: 1 addition & 1 deletion Signum.React.Extensions/Tree/TreeClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export function start(options: { routes: JSX.Element[] }) {
new EntityOperationSettings(TreeOperation.Copy, {
onClick: ctx => copyModal(toLite(ctx.entity)).then(m => {
if (m) {
ctx.onConstructFromSuccess = pack => Operations.notifySuccess();
ctx.onConstructFromSuccess = pack => { Operations.notifySuccess(); return Promise.resolve(); };
ctx.defaultClick(m);
}
}),
Expand Down
6 changes: 3 additions & 3 deletions Signum.React.Extensions/Word/WordClient.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export function start(options: { routes: JSX.Element[], contextual: boolean, que
onClick: ctx => {

var promise: Promise<string | undefined> = ctx.entity.model ? API.getConstructorType(ctx.entity.model) : Promise.resolve(undefined);
promise
return promise
.then<Response | undefined>(ct => {
var template = toLite(ctx.entity);

Expand All @@ -77,8 +77,8 @@ export function start(options: { routes: JSX.Element[], contextual: boolean, que
if (!response)
return;

saveFile(response);
}).done();
return saveFile(response);
});
}
}));

Expand Down
31 changes: 27 additions & 4 deletions Signum.React.Extensions/Workflow/Case/CaseFrameModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ interface CaseFrameModalState {
show: boolean;
prefix?: string;
refreshCount: number;
executing?: boolean;
}

var modalCount = 0;
Expand Down Expand Up @@ -65,7 +66,7 @@ export default class CaseFrameModal extends React.Component<CaseFrameModalProps,
.done();
}

handleKeyDown(e: KeyboardEvent) {
handleKeyDown(e: KeyboardEvent) {
this.buttonBar && this.buttonBar.handleKeyDown(e);
}

Expand Down Expand Up @@ -176,7 +177,18 @@ export default class CaseFrameModal extends React.Component<CaseFrameModalProps,
},
refreshCount: this.state.refreshCount,
allowExchangeEntity: false,
prefix: this.prefix
prefix: this.prefix,
isExecuting: () => this.state.executing == true,
execute: action => {
if (this.state.executing)
return;

this.setState({ executing: true });
action()
.finally(() => this.setState({ executing: undefined }))
.done();
}

};

var activityPack = { entity: pack.activity, canExecute: pack.canExecuteActivity };
Expand Down Expand Up @@ -229,7 +241,18 @@ export default class CaseFrameModal extends React.Component<CaseFrameModalProps,
},
refreshCount: this.state.refreshCount,
allowExchangeEntity: false,
prefix: this.prefix
prefix: this.prefix,
isExecuting: () => this.state.executing == true,
execute: action => {
if (this.state.executing)
return;

this.setState({ executing: true });

action()
.finally(() => this.setState({ executing: undefined }))
.done();
}
};

var ti = this.getMainTypeInfo();
Expand All @@ -247,7 +270,7 @@ export default class CaseFrameModal extends React.Component<CaseFrameModalProps,
};

return (
<div className="sf-main-entity case-main-entity" data-main-entity={entityInfo(mainEntity)}>
<div className="sf-main-entity case-main-entity" style={this.state.executing == true ? { opacity: ".7" } : undefined} data-main-entity={entityInfo(mainEntity)}>
<div className="sf-button-widget-container">
{renderWidgets(wc)}
{this.entityComponent && !mainEntity.isNew && !pack.activity.doneBy ? <ButtonBar ref={bb => this.buttonBar = bb} frame={mainFrame} pack={mainFrame.pack} /> : <br />}
Expand Down
27 changes: 24 additions & 3 deletions Signum.React.Extensions/Workflow/Case/CaseFramePage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ interface CaseFramePageState {
pack?: WorkflowClient.CaseEntityPack;
getComponent?: (ctx: TypeContext<Entity>) => React.ReactElement<any>;
refreshCount: number;
executing?: boolean;
}

export default class CaseFramePage extends React.Component<CaseFramePageProps, CaseFramePageState> implements IHasCaseActivity {
Expand Down Expand Up @@ -161,7 +162,17 @@ export default class CaseFramePage extends React.Component<CaseFramePageProps, C
},
refreshCount: this.state.refreshCount,
allowExchangeEntity: false,
prefix: "caseFrame"
prefix: "caseFrame",
isExecuting: () => this.state.executing == true,
execute: action => {
if (this.state.executing)
return;

this.setState({ executing: true });
action()
.finally(() => { this.setState({ executing: undefined }) })
.done();
}
};


Expand Down Expand Up @@ -232,7 +243,17 @@ export default class CaseFramePage extends React.Component<CaseFramePageProps, C
},
refreshCount: this.state.refreshCount,
allowExchangeEntity: false,
prefix: "caseFrame"
prefix: "caseFrame",
isExecuting: () => this.state.executing == true,
execute: action => {
if (this.state.executing)
return;

this.setState({ executing: true });
action()
.finally(() => this.setState({ executing: undefined }))
.done();
}
};

var ti = this.getMainTypeInfo();
Expand All @@ -252,7 +273,7 @@ export default class CaseFramePage extends React.Component<CaseFramePageProps, C
};

return (
<div className="sf-main-entity case-main-entity" data-main-entity={entityInfo(mainEntity)}>
<div className="sf-main-entity case-main-entity" style={this.state.executing == true ? { opacity: ".7" } : undefined} data-main-entity={entityInfo(mainEntity)}>
<div className="sf-button-widget-container">
{renderWidgets(wc)}
{this.entityComponent && !mainEntity.isNew && !pack.activity.doneBy ? <ButtonBar ref={a => this.buttonBar = a} frame={mainFrame} pack={mainFrame.pack} /> : <br />}
Expand Down
Loading

2 comments on commit 002bcc9

@olmobrutall
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Operation executing

Thanks to @mapacheL, this change solves one recurrent problem with Operation buttons: The UI is fully active while executing an operation.

Particularly I never liked to show a full-screen loading spinner that prevents UI interactions while waiting for the server, but this led to three potential problems:

  • Executing an operation twice, typically producing a ConcurrencyException for saved entities, or either a UniqueKeyException or a duplicated entity for new entities.
  • Loosing any change made in the UI after the button is clicked but the answer has not yet been received (only noticeable for slow operations)

This change prevents both errors: While the operation is executing all the other operation buttons will do a NO-OP while clicking (they are not disable for now) and the full entity UI gets opacity: .7 indicating that the entity as you see it will soon be replaced by the version coming from the server (typically just a short blink).

How to update

In order to change this behavior the UI needs to know when the operation has finished executing, so many methods are now returning Promise<void> instead of void:

  • EntityOperationSettings<T>.onClick
  • EntityOperationContext<T>.defaultClick
  • ContextualOperationSettings<T>.onClick
  • ContextualOperationContext<T>.defaultContextualClick
  • ...

There is a Signum.Upgrade, Upgrade_20211111_ReplaceDefaultExecute but it's definitely very optimistic only fixing like 10% of the operation overrides. You will need to fix most (all?) of them manually.

@MehdyKarimpour
Copy link
Contributor

@MehdyKarimpour MehdyKarimpour commented on 002bcc9 Nov 15, 2021 via email

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.