diff --git a/Signum.React.Extensions/Alerts/AlertsClient.tsx b/Signum.React.Extensions/Alerts/AlertsClient.tsx index c431356d54..4fcc27aea0 100644 --- a/Signum.React.Extensions/Alerts/AlertsClient.tsx +++ b/Signum.React.Extensions/Alerts/AlertsClient.tsx @@ -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) => { diff --git a/Signum.React.Extensions/Dynamic/DynamicTypeClient.tsx b/Signum.React.Extensions/Dynamic/DynamicTypeClient.tsx index 027e151e94..e8cf2a6804 100644 --- a/Signum.React.Extensions/Dynamic/DynamicTypeClient.tsx +++ b/Signum.React.Extensions/Dynamic/DynamicTypeClient.tsx @@ -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 => [], })); diff --git a/Signum.React.Extensions/Dynamic/DynamicViewClient.tsx b/Signum.React.Extensions/Dynamic/DynamicViewClient.tsx index 16493c532c..10bd236c75 100644 --- a/Signum.React.Extensions/Dynamic/DynamicViewClient.tsx +++ b/Signum.React.Extensions/Dynamic/DynamicViewClient.tsx @@ -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()); diff --git a/Signum.React.Extensions/MachineLearning/PredictorClient.tsx b/Signum.React.Extensions/MachineLearning/PredictorClient.tsx index da66b980c0..ca0724f152 100644 --- a/Signum.React.Extensions/MachineLearning/PredictorClient.tsx +++ b/Signum.React.Extensions/MachineLearning/PredictorClient.tsx @@ -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(); - } + } })); diff --git a/Signum.React.Extensions/Mailing/MailingClient.tsx b/Signum.React.Extensions/Mailing/MailingClient.tsx index ceb2d3218a..0c82eef96b 100644 --- a/Signum.React.Extensions/Mailing/MailingClient.tsx +++ b/Signum.React.Extensions/Mailing/MailingClient.tsx @@ -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' @@ -61,17 +62,20 @@ export function start(options: { Operations.addSettings(new EntityOperationSettings(EmailMessageOperation.CreateEmailFromTemplate, { onClick: (ctx) => { - var promise: Promise = 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)); + } + }); } })); diff --git a/Signum.React.Extensions/Tree/TreeClient.tsx b/Signum.React.Extensions/Tree/TreeClient.tsx index d03e23be46..72821031eb 100644 --- a/Signum.React.Extensions/Tree/TreeClient.tsx +++ b/Signum.React.Extensions/Tree/TreeClient.tsx @@ -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); } }), diff --git a/Signum.React.Extensions/Word/WordClient.tsx b/Signum.React.Extensions/Word/WordClient.tsx index a467d7ef3d..77be18c049 100644 --- a/Signum.React.Extensions/Word/WordClient.tsx +++ b/Signum.React.Extensions/Word/WordClient.tsx @@ -60,7 +60,7 @@ export function start(options: { routes: JSX.Element[], contextual: boolean, que onClick: ctx => { var promise: Promise = ctx.entity.model ? API.getConstructorType(ctx.entity.model) : Promise.resolve(undefined); - promise + return promise .then(ct => { var template = toLite(ctx.entity); @@ -77,8 +77,8 @@ export function start(options: { routes: JSX.Element[], contextual: boolean, que if (!response) return; - saveFile(response); - }).done(); + return saveFile(response); + }); } })); diff --git a/Signum.React.Extensions/Workflow/Case/CaseFrameModal.tsx b/Signum.React.Extensions/Workflow/Case/CaseFrameModal.tsx index 2b264e897c..8c6eab7b63 100644 --- a/Signum.React.Extensions/Workflow/Case/CaseFrameModal.tsx +++ b/Signum.React.Extensions/Workflow/Case/CaseFrameModal.tsx @@ -38,6 +38,7 @@ interface CaseFrameModalState { show: boolean; prefix?: string; refreshCount: number; + executing?: boolean; } var modalCount = 0; @@ -65,7 +66,7 @@ export default class CaseFrameModal extends React.Component 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 }; @@ -229,7 +241,18 @@ export default class CaseFrameModal extends React.Component 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(); @@ -247,7 +270,7 @@ export default class CaseFrameModal extends React.Component +
{renderWidgets(wc)} {this.entityComponent && !mainEntity.isNew && !pack.activity.doneBy ? this.buttonBar = bb} frame={mainFrame} pack={mainFrame.pack} /> :
} diff --git a/Signum.React.Extensions/Workflow/Case/CaseFramePage.tsx b/Signum.React.Extensions/Workflow/Case/CaseFramePage.tsx index f527a1322f..f29803d871 100644 --- a/Signum.React.Extensions/Workflow/Case/CaseFramePage.tsx +++ b/Signum.React.Extensions/Workflow/Case/CaseFramePage.tsx @@ -29,6 +29,7 @@ interface CaseFramePageState { pack?: WorkflowClient.CaseEntityPack; getComponent?: (ctx: TypeContext) => React.ReactElement; refreshCount: number; + executing?: boolean; } export default class CaseFramePage extends React.Component implements IHasCaseActivity { @@ -161,7 +162,17 @@ export default class CaseFramePage extends React.Component this.state.executing == true, + execute: action => { + if (this.state.executing) + return; + + this.setState({ executing: true }); + action() + .finally(() => { this.setState({ executing: undefined }) }) + .done(); + } }; @@ -232,7 +243,17 @@ export default class CaseFramePage extends React.Component 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(); @@ -252,7 +273,7 @@ export default class CaseFramePage extends React.Component +
{renderWidgets(wc)} {this.entityComponent && !mainEntity.isNew && !pack.activity.doneBy ? this.buttonBar = a} frame={mainFrame} pack={mainFrame.pack} /> :
} diff --git a/Signum.React.Extensions/Workflow/WorkflowClient.tsx b/Signum.React.Extensions/Workflow/WorkflowClient.tsx index 28665b00a6..01b8b9a6ea 100644 --- a/Signum.React.Extensions/Workflow/WorkflowClient.tsx +++ b/Signum.React.Extensions/Workflow/WorkflowClient.tsx @@ -201,19 +201,18 @@ export function start(options: { routes: JSX.Element[], overrideCaseActivityMixi eoc.onExecuteSuccess = pack => { Operations.notifySuccess(); eoc.frame.onClose(pack); + return Promise.resolve(); } - getWorkflowJumpSelector(toLite(eoc.entity.workflowActivity as WorkflowActivityEntity)) - .then(dest => dest && eoc.defaultClick(dest)) - .done(); + return getWorkflowJumpSelector(toLite(eoc.entity.workflowActivity as WorkflowActivityEntity)) + .then(dest => dest && eoc.defaultClick(dest)); }), contextual: { isVisible: ctx => true, - onClick: coc => { + onClick: coc => Navigator.API.fetch(coc.context.lites[0]) .then(ca => getWorkflowJumpSelector(toLite(ca.workflowActivity as WorkflowActivityEntity))) .then(dest => dest && coc.defaultContextualClick(dest)) - .done() - } + } })); Operations.addSettings(new EntityOperationSettings(CaseActivityOperation.FreeJump, { @@ -222,22 +221,19 @@ export function start(options: { routes: JSX.Element[], overrideCaseActivityMixi iconColor: "#800080", hideOnCanExecute: true, onClick: eoc => executeCaseActivity(eoc, eoc => { - eoc.onExecuteSuccess = pack => { + eoc.onExecuteSuccess = async pack => { Operations.notifySuccess(); eoc.frame.onClose(pack); } - getWorkflowFreeJump(eoc.entity.case.workflow) - .then(dest => dest && eoc.defaultClick(dest)) - .done(); + return getWorkflowFreeJump(eoc.entity.case.workflow) + .then(dest => dest && eoc.defaultClick(dest)); }), contextual: { isVisible: ctx => true, - onClick: coc => { + onClick: coc => Navigator.API.fetch(coc.context.lites[0]) .then(ca => getWorkflowFreeJump(ca.case.workflow)) .then(dest => dest && coc.defaultContextualClick(dest)) - .done() - } } })); Operations.addSettings(new EntityOperationSettings(CaseActivityOperation.Timer, { isVisible: ctx => false })); @@ -325,14 +321,14 @@ export function start(options: { routes: JSX.Element[], overrideCaseActivityMixi contextualFromMany: { icon: "heartbeat", iconColor: "red" }, })); Operations.addSettings(new EntityOperationSettings(WorkflowOperation.Deactivate, { - onClick: eoc => chooseWorkflowExpirationDate([toLite(eoc.entity)]).then(val => val && eoc.defaultClick(val)).done(), + onClick: eoc => chooseWorkflowExpirationDate([toLite(eoc.entity)]).then(val => !val ? undefined : eoc.defaultClick(val)), contextual: { - onClick: coc => chooseWorkflowExpirationDate(coc.context.lites).then(val => val && coc.defaultContextualClick(val)).done(), + onClick: coc => chooseWorkflowExpirationDate(coc.context.lites).then(val => !val ? undefined : coc.defaultContextualClick(val)), icon: ["far", "heart"], iconColor: "gray" }, contextualFromMany: { - onClick: coc => chooseWorkflowExpirationDate(coc.context.lites).then(val => val && coc.defaultContextualClick(val)).done(), + onClick: coc => chooseWorkflowExpirationDate(coc.context.lites).then(val => !val ? undefined : coc.defaultContextualClick(val)), icon: ["far", "heart"], iconColor: "gray" }, @@ -509,22 +505,22 @@ function hide(type: Type) { Navigator.addSettings(new EntitySettings(type, undefined, { isViewable: "Never", isCreable: "Never" })); } -export function executeCaseActivity(eoc: Operations.EntityOperationContext, defaultOnClick: (eoc: Operations.EntityOperationContext) => void) { +export function executeCaseActivity(eoc: Operations.EntityOperationContext, defaultOnClick: (eoc: Operations.EntityOperationContext) => Promise) : Promise { const op = customOnClicks[eoc.operationInfo.key]; const onClick = op && op[eoc.entity.case.mainEntity.Type]; if (onClick) - onClick(eoc); + return onClick(eoc); else - defaultOnClick(eoc); + return defaultOnClick(eoc); } -export function executeWorkflowSave(eoc: Operations.EntityOperationContext) { +export function executeWorkflowSave(eoc: Operations.EntityOperationContext) : Promise { - function saveAndSetErrors(entity: WorkflowEntity, model: WorkflowModel, replacementModel: WorkflowReplacementModel | undefined) { - API.saveWorkflow(entity, model, replacementModel) + function saveAndSetErrors(entity: WorkflowEntity, model: WorkflowModel, replacementModel: WorkflowReplacementModel | undefined): Promise { + return API.saveWorkflow(entity, model, replacementModel) .then(packWithIssues => { eoc.frame.onReload(packWithIssues.entityPack); wf.setIssues(packWithIssues.issues); @@ -539,12 +535,11 @@ export function executeWorkflowSave(eoc: Operations.EntityOperationContext { var wfModel = WorkflowModel.New({ diagramXml: xml, @@ -558,18 +553,18 @@ export function executeWorkflowSave(eoc: Operations.EntityOperationContext (undefined) : API.previewChanges(toLite(eoc.entity), wfModel); - promise.then(repoModel => { + return promise.then(repoModel => { if (!repoModel || repoModel.replacements.length == 0) - saveAndSetErrors(eoc.entity, wfModel, undefined); + return saveAndSetErrors(eoc.entity, wfModel, undefined); else - Navigator.view(repoModel).then(replacementModel => { + return Navigator.view(repoModel).then(replacementModel => { if (!replacementModel) return; - saveAndSetErrors(eoc.entity, wfModel, replacementModel); - }).done(); - }).done(); - }).done(); + return saveAndSetErrors(eoc.entity, wfModel, replacementModel); + }); + }); + }); } @@ -593,16 +588,15 @@ function getWorkflowFreeJump(workflow: WorkflowEntity): PromiseFreeJump is an unrestricted but dangerous operation! If you don't know what you're doing... don't do it! }); } -export function executeAndClose(eoc: Operations.EntityOperationContext) { +export function executeAndClose(eoc: Operations.EntityOperationContext) : Promise { - confirmInNecessary(eoc).then(conf => { + return confirmInNecessary(eoc).then(conf => { if (!conf) return; - Operations.API.executeEntity(eoc.entity, eoc.operationInfo.key) + return Operations.API.executeEntity(eoc.entity, eoc.operationInfo.key) .then(pack => { eoc.frame.onClose(); return Operations.notifySuccess(); }) - .catch(ifError(ValidationError, e => eoc.frame.setError(e.modelState, "entity"))) - .done(); + .catch(ifError(ValidationError, e => eoc.frame.setError(e.modelState, "entity"))); }); } @@ -657,9 +651,9 @@ export function toEntityPackWorkflow(entityOrEntityPack: Lite) => void } } = {}; +export const customOnClicks: { [operationKey: string]: { [typeName: string]: (ctx: EntityOperationContext) => Promise } } = {}; -export function registerOnClick(type: Type, operationKey: ExecuteSymbol, action: (ctx: EntityOperationContext) => void) { +export function registerOnClick(type: Type, operationKey: ExecuteSymbol, action: (ctx: EntityOperationContext) => Promise) { var op = customOnClicks[operationKey.key] || (customOnClicks[operationKey.key] = {}); op[type.typeName] = action; } diff --git a/Signum.React/Scripts/Frames/ButtonBar.tsx b/Signum.React/Scripts/Frames/ButtonBar.tsx index 1ee7db18f0..31caad2107 100644 --- a/Signum.React/Scripts/Frames/ButtonBar.tsx +++ b/Signum.React/Scripts/Frames/ButtonBar.tsx @@ -25,7 +25,7 @@ export const ButtonBar = React.forwardRef(function ButtonBar(p: ButtonBarProps, var shortcuts = buttons.filter(a => a!.shortcut != null).map(a => a!.shortcut!); - function handleKeyDown(e: KeyboardEvent) { + function handleKeyDown(e: KeyboardEvent) { var s = shortcuts; if (s != null) { for (var i = 0; i < s.length; i++) { diff --git a/Signum.React/Scripts/Frames/FrameModal.tsx b/Signum.React/Scripts/Frames/FrameModal.tsx index 00a4d3eef9..d71ff6423c 100644 --- a/Signum.React/Scripts/Frames/FrameModal.tsx +++ b/Signum.React/Scripts/Frames/FrameModal.tsx @@ -48,6 +48,7 @@ interface PackAndComponent { lastEntity: string; refreshCount: number; getComponent: (ctx: TypeContext) => React.ReactElement; + executing?: boolean; } export const FrameModal = React.forwardRef(function FrameModal(p: FrameModalProps, ref: React.Ref) { @@ -186,8 +187,9 @@ export const FrameModal = React.forwardRef(function FrameModal(p: FrameModalProp if (result instanceof EntityOperationContext) { result.onExecuteSuccess = pack => { - notifySuccess(); - frameRef.current!.onClose(pack); + notifySuccess(); + frameRef.current!.onClose(pack); + return Promise.resolve(); }; result.defaultClick(); @@ -262,6 +264,20 @@ export const FrameModal = React.forwardRef(function FrameModal(p: FrameModalProp createNew: p.createNew, allowExchangeEntity: p.buttons == "close" && (p.allowExchangeEntity ?? true), prefix: prefix, + isExecuting: () => pc.executing == true, + execute: action => { + if (pc.executing) + return; + + pc.executing = true; + forceUpdate(); + action() + .finally(() => { + pc.executing = undefined; + forceUpdate(); + }) + .done(); + } }; frameRef.current = frame; @@ -280,7 +296,7 @@ export const FrameModal = React.forwardRef(function FrameModal(p: FrameModalProp const wc: WidgetContext = { ctx: ctx, frame: frame }; return ( -
+
{renderWidgets(wc)} diff --git a/Signum.React/Scripts/Frames/FramePage.tsx b/Signum.React/Scripts/Frames/FramePage.tsx index 8cbd95b1bd..f097216dec 100644 --- a/Signum.React/Scripts/Frames/FramePage.tsx +++ b/Signum.React/Scripts/Frames/FramePage.tsx @@ -31,7 +31,7 @@ interface FramePageState { getComponent: (ctx: TypeContext) => React.ReactElement; refreshCount: number; createNew?: () => Promise | undefined>; - avoidPrompt?: boolean; + executing?: boolean; } export default function FramePage(p: FramePageProps) { @@ -181,7 +181,16 @@ export default function FramePage(p: FramePageProps) { frameComponent: { forceUpdate, type: FramePage as any }, entityComponent: entityComponent.current, pack: state.pack, - avoidPrompt: () => state.avoidPrompt = true, + isExecuting: () => state.executing == true, + execute: action => { + if (state.executing) + return; + + state.executing = true; + forceUpdate(); + action() + .finally(() => { state.executing = undefined; forceUpdate(); }).done(); + }, onReload: (pack, reloadComponent, callback) => { var packEntity = (pack ?? state.pack) as EntityPack; @@ -252,18 +261,20 @@ export default function FramePage(p: FramePageProps) {
hasChanges(state) ? JavascriptMessage.loseCurrentChanges.niceToString() : true} /> {renderTitle()} -
- {renderWidgets(wc)} - {entityComponent.current && } -
- - -
- - {state.getComponent && {FunctionalAdapter.withRef(state.getComponent(ctx), c => setComponent(c))}} - +
+
+ {renderWidgets(wc)} + {entityComponent.current && }
- + + +
+ + {state.getComponent && {FunctionalAdapter.withRef(state.getComponent(ctx), c => setComponent(c))}} + +
+
+
); @@ -286,7 +297,7 @@ export default function FramePage(p: FramePageProps) { function hasChanges(state: FramePageState) { - if (state.avoidPrompt) + if (state.executing) return false; const entity = state.pack.entity; diff --git a/Signum.React/Scripts/Lines/RenderEntity.tsx b/Signum.React/Scripts/Lines/RenderEntity.tsx index d058f0eee2..9d77eae5f1 100644 --- a/Signum.React/Scripts/Lines/RenderEntity.tsx +++ b/Signum.React/Scripts/Lines/RenderEntity.tsx @@ -75,6 +75,8 @@ export function RenderEntity(p: RenderEntityProps) { refreshCount: (ctx.frame ? ctx.frame.refreshCount : 0), allowExchangeEntity: false, prefix: prefix, + isExecuting: () => false, + execute: () => { throw new Error("Not implemented Exception"); } }; function setComponent(c: React.Component | null) { diff --git a/Signum.React/Scripts/Operations.tsx b/Signum.React/Scripts/Operations.tsx index 88ff3aa2a0..9e486ac9fb 100644 --- a/Signum.React/Scripts/Operations.tsx +++ b/Signum.React/Scripts/Operations.tsx @@ -176,7 +176,7 @@ export class ContextualOperationSettings extends OperationSett showOnReadOnly?: boolean; confirmMessage?: (coc: ContextualOperationContext) => string | undefined | null; createMenuItems?: (eoc: ContextualOperationContext) => React.ReactElement[]; - onClick?: (coc: ContextualOperationContext) => void; + onClick?: (coc: ContextualOperationContext) => Promise; settersConfig?: (coc: ContextualOperationContext) => SettersConfig; color?: BsColor; icon?: IconProp; @@ -196,7 +196,7 @@ export interface ContextualOperationOptions { hideOnCanExecute?: boolean; showOnReadOnly?: boolean; confirmMessage?: (coc: ContextualOperationContext) => string | undefined | null; - onClick?: (coc: ContextualOperationContext) => void; + onClick?: (coc: ContextualOperationContext) => Promise; createMenuItems?: (eoc: ContextualOperationContext) => React.ReactElement[]; settersConfig?: (coc: ContextualOperationContext) => SettersConfig; color?: BsColor; @@ -219,8 +219,8 @@ export class ContextualOperationContext { onConstructFromSuccess?: (pack: EntityPack | undefined) => void; - defaultContextualClick(...args: any[]) { - defaultContextualClick(this, ...args); + defaultContextualClick(...args: any[]): Promise { + return defaultContextualClick(this, ...args); } constructor(operationInfo: OperationInfo, context: ContextualItemsContext) { @@ -335,9 +335,9 @@ export class EntityOperationContext { settings?: EntityOperationSettings; canExecute?: string; event?: React.MouseEvent; - onExecuteSuccess?: (pack: EntityPack) => void; - onConstructFromSuccess?: (pack: EntityPack | undefined) => void; - onDeleteSuccess?: () => void; + onExecuteSuccess?: (pack: EntityPack) => Promise; + onConstructFromSuccess?: (pack: EntityPack | undefined) => Promise; + onDeleteSuccess?: () => Promise; color?: BsColor; icon?: IconProp; @@ -401,17 +401,16 @@ export class EntityOperationContext { } defaultClick(...args: any[]) { - defaultOnClick(this, ...args); + return defaultOnClick(this, ...args); } click() { - if (this.frame.avoidPrompt) //othwersie FrontPage will prompt then executing and navigating - this.frame.avoidPrompt(); - - if (this.settings && this.settings.onClick) - this.settings.onClick(this); - else - defaultOnClick(this); + this.frame.execute(() => { + if (this.settings && this.settings.onClick) + return this.settings.onClick(this); + else + return defaultOnClick(this); + }); } textOrNiceName() { @@ -466,7 +465,7 @@ export class EntityOperationSettings extends OperationSettings isVisible?: (eoc: EntityOperationContext) => boolean; confirmMessage?: (eoc: EntityOperationContext) => string | undefined | null; overrideCanExecute?: (ctx: EntityOperationContext) => string | undefined | null; - onClick?: (eoc: EntityOperationContext) => void; + onClick?: (eoc: EntityOperationContext) => Promise; createButton?: (eoc: EntityOperationContext, group?: EntityOperationGroup) => ButtonBarElement[]; hideOnCanExecute?: boolean; showOnReadOnly?: boolean; @@ -498,7 +497,7 @@ export interface EntityOperationOptions { isVisible?: (eoc: EntityOperationContext) => boolean; overrideCanExecute?: (eoc: EntityOperationContext) => string | undefined | null; confirmMessage?: (eoc: EntityOperationContext) => string | undefined | null; - onClick?: (eoc: EntityOperationContext) => void; + onClick?: (eoc: EntityOperationContext) => Promise; createButton?: (eoc: EntityOperationContext, group?: EntityOperationGroup) => ButtonBarElement[]; hideOnCanExecute?: boolean; showOnReadOnly?: boolean; diff --git a/Signum.React/Scripts/Operations/ContextualOperations.tsx b/Signum.React/Scripts/Operations/ContextualOperations.tsx index b0f362c978..cb82e9b777 100644 --- a/Signum.React/Scripts/Operations/ContextualOperations.tsx +++ b/Signum.React/Scripts/Operations/ContextualOperations.tsx @@ -200,7 +200,7 @@ function getConfirmMessage(coc: ContextualOperationContext) { export interface OperationMenuItemProps { coc: ContextualOperationContext; - onOperationClick?: (coc: ContextualOperationContext) => void; + onOperationClick?: (coc: ContextualOperationContext) => Promise; onClick?: (me: React.MouseEvent) => void; /*used to hide contextual menu*/ extraButtons?: React.ReactNode; children?: React.ReactNode; @@ -227,9 +227,9 @@ export function OperationMenuItem({ coc, onOperationClick, onClick, extraButtons const handleOnClick = (me: React.MouseEvent) => { coc.event = me; - operationClickOrDefault(coc); - if (onClick) - onClick(me); + operationClickOrDefault(coc) + .finally(() => onClick?.(me)) + .done(); } const item = ( @@ -276,69 +276,57 @@ OperationMenuItem.simplifyName = (niceName: string) => { } -export function defaultContextualClick(coc: ContextualOperationContext, ...args: any[]) { +export function defaultContextualClick(coc: ContextualOperationContext, ...args: any[]) : Promise { coc.event!.persist(); - confirmInNecessary(coc).then(conf => { + return confirmInNecessary(coc).then(conf => { if (!conf) return; switch (coc.operationInfo.operationType) { case "ConstructorFromMany": { - - API.constructFromMany(coc.context.lites, coc.operationInfo.key, ...args) + return API.constructFromMany(coc.context.lites, coc.operationInfo.key, ...args) .then(coc.onConstructFromSuccess ?? (pack => { notifySuccess(); Navigator.createNavigateOrTab(pack, coc.event!) .then(() => coc.context.markRows({})) .done(); - })) - .done(); - - break; + })); } case "ConstructorFrom": if (coc.context.lites.length == 1) { - API.constructFromLite(coc.context.lites[0], coc.operationInfo.key, ...args) + return API.constructFromLite(coc.context.lites[0], coc.operationInfo.key, ...args) .then(coc.onConstructFromSuccess ?? (pack => { notifySuccess(); - Navigator.createNavigateOrTab(pack, coc.event!) + return Navigator.createNavigateOrTab(pack, coc.event!) .then(() => coc.context.markRows({})) - .done(); - })) - .done(); + })); } else { - getSetters(coc) + return getSetters(coc) .then(setters => setters && API.constructFromMultiple(coc.context.lites, coc.operationInfo.key, setters, ...args) .then(coc.onContextualSuccess ?? (report => { notifySuccess(); coc.context.markRows(report.errors); - }))) - .done(); + }))); } - break; case "Execute": - getSetters(coc) + return getSetters(coc) .then(setters => setters && API.executeMultiple(coc.context.lites, coc.operationInfo.key, setters, ...args) .then(coc.onContextualSuccess ?? (report => { notifySuccess(); coc.context.markRows(report.errors); - }))) - .done(); - break; + }))); case "Delete": - getSetters(coc) + return getSetters(coc) .then(setters => setters && API.deleteMultiple(coc.context.lites, coc.operationInfo.key, setters, ...args) .then(coc.onContextualSuccess ?? (report => { notifySuccess(); coc.context.markRows(report.errors); - }))) - .done(); - break; + }))); } - }).done(); + }); function getSetters(coc: ContextualOperationContext): Promise { diff --git a/Signum.React/Scripts/Operations/EntityOperations.tsx b/Signum.React/Scripts/Operations/EntityOperations.tsx index 92f8a5bc31..5f57a4cad9 100644 --- a/Signum.React/Scripts/Operations/EntityOperations.tsx +++ b/Signum.React/Scripts/Operations/EntityOperations.tsx @@ -20,7 +20,7 @@ import { FunctionalAdapter } from "../Modals"; import { getTypeNiceName } from "../Finder"; -export function getEntityOperationButtons(ctx: ButtonsContext): Array | undefined { +export function getEntityOperationButtons(ctx: ButtonsContext): Array | undefined { const ti = tryGetTypeInfo(ctx.pack.entity.Type); if (ti == undefined) @@ -51,16 +51,16 @@ export function getEntityOperationButtons(ctx: ButtonsContext): Array eoc.createButton(group)).orderBy(a => a.order); - + return [{ order: group.order != undefined ? group.order : 100, shortcut: e => groupButtons.some(bbe => bbe.shortcut != null && bbe.shortcut(e)), button: React.cloneElement( - , - undefined, - ...groupButtons.map(bbe => bbe.button) - ) + , + undefined, + ...groupButtons.map(bbe => bbe.button) + ) } as ButtonBarElement]; } }); @@ -69,7 +69,7 @@ export function getEntityOperationButtons(ctx: ButtonsContext): Array(eoc: EntityOperationContext, inDropdown?: boolean): AlternativeOperationSetting { - + return ({ name: "andClose", text: () => OperationMessage._0AndClose.niceToString(eoc.textOrNiceName()), @@ -81,6 +81,7 @@ export function andClose(eoc: EntityOperationContext, inDro eoc.onExecuteSuccess = pack => { notifySuccess(); eoc.frame.onClose(pack); + return Promise.resolve(undefined); }; eoc.click(); } @@ -100,16 +101,15 @@ export function andNew(eoc: EntityOperationContext, inDropd eoc.onExecuteSuccess = pack => { notifySuccess(); - (eoc.frame.createNew!(pack) ?? Promise.resolve(undefined)) - .then(newPack => newPack && eoc.frame.onReload(newPack, reloadComponent)) - .done(); + return (eoc.frame.createNew!(pack) ?? Promise.resolve(undefined)) + .then(newPack => newPack && eoc.frame.onReload(newPack, reloadComponent)); }; eoc.defaultClick(); } }); } -type OutlineBsColor = +type OutlineBsColor = | 'outline-primary' | 'outline-secondary' | 'outline-success' @@ -172,7 +172,7 @@ export function OperationButton({ group, onOperationClick, canExecute, eoc: eocO {alternatives?.map(a => renderAlternative(a))} ); - } + } if (outline == null) outline = eoc.outline; @@ -180,7 +180,7 @@ export function OperationButton({ group, onOperationClick, canExecute, eoc: eocO if (color == null) color = eoc.color; - var button =