From 78b1a2685e48de973ae4d2e0a06d545664751b46 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Tue, 11 Jul 2023 16:58:59 -0400 Subject: [PATCH 1/4] wip, combo-box not rendering correctly --- .../Components/Layers/FeatureLayer.cs | 74 +++++- .../Components/Widgets/Domain.cs | 112 +++++++++ .../Components/Widgets/FormElement.cs | 213 ++++++++++++++++++ .../Components/Widgets/FormInput.cs | 184 +++++++++++++++ .../Components/Widgets/FormTemplate.cs | 63 ++++++ src/dymaptic.GeoBlazor.Core/Objects/Query.cs | 2 +- .../Scripts/arcGisJsInterop.ts | 7 +- .../Scripts/definitions.d.ts | 22 ++ .../Scripts/featureLayer.ts | 8 +- .../Scripts/jsBuilder.ts | 181 ++++++++++++++- src/dymaptic.GeoBlazor.Core/package-lock.json | 170 ++++++++------ src/dymaptic.GeoBlazor.Core/package.json | 4 +- 12 files changed, 953 insertions(+), 87 deletions(-) create mode 100644 src/dymaptic.GeoBlazor.Core/Components/Widgets/Domain.cs create mode 100644 src/dymaptic.GeoBlazor.Core/Components/Widgets/FormElement.cs create mode 100644 src/dymaptic.GeoBlazor.Core/Components/Widgets/FormInput.cs create mode 100644 src/dymaptic.GeoBlazor.Core/Components/Widgets/FormTemplate.cs diff --git a/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayer.cs b/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayer.cs index 41114ae0..c8a931f9 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayer.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayer.cs @@ -1,6 +1,7 @@ using dymaptic.GeoBlazor.Core.Components.Geometries; using dymaptic.GeoBlazor.Core.Components.Popups; using dymaptic.GeoBlazor.Core.Components.Renderers; +using dymaptic.GeoBlazor.Core.Components.Widgets; using dymaptic.GeoBlazor.Core.Exceptions; using dymaptic.GeoBlazor.Core.Objects; using Microsoft.AspNetCore.Components; @@ -227,6 +228,8 @@ public IReadOnlyCollection? Fields /// another layer or table. /// public Relationship[]? Relationships { get; set; } + + public FormTemplate? FormTemplate { get; set; } /// public override string LayerType => "feature"; @@ -300,6 +303,12 @@ public async Task SetPopupTemplate(PopupTemplate template) await RegisterChildComponent(template); } + public async Task ApplyEdits(FeatureEdits edits, FeatureEditOptions? options = null) + { + return await JsLayerReference!.InvokeAsync("applyEdits", edits, options, + View!.Id); + } + /// public override async Task RegisterChildComponent(MapComponent child) { @@ -381,6 +390,14 @@ public override async Task RegisterChildComponent(MapComponent child) LayerChanged = true; } + break; + case FormTemplate formTemplate: + if (!formTemplate.Equals(FormTemplate)) + { + FormTemplate = formTemplate; + LayerChanged = true; + } + break; default: await base.RegisterChildComponent(child); @@ -826,7 +843,8 @@ internal override async Task UpdateFromJavaScript(Layer renderedLayer) { await base.UpdateFromJavaScript(renderedLayer); var renderedFeatureLayer = (FeatureLayer)renderedLayer; - Url = renderedFeatureLayer.Url; + Url ??= renderedFeatureLayer.Url; + Title ??= renderedFeatureLayer.Title; if (renderedFeatureLayer.Source is not null && renderedFeatureLayer.Source.Any() && Source is null) @@ -963,4 +981,56 @@ public class CreatePopupTemplateOptions /// An array of field names set to be visible within the PopupTemplate. /// public HashSet? VisibleFieldNames { get; set; } -} \ No newline at end of file +} + +public class FeatureEdits +{ + public IEnumerable? AddFeatures { get; set; } + public IEnumerable? UpdateFeatures { get; set; } + public IEnumerable? DeleteFeatures { get; set; } + public IEnumerable? AddAttachments { get; set; } + public IEnumerable? UpdateAttachments { get; set; } + public IEnumerable? DeleteAttachments { get; set; } +} + +public class AttachmentEdit +{ + public Graphic Feature { get; set; } = default!; + public Attachment Attachment { get; set; } = default!; +} + +public class Attachment +{ + public string GlobalId { get; set; } = default!; + public string? Name { get; set; } + public string? ContentType { get; set; } + public string? UploadId { get; set; } + public string? Data { get; set; } +} + +public class FeatureEditOptions +{ + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? GdbVersion { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public bool? ReturnEditMoment { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? ReturnServiceEditsOption { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public bool? RollbackOnFailureEnabled { get; set; } + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public bool? GlobalIdUsed { get; set; } +} + +public record FeatureEditsResult(FeatureEditResult[] AddFeatureResults, FeatureEditResult[] UpdateFeatureResults, + FeatureEditResult[] DeleteFeatureResults, FeatureEditResult[] AddAttachmentResults, + FeatureEditResult[] UpdateAttachmentResults, FeatureEditResult[] DeleteAttachmentResults, + EditedFeatureResult[]? EditedFeatureResults, long? EditMoment); + +public record FeatureEditResult(long? ObjectId, string? GlobalId, EditError? Error); +public record EditError(string? Name, string? Message); +public record EditedFeatureResult(long? LayerId, EditedFeatures? EditedFeatures); + +public record EditedFeatures(Graphic[] Adds, EditedFeatureUpdate[] Updates, Graphic[] Deletes, + SpatialReference SpatialReference); +public record EditedFeatureUpdate(Graphic[] Original, Graphic[] Current); \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/Widgets/Domain.cs b/src/dymaptic.GeoBlazor.Core/Components/Widgets/Domain.cs new file mode 100644 index 00000000..16b6eb9a --- /dev/null +++ b/src/dymaptic.GeoBlazor.Core/Components/Widgets/Domain.cs @@ -0,0 +1,112 @@ +using Microsoft.AspNetCore.Components; + + +namespace dymaptic.GeoBlazor.Core.Components.Widgets; + +/// +/// Domains define constraints on a layer field. There are two types of domains: coded values and range domains. This is an abstract class. +/// ArcGIS JS API +/// +public abstract class Domain : MapComponent +{ + /// + /// The domain type. + /// + public abstract string Type { get; } + + /// + /// The domain name. + /// + [Parameter] + public string? Name { get; set; } +} + +/// +/// Information about the coded values belonging to the domain. Coded value domains specify a valid set of values for a field. Each valid value is assigned a unique name. For example, in a layer for water mains, water main features may be buried under different types of surfaces as signified by a GroundSurfaceType field: pavement, gravel, sand, or none (for exposed water mains). The coded value domain includes both the actual value that is stored in the database (for example, 1 for pavement) and a more user-friendly description of what that value actually means. +/// ArcGIS JS API +/// +public class CodedValueDomain : Domain +{ + /// + public override string Type => "coded-value"; + + /// + /// An array of the coded values in the domain. + /// + public HashSet? CodedValues { get; set; } + + /// + public override async Task RegisterChildComponent(MapComponent child) + { + switch (child) + { + case CodedValue codedValue: + CodedValues ??= new HashSet(); + + CodedValues.Add(codedValue); + + break; + default: + await base.RegisterChildComponent(child); + + break; + } + } + + /// + public override async Task UnregisterChildComponent(MapComponent child) + { + switch (child) + { + case CodedValue codedValue: + CodedValues?.Remove(codedValue); + + break; + default: + await base.UnregisterChildComponent(child); + + break; + } + } +} + +/// +/// The coded value in a domain. +/// ArcGIS JS API +/// +public class CodedValue : MapComponent +{ + /// + /// The name of the coded value. + /// + [Parameter] + public string? Name { get; set; } + + /// + /// The value of the code. + /// + [Parameter] + public string? Code { get; set; } +} + +/// +/// Range domains specify a valid minimum and maximum valid value that can be stored in numeric and date fields. +/// ArcGIS JS API +/// +public class RangeDomain : Domain +{ + /// + public override string Type => "range"; + + /// + /// The maximum valid value. + /// + [Parameter] + public double? MaxValue { get; set; } + + /// + /// The minimum valid value. + /// + [Parameter] + public double? MinValue { get; set; } +} \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/Widgets/FormElement.cs b/src/dymaptic.GeoBlazor.Core/Components/Widgets/FormElement.cs new file mode 100644 index 00000000..00def72e --- /dev/null +++ b/src/dymaptic.GeoBlazor.Core/Components/Widgets/FormElement.cs @@ -0,0 +1,213 @@ +using dymaptic.GeoBlazor.Core.Serialization; +using Microsoft.AspNetCore.Components; +using System.Text.Json; +using System.Text.Json.Serialization; + + +namespace dymaptic.GeoBlazor.Core.Components.Widgets; +[JsonConverter(typeof(FormElementConverter))] +public abstract class FormElement: MapComponent +{ + /// + /// The element's description providing the purpose behind it. + /// + [Parameter] + public string? Description { get; set; } + + /// + /// A string value containing the field alias. This is not to Arcade expressions as the title is used instead. + /// + [Parameter] + public string? Label { get; set; } + + /// + /// A reference to the name of an Arcade expression defined in the expressionInfos of the FormTemplate. The expression must follow the specification defined by the Form Constraint Profile. Expressions may reference field values using the $feature profile variable and must return either true or false. + /// + /// + /// When this expression evaluates to true, the element is displayed. When the expression evaluates to false the element is not displayed. If no expression is provided, the element is always displayed. Care must be taken when defining a visibility expression for a non-nullable field i.e. to make sure that such fields either have default values or are made visible to users so that they can provide a value before submitting the form. + /// The referenced expression must be defined in the form template's expressionInfos. It cannot be set inline within the element object. + /// + [Parameter] + public string? VisibilityExpression { get; set; } + + /// + /// Indicates the type of form element. + /// + public abstract string Type { get; } +} + +/// +/// A FieldElement form element defines how a feature layer's field participates in the FeatureForm. This is the recommended approach to set field configurations within a feature form's or feature layer's formTemplate. +/// ArcGIS JS API +/// +public class FieldElement : FormElement +{ + /// + public override string Type => "field"; + + /// + /// A reference to the name of an Arcade expression defined in the expressionInfos of the FormTemplate. The expression must follow the specification defined in the Form Constraint Profile. Expressions may reference field values using the $feature global input and must return either true or false. + /// + /// + /// A reference to the name of an Arcade expression defined in the expressionInfos of the FormTemplate. The expression must follow the specification defined in the Form Constraint Profile. Expressions may reference field values using the $feature global input and must return either true or false. + /// The referenced expression must be defined in the form template's expressionInfos. It cannot be set inline within the element object. + /// + [Parameter] + public string? EditableExpression { get; set; } + + [Parameter] + public string? RequiredExpression { get; set; } + + /// + /// The field name as defined by the feature layer. Set this property to indicate which field to edit. + /// + [Parameter] + public string? FieldName { get; set; } + + /// + /// Contains a hint used to help editors while editing fields. Set this as a temporary placeholder for text/number inputs in either TextAreaInput or TextBoxInput. + /// + [Parameter] + public string? Hint { get; set; } + + /// + /// A reference to the name of an Arcade expression defined in the expressionInfos of the FormTemplate. The expression must follow the specification defined in the Form Calculation Profile. This expression references field values within an individual feature or in other layers and must return either a date, number, or string value. + /// + /// + /// Once the expression evaluates, the form's field value updates to the expressions' result. + /// It is required to set the view property when instantiating a FeatureForm widget and using expressions that use the $map variable. + /// The referenced expression must be defined in the form template's expressionInfos. It cannot be set inline within the element object. + /// + public string? ValueExpression { get; internal set; } + + /// + /// The coded value domain or range domain of the field. + /// + public Domain? Domain { get; set; } + + /// + /// The input to use for the element. The client application is responsible for defining the default user interface. + /// + public FormInput? Input { get; set; } + + /// + public override async Task RegisterChildComponent(MapComponent child) + { + switch (child) + { + case Domain domain: + Domain = domain; + + break; + case FormInput formInput: + Input = formInput; + + break; + default: + await base.RegisterChildComponent(child); + + break; + } + } + + /// + public override async Task UnregisterChildComponent(MapComponent child) + { + switch (child) + { + case Domain _: + Domain = null; + + break; + case FormInput _: + Input = null; + + break; + default: + await base.UnregisterChildComponent(child); + + break; + } + } +} + +public class GroupElement : FormElement +{ + /// + public override string Type => "group"; + + public List? Elements { get; set; } + + /// + public override async Task RegisterChildComponent(MapComponent child) + { + switch (child) + { + case FieldElement fieldElement: + Elements ??= new List(); + Elements.Add(fieldElement); + + break; + + default: + await base.RegisterChildComponent(child); + + break; + } + } + + /// + public override async Task UnregisterChildComponent(MapComponent child) + { + switch (child) + { + case FieldElement fieldElement: + Elements?.Remove(fieldElement); + + break; + + default: + await base.UnregisterChildComponent(child); + + break; + } + } + + /// + internal override void ValidateRequiredChildren() + { + if (Elements is not null) + { + foreach (FieldElement element in Elements) + { + element.ValidateRequiredChildren(); + } + } + base.ValidateRequiredChildren(); + } +} + +/// +/// Return types for Arcade expressions. +/// +[JsonConverter(typeof(EnumToKebabCaseStringConverter))] +public enum ArcadeReturnType +{ + Boolean, + Date, + Number, + String +} + +internal class FormElementConverter : JsonConverter +{ + public override FormElement? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + throw new NotImplementedException(); + } + + public override void Write(Utf8JsonWriter writer, FormElement value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, (object)value, options); + } +} \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/Widgets/FormInput.cs b/src/dymaptic.GeoBlazor.Core/Components/Widgets/FormInput.cs new file mode 100644 index 00000000..bf52e439 --- /dev/null +++ b/src/dymaptic.GeoBlazor.Core/Components/Widgets/FormInput.cs @@ -0,0 +1,184 @@ +using Microsoft.AspNetCore.Components; + + +namespace dymaptic.GeoBlazor.Core.Components.Widgets; + +/// +/// Abstract base class for Input fields in a form element. +/// +public abstract class FormInput : MapComponent +{ + /// + /// The type of form element input. + /// + public abstract string Type { get; } +} + +/// +/// The TextBoxInput class defines the desired user interface as a single-line text box. +/// ArcGIS JS API +/// +public class TextBoxInput : FormInput +{ + /// + public override string Type => "text-box"; + + /// + /// When set, defines the text input's maximum length. + /// + [Parameter] + public int? MaxLength { get; set; } + + /// + /// When set, defines the text input's minimum length. + /// + [Parameter] + public int? MinLength { get; set; } +} + +/// +/// The TextAreaInput class defines the desired user interface as a multi-line text area. +/// ArcGIS JS API +/// +public class TextAreaInput : FormInput +{ + /// + public override string Type => "text-area"; + + /// + /// When set, defines the text input's maximum length. + /// + [Parameter] + public int? MaxLength { get; set; } + + /// + /// When set, defines the text input's minimum length. + /// + [Parameter] + public int? MinLength { get; set; } +} + +/// +/// The DateTimePickerInput class defines the desired user interface for editing date fields in a form. +/// ArcGIS JS API +/// +public class DateTimePickerInput : FormInput +{ + /// + public override string Type => "datetime-picker"; + + /// + /// Indicates if the input should provide an option to select the time. If not provided, the default value is false. + /// + [Parameter] + public bool? IncludeTime { get; set; } + + /// + /// The maximum date to allow. The number represents the number of milliseconds since epoch (January 1, 1970) in UTC. + /// + [Parameter] + public long? Max { get; set; } + + /// + /// The minimum date to allow. The number represents the number of milliseconds since epoch (January 1, 1970) in UTC. + /// + [Parameter] + public long? Min { get; set; } +} + +/// +/// The BarcodeScannerInput class defines the desired user interface for a barcode or QR code scanner. This input type will default to the TextBoxInput type as the API does not currently support bar code scanning. +/// ArcGIS JS API +/// +public class BarcodeScannerInput : FormInput +{ + /// + public override string Type => "barcode-scanner"; + + /// + /// When set, defines the text input's maximum length. + /// + [Parameter] + public int? MaxLength { get; set; } + + /// + /// When set, defines the text input's minimum length. + /// + [Parameter] + public int? MinLength { get; set; } +} + +/// +/// The ComboBoxInput class defines the desired user interface for a combo box group. +/// ArcGIS JS API +/// +/// +/// Coded-value domains are required when using this input type. Previously, fields containing values that weren't compatible with their associated coded-value domain(s) displayed the option and would remove it once a user updated the value. The ComboBoxInput will now display and keep the value but will disable it. +/// +public class ComboBoxInput : FormInput +{ + /// + public override string Type => "combo-box"; + + /// + /// The text used to represent a null value. + /// + [Parameter] + public string? NoValueOptionLabel { get; set; } + + /// + /// Determines whether a null value option is displayed. This only applies to fields that support null values. + /// + [Parameter] + public bool? ShowNoValueOption { get; set; } +} + +/// +/// https://developers.arcgis.com/javascript/latest/api-reference/esri-form-elements-inputs-RadioButtonsInput.html +/// ArcGIS JS API +/// +/// +/// Coded-value domains are required when using this input type. Previously, fields containing values that weren't compatible with their associated coded-value domain(s) displayed the option and would remove it once a user updated the value. The RadioButtonsInput will now keep the value, but it will not display an option in the user interface. +/// +public class RadioButtonsInput : FormInput +{ + /// + public override string Type => "radio-buttons"; + + /// + /// The text used to represent a null value. + /// + [Parameter] + public string? NoValueOptionLabel { get; set; } + + /// + /// Determines whether a null value option is displayed. This only applies to fields that support null values. + /// + [Parameter] + public bool? ShowNoValueOption { get; set; } +} + +/// +/// The SwitchInput class defines the desired user interface for a binary switch or toggle. This should be used when selecting between two options. +/// ArcGIS JS API +/// +/// +/// Coded-value domains are required when using this input type. +/// +public class SwitchInput : FormInput +{ + /// + public override string Type => "switch"; + + /// + /// Coded value used when the switch state is turned off. Values that are parseable as numbers will be converted. + /// + [Parameter] + public string? OffValue { get; set; } + + /// + /// Coded value used when the switch state is turned on. Values that are parseable as numbers will be converted. + /// + [Parameter] + public string? OnValue { get; set; } +} \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/Widgets/FormTemplate.cs b/src/dymaptic.GeoBlazor.Core/Components/Widgets/FormTemplate.cs new file mode 100644 index 00000000..891815dc --- /dev/null +++ b/src/dymaptic.GeoBlazor.Core/Components/Widgets/FormTemplate.cs @@ -0,0 +1,63 @@ +using dymaptic.GeoBlazor.Core.Components.Popups; +using Microsoft.AspNetCore.Components; + + +namespace dymaptic.GeoBlazor.Core.Components.Widgets; + +public class FormTemplate : MapComponent +{ + [Parameter] + public string? Description { get; set; } + + [Parameter] + public bool? PreserveFieldValuesWhenHidden { get; set; } + + [Parameter] + public string? Title { get; set; } + + public HashSet? Elements { get; set; } + + public HashSet? ExpressionInfos { get; set; } + + public override async Task RegisterChildComponent(MapComponent child) + { + switch (child) + { + case FormElement formElement: + Elements ??= new HashSet(); + + Elements.Add(formElement); + + break; + case ExpressionInfo expressionInfo: + ExpressionInfos ??= new HashSet(); + + ExpressionInfos.Add(expressionInfo); + + break; + default: + await base.RegisterChildComponent(child); + + break; + } + } + + public override async Task UnregisterChildComponent(MapComponent child) + { + switch (child) + { + case FormElement formElement: + Elements?.Remove(formElement); + + break; + case ExpressionInfo expressionInfo: + ExpressionInfos?.Remove(expressionInfo); + + break; + default: + await base.UnregisterChildComponent(child); + + break; + } + } +} \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Objects/Query.cs b/src/dymaptic.GeoBlazor.Core/Objects/Query.cs index 26c330d7..31dd966a 100644 --- a/src/dymaptic.GeoBlazor.Core/Objects/Query.cs +++ b/src/dymaptic.GeoBlazor.Core/Objects/Query.cs @@ -158,7 +158,7 @@ public class Query /// An array of ObjectIDs to be used to query for features in a layer. /// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] - public IEnumerable? ObjectIds { get; set; } + public IEnumerable? ObjectIds { get; set; } /// /// One or more field names used to order the query results. Specify ASC (ascending) or DESC (descending) after the diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts b/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts index 4a6c38a8..894927fe 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts @@ -64,7 +64,7 @@ import { import { buildJsAttributes, buildJsExtent, - buildJsFields, + buildJsFields, buildJsFormTemplate, buildJsGeometry, buildJsGraphic, buildJsPoint, @@ -1853,6 +1853,11 @@ export async function createLayer(layerObject: any, wrap?: boolean | null, viewI copyValuesIfExists(layerObject, featureLayer, 'minScale', 'maxScale', 'orderBy', 'objectIdField', 'definitionExpression', 'labelingInfo', 'outFields'); + + if (hasValue(layerObject.formTemplate)) { + featureLayer.formTemplate = buildJsFormTemplate(layerObject.formTemplate); + console.log('feature form: ' + JSON.stringify(featureLayer.formTemplate.toJSON())); + } if (hasValue(layerObject.popupTemplate)) { featureLayer.popupTemplate = buildJsPopupTemplate(layerObject.popupTemplate, viewId ?? null); diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/definitions.d.ts b/src/dymaptic.GeoBlazor.Core/Scripts/definitions.d.ts index 202a5f04..4c52a6e2 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/definitions.d.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/definitions.d.ts @@ -464,4 +464,26 @@ export interface DotNetRelationshipPopupContent extends DotNetPopupContent { export interface DotNetRelatedRecordsInfoFieldOrder { field: string; order: string; +} + +export interface DotNetApplyEdits { + addFeatures: DotNetGraphic[]; + updateFeatures: DotNetGraphic[]; + deleteFeatures: DotNetGraphic[]; + addAttachments: DotNetAttachmentsEdit[]; + updateAttachments: DotNetAttachmentsEdit[]; + deleteAttachments: string[]; +} + +export interface DotNetAttachmentsEdit { + feature: DotNetGraphic | number | string; + attachment: DotNetAttachment; +} + +export interface DotNetAttachment { + globalId: string; + name: string; + contentType: string; + uploadId: string; + data: string; } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts index 513d34bb..cfe143ab 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts @@ -3,6 +3,7 @@ import FeatureLayer from "@arcgis/core/layers/FeatureLayer"; import Query from "@arcgis/core/rest/support/Query"; import FeatureSet from "@arcgis/core/rest/support/FeatureSet"; import { + DotNetApplyEdits, DotNetFeatureSet, DotNetGraphic, DotNetPopupTemplate, @@ -10,7 +11,7 @@ import { DotNetRelationshipQuery, DotNetTopFeaturesQuery } from "./definitions"; -import {buildJsQuery, buildJsRelationshipQuery, buildJsTopFeaturesQuery} from "./jsBuilder"; +import {buildJsApplyEdits, buildJsQuery, buildJsRelationshipQuery, buildJsTopFeaturesQuery} from "./jsBuilder"; import { buildDotNetExtent, buildDotNetGeometry, @@ -225,4 +226,9 @@ export default class FeatureLayerWrapper { extent: buildDotNetExtent(result.extent) }; } + + applyEdits(edits: DotNetApplyEdits, options: any, viewId: string): Promise { + let jsEdits = buildJsApplyEdits(edits, viewId); + return this.layer.applyEdits(jsEdits, options); + } } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts b/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts index 32bc7acf..b05b70d6 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts @@ -13,6 +13,8 @@ import Renderer from "@arcgis/core/renderers/Renderer"; import Field from "@arcgis/core/layers/support/Field"; import Font from "@arcgis/core/symbols/Font"; import { + DotNetApplyEdits, + DotNetAttachmentsEdit, DotNetAttachmentsPopupContent, DotNetBarChartMediaInfo, DotNetChartMediaInfoValue, @@ -76,15 +78,29 @@ import SimpleLineSymbol from "@arcgis/core/symbols/SimpleLineSymbol"; import ElementExpressionInfo from "@arcgis/core/popup/ElementExpressionInfo"; import ChartMediaInfoValueSeries from "@arcgis/core/popup/content/support/ChartMediaInfoValueSeries"; import View from "@arcgis/core/views/View"; +import {buildDotNetGraphic} from "./dotNetBuilder"; import ViewClickEvent = __esri.ViewClickEvent; import PopupOpenOptions = __esri.PopupOpenOptions; import PopupDockOptions = __esri.PopupDockOptions; import ContentProperties = __esri.ContentProperties; import PopupTriggerActionEvent = __esri.PopupTriggerActionEvent; -import { buildDotNetGraphic } from "./dotNetBuilder"; -import ActionBase from "@arcgis/core/support/actions/ActionBase"; -import ActionButton from "@arcgis/core/support/actions/ActionButton"; -import ActionToggle from "@arcgis/core/support/actions/ActionToggle"; +import FeatureLayerBaseApplyEditsEdits = __esri.FeatureLayerBaseApplyEditsEdits; +import AttachmentEdit = __esri.AttachmentEdit; +import FormTemplate from "@arcgis/core/form/FormTemplate"; +import ElementProperties = __esri.ElementProperties; +import Element from "@arcgis/core/form/elements/Element"; +import GroupElement from "@arcgis/core/form/elements/GroupElement"; +import FieldElement from "@arcgis/core/form/elements/FieldElement"; +import CodedValueDomain from "@arcgis/core/layers/support/CodedValueDomain"; +import RangeDomain from "@arcgis/core/layers/support/RangeDomain"; +import CodedValue = __esri.CodedValue; +import TextBoxInput from "@arcgis/core/form/elements/inputs/TextBoxInput"; +import TextAreaInput from "@arcgis/core/form/elements/inputs/TextAreaInput"; +import DateTimePickerInput from "@arcgis/core/form/elements/inputs/DateTimePickerInput"; +import BarcodeScannerInput from "@arcgis/core/form/elements/inputs/BarcodeScannerInput"; +import ComboBoxInput from "@arcgis/core/form/elements/inputs/ComboBoxInput"; +import RadioButtonsInput from "@arcgis/core/form/elements/inputs/RadioButtonsInput"; +import SwitchInput from "@arcgis/core/form/elements/inputs/SwitchInput"; export function buildJsSpatialReference(dotNetSpatialReference: DotNetSpatialReference): SpatialReference { if (dotNetSpatialReference === undefined || dotNetSpatialReference === null) { @@ -321,8 +337,7 @@ export function buildJsExpressionInfo(expressionInfoObject: DotNetExpressionInfo return { name: expressionInfoObject.name ?? undefined, title: expressionInfoObject.title ?? undefined, - expression: expressionInfoObject.expression ?? undefined, - returnType: expressionInfoObject.returnType as any ?? undefined + expression: expressionInfoObject.expression ?? undefined } as popupExpressionInfo; } @@ -805,7 +820,6 @@ export function buildJsImageMediaInfoValue(dotNetImageMediaInfoValue: DotNetImag export function buildJsElementExpressionInfo(dotNetExpressionInfo: DotNetElementExpressionInfo): ElementExpressionInfo { let info = new ElementExpressionInfo({ expression: dotNetExpressionInfo.expression ?? undefined, - returnType: dotNetExpressionInfo.returnType as any ?? undefined, title: dotNetExpressionInfo.title ?? undefined }); @@ -829,8 +843,161 @@ export function buildJsPortalItem(dotNetPortalItem: any): any { return portalItem; } +export function buildJsApplyEdits(dotNetApplyEdits: DotNetApplyEdits, viewId: string): FeatureLayerBaseApplyEditsEdits { + let addFeatures = dotNetApplyEdits.addFeatures?.map(f => buildJsGraphic(f, viewId)!); + let deleteFeatures = dotNetApplyEdits.deleteFeatures?.map(f => buildJsGraphic(f, viewId)!); + let updateFeatures = dotNetApplyEdits.updateFeatures?.map(f => buildJsGraphic(f, viewId)!); + let addAttachments = dotNetApplyEdits.addAttachments?.map(a => buildJsAttachmentEdit(a, viewId)!); + let updateAttachments = dotNetApplyEdits.updateAttachments?.map(a => buildJsAttachmentEdit(a, viewId)!); + + return { + addFeatures: addFeatures ?? undefined, + deleteFeatures: deleteFeatures ?? undefined, + updateFeatures: updateFeatures ?? undefined, + addAttachments: addAttachments ?? undefined, + updateAttachments: updateAttachments ?? undefined, + deleteAttachments: dotNetApplyEdits.deleteAttachments ?? undefined + }; +} + +export function buildJsFormTemplate(dotNetFormTemplate: any): FormTemplate { + let formTemplate = new FormTemplate({ + title: dotNetFormTemplate.title ?? undefined, + description: dotNetFormTemplate.description ?? undefined, + preserveFieldValuesWhenHidden: dotNetFormTemplate.preserveFieldValuesWhenHidden ?? undefined + }); + if (hasValue(dotNetFormTemplate?.elements)) { + formTemplate.elements = dotNetFormTemplate.elements.map(e => buildJsFormTemplateElement(e)); + } + if (hasValue(dotNetFormTemplate.expressionInfos)) { + formTemplate.expressionInfos = dotNetFormTemplate.expressionInfos.map(e => buildJsExpressionInfo(e)); + } + return formTemplate; +} + +function buildJsFormTemplateElement(dotNetFormTemplateElement: any): Element { + switch (dotNetFormTemplateElement.type){ + case 'group': + return new GroupElement({ + label: dotNetFormTemplateElement.label ?? undefined, + description: dotNetFormTemplateElement.description ?? undefined, + elements: dotNetFormTemplateElement.elements?.map(e => buildJsFormTemplateElement(e)) ?? [] + }); + } + let fieldElement: any = { + type: 'field' + }; + if (hasValue(dotNetFormTemplateElement.description)) { + fieldElement.description = dotNetFormTemplateElement.description; + } + if (hasValue(dotNetFormTemplateElement.label)) { + fieldElement.label = dotNetFormTemplateElement.label; + } + if (hasValue(dotNetFormTemplateElement.visibilityExpression)) { + fieldElement.visibilityExpression = dotNetFormTemplateElement.visibilityExpression; + } + if (hasValue(dotNetFormTemplateElement.editableExpression)) { + fieldElement.editableExpression = dotNetFormTemplateElement.editableExpression; + } + if (hasValue(dotNetFormTemplateElement.requiredExpression)) { + fieldElement.requiredExpression = dotNetFormTemplateElement.requiredExpression; + } + if (hasValue(dotNetFormTemplateElement.fieldName)) { + fieldElement.fieldName = dotNetFormTemplateElement.fieldName; + } + if (hasValue(dotNetFormTemplateElement.hint)) { + fieldElement.hint = dotNetFormTemplateElement.hint; + } + if (hasValue(dotNetFormTemplateElement.valueExpression)) { + fieldElement.valueExpression = dotNetFormTemplateElement.valueExpression; + } + if (hasValue(dotNetFormTemplateElement?.domain)) { + fieldElement.domain = buildJsDomain(dotNetFormTemplateElement.domain); + } + if (hasValue(dotNetFormTemplateElement?.input)) { + fieldElement.input = buildJsFormInput(dotNetFormTemplateElement.input); + } + return fieldElement; +} + +function buildJsDomain(dotNetDomain: any): any { + switch (dotNetDomain?.type){ + case 'coded-value': + return new CodedValueDomain({ + name: dotNetDomain.name ?? undefined, + codedValues: dotNetDomain.codedValues?.map(c => buildJsCodedValue(c)) ?? undefined + }); + case 'range': + return new RangeDomain({ + name: dotNetDomain.name ?? undefined, + maxValue: dotNetDomain.maxValue ?? undefined, + minValue: dotNetDomain.minValue ?? undefined + }); + } + + return undefined; +} + +function buildJsCodedValue(dotNetCodedValue: any): CodedValue { + return { + name: dotNetCodedValue.name ?? undefined, + code: dotNetCodedValue.code ?? undefined + }; +} + +function buildJsFormInput(dotNetFormInput: any): any { + switch (dotNetFormInput?.type) { + case 'text-box': + return new TextBoxInput({ + maxLength: dotNetFormInput.maxLength ?? undefined, + minLength: dotNetFormInput.minLength ?? undefined + }); + case 'text-area': + return new TextAreaInput({ + maxLength: dotNetFormInput.maxLength ?? undefined, + minLength: dotNetFormInput.minLength ?? undefined + }); + case 'datetime-picker': + return new DateTimePickerInput({ + includeTime: dotNetFormInput.includeTime ?? undefined, + max: dotNetFormInput.max ?? undefined, + min: dotNetFormInput.min ?? undefined + }); + case 'barcode-scanner': + return new BarcodeScannerInput({ + maxLength: dotNetFormInput.maxLength ?? undefined, + minLength: dotNetFormInput.minLength ?? undefined + }); + case 'combo-box': + return new ComboBoxInput({ + noValueOptionLabel: dotNetFormInput.noValueOptionLabel ?? undefined, + showNoValueOption: dotNetFormInput.showNoValueOption ?? undefined + }); + case 'radio-buttons': + return new RadioButtonsInput({ + noValueOptionLabel: dotNetFormInput.noValueOptionLabel ?? undefined, + showNoValueOption: dotNetFormInput.showNoValueOption ?? undefined + }); + case 'switch': + return new SwitchInput({ + offValue: dotNetFormInput.offValue ?? undefined, + onValue: dotNetFormInput.onValue ?? undefined + }); + } + + return undefined; +} + +function buildJsAttachmentEdit(dotNetAttachmentEdit: DotNetAttachmentsEdit, viewId: string): AttachmentEdit { + return { + feature: buildJsGraphic(dotNetAttachmentEdit.feature, viewId)!, + attachment: dotNetAttachmentEdit.attachment + }; +} + function buildJsColor(color: any) { if (!hasValue(color)) return null; + // @ts-ignore if (typeof color === "string" || color instanceof Array) return color; if (hasValue(color?.hexOrNameValue)) { return color.hexOrNameValue; diff --git a/src/dymaptic.GeoBlazor.Core/package-lock.json b/src/dymaptic.GeoBlazor.Core/package-lock.json index 8c3580a4..79d3a9ad 100644 --- a/src/dymaptic.GeoBlazor.Core/package-lock.json +++ b/src/dymaptic.GeoBlazor.Core/package-lock.json @@ -9,7 +9,7 @@ "version": "2.2.1", "license": "ISC", "dependencies": { - "@arcgis/core": "^4.26.5", + "@arcgis/core": "^4.27.6", "esbuild": "^0.17.5", "protobufjs": "^7.2.2", "protobufjs-cli": "^1.1.1" @@ -19,16 +19,16 @@ } }, "node_modules/@arcgis/core": { - "version": "4.26.5", - "resolved": "https://registry.npmjs.org/@arcgis/core/-/core-4.26.5.tgz", - "integrity": "sha512-Z8KoJrTD1ZQwVVZAIpkdjAQVT/MjAaWAr5u0krifKd6Y1m0TrEb8/iK86KvyWX8yNNWEroEP9SiE19vH/JKquQ==", + "version": "4.27.6", + "resolved": "https://registry.npmjs.org/@arcgis/core/-/core-4.27.6.tgz", + "integrity": "sha512-Pdz8Y1hHpQm2LKkJUGNrag2o2pQ9Pe8KHjywWb+TOJcfqM8L2UQBLmtUGY26VmfMwT/FfnUfNJ7HhYzHi5ef0w==", "dependencies": { "@esri/arcgis-html-sanitizer": "~3.0.1", "@esri/calcite-colors": "~6.1.0", - "@esri/calcite-components": "~1.0.7", - "@popperjs/core": "~2.11.6", - "focus-trap": "~7.2.0", - "luxon": "~3.2.1", + "@esri/calcite-components": "^1.4.2", + "@popperjs/core": "~2.11.7", + "focus-trap": "~7.4.3", + "luxon": "~3.3.0", "sortablejs": "~1.15.0" } }, @@ -387,31 +387,33 @@ "integrity": "sha512-wHQYWFtDa6c328EraXEVZvgOiaQyYr0yuaaZ0G3cB4C3lSkWefW34L/e5TLAhtuG3zJ/wR6pl5X1YYNfBc0/4Q==" }, "node_modules/@esri/calcite-components": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@esri/calcite-components/-/calcite-components-1.0.7.tgz", - "integrity": "sha512-Ucw0Ly+hvWl4RDMEcHAv2amMItgMYzCGfJS5M7gnIu9bEXcY2Areo+o0KaqvvdAcIjChsRY9fy05ke3ypw0tuA==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@esri/calcite-components/-/calcite-components-1.4.3.tgz", + "integrity": "sha512-3Yj0ZBOPBCIEp+YPJTM0C6cBmbxFXXsG9a6yv0DKxkaERj7te+hb3DQ1fVbG/lQsMLOdrQHiE70zdXSzMKvKCg==", "dependencies": { - "@floating-ui/dom": "1.1.0", - "@stencil/core": "2.20.0", + "@floating-ui/dom": "1.4.1", + "@stencil/core": "2.22.3", "@types/color": "3.0.3", "color": "4.2.3", - "focus-trap": "7.2.0", + "composed-offset-position": "0.0.4", + "dayjs": "1.11.8", + "focus-trap": "7.4.3", "form-request-submit-polyfill": "2.0.0", "lodash-es": "4.17.21", "sortablejs": "1.15.0" } }, "node_modules/@floating-ui/core": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.2.1.tgz", - "integrity": "sha512-LSqwPZkK3rYfD7GKoIeExXOyYx6Q1O4iqZWwIehDNuv3Dv425FIAE8PRwtAx1imEolFTHgBEcoFHm9MDnYgPCg==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.3.1.tgz", + "integrity": "sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g==" }, "node_modules/@floating-ui/dom": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.1.0.tgz", - "integrity": "sha512-TSogMPVxbRe77QCj1dt8NmRiJasPvuc+eT5jnJ6YpLqgOD2zXc5UA3S1qwybN+GVCDNdKfpKy1oj8RpzLJvh6A==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.4.1.tgz", + "integrity": "sha512-loCXUOLzIC3jp50RFOKXZ/kQjjz26ryr/23M+FWG9jrmAv8lRf3DUfC2AiVZ3+K316GOhB08CR+Povwz8e9mDw==", "dependencies": { - "@floating-ui/core": "^1.0.5" + "@floating-ui/core": "^1.3.1" } }, "node_modules/@jsdoc/salty": { @@ -426,9 +428,9 @@ } }, "node_modules/@popperjs/core": { - "version": "2.11.6", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", - "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==", + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "funding": { "type": "opencollective", "url": "https://opencollective.com/popperjs" @@ -489,9 +491,9 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, "node_modules/@stencil/core": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.20.0.tgz", - "integrity": "sha512-ka+eOW+dNteXIfLCRipNbbAlBEQjqJ2fkx3fxzlKgnNHEQMdZiuIjlWt63KzvOJStNeuADdQXo89BB1dC2VRUw==", + "version": "2.22.3", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.22.3.tgz", + "integrity": "sha512-kmVA0M/HojwsfkeHsifvHVIYe4l5tin7J5+DLgtl8h6WWfiMClND5K3ifCXXI2ETDNKiEk21p6jql3Fx9o2rng==", "bin": { "stencil": "bin/stencil" }, @@ -669,6 +671,11 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, + "node_modules/composed-offset-position": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/composed-offset-position/-/composed-offset-position-0.0.4.tgz", + "integrity": "sha512-vMlvu1RuNegVE0YsCDSV/X4X10j56mq7PCIyOKK74FxkXzGLwhOUmdkJLSdOBOMwWycobGUMgft2lp+YgTe8hw==" + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -679,6 +686,11 @@ "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==" }, + "node_modules/dayjs": { + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.8.tgz", + "integrity": "sha512-LcgxzFoWMEPO7ggRv1Y2N31hUf2R0Vj7fuy/m+Bg1K8rr+KAs1AEy4y9jd5DXe8pbHgX+srkHNS7TH6Q6ZhYeQ==" + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -823,11 +835,11 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, "node_modules/focus-trap": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.2.0.tgz", - "integrity": "sha512-v4wY6HDDYvzkBy4735kW5BUEuw6Yz9ABqMYLuTNbzAFPcBOGiGHwwcNVMvUz4G0kgSYh13wa/7TG3XwTeT4O/A==", + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.4.3.tgz", + "integrity": "sha512-BgSSbK4GPnS2VbtZ50VtOv1Sti6DIkj3+LkVjiWMNjLeAp1SH1UlLx3ULu/DCu4vq5R4/uvTm+zrvsMsuYmGLg==", "dependencies": { - "tabbable": "^6.0.1" + "tabbable": "^6.1.2" } }, "node_modules/form-request-submit-polyfill": { @@ -981,9 +993,9 @@ } }, "node_modules/luxon": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.2.1.tgz", - "integrity": "sha512-QrwPArQCNLAKGO/C+ZIilgIuDnEnKx5QYODdDtbFaxzsbZcc/a7WFq7MhsVYgRlwawLtvOUESTlfJ+hc/USqPg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz", + "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==", "engines": { "node": ">=12" } @@ -1268,9 +1280,9 @@ } }, "node_modules/tabbable": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.1.1.tgz", - "integrity": "sha512-4kl5w+nCB44EVRdO0g/UGoOp3vlwgycUVtkk/7DPyeLZUCuNFFKCFG6/t/DgHLrUPHjrZg6s5tNm+56Q2B0xyg==" + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" }, "node_modules/tmp": { "version": "0.2.1", @@ -1356,16 +1368,16 @@ }, "dependencies": { "@arcgis/core": { - "version": "4.26.5", - "resolved": "https://registry.npmjs.org/@arcgis/core/-/core-4.26.5.tgz", - "integrity": "sha512-Z8KoJrTD1ZQwVVZAIpkdjAQVT/MjAaWAr5u0krifKd6Y1m0TrEb8/iK86KvyWX8yNNWEroEP9SiE19vH/JKquQ==", + "version": "4.27.6", + "resolved": "https://registry.npmjs.org/@arcgis/core/-/core-4.27.6.tgz", + "integrity": "sha512-Pdz8Y1hHpQm2LKkJUGNrag2o2pQ9Pe8KHjywWb+TOJcfqM8L2UQBLmtUGY26VmfMwT/FfnUfNJ7HhYzHi5ef0w==", "requires": { "@esri/arcgis-html-sanitizer": "~3.0.1", "@esri/calcite-colors": "~6.1.0", - "@esri/calcite-components": "~1.0.7", - "@popperjs/core": "~2.11.6", - "focus-trap": "~7.2.0", - "luxon": "~3.2.1", + "@esri/calcite-components": "^1.4.2", + "@popperjs/core": "~2.11.7", + "focus-trap": "~7.4.3", + "luxon": "~3.3.0", "sortablejs": "~1.15.0" } }, @@ -1520,31 +1532,33 @@ "integrity": "sha512-wHQYWFtDa6c328EraXEVZvgOiaQyYr0yuaaZ0G3cB4C3lSkWefW34L/e5TLAhtuG3zJ/wR6pl5X1YYNfBc0/4Q==" }, "@esri/calcite-components": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/@esri/calcite-components/-/calcite-components-1.0.7.tgz", - "integrity": "sha512-Ucw0Ly+hvWl4RDMEcHAv2amMItgMYzCGfJS5M7gnIu9bEXcY2Areo+o0KaqvvdAcIjChsRY9fy05ke3ypw0tuA==", + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@esri/calcite-components/-/calcite-components-1.4.3.tgz", + "integrity": "sha512-3Yj0ZBOPBCIEp+YPJTM0C6cBmbxFXXsG9a6yv0DKxkaERj7te+hb3DQ1fVbG/lQsMLOdrQHiE70zdXSzMKvKCg==", "requires": { - "@floating-ui/dom": "1.1.0", - "@stencil/core": "2.20.0", + "@floating-ui/dom": "1.4.1", + "@stencil/core": "2.22.3", "@types/color": "3.0.3", "color": "4.2.3", - "focus-trap": "7.2.0", + "composed-offset-position": "0.0.4", + "dayjs": "1.11.8", + "focus-trap": "7.4.3", "form-request-submit-polyfill": "2.0.0", "lodash-es": "4.17.21", "sortablejs": "1.15.0" } }, "@floating-ui/core": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.2.1.tgz", - "integrity": "sha512-LSqwPZkK3rYfD7GKoIeExXOyYx6Q1O4iqZWwIehDNuv3Dv425FIAE8PRwtAx1imEolFTHgBEcoFHm9MDnYgPCg==" + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.3.1.tgz", + "integrity": "sha512-Bu+AMaXNjrpjh41znzHqaz3r2Nr8hHuHZT6V2LBKMhyMl0FgKA62PNYbqnfgmzOhoWZj70Zecisbo4H1rotP5g==" }, "@floating-ui/dom": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.1.0.tgz", - "integrity": "sha512-TSogMPVxbRe77QCj1dt8NmRiJasPvuc+eT5jnJ6YpLqgOD2zXc5UA3S1qwybN+GVCDNdKfpKy1oj8RpzLJvh6A==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.4.1.tgz", + "integrity": "sha512-loCXUOLzIC3jp50RFOKXZ/kQjjz26ryr/23M+FWG9jrmAv8lRf3DUfC2AiVZ3+K316GOhB08CR+Povwz8e9mDw==", "requires": { - "@floating-ui/core": "^1.0.5" + "@floating-ui/core": "^1.3.1" } }, "@jsdoc/salty": { @@ -1556,9 +1570,9 @@ } }, "@popperjs/core": { - "version": "2.11.6", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.6.tgz", - "integrity": "sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==" + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==" }, "@protobufjs/aspromise": { "version": "1.1.2", @@ -1615,9 +1629,9 @@ "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, "@stencil/core": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.20.0.tgz", - "integrity": "sha512-ka+eOW+dNteXIfLCRipNbbAlBEQjqJ2fkx3fxzlKgnNHEQMdZiuIjlWt63KzvOJStNeuADdQXo89BB1dC2VRUw==" + "version": "2.22.3", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.22.3.tgz", + "integrity": "sha512-kmVA0M/HojwsfkeHsifvHVIYe4l5tin7J5+DLgtl8h6WWfiMClND5K3ifCXXI2ETDNKiEk21p6jql3Fx9o2rng==" }, "@types/color": { "version": "3.0.3", @@ -1759,6 +1773,11 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" }, + "composed-offset-position": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/composed-offset-position/-/composed-offset-position-0.0.4.tgz", + "integrity": "sha512-vMlvu1RuNegVE0YsCDSV/X4X10j56mq7PCIyOKK74FxkXzGLwhOUmdkJLSdOBOMwWycobGUMgft2lp+YgTe8hw==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -1769,6 +1788,11 @@ "resolved": "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz", "integrity": "sha512-FAaLDaplstoRsDR8XGYH51znUN0UY7nMc6Z9/fvE8EXGwvJE9hu7W2vHwx1+bd6gCYnln9nLbzxFTrcO9YQDZw==" }, + "dayjs": { + "version": "1.11.8", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.8.tgz", + "integrity": "sha512-LcgxzFoWMEPO7ggRv1Y2N31hUf2R0Vj7fuy/m+Bg1K8rr+KAs1AEy4y9jd5DXe8pbHgX+srkHNS7TH6Q6ZhYeQ==" + }, "deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -1868,11 +1892,11 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" }, "focus-trap": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.2.0.tgz", - "integrity": "sha512-v4wY6HDDYvzkBy4735kW5BUEuw6Yz9ABqMYLuTNbzAFPcBOGiGHwwcNVMvUz4G0kgSYh13wa/7TG3XwTeT4O/A==", + "version": "7.4.3", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.4.3.tgz", + "integrity": "sha512-BgSSbK4GPnS2VbtZ50VtOv1Sti6DIkj3+LkVjiWMNjLeAp1SH1UlLx3ULu/DCu4vq5R4/uvTm+zrvsMsuYmGLg==", "requires": { - "tabbable": "^6.0.1" + "tabbable": "^6.1.2" } }, "form-request-submit-polyfill": { @@ -2005,9 +2029,9 @@ } }, "luxon": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.2.1.tgz", - "integrity": "sha512-QrwPArQCNLAKGO/C+ZIilgIuDnEnKx5QYODdDtbFaxzsbZcc/a7WFq7MhsVYgRlwawLtvOUESTlfJ+hc/USqPg==" + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.3.0.tgz", + "integrity": "sha512-An0UCfG/rSiqtAIiBPO0Y9/zAnHUZxAMiCpTd5h2smgsj7GGmcenvrvww2cqNA8/4A5ZrD1gJpHN2mIHZQF+Mg==" }, "markdown-it": { "version": "12.3.2", @@ -2211,9 +2235,9 @@ } }, "tabbable": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.1.1.tgz", - "integrity": "sha512-4kl5w+nCB44EVRdO0g/UGoOp3vlwgycUVtkk/7DPyeLZUCuNFFKCFG6/t/DgHLrUPHjrZg6s5tNm+56Q2B0xyg==" + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz", + "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==" }, "tmp": { "version": "0.2.1", diff --git a/src/dymaptic.GeoBlazor.Core/package.json b/src/dymaptic.GeoBlazor.Core/package.json index ac046ecc..818eca9e 100644 --- a/src/dymaptic.GeoBlazor.Core/package.json +++ b/src/dymaptic.GeoBlazor.Core/package.json @@ -12,9 +12,9 @@ "author": "Tim Purdum", "license": "ISC", "dependencies": { - "@arcgis/core": "^4.26.5", + "@arcgis/core": "^4.27.6", "esbuild": "^0.17.5", - "protobufjs": "^7.2.2", + "protobufjs": "^7.2.4", "protobufjs-cli": "^1.1.1" }, "devDependencies": { From 5cf8ad683a241415b42e70a402e02b05d55788bb Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Wed, 12 Jul 2023 13:53:52 -0400 Subject: [PATCH 2/4] working, tested --- .../Components/Layers/Field.cs | 41 ++++++++++- .../Components/Widgets/Domain.cs | 24 +++++++ .../Scripts/arcGisJsInterop.ts | 71 ++++++++++--------- .../Scripts/featureLayer.ts | 9 ++- .../Scripts/featureLayerView.ts | 21 +++++- .../Scripts/jsBuilder.ts | 62 ++++++++++++++-- .../dymaptic.GeoBlazor.Core.csproj | 2 +- src/dymaptic.GeoBlazor.Core/package-lock.json | 14 ++-- src/global.json | 3 +- 9 files changed, 194 insertions(+), 53 deletions(-) diff --git a/src/dymaptic.GeoBlazor.Core/Components/Layers/Field.cs b/src/dymaptic.GeoBlazor.Core/Components/Layers/Field.cs index 92e8ecb9..abb6967a 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Layers/Field.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Layers/Field.cs @@ -1,4 +1,5 @@ -using dymaptic.GeoBlazor.Core.Serialization; +using dymaptic.GeoBlazor.Core.Components.Widgets; +using dymaptic.GeoBlazor.Core.Serialization; using Microsoft.AspNetCore.Components; using System.Text.Json.Serialization; @@ -131,6 +132,44 @@ public Field(FieldType type, string? name = null, string? alias = null, string? [Parameter] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public FieldValueType? ValueType { get; set; } + + public Domain? Domain { get; set; } + + public override async Task RegisterChildComponent(MapComponent child) + { + switch (child) + { + case Domain domain: + if (!domain.Equals(Domain)) + { + Domain = domain; + } + + break; + default: + await base.RegisterChildComponent(child); + + break; + } + } + + public override async Task UnregisterChildComponent(MapComponent child) + { + switch (child) + { + case Domain domain: + if (domain.Equals(Domain)) + { + Domain = null; + } + + break; + default: + await base.UnregisterChildComponent(child); + + break; + } + } } /// diff --git a/src/dymaptic.GeoBlazor.Core/Components/Widgets/Domain.cs b/src/dymaptic.GeoBlazor.Core/Components/Widgets/Domain.cs index 16b6eb9a..2a57293d 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Widgets/Domain.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Widgets/Domain.cs @@ -1,4 +1,6 @@ using Microsoft.AspNetCore.Components; +using System.Text.Json; +using System.Text.Json.Serialization; namespace dymaptic.GeoBlazor.Core.Components.Widgets; @@ -7,6 +9,7 @@ namespace dymaptic.GeoBlazor.Core.Components.Widgets; /// Domains define constraints on a layer field. There are two types of domains: coded values and range domains. This is an abstract class. /// ArcGIS JS API /// +[JsonConverter(typeof(DomainConverter))] public abstract class Domain : MapComponent { /// @@ -109,4 +112,25 @@ public class RangeDomain : Domain /// [Parameter] public double? MinValue { get; set; } +} + +internal class DomainConverter : JsonConverter +{ + public override Domain? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + var json = JsonDocument.ParseValue(ref reader); + var type = json.RootElement.GetProperty("type").GetString(); + + return type switch + { + "coded-value" => JsonSerializer.Deserialize(json.RootElement.GetRawText(), options), + "range" => JsonSerializer.Deserialize(json.RootElement.GetRawText(), options), + _ => null + }; + } + + public override void Write(Utf8JsonWriter writer, Domain value, JsonSerializerOptions options) + { + JsonSerializer.Serialize(writer, (object)value, options); + } } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts b/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts index 894927fe..4c399e88 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts @@ -832,43 +832,47 @@ export async function updateLayer(layerObject: any, viewId: string): Promise 0) { + featureLayer.fields = buildJsFields(layerObject.fields); + } + if (hasValue(layerObject.spatialReference) && + layerObject.spatialReference.wkid !== featureLayer.spatialReference.wkid) { + featureLayer.spatialReference = buildJsSpatialReference(layerObject.spatialReference); } } - if (hasValue(layerObject.fields) && layerObject.fields.length > 0) { - featureLayer.fields = buildJsFields(layerObject.fields); - } - if (hasValue(layerObject.spatialReference) && - layerObject.spatialReference.wkid !== featureLayer.spatialReference.wkid) { - featureLayer.spatialReference = buildJsSpatialReference(layerObject.spatialReference); - } + break; case 'geo-json': let geoJsonLayer = currentLayer as GeoJSONLayer; @@ -1856,7 +1860,6 @@ export async function createLayer(layerObject: any, wrap?: boolean | null, viewI if (hasValue(layerObject.formTemplate)) { featureLayer.formTemplate = buildJsFormTemplate(layerObject.formTemplate); - console.log('feature form: ' + JSON.stringify(featureLayer.formTemplate.toJSON())); } if (hasValue(layerObject.popupTemplate)) { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts index cfe143ab..87dfc915 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts @@ -229,6 +229,13 @@ export default class FeatureLayerWrapper { applyEdits(edits: DotNetApplyEdits, options: any, viewId: string): Promise { let jsEdits = buildJsApplyEdits(edits, viewId); - return this.layer.applyEdits(jsEdits, options); + let result; + if (options !== null) { + result = this.layer.applyEdits(jsEdits, options); + } else { + result = this.layer.applyEdits(jsEdits); + } + + return result; } } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayerView.ts b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayerView.ts index f99d00b5..d5df4a34 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayerView.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayerView.ts @@ -2,10 +2,11 @@ import FeatureEffect from "@arcgis/core/layers/support/FeatureEffect"; import Query from "@arcgis/core/rest/support/Query"; import {DotNetFeatureSet, DotNetGraphic, DotNetQuery} from "./definitions"; -import {buildJsQuery} from "./jsBuilder"; +import {buildJsGraphic, buildJsQuery} from "./jsBuilder"; import {blazorServer, dotNetRefs, graphicsRefs} from "./arcGisJsInterop"; import Handle = __esri.Handle; import {buildDotNetGeometry, buildDotNetGraphic, buildDotNetSpatialReference} from "./dotNetBuilder"; +import Graphic from "@arcgis/core/Graphic"; export default class FeatureLayerViewWrapper { private featureLayerView: FeatureLayerView; @@ -41,7 +42,23 @@ export default class FeatureLayerViewWrapper { } highlight(target: any): Handle { - return this.featureLayerView.highlight(target); + let graphics: Graphic[] = []; + if (Array.isArray(target)) { + target.forEach(t => { + if (graphicsRefs.hasOwnProperty(t.id)) { + graphics.push(graphicsRefs[t.id]); + } else { + graphics.push(buildJsGraphic(t, null) as Graphic); + } + }); + } else { + if (graphicsRefs.hasOwnProperty(target.id)) { + graphics.push(graphicsRefs[target.id]); + } else { + graphics.push(buildJsGraphic(target, null) as Graphic); + } + } + return this.featureLayerView.highlight(graphics); } async queryExtent(query: DotNetQuery, options: any): Promise { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts b/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts index b05b70d6..a6c42a3c 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts @@ -101,6 +101,7 @@ import BarcodeScannerInput from "@arcgis/core/form/elements/inputs/BarcodeScanne import ComboBoxInput from "@arcgis/core/form/elements/inputs/ComboBoxInput"; import RadioButtonsInput from "@arcgis/core/form/elements/inputs/RadioButtonsInput"; import SwitchInput from "@arcgis/core/form/elements/inputs/SwitchInput"; +import Domain from "@arcgis/core/layers/support/Domain"; export function buildJsSpatialReference(dotNetSpatialReference: DotNetSpatialReference): SpatialReference { if (dotNetSpatialReference === undefined || dotNetSpatialReference === null) { @@ -495,18 +496,67 @@ export function buildJsRenderer(dotNetRenderer: any): Renderer | null { export function buildJsFields(dotNetFields: any): Array { let fields: Array = []; dotNetFields.forEach(dnField => { - let field = new Field(); - for (const prop in dnField) { - if (Object.prototype.hasOwnProperty.call(dnField, prop) && prop !== 'id') { - field[prop] = dnField[prop]; - } - } + let field = buildJsField(dnField); fields.push(field); }); return fields; } +function buildJsField(dotNetField: any): Field { + let field = new Field(); + if (hasValue(dotNetField.type)) { + field.type = dotNetField.type; + } + if (hasValue(dotNetField.alias)) { + field.alias = dotNetField.alias; + } + if (hasValue(dotNetField.name)) { + field.name = dotNetField.name; + } + if (hasValue(dotNetField.length)) { + field.length = dotNetField.length; + } + if (hasValue(dotNetField.nullable)) { + field.nullable = dotNetField.nullable; + } + if (hasValue(dotNetField.domain)) { + switch (dotNetField.domain.type) { + case "coded-value": + let codedValueDomain = new CodedValueDomain(); + codedValueDomain.name = dotNetField.domain.name; + codedValueDomain.codedValues = dotNetField.domain.codedValues.map(cv => { + return { + name: cv.name, + code: cv.code + } + }); + field.domain = codedValueDomain; + break; + case "range": + let rangeDomain = new RangeDomain(); + rangeDomain.name = dotNetField.domain.name; + rangeDomain.minValue = dotNetField.domain.minValue; + rangeDomain.maxValue = dotNetField.domain.maxValue; + field.domain = rangeDomain; + break; + } + } + if (hasValue(dotNetField.defaultValue)) { + field.defaultValue = dotNetField.defaultValue; + } + if (hasValue(dotNetField.description)) { + field.description = dotNetField.description; + } + if (hasValue(dotNetField.editable)) { + field.editable = dotNetField.editable; + } + if (hasValue(dotNetField.valueType)) { + field.valueType = dotNetField.valueType; + } + return field; +} + export function buildJsViewClickEvent(dotNetClickEvent: any): ViewClickEvent { return { type: dotNetClickEvent.type, diff --git a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj index a4fa0a73..abb58948 100644 --- a/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj +++ b/src/dymaptic.GeoBlazor.Core/dymaptic.GeoBlazor.Core.csproj @@ -83,7 +83,7 @@ - + diff --git a/src/dymaptic.GeoBlazor.Core/package-lock.json b/src/dymaptic.GeoBlazor.Core/package-lock.json index 79d3a9ad..c266056a 100644 --- a/src/dymaptic.GeoBlazor.Core/package-lock.json +++ b/src/dymaptic.GeoBlazor.Core/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@arcgis/core": "^4.27.6", "esbuild": "^0.17.5", - "protobufjs": "^7.2.2", + "protobufjs": "^7.2.4", "protobufjs-cli": "^1.1.1" }, "devDependencies": { @@ -1111,9 +1111,9 @@ } }, "node_modules/protobufjs": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.2.tgz", - "integrity": "sha512-++PrQIjrom+bFDPpfmqXfAGSQs40116JRrqqyf53dymUMvvb5d/LMRyicRoF1AUKoXVS1/IgJXlEgcpr4gTF3Q==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", + "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", "hasInstallScript": true, "dependencies": { "@protobufjs/aspromise": "^1.1.2", @@ -2111,9 +2111,9 @@ "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==" }, "protobufjs": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.2.tgz", - "integrity": "sha512-++PrQIjrom+bFDPpfmqXfAGSQs40116JRrqqyf53dymUMvvb5d/LMRyicRoF1AUKoXVS1/IgJXlEgcpr4gTF3Q==", + "version": "7.2.4", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.2.4.tgz", + "integrity": "sha512-AT+RJgD2sH8phPmCf7OUZR8xGdcJRga4+1cOaXJ64hvcSkVhNcRHOwIxUatPH15+nj59WAGTDv3LSGZPEQbJaQ==", "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", diff --git a/src/global.json b/src/global.json index aa2aca45..5bbfe867 100644 --- a/src/global.json +++ b/src/global.json @@ -1,5 +1,6 @@ { "sdk": { - "version": "7.0.200" + "version": "7.0.200", + "rollForward": "latestMajor" } } \ No newline at end of file From 2a0f04a48693cdacf546d4af7c6c52cfb57e1675 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Wed, 12 Jul 2023 15:16:29 -0400 Subject: [PATCH 3/4] Add XML documentation for new classes and properties --- ...PermissionManagingBlazorWebChromeClient.cs | 1 - .../Components/Layers/FeatureLayer.cs | 178 ++++++++++++++++++ .../Components/Layers/Field.cs | 10 +- .../Components/Widgets/FormElement.cs | 32 +++- .../Components/Widgets/FormTemplate.cs | 28 ++- .../Components/GeometryEngineTests.cs | 16 +- 6 files changed, 247 insertions(+), 18 deletions(-) diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.Maui/Platforms/Android/PermissionManagingBlazorWebChromeClient.cs b/samples/dymaptic.GeoBlazor.Core.Sample.Maui/Platforms/Android/PermissionManagingBlazorWebChromeClient.cs index 6992bfe8..e6d92a46 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.Maui/Platforms/Android/PermissionManagingBlazorWebChromeClient.cs +++ b/samples/dymaptic.GeoBlazor.Core.Sample.Maui/Platforms/Android/PermissionManagingBlazorWebChromeClient.cs @@ -16,7 +16,6 @@ namespace dymaptic.GeoBlazor.Core.Sample.Maui.Platforms.Android; -#nullable enable /// /// Borrowed from https://github.com/MackinnonBuck/MauiBlazorPermissionsExample /// diff --git a/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayer.cs b/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayer.cs index c8a931f9..e6f25a2a 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayer.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayer.cs @@ -229,6 +229,9 @@ public IReadOnlyCollection? Fields /// public Relationship[]? Relationships { get; set; } + /// + /// The template used in an associated layer's FeatureForm Widget (Available in GeoBlazor Pro). All of the properties and field configurations set on the layer's FeatureForm are handled via the FormTemplate. + /// public FormTemplate? FormTemplate { get; set; } /// @@ -303,6 +306,10 @@ public async Task SetPopupTemplate(PopupTemplate template) await RegisterChildComponent(template); } + /// + /// Applies edits to features in a layer. New features can be created and existing features can be updated or deleted. Feature geometries and/or attributes may be modified. Only applicable to layers in a feature service and client-side features set through the FeatureLayer's source property. Attachments can also be added, updated or deleted. + /// If client-side features are added, removed or updated at runtime using applyEdits() then use FeatureLayer's queryFeatures() method to return updated features. + /// public async Task ApplyEdits(FeatureEdits edits, FeatureEditOptions? options = null) { return await JsLayerReference!.InvokeAsync("applyEdits", edits, options, @@ -983,54 +990,225 @@ public class CreatePopupTemplateOptions public HashSet? VisibleFieldNames { get; set; } } +/// +/// Object containing features and attachments to be added, updated or deleted. +/// For use with +/// ArcGIS JS API +/// public class FeatureEdits { + /// + /// An array or a collection of features to be added. Values of non nullable fields must be provided when adding new features. Date fields must have numeric values representing universal time. + /// public IEnumerable? AddFeatures { get; set; } + /// + /// An array or a collection of features to be updated. Each feature must have valid objectId. Values of non nullable fields must be provided when updating features. Date fields must have numeric values representing universal time. + /// public IEnumerable? UpdateFeatures { get; set; } + /// + /// An array or a collection of features, or an array of objects with objectId or globalId of each feature to be deleted. When an array or collection of features is passed, each feature must have a valid objectId. When an array of objects is used, each object must have a valid value set for objectId or globalId property. + /// public IEnumerable? DeleteFeatures { get; set; } + /// + /// An array of attachments to be added. Applies only when the options.globalIdUsed parameter is set to true. User must provide globalIds for all attachments to be added. + /// public IEnumerable? AddAttachments { get; set; } + /// + /// An array of attachments to be updated. Applies only when the options.globalIdUsed parameter is set to true. User must provide globalIds for all attachments to be updated. + /// public IEnumerable? UpdateAttachments { get; set; } + /// + /// An array of globalIds for attachments to be deleted. Applies only when the options.globalIdUsed parameter is set to true. + /// public IEnumerable? DeleteAttachments { get; set; } } + +/// +/// AttachmentEdit represents an attachment that can be added, updated or deleted via applyEdits. This object can be either pre-uploaded data or base 64 encoded data. +/// ArcGIS JS API +/// public class AttachmentEdit { + /// + /// The feature, objectId or globalId of feature associated with the attachment. + /// public Graphic Feature { get; set; } = default!; + + /// + /// The attachment to be added, updated or deleted. + /// public Attachment Attachment { get; set; } = default!; } +/// +/// The attachment to be added, updated or deleted in an . +/// public class Attachment { + /// + /// The globalId of the attachment to be added or updated. These Global IDs must be from the Global ID field created by ArcGIS. For more information on ArcGIS generated Global IDs, see the Global IDs and Attachments and relationship classes sections in the Data Preparation documentation. + /// public string GlobalId { get; set; } = default!; + + /// + /// The name of the attachment. This parameter must be set if the attachment type is Blob. + /// public string? Name { get; set; } + + /// + /// The content type of the attachment. For example, 'image/jpeg'. See the ArcGIS REST API documentation for more information on supported attachment types. + /// public string? ContentType { get; set; } + + /// + /// The id of pre-loaded attachment. + /// public string? UploadId { get; set; } + + /// + /// The attachment data. + /// public string? Data { get; set; } } +/// +/// The options to use with . +/// public class FeatureEditOptions { + /// + /// The geodatabase version to apply the edits. This parameter applies only if the capabilities.data.isVersioned property of the layer is true. If the gdbVersion parameter is not specified, edits are made to the published map’s version. + /// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? GdbVersion { get; set; } + + /// + /// Indicates whether the edit results should return the time edits were applied. If true, the feature service will return the time edits were applied in the edit result's editMoment property. Only applicable with ArcGIS Server services only. + /// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? ReturnEditMoment { get; set; } + + /// + /// If set to original-and-current-features, the EditedFeatureResult parameter will be included in the applyEdits response. It contains all edited features participating in composite relationships in a database as result of editing a feature. Note that even for deletions, the geometry and attributes of the deleted feature are returned. The original-and-current-features option is only valid when rollbackOnFailureEnabled is true. The default value is none, which will not include the EditedFeatureResult parameter in the response. This is only applicable with ArcGIS Server services only. + /// + /// + /// Possible values: "none" | "original-and-current-features" + /// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? ReturnServiceEditsOption { get; set; } + + /// + /// Indicates whether the edits should be applied only if all submitted edits succeed. If false, the server will apply the edits that succeed even if some of the submitted edits fail. If true, the server will apply the edits only if all edits succeed. The layer's capabilities.editing.supportsRollbackOnFailure property must be true if using this parameter. If supportsRollbackOnFailure is false for a layer, then rollbackOnFailureEnabled will always be true, regardless of how the parameter is set. + /// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? RollbackOnFailureEnabled { get; set; } + + /// + /// Indicates whether the edits can be applied using globalIds of features or attachments. This parameter applies only if the layer's capabilities.editing.supportsGlobalId property is true. When false, globalIds submitted with the features are ignored and the service assigns new globalIds to the new features. When true, the globalIds must be submitted with the new features. When updating existing features, if the globalIdUsed is false, the objectIds of the features to be updated must be provided. If the globalIdUsed is true, globalIds of features to be updated must be provided. When deleting existing features, set this property to false as deletes operation only accepts objectIds at the current version of the API. + /// When adding, updating or deleting attachments, globalIdUsed parameter must be set to true and the attachment globalId must be set. For new attachments, the user must provide globalIds. In order for an attachment to be updated or deleted, clients must include its globalId. Attachments are not supported in an edit payload when globalIdUsed is false. + /// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? GlobalIdUsed { get; set; } } +/// +/// The result of . +/// ArcGIS JS API +/// +/// +/// Result of adding features. +/// +/// +/// Result of updating features. +/// +/// +/// Result of deleting features. +/// +/// +/// Result of adding attachments. +/// +/// +/// Result of updating attachments. +/// +/// +/// Result of deleting attachments. +/// +/// +/// Edited features as result of editing a feature that participates in composite relationships in a database. This parameter is returned only when the returnServiceEditsOption parameter of the applyEdits() method is set to original-and-current-features. +/// +/// +/// The time edits were applied to the feature service. This parameter is returned only when the returnEditMoment parameter of the applyEdits() method is set to true. +/// public record FeatureEditsResult(FeatureEditResult[] AddFeatureResults, FeatureEditResult[] UpdateFeatureResults, FeatureEditResult[] DeleteFeatureResults, FeatureEditResult[] AddAttachmentResults, FeatureEditResult[] UpdateAttachmentResults, FeatureEditResult[] DeleteAttachmentResults, EditedFeatureResult[]? EditedFeatureResults, long? EditMoment); +/// +/// FeatureEditResult represents the result of adding, updating or deleting a feature or an attachment. +/// ArcGIS JS API +/// +/// +/// The objectId of the feature or the attachmentId of the attachment that was edited. +/// +/// +/// The globalId of the feature or the attachment that was edited. +/// +/// +/// If the edit failed, the edit result includes an error. +/// public record FeatureEditResult(long? ObjectId, string? GlobalId, EditError? Error); + +/// +/// The error object in a +/// +/// +/// Error name. +/// +/// +/// Message describing the error. +/// public record EditError(string? Name, string? Message); + +/// +/// Results returned from the applyEdits method if the returnServiceEditsOption parameter is set to original-and-current-features. It contains features that were added, deleted or updated in different feature layers of a feature service as a result of editing a single feature that participates in a composite relationship in a database. The results are organized by each layer affected by the edit. For example, if a feature is deleted and it is the origin in a composite relationship, the edited features as a result of this deletion are returned. +/// The editedFeatures object returns full features including newly added features, the original features prior to delete, the original and current features for updates. +/// ArcGIS JS API +/// +/// +/// The layerId of the feature layer where features were edited. +/// +/// +/// Object containing all edited features belonging to the specified layer. +/// public record EditedFeatureResult(long? LayerId, EditedFeatures? EditedFeatures); +/// +/// The edited features of an +/// +/// +/// Newly added features as a result of editing a feature that participates in a composite relationship. +/// +/// +/// Object containing original and updated features as a result of editing a feature that participates in a composite relationship. +/// +/// +/// Deleted features as a result of editing a feature that participates in a composite relationship. +/// +/// +/// Edited features are returned in the spatial reference of the feature service. +/// public record EditedFeatures(Graphic[] Adds, EditedFeatureUpdate[] Updates, Graphic[] Deletes, SpatialReference SpatialReference); + +/// +/// The update object of a . +/// +/// +/// Original feature before the update took place. +/// +/// +/// Updated feature as a result of editing a feature that participates in a composite relationship. +/// public record EditedFeatureUpdate(Graphic[] Original, Graphic[] Current); \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/Layers/Field.cs b/src/dymaptic.GeoBlazor.Core/Components/Layers/Field.cs index abb6967a..ec6b120b 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Layers/Field.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Layers/Field.cs @@ -9,10 +9,7 @@ namespace dymaptic.GeoBlazor.Core.Components.Layers; /// /// Information about each field in a layer. Field objects must be constructed when creating a FeatureLayer from /// client-side graphics. This class allows you to define the schema of each field in the FeatureLayer. -/// -/// ArcGIS -/// JS API -/// +/// ArcGIS JS API /// public class Field : MapComponent { @@ -133,8 +130,12 @@ public Field(FieldType type, string? name = null, string? alias = null, string? [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public FieldValueType? ValueType { get; set; } + /// + /// The domain associated with the field. Domains are used to constrain the values allowed in a field. There are two types of domains: RangeDomain and CodedValueDomain. + /// public Domain? Domain { get; set; } + /// public override async Task RegisterChildComponent(MapComponent child) { switch (child) @@ -153,6 +154,7 @@ public override async Task RegisterChildComponent(MapComponent child) } } + /// public override async Task UnregisterChildComponent(MapComponent child) { switch (child) diff --git a/src/dymaptic.GeoBlazor.Core/Components/Widgets/FormElement.cs b/src/dymaptic.GeoBlazor.Core/Components/Widgets/FormElement.cs index 00def72e..b0cbfb79 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Widgets/FormElement.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Widgets/FormElement.cs @@ -5,6 +5,11 @@ namespace dymaptic.GeoBlazor.Core.Components.Widgets; + +/// +/// Form elements define what should display within the FormTemplate elements. There are three specific element types: +/// ArcGIS JS API +/// [JsonConverter(typeof(FormElementConverter))] public abstract class FormElement: MapComponent { @@ -131,11 +136,21 @@ public override async Task UnregisterChildComponent(MapComponent child) } } +/// +/// A GroupElement form element defines a container that holds a set of form elements that can be expanded, collapsed, or displayed together. This is the preferred way to set grouped field configurations within a FeatureForm or Featurelayer formTemplate. +/// ArcGIS JS API +/// public class GroupElement : FormElement { /// public override string Type => "group"; + /// + /// An array of field elements to display as grouped. These objects represent an ordered list of form elements. + /// + /// + /// Nested group elements are not supported. + /// public List? Elements { get; set; } /// @@ -203,7 +218,22 @@ internal class FormElementConverter : JsonConverter { public override FormElement? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - throw new NotImplementedException(); + using JsonDocument document = JsonDocument.ParseValue(ref reader); + JsonElement element = document.RootElement; + + if (element.TryGetProperty("type", out JsonElement typeElement)) + { + string type = typeElement.GetString() ?? string.Empty; + + return type switch + { + "field" => JsonSerializer.Deserialize(element.GetRawText(), options), + "group" => JsonSerializer.Deserialize(element.GetRawText(), options), + _ => throw new JsonException($"Unknown type: {type}") + }; + } + + return null; } public override void Write(Utf8JsonWriter writer, FormElement value, JsonSerializerOptions options) diff --git a/src/dymaptic.GeoBlazor.Core/Components/Widgets/FormTemplate.cs b/src/dymaptic.GeoBlazor.Core/Components/Widgets/FormTemplate.cs index 891815dc..3b9b4d62 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Widgets/FormTemplate.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Widgets/FormTemplate.cs @@ -4,21 +4,46 @@ namespace dymaptic.GeoBlazor.Core.Components.Widgets; +/// +/// A FormTemplate formats and defines the content of a FeatureForm for a specific Layer or Graphic. A FormTemplate allows the user to access values from feature attributes when a feature in the view is selected. +/// The FormTemplate can be set directly on a FeatureLayer, a FeatureForm, or its view model. The template consists of various elements that display a specific type of form data. +/// ArcGIS JS API +/// public class FormTemplate : MapComponent { + /// + /// The description of the form. + /// [Parameter] public string? Description { get; set; } + /// + /// Indicates whether to retain or clear a form's field element values. Use this property when a field element initially displays as visible but then updates to not display as a result of an applied visibilityExpression. + /// [Parameter] public bool? PreserveFieldValuesWhenHidden { get; set; } - + + /// + /// The string template for defining how to format the title displayed at the top of a form. + /// [Parameter] public string? Title { get; set; } + /// + /// An array of form element objects that represent an ordered list of form elements. + /// Elements are designed to allow the form author the ability to define the layout for fields when collecting and/or editing data. + /// + /// + /// Nested group elements are not supported. + /// public HashSet? Elements { get; set; } + /// + /// An array of objects or ExpressionInfo[] that reference Arcade expressions following the specification defined by the Form Constraint Profile or the Form Calculation Profile. Form Constraint expressions must return either true or false. Form Calculation expressions must return a string, date, or a number. + /// public HashSet? ExpressionInfos { get; set; } + /// public override async Task RegisterChildComponent(MapComponent child) { switch (child) @@ -42,6 +67,7 @@ public override async Task RegisterChildComponent(MapComponent child) } } + /// public override async Task UnregisterChildComponent(MapComponent child) { switch (child) diff --git a/tests/dymaptic.GeoBlazor.Core.Test.Blazor/Components/GeometryEngineTests.cs b/tests/dymaptic.GeoBlazor.Core.Test.Blazor/Components/GeometryEngineTests.cs index 9f1657c5..7b3769b3 100644 --- a/tests/dymaptic.GeoBlazor.Core.Test.Blazor/Components/GeometryEngineTests.cs +++ b/tests/dymaptic.GeoBlazor.Core.Test.Blazor/Components/GeometryEngineTests.cs @@ -175,7 +175,6 @@ public async Task TestBufferCallAfterDensified() } }; var polyline = new PolyLine(mapPaths, new SpatialReference(102100)); - PolyLine densifiedLine = (PolyLine)await GeometryEngine.Densify(polyline, 0.1, LinearUnit.Miles); Polygon buffer = await GeometryEngine.Buffer(polyline, 20, LinearUnit.Yards); Assert.IsNotNull(buffer); } @@ -1318,7 +1317,7 @@ public async Task TestSimplify() } }, new SpatialReference(102100)); - Geometry? simplifiedGeometry = await GeometryEngine.Simplify(polygon); + Geometry simplifiedGeometry = await GeometryEngine.Simplify(polygon); Assert.IsNotNull(simplifiedGeometry); Assert.AreNotEqual(polygon, simplifiedGeometry); @@ -1355,7 +1354,7 @@ public async Task TestSymmetricDifference() } }, new SpatialReference(102100)); - Geometry? symmetricDifference = await GeometryEngine.SymmetricDifference(polygon1, polygon2); + Geometry symmetricDifference = await GeometryEngine.SymmetricDifference(polygon1, polygon2); Assert.IsNotNull(symmetricDifference); Assert.AreNotEqual(polygon1, symmetricDifference); @@ -1406,7 +1405,7 @@ public async Task TestSymmetricDifferences() } }, new SpatialReference(102100)); - Geometry[]? symmetricDifferences = + Geometry[] symmetricDifferences = await GeometryEngine.SymmetricDifference(new Geometry[] { polygon1, polygon2 }, polygon3); Assert.IsNotNull(symmetricDifferences); @@ -1522,7 +1521,7 @@ public async Task TestUnion() } }, new SpatialReference(102100)); - Geometry? union = await GeometryEngine.Union(polygon1, polygon2); + Geometry union = await GeometryEngine.Union(polygon1, polygon2); Assert.IsNotNull(union); Assert.AreNotEqual(polygon1, union); @@ -1600,11 +1599,6 @@ public async Task TestWithinFalse() Assert.IsFalse(within); } - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - await base.OnAfterRenderAsync(firstRender); - } - + private readonly Random _random = new(); } \ No newline at end of file From 386825bdadf3fcb49ba6b83091dcc09120006545 Mon Sep 17 00:00:00 2001 From: Tim Purdum Date: Thu, 13 Jul 2023 08:50:06 -0400 Subject: [PATCH 4/4] Added cursor management for new Pro sample --- .../Components/Views/MapView.razor.cs | 18 ++++++++++++++++++ .../Scripts/arcGisJsInterop.ts | 10 ++++++++++ .../Scripts/featureLayer.ts | 6 +++--- 3 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/dymaptic.GeoBlazor.Core/Components/Views/MapView.razor.cs b/src/dymaptic.GeoBlazor.Core/Components/Views/MapView.razor.cs index 6b85438e..e0cca48f 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Views/MapView.razor.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Views/MapView.razor.cs @@ -1986,6 +1986,24 @@ public async Task ToScreen(Point mapPoint) CancellationTokenSource.Token, mapPoint, Id); } + /// + /// Returns the current cursor when hovering over the view. + /// + public async Task GetCursor() + { + return await ViewJsModule!.InvokeAsync("getCursor", + CancellationTokenSource.Token, Id); + } + + /// + /// Sets the cursor for the view. + /// + public async Task SetCursor(string cursor) + { + await ViewJsModule!.InvokeVoidAsync("setCursor", + CancellationTokenSource.Token, cursor, Id); + } + /// /// The callback method for returning a chunk of data from a Blazor Server hit test. /// diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts b/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts index 4c399e88..8adfb850 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts @@ -2329,4 +2329,14 @@ export function getAuthenticationManager(dotNetRef: any, apiKey: string | null, _authenticationManager = new AuthenticationManager(dotNetRef, apiKey, appId, portalUrl); } return _authenticationManager; +} + +export function getCursor(viewId: string): string { + let view = arcGisObjectRefs[viewId] as MapView; + return view.container.style.cursor; +} + +export function setCursor(cursorType: string, viewId: string) { + let view = arcGisObjectRefs[viewId] as MapView; + view.container.style.cursor = cursorType; } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts index 87dfc915..812ebcde 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayer.ts @@ -227,13 +227,13 @@ export default class FeatureLayerWrapper { }; } - applyEdits(edits: DotNetApplyEdits, options: any, viewId: string): Promise { + async applyEdits(edits: DotNetApplyEdits, options: any, viewId: string): Promise { let jsEdits = buildJsApplyEdits(edits, viewId); let result; if (options !== null) { - result = this.layer.applyEdits(jsEdits, options); + result = await this.layer.applyEdits(jsEdits, options); } else { - result = this.layer.applyEdits(jsEdits); + result = await this.layer.applyEdits(jsEdits); } return result;