From c30563bbb6c85e91864696b45a0253ceedb59674 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Wed, 4 Oct 2023 15:10:48 +0200 Subject: [PATCH 01/32] Add new record button appears in combo box if NewRecordScreenBinding is defined the button functionality is not implemented --- .../ModelXmlBuilders/ComboBoxBuilder.cs | 12 ++- .../SchemaItems/AbstractDataLookup.cs | 3 +- .../SchemaItems/NewRecordScreenBinding.cs | 82 +++++++++++++++++++ .../DataLookupService.cs | 32 +++++++- .../ScreenArea/TableView/TableViewEditor.tsx | 3 + frontend-html/src/model/entities/Lookup.ts | 21 +++++ .../src/model/entities/types/ILookup.ts | 2 + .../Editors/DropdownEditor/DropdownEditor.tsx | 5 ++ .../DropdownEditor/DropdownEditorBehavior.tsx | 15 +++- .../DropdownEditor/DropdownEditorBody.tsx | 34 ++++++-- .../DropdownEditor/DropdownEditorControl.tsx | 3 + .../src/xmlInterpreters/screenXml.ts | 19 ++++- .../Widgets/TagInputSource_Label_GetId.origam | 16 +++- 13 files changed, 234 insertions(+), 13 deletions(-) create mode 100644 backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs diff --git a/backend/Origam.OrigamEngine/ModelXmlBuilders/ComboBoxBuilder.cs b/backend/Origam.OrigamEngine/ModelXmlBuilders/ComboBoxBuilder.cs index dc8fdccb22..eaf3195f94 100644 --- a/backend/Origam.OrigamEngine/ModelXmlBuilders/ComboBoxBuilder.cs +++ b/backend/Origam.OrigamEngine/ModelXmlBuilders/ComboBoxBuilder.cs @@ -22,7 +22,7 @@ using System; using System.Xml; using System.Data; - +using System.Linq; using Origam.Schema; using Origam.Workbench.Services; using Origam.Schema.EntityModel; @@ -161,6 +161,16 @@ public static void BuildCommonDropdown(XmlElement propertyElement, Guid lookupId useCache = false; } } + + var dataLookupService = ServiceManager.Services.GetService(); + NewRecordScreenBinding newRecordScreenBinding = dataLookupService.GetNewRecordScreenBinding(lookup); + if (newRecordScreenBinding != null) + { + XmlElement newRecordElement = propertyElement.OwnerDocument.CreateElement("NewRecordScreen"); + newRecordElement.SetAttribute("Width", XmlConvert.ToString(newRecordScreenBinding.DialogWidth)); + newRecordElement.SetAttribute("Height", XmlConvert.ToString(newRecordScreenBinding.DialogHeight)); + propertyElement.AppendChild(newRecordElement); + } propertyElement.SetAttribute("Cached", XmlConvert.ToString(useCache)); } diff --git a/backend/Origam.Schema.LookupModel/SchemaItems/AbstractDataLookup.cs b/backend/Origam.Schema.LookupModel/SchemaItems/AbstractDataLookup.cs index 4e61e19f45..cf09101f86 100644 --- a/backend/Origam.Schema.LookupModel/SchemaItems/AbstractDataLookup.cs +++ b/backend/Origam.Schema.LookupModel/SchemaItems/AbstractDataLookup.cs @@ -353,7 +353,8 @@ public DataStructureSortSet ListSortSet public override Type[] NewItemTypes => new[] { typeof(DataLookupMenuBinding), - typeof(DataServiceDataTooltip) + typeof(DataServiceDataTooltip), + typeof(NewRecordScreenBinding) }; public override T NewItem( diff --git a/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs b/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs new file mode 100644 index 0000000000..aadc322189 --- /dev/null +++ b/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs @@ -0,0 +1,82 @@ +using System; +using System.ComponentModel; +using System.Xml.Serialization; +using Origam.DA.Common; +using Origam.DA.ObjectPersistence; +using Origam.Schema.MenuModel; + +namespace Origam.Schema.LookupModel; + +[SchemaItemDescription("New Record Screen Binding", "icon_menu-binding.png")] +[HelpTopic("")] +[XmlModelRoot(CategoryConst)] +[DefaultProperty("MenuItem")] +[ClassMetaVersion("6.0.0")] +public class NewRecordScreenBinding : AbstractSchemaItem, IAuthorizationContextContainer, IComparable +{ + public const string CategoryConst = "NewRecordScreenBinding"; + + public NewRecordScreenBinding() { } + + public NewRecordScreenBinding(Guid schemaExtensionId) : base(schemaExtensionId) { } + + public NewRecordScreenBinding(Key primaryKey) : base(primaryKey) {} + + #region Overriden AbstractSchemaItem Members + public override string ItemType => CategoryConst; + + public override void GetExtraDependencies(System.Collections.ArrayList dependencies) + { + dependencies.Add(MenuItem); + + AbstractSchemaItem menu = MenuItem; + while(menu.ParentItem != null) + { + menu = menu.ParentItem; + dependencies.Add(menu); + } + base.GetExtraDependencies (dependencies); + } + + public override SchemaItemCollection ChildItems => new(); + + public override bool CanMove(UI.IBrowserNode2 newNode) + { + return newNode is AbstractDataLookup; + } + + #endregion + + #region Properties + public Guid MenuItemId; + + [Category("Menu Reference")] + [TypeConverter(typeof(MenuItemConverter))] + [NotNullModelElementRule] + [NotNullMenuRecordEditMethod] + [XmlReference("menuItem", "MenuItemId")] + public AbstractMenuItem MenuItem + { + get => PersistenceProvider.RetrieveInstance(MenuItemId); + set => MenuItemId = value == null + ? Guid.Empty + : (Guid)value.PrimaryKey["Id"]; + } + + [Category("Security")] + [NotNullModelElementRule] + [XmlAttribute("roles")] + public string Roles { get; set; } + + public int DialogWidth { get; set; } + public int DialogHeight { get; set; } + + #endregion + + #region IAuthorizationContextContainer Members + + [Browsable(false)] + public string AuthorizationContext => Roles; + + #endregion +} \ No newline at end of file diff --git a/backend/Origam.Workbench.Services/DataLookupService.cs b/backend/Origam.Workbench.Services/DataLookupService.cs index 9db66ab982..64ff26328c 100644 --- a/backend/Origam.Workbench.Services/DataLookupService.cs +++ b/backend/Origam.Workbench.Services/DataLookupService.cs @@ -31,6 +31,7 @@ using Origam.Schema.LookupModel; using Origam.Schema.MenuModel; using System.Collections.Generic; +using System.Linq; using Origam.DA.Service; using log4net; using Origam.Workbench.Services.CoreServices; @@ -602,7 +603,36 @@ public IMenuBindingResult GetMenuBinding(Guid lookupId, object value) } } - public DataLookupMenuBinding GetMenuBindingElement(AbstractDataLookup lookup, object value) + public NewRecordScreenBinding GetNewRecordScreenBinding(AbstractDataLookup lookup) + { + IParameterService param = + ServiceManager.Services.GetService(typeof(IParameterService)) as IParameterService; + IOrigamAuthorizationProvider authorizationProvider = SecurityManager.GetAuthorizationProvider(); + IPrincipal principal = SecurityManager.CurrentPrincipal; + + + var newRecordScreenBinding = lookup.ChildItems + .ToGeneric() + .OfType() + .FirstOrDefault(); + + if (newRecordScreenBinding == null) + { + return null; + } + + bool isAvailable = authorizationProvider.Authorize(principal, newRecordScreenBinding.AuthorizationContext) + && authorizationProvider.Authorize(principal, newRecordScreenBinding.MenuItem.AuthorizationContext) + && param.IsFeatureOn(newRecordScreenBinding.MenuItem.Features); + if(!isAvailable) + { + return null; + } + + return newRecordScreenBinding; + } + + public DataLookupMenuBinding GetMenuBindingElement(AbstractDataLookup lookup, object value) { IOrigamAuthorizationProvider authorizationProvider = SecurityManager.GetAuthorizationProvider(); IPrincipal principal = SecurityManager.CurrentPrincipal; diff --git a/frontend-html/src/gui/Workbench/ScreenArea/TableView/TableViewEditor.tsx b/frontend-html/src/gui/Workbench/ScreenArea/TableView/TableViewEditor.tsx index b169ed9e95..23a7af0e8f 100644 --- a/frontend-html/src/gui/Workbench/ScreenArea/TableView/TableViewEditor.tsx +++ b/frontend-html/src/gui/Workbench/ScreenArea/TableView/TableViewEditor.tsx @@ -47,6 +47,7 @@ import { getGridFocusManager } from "model/entities/GridFocusManager"; import { flashColor2htmlColor, htmlColor2FlashColor } from "@origam/utils"; import { resolveCellAlignment } from "gui/Workbench/ScreenArea/TableView/ResolveCellAlignment"; import S from "./TableViewEditor.module.scss"; +import { NewRecordScreen } from "model/entities/Lookup"; @inject(({tablePanelView}) => { const row = getSelectedRow(tablePanelView)!; @@ -207,6 +208,8 @@ export class TableViewEditor extends React.Component<{ gridFocusManager.activeEditor = undefined; gridFocusManager.editorBlur = undefined; }} + newRecordScreen={this.props.property?.lookup?.newRecordScreen} + onAddNewRecordClick={() => {console.log("Add new click")}} autoSort={this.props.property!.autoSort} onKeyDown={this.props.onEditorKeyDown} subscribeToFocusManager={(editor) => diff --git a/frontend-html/src/model/entities/Lookup.ts b/frontend-html/src/model/entities/Lookup.ts index 1291ccfd66..b54c153777 100644 --- a/frontend-html/src/model/entities/Lookup.ts +++ b/frontend-html/src/model/entities/Lookup.ts @@ -25,6 +25,26 @@ export enum IIdState { ERROR = "ERROR", } +export class NewRecordScreen { + private _width: number; + private _height: number; + constructor(args: { + width: number, + height: number} + ) { + this._width = args.width; + this._height = args.height; + } + + get width() { + return this._width; + } + + get height() { + return this._height; + } +} + export class Lookup implements ILookup { constructor(data: ILookupData) { Object.assign(this, data); @@ -34,6 +54,7 @@ export class Lookup implements ILookup { $type_ILookup: 1 = 1; lookupId: string = ""; + newRecordScreen?: NewRecordScreen = null as any; dropDownShowUniqueValues: boolean = false; identifier: string = ""; identifierIndex: number = 0; diff --git a/frontend-html/src/model/entities/types/ILookup.ts b/frontend-html/src/model/entities/types/ILookup.ts index 3ea5d38ce0..fc3a2d659c 100644 --- a/frontend-html/src/model/entities/types/ILookup.ts +++ b/frontend-html/src/model/entities/types/ILookup.ts @@ -18,6 +18,7 @@ along with ORIGAM. If not, see . */ import { IDropDownColumn } from "./IDropDownColumn"; +import { NewRecordScreen } from "model/entities/Lookup"; export enum IDropDownType { EagerlyLoadedGrid = "EagerlyLoadedGrid", @@ -40,6 +41,7 @@ export interface ILookupData { searchByFirstColumnOnly: boolean; dropDownColumns: IDropDownColumn[]; dropDownParameters: IDropDownParameter[]; + newRecordScreen?: NewRecordScreen; } export interface ILookup extends ILookupData { diff --git a/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditor.tsx b/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditor.tsx index 633b8a453b..d344695c35 100644 --- a/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditor.tsx +++ b/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditor.tsx @@ -33,6 +33,7 @@ import { IFocusable } from "../../../model/entities/FormFocusManager"; import { IWorkbench } from "model/entities/types/IWorkbench"; import { isMobileLayoutActive } from "model/selectors/isMobileLayoutActive"; import { DropdownEditorSetup, DropdownEditorSetupFromXml } from "modules/Editors/DropdownEditor/DropdownEditorSetup"; +import { NewRecordScreen } from "model/entities/Lookup"; export interface IDropdownEditorContext { behavior: DropdownEditorBehavior; @@ -98,6 +99,8 @@ export function XmlBuildDropdownEditor(props: { onDoubleClick?: (event: any) => void; onClick?: (event: any) => void; onBlur?: () => void; + newRecordScreen? : NewRecordScreen; + onAddNewRecordClick?: () => void; subscribeToFocusManager?: (obj: IFocusable) => void; onKeyDown?(event: any): void; }) { @@ -140,6 +143,8 @@ export function XmlBuildDropdownEditor(props: { onKeyDown: props.onKeyDown, autoSort: props.autoSort, onTextOverflowChanged: props.onTextOverflowChanged, + newRecordScreen: props.newRecordScreen, + onAddNewRecordClick: props.onAddNewRecordClick }); const dropdownEditorSetup = DropdownEditorSetupFromXml( diff --git a/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBehavior.tsx b/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBehavior.tsx index 437b18d2b0..5f8a164824 100644 --- a/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBehavior.tsx +++ b/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBehavior.tsx @@ -29,12 +29,15 @@ import { compareStrings } from "../../../utils/string"; import { IDriverState } from "modules/Editors/DropdownEditor/Cells/IDriverState"; import { DropdownEditorSetup } from "modules/Editors/DropdownEditor/DropdownEditorSetup"; import { requestFocus } from "utils/focus"; +import { NewRecordScreen } from "model/entities/Lookup"; export const dropdownPageSize = 100; export interface IDropdownEditorBehavior extends IDriverState{ + onAddNewRecordClick?: () => void; scrollToRowIndex: number | undefined; willLoadPage: number; + hasNewScreenButton: boolean; handleScroll(args: { clientHeight: number; clientWidth: number; @@ -59,6 +62,8 @@ export interface IBehaviorData { onKeyDown?: (event: any) => void, autoSort?: boolean, onTextOverflowChanged?: (toolTip: string | null | undefined) => void, + newRecordScreen?: NewRecordScreen, + onAddNewRecordClick?: () => void; } export class DropdownEditorBehavior implements IDropdownEditorBehavior { @@ -76,6 +81,12 @@ export class DropdownEditorBehavior implements IDropdownEditorBehavior { private onKeyDown?: (event: any) => void; private autoSort?: boolean; private onTextOverflowChanged?: (toolTip: string | null | undefined) => void; + private newRecordScreen?: NewRecordScreen; + public onAddNewRecordClick?: () => void; + + get hasNewScreenButton(){ + return !!this.newRecordScreen; + } constructor(args: IBehaviorData) { this.api = args.api; @@ -91,6 +102,8 @@ export class DropdownEditorBehavior implements IDropdownEditorBehavior { this.onKeyDown = args.onKeyDown; this.autoSort = args.autoSort; this.onTextOverflowChanged = args.onTextOverflowChanged; + this.newRecordScreen = args.newRecordScreen + this.onAddNewRecordClick = args.onAddNewRecordClick } @observable isDropped = false; @@ -105,7 +118,7 @@ export class DropdownEditorBehavior implements IDropdownEditorBehavior { willLoadNextPage = true; @computed get isBodyDisplayed() { - return this.isDropped && this.dataTable.rowCount > 0; + return this.isDropped && (this.dataTable.rowCount > 0 || this.hasNewScreenButton); } @computed get chosenRowId() { diff --git a/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBody.tsx b/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBody.tsx index 7ede5ef6bd..fdc8a98fa7 100644 --- a/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBody.tsx +++ b/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBody.tsx @@ -18,7 +18,7 @@ along with ORIGAM. If not, see . */ import { observer, Observer } from "mobx-react"; -import React, { useContext, useEffect, useMemo, createRef } from "react"; +import React, { createRef, useContext, useEffect, useMemo } from "react"; import { GridCellProps, MultiGrid } from "react-virtualized"; import { CtxCell } from "./Cells/CellsCommon"; import S from "@origam/components/src/components/Dropdown/Dropdown.module.scss" @@ -82,9 +82,7 @@ export class DropdownEditorTable extends React.Component<{ refMultiGrid = createRef(); @observable scrollbarSize = { horiz: 0, vert: 0 }; - hasHeader: boolean; hoveredRowIndex= - 1; - columnCount = 0; readonly cellPadding = 20; readonly maxHeight = 150; disposer: any; @@ -94,7 +92,23 @@ export class DropdownEditorTable extends React.Component<{ } get rowCount(){ - return this.props.dataTable.rowCount + (this.hasHeader ? 1 : 0); + return this.showAddNewRecord + ? 1 // will show single row: "Add New Record" + : this.props.dataTable.rowCount + (this.hasHeader ? 1 : 0); + } + + get columnCount(){ + return this.showAddNewRecord + ? 1 + : this.props.drivers.driverCount; + } + + get hasHeader(){ + return this.columnCount > 1 + } + + get showAddNewRecord(){ + return this.props.beh.hasNewScreenButton && this.props.dataTable.rowCount === 0; } get height(){ @@ -110,8 +124,6 @@ export class DropdownEditorTable extends React.Component<{ constructor(props: any) { super(props); - this.columnCount = this.props.drivers.driverCount; - this.hasHeader = this.columnCount > 1; } handleScrollbarPresenceChange(args: { @@ -128,6 +140,16 @@ export class DropdownEditorTable extends React.Component<{ renderTableCell({columnIndex, key, parent, rowIndex, style}: GridCellProps) { const Prov = CtxCell.Provider as any; + if (this.showAddNewRecord) { + return( +
+ Add New Record +
); + } return ( + {beh.hasNewScreenButton + &&
+
+ }
n.name === "NewRecordScreen"); + if (childNodes.length === 0) { + return undefined; + } + if(childNodes.length > 1) { + throw new Error("More than one NewRecordScreenBinding node found in xml"); + } + const newRecordScreenNode = childNodes[0]; + return new NewRecordScreen( + { + width: parseInt(newRecordScreenNode.attributes.Width), + height: parseInt(newRecordScreenNode.attributes.Height) + }); +} + function parseProperty(node: any, idx: number): IProperty { const propertyObject = new Property({ xmlNode: node, @@ -163,6 +179,7 @@ function parseProperty(node: any, idx: number): IProperty { lookup: !node.attributes.LookupId ? undefined : new Lookup({ + newRecordScreen: getNewRecordScreen(node), dropDownShowUniqueValues: node.attributes.DropDownShowUniqueValues === "true", lookupId: node.attributes.LookupId, identifier: node.attributes.Identifier, diff --git a/model-tests/model/Widgets/DataLookup/Widgets/TagInputSource_Label_GetId.origam b/model-tests/model/Widgets/DataLookup/Widgets/TagInputSource_Label_GetId.origam index 4e2daa14ca..a3facc9787 100644 --- a/model-tests/model/Widgets/DataLookup/Widgets/TagInputSource_Label_GetId.origam +++ b/model-tests/model/Widgets/DataLookup/Widgets/TagInputSource_Label_GetId.origam @@ -1,5 +1,10 @@ - + + adl:valueValueMember="Id"> + + \ No newline at end of file From 3574f14087743ce93fb942762eb5b50024a68411 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Thu, 5 Oct 2023 10:20:23 +0200 Subject: [PATCH 02/32] Changed menu item in demo project --- .../DataLookup/Widgets/TagInputSource_Label_GetId.origam | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model-tests/model/Widgets/DataLookup/Widgets/TagInputSource_Label_GetId.origam b/model-tests/model/Widgets/DataLookup/Widgets/TagInputSource_Label_GetId.origam index a3facc9787..e6443f693a 100644 --- a/model-tests/model/Widgets/DataLookup/Widgets/TagInputSource_Label_GetId.origam +++ b/model-tests/model/Widgets/DataLookup/Widgets/TagInputSource_Label_GetId.origam @@ -24,7 +24,7 @@ From bd8d6e8ebe5fd6bbe6a71596b9f85f0179d49ff9 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Fri, 6 Oct 2023 09:58:12 +0200 Subject: [PATCH 03/32] MenuItemId sent to client --- .../ModelXmlBuilders/ComboBoxBuilder.cs | 1 + frontend-html/src/model/entities/Lookup.ts | 10 +++++++++- frontend-html/src/xmlInterpreters/screenXml.ts | 3 ++- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/backend/Origam.OrigamEngine/ModelXmlBuilders/ComboBoxBuilder.cs b/backend/Origam.OrigamEngine/ModelXmlBuilders/ComboBoxBuilder.cs index eaf3195f94..f4b0b5f6ab 100644 --- a/backend/Origam.OrigamEngine/ModelXmlBuilders/ComboBoxBuilder.cs +++ b/backend/Origam.OrigamEngine/ModelXmlBuilders/ComboBoxBuilder.cs @@ -169,6 +169,7 @@ public static void BuildCommonDropdown(XmlElement propertyElement, Guid lookupId XmlElement newRecordElement = propertyElement.OwnerDocument.CreateElement("NewRecordScreen"); newRecordElement.SetAttribute("Width", XmlConvert.ToString(newRecordScreenBinding.DialogWidth)); newRecordElement.SetAttribute("Height", XmlConvert.ToString(newRecordScreenBinding.DialogHeight)); + newRecordElement.SetAttribute("MenuItemId", XmlConvert.ToString(newRecordScreenBinding.MenuItemId)); propertyElement.AppendChild(newRecordElement); } diff --git a/frontend-html/src/model/entities/Lookup.ts b/frontend-html/src/model/entities/Lookup.ts index b54c153777..bad45c94c5 100644 --- a/frontend-html/src/model/entities/Lookup.ts +++ b/frontend-html/src/model/entities/Lookup.ts @@ -28,12 +28,16 @@ export enum IIdState { export class NewRecordScreen { private _width: number; private _height: number; + private _menuItemId: string; + constructor(args: { width: number, - height: number} + height: number, + menuItemId: string} ) { this._width = args.width; this._height = args.height; + this._menuItemId = args.menuItemId; } get width() { @@ -43,6 +47,10 @@ export class NewRecordScreen { get height() { return this._height; } + + get menuItemId() { + return this._menuItemId; + } } export class Lookup implements ILookup { diff --git a/frontend-html/src/xmlInterpreters/screenXml.ts b/frontend-html/src/xmlInterpreters/screenXml.ts index 3d0ec9a02b..ddf8992668 100644 --- a/frontend-html/src/xmlInterpreters/screenXml.ts +++ b/frontend-html/src/xmlInterpreters/screenXml.ts @@ -131,7 +131,8 @@ function getNewRecordScreen(node: any){ return new NewRecordScreen( { width: parseInt(newRecordScreenNode.attributes.Width), - height: parseInt(newRecordScreenNode.attributes.Height) + height: parseInt(newRecordScreenNode.attributes.Height), + menuItemId: newRecordScreenNode.attributes.MenuItemId }); } From 91427389e24021c11018e1f96d2a011d24682df0 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Fri, 13 Oct 2023 19:51:04 +0200 Subject: [PATCH 04/32] Adding a new record in combo box is partially implemented --- .../SchemaItems/NewRecordScreenBinding.cs | 5 +- .../Origam.Server/Common/SessionManager.cs | 9 ++- backend/Origam.Server/Common/UIRequest.cs | 1 + .../Session Stores/FormSessionStore.cs | 57 +++++++++++++++++++ .../Components/ScreenElements/DataView.tsx | 2 +- .../ScreenArea/TableView/TableViewEditor.tsx | 47 ++++++++++++++- .../src/model/entities/FormScreen.ts | 9 ++- .../FormScreenLifecycle.tsx | 23 ++++---- frontend-html/src/model/entities/OrigamAPI.ts | 1 + .../WorkbenchLifecycle/WorkbenchLifecycle.ts | 16 ++++-- .../src/model/entities/types/IApi.ts | 1 + .../entities/types/IFormScreenLifecycle.ts | 2 +- .../entities/types/IWorkbenchLifecycle.ts | 11 +++- .../Widgets/TagInputSource_Label_GetId.origam | 2 + 14 files changed, 163 insertions(+), 23 deletions(-) diff --git a/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs b/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs index aadc322189..5bd51e06f1 100644 --- a/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs +++ b/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs @@ -67,8 +67,11 @@ public AbstractMenuItem MenuItem [NotNullModelElementRule] [XmlAttribute("roles")] public string Roles { get; set; } - + + [XmlAttribute("dialogWidth")] public int DialogWidth { get; set; } + + [XmlAttribute("dialogHeight")] public int DialogHeight { get; set; } #endregion diff --git a/backend/Origam.Server/Common/SessionManager.cs b/backend/Origam.Server/Common/SessionManager.cs index 2d893e4878..176bfd71d2 100644 --- a/backend/Origam.Server/Common/SessionManager.cs +++ b/backend/Origam.Server/Common/SessionManager.cs @@ -257,7 +257,14 @@ public SessionStore CreateSessionStore(UIRequest request, IBasicUIService basicU menuItem = ps.SchemaProvider.RetrieveInstance(typeof(AbstractMenuItem), new ModelElementKey(new Guid(request.ObjectId))) as AbstractMenuItem; formMenuItem = menuItem as FormReferenceMenuItem; // FORM - ss = new FormSessionStore(basicUIService, request, menuItem.Name, analytics); + if (request.CreateNewRecord) + { + ss = new NewRecordSessionStore(basicUIService, request, menuItem.Name, analytics); + } + else + { + ss = new FormSessionStore(basicUIService, request, menuItem.Name, analytics); + } break; case UIRequestType.WorkflowReferenceMenuItem: diff --git a/backend/Origam.Server/Common/UIRequest.cs b/backend/Origam.Server/Common/UIRequest.cs index c7489690fb..2b642f3e97 100644 --- a/backend/Origam.Server/Common/UIRequest.cs +++ b/backend/Origam.Server/Common/UIRequest.cs @@ -63,6 +63,7 @@ public class UIRequest public string Icon { get; set; } public string ObjectId { get; set; } public bool IsSingleRecordEdit { get; set; } + public bool CreateNewRecord { get; set; } public bool RequestCurrentRecordId { get; set; } public int DialogWidth { get; set; } public int DialogHeight { get; set; } diff --git a/backend/Origam.Server/Session Stores/FormSessionStore.cs b/backend/Origam.Server/Session Stores/FormSessionStore.cs index 38d592724e..f7356a9e45 100644 --- a/backend/Origam.Server/Session Stores/FormSessionStore.cs +++ b/backend/Origam.Server/Session Stores/FormSessionStore.cs @@ -32,11 +32,68 @@ using core = Origam.Workbench.Services.CoreServices; using Origam.Schema; using System.Collections.Generic; +using System.Globalization; +using Origam.Schema.GuiModel; using Origam.Server; using Origam.Server.Session_Stores; namespace Origam.Server { + public class NewRecordSessionStore : FormSessionStore + { + public NewRecordSessionStore(IBasicUIService service, UIRequest request, string name, FormReferenceMenuItem menuItem, Analytics analytics) : base(service, request, name, menuItem, analytics) + { + } + + public NewRecordSessionStore(IBasicUIService service, UIRequest request, string name, Analytics analytics) : base(service, request, name, analytics) + { + } + + public override void Init() + { + IPersistenceService persistence = ServiceManager.Services.GetService(); + var menuItem = persistence.SchemaProvider.RetrieveInstance(typeof(AbstractMenuItem), new ModelElementKey(new Guid(Request.ObjectId))) as AbstractMenuItem; + FormReferenceMenuItem formMenuItem = menuItem as FormReferenceMenuItem; + var screen = persistence.SchemaProvider.RetrieveInstance(formMenuItem.ScreenId); + var dataStructure = persistence.SchemaProvider.RetrieveInstance(screen.DataSourceId); + var entity = dataStructure.Entities[0] as DataStructureEntity;//?.Entity as IDataEntity; + + var dataService = core.DataServiceFactory.GetDataService(); + var dataSet = dataService.GetEmptyDataSet( + entity.RootEntity.ParentItemId, CultureInfo.InvariantCulture); + var table = dataSet.Tables[entity.Name]; + var row = table.NewRow(); + + DatasetTools.ApplyPrimaryKey(row); + DatasetTools.UpdateOrigamSystemColumns( + row, true, SecurityManager.CurrentUserProfile().Id); + row.Table.NewRow(); + + // try + // { + core.DataService.Instance.StoreData( + dataStructureId: entity.RootEntity.ParentItemId, + data: row.Table.DataSet, + loadActualValuesAfterUpdate: false, + transactionId: null); + // } + // catch(DBConcurrencyException ex) + // { + // if(string.IsNullOrEmpty(ex.Message) + // && (ex.InnerException != null)) + // { + // return Conflict(ex.InnerException.Message); + // } + // return Conflict(ex.Message); + // } + + dataSet.Tables[entity.Name].Rows.Add(row); + + SetDataSource(dataSet); + // base.Init(); + } + } + public class FormSessionStore : SaveableSessionStore { private string _delayedLoadingParameterName; diff --git a/frontend-html/src/gui/Components/ScreenElements/DataView.tsx b/frontend-html/src/gui/Components/ScreenElements/DataView.tsx index 9d391f1ab6..9b530964e7 100644 --- a/frontend-html/src/gui/Components/ScreenElements/DataView.tsx +++ b/frontend-html/src/gui/Components/ScreenElements/DataView.tsx @@ -118,7 +118,7 @@ export class DataViewInner extends React.Component {
{isMobileLayoutActive(this.props.dataView) ? - : + : } {isWorking && } diff --git a/frontend-html/src/gui/Workbench/ScreenArea/TableView/TableViewEditor.tsx b/frontend-html/src/gui/Workbench/ScreenArea/TableView/TableViewEditor.tsx index 23a7af0e8f..41baa10175 100644 --- a/frontend-html/src/gui/Workbench/ScreenArea/TableView/TableViewEditor.tsx +++ b/frontend-html/src/gui/Workbench/ScreenArea/TableView/TableViewEditor.tsx @@ -48,6 +48,13 @@ import { flashColor2htmlColor, htmlColor2FlashColor } from "@origam/utils"; import { resolveCellAlignment } from "gui/Workbench/ScreenArea/TableView/ResolveCellAlignment"; import S from "./TableViewEditor.module.scss"; import { NewRecordScreen } from "model/entities/Lookup"; +import { onMainMenuItemClick } from "model/actions-ui/MainMenu/onMainMenuItemClick"; +import { runGeneratorInFlowWithHandler, runInFlowWithHandler } from "utils/runInFlowWithHandler"; +import { getMainMenuItemById } from "model/selectors/MainMenu/getMainMenuItemById"; +import { getConfigurationManager } from "model/selectors/TablePanelView/getConfigurationManager"; +import { getWorkbenchLifecycle } from "model/selectors/getWorkbenchLifecycle"; +import { IMainMenuItemType } from "model/entities/types/IMainMenu"; +import { DialogInfo } from "model/entities/OpenedScreen"; @inject(({tablePanelView}) => { const row = getSelectedRow(tablePanelView)!; @@ -209,7 +216,45 @@ export class TableViewEditor extends React.Component<{ gridFocusManager.editorBlur = undefined; }} newRecordScreen={this.props.property?.lookup?.newRecordScreen} - onAddNewRecordClick={() => {console.log("Add new click")}} + onAddNewRecordClick={() => { + if(!this.props.property?.lookup?.newRecordScreen){ + throw new Error("newRecordScreen not found on property " + this.props.property?.id); + } + const newRecordScreen = this.props.property.lookup.newRecordScreen; + const menuItem = getMainMenuItemById(this.props.property, this.props.property.lookup.newRecordScreen.menuItemId); + // onMainMenuItemClick(this.props.property)({ + // event: undefined, + // item: menuItem, + // idParameter: "74ba6e7d-6d77-4268-9e73-601a71d8b385", + // isSingleRecordEdit: true, + // }); + const self = this; + const workbenchLifecycle = getWorkbenchLifecycle(this.props.property); + const dialogInfo = new DialogInfo(newRecordScreen.width, newRecordScreen.height); + runGeneratorInFlowWithHandler({ + ctx: this.props.property, + generator: function*() { + yield*workbenchLifecycle.openNewForm( + self.props.property!.lookup!.newRecordScreen!.menuItemId, + menuItem.attributes.type,//IMainMenuItemType.FormRefWithSelection, + "New", + menuItem.attributes.lazyLoading === "true", + dialogInfo, + {"id": "74ba6e7d-6d77-4268-9e73-601a71d8b385"}, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + true, + true + ); + }() + }) + + console.log("Add new click"); + }} autoSort={this.props.property!.autoSort} onKeyDown={this.props.onEditorKeyDown} subscribeToFocusManager={(editor) => diff --git a/frontend-html/src/model/entities/FormScreen.ts b/frontend-html/src/model/entities/FormScreen.ts index 79f2f0d658..34f8d9afb6 100644 --- a/frontend-html/src/model/entities/FormScreen.ts +++ b/frontend-html/src/model/entities/FormScreen.ts @@ -283,8 +283,13 @@ export class FormScreenEnvelope implements IFormScreenEnvelope { this.formScreen = formScreen; } - *start(args: {initUIResult: any, preloadIsDirty?: boolean}): Generator { - yield*this.formScreenLifecycle.start(args.initUIResult, args.preloadIsDirty); + *start(args: {initUIResult: any, preloadIsDirty?: boolean, createNewRecord?: boolean}): Generator { + yield*this.formScreenLifecycle.start( + { + initUIResult: args.initUIResult, + preloadIsDirty: args.preloadIsDirty, + createNewRecord: args.createNewRecord + }); } parent?: any; diff --git a/frontend-html/src/model/entities/FormScreenLifecycle/FormScreenLifecycle.tsx b/frontend-html/src/model/entities/FormScreenLifecycle/FormScreenLifecycle.tsx index b68ec00c6a..8f7f2d4923 100644 --- a/frontend-html/src/model/entities/FormScreenLifecycle/FormScreenLifecycle.tsx +++ b/frontend-html/src/model/entities/FormScreenLifecycle/FormScreenLifecycle.tsx @@ -260,7 +260,7 @@ export class FormScreenLifecycle02 implements IFormScreenLifecycle02 { formScreen.dataUpdateCRS.leave(); } this.killForm(); - yield*this.start(uiResult); + yield*this.start({initUIResult: uiResult}); } finally { this.monitor.inFlow--; } @@ -279,7 +279,7 @@ export class FormScreenLifecycle02 implements IFormScreenLifecycle02 { formScreen.dataUpdateCRS.leave(); } this.killForm(); - yield*this.start(uiResult); + yield*this.start({initUIResult: uiResult}); } finally { this.monitor.inFlow--; } @@ -299,7 +299,7 @@ export class FormScreenLifecycle02 implements IFormScreenLifecycle02 { formScreen.dataUpdateCRS.leave(); } this.killForm(); - yield*this.start(uiResult); + yield*this.start({initUIResult: uiResult}); } finally { this.monitor.inFlow--; } @@ -367,7 +367,7 @@ export class FormScreenLifecycle02 implements IFormScreenLifecycle02 { })(); } - *start(initUIResult: any, preloadIsDirty?: boolean): Generator { + *start(args:{initUIResult: any, preloadIsDirty?: boolean, createNewRecord?: boolean}): Generator { let _steadyDebounceTimeout: any; this.disposers.push( reaction( @@ -414,14 +414,14 @@ export class FormScreenLifecycle02 implements IFormScreenLifecycle02 { if(!openedScreen){ return; } - this.initialSelectedRowId = initUIResult.currentRecordId; - yield*this.applyInitUIResult({initUIResult}); + this.initialSelectedRowId = args.initUIResult.currentRecordId; + yield*this.applyInitUIResult({initUIResult: args.initUIResult, createNewRecord: args.createNewRecord}); const formScreen = getFormScreen(this); - formScreen.setDirty(!!preloadIsDirty) - this.initializePlugins(initUIResult); + formScreen.setDirty(!!args.preloadIsDirty) + this.initializePlugins(args.initUIResult); if (!this.eagerLoading) { yield*this.clearTotalCounts(); - yield*this.loadData(preloadIsDirty); + yield*this.loadData(args.preloadIsDirty); yield*this.updateTotalRowCounts(); for (let rootDataView of formScreen.rootDataViews) { const orderingConfiguration = getOrderingConfiguration(rootDataView); @@ -550,7 +550,7 @@ export class FormScreenLifecycle02 implements IFormScreenLifecycle02 { })(); } - *applyInitUIResult(args: { initUIResult: any }): any { + *applyInitUIResult(args: { initUIResult: any, createNewRecord?: boolean}): any { const openedScreen = getOpenedScreen(this); assignIIds(args.initUIResult.formDefinition); @@ -564,6 +564,9 @@ export class FormScreenLifecycle02 implements IFormScreenLifecycle02 { args.initUIResult.workflowTaskId, openedScreen.lazyLoading ); + if (args.createNewRecord) { + screen.rootDataViews[0].isHeadless = true; + } screen.notifications = args.initUIResult.notifications; const api = getApi(openedScreen); const cacheDependencies = getWorkbench(openedScreen).lookupMultiEngine.cacheDependencies; diff --git a/frontend-html/src/model/entities/OrigamAPI.ts b/frontend-html/src/model/entities/OrigamAPI.ts index 818c806f7f..ecce5c3a86 100644 --- a/frontend-html/src/model/entities/OrigamAPI.ts +++ b/frontend-html/src/model/entities/OrigamAPI.ts @@ -148,6 +148,7 @@ export class OrigamAPI implements IApi { Caption: string; Parameters: { [key: string]: any }; IsSingleRecordEdit?: boolean; + CreateNewRecord?: boolean; RequestCurrentRecordId: boolean; }) { const result = (await this.axiosInstance.post("/UIService/InitUI", data)).data; diff --git a/frontend-html/src/model/entities/WorkbenchLifecycle/WorkbenchLifecycle.ts b/frontend-html/src/model/entities/WorkbenchLifecycle/WorkbenchLifecycle.ts index c3bda4e57b..7d12108d57 100644 --- a/frontend-html/src/model/entities/WorkbenchLifecycle/WorkbenchLifecycle.ts +++ b/frontend-html/src/model/entities/WorkbenchLifecycle/WorkbenchLifecycle.ts @@ -408,7 +408,8 @@ export class WorkbenchLifecycle implements IWorkbenchLifecycle { isSessionRebirth?: boolean, isSleepingDirty?: boolean, refreshOnReturnType?: IRefreshOnReturnType, - isSingleRecordEdit?: boolean + isSingleRecordEdit?: boolean, + createNewRecord?: boolean ) { const openedScreens = getOpenedScreens(this); const existingItem = openedScreens.findLastExistingTabItem(id); @@ -441,9 +442,14 @@ export class WorkbenchLifecycle implements IWorkbenchLifecycle { newScreen, !isSessionRebirth, requestParameters, - isSingleRecordEdit + isSingleRecordEdit, + createNewRecord ); - yield*newFormScreen.start({initUIResult: initUIResult}); + yield*newFormScreen.start( + { + initUIResult: initUIResult, + createNewRecord: true + }); const rowIdToSelect = parameters["id"]; yield*this.selectAndOpenRowById(rowIdToSelect, newFormScreen); const formScreen = newScreen.content.formScreen; @@ -475,7 +481,8 @@ export class WorkbenchLifecycle implements IWorkbenchLifecycle { screen: IOpenedScreen, isNewSession: boolean, requestParameters?: object | undefined, - isSingleRecordEdit?: boolean + isSingleRecordEdit?: boolean, + createNewRecord?: boolean ): any { const api = getApi(this); if (requestParameters) { @@ -491,6 +498,7 @@ export class WorkbenchLifecycle implements IWorkbenchLifecycle { DataRequested: !screen.lazyLoading, Parameters: screen.parameters, IsSingleRecordEdit: isSingleRecordEdit, + CreateNewRecord: createNewRecord, RequestCurrentRecordId: true }); } diff --git a/frontend-html/src/model/entities/types/IApi.ts b/frontend-html/src/model/entities/types/IApi.ts index faec8dc3a6..6ec17224a9 100644 --- a/frontend-html/src/model/entities/types/IApi.ts +++ b/frontend-html/src/model/entities/types/IApi.ts @@ -173,6 +173,7 @@ export interface IApi { Caption: string; Parameters: { [key: string]: any } | undefined; IsSingleRecordEdit?: boolean; + CreateNewRecord?: boolean RequestCurrentRecordId: boolean; }): Promise; diff --git a/frontend-html/src/model/entities/types/IFormScreenLifecycle.ts b/frontend-html/src/model/entities/types/IFormScreenLifecycle.ts index d032a96471..9eedfda1d5 100644 --- a/frontend-html/src/model/entities/types/IFormScreenLifecycle.ts +++ b/frontend-html/src/model/entities/types/IFormScreenLifecycle.ts @@ -107,7 +107,7 @@ export interface IFormScreenLifecycle02 extends IFormScreenLifecycleData { killForm(): void; - start(initUIResult: any, preloadIsDirty?: boolean): Generator; + start(args:{initUIResult: any, preloadIsDirty?: boolean, createNewRecord?: boolean}): Generator; loadGroups(rootDataView: IDataView, columnSettings: IGroupingSettings, groupByLookupId: string | undefined, aggregations: IAggregationInfo[] | undefined): Promise; diff --git a/frontend-html/src/model/entities/types/IWorkbenchLifecycle.ts b/frontend-html/src/model/entities/types/IWorkbenchLifecycle.ts index 9510b21311..39a2304433 100644 --- a/frontend-html/src/model/entities/types/IWorkbenchLifecycle.ts +++ b/frontend-html/src/model/entities/types/IWorkbenchLifecycle.ts @@ -22,6 +22,7 @@ import { IMainMenuItemType } from "./IMainMenu"; import { IUserInfo } from "model/entities/types/IUserInfo"; import { IPortalSettings } from "model/entities/types/IPortalSettings"; import { EventHandler } from "@origam/utils"; +import { IRefreshOnReturnType } from "model/entities/WorkbenchLifecycle/WorkbenchLifecycle"; export interface IWorkbenchLifecycle { $type_IWorkbenchLifecycle: 1; @@ -60,8 +61,14 @@ export interface IWorkbenchLifecycle { isLazyLoading: boolean, dialogInfo: IDialogInfo | undefined, parameters: { [key: string]: any }, - parentContext: any, - additionalRequestParameters?: object | undefined + parentContext?: any, + requestParameters?: object | undefined, + formSessionId?: string, + isSessionRebirth?: boolean, + isSleepingDirty?: boolean, + refreshOnReturnType?: IRefreshOnReturnType, + isSingleRecordEdit?: boolean, + createNewRecord?: boolean ): Generator; openNewUrl(url: string, title: string): Generator; diff --git a/model-tests/model/Widgets/DataLookup/Widgets/TagInputSource_Label_GetId.origam b/model-tests/model/Widgets/DataLookup/Widgets/TagInputSource_Label_GetId.origam index e6443f693a..16400d4742 100644 --- a/model-tests/model/Widgets/DataLookup/Widgets/TagInputSource_Label_GetId.origam +++ b/model-tests/model/Widgets/DataLookup/Widgets/TagInputSource_Label_GetId.origam @@ -23,6 +23,8 @@ adl:valueValueMember="Id"> Date: Fri, 20 Oct 2023 15:23:05 +0200 Subject: [PATCH 05/32] Save and Cancel buttons added to the new record screen --- .../Session Stores/FormSessionStore.cs | 57 ------------ .../Session Stores/NewRecordSessionStore.cs | 86 +++++++++++++++++++ .../gui/Workbench/ScreenArea/ScreenArea.tsx | 39 +++++++++ .../ScreenArea/TableView/TableViewEditor.tsx | 14 +-- .../src/model/entities/OpenedScreen.ts | 1 + .../WorkbenchLifecycle/WorkbenchLifecycle.ts | 30 +++---- .../entities/types/IFormScreenLifecycle.ts | 2 + .../src/model/entities/types/IOpenedScreen.ts | 1 + .../src/model/factories/createOpenedScreen.ts | 50 ----------- 9 files changed, 147 insertions(+), 133 deletions(-) create mode 100644 backend/Origam.Server/Session Stores/NewRecordSessionStore.cs delete mode 100644 frontend-html/src/model/factories/createOpenedScreen.ts diff --git a/backend/Origam.Server/Session Stores/FormSessionStore.cs b/backend/Origam.Server/Session Stores/FormSessionStore.cs index f7356a9e45..38d592724e 100644 --- a/backend/Origam.Server/Session Stores/FormSessionStore.cs +++ b/backend/Origam.Server/Session Stores/FormSessionStore.cs @@ -32,68 +32,11 @@ using core = Origam.Workbench.Services.CoreServices; using Origam.Schema; using System.Collections.Generic; -using System.Globalization; -using Origam.Schema.GuiModel; using Origam.Server; using Origam.Server.Session_Stores; namespace Origam.Server { - public class NewRecordSessionStore : FormSessionStore - { - public NewRecordSessionStore(IBasicUIService service, UIRequest request, string name, FormReferenceMenuItem menuItem, Analytics analytics) : base(service, request, name, menuItem, analytics) - { - } - - public NewRecordSessionStore(IBasicUIService service, UIRequest request, string name, Analytics analytics) : base(service, request, name, analytics) - { - } - - public override void Init() - { - IPersistenceService persistence = ServiceManager.Services.GetService(); - var menuItem = persistence.SchemaProvider.RetrieveInstance(typeof(AbstractMenuItem), new ModelElementKey(new Guid(Request.ObjectId))) as AbstractMenuItem; - FormReferenceMenuItem formMenuItem = menuItem as FormReferenceMenuItem; - var screen = persistence.SchemaProvider.RetrieveInstance(formMenuItem.ScreenId); - var dataStructure = persistence.SchemaProvider.RetrieveInstance(screen.DataSourceId); - var entity = dataStructure.Entities[0] as DataStructureEntity;//?.Entity as IDataEntity; - - var dataService = core.DataServiceFactory.GetDataService(); - var dataSet = dataService.GetEmptyDataSet( - entity.RootEntity.ParentItemId, CultureInfo.InvariantCulture); - var table = dataSet.Tables[entity.Name]; - var row = table.NewRow(); - - DatasetTools.ApplyPrimaryKey(row); - DatasetTools.UpdateOrigamSystemColumns( - row, true, SecurityManager.CurrentUserProfile().Id); - row.Table.NewRow(); - - // try - // { - core.DataService.Instance.StoreData( - dataStructureId: entity.RootEntity.ParentItemId, - data: row.Table.DataSet, - loadActualValuesAfterUpdate: false, - transactionId: null); - // } - // catch(DBConcurrencyException ex) - // { - // if(string.IsNullOrEmpty(ex.Message) - // && (ex.InnerException != null)) - // { - // return Conflict(ex.InnerException.Message); - // } - // return Conflict(ex.Message); - // } - - dataSet.Tables[entity.Name].Rows.Add(row); - - SetDataSource(dataSet); - // base.Init(); - } - } - public class FormSessionStore : SaveableSessionStore { private string _delayedLoadingParameterName; diff --git a/backend/Origam.Server/Session Stores/NewRecordSessionStore.cs b/backend/Origam.Server/Session Stores/NewRecordSessionStore.cs new file mode 100644 index 0000000000..e43f630091 --- /dev/null +++ b/backend/Origam.Server/Session Stores/NewRecordSessionStore.cs @@ -0,0 +1,86 @@ +#region license +/* +Copyright 2005 - 2023 Advantage Solutions, s. r. o. + +This file is part of ORIGAM (http://www.origam.org). + +ORIGAM is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +ORIGAM is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with ORIGAM. If not, see . +*/ +#endregion + +using System; +using System.Globalization; +using Origam.DA; +using Origam.Schema; +using Origam.Schema.EntityModel; +using Origam.Schema.GuiModel; +using Origam.Schema.MenuModel; +using Origam.Workbench.Services; + +namespace Origam.Server; + +public class NewRecordSessionStore : FormSessionStore +{ + public NewRecordSessionStore(IBasicUIService service, UIRequest request, string name, FormReferenceMenuItem menuItem, Analytics analytics) : base(service, request, name, menuItem, analytics) + { + } + + public NewRecordSessionStore(IBasicUIService service, UIRequest request, string name, Analytics analytics) : base(service, request, name, analytics) + { + } + + public override void Init() + { + IPersistenceService persistence = ServiceManager.Services.GetService(); + var menuItem = persistence.SchemaProvider.RetrieveInstance(typeof(AbstractMenuItem), new ModelElementKey(new Guid(Request.ObjectId))) as AbstractMenuItem; + FormReferenceMenuItem formMenuItem = menuItem as FormReferenceMenuItem; + var screen = persistence.SchemaProvider.RetrieveInstance(formMenuItem.ScreenId); + var dataStructure = persistence.SchemaProvider.RetrieveInstance(screen.DataSourceId); + var entity = dataStructure.Entities[0] as DataStructureEntity;//?.Entity as IDataEntity; + + var dataService = Workbench.Services.CoreServices.DataServiceFactory.GetDataService(); + var dataSet = dataService.GetEmptyDataSet( + entity.RootEntity.ParentItemId, CultureInfo.InvariantCulture); + var table = dataSet.Tables[entity.Name]; + var row = table.NewRow(); + + DatasetTools.ApplyPrimaryKey(row); + DatasetTools.UpdateOrigamSystemColumns( + row, true, SecurityManager.CurrentUserProfile().Id); + row.Table.NewRow(); + + // try + // { + Workbench.Services.CoreServices.DataService.Instance.StoreData( + dataStructureId: entity.RootEntity.ParentItemId, + data: row.Table.DataSet, + loadActualValuesAfterUpdate: false, + transactionId: null); + // } + // catch(DBConcurrencyException ex) + // { + // if(string.IsNullOrEmpty(ex.Message) + // && (ex.InnerException != null)) + // { + // return Conflict(ex.InnerException.Message); + // } + // return Conflict(ex.Message); + // } + + dataSet.Tables[entity.Name].Rows.Add(row); + + SetDataSource(dataSet); + // base.Init(); + } +} \ No newline at end of file diff --git a/frontend-html/src/gui/Workbench/ScreenArea/ScreenArea.tsx b/frontend-html/src/gui/Workbench/ScreenArea/ScreenArea.tsx index 7669b1f9a7..92ab57ea2a 100644 --- a/frontend-html/src/gui/Workbench/ScreenArea/ScreenArea.tsx +++ b/frontend-html/src/gui/Workbench/ScreenArea/ScreenArea.tsx @@ -36,6 +36,8 @@ import { IActionPlacement } from "model/entities/types/IAction"; import cx from "classnames"; import { ModalDialog } from "gui/Components/Dialog/ModalDialog"; import { isMobileLayoutActive } from "model/selectors/isMobileLayoutActive"; +import { getFormScreenLifecycle } from "model/selectors/FormScreen/getFormScreenLifecycle"; +import { runGeneratorInFlowWithHandler } from "utils/runInFlowWithHandler"; export const DialogScreen: React.FC<{ openedScreen: IOpenedScreen; @@ -43,6 +45,27 @@ export const DialogScreen: React.FC<{ const key = `ScreenDialog@${props.openedScreen.menuItemId}@${props.openedScreen.order}`; const workbenchLifecycle = getWorkbenchLifecycle(props.openedScreen); + async function onSaveClick(){ + await runGeneratorInFlowWithHandler({ + ctx: props.openedScreen, + generator: function*() { + const formScreenLifecycle = getFormScreenLifecycle(props.openedScreen.content.formScreen); + yield*formScreenLifecycle.onSaveSession(); + yield*formScreenLifecycle.closeForm(); + }() + }); + } + + async function onCloseClick(){ + await runGeneratorInFlowWithHandler({ + ctx: props.openedScreen, + generator: function*() { + const formScreenLifecycle = getFormScreenLifecycle(props.openedScreen.content.formScreen); + yield*formScreenLifecycle.closeForm(); + }() + }); + } + function renderActionButtons() { const content = props.openedScreen.content; const isNextButton = content.formScreen && content.formScreen.showWorkflowNextButton; @@ -111,6 +134,22 @@ export const DialogScreen: React.FC<{ {action.caption} ))} + {props.openedScreen.isNewRecordScreen && ( + + )} + {props.openedScreen.isNewRecordScreen && ( + + )} ) : ( <> diff --git a/frontend-html/src/gui/Workbench/ScreenArea/TableView/TableViewEditor.tsx b/frontend-html/src/gui/Workbench/ScreenArea/TableView/TableViewEditor.tsx index 41baa10175..d82bc4bac3 100644 --- a/frontend-html/src/gui/Workbench/ScreenArea/TableView/TableViewEditor.tsx +++ b/frontend-html/src/gui/Workbench/ScreenArea/TableView/TableViewEditor.tsx @@ -222,12 +222,6 @@ export class TableViewEditor extends React.Component<{ } const newRecordScreen = this.props.property.lookup.newRecordScreen; const menuItem = getMainMenuItemById(this.props.property, this.props.property.lookup.newRecordScreen.menuItemId); - // onMainMenuItemClick(this.props.property)({ - // event: undefined, - // item: menuItem, - // idParameter: "74ba6e7d-6d77-4268-9e73-601a71d8b385", - // isSingleRecordEdit: true, - // }); const self = this; const workbenchLifecycle = getWorkbenchLifecycle(this.props.property); const dialogInfo = new DialogInfo(newRecordScreen.width, newRecordScreen.height); @@ -236,11 +230,11 @@ export class TableViewEditor extends React.Component<{ generator: function*() { yield*workbenchLifecycle.openNewForm( self.props.property!.lookup!.newRecordScreen!.menuItemId, - menuItem.attributes.type,//IMainMenuItemType.FormRefWithSelection, - "New", + menuItem.attributes.type, + menuItem.attributes.label, menuItem.attributes.lazyLoading === "true", dialogInfo, - {"id": "74ba6e7d-6d77-4268-9e73-601a71d8b385"}, + {}, undefined, undefined, undefined, @@ -252,8 +246,6 @@ export class TableViewEditor extends React.Component<{ ); }() }) - - console.log("Add new click"); }} autoSort={this.props.property!.autoSort} onKeyDown={this.props.onEditorKeyDown} diff --git a/frontend-html/src/model/entities/OpenedScreen.ts b/frontend-html/src/model/entities/OpenedScreen.ts index fd1835f884..15c2412b7d 100644 --- a/frontend-html/src/model/entities/OpenedScreen.ts +++ b/frontend-html/src/model/entities/OpenedScreen.ts @@ -53,6 +53,7 @@ export class OpenedScreen implements IOpenedScreen { _title: string = ""; @observable isSleeping?: boolean = false; @observable isSleepingDirty?: boolean = false; + isNewRecordScreen?: boolean; isClosed: boolean = false; @observable content: IFormScreenEnvelope = null as any; parameters: { [key: string]: any } = {}; diff --git a/frontend-html/src/model/entities/WorkbenchLifecycle/WorkbenchLifecycle.ts b/frontend-html/src/model/entities/WorkbenchLifecycle/WorkbenchLifecycle.ts index 7d12108d57..13ac2e43f9 100644 --- a/frontend-html/src/model/entities/WorkbenchLifecycle/WorkbenchLifecycle.ts +++ b/frontend-html/src/model/entities/WorkbenchLifecycle/WorkbenchLifecycle.ts @@ -21,7 +21,6 @@ import bind from "bind-decorator"; import { reloadScreen } from "model/actions/FormScreen/reloadScreen"; import { handleError } from "model/actions/handleError"; import { createFormScreenEnvelope } from "model/factories/createFormScreenEnvelope"; -import { createOpenedScreen } from "model/factories/createOpenedScreen"; import { getIsFormScreenDirty } from "model/selectors/FormScreen/getisFormScreenDirty"; import { getApi } from "model/selectors/getApi"; import { getSearcher } from "model/selectors/getSearcher"; @@ -31,7 +30,7 @@ import { getMainMenuItemById } from "model/selectors/MainMenu/getMainMenuItemByI import { getWorkQueues } from "model/selectors/WorkQueues/getWorkQueues"; import { findMenu } from "xmlInterpreters/menuXml"; import { MainMenuContent } from "../MainMenu"; -import { DialogInfo } from "../OpenedScreen"; +import { DialogInfo, OpenedScreen } from "../OpenedScreen"; import { IMainMenuItemType } from "../types/IMainMenu"; import { IDialogInfo, IOpenedScreen } from "../types/IOpenedScreen"; import { IWorkbenchLifecycle } from "../types/IWorkbenchLifecycle"; @@ -414,19 +413,20 @@ export class WorkbenchLifecycle implements IWorkbenchLifecycle { const openedScreens = getOpenedScreens(this); const existingItem = openedScreens.findLastExistingTabItem(id); const newFormScreen = createFormScreenEnvelope(formSessionId, refreshOnReturnType); - const newScreen = yield*createOpenedScreen( - this, - id, - type, - existingItem ? existingItem.order + 1 : 0, - label, - newFormScreen, - isLazyLoading, - dialogInfo, - parameters, - isSessionRebirth, - isSleepingDirty - ); + + const newScreen = new OpenedScreen({ + menuItemId: id, + menuItemType: type, + order: existingItem ? existingItem.order + 1 : 0, + tabTitle: label, + content: newFormScreen, + dialogInfo: dialogInfo, + lazyLoading: isLazyLoading, + parameters: parameters, + isSleeping: isSessionRebirth, + isSleepingDirty: isSleepingDirty, + isNewRecordScreen: createNewRecord + }); try { openedScreens.pushItem(newScreen); if (!isSessionRebirth) { diff --git a/frontend-html/src/model/entities/types/IFormScreenLifecycle.ts b/frontend-html/src/model/entities/types/IFormScreenLifecycle.ts index 9eedfda1d5..75adcb3271 100644 --- a/frontend-html/src/model/entities/types/IFormScreenLifecycle.ts +++ b/frontend-html/src/model/entities/types/IFormScreenLifecycle.ts @@ -107,6 +107,8 @@ export interface IFormScreenLifecycle02 extends IFormScreenLifecycleData { killForm(): void; + closeForm(): Generator; + start(args:{initUIResult: any, preloadIsDirty?: boolean, createNewRecord?: boolean}): Generator; loadGroups(rootDataView: IDataView, columnSettings: IGroupingSettings, groupByLookupId: string | undefined, aggregations: IAggregationInfo[] | undefined): Promise; diff --git a/frontend-html/src/model/entities/types/IOpenedScreen.ts b/frontend-html/src/model/entities/types/IOpenedScreen.ts index 82a7898114..e8772295f0 100644 --- a/frontend-html/src/model/entities/types/IOpenedScreen.ts +++ b/frontend-html/src/model/entities/types/IOpenedScreen.ts @@ -38,6 +38,7 @@ export interface IOpenedScreenData { parameters: { [key: string]: any }; isSleeping?: boolean; isSleepingDirty?: boolean; + isNewRecordScreen?: boolean; } export interface IOpenedScreen extends IOpenedScreenData { diff --git a/frontend-html/src/model/factories/createOpenedScreen.ts b/frontend-html/src/model/factories/createOpenedScreen.ts deleted file mode 100644 index fa32de9a0f..0000000000 --- a/frontend-html/src/model/factories/createOpenedScreen.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* -Copyright 2005 - 2021 Advantage Solutions, s. r. o. - -This file is part of ORIGAM (http://www.origam.org). - -ORIGAM is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -ORIGAM is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with ORIGAM. If not, see . -*/ - -import { IDialogInfo } from "../entities/types/IOpenedScreen"; -import { OpenedScreen } from "../entities/OpenedScreen"; -import { IFormScreenEnvelope } from "../entities/types/IFormScreen"; -import { IMainMenuItemType } from "../entities/types/IMainMenu"; - -export function*createOpenedScreen( - ctx: any, - menuItemId: string, - menuItemType: IMainMenuItemType, - order: number, - title: string, - content: IFormScreenEnvelope, - lazyLoading: boolean, - dialogInfo: IDialogInfo | undefined, - parameters: { [key: string]: any }, - isSleeping?: boolean, - isSleepingDirty?: boolean -): Generator { - return new OpenedScreen({ - menuItemId, - menuItemType, - order, - tabTitle: title, - content, - dialogInfo, - lazyLoading, - parameters, - isSleeping, - isSleepingDirty - }); -} From 25fd9da7ca4af7ce7f1064ef6e51ee760e02302c Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Fri, 20 Oct 2023 16:51:18 +0200 Subject: [PATCH 06/32] New record screen is always open in form view --- .../FormScreenLifecycle.tsx | 6 ++---- .../WorkbenchLifecycle/WorkbenchLifecycle.ts | 2 +- frontend-html/src/xmlInterpreters/screenXml.ts | 18 +++++++++++++----- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/frontend-html/src/model/entities/FormScreenLifecycle/FormScreenLifecycle.tsx b/frontend-html/src/model/entities/FormScreenLifecycle/FormScreenLifecycle.tsx index 8f7f2d4923..bf3c3a8a1b 100644 --- a/frontend-html/src/model/entities/FormScreenLifecycle/FormScreenLifecycle.tsx +++ b/frontend-html/src/model/entities/FormScreenLifecycle/FormScreenLifecycle.tsx @@ -562,11 +562,9 @@ export class FormScreenLifecycle02 implements IFormScreenLifecycle02 { args.initUIResult.lookupMenuMappings, args.initUIResult.sessionId, args.initUIResult.workflowTaskId, - openedScreen.lazyLoading + openedScreen.lazyLoading, + args.createNewRecord ); - if (args.createNewRecord) { - screen.rootDataViews[0].isHeadless = true; - } screen.notifications = args.initUIResult.notifications; const api = getApi(openedScreen); const cacheDependencies = getWorkbench(openedScreen).lookupMultiEngine.cacheDependencies; diff --git a/frontend-html/src/model/entities/WorkbenchLifecycle/WorkbenchLifecycle.ts b/frontend-html/src/model/entities/WorkbenchLifecycle/WorkbenchLifecycle.ts index 13ac2e43f9..0926571d96 100644 --- a/frontend-html/src/model/entities/WorkbenchLifecycle/WorkbenchLifecycle.ts +++ b/frontend-html/src/model/entities/WorkbenchLifecycle/WorkbenchLifecycle.ts @@ -448,7 +448,7 @@ export class WorkbenchLifecycle implements IWorkbenchLifecycle { yield*newFormScreen.start( { initUIResult: initUIResult, - createNewRecord: true + createNewRecord: createNewRecord }); const rowIdToSelect = parameters["id"]; yield*this.selectAndOpenRowById(rowIdToSelect, newFormScreen); diff --git a/frontend-html/src/xmlInterpreters/screenXml.ts b/frontend-html/src/xmlInterpreters/screenXml.ts index ddf8992668..054c4679b1 100644 --- a/frontend-html/src/xmlInterpreters/screenXml.ts +++ b/frontend-html/src/xmlInterpreters/screenXml.ts @@ -240,7 +240,8 @@ export function*interpretScreenXml( lookupMenuMappings: any, sessionId: string, workflowTaskId: string | null, - isLazyLoading: boolean + isLazyLoading: boolean, + createNewRecord?: boolean ) { const workbench = getWorkbench(formScreenLifecycle); const workbenchLifeCycle = getWorkbenchLifecycle(formScreenLifecycle); @@ -416,6 +417,13 @@ export function*interpretScreenXml( .filter((conf: any) => conf.panel.instanceId === dataView.attributes.ModelInstanceId) .forEach((conf: any) => addFilterGroups(filterGroupManager, properties, conf)); + function getDefaultPanelView(){ + if(createNewRecord){ + return IPanelViewType.Form; + } + return panelViewFromNumber(parseInt(dataView.attributes.DefaultPanelView)); + } + const dataViewInstance: DataView = new DataView({ isFirst: i === 0, id: dataView.attributes.Id, @@ -427,10 +435,10 @@ export function*interpretScreenXml( name: dataView.attributes.Name, modelId: dataView.attributes.ModelId, newRecordView: dataView.attributes.NewRecordView, - defaultPanelView: panelViewFromNumber(parseInt(dataView.attributes.DefaultPanelView)), - activePanelView: panelViewFromNumber(parseInt(dataView.attributes.DefaultPanelView)), + defaultPanelView: getDefaultPanelView(), + activePanelView: getDefaultPanelView(), isMapSupported: dataView.attributes.IsMapSupported === "true", - isHeadless: dataView.attributes.IsHeadless === "true", + isHeadless: createNewRecord || dataView.attributes.IsHeadless === "true", disableActionButtons: dataView.attributes.DisableActionButtons === "true", showAddButton: dataView.attributes.ShowAddButton === "true", hideCopyButton: dataView.attributes.HideCopyButton === "true", @@ -498,7 +506,7 @@ export function*interpretScreenXml( ); const configurationNode = gridConfigurationNodes.length === 1 ? gridConfigurationNodes[0] : undefined; - if (configurationNode) { + if (!createNewRecord && configurationNode) { const defaultView = findStopping( configurationNode, (n) => n.name === "view" && n.parent.name === "defaultView" From ae3c27c24617a67afbe51415dba4367ab1339724 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Fri, 20 Oct 2023 17:02:54 +0200 Subject: [PATCH 07/32] NewRecordScreens are closed after browser reload --- backend/Origam.Server/ServerCoreUIService.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/backend/Origam.Server/ServerCoreUIService.cs b/backend/Origam.Server/ServerCoreUIService.cs index 43097325b2..e2ac602e5c 100644 --- a/backend/Origam.Server/ServerCoreUIService.cs +++ b/backend/Origam.Server/ServerCoreUIService.cs @@ -145,8 +145,9 @@ public PortalResult InitPortal(int maxRequestLength) { var sessionStore = mainSessionStore.ActiveSession ?? mainSessionStore; - if((sessionStore is SelectionDialogSessionStore) - || sessionStore.IsModalDialog) + if(sessionStore is SelectionDialogSessionStore + || sessionStore.IsModalDialog + || sessionStore is NewRecordSessionStore) { sessionsToDestroy.Add(sessionStore.Id); } From ec7d48f217c054f65d19423613bde3b3cc7c3772 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Fri, 20 Oct 2023 20:50:57 +0200 Subject: [PATCH 08/32] Clicked dropdown editor is closed after clicking the "+" and is reopened after the New record screen is closed --- .../gui/Workbench/ScreenArea/ScreenArea.tsx | 41 +--------- .../ScreenArea/TableView/TableViewEditor.tsx | 11 ++- .../src/gui/connections/CDialogContent.tsx | 11 ++- .../src/gui/connections/NewRecordScreen.tsx | 77 +++++++++++++++++++ .../src/model/entities/NewRecordScreenData.ts | 24 ++++++ frontend-html/src/model/entities/Workbench.ts | 2 + .../src/model/entities/types/IWorkbench.ts | 4 +- .../src/model/factories/createWorkbench.ts | 4 +- .../model/selectors/getNewRecordScreenData.ts | 25 ++++++ 9 files changed, 151 insertions(+), 48 deletions(-) create mode 100644 frontend-html/src/gui/connections/NewRecordScreen.tsx create mode 100644 frontend-html/src/model/entities/NewRecordScreenData.ts create mode 100644 frontend-html/src/model/selectors/getNewRecordScreenData.ts diff --git a/frontend-html/src/gui/Workbench/ScreenArea/ScreenArea.tsx b/frontend-html/src/gui/Workbench/ScreenArea/ScreenArea.tsx index 92ab57ea2a..e7b1ee9f95 100644 --- a/frontend-html/src/gui/Workbench/ScreenArea/ScreenArea.tsx +++ b/frontend-html/src/gui/Workbench/ScreenArea/ScreenArea.tsx @@ -36,36 +36,14 @@ import { IActionPlacement } from "model/entities/types/IAction"; import cx from "classnames"; import { ModalDialog } from "gui/Components/Dialog/ModalDialog"; import { isMobileLayoutActive } from "model/selectors/isMobileLayoutActive"; -import { getFormScreenLifecycle } from "model/selectors/FormScreen/getFormScreenLifecycle"; -import { runGeneratorInFlowWithHandler } from "utils/runInFlowWithHandler"; export const DialogScreen: React.FC<{ openedScreen: IOpenedScreen; + bottomButtons: JSX.Element | null; }> = observer((props) => { const key = `ScreenDialog@${props.openedScreen.menuItemId}@${props.openedScreen.order}`; const workbenchLifecycle = getWorkbenchLifecycle(props.openedScreen); - async function onSaveClick(){ - await runGeneratorInFlowWithHandler({ - ctx: props.openedScreen, - generator: function*() { - const formScreenLifecycle = getFormScreenLifecycle(props.openedScreen.content.formScreen); - yield*formScreenLifecycle.onSaveSession(); - yield*formScreenLifecycle.closeForm(); - }() - }); - } - - async function onCloseClick(){ - await runGeneratorInFlowWithHandler({ - ctx: props.openedScreen, - generator: function*() { - const formScreenLifecycle = getFormScreenLifecycle(props.openedScreen.content.formScreen); - yield*formScreenLifecycle.closeForm(); - }() - }); - } - function renderActionButtons() { const content = props.openedScreen.content; const isNextButton = content.formScreen && content.formScreen.showWorkflowNextButton; @@ -134,22 +112,7 @@ export const DialogScreen: React.FC<{ {action.caption} ))} - {props.openedScreen.isNewRecordScreen && ( - - )} - {props.openedScreen.isNewRecordScreen && ( - - )} + {props.bottomButtons} ) : ( <> diff --git a/frontend-html/src/gui/Workbench/ScreenArea/TableView/TableViewEditor.tsx b/frontend-html/src/gui/Workbench/ScreenArea/TableView/TableViewEditor.tsx index d82bc4bac3..c78eee4496 100644 --- a/frontend-html/src/gui/Workbench/ScreenArea/TableView/TableViewEditor.tsx +++ b/frontend-html/src/gui/Workbench/ScreenArea/TableView/TableViewEditor.tsx @@ -55,6 +55,7 @@ import { getConfigurationManager } from "model/selectors/TablePanelView/getConfi import { getWorkbenchLifecycle } from "model/selectors/getWorkbenchLifecycle"; import { IMainMenuItemType } from "model/entities/types/IMainMenu"; import { DialogInfo } from "model/entities/OpenedScreen"; +import { getNewRecordScreenData } from "model/selectors/getNewRecordScreenData"; @inject(({tablePanelView}) => { const row = getSelectedRow(tablePanelView)!; @@ -216,7 +217,7 @@ export class TableViewEditor extends React.Component<{ gridFocusManager.editorBlur = undefined; }} newRecordScreen={this.props.property?.lookup?.newRecordScreen} - onAddNewRecordClick={() => { + onAddNewRecordClick={async () => { if(!this.props.property?.lookup?.newRecordScreen){ throw new Error("newRecordScreen not found on property " + this.props.property?.id); } @@ -225,7 +226,11 @@ export class TableViewEditor extends React.Component<{ const self = this; const workbenchLifecycle = getWorkbenchLifecycle(this.props.property); const dialogInfo = new DialogInfo(newRecordScreen.width, newRecordScreen.height); - runGeneratorInFlowWithHandler({ + const newRecordScreenData = getNewRecordScreenData(this.props.property); + const tablePanelView = getTablePanelView(this.props.property)!; + newRecordScreenData.parentTablePanelView = tablePanelView; + tablePanelView.isEditing = false; + await runGeneratorInFlowWithHandler({ ctx: this.props.property, generator: function*() { yield*workbenchLifecycle.openNewForm( @@ -245,7 +250,7 @@ export class TableViewEditor extends React.Component<{ true ); }() - }) + }); }} autoSort={this.props.property!.autoSort} onKeyDown={this.props.onEditorKeyDown} diff --git a/frontend-html/src/gui/connections/CDialogContent.tsx b/frontend-html/src/gui/connections/CDialogContent.tsx index f0781696a7..b889a9de70 100644 --- a/frontend-html/src/gui/connections/CDialogContent.tsx +++ b/frontend-html/src/gui/connections/CDialogContent.tsx @@ -21,6 +21,7 @@ import { DialogScreen } from "gui/Workbench/ScreenArea/ScreenArea"; import { MobXProviderContext, observer } from "mobx-react"; import { getOpenedDialogScreenItems } from "model/selectors/getOpenedDialogScreenItems"; import React from "react"; +import { getNewRecordScreenButtons } from "gui/connections/NewRecordScreen"; @observer export class CDialogContent extends React.Component { @@ -34,9 +35,13 @@ export class CDialogContent extends React.Component { const openedDialogItems = getOpenedDialogScreenItems(this.workbench); return ( <> - {openedDialogItems.map(item => ( - - ))} + {openedDialogItems.map(item => + + )} ); } diff --git a/frontend-html/src/gui/connections/NewRecordScreen.tsx b/frontend-html/src/gui/connections/NewRecordScreen.tsx new file mode 100644 index 0000000000..b6d6fb6458 --- /dev/null +++ b/frontend-html/src/gui/connections/NewRecordScreen.tsx @@ -0,0 +1,77 @@ +/* +Copyright 2005 - 2023 Advantage Solutions, s. r. o. + +This file is part of ORIGAM (http://www.origam.org). + +ORIGAM is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +ORIGAM is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with ORIGAM. If not, see . +*/ + +import { IOpenedScreen } from "model/entities/types/IOpenedScreen"; +import { getNewRecordScreenData } from "model/selectors/getNewRecordScreenData"; +import { runGeneratorInFlowWithHandler } from "utils/runInFlowWithHandler"; +import { getFormScreenLifecycle } from "model/selectors/FormScreen/getFormScreenLifecycle"; +import S from "gui/Workbench/ScreenArea/ScreenArea.module.scss"; +import { T } from "utils/translation"; +import React from "react"; + +export function getNewRecordScreenButtons(openedScreen: IOpenedScreen) { + + function afterClose() { + const newRecordScreenData = getNewRecordScreenData(openedScreen); + if (newRecordScreenData.parentTablePanelView) { + newRecordScreenData.parentTablePanelView.isEditing = true; + newRecordScreenData.parentTablePanelView = undefined; + } + } + + async function onSaveClick() { + await runGeneratorInFlowWithHandler({ + ctx: openedScreen, + generator: function*() { + const formScreenLifecycle = getFormScreenLifecycle(openedScreen.content.formScreen); + yield*formScreenLifecycle.onSaveSession(); + yield*formScreenLifecycle.closeForm(); + afterClose(); + }() + }); + } + + async function onCloseClick() { + await runGeneratorInFlowWithHandler({ + ctx: openedScreen, + generator: function*() { + const formScreenLifecycle = getFormScreenLifecycle(openedScreen.content.formScreen); + yield*formScreenLifecycle.closeForm(); + afterClose(); + }() + }); + } + + return ( + <> + + + + ); +} \ No newline at end of file diff --git a/frontend-html/src/model/entities/NewRecordScreenData.ts b/frontend-html/src/model/entities/NewRecordScreenData.ts new file mode 100644 index 0000000000..5f87a72b90 --- /dev/null +++ b/frontend-html/src/model/entities/NewRecordScreenData.ts @@ -0,0 +1,24 @@ +/* +Copyright 2005 - 2023 Advantage Solutions, s. r. o. + +This file is part of ORIGAM (http://www.origam.org). + +ORIGAM is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +ORIGAM is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with ORIGAM. If not, see . +*/ + +import { ITablePanelView } from "model/entities/TablePanelView/types/ITablePanelView"; + +export class NewRecordScreenData { + parentTablePanelView: ITablePanelView | undefined; +} \ No newline at end of file diff --git a/frontend-html/src/model/entities/Workbench.ts b/frontend-html/src/model/entities/Workbench.ts index 3f8d18e192..5b2e8aaa8b 100644 --- a/frontend-html/src/model/entities/Workbench.ts +++ b/frontend-html/src/model/entities/Workbench.ts @@ -32,6 +32,7 @@ import { Notifications } from "./Notifications"; import { Favorites } from "model/entities/Favorites"; import { SidebarState } from "./SidebarState"; import { About } from "model/entities/AboutInfo"; +import { NewRecordScreenData } from "model/entities/NewRecordScreenData"; export class Workbench implements IWorkbench { $type_IWorkbench: 1 = 1; @@ -65,6 +66,7 @@ export class Workbench implements IWorkbench { favorites: Favorites = null as any; sidebarState: SidebarState = null as any; about: About = null as any; + newRecordScreenData: NewRecordScreenData = null as any; @observable isFullScreen: boolean = false; diff --git a/frontend-html/src/model/entities/types/IWorkbench.ts b/frontend-html/src/model/entities/types/IWorkbench.ts index 2d7e45d7fd..ce9d453db9 100644 --- a/frontend-html/src/model/entities/types/IWorkbench.ts +++ b/frontend-html/src/model/entities/types/IWorkbench.ts @@ -29,9 +29,8 @@ import { Chatrooms } from "../Chatrooms"; import { Notifications } from "../Notifications"; import { Favorites } from "model/entities/Favorites"; import { SidebarState } from "../SidebarState"; -import { observable } from "mobx"; -import { IAboutInfo } from "model/entities/types/IAboutInfo"; import { About } from "model/entities/AboutInfo"; +import { NewRecordScreenData } from "model/entities/NewRecordScreenData"; export interface IWorkbenchData { mainMenuEnvelope: IMainMenuEnvelope; @@ -48,6 +47,7 @@ export interface IWorkbenchData { about: About; lookupListCache: LookupListCacheMulti; lookupMultiEngine: IMultiLookupEngine; + newRecordScreenData: NewRecordScreenData } export interface IWorkbench extends IWorkbenchData { diff --git a/frontend-html/src/model/factories/createWorkbench.ts b/frontend-html/src/model/factories/createWorkbench.ts index e91c7805e8..45c4f885de 100644 --- a/frontend-html/src/model/factories/createWorkbench.ts +++ b/frontend-html/src/model/factories/createWorkbench.ts @@ -37,6 +37,7 @@ import { Notifications } from "model/entities/Notifications"; import { Favorites } from "model/entities/Favorites"; import { SidebarState } from "model/entities/SidebarState"; import { About } from "model/entities/AboutInfo"; +import { NewRecordScreenData } from "model/entities/NewRecordScreenData"; export function createWorkbench() { const clock = new Clock(); @@ -57,7 +58,8 @@ export function createWorkbench() { sidebarState: new SidebarState(), lookupListCache: workbenchLookupListCache, lookupMultiEngine, - about: new About() + about: new About(), + newRecordScreenData: new NewRecordScreenData() }); workbenchLookupListCache.startup(); lookupMultiEngine.startup(); diff --git a/frontend-html/src/model/selectors/getNewRecordScreenData.ts b/frontend-html/src/model/selectors/getNewRecordScreenData.ts new file mode 100644 index 0000000000..ff01774465 --- /dev/null +++ b/frontend-html/src/model/selectors/getNewRecordScreenData.ts @@ -0,0 +1,25 @@ +/* +Copyright 2005 - 2023 Advantage Solutions, s. r. o. + +This file is part of ORIGAM (http://www.origam.org). + +ORIGAM is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +ORIGAM is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with ORIGAM. If not, see . +*/ + +import { NewRecordScreenData } from "model/entities/NewRecordScreenData"; +import { getWorkbench } from "model/selectors/getWorkbench"; + +export function getNewRecordScreenData(ctx: any): NewRecordScreenData { + return getWorkbench(ctx).newRecordScreenData; +} \ No newline at end of file From 2a622f8e267e92edaca431a26ae871b678102c93 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Fri, 20 Oct 2023 20:59:18 +0200 Subject: [PATCH 09/32] NewRecordScreen refactored --- .../ScreenArea/TableView/TableViewEditor.tsx | 46 +----------- .../src/gui/connections/NewRecordScreen.tsx | 72 ++++++++++++++++++- frontend-html/src/model/entities/Lookup.ts | 29 +------- .../src/xmlInterpreters/screenXml.ts | 3 +- 4 files changed, 76 insertions(+), 74 deletions(-) diff --git a/frontend-html/src/gui/Workbench/ScreenArea/TableView/TableViewEditor.tsx b/frontend-html/src/gui/Workbench/ScreenArea/TableView/TableViewEditor.tsx index c78eee4496..45dbf47b4c 100644 --- a/frontend-html/src/gui/Workbench/ScreenArea/TableView/TableViewEditor.tsx +++ b/frontend-html/src/gui/Workbench/ScreenArea/TableView/TableViewEditor.tsx @@ -47,15 +47,7 @@ import { getGridFocusManager } from "model/entities/GridFocusManager"; import { flashColor2htmlColor, htmlColor2FlashColor } from "@origam/utils"; import { resolveCellAlignment } from "gui/Workbench/ScreenArea/TableView/ResolveCellAlignment"; import S from "./TableViewEditor.module.scss"; -import { NewRecordScreen } from "model/entities/Lookup"; -import { onMainMenuItemClick } from "model/actions-ui/MainMenu/onMainMenuItemClick"; -import { runGeneratorInFlowWithHandler, runInFlowWithHandler } from "utils/runInFlowWithHandler"; -import { getMainMenuItemById } from "model/selectors/MainMenu/getMainMenuItemById"; -import { getConfigurationManager } from "model/selectors/TablePanelView/getConfigurationManager"; -import { getWorkbenchLifecycle } from "model/selectors/getWorkbenchLifecycle"; -import { IMainMenuItemType } from "model/entities/types/IMainMenu"; -import { DialogInfo } from "model/entities/OpenedScreen"; -import { getNewRecordScreenData } from "model/selectors/getNewRecordScreenData"; +import { makeOnAddNewRecordClick } from "gui/connections/NewRecordScreen"; @inject(({tablePanelView}) => { const row = getSelectedRow(tablePanelView)!; @@ -217,41 +209,7 @@ export class TableViewEditor extends React.Component<{ gridFocusManager.editorBlur = undefined; }} newRecordScreen={this.props.property?.lookup?.newRecordScreen} - onAddNewRecordClick={async () => { - if(!this.props.property?.lookup?.newRecordScreen){ - throw new Error("newRecordScreen not found on property " + this.props.property?.id); - } - const newRecordScreen = this.props.property.lookup.newRecordScreen; - const menuItem = getMainMenuItemById(this.props.property, this.props.property.lookup.newRecordScreen.menuItemId); - const self = this; - const workbenchLifecycle = getWorkbenchLifecycle(this.props.property); - const dialogInfo = new DialogInfo(newRecordScreen.width, newRecordScreen.height); - const newRecordScreenData = getNewRecordScreenData(this.props.property); - const tablePanelView = getTablePanelView(this.props.property)!; - newRecordScreenData.parentTablePanelView = tablePanelView; - tablePanelView.isEditing = false; - await runGeneratorInFlowWithHandler({ - ctx: this.props.property, - generator: function*() { - yield*workbenchLifecycle.openNewForm( - self.props.property!.lookup!.newRecordScreen!.menuItemId, - menuItem.attributes.type, - menuItem.attributes.label, - menuItem.attributes.lazyLoading === "true", - dialogInfo, - {}, - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, - true, - true - ); - }() - }); - }} + onAddNewRecordClick={makeOnAddNewRecordClick(this.props.property!)} autoSort={this.props.property!.autoSort} onKeyDown={this.props.onEditorKeyDown} subscribeToFocusManager={(editor) => diff --git a/frontend-html/src/gui/connections/NewRecordScreen.tsx b/frontend-html/src/gui/connections/NewRecordScreen.tsx index b6d6fb6458..b75911966e 100644 --- a/frontend-html/src/gui/connections/NewRecordScreen.tsx +++ b/frontend-html/src/gui/connections/NewRecordScreen.tsx @@ -24,6 +24,39 @@ import { getFormScreenLifecycle } from "model/selectors/FormScreen/getFormScreen import S from "gui/Workbench/ScreenArea/ScreenArea.module.scss"; import { T } from "utils/translation"; import React from "react"; +import { getMainMenuItemById } from "model/selectors/MainMenu/getMainMenuItemById"; +import { getWorkbenchLifecycle } from "model/selectors/getWorkbenchLifecycle"; +import { DialogInfo } from "model/entities/OpenedScreen"; +import { getTablePanelView } from "model/selectors/TablePanelView/getTablePanelView"; +import { IProperty } from "model/entities/types/IProperty"; + +export class NewRecordScreen { + private _width: number; + private _height: number; + private _menuItemId: string; + + constructor(args: { + width: number, + height: number, + menuItemId: string} + ) { + this._width = args.width; + this._height = args.height; + this._menuItemId = args.menuItemId; + } + + get width() { + return this._width; + } + + get height() { + return this._height; + } + + get menuItemId() { + return this._menuItemId; + } +} export function getNewRecordScreenButtons(openedScreen: IOpenedScreen) { @@ -74,4 +107,41 @@ export function getNewRecordScreenButtons(openedScreen: IOpenedScreen) { ); -} \ No newline at end of file +} + +export function makeOnAddNewRecordClick(property: IProperty){ + return async function onAddNewRecordClick(){ + if(!property.lookup?.newRecordScreen){ + throw new Error("newRecordScreen not found on property " + property.id); + } + const newRecordScreen = property.lookup.newRecordScreen; + const menuItem = getMainMenuItemById(property, property.lookup.newRecordScreen.menuItemId); + const workbenchLifecycle = getWorkbenchLifecycle(property); + const dialogInfo = new DialogInfo(newRecordScreen.width, newRecordScreen.height); + const newRecordScreenData = getNewRecordScreenData(property); + const tablePanelView = getTablePanelView(property)!; + newRecordScreenData.parentTablePanelView = tablePanelView; + tablePanelView.isEditing = false; + await runGeneratorInFlowWithHandler({ + ctx: property, + generator: function*() { + yield*workbenchLifecycle.openNewForm( + property!.lookup!.newRecordScreen!.menuItemId, + menuItem.attributes.type, + menuItem.attributes.label, + menuItem.attributes.lazyLoading === "true", + dialogInfo, + {}, + undefined, + undefined, + undefined, + undefined, + undefined, + undefined, + true, + true + ); + }() + }); + } +} diff --git a/frontend-html/src/model/entities/Lookup.ts b/frontend-html/src/model/entities/Lookup.ts index bad45c94c5..5d0b110802 100644 --- a/frontend-html/src/model/entities/Lookup.ts +++ b/frontend-html/src/model/entities/Lookup.ts @@ -19,40 +19,13 @@ along with ORIGAM. If not, see . import { IDropDownColumn } from "./types/IDropDownColumn"; import { IDropDownParameter, IDropDownType, ILookup, ILookupData } from "./types/ILookup"; +import { NewRecordScreen } from "gui/connections/NewRecordScreen"; export enum IIdState { LOADING = "LOADING", ERROR = "ERROR", } -export class NewRecordScreen { - private _width: number; - private _height: number; - private _menuItemId: string; - - constructor(args: { - width: number, - height: number, - menuItemId: string} - ) { - this._width = args.width; - this._height = args.height; - this._menuItemId = args.menuItemId; - } - - get width() { - return this._width; - } - - get height() { - return this._height; - } - - get menuItemId() { - return this._menuItemId; - } -} - export class Lookup implements ILookup { constructor(data: ILookupData) { Object.assign(this, data); diff --git a/frontend-html/src/xmlInterpreters/screenXml.ts b/frontend-html/src/xmlInterpreters/screenXml.ts index 054c4679b1..cc616c00a5 100644 --- a/frontend-html/src/xmlInterpreters/screenXml.ts +++ b/frontend-html/src/xmlInterpreters/screenXml.ts @@ -31,7 +31,7 @@ import { DropDownColumn } from "model/entities/DropDownColumn"; import { FilterConfiguration } from "model/entities/FilterConfiguration"; import { FormPanelView } from "model/entities/FormPanelView/FormPanelView"; import { FormScreen } from "model/entities/FormScreen"; -import { Lookup, NewRecordScreen } from "model/entities/Lookup"; +import { Lookup } from "model/entities/Lookup"; import { OrderingConfiguration } from "model/entities/OrderingConfiguration"; import { Property } from "model/entities/Property"; import { ColumnConfigurationModel } from "model/entities/TablePanelView/ColumnConfigurationModel"; @@ -97,6 +97,7 @@ import { isMobileLayoutActive } from "model/selectors/isMobileLayoutActive"; import { ScreenFocusManager } from "model/entities/ScreenFocusManager"; import { getWorkbenchLifecycle } from "model/selectors/getWorkbenchLifecycle"; import {getCommonTabIndex, TabIndex} from "../model/entities/TabIndexOwner"; +import { NewRecordScreen } from "gui/connections/NewRecordScreen"; function getPropertyParameters(node: any) { From 1f24e1ee76d7bac02771d23451f4f86807931231 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Mon, 23 Oct 2023 11:16:33 +0200 Subject: [PATCH 10/32] Newly created record is set as the current value in the source combo box --- .../src/gui/connections/NewRecordScreen.tsx | 48 +++++++++++++++---- .../src/model/entities/NewRecordScreenData.ts | 8 +++- frontend-html/src/model/entities/Workbench.ts | 2 +- .../src/model/entities/types/IWorkbench.ts | 3 +- .../model/selectors/getNewRecordScreenData.ts | 25 ---------- 5 files changed, 48 insertions(+), 38 deletions(-) delete mode 100644 frontend-html/src/model/selectors/getNewRecordScreenData.ts diff --git a/frontend-html/src/gui/connections/NewRecordScreen.tsx b/frontend-html/src/gui/connections/NewRecordScreen.tsx index b75911966e..fc753bdd62 100644 --- a/frontend-html/src/gui/connections/NewRecordScreen.tsx +++ b/frontend-html/src/gui/connections/NewRecordScreen.tsx @@ -18,7 +18,6 @@ along with ORIGAM. If not, see . */ import { IOpenedScreen } from "model/entities/types/IOpenedScreen"; -import { getNewRecordScreenData } from "model/selectors/getNewRecordScreenData"; import { runGeneratorInFlowWithHandler } from "utils/runInFlowWithHandler"; import { getFormScreenLifecycle } from "model/selectors/FormScreen/getFormScreenLifecycle"; import S from "gui/Workbench/ScreenArea/ScreenArea.module.scss"; @@ -29,6 +28,10 @@ import { getWorkbenchLifecycle } from "model/selectors/getWorkbenchLifecycle"; import { DialogInfo } from "model/entities/OpenedScreen"; import { getTablePanelView } from "model/selectors/TablePanelView/getTablePanelView"; import { IProperty } from "model/entities/types/IProperty"; +import { onFieldChange } from "model/actions-ui/DataView/TableView/onFieldChange"; +import { getWorkbench } from "model/selectors/getWorkbench"; +import { NewRecordScreenData } from "model/entities/NewRecordScreenData"; +import { getDataView } from "model/selectors/DataView/getDataView"; export class NewRecordScreen { private _width: number; @@ -61,20 +64,47 @@ export class NewRecordScreen { export function getNewRecordScreenButtons(openedScreen: IOpenedScreen) { function afterClose() { - const newRecordScreenData = getNewRecordScreenData(openedScreen); - if (newRecordScreenData.parentTablePanelView) { - newRecordScreenData.parentTablePanelView.isEditing = true; - newRecordScreenData.parentTablePanelView = undefined; + const workbench = getWorkbench(openedScreen); + const newRecordScreenData = workbench.newRecordScreenData; + if (!newRecordScreenData) { + throw new Error("newRecordScreenData was not found"); } + const comboBoxTablePanelView = getTablePanelView(newRecordScreenData.comboBoxProperty); + comboBoxTablePanelView.isEditing = true; + workbench.newRecordScreenData = undefined; + } + + function*updateComboBoxValue(insertedRowId: string) { + const workbench = getWorkbench(openedScreen); + const newRecordScreenData = workbench.newRecordScreenData; + if (!newRecordScreenData) { + throw new Error("newRecordScreenData was not found"); + } + yield onFieldChange(newRecordScreenData.comboBoxProperty)({ + event: undefined, + row: newRecordScreenData.comboBoxRow, + property: newRecordScreenData.comboBoxProperty, + value: insertedRowId, + }); } async function onSaveClick() { await runGeneratorInFlowWithHandler({ ctx: openedScreen, generator: function*() { + const rootDataView = openedScreen.content.formScreen?.rootDataViews[0]; + if (!rootDataView) { + throw new Error("rootDataView not found") + } + if (rootDataView.dataTable.rows.length !== 1) { + throw new Error("first row not found") + } + const firstRow = rootDataView.dataTable.rows[0]; + const insertedRowId = rootDataView.dataTable.getRowId(firstRow); const formScreenLifecycle = getFormScreenLifecycle(openedScreen.content.formScreen); yield*formScreenLifecycle.onSaveSession(); yield*formScreenLifecycle.closeForm(); + yield*updateComboBoxValue(insertedRowId); afterClose(); }() }); @@ -111,16 +141,18 @@ export function getNewRecordScreenButtons(openedScreen: IOpenedScreen) { export function makeOnAddNewRecordClick(property: IProperty){ return async function onAddNewRecordClick(){ - if(!property.lookup?.newRecordScreen){ + if (!property.lookup?.newRecordScreen) { throw new Error("newRecordScreen not found on property " + property.id); } const newRecordScreen = property.lookup.newRecordScreen; const menuItem = getMainMenuItemById(property, property.lookup.newRecordScreen.menuItemId); const workbenchLifecycle = getWorkbenchLifecycle(property); const dialogInfo = new DialogInfo(newRecordScreen.width, newRecordScreen.height); - const newRecordScreenData = getNewRecordScreenData(property); + + const selectedRow = getDataView(property).selectedRow!; + getWorkbench(property).newRecordScreenData = new NewRecordScreenData(property, selectedRow); + const tablePanelView = getTablePanelView(property)!; - newRecordScreenData.parentTablePanelView = tablePanelView; tablePanelView.isEditing = false; await runGeneratorInFlowWithHandler({ ctx: property, diff --git a/frontend-html/src/model/entities/NewRecordScreenData.ts b/frontend-html/src/model/entities/NewRecordScreenData.ts index 5f87a72b90..e28d5e9d0e 100644 --- a/frontend-html/src/model/entities/NewRecordScreenData.ts +++ b/frontend-html/src/model/entities/NewRecordScreenData.ts @@ -17,8 +17,12 @@ You should have received a copy of the GNU General Public License along with ORIGAM. If not, see . */ -import { ITablePanelView } from "model/entities/TablePanelView/types/ITablePanelView"; +import { IProperty } from "model/entities/types/IProperty"; export class NewRecordScreenData { - parentTablePanelView: ITablePanelView | undefined; + constructor( + public comboBoxProperty: IProperty, + public comboBoxRow: any[] + ) { + } } \ No newline at end of file diff --git a/frontend-html/src/model/entities/Workbench.ts b/frontend-html/src/model/entities/Workbench.ts index 5b2e8aaa8b..20945925ec 100644 --- a/frontend-html/src/model/entities/Workbench.ts +++ b/frontend-html/src/model/entities/Workbench.ts @@ -66,7 +66,7 @@ export class Workbench implements IWorkbench { favorites: Favorites = null as any; sidebarState: SidebarState = null as any; about: About = null as any; - newRecordScreenData: NewRecordScreenData = null as any; + newRecordScreenData?: NewRecordScreenData; @observable isFullScreen: boolean = false; diff --git a/frontend-html/src/model/entities/types/IWorkbench.ts b/frontend-html/src/model/entities/types/IWorkbench.ts index ce9d453db9..c3a7e312d3 100644 --- a/frontend-html/src/model/entities/types/IWorkbench.ts +++ b/frontend-html/src/model/entities/types/IWorkbench.ts @@ -47,14 +47,13 @@ export interface IWorkbenchData { about: About; lookupListCache: LookupListCacheMulti; lookupMultiEngine: IMultiLookupEngine; - newRecordScreenData: NewRecordScreenData + newRecordScreenData?: NewRecordScreenData } export interface IWorkbench extends IWorkbenchData { $type_IWorkbench: 1; isFullScreen: boolean; - // loggedUserName: any mainMenuEnvelope: IMainMenuEnvelope; run(): Generator; diff --git a/frontend-html/src/model/selectors/getNewRecordScreenData.ts b/frontend-html/src/model/selectors/getNewRecordScreenData.ts deleted file mode 100644 index ff01774465..0000000000 --- a/frontend-html/src/model/selectors/getNewRecordScreenData.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* -Copyright 2005 - 2023 Advantage Solutions, s. r. o. - -This file is part of ORIGAM (http://www.origam.org). - -ORIGAM is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -ORIGAM is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with ORIGAM. If not, see . -*/ - -import { NewRecordScreenData } from "model/entities/NewRecordScreenData"; -import { getWorkbench } from "model/selectors/getWorkbench"; - -export function getNewRecordScreenData(ctx: any): NewRecordScreenData { - return getWorkbench(ctx).newRecordScreenData; -} \ No newline at end of file From 51f09dba93af279ddbbc6c105f2d88186bed51e3 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Mon, 23 Oct 2023 11:31:24 +0200 Subject: [PATCH 11/32] NewRecordSessionStore refactored --- .../Session Stores/NewRecordSessionStore.cs | 46 ++++++++----------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/backend/Origam.Server/Session Stores/NewRecordSessionStore.cs b/backend/Origam.Server/Session Stores/NewRecordSessionStore.cs index e43f630091..4c187db99a 100644 --- a/backend/Origam.Server/Session Stores/NewRecordSessionStore.cs +++ b/backend/Origam.Server/Session Stores/NewRecordSessionStore.cs @@ -22,6 +22,7 @@ using System; using System.Globalization; using Origam.DA; +using Origam.DA.ObjectPersistence; using Origam.Schema; using Origam.Schema.EntityModel; using Origam.Schema.GuiModel; @@ -32,55 +33,44 @@ namespace Origam.Server; public class NewRecordSessionStore : FormSessionStore { - public NewRecordSessionStore(IBasicUIService service, UIRequest request, string name, FormReferenceMenuItem menuItem, Analytics analytics) : base(service, request, name, menuItem, analytics) + public NewRecordSessionStore(IBasicUIService service, UIRequest request, + string name, FormReferenceMenuItem menuItem, Analytics analytics) + : base(service, request, name, menuItem, analytics) { } - public NewRecordSessionStore(IBasicUIService service, UIRequest request, string name, Analytics analytics) : base(service, request, name, analytics) + public NewRecordSessionStore(IBasicUIService service, UIRequest request, + string name, Analytics analytics) + : base(service, request, name, analytics) { } public override void Init() { IPersistenceService persistence = ServiceManager.Services.GetService(); - var menuItem = persistence.SchemaProvider.RetrieveInstance(typeof(AbstractMenuItem), new ModelElementKey(new Guid(Request.ObjectId))) as AbstractMenuItem; - FormReferenceMenuItem formMenuItem = menuItem as FormReferenceMenuItem; - var screen = persistence.SchemaProvider.RetrieveInstance(formMenuItem.ScreenId); - var dataStructure = persistence.SchemaProvider.RetrieveInstance(screen.DataSourceId); - var entity = dataStructure.Entities[0] as DataStructureEntity;//?.Entity as IDataEntity; - + IPersistenceProvider schemaProvider = persistence.SchemaProvider; + var formMenuItem = schemaProvider.RetrieveInstance(new Guid(Request.ObjectId)); + var screen = schemaProvider.RetrieveInstance(formMenuItem.ScreenId); + var dataStructure = schemaProvider.RetrieveInstance(screen.DataSourceId); + var rootEntity = ((DataStructureEntity)dataStructure.Entities[0])!.RootEntity; + var dataService = Workbench.Services.CoreServices.DataServiceFactory.GetDataService(); var dataSet = dataService.GetEmptyDataSet( - entity.RootEntity.ParentItemId, CultureInfo.InvariantCulture); - var table = dataSet.Tables[entity.Name]; + rootEntity.ParentItemId, CultureInfo.InvariantCulture); + var table = dataSet.Tables[rootEntity.Name]; var row = table.NewRow(); DatasetTools.ApplyPrimaryKey(row); DatasetTools.UpdateOrigamSystemColumns( row, true, SecurityManager.CurrentUserProfile().Id); row.Table.NewRow(); - - // try - // { + Workbench.Services.CoreServices.DataService.Instance.StoreData( - dataStructureId: entity.RootEntity.ParentItemId, + dataStructureId: rootEntity.ParentItemId, data: row.Table.DataSet, loadActualValuesAfterUpdate: false, transactionId: null); - // } - // catch(DBConcurrencyException ex) - // { - // if(string.IsNullOrEmpty(ex.Message) - // && (ex.InnerException != null)) - // { - // return Conflict(ex.InnerException.Message); - // } - // return Conflict(ex.Message); - // } - - dataSet.Tables[entity.Name].Rows.Add(row); - + dataSet.Tables[rootEntity.Name].Rows.Add(row); SetDataSource(dataSet); - // base.Init(); } } \ No newline at end of file From ff14bae0965706145f10f108013cb6be37aac325 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Mon, 23 Oct 2023 12:11:10 +0200 Subject: [PATCH 12/32] "Add New Record" localized --- frontend-html/public/locale/localization_cs-CZ.json | 3 ++- frontend-html/public/locale/localization_de-CH.json | 3 ++- frontend-html/public/locale/localization_de-DE.json | 3 ++- frontend-html/public/locale/localization_en-US.json | 3 ++- frontend-html/public/locale/localization_fr-CH.json | 3 ++- .../src/modules/Editors/DropdownEditor/DropdownEditorBody.tsx | 3 ++- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/frontend-html/public/locale/localization_cs-CZ.json b/frontend-html/public/locale/localization_cs-CZ.json index 829c7ebd4e..c6838cf31f 100644 --- a/frontend-html/public/locale/localization_cs-CZ.json +++ b/frontend-html/public/locale/localization_cs-CZ.json @@ -422,5 +422,6 @@ "row_not_found": "Tento řádek nebyl nalezen na serveru. Obnovte prosím data.", "column_width": "Šířka", "order_columns_button": "Seřadit", - "order_columns_title": "Pořadí sloupců" + "order_columns_title": "Pořadí sloupců", + "add_new_record": "Přidat nový záznam" } \ No newline at end of file diff --git a/frontend-html/public/locale/localization_de-CH.json b/frontend-html/public/locale/localization_de-CH.json index 3bfc13c47d..8d11b02cec 100644 --- a/frontend-html/public/locale/localization_de-CH.json +++ b/frontend-html/public/locale/localization_de-CH.json @@ -421,5 +421,6 @@ "row_not_found": "Die angeforderte Zeile wurde auf dem Server nicht gefunden. Bitte aktualisieren Sie die Daten.", "column_width": "Breite", "order_columns_button": "Sortieren", - "order_columns_title": "Spalten sortieren" + "order_columns_title": "Spalten sortieren", + "add_new_record": "Neuer Datensatz" } \ No newline at end of file diff --git a/frontend-html/public/locale/localization_de-DE.json b/frontend-html/public/locale/localization_de-DE.json index 73974f3cc7..91449a9825 100644 --- a/frontend-html/public/locale/localization_de-DE.json +++ b/frontend-html/public/locale/localization_de-DE.json @@ -421,5 +421,6 @@ "row_not_found": "Die angeforderte Zeile wurde auf dem Server nicht gefunden. Bitte aktualisieren Sie die Daten.", "column_width": "Breite", "order_columns_button": "Sortieren", - "order_columns_title": "Spalten sortieren" + "order_columns_title": "Spalten sortieren", + "add_new_record": "Neuer Datensatz" } \ No newline at end of file diff --git a/frontend-html/public/locale/localization_en-US.json b/frontend-html/public/locale/localization_en-US.json index 7fa2796d01..9d2312c6af 100644 --- a/frontend-html/public/locale/localization_en-US.json +++ b/frontend-html/public/locale/localization_en-US.json @@ -425,5 +425,6 @@ "row_not_found": "The row you requested was not found on the server. Please refresh the data.", "column_width": "Width", "order_columns_button": "Order", - "order_columns_title": "Order Columns" + "order_columns_title": "Order Columns", + "add_new_record": "Add New Record" } \ No newline at end of file diff --git a/frontend-html/public/locale/localization_fr-CH.json b/frontend-html/public/locale/localization_fr-CH.json index b67b513881..2ffe39f469 100644 --- a/frontend-html/public/locale/localization_fr-CH.json +++ b/frontend-html/public/locale/localization_fr-CH.json @@ -409,5 +409,6 @@ "row_not_found": "La ligne que vous avez demandée est introuvable sur le serveur. Veuillez actualiser les données.", "column_width": "largeur", "order_columns_button": "organiser", - "order_columns_title": "organiser les colonnes" + "order_columns_title": "organiser les colonnes", + "add_new_record": "Ajouter nouveau dossier" } \ No newline at end of file diff --git a/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBody.tsx b/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBody.tsx index fdc8a98fa7..b5e4d29874 100644 --- a/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBody.tsx +++ b/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBody.tsx @@ -31,6 +31,7 @@ import { DropdownColumnDrivers, DropdownDataTable } from "modules/Editors/Dropdo import { BoundingRect } from "react-measure"; import { IDropdownEditorBehavior } from "modules/Editors/DropdownEditor/DropdownEditorBehavior"; import { observable } from "mobx"; +import { T } from "utils/translation"; export function DropdownEditorBody() { const refCtxBody = useContext(CtxDropdownRefBody); @@ -147,7 +148,7 @@ export class DropdownEditorTable extends React.Component<{ className={"cell"} onClick={this.props.beh.onAddNewRecordClick} > - Add New Record + {T("Add New Record", "add_new_record")}
); } return ( From 958bb397fc82014b66778aa2123649b4f36c4c6b Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Mon, 23 Oct 2023 12:41:49 +0200 Subject: [PATCH 13/32] openNewForm method refactored --- .../DataLookupService.cs | 2 +- .../Form/ComboBox/MobileDropdownBehavior.tsx | 1 + .../src/gui/connections/NewRecordScreen.tsx | 24 ++-- .../actions/Actions/processActionResult.ts | 49 +++---- .../WorkbenchLifecycle/WorkbenchLifecycle.ts | 133 +++++++++--------- .../src/model/entities/types/ILookup.ts | 3 +- .../entities/types/IWorkbenchLifecycle.ts | 31 ++-- .../src/model/factories/createWorkbench.ts | 3 +- .../Editors/DropdownEditor/DropdownEditor.tsx | 2 +- .../DropdownEditor/DropdownEditorBehavior.tsx | 2 +- 10 files changed, 125 insertions(+), 125 deletions(-) diff --git a/backend/Origam.Workbench.Services/DataLookupService.cs b/backend/Origam.Workbench.Services/DataLookupService.cs index 64ff26328c..74b70c953d 100644 --- a/backend/Origam.Workbench.Services/DataLookupService.cs +++ b/backend/Origam.Workbench.Services/DataLookupService.cs @@ -624,7 +624,7 @@ public NewRecordScreenBinding GetNewRecordScreenBinding(AbstractDataLookup looku bool isAvailable = authorizationProvider.Authorize(principal, newRecordScreenBinding.AuthorizationContext) && authorizationProvider.Authorize(principal, newRecordScreenBinding.MenuItem.AuthorizationContext) && param.IsFeatureOn(newRecordScreenBinding.MenuItem.Features); - if(!isAvailable) + if (!isAvailable) { return null; } diff --git a/frontend-html/src/gui/connections/MobileComponents/Form/ComboBox/MobileDropdownBehavior.tsx b/frontend-html/src/gui/connections/MobileComponents/Form/ComboBox/MobileDropdownBehavior.tsx index 5472ee923a..95010b932f 100644 --- a/frontend-html/src/gui/connections/MobileComponents/Form/ComboBox/MobileDropdownBehavior.tsx +++ b/frontend-html/src/gui/connections/MobileComponents/Form/ComboBox/MobileDropdownBehavior.tsx @@ -74,6 +74,7 @@ export class MobileDropdownBehavior implements IDropdownEditorBehavior{ @observable userEnteredValue: string | undefined = undefined; @observable scrollToRowIndex: number | undefined = undefined; dontClearScrollToRow = true; + hasNewScreenButton = false; @observable cursorRowId = ""; diff --git a/frontend-html/src/gui/connections/NewRecordScreen.tsx b/frontend-html/src/gui/connections/NewRecordScreen.tsx index fc753bdd62..69134dbb0c 100644 --- a/frontend-html/src/gui/connections/NewRecordScreen.tsx +++ b/frontend-html/src/gui/connections/NewRecordScreen.tsx @@ -158,20 +158,16 @@ export function makeOnAddNewRecordClick(property: IProperty){ ctx: property, generator: function*() { yield*workbenchLifecycle.openNewForm( - property!.lookup!.newRecordScreen!.menuItemId, - menuItem.attributes.type, - menuItem.attributes.label, - menuItem.attributes.lazyLoading === "true", - dialogInfo, - {}, - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, - true, - true + { + id: property!.lookup!.newRecordScreen!.menuItemId, + type: menuItem.attributes.type, + label: menuItem.attributes.label, + isLazyLoading: menuItem.attributes.lazyLoading === "true", + dialogInfo: dialogInfo, + parameters: { }, + isSingleRecordEdit: true, + createNewRecord: true + } ); }() }); diff --git a/frontend-html/src/model/actions/Actions/processActionResult.ts b/frontend-html/src/model/actions/Actions/processActionResult.ts index 98649f7355..0f7cc070df 100644 --- a/frontend-html/src/model/actions/Actions/processActionResult.ts +++ b/frontend-html/src/model/actions/Actions/processActionResult.ts @@ -37,18 +37,20 @@ import { getMainMenuItemById } from "model/selectors/MainMenu/getMainMenuItemByI export interface IOpenNewForm { ( - id: string, - type: IMainMenuItemType, - label: string, - isLazyLoading: boolean, - dialogInfo: IDialogInfo | undefined, - parameters: { [key: string]: any }, - parentContext: any, - requestParameters: object, - formSessionId?: string, - isSessionRebirth?: boolean, - registerSession?: true, - refreshOnReturnType?: IRefreshOnReturnType + args: { + id: string, + type: IMainMenuItemType, + label: string, + isLazyLoading: boolean, + dialogInfo: IDialogInfo | undefined, + parameters: { [key: string]: any }, + parentContext?: any, + requestParameters?: object | undefined, + formSessionId?: string, + isSessionRebirth?: boolean, + isSleepingDirty?: boolean, + refreshOnReturnType?: IRefreshOnReturnType, + } ): Generator; //boolean } @@ -123,18 +125,17 @@ export function processActionResult2(dep: { } = request; const dialogInfo = isModalDialog ? new DialogInfo(dialogWidth, dialogHeight) : undefined; yield*dep.openNewForm( - objectId, - typeString, - caption || dep.getActionCaption(), - lazyLoading, - dialogInfo, - parameters, - dep.parentContext, - actionResultItem.request, - undefined, - undefined, - undefined, - refreshOnReturnType, + { + id: objectId, + type: typeString, + label: caption || dep.getActionCaption(), + isLazyLoading: lazyLoading, + dialogInfo: dialogInfo, + parameters: parameters, + parentContext: dep.parentContext, + requestParameters: actionResultItem.request, + refreshOnReturnType: refreshOnReturnType, + } ); break; } diff --git a/frontend-html/src/model/entities/WorkbenchLifecycle/WorkbenchLifecycle.ts b/frontend-html/src/model/entities/WorkbenchLifecycle/WorkbenchLifecycle.ts index 0926571d96..74f7e74108 100644 --- a/frontend-html/src/model/entities/WorkbenchLifecycle/WorkbenchLifecycle.ts +++ b/frontend-html/src/model/entities/WorkbenchLifecycle/WorkbenchLifecycle.ts @@ -149,21 +149,15 @@ export class WorkbenchLifecycle implements IWorkbenchLifecycle { yield*this.openNewUrl(url, args.item.attributes["label"]); return; } else { - yield*this.openNewForm( - id, - type, - label, - lazyLoading === "true", - dialogInfo, - args.idParameter ? {id: args.idParameter} : {}, - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, - args.isSingleRecordEdit - ); + yield*this.openNewForm({ + id: id, + type: type, + label: label, + isLazyLoading: lazyLoading === "true", + dialogInfo: dialogInfo, + parameters: args.idParameter ? {id: args.idParameter} : {}, + createNewRecord: args.isSingleRecordEdit + }); } } } @@ -172,21 +166,15 @@ export class WorkbenchLifecycle implements IWorkbenchLifecycle { yield*this.openNewUrl(url, args.item.attributes["label"]); return; } else { - yield*this.openNewForm( - id, - type, - label, - lazyLoading === "true", - dialogInfo, - args.idParameter ? {id: args.idParameter} : {}, - undefined, - undefined, - undefined, - undefined, - undefined, - undefined, - args.isSingleRecordEdit - ); + yield*this.openNewForm({ + id: id, + type: type, + label: label, + isLazyLoading: lazyLoading === "true", + dialogInfo: dialogInfo, + parameters: args.idParameter ? {id: args.idParameter} : {}, + createNewRecord: args.isSingleRecordEdit + }); } } @@ -257,10 +245,24 @@ export class WorkbenchLifecycle implements IWorkbenchLifecycle { } } } else { - yield*this.openNewForm(id, type, label, true, dialogInfo, {}); + yield*this.openNewForm({ + id: id, + type: type, + label: label, + isLazyLoading: true, + dialogInfo: dialogInfo, + parameters: { } + }); } } else { - yield*this.openNewForm(id, type, label, true, dialogInfo, {}); + yield*this.openNewForm({ + id: id, + type: type, + label: label, + isLazyLoading: true, + dialogInfo: dialogInfo, + parameters: { } + }); } } @@ -394,7 +396,7 @@ export class WorkbenchLifecycle implements IWorkbenchLifecycle { } @bind - *openNewForm( + *openNewForm(args: { id: string, type: IMainMenuItemType, label: string, @@ -409,48 +411,49 @@ export class WorkbenchLifecycle implements IWorkbenchLifecycle { refreshOnReturnType?: IRefreshOnReturnType, isSingleRecordEdit?: boolean, createNewRecord?: boolean + } ) { const openedScreens = getOpenedScreens(this); - const existingItem = openedScreens.findLastExistingTabItem(id); - const newFormScreen = createFormScreenEnvelope(formSessionId, refreshOnReturnType); + const existingItem = openedScreens.findLastExistingTabItem(args.id); + const newFormScreen = createFormScreenEnvelope(args.formSessionId, args.refreshOnReturnType); const newScreen = new OpenedScreen({ - menuItemId: id, - menuItemType: type, + menuItemId: args.id, + menuItemType: args.type, order: existingItem ? existingItem.order + 1 : 0, - tabTitle: label, + tabTitle: args.label, content: newFormScreen, - dialogInfo: dialogInfo, - lazyLoading: isLazyLoading, - parameters: parameters, - isSleeping: isSessionRebirth, - isSleepingDirty: isSleepingDirty, - isNewRecordScreen: createNewRecord + dialogInfo: args.dialogInfo, + lazyLoading: args.isLazyLoading, + parameters: args.parameters, + isSleeping: args.isSessionRebirth, + isSleepingDirty: args.isSleepingDirty, + isNewRecordScreen: args.createNewRecord }); try { openedScreens.pushItem(newScreen); - if (!isSessionRebirth) { - newScreen.parentContext = parentContext; + if (!args.isSessionRebirth) { + newScreen.parentContext = args.parentContext; openedScreens.activateItem(newScreen.menuItemId, newScreen.order); } - if (isSessionRebirth) { + if (args.isSessionRebirth) { return; } const initUIResult = yield*this.initUIForScreen( newScreen, - !isSessionRebirth, - requestParameters, - isSingleRecordEdit, - createNewRecord + !args.isSessionRebirth, + args.requestParameters, + args.isSingleRecordEdit, + args.createNewRecord ); yield*newFormScreen.start( { initUIResult: initUIResult, - createNewRecord: createNewRecord + createNewRecord: args.createNewRecord }); - const rowIdToSelect = parameters["id"]; + const rowIdToSelect = args.parameters["id"]; yield*this.selectAndOpenRowById(rowIdToSelect, newFormScreen); const formScreen = newScreen.content.formScreen; if (formScreen?.autoWorkflowNext) { @@ -586,19 +589,17 @@ export class WorkbenchLifecycle implements IWorkbenchLifecycle { const lazyLoading = menuItem ? menuItem?.attributes?.lazyLoading === "true" : session.type === IMainMenuItemType.WorkQueue; - yield*this.openNewForm( - session.objectId, - session.type, - session.caption, // TODO: Find in menu - lazyLoading, - undefined, // TODO: Find in... menu? - {}, - undefined, - undefined, - session.formSessionId, - true, - session.isDirty - ); + yield*this.openNewForm( { + id: session.objectId, + type: session.type, + label: session.caption, + isLazyLoading: lazyLoading, + dialogInfo: undefined, + parameters: {}, + formSessionId: session.formSessionId, + isSessionRebirth: true, + isSleepingDirty: session.isDirty + }); } } else { for (let session of portalInfo.sessions) { diff --git a/frontend-html/src/model/entities/types/ILookup.ts b/frontend-html/src/model/entities/types/ILookup.ts index fc3a2d659c..ccd0f531c2 100644 --- a/frontend-html/src/model/entities/types/ILookup.ts +++ b/frontend-html/src/model/entities/types/ILookup.ts @@ -18,7 +18,8 @@ along with ORIGAM. If not, see . */ import { IDropDownColumn } from "./IDropDownColumn"; -import { NewRecordScreen } from "model/entities/Lookup"; +import { NewRecordScreen } from "gui/connections/NewRecordScreen"; + export enum IDropDownType { EagerlyLoadedGrid = "EagerlyLoadedGrid", diff --git a/frontend-html/src/model/entities/types/IWorkbenchLifecycle.ts b/frontend-html/src/model/entities/types/IWorkbenchLifecycle.ts index 39a2304433..e5cececee8 100644 --- a/frontend-html/src/model/entities/types/IWorkbenchLifecycle.ts +++ b/frontend-html/src/model/entities/types/IWorkbenchLifecycle.ts @@ -54,21 +54,22 @@ export interface IWorkbenchLifecycle { mainMenuItemClickHandler: EventHandler; - openNewForm( - id: string, - type: IMainMenuItemType, - label: string, - isLazyLoading: boolean, - dialogInfo: IDialogInfo | undefined, - parameters: { [key: string]: any }, - parentContext?: any, - requestParameters?: object | undefined, - formSessionId?: string, - isSessionRebirth?: boolean, - isSleepingDirty?: boolean, - refreshOnReturnType?: IRefreshOnReturnType, - isSingleRecordEdit?: boolean, - createNewRecord?: boolean + openNewForm(args: { + id: string, + type: IMainMenuItemType, + label: string, + isLazyLoading: boolean, + dialogInfo: IDialogInfo | undefined, + parameters: { [key: string]: any }, + parentContext?: any, + requestParameters?: object | undefined, + formSessionId?: string, + isSessionRebirth?: boolean, + isSleepingDirty?: boolean, + refreshOnReturnType?: IRefreshOnReturnType, + isSingleRecordEdit?: boolean, + createNewRecord?: boolean + } ): Generator; openNewUrl(url: string, title: string): Generator; diff --git a/frontend-html/src/model/factories/createWorkbench.ts b/frontend-html/src/model/factories/createWorkbench.ts index 45c4f885de..318c719236 100644 --- a/frontend-html/src/model/factories/createWorkbench.ts +++ b/frontend-html/src/model/factories/createWorkbench.ts @@ -58,8 +58,7 @@ export function createWorkbench() { sidebarState: new SidebarState(), lookupListCache: workbenchLookupListCache, lookupMultiEngine, - about: new About(), - newRecordScreenData: new NewRecordScreenData() + about: new About() }); workbenchLookupListCache.startup(); lookupMultiEngine.startup(); diff --git a/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditor.tsx b/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditor.tsx index 38076b1279..19739a6139 100644 --- a/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditor.tsx +++ b/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditor.tsx @@ -33,7 +33,7 @@ import { IFocusable } from "../../../model/entities/FormFocusManager"; import { IWorkbench } from "model/entities/types/IWorkbench"; import { isMobileLayoutActive } from "model/selectors/isMobileLayoutActive"; import { DropdownEditorSetup, DropdownEditorSetupFromXml } from "modules/Editors/DropdownEditor/DropdownEditorSetup"; -import { NewRecordScreen } from "model/entities/Lookup"; +import { NewRecordScreen } from "gui/connections/NewRecordScreen"; export interface IDropdownEditorContext { behavior: DropdownEditorBehavior; diff --git a/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBehavior.tsx b/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBehavior.tsx index fbf75c199f..55e382dad4 100644 --- a/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBehavior.tsx +++ b/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBehavior.tsx @@ -29,7 +29,7 @@ import { compareStrings } from "../../../utils/string"; import { IDriverState } from "modules/Editors/DropdownEditor/Cells/IDriverState"; import { DropdownEditorSetup } from "modules/Editors/DropdownEditor/DropdownEditorSetup"; import { requestFocus } from "utils/focus"; -import { NewRecordScreen } from "model/entities/Lookup"; +import { NewRecordScreen } from "gui/connections/NewRecordScreen"; export const dropdownPageSize = 100; From a8c8d53389e5acb0426fa5ad2733243e235d60bc Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Mon, 23 Oct 2023 13:53:28 +0200 Subject: [PATCH 14/32] Dropdown width was not adjusted to "Add New Record" text --- .../DropdownEditor/DropdownEditorBody.tsx | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBody.tsx b/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBody.tsx index b5e4d29874..00c767dd35 100644 --- a/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBody.tsx +++ b/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBody.tsx @@ -243,19 +243,25 @@ export class DropdownEditorTable extends React.Component<{ return {windowWidth, visibleColumnWidthSum}; } + private getCellWidth(cellText: string, cellWidth: number) { + const customPadding = parsePaddingValue(this.props.drivers.customFieldStyle?.paddingLeft); + const currentCellWidth = + Math.round(getTextWidth(cellText, getCanvasFontSize())) + this.cellPadding + customPadding; + if (currentCellWidth > cellWidth) { + cellWidth = currentCellWidth; + } + return cellWidth; + } + private calculateColumnWidths() { let widths: number[] = []; for (let columnIndex = 0; columnIndex < this.columnCount; columnIndex++) { - let cellWidth = 100; + let cellWidth = this.showAddNewRecord + ? this.getCellWidth(T("Add New Record", "add_new_record"), 100) + : 100; for (let rowIndex = 0; rowIndex < this.rowCount - 1; rowIndex++) { const cellText = this.props.drivers.getDriver(columnIndex).bodyCellDriver.formattedText(rowIndex); - const customPadding = parsePaddingValue(this.props.drivers.customFieldStyle?.paddingLeft); - const currentCellWidth = - Math.round(getTextWidth(cellText, getCanvasFontSize())) + this.cellPadding + customPadding; - const customFieldStyle = this.props.drivers.customFieldStyle; - if (currentCellWidth > cellWidth) { - cellWidth = currentCellWidth; - } + cellWidth = this.getCellWidth(cellText, cellWidth); } widths.push(cellWidth); } From a78a0a6f96554fe24538a7acb86183b1a86039b7 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Mon, 23 Oct 2023 15:02:42 +0200 Subject: [PATCH 15/32] "Add New Record" is highlighted and can be activated by pressing Enter --- .../Editors/DropdownEditor/DropdownEditor.tsx | 1 + .../DropdownEditor/DropdownEditorBehavior.tsx | 12 ++- .../DropdownEditor/DropdownEditorBody.tsx | 78 +++++++++---------- 3 files changed, 46 insertions(+), 45 deletions(-) diff --git a/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditor.tsx b/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditor.tsx index 19739a6139..4b8d3ceef1 100644 --- a/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditor.tsx +++ b/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditor.tsx @@ -57,6 +57,7 @@ export function DropdownEditor(props: { }) { const beh = useContext(CtxDropdownEditor).behavior; const workbench = useContext(MobXProviderContext).workbench as IWorkbench; + const dataTable = useContext(CtxDropdownEditor).editorDataTable; return ( {() => ( diff --git a/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBehavior.tsx b/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBehavior.tsx index 55e382dad4..ed0be7ca53 100644 --- a/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBehavior.tsx +++ b/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBehavior.tsx @@ -84,10 +84,14 @@ export class DropdownEditorBehavior implements IDropdownEditorBehavior { private newRecordScreen?: NewRecordScreen; public onAddNewRecordClick?: () => void; - get hasNewScreenButton(){ + get hasNewScreenButton() { return !!this.newRecordScreen; } + get addNewDropDownVisible() { + return this.hasNewScreenButton && this.dataTable.rowCount === 0 + } + constructor(args: IBehaviorData) { this.api = args.api; this.data = args.data; @@ -234,6 +238,9 @@ export class DropdownEditorBehavior implements IDropdownEditorBehavior { } break; case "Enter": + if (this.addNewDropDownVisible) { + this.onAddNewRecordClick?.(); + } if (this.isDropped && !this.isWorking) { this.data.chooseNewValue(this.cursorRowId === "" ? null : this.cursorRowId); this.dropUp(); @@ -501,6 +508,3 @@ decorate(DropdownEditorBehavior, { isReadOnly: observable, }); -// export const IDropdownEditorBehavior = TypeSymbol( -// "IDropdownEditorBehavior" -// ); diff --git a/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBody.tsx b/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBody.tsx index 00c767dd35..c586a1d4d4 100644 --- a/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBody.tsx +++ b/frontend-html/src/modules/Editors/DropdownEditor/DropdownEditorBody.tsx @@ -58,19 +58,40 @@ export function DropdownEditorBody() { {() => (
- + { beh.addNewDropDownVisible + ? + : + }
)}
); } +function AddNewDropDown(props: {rowHeight: number}) { + const beh = useContext(CtxDropdownEditor).behavior; + + return ( +
+
+ {T("Add New Record", "add_new_record")} +
+
+ ); +} + @observer export class DropdownEditorTable extends React.Component<{ drivers: DropdownColumnDrivers, @@ -93,25 +114,17 @@ export class DropdownEditorTable extends React.Component<{ } get rowCount(){ - return this.showAddNewRecord - ? 1 // will show single row: "Add New Record" - : this.props.dataTable.rowCount + (this.hasHeader ? 1 : 0); + return this.props.dataTable.rowCount + (this.hasHeader ? 1 : 0); } get columnCount(){ - return this.showAddNewRecord - ? 1 - : this.props.drivers.driverCount; + return this.props.drivers.driverCount; } get hasHeader(){ return this.columnCount > 1 } - get showAddNewRecord(){ - return this.props.beh.hasNewScreenButton && this.props.dataTable.rowCount === 0; - } - get height(){ if(this.props.height){ return this.props.height; @@ -141,16 +154,6 @@ export class DropdownEditorTable extends React.Component<{ renderTableCell({columnIndex, key, parent, rowIndex, style}: GridCellProps) { const Prov = CtxCell.Provider as any; - if (this.showAddNewRecord) { - return( -
- {T("Add New Record", "add_new_record")} -
); - } return ( cellWidth) { - cellWidth = currentCellWidth; - } - return cellWidth; - } - private calculateColumnWidths() { let widths: number[] = []; for (let columnIndex = 0; columnIndex < this.columnCount; columnIndex++) { - let cellWidth = this.showAddNewRecord - ? this.getCellWidth(T("Add New Record", "add_new_record"), 100) - : 100; + let cellWidth = 100; for (let rowIndex = 0; rowIndex < this.rowCount - 1; rowIndex++) { const cellText = this.props.drivers.getDriver(columnIndex).bodyCellDriver.formattedText(rowIndex); - cellWidth = this.getCellWidth(cellText, cellWidth); + const customPadding = parsePaddingValue(this.props.drivers.customFieldStyle?.paddingLeft); + const currentCellWidth = + Math.round(getTextWidth(cellText, getCanvasFontSize())) + this.cellPadding + customPadding; + if (currentCellWidth > cellWidth) { + cellWidth = currentCellWidth; + } } widths.push(cellWidth); } From 9289df5d2f2575a870880ea34b75e4e1c21d3eec Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Tue, 24 Oct 2023 12:51:30 +0200 Subject: [PATCH 16/32] Enter in new record screen will save the data and close the screen --- .../ScreenArea/FormView/FormViewEditor.tsx | 7 + .../connections/NewRecordScreen.module.scss | 5 + .../src/gui/connections/NewRecordScreen.tsx | 130 +++++++++--------- 3 files changed, 78 insertions(+), 64 deletions(-) create mode 100644 frontend-html/src/gui/connections/NewRecordScreen.module.scss diff --git a/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx b/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx index e1aec67eca..6efeb64f3f 100644 --- a/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx +++ b/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx @@ -65,6 +65,8 @@ import { getIsAddButtonVisible } from "model/selectors/DataView/getIsAddButtonVi import { getIsDelButtonVisible } from "model/selectors/DataView/getIsDelButtonVisible"; import { getIsCopyButtonVisible } from "model/selectors/DataView/getIsCopyButtonVisible"; import { geScreenActionButtonsState } from "model/actions-ui/ScreenToolbar/saveButtonVisible"; +import { getOpenedScreen } from "model/selectors/getOpenedScreen"; +import { onSaveClick } from "gui/connections/NewRecordScreen"; @inject(({property, formPanelView}) => { @@ -398,6 +400,11 @@ export class FormViewEditor extends React.Component<{ } if (event.key === "Enter") { await this.props.onEditorBlur?.(); + const openedScreen = getOpenedScreen(this.props.property); + if(openedScreen.isNewRecordScreen){ + await onSaveClick(openedScreen); + return; + } if (dataView.firstEnabledDefaultAction) { uiActions.actions.onActionClick(dataView.firstEnabledDefaultAction)( event, diff --git a/frontend-html/src/gui/connections/NewRecordScreen.module.scss b/frontend-html/src/gui/connections/NewRecordScreen.module.scss new file mode 100644 index 0000000000..ca23909278 --- /dev/null +++ b/frontend-html/src/gui/connections/NewRecordScreen.module.scss @@ -0,0 +1,5 @@ +div#modal-window-portal div div button.defaultButton { + background: var(--foreground1); + border-color: var(--foreground1); + color: var(--background1); +} \ No newline at end of file diff --git a/frontend-html/src/gui/connections/NewRecordScreen.tsx b/frontend-html/src/gui/connections/NewRecordScreen.tsx index 69134dbb0c..ba014c672a 100644 --- a/frontend-html/src/gui/connections/NewRecordScreen.tsx +++ b/frontend-html/src/gui/connections/NewRecordScreen.tsx @@ -20,7 +20,8 @@ along with ORIGAM. If not, see . import { IOpenedScreen } from "model/entities/types/IOpenedScreen"; import { runGeneratorInFlowWithHandler } from "utils/runInFlowWithHandler"; import { getFormScreenLifecycle } from "model/selectors/FormScreen/getFormScreenLifecycle"; -import S from "gui/Workbench/ScreenArea/ScreenArea.module.scss"; +import SA from "gui/Workbench/ScreenArea/ScreenArea.module.scss"; +import S from "gui/connections/NewRecordScreen.module.scss"; import { T } from "utils/translation"; import React from "react"; import { getMainMenuItemById } from "model/selectors/MainMenu/getMainMenuItemById"; @@ -32,6 +33,7 @@ import { onFieldChange } from "model/actions-ui/DataView/TableView/onFieldChange import { getWorkbench } from "model/selectors/getWorkbench"; import { NewRecordScreenData } from "model/entities/NewRecordScreenData"; import { getDataView } from "model/selectors/DataView/getDataView"; +import cx from "classnames"; export class NewRecordScreen { private _width: number; @@ -62,76 +64,17 @@ export class NewRecordScreen { } export function getNewRecordScreenButtons(openedScreen: IOpenedScreen) { - - function afterClose() { - const workbench = getWorkbench(openedScreen); - const newRecordScreenData = workbench.newRecordScreenData; - if (!newRecordScreenData) { - throw new Error("newRecordScreenData was not found"); - } - const comboBoxTablePanelView = getTablePanelView(newRecordScreenData.comboBoxProperty); - comboBoxTablePanelView.isEditing = true; - workbench.newRecordScreenData = undefined; - } - - function*updateComboBoxValue(insertedRowId: string) { - const workbench = getWorkbench(openedScreen); - const newRecordScreenData = workbench.newRecordScreenData; - if (!newRecordScreenData) { - throw new Error("newRecordScreenData was not found"); - } - yield onFieldChange(newRecordScreenData.comboBoxProperty)({ - event: undefined, - row: newRecordScreenData.comboBoxRow, - property: newRecordScreenData.comboBoxProperty, - value: insertedRowId, - }); - } - - async function onSaveClick() { - await runGeneratorInFlowWithHandler({ - ctx: openedScreen, - generator: function*() { - const rootDataView = openedScreen.content.formScreen?.rootDataViews[0]; - if (!rootDataView) { - throw new Error("rootDataView not found") - } - if (rootDataView.dataTable.rows.length !== 1) { - throw new Error("first row not found") - } - const firstRow = rootDataView.dataTable.rows[0]; - const insertedRowId = rootDataView.dataTable.getRowId(firstRow); - const formScreenLifecycle = getFormScreenLifecycle(openedScreen.content.formScreen); - yield*formScreenLifecycle.onSaveSession(); - yield*formScreenLifecycle.closeForm(); - yield*updateComboBoxValue(insertedRowId); - afterClose(); - }() - }); - } - - async function onCloseClick() { - await runGeneratorInFlowWithHandler({ - ctx: openedScreen, - generator: function*() { - const formScreenLifecycle = getFormScreenLifecycle(openedScreen.content.formScreen); - yield*formScreenLifecycle.closeForm(); - afterClose(); - }() - }); - } - return ( <> @@ -139,6 +82,65 @@ export function getNewRecordScreenButtons(openedScreen: IOpenedScreen) { ); } +export async function onSaveClick(openedScreen: IOpenedScreen) { + await runGeneratorInFlowWithHandler({ + ctx: openedScreen, + generator: function*() { + const rootDataView = openedScreen.content.formScreen?.rootDataViews[0]; + if (!rootDataView) { + throw new Error("rootDataView not found") + } + if (rootDataView.dataTable.rows.length !== 1) { + throw new Error("first row not found") + } + const firstRow = rootDataView.dataTable.rows[0]; + const insertedRowId = rootDataView.dataTable.getRowId(firstRow); + const formScreenLifecycle = getFormScreenLifecycle(openedScreen.content.formScreen); + yield*formScreenLifecycle.onSaveSession(); + yield*formScreenLifecycle.closeForm(); + yield*updateComboBoxValue(insertedRowId, openedScreen); + afterClose(openedScreen); + }() + }); +} + +function afterClose(openedScreen: IOpenedScreen) { + const workbench = getWorkbench(openedScreen); + const newRecordScreenData = workbench.newRecordScreenData; + if (!newRecordScreenData) { + throw new Error("newRecordScreenData was not found"); + } + const comboBoxTablePanelView = getTablePanelView(newRecordScreenData.comboBoxProperty); + comboBoxTablePanelView.isEditing = true; + workbench.newRecordScreenData = undefined; +} + +async function onCloseClick(openedScreen: IOpenedScreen) { + await runGeneratorInFlowWithHandler({ + ctx: openedScreen, + generator: function*() { + const formScreenLifecycle = getFormScreenLifecycle(openedScreen.content.formScreen); + yield*formScreenLifecycle.closeForm(); + afterClose(openedScreen); + }() + }); +} + +function*updateComboBoxValue(insertedRowId: string, openedScreen: IOpenedScreen) { + const workbench = getWorkbench(openedScreen); + const newRecordScreenData = workbench.newRecordScreenData; + if (!newRecordScreenData) { + throw new Error("newRecordScreenData was not found"); + } + yield onFieldChange(newRecordScreenData.comboBoxProperty)({ + event: undefined, + row: newRecordScreenData.comboBoxRow, + property: newRecordScreenData.comboBoxProperty, + value: insertedRowId, + }); +} + + export function makeOnAddNewRecordClick(property: IProperty){ return async function onAddNewRecordClick(){ if (!property.lookup?.newRecordScreen) { From 088c80d35b419bc8bf7443d12c3a3cb5cbc7f795 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Tue, 24 Oct 2023 12:51:30 +0200 Subject: [PATCH 17/32] Enter in new record screen will save the data and close the screen --- .../ScreenArea/FormView/FormViewEditor.tsx | 7 + .../connections/NewRecordScreen.module.scss | 5 + .../src/gui/connections/NewRecordScreen.tsx | 131 +++++++++--------- 3 files changed, 79 insertions(+), 64 deletions(-) create mode 100644 frontend-html/src/gui/connections/NewRecordScreen.module.scss diff --git a/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx b/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx index e1aec67eca..6efeb64f3f 100644 --- a/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx +++ b/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx @@ -65,6 +65,8 @@ import { getIsAddButtonVisible } from "model/selectors/DataView/getIsAddButtonVi import { getIsDelButtonVisible } from "model/selectors/DataView/getIsDelButtonVisible"; import { getIsCopyButtonVisible } from "model/selectors/DataView/getIsCopyButtonVisible"; import { geScreenActionButtonsState } from "model/actions-ui/ScreenToolbar/saveButtonVisible"; +import { getOpenedScreen } from "model/selectors/getOpenedScreen"; +import { onSaveClick } from "gui/connections/NewRecordScreen"; @inject(({property, formPanelView}) => { @@ -398,6 +400,11 @@ export class FormViewEditor extends React.Component<{ } if (event.key === "Enter") { await this.props.onEditorBlur?.(); + const openedScreen = getOpenedScreen(this.props.property); + if(openedScreen.isNewRecordScreen){ + await onSaveClick(openedScreen); + return; + } if (dataView.firstEnabledDefaultAction) { uiActions.actions.onActionClick(dataView.firstEnabledDefaultAction)( event, diff --git a/frontend-html/src/gui/connections/NewRecordScreen.module.scss b/frontend-html/src/gui/connections/NewRecordScreen.module.scss new file mode 100644 index 0000000000..718c1658d1 --- /dev/null +++ b/frontend-html/src/gui/connections/NewRecordScreen.module.scss @@ -0,0 +1,5 @@ +#defaultSaveButton { + background: var(--foreground1); + border-color: var(--foreground1); + color: var(--background1); +} \ No newline at end of file diff --git a/frontend-html/src/gui/connections/NewRecordScreen.tsx b/frontend-html/src/gui/connections/NewRecordScreen.tsx index 69134dbb0c..4e7513d588 100644 --- a/frontend-html/src/gui/connections/NewRecordScreen.tsx +++ b/frontend-html/src/gui/connections/NewRecordScreen.tsx @@ -20,7 +20,8 @@ along with ORIGAM. If not, see . import { IOpenedScreen } from "model/entities/types/IOpenedScreen"; import { runGeneratorInFlowWithHandler } from "utils/runInFlowWithHandler"; import { getFormScreenLifecycle } from "model/selectors/FormScreen/getFormScreenLifecycle"; -import S from "gui/Workbench/ScreenArea/ScreenArea.module.scss"; +import SA from "gui/Workbench/ScreenArea/ScreenArea.module.scss"; +import S from "gui/connections/NewRecordScreen.module.scss"; import { T } from "utils/translation"; import React from "react"; import { getMainMenuItemById } from "model/selectors/MainMenu/getMainMenuItemById"; @@ -32,6 +33,7 @@ import { onFieldChange } from "model/actions-ui/DataView/TableView/onFieldChange import { getWorkbench } from "model/selectors/getWorkbench"; import { NewRecordScreenData } from "model/entities/NewRecordScreenData"; import { getDataView } from "model/selectors/DataView/getDataView"; +import cx from "classnames"; export class NewRecordScreen { private _width: number; @@ -62,76 +64,18 @@ export class NewRecordScreen { } export function getNewRecordScreenButtons(openedScreen: IOpenedScreen) { - - function afterClose() { - const workbench = getWorkbench(openedScreen); - const newRecordScreenData = workbench.newRecordScreenData; - if (!newRecordScreenData) { - throw new Error("newRecordScreenData was not found"); - } - const comboBoxTablePanelView = getTablePanelView(newRecordScreenData.comboBoxProperty); - comboBoxTablePanelView.isEditing = true; - workbench.newRecordScreenData = undefined; - } - - function*updateComboBoxValue(insertedRowId: string) { - const workbench = getWorkbench(openedScreen); - const newRecordScreenData = workbench.newRecordScreenData; - if (!newRecordScreenData) { - throw new Error("newRecordScreenData was not found"); - } - yield onFieldChange(newRecordScreenData.comboBoxProperty)({ - event: undefined, - row: newRecordScreenData.comboBoxRow, - property: newRecordScreenData.comboBoxProperty, - value: insertedRowId, - }); - } - - async function onSaveClick() { - await runGeneratorInFlowWithHandler({ - ctx: openedScreen, - generator: function*() { - const rootDataView = openedScreen.content.formScreen?.rootDataViews[0]; - if (!rootDataView) { - throw new Error("rootDataView not found") - } - if (rootDataView.dataTable.rows.length !== 1) { - throw new Error("first row not found") - } - const firstRow = rootDataView.dataTable.rows[0]; - const insertedRowId = rootDataView.dataTable.getRowId(firstRow); - const formScreenLifecycle = getFormScreenLifecycle(openedScreen.content.formScreen); - yield*formScreenLifecycle.onSaveSession(); - yield*formScreenLifecycle.closeForm(); - yield*updateComboBoxValue(insertedRowId); - afterClose(); - }() - }); - } - - async function onCloseClick() { - await runGeneratorInFlowWithHandler({ - ctx: openedScreen, - generator: function*() { - const formScreenLifecycle = getFormScreenLifecycle(openedScreen.content.formScreen); - yield*formScreenLifecycle.closeForm(); - afterClose(); - }() - }); - } - return ( <> @@ -139,6 +83,65 @@ export function getNewRecordScreenButtons(openedScreen: IOpenedScreen) { ); } +export async function onSaveClick(openedScreen: IOpenedScreen) { + await runGeneratorInFlowWithHandler({ + ctx: openedScreen, + generator: function*() { + const rootDataView = openedScreen.content.formScreen?.rootDataViews[0]; + if (!rootDataView) { + throw new Error("rootDataView not found") + } + if (rootDataView.dataTable.rows.length !== 1) { + throw new Error("first row not found") + } + const firstRow = rootDataView.dataTable.rows[0]; + const insertedRowId = rootDataView.dataTable.getRowId(firstRow); + const formScreenLifecycle = getFormScreenLifecycle(openedScreen.content.formScreen); + yield*formScreenLifecycle.onSaveSession(); + yield*formScreenLifecycle.closeForm(); + yield*updateComboBoxValue(insertedRowId, openedScreen); + afterClose(openedScreen); + }() + }); +} + +function afterClose(openedScreen: IOpenedScreen) { + const workbench = getWorkbench(openedScreen); + const newRecordScreenData = workbench.newRecordScreenData; + if (!newRecordScreenData) { + throw new Error("newRecordScreenData was not found"); + } + const comboBoxTablePanelView = getTablePanelView(newRecordScreenData.comboBoxProperty); + comboBoxTablePanelView.isEditing = true; + workbench.newRecordScreenData = undefined; +} + +async function onCloseClick(openedScreen: IOpenedScreen) { + await runGeneratorInFlowWithHandler({ + ctx: openedScreen, + generator: function*() { + const formScreenLifecycle = getFormScreenLifecycle(openedScreen.content.formScreen); + yield*formScreenLifecycle.closeForm(); + afterClose(openedScreen); + }() + }); +} + +function*updateComboBoxValue(insertedRowId: string, openedScreen: IOpenedScreen) { + const workbench = getWorkbench(openedScreen); + const newRecordScreenData = workbench.newRecordScreenData; + if (!newRecordScreenData) { + throw new Error("newRecordScreenData was not found"); + } + yield onFieldChange(newRecordScreenData.comboBoxProperty)({ + event: undefined, + row: newRecordScreenData.comboBoxRow, + property: newRecordScreenData.comboBoxProperty, + value: insertedRowId, + }); +} + + export function makeOnAddNewRecordClick(property: IProperty){ return async function onAddNewRecordClick(){ if (!property.lookup?.newRecordScreen) { From 2971d87cf35cd237362ff75aaa6e9fca070f1e33 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Tue, 24 Oct 2023 15:04:53 +0200 Subject: [PATCH 18/32] Close button is not shown on the new record screen --- frontend-html/src/gui/Workbench/ScreenArea/ScreenArea.tsx | 5 ++++- frontend-html/src/gui/connections/CDialogContent.tsx | 1 + 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/frontend-html/src/gui/Workbench/ScreenArea/ScreenArea.tsx b/frontend-html/src/gui/Workbench/ScreenArea/ScreenArea.tsx index e7b1ee9f95..f23ebb586b 100644 --- a/frontend-html/src/gui/Workbench/ScreenArea/ScreenArea.tsx +++ b/frontend-html/src/gui/Workbench/ScreenArea/ScreenArea.tsx @@ -40,6 +40,7 @@ import { isMobileLayoutActive } from "model/selectors/isMobileLayoutActive"; export const DialogScreen: React.FC<{ openedScreen: IOpenedScreen; bottomButtons: JSX.Element | null; + showCloseButton: boolean }> = observer((props) => { const key = `ScreenDialog@${props.openedScreen.menuItemId}@${props.openedScreen.order}`; const workbenchLifecycle = getWorkbenchLifecycle(props.openedScreen); @@ -83,7 +84,9 @@ export const DialogScreen: React.FC<{ !!window.localStorage.getItem("debugKeepProgressIndicatorsOn") } titleButtons={ - onScreenTabCloseClick(props.openedScreen)(event)}/> + props.showCloseButton + ? onScreenTabCloseClick(props.openedScreen)(event)}/> + : null } buttonsCenter={null} buttonsLeft={null} diff --git a/frontend-html/src/gui/connections/CDialogContent.tsx b/frontend-html/src/gui/connections/CDialogContent.tsx index b889a9de70..b809b4372f 100644 --- a/frontend-html/src/gui/connections/CDialogContent.tsx +++ b/frontend-html/src/gui/connections/CDialogContent.tsx @@ -39,6 +39,7 @@ export class CDialogContent extends React.Component { )} From 66c570ed0a7ce2aeace772462cbb9390b65da2bb Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Tue, 24 Oct 2023 15:29:44 +0200 Subject: [PATCH 19/32] Pressing Esc will close the new record screen --- .../gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx | 7 ++++++- .../actions-ui/ScreenTabHandleRow/onScreenTabCloseClick.ts | 4 ++-- .../entities/FormScreenLifecycle/FormScreenLifecycle.tsx | 4 ++-- .../src/model/entities/types/IFormScreenLifecycle.ts | 2 +- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx b/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx index 6efeb64f3f..f8d4875118 100644 --- a/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx +++ b/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx @@ -67,6 +67,7 @@ import { getIsCopyButtonVisible } from "model/selectors/DataView/getIsCopyButton import { geScreenActionButtonsState } from "model/actions-ui/ScreenToolbar/saveButtonVisible"; import { getOpenedScreen } from "model/selectors/getOpenedScreen"; import { onSaveClick } from "gui/connections/NewRecordScreen"; +import { onScreenTabCloseClick } from "model/actions-ui/ScreenTabHandleRow/onScreenTabCloseClick"; @inject(({property, formPanelView}) => { @@ -344,6 +345,7 @@ export class FormViewEditor extends React.Component<{ ctx: this.props.property, action: async () => { dataView.formFocusManager.stopAutoFocus(); + const openedScreen = getOpenedScreen(this.props.property); if (event.key === "Tab") { DomEvent.preventDefault(event); if (event.shiftKey) { @@ -392,6 +394,10 @@ export class FormViewEditor extends React.Component<{ return; } if (event.key === "Escape") { + if(openedScreen.isNewRecordScreen){ + onScreenTabCloseClick(openedScreen)(event, true); + return; + } await onEscapePressed(dataView, event); return; } @@ -400,7 +406,6 @@ export class FormViewEditor extends React.Component<{ } if (event.key === "Enter") { await this.props.onEditorBlur?.(); - const openedScreen = getOpenedScreen(this.props.property); if(openedScreen.isNewRecordScreen){ await onSaveClick(openedScreen); return; diff --git a/frontend-html/src/model/actions-ui/ScreenTabHandleRow/onScreenTabCloseClick.ts b/frontend-html/src/model/actions-ui/ScreenTabHandleRow/onScreenTabCloseClick.ts index e7ddf6ff31..6307f6a7d0 100644 --- a/frontend-html/src/model/actions-ui/ScreenTabHandleRow/onScreenTabCloseClick.ts +++ b/frontend-html/src/model/actions-ui/ScreenTabHandleRow/onScreenTabCloseClick.ts @@ -37,7 +37,7 @@ export function onScreenTabCloseMouseDown(ctx: any) { export function onScreenTabCloseClick(ctx: any) { - return flow(function*onFormTabCloseClick(event: any, isDueToError?: boolean) { + return flow(function*onFormTabCloseClick(event: any, closeWithoutSaving?: boolean) { const openedScreen = getOpenedScreen(ctx); let dataViews = openedScreen.content?.formScreen?.dataViews ?? []; for (const dataView of dataViews) { @@ -51,7 +51,7 @@ export function onScreenTabCloseClick(ctx: any) { // TODO: Better lifecycle handling if (openedScreen.content && !openedScreen.content.isLoading) { const lifecycle = getFormScreenLifecycle(openedScreen.content.formScreen!); - yield*lifecycle.onRequestScreenClose(isDueToError); + yield*lifecycle.onRequestScreenClose(closeWithoutSaving); } else { yield*closeForm(ctx)(); } diff --git a/frontend-html/src/model/entities/FormScreenLifecycle/FormScreenLifecycle.tsx b/frontend-html/src/model/entities/FormScreenLifecycle/FormScreenLifecycle.tsx index 5261d06d55..e157420e0a 100644 --- a/frontend-html/src/model/entities/FormScreenLifecycle/FormScreenLifecycle.tsx +++ b/frontend-html/src/model/entities/FormScreenLifecycle/FormScreenLifecycle.tsx @@ -180,7 +180,7 @@ export class FormScreenLifecycle02 implements IFormScreenLifecycle02 { yield*this.executeAction(gridId, entity, action, selectedItems); } - *onRequestScreenClose(isDueToError?: boolean): Generator { + *onRequestScreenClose(closeWithoutSaving?: boolean): Generator { const formScreen = getFormScreen(this); for (let dataView of formScreen.dataViews) { yield onFieldBlur(dataView)(); @@ -188,7 +188,7 @@ export class FormScreenLifecycle02 implements IFormScreenLifecycle02 { // Just wait if there is some data manipulation in progress. yield formScreen.dataUpdateCRS.runAsync(() => Promise.resolve()); - if (isDueToError || !getIsFormScreenDirty(this) || getIsSuppressSave(this)) { + if (closeWithoutSaving || !getIsFormScreenDirty(this) || getIsSuppressSave(this)) { yield*this.closeForm(); return; } diff --git a/frontend-html/src/model/entities/types/IFormScreenLifecycle.ts b/frontend-html/src/model/entities/types/IFormScreenLifecycle.ts index 75adcb3271..1b94fa53c3 100644 --- a/frontend-html/src/model/entities/types/IFormScreenLifecycle.ts +++ b/frontend-html/src/model/entities/types/IFormScreenLifecycle.ts @@ -93,7 +93,7 @@ export interface IFormScreenLifecycle02 extends IFormScreenLifecycleData { updateTotalRowCount(dataView: IDataView): Promise; - onRequestScreenClose(isDueToError?: boolean): Generator; + onRequestScreenClose(closeWithoutSaving?: boolean): Generator; clearAutoRefreshInterval(): void; From dbc9e10a197cf9c76f047431867ef03bbea46ff2 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Wed, 25 Oct 2023 11:06:43 +0200 Subject: [PATCH 20/32] Added NoRecursiveNewRecordScreenBindings rule --- .../SchemaItems/NewRecordScreenBinding.cs | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs b/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs index 5bd51e06f1..5cde10ca36 100644 --- a/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs +++ b/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs @@ -1,8 +1,11 @@ using System; +using System.Collections.Generic; using System.ComponentModel; +using System.Linq; using System.Xml.Serialization; using Origam.DA.Common; using Origam.DA.ObjectPersistence; +using Origam.Schema.EntityModel; using Origam.Schema.MenuModel; namespace Origam.Schema.LookupModel; @@ -54,6 +57,7 @@ public override bool CanMove(UI.IBrowserNode2 newNode) [TypeConverter(typeof(MenuItemConverter))] [NotNullModelElementRule] [NotNullMenuRecordEditMethod] + [NoRecursiveNewRecordScreenBindings] [XmlReference("menuItem", "MenuItemId")] public AbstractMenuItem MenuItem { @@ -82,4 +86,68 @@ public AbstractMenuItem MenuItem public string AuthorizationContext => Roles; #endregion +} + + + +[AttributeUsage(AttributeTargets.Property, AllowMultiple=false, Inherited=true)] +class NoRecursiveNewRecordScreenBindings : AbstractModelElementRuleAttribute +{ + public override Exception CheckRule(object instance) + { + if (!(instance is NewRecordScreenBinding newRecordScreenBinding)) + { + return new Exception( + $"{nameof(NoRecursiveNewRecordScreenBindings)} can be only applied to type {nameof(NewRecordScreenBinding)}"); + } + + if (newRecordScreenBinding.MenuItem == null) + { + return null; + } + + if (newRecordScreenBinding.MenuItem is not FormReferenceMenuItem menuItem) + { + return new Exception( + $"The MenuItem in {nameof(NewRecordScreenBinding)} must be a {nameof(FormReferenceMenuItem)}"); + } + + var allEntities = menuItem.Screen.DataStructure.Entities + .Cast() + .SelectMany(entity => entity.ChildrenRecursive.OfType().Concat(new []{entity})); + + IEnumerable allLookups = allEntities + .SelectMany(entity => + { + var dataStructureColumnLookups = entity.ChildItems.ToGeneric() + .OfType() + .Where(x => x.UseLookupValue) + .Select(x => x.FinalLookup); + var entityLookups = entity.EntityDefinition.EntityColumns + .OfType() + .Select(lookupField => lookupField.DefaultLookup); + return dataStructureColumnLookups.Concat(entityLookups); + }) + .Distinct(); + + var conflictingNewRecordBindingIds = allLookups + .Select(lookup => lookup.ChildItems.ToGeneric().OfType().FirstOrDefault()) + .Where(binding => binding != null) + .Select(binding => binding.Id.ToString()) + .ToList(); + + if (conflictingNewRecordBindingIds.Count != 0) + { + return new Exception("The selected menu item references a screen with NewRecordScreenBindings. " + + "This would lead to nested NewRecordScreenBindings and is therefore not allowed. " + + $"The conflicting NewRecordScreenBinding ids are: [{string.Join(", ",conflictingNewRecordBindingIds)}]"); + } + + return null; + } + + public override Exception CheckRule(object instance, string memberName) + { + return CheckRule(instance); + } } \ No newline at end of file From 9153278183d14f32ef7b7c7fffdb10c51a29cac7 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Wed, 25 Oct 2023 13:12:21 +0200 Subject: [PATCH 21/32] New record screen works in form view --- .../ScreenArea/FormView/FormBuilder.tsx | 18 +++++------------- .../Workbench/ScreenArea/FormView/FormRoot.tsx | 5 ++++- .../ScreenArea/FormView/FormViewEditor.tsx | 4 +++- .../src/gui/connections/NewRecordScreen.tsx | 12 ++++++++++-- 4 files changed, 22 insertions(+), 17 deletions(-) diff --git a/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormBuilder.tsx b/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormBuilder.tsx index a5dac6f712..e38a640fe3 100644 --- a/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormBuilder.tsx +++ b/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormBuilder.tsx @@ -55,18 +55,6 @@ export class FormBuilder extends React.Component<{ }> { static contextType = CtxPanelVisibility - componentDidMount() { - document.addEventListener("click", event => this.notifyClick(event)) - } - - componentWillUnmount() { - document.removeEventListener("click", event => this.notifyClick(event)); - } - - notifyClick(event: any) { - this.props.dataView!.formFocusManager.setLastFocused(event.target); - } - onKeyDown(event: any) { if (event.key === "Tab") { DomEvent.preventDefault(event); @@ -98,7 +86,11 @@ export class FormBuilder extends React.Component<{ function recursive(xfo: any) { if (xfo.name === "FormRoot") { return ( - + {xfo.elements.map((child: any) => recursive(child))} ); diff --git a/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormRoot.tsx b/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormRoot.tsx index b94a111010..e22a7657e9 100644 --- a/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormRoot.tsx +++ b/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormRoot.tsx @@ -22,10 +22,12 @@ import React from "react"; import { observer } from "mobx-react"; import { action, observable } from "mobx"; import cx from "classnames"; +import { IDataView } from "model/entities/types/IDataView"; @observer export class FormRoot extends React.Component<{ className?: string; + dataView: IDataView; style?: any }> { @@ -55,7 +57,8 @@ export class FormRoot extends React.Component<{ } @action.bound handleWindowClick(event: any) { - if (this.elmFormRoot && !this.elmFormRoot.contains(event.target)) { + if (this.elmFormRoot && this.elmFormRoot.contains(event.target) && event.target.tagName !== "DIV") { + this.props.dataView!.formFocusManager.setLastFocused(event.target); } } diff --git a/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx b/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx index f8d4875118..c023db3134 100644 --- a/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx +++ b/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx @@ -66,7 +66,7 @@ import { getIsDelButtonVisible } from "model/selectors/DataView/getIsDelButtonVi import { getIsCopyButtonVisible } from "model/selectors/DataView/getIsCopyButtonVisible"; import { geScreenActionButtonsState } from "model/actions-ui/ScreenToolbar/saveButtonVisible"; import { getOpenedScreen } from "model/selectors/getOpenedScreen"; -import { onSaveClick } from "gui/connections/NewRecordScreen"; +import { makeOnAddNewRecordClick, onSaveClick } from "gui/connections/NewRecordScreen"; import { onScreenTabCloseClick } from "model/actions-ui/ScreenTabHandleRow/onScreenTabCloseClick"; @@ -237,6 +237,8 @@ export class FormViewEditor extends React.Component<{ backgroundColor={backgroundColor} foregroundColor={foregroundColor} customStyle={property.style} + newRecordScreen={this.props.property?.lookup?.newRecordScreen} + onAddNewRecordClick={makeOnAddNewRecordClick(this.props.property!)} isLink={property.isLink} onClick={(event) => { onDropdownEditorClick(property)(event, property, row); diff --git a/frontend-html/src/gui/connections/NewRecordScreen.tsx b/frontend-html/src/gui/connections/NewRecordScreen.tsx index 4e7513d588..acec21c5dc 100644 --- a/frontend-html/src/gui/connections/NewRecordScreen.tsx +++ b/frontend-html/src/gui/connections/NewRecordScreen.tsx @@ -34,6 +34,7 @@ import { getWorkbench } from "model/selectors/getWorkbench"; import { NewRecordScreenData } from "model/entities/NewRecordScreenData"; import { getDataView } from "model/selectors/DataView/getDataView"; import cx from "classnames"; +import { getFormFocusManager } from "model/selectors/DataView/getFormFocusManager"; export class NewRecordScreen { private _width: number; @@ -111,8 +112,15 @@ function afterClose(openedScreen: IOpenedScreen) { if (!newRecordScreenData) { throw new Error("newRecordScreenData was not found"); } - const comboBoxTablePanelView = getTablePanelView(newRecordScreenData.comboBoxProperty); - comboBoxTablePanelView.isEditing = true; + const dataView = getDataView(newRecordScreenData.comboBoxProperty); + if (dataView.isTableViewActive()) { + const comboBoxTablePanelView = getTablePanelView(newRecordScreenData.comboBoxProperty); + comboBoxTablePanelView.isEditing = true; + } + else if (dataView.isFormViewActive()) { + const formFocusManager = getFormFocusManager(dataView); + formFocusManager.refocusLast(); + } workbench.newRecordScreenData = undefined; } From 120c40e88881aa2033383220e6a8d5d2fe4d41e9 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Wed, 25 Oct 2023 13:57:40 +0200 Subject: [PATCH 22/32] Build fixes --- .../gui/connections/MobileComponents/Form/MobileFormBuilder.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend-html/src/gui/connections/MobileComponents/Form/MobileFormBuilder.tsx b/frontend-html/src/gui/connections/MobileComponents/Form/MobileFormBuilder.tsx index e82a09bbce..86ddb6bcb8 100644 --- a/frontend-html/src/gui/connections/MobileComponents/Form/MobileFormBuilder.tsx +++ b/frontend-html/src/gui/connections/MobileComponents/Form/MobileFormBuilder.tsx @@ -149,6 +149,7 @@ export class MobileFormBuilder extends React.Component<{ return ( From ae48d6d82c48b53113b10fb8136422d800da6629 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Wed, 25 Oct 2023 19:46:05 +0200 Subject: [PATCH 23/32] License added --- .../SchemaItems/NewRecordScreenBinding.cs | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs b/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs index 5cde10ca36..b9fb4d76b7 100644 --- a/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs +++ b/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs @@ -1,4 +1,25 @@ -using System; +#region license +/* +Copyright 2005 - 2023 Advantage Solutions, s. r. o. + +This file is part of ORIGAM (http://www.origam.org). + +ORIGAM is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +ORIGAM is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with ORIGAM. If not, see . +*/ +#endregion + +using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; From e14ae8d9d4cf5c055f435e6c576af1469f9b8852 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Wed, 25 Oct 2023 19:50:29 +0200 Subject: [PATCH 24/32] License added --- .../connections/NewRecordScreen.module.scss | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/frontend-html/src/gui/connections/NewRecordScreen.module.scss b/frontend-html/src/gui/connections/NewRecordScreen.module.scss index 718c1658d1..5305d15fc0 100644 --- a/frontend-html/src/gui/connections/NewRecordScreen.module.scss +++ b/frontend-html/src/gui/connections/NewRecordScreen.module.scss @@ -1,3 +1,22 @@ +/* +Copyright 2005 - 2023 Advantage Solutions, s. r. o. + +This file is part of ORIGAM (http://www.origam.org). + +ORIGAM is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +ORIGAM is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with ORIGAM. If not, see . +*/ + #defaultSaveButton { background: var(--foreground1); border-color: var(--foreground1); From a20036ced9929b31d3e1501d94eafce85c095b21 Mon Sep 17 00:00:00 2001 From: Petr Hrehorovsky <83349812+washibana@users.noreply.github.com> Date: Thu, 26 Oct 2023 08:54:06 +0200 Subject: [PATCH 25/32] Code formatting --- .../src/gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx b/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx index c023db3134..dbdf93d0f7 100644 --- a/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx +++ b/frontend-html/src/gui/Workbench/ScreenArea/FormView/FormViewEditor.tsx @@ -396,7 +396,7 @@ export class FormViewEditor extends React.Component<{ return; } if (event.key === "Escape") { - if(openedScreen.isNewRecordScreen){ + if (openedScreen.isNewRecordScreen){ onScreenTabCloseClick(openedScreen)(event, true); return; } @@ -408,7 +408,7 @@ export class FormViewEditor extends React.Component<{ } if (event.key === "Enter") { await this.props.onEditorBlur?.(); - if(openedScreen.isNewRecordScreen){ + if (openedScreen.isNewRecordScreen){ await onSaveClick(openedScreen); return; } From 9fafc34be75b9f91b7a9e5cb4717f3bf9dab3303 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Thu, 26 Oct 2023 09:54:26 +0200 Subject: [PATCH 26/32] Redundant import removed --- frontend-html/src/model/factories/createWorkbench.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend-html/src/model/factories/createWorkbench.ts b/frontend-html/src/model/factories/createWorkbench.ts index 318c719236..e91c7805e8 100644 --- a/frontend-html/src/model/factories/createWorkbench.ts +++ b/frontend-html/src/model/factories/createWorkbench.ts @@ -37,7 +37,6 @@ import { Notifications } from "model/entities/Notifications"; import { Favorites } from "model/entities/Favorites"; import { SidebarState } from "model/entities/SidebarState"; import { About } from "model/entities/AboutInfo"; -import { NewRecordScreenData } from "model/entities/NewRecordScreenData"; export function createWorkbench() { const clock = new Clock(); From e105179de24fe993d2e92b33bb92f41009daf488 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Fri, 27 Oct 2023 12:38:17 +0200 Subject: [PATCH 27/32] Lookups are resolved from ScreenSections --- .../SchemaItems/NewRecordScreenBinding.cs | 55 +++++++++++++------ 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs b/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs index b9fb4d76b7..565e6475f7 100644 --- a/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs +++ b/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs @@ -20,13 +20,16 @@ #endregion using System; +using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Runtime.InteropServices.ComTypes; using System.Xml.Serialization; using Origam.DA.Common; using Origam.DA.ObjectPersistence; using Origam.Schema.EntityModel; +using Origam.Schema.GuiModel; using Origam.Schema.MenuModel; namespace Origam.Schema.LookupModel; @@ -78,7 +81,7 @@ public override bool CanMove(UI.IBrowserNode2 newNode) [TypeConverter(typeof(MenuItemConverter))] [NotNullModelElementRule] [NotNullMenuRecordEditMethod] - [NoRecursiveNewRecordScreenBindings] + [NoRecursiveNewRecordScreenBindingsRule] [XmlReference("menuItem", "MenuItemId")] public AbstractMenuItem MenuItem { @@ -112,14 +115,14 @@ public AbstractMenuItem MenuItem [AttributeUsage(AttributeTargets.Property, AllowMultiple=false, Inherited=true)] -class NoRecursiveNewRecordScreenBindings : AbstractModelElementRuleAttribute +class NoRecursiveNewRecordScreenBindingsRule : AbstractModelElementRuleAttribute { public override Exception CheckRule(object instance) { if (!(instance is NewRecordScreenBinding newRecordScreenBinding)) { return new Exception( - $"{nameof(NoRecursiveNewRecordScreenBindings)} can be only applied to type {nameof(NewRecordScreenBinding)}"); + $"{nameof(NoRecursiveNewRecordScreenBindingsRule)} can be only applied to type {nameof(NewRecordScreenBinding)}"); } if (newRecordScreenBinding.MenuItem == null) @@ -133,21 +136,14 @@ public override Exception CheckRule(object instance) $"The MenuItem in {nameof(NewRecordScreenBinding)} must be a {nameof(FormReferenceMenuItem)}"); } - var allEntities = menuItem.Screen.DataStructure.Entities - .Cast() - .SelectMany(entity => entity.ChildrenRecursive.OfType().Concat(new []{entity})); - - IEnumerable allLookups = allEntities - .SelectMany(entity => + IEnumerable allLookups = menuItem.Screen.ChildrenRecursive + .OfType() + .SelectMany(controlSetItem => { - var dataStructureColumnLookups = entity.ChildItems.ToGeneric() - .OfType() - .Where(x => x.UseLookupValue) - .Select(x => x.FinalLookup); - var entityLookups = entity.EntityDefinition.EntityColumns - .OfType() - .Select(lookupField => lookupField.DefaultLookup); - return dataStructureColumnLookups.Concat(entityLookups); + IEnumerable panelControlSets = FindScreenSections(controlSetItem); + IEnumerable comboBoxes = panelControlSets.SelectMany(FindComboBoxes); + IEnumerable lookups = comboBoxes.SelectMany(GetLookups); + return lookups; }) .Distinct(); @@ -167,6 +163,31 @@ public override Exception CheckRule(object instance) return null; } + private IEnumerable FindScreenSections(ControlSetItem controlSetItem) + { + var dependencies = new ArrayList(); + controlSetItem.GetExtraDependencies(dependencies); + return dependencies.OfType(); + } + + private IEnumerable FindComboBoxes(PanelControlSet panelControlSet) + { + if (panelControlSet == null) + { + return Enumerable.Empty(); + } + return panelControlSet.ChildrenRecursive + .OfType() + .Where(item => item.Name == "AsCombo"); + } + + private IEnumerable GetLookups(ControlSetItem comboBox) + { + var dependencies = new ArrayList(); + comboBox.GetExtraDependencies(dependencies); + return dependencies.OfType(); + } + public override Exception CheckRule(object instance, string memberName) { return CheckRule(instance); From 815673cd3266a082e1b6ba392b7dd7030918abe0 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Fri, 27 Oct 2023 13:00:23 +0200 Subject: [PATCH 28/32] if statement simplified --- .../SchemaItems/NewRecordScreenBinding.cs | 2 +- backend/Origam.Server/ServerCoreUIService.cs | 3 +-- backend/Origam.Server/Session Stores/NewRecordSessionStore.cs | 2 ++ 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs b/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs index 565e6475f7..885cb1211f 100644 --- a/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs +++ b/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs @@ -52,7 +52,7 @@ public class NewRecordScreenBinding : AbstractSchemaItem, IAuthorizationContextC #region Overriden AbstractSchemaItem Members public override string ItemType => CategoryConst; - public override void GetExtraDependencies(System.Collections.ArrayList dependencies) + public override void GetExtraDependencies(ArrayList dependencies) { dependencies.Add(MenuItem); diff --git a/backend/Origam.Server/ServerCoreUIService.cs b/backend/Origam.Server/ServerCoreUIService.cs index e2ac602e5c..9c8ed1524b 100644 --- a/backend/Origam.Server/ServerCoreUIService.cs +++ b/backend/Origam.Server/ServerCoreUIService.cs @@ -146,8 +146,7 @@ public PortalResult InitPortal(int maxRequestLength) var sessionStore = mainSessionStore.ActiveSession ?? mainSessionStore; if(sessionStore is SelectionDialogSessionStore - || sessionStore.IsModalDialog - || sessionStore is NewRecordSessionStore) + || sessionStore.IsModalDialog) { sessionsToDestroy.Add(sessionStore.Id); } diff --git a/backend/Origam.Server/Session Stores/NewRecordSessionStore.cs b/backend/Origam.Server/Session Stores/NewRecordSessionStore.cs index 4c187db99a..a0fc5c12bb 100644 --- a/backend/Origam.Server/Session Stores/NewRecordSessionStore.cs +++ b/backend/Origam.Server/Session Stores/NewRecordSessionStore.cs @@ -37,12 +37,14 @@ public class NewRecordSessionStore : FormSessionStore string name, FormReferenceMenuItem menuItem, Analytics analytics) : base(service, request, name, menuItem, analytics) { + IsModalDialog = true; } public NewRecordSessionStore(IBasicUIService service, UIRequest request, string name, Analytics analytics) : base(service, request, name, analytics) { + IsModalDialog = true; } public override void Init() From 12277aa95907582256b95db68d9c6cecfbd297af Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Fri, 27 Oct 2023 13:02:24 +0200 Subject: [PATCH 29/32] Blank line removed --- backend/Origam.Workbench.Services/DataLookupService.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/Origam.Workbench.Services/DataLookupService.cs b/backend/Origam.Workbench.Services/DataLookupService.cs index 74b70c953d..e4ab04cb0d 100644 --- a/backend/Origam.Workbench.Services/DataLookupService.cs +++ b/backend/Origam.Workbench.Services/DataLookupService.cs @@ -609,8 +609,7 @@ public NewRecordScreenBinding GetNewRecordScreenBinding(AbstractDataLookup looku ServiceManager.Services.GetService(typeof(IParameterService)) as IParameterService; IOrigamAuthorizationProvider authorizationProvider = SecurityManager.GetAuthorizationProvider(); IPrincipal principal = SecurityManager.CurrentPrincipal; - - + var newRecordScreenBinding = lookup.ChildItems .ToGeneric() .OfType() From bfc5b4b2c2396a0a22f3df42b041304ac1ad4720 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Fri, 27 Oct 2023 13:11:32 +0200 Subject: [PATCH 30/32] Redundant code removed --- .../Origam.Server/Session Stores/NewRecordSessionStore.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/backend/Origam.Server/Session Stores/NewRecordSessionStore.cs b/backend/Origam.Server/Session Stores/NewRecordSessionStore.cs index a0fc5c12bb..269eaa2199 100644 --- a/backend/Origam.Server/Session Stores/NewRecordSessionStore.cs +++ b/backend/Origam.Server/Session Stores/NewRecordSessionStore.cs @@ -67,11 +67,6 @@ public override void Init() row, true, SecurityManager.CurrentUserProfile().Id); row.Table.NewRow(); - Workbench.Services.CoreServices.DataService.Instance.StoreData( - dataStructureId: rootEntity.ParentItemId, - data: row.Table.DataSet, - loadActualValuesAfterUpdate: false, - transactionId: null); dataSet.Tables[rootEntity.Name].Rows.Add(row); SetDataSource(dataSet); } From f515423a77c6a60c97eae3c8f0f017df951a1c80 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Fri, 27 Oct 2023 13:16:22 +0200 Subject: [PATCH 31/32] First valid NewRecordScreenBinding returned --- .../DataLookupService.cs | 28 ++++++------------- 1 file changed, 8 insertions(+), 20 deletions(-) diff --git a/backend/Origam.Workbench.Services/DataLookupService.cs b/backend/Origam.Workbench.Services/DataLookupService.cs index e4ab04cb0d..271bf7e723 100644 --- a/backend/Origam.Workbench.Services/DataLookupService.cs +++ b/backend/Origam.Workbench.Services/DataLookupService.cs @@ -605,30 +605,18 @@ public IMenuBindingResult GetMenuBinding(Guid lookupId, object value) public NewRecordScreenBinding GetNewRecordScreenBinding(AbstractDataLookup lookup) { - IParameterService param = - ServiceManager.Services.GetService(typeof(IParameterService)) as IParameterService; + IParameterService parameterService = ServiceManager.Services.GetService(); IOrigamAuthorizationProvider authorizationProvider = SecurityManager.GetAuthorizationProvider(); IPrincipal principal = SecurityManager.CurrentPrincipal; - - var newRecordScreenBinding = lookup.ChildItems + + return lookup.ChildItems .ToGeneric() .OfType() - .FirstOrDefault(); - - if (newRecordScreenBinding == null) - { - return null; - } - - bool isAvailable = authorizationProvider.Authorize(principal, newRecordScreenBinding.AuthorizationContext) - && authorizationProvider.Authorize(principal, newRecordScreenBinding.MenuItem.AuthorizationContext) - && param.IsFeatureOn(newRecordScreenBinding.MenuItem.Features); - if (!isAvailable) - { - return null; - } - - return newRecordScreenBinding; + .FirstOrDefault(binding => + authorizationProvider.Authorize(principal, binding.AuthorizationContext) + && authorizationProvider.Authorize(principal, binding.MenuItem.AuthorizationContext) + && parameterService.IsFeatureOn(binding.MenuItem.Features) + ); } public DataLookupMenuBinding GetMenuBindingElement(AbstractDataLookup lookup, object value) From b582ff63215043302a465214da9103c5e5f58e15 Mon Sep 17 00:00:00 2001 From: Jindrich Susen Date: Mon, 30 Oct 2023 19:51:29 +0100 Subject: [PATCH 32/32] Read only role and read only access are checked when looking for available NewRecordScreenBinding --- .../SchemaItems/NewRecordScreenBinding.cs | 41 ++++++++++++++++++- .../DataLookupService.cs | 10 +---- 2 files changed, 40 insertions(+), 11 deletions(-) diff --git a/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs b/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs index 885cb1211f..25e8ea2ee9 100644 --- a/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs +++ b/backend/Origam.Schema.LookupModel/SchemaItems/NewRecordScreenBinding.cs @@ -24,13 +24,14 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; -using System.Runtime.InteropServices.ComTypes; +using System.Security.Principal; using System.Xml.Serialization; using Origam.DA.Common; using Origam.DA.ObjectPersistence; using Origam.Schema.EntityModel; using Origam.Schema.GuiModel; using Origam.Schema.MenuModel; +using Origam.Workbench.Services; namespace Origam.Schema.LookupModel; @@ -48,7 +49,43 @@ public class NewRecordScreenBinding : AbstractSchemaItem, IAuthorizationContextC public NewRecordScreenBinding(Guid schemaExtensionId) : base(schemaExtensionId) { } public NewRecordScreenBinding(Key primaryKey) : base(primaryKey) {} - + + + public bool IsAvailable + { + get + { + if (MenuItem is not FormReferenceMenuItem referenceMenuItem || + referenceMenuItem.ReadOnlyAccess) + { + return false; + } + + IParameterService parameterService = ServiceManager.Services + .GetService(); + IOrigamAuthorizationProvider authorizationProvider = + SecurityManager.GetAuthorizationProvider(); + IPrincipal principal = SecurityManager.CurrentPrincipal; + + string authContext = SecurityManager + .GetReadOnlyRoles(referenceMenuItem.AuthorizationContext); + bool hasReadOnlyRole = SecurityManager + .GetAuthorizationProvider() + .Authorize(SecurityManager.CurrentPrincipal, authContext); + if (hasReadOnlyRole) + { + return false; + } + + return + authorizationProvider.Authorize(principal, AuthorizationContext) + && authorizationProvider.Authorize(principal, + MenuItem.AuthorizationContext) + && parameterService.IsFeatureOn(MenuItem.Features); + } + } + + #region Overriden AbstractSchemaItem Members public override string ItemType => CategoryConst; diff --git a/backend/Origam.Workbench.Services/DataLookupService.cs b/backend/Origam.Workbench.Services/DataLookupService.cs index 271bf7e723..891fb66e1a 100644 --- a/backend/Origam.Workbench.Services/DataLookupService.cs +++ b/backend/Origam.Workbench.Services/DataLookupService.cs @@ -605,18 +605,10 @@ public IMenuBindingResult GetMenuBinding(Guid lookupId, object value) public NewRecordScreenBinding GetNewRecordScreenBinding(AbstractDataLookup lookup) { - IParameterService parameterService = ServiceManager.Services.GetService(); - IOrigamAuthorizationProvider authorizationProvider = SecurityManager.GetAuthorizationProvider(); - IPrincipal principal = SecurityManager.CurrentPrincipal; - return lookup.ChildItems .ToGeneric() .OfType() - .FirstOrDefault(binding => - authorizationProvider.Authorize(principal, binding.AuthorizationContext) - && authorizationProvider.Authorize(principal, binding.MenuItem.AuthorizationContext) - && parameterService.IsFeatureOn(binding.MenuItem.Features) - ); + .FirstOrDefault(x => x.IsAvailable); } public DataLookupMenuBinding GetMenuBindingElement(AbstractDataLookup lookup, object value)