diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Bookmarks.razor b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Bookmarks.razor new file mode 100644 index 00000000..6a2e6e7a --- /dev/null +++ b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Bookmarks.razor @@ -0,0 +1,71 @@ +@page "/Bookmarks" +@using dymaptic.GeoBlazor.Core.Components.Widgets; + +Bookmarks and Bookmarks Widget +

Bookmarks Widget

+ + + + + +

+ Sample demonstrates the use of the Bookmarks widget and Bookmark actions. + This follows the ESRI eample, but the Timeline Widget is included with GeoBlazor pro. +

+ + + + + + + + + + + +@code { + private MapView? MapView { get; set; } + + private FeatureLayerView? _layerView { get; set; } + + private void OnLayerViewCreate(LayerViewCreateEvent evt) + { + _layerView = evt.LayerView as FeatureLayerView; + } + + private async void OnMapClick() + { + var bookmarks = await ((WebMap)MapView?.Map).GetBookmarks(); + } + + private void OnBookmarkClick(BookmarkSelectEvent eventArgs) + { + var layer = MapView!.Map!.Layers.FirstOrDefault(); + if (_layerView is null) return; + + var hurricaneName = eventArgs.Bookmark.Name; + + var featureEffect = new FeatureEffect + { + Filter = new FeatureFilter() + { + Where = $"Name = '{hurricaneName.ToUpper()}'" + }, + ExcludedEffect = new() { new Effect() { Value = "grayscale(100%) opacity(30%)" } } + }; + _ = _layerView.SetFeatureEffect(featureEffect); + } + +} \ No newline at end of file diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Widgets.razor b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Widgets.razor index 06d508c2..1185b173 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Widgets.razor +++ b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Pages/Widgets.razor @@ -1,4 +1,5 @@ @page "/widgets" +@using GeoBlazor.Core.Components.Widgets; Widgets

Widgets

@@ -37,6 +38,9 @@
+
+ +
@@ -83,6 +87,11 @@ { } + @if (_showBookmarks) + { + + + } @code { @@ -119,9 +128,72 @@ case nameof(MeasurementWidget): _showMeasurement = !_showMeasurement; break; + case nameof(BookmarksWidget): + _showBookmarks = !_showBookmarks; + break; } } + protected override void OnAfterRender(bool firstRender) + { + if (_showBookmarks) + { + //the MapView.Refresh() is needed because the bookmarks are added after the BookmarksWidget is rendered + _bookmarkWidget!.Bookmarks = _bookmarks; + MapView!.Refresh(); + } + } + + private List _bookmarks = new List() + { + new Bookmark() + { + Name="Angeles National Forest", + Thumbnail="/_content/dymaptic.GeoBlazor.Core.Sample.Shared/images/Blazor-API-60px.png", + Viewpoint = new Viewpoint() + { + TargetGeometry = new Extent() + { + SpatialReference = new SpatialReference(102100), + Xmin = -13139131.948889678, + Ymin = 4047767.23531948, + Xmax = -13092887.54677721, + Ymax = 4090610.189673263, + } + } + }, + new Bookmark() + { + Name = "Crystal Lake", + Viewpoint = new Viewpoint() + { + TargetGeometry = new Extent() + { + SpatialReference = new SpatialReference(102100), + Xmin = -13125852.551697943, + Ymin = 4066904.1101411926, + Xmax = -13114291.451169826, + Ymax = 4077614.8487296384 + } + } + }, + new Bookmark() + { + Name = "Mt. Waterman", + Viewpoint = new Viewpoint() + { + TargetGeometry = new Extent() + { + SpatialReference = new SpatialReference(102100), + Xmin= -13185668.186639601, + Ymin= 4066176.418652561, + Xmax= -13183855.195875114, + Ymax= 4067515.260976006 + } + } + } + }; + private bool _showSearch; private bool _showLocate; private bool _showBasemapToggle; @@ -131,4 +203,7 @@ private bool _showHome; private bool _showCompass; private bool _showMeasurement; + private bool _showBookmarks; + + private BookmarksWidget? _bookmarkWidget; } \ No newline at end of file diff --git a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor index 0c9f250b..bcc740f7 100644 --- a/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor +++ b/samples/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor @@ -77,6 +77,7 @@ new("basemaps", "Basemaps", "oi-map"), new("feature-layers", "Feature Layers", "oi-layers"), new("popups", "Popups", "oi-chat"), + new("bookmarks", "Bookmarks", "oi-bookmark"), new("popup-actions", "Popup Actions", "oi-bullhorn"), new("vector-layer", "Vector Layer", "oi-arrow-right"), new("layer-lists", "Layer Lists", "oi-list"), diff --git a/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayerView.cs b/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayerView.cs index 945c1b2c..4c8da8ef 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayerView.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Layers/FeatureLayerView.cs @@ -36,6 +36,12 @@ internal FeatureLayerView(LayerView layerView, AbortManager abortManager, bool i /// public FeatureFilter? Filter { get; private set; } + + /// + /// The featureEffect can be used to draw attention features of interest. + /// + public FeatureEffect? FeatureEffect { get; private set; } + /// /// Sets the for this view. /// @@ -48,6 +54,20 @@ public async Task SetFilter(FeatureFilter? filter) Filter = filter; } + + /// + /// Sets the for this view. + /// + /// + /// The new effect (or null to clear) to apply to this view. + /// + + public async Task SetFeatureEffect(FeatureEffect? featureEffect) + { + await JsObjectReference!.InvokeVoidAsync("setFeatureEffect", CancellationTokenSource.Token, featureEffect); + FeatureEffect = featureEffect; + } + /// /// Highlights the given feature(s). /// @@ -377,4 +397,52 @@ public class FeatureFilter /// [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public string? Where { get; set; } +} + +/// +/// +/// ArcGIS +/// JS API +/// +/// +public class FeatureEffect +{ + /// + /// The effect applied to features that do not meet the filter requirements. + /// + public List? ExcludedEffect { get; set; } + + /// + /// Indicates if labels are visible for features that are excluded from the filter. + /// + public bool? ExcludedLabelsVisible { get; set; } + + /// + /// The filter that drives the effect. + /// + public FeatureFilter? Filter { get; set; } + + /// + /// The effect applied to features that meet the filter requirements. + /// + public List? IncludedEffect { get; set; } + +} + +/// +/// +/// ArcGIS +/// JS API +/// +/// +public class Effect +{ + /// + /// The scale of the view for the effect to take place. Use only when setting a scale dependent effect. + /// + public double? Scale { get; set; } + /// + /// The effect to be applied to a layer or layerView at the corresponding scale. Use only when setting a scale dependent effect. + /// + public string? Value { get; set; } } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/Views/Viewpoint.cs b/src/dymaptic.GeoBlazor.Core/Components/Views/Viewpoint.cs new file mode 100644 index 00000000..87662bc6 --- /dev/null +++ b/src/dymaptic.GeoBlazor.Core/Components/Views/Viewpoint.cs @@ -0,0 +1,68 @@ +using dymaptic.GeoBlazor.Core.Components.Geometries; +using Microsoft.AspNetCore.Components; +using System.Text.Json.Serialization; + +namespace dymaptic.GeoBlazor.Core.Components.Views; + +/// +/// Describes a point of view for a 2D or 3D view. In a 2D view, the viewpoint is determined using a center point and scale value. +/// In a 3D view, it is determined using a camera position. +/// The Viewpoint can be bookmarked for later use, or used for navigation purposes. +/// +public class Viewpoint : MapComponent +{ + ///public Camera Camera { get; set; } + + /// + /// The rotation of due north in relation to the top of the view in degrees. + /// + [Parameter] + public double? Rotation { get; set; } = 0; + + /// + /// The scale of the viewpoint. + /// + [Parameter] + public double? Scale { get; set; } = 0; + + /// + /// The target geometry framed by the viewpoint. + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Geometry? TargetGeometry { get; set; } + + /// + public override async Task RegisterChildComponent(MapComponent child) + { + switch (child) + { + case Geometry geometry: + TargetGeometry = geometry; + break; + default: + await base.RegisterChildComponent(child); + break; + } + } + + /// + public override async Task UnregisterChildComponent(MapComponent child) + { + switch (child) + { + case Geometry geometry: + TargetGeometry = null; + break; + default: + await base.UnregisterChildComponent(child); + break; + } + + } + + internal override void ValidateRequiredChildren() + { + TargetGeometry?.ValidateRequiredChildren(); + } + +} diff --git a/src/dymaptic.GeoBlazor.Core/Components/WebMap.cs b/src/dymaptic.GeoBlazor.Core/Components/WebMap.cs index 40504749..fe7a1bf6 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/WebMap.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/WebMap.cs @@ -1,4 +1,10 @@ -namespace dymaptic.GeoBlazor.Core.Components; +using dymaptic.GeoBlazor.Core.Components.Widgets; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using System.Text.Json.Serialization; + + +namespace dymaptic.GeoBlazor.Core.Components; /// /// Loads a WebMap from ArcGIS Online or ArcGIS Enterprise portal into a MapView. It defines the content, style, and @@ -58,4 +64,20 @@ internal override void ValidateRequiredChildren() base.ValidateRequiredChildren(); PortalItem?.ValidateRequiredChildren(); } + + /// + /// Gets the bookmarks defined in the WebMap from layers + /// + /// A list of bookmarks or null + public async Task> GetBookmarks() + { + var bookmarks = new List(); + + if (JsModule != null) + { + bookmarks = + await JsModule!.InvokeAsync>("getWebMapBookmarks", CancellationTokenSource.Token, this.View?.Id); + } + return bookmarks; + } } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/Widgets/Bookmark.cs b/src/dymaptic.GeoBlazor.Core/Components/Widgets/Bookmark.cs new file mode 100644 index 00000000..ad61351d --- /dev/null +++ b/src/dymaptic.GeoBlazor.Core/Components/Widgets/Bookmark.cs @@ -0,0 +1,69 @@ +using dymaptic.GeoBlazor.Core.Components.Views; +using dymaptic.GeoBlazor.Core.Objects; +using Microsoft.AspNetCore.Components; +using System.Text.Json.Serialization; + +namespace dymaptic.GeoBlazor.Core.Components.Widgets; + +/// +/// A bookmark is a saved map extent that allows end users to quickly navigate +/// to a particular area of interest using the Bookmarks widget. +/// They are usually defined part of the WebMap. +/// +public class Bookmark : MapComponent +{ + /// + /// The extent of the specified bookmark. + /// + [Parameter] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public TimeExtent? TimeExtent { get; set; } + + /// + /// The name of the bookmark. + /// + [Parameter] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Name { get; set; } + + /// + /// The URL for a thumbnail image. + /// + [Parameter] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? Thumbnail { get; set; } + + /// + /// The viewpoint of the bookmark item. Defines the rotation, scale, and target geometry of the bookmark. + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public Viewpoint? Viewpoint { get; set; } + + /// + public override async Task RegisterChildComponent(MapComponent child) + { + switch (child) + { + case Viewpoint viewpoint: + Viewpoint = viewpoint; + break; + default: + await base.RegisterChildComponent(child); + break; + } + } + + /// + public override async Task UnregisterChildComponent(MapComponent child) + { + switch (child) + { + case Viewpoint viewpoint: + Viewpoint = null; + break; + default: + await base.UnregisterChildComponent(child); + break; + } + } +} diff --git a/src/dymaptic.GeoBlazor.Core/Components/Widgets/BookmarksWidget.cs b/src/dymaptic.GeoBlazor.Core/Components/Widgets/BookmarksWidget.cs new file mode 100644 index 00000000..c68c8a98 --- /dev/null +++ b/src/dymaptic.GeoBlazor.Core/Components/Widgets/BookmarksWidget.cs @@ -0,0 +1,100 @@ +using dymaptic.GeoBlazor.Core.Events; +using Microsoft.AspNetCore.Components; +using Microsoft.JSInterop; +using System.Text.Json.Serialization; + + +namespace dymaptic.GeoBlazor.Core.Components.Widgets; + +/// +/// The Bookmarks widget allows end users to quickly navigate to a particular area of interest. It displays a list of bookmarks, which are typically defined inside the WebMap. +/// +/// ArcGIS +/// JS API +/// +/// +public class BookmarksWidget : Widget +{ + /// + [JsonPropertyName("type")] + public override string WidgetType => "bookmarks"; + + /// + /// When true, the widget is visually withdrawn and cannot be interacted with. + /// + [Parameter] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public bool? Disabled { get; set; } + + /// + /// Indicates whether the bookmarks are able to be edited. + /// + [Parameter] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public bool? EditingEnabled { get; set; } + + /// + /// Indicates the heading level to use for the message "No bookmarks" when no bookmarks are available in this widget. + /// + [Parameter] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public int? HeadingLevel { get; set; } + + /// + /// A collection of Bookmarks. + /// + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public List? Bookmarks { get; set; } + + /// + /// Handler delegate for click events on the view. + /// + [Parameter] + public EventCallback OnBookmarkSelect { get; set; } + + /// + /// JS-Invokable method to return a selected bookmark + /// + /// + /// The return meta object. + /// + /// + /// Fires after a user clicks on a bookmark. + /// + [JSInvokable] + public async Task OnJavascriptBookmarkSelect(BookmarkSelectEvent bookmarkSelectEvent) + { + await OnBookmarkSelect.InvokeAsync(bookmarkSelectEvent); + } + + /// + public override async Task RegisterChildComponent(MapComponent child) + { + switch (child) + { + case Bookmark bookmark: + if (!Bookmarks.Contains(bookmark)) Bookmarks.Add(bookmark); + WidgetChanged = true; + break; + default: + await base.RegisterChildComponent(child); + break; + } + } + + + /// + public override async Task UnregisterChildComponent(MapComponent child) + { + switch (child) + { + case Bookmark bookmark: + if (Bookmarks!.Contains(bookmark)) Bookmarks.Remove(bookmark); + WidgetChanged = true; + break; + default: + await base.UnregisterChildComponent(child); + break; + } + } +} \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/Widgets/CompassWidget.cs b/src/dymaptic.GeoBlazor.Core/Components/Widgets/CompassWidget.cs index 49ff1b77..900a9f24 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Widgets/CompassWidget.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Widgets/CompassWidget.cs @@ -25,6 +25,7 @@ public class CompassWidget : Widget /// [Parameter] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Obsolete("Use Icon instead")] public string? IconClass { get; set; } /// diff --git a/src/dymaptic.GeoBlazor.Core/Components/Widgets/ExpandWidget.cs b/src/dymaptic.GeoBlazor.Core/Components/Widgets/ExpandWidget.cs index 9dbd73aa..d704bcf5 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Widgets/ExpandWidget.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Widgets/ExpandWidget.cs @@ -1,4 +1,5 @@ -using Microsoft.AspNetCore.Components; +using dymaptic.GeoBlazor.Core.Serialization; +using Microsoft.AspNetCore.Components; using System.Text.Json.Serialization; @@ -21,6 +22,7 @@ public class ExpandWidget : Widget /// [Parameter] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Obsolete("Use ExpandIcon instead")] public string? ExpandIconClass { get; set; } /// @@ -28,10 +30,11 @@ public class ExpandWidget : Widget /// [Parameter] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Obsolete("Use CollapseIcon instead")] public string? CollapseIconClass { get; set; } /// - /// Tooltip to display to indicate Expand widget can be expanded. + /// Tooltip to display to indicate Expand widget can be expanded /// [Parameter] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] @@ -58,7 +61,7 @@ public class ExpandWidget : Widget [Parameter] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public bool? CloseOnEsc { get; set; } - + /// /// The custom HTML content to display within the expanded Expand widget. /// @@ -76,6 +79,33 @@ public class ExpandWidget : Widget [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] public Widget? WidgetContent { get; set; } + /// + /// Indicates whether the widget is currently expanded or not. + /// + [Parameter] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public bool? Expanded { get; set; } + + /// + /// Calcite icon used when the widget is collapsed. Will automatically use the content's icon if it has one. + /// + [Parameter] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? ExpandIcon { get; set; } + + /// + /// Calcite icon used to style the Expand button when the content can be collapsed. + /// + [Parameter] + [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + public string? CollapseIcon { get; set; } + + /// + /// The mode in which the widget displays. + /// + [Parameter] + public Mode Mode { get; set; } = Mode.Auto; + /// public override async Task RegisterChildComponent(MapComponent child) { @@ -110,4 +140,18 @@ public override async Task UnregisterChildComponent(MapComponent child) break; } } +} + + +/// +/// The mode in which the Expander widget displays. These modes are listed below. +/// Possible Values:"auto"|"floating"|"drawer" +//Default Value:"auto" +/// +[JsonConverter(typeof(EnumToKebabCaseStringConverter))] +public enum Mode +{ + Auto, + Floating, + Drawer } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Components/Widgets/HomeWidget.cs b/src/dymaptic.GeoBlazor.Core/Components/Widgets/HomeWidget.cs index 0436f0b8..2265c0cc 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Widgets/HomeWidget.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Widgets/HomeWidget.cs @@ -21,6 +21,7 @@ public class HomeWidget : Widget /// [Parameter] [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] + [Obsolete("Use Icon instead")] public string? IconClass { get; set; } /// diff --git a/src/dymaptic.GeoBlazor.Core/Components/Widgets/Widget.cs b/src/dymaptic.GeoBlazor.Core/Components/Widgets/Widget.cs index 03a14d7c..e1f94c25 100644 --- a/src/dymaptic.GeoBlazor.Core/Components/Widgets/Widget.cs +++ b/src/dymaptic.GeoBlazor.Core/Components/Widgets/Widget.cs @@ -69,6 +69,34 @@ public void OnWidgetCreated(IJSObjectReference jsObjectReference) JsObjectReference = jsObjectReference; } + /// + protected override async Task OnAfterRenderAsync(bool firstRender) + { + await base.OnAfterRenderAsync(firstRender); + + if (WidgetChanged && MapRendered) + { + await UpdateWidget(); + } + } + + + private async Task UpdateWidget() + { + WidgetChanged = false; + + if (JsModule is null) return; + + // ReSharper disable once RedundantCast + await JsModule!.InvokeVoidAsync("updateWidget", CancellationTokenSource.Token, + (object)this, View!.Id); + } + + /// + /// Indicates if the widget has changed since the last render. + /// + protected bool WidgetChanged; + /// /// JS Object Reference to the widget /// diff --git a/src/dymaptic.GeoBlazor.Core/Events/BookmarkSelectEvent.cs b/src/dymaptic.GeoBlazor.Core/Events/BookmarkSelectEvent.cs new file mode 100644 index 00000000..4aca63dc --- /dev/null +++ b/src/dymaptic.GeoBlazor.Core/Events/BookmarkSelectEvent.cs @@ -0,0 +1,10 @@ +using dymaptic.GeoBlazor.Core.Components.Widgets; + +namespace dymaptic.GeoBlazor.Core.Events; + +/// +/// Event object for a a bookmark select event from the BookmarksWidget. +/// +/// The Bookmark that was selected +public record BookmarkSelectEvent(Bookmark Bookmark); + diff --git a/src/dymaptic.GeoBlazor.Core/Events/ClickEvent.cs b/src/dymaptic.GeoBlazor.Core/Events/ClickEvent.cs index 46724536..cb2bd521 100644 --- a/src/dymaptic.GeoBlazor.Core/Events/ClickEvent.cs +++ b/src/dymaptic.GeoBlazor.Core/Events/ClickEvent.cs @@ -41,4 +41,4 @@ namespace dymaptic.GeoBlazor.Core.Events; /// public record ClickEvent(string Type, int? EventId, bool? Cancelable, Point MapPoint, double X, double Y, int Button, int Buttons, double Timestamp, DomPointerEvent Native, PointerType? PointerType) - : JsEvent(Type, EventId, Cancelable, Timestamp, Native, PointerType); \ No newline at end of file + : JsEvent(Type, EventId, Cancelable, Timestamp, Native, PointerType); diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts b/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts index e35780f7..0aa7dc6f 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/arcGisJsInterop.ts @@ -27,6 +27,7 @@ import Search from "@arcgis/core/widgets/Search"; import Locate from "@arcgis/core/widgets/Locate"; import Widget from "@arcgis/core/widgets/Widget"; import Measurement from "@arcgis/core/widgets/Measurement"; +import Bookmarks from "@arcgis/core/widgets/Bookmarks"; import GraphicsLayer from "@arcgis/core/layers/GraphicsLayer"; import FeatureLayer from "@arcgis/core/layers/FeatureLayer"; import Layer from "@arcgis/core/layers/Layer"; @@ -61,13 +62,15 @@ import { buildDotNetPoint, buildDotNetPopupTemplate, buildDotNetSpatialReference, - buildViewExtentUpdate + buildViewExtentUpdate, + buildDotNetBookmark } from "./dotNetBuilder"; import { buildJsAttributes, buildJsExtent, - buildJsFields, buildJsFormTemplate, + buildJsFields, + buildJsFormTemplate, buildJsGeometry, buildJsGraphic, buildJsPoint, @@ -77,7 +80,9 @@ import { buildJsPortalItem, buildJsRenderer, buildJsSpatialReference, - buildJsSymbol, templateTriggerActionHandler + buildJsSymbol, + templateTriggerActionHandler, + buildJsBookmark } from "./jsBuilder"; import { DotNetExtent, @@ -984,6 +989,28 @@ export async function updateLayer(layerObject: any, viewId: string): Promise { + try { + setWaitCursor(viewId); + let currentWidget = arcGisObjectRefs[widgetObject.id] as Widget; + let view = arcGisObjectRefs[viewId] as View; + + if (currentWidget === undefined) { + unsetWaitCursor(viewId); + return; + } + + switch (widgetObject.type) { + case 'bookmarks': + let bookmarks = currentWidget as Bookmarks; + bookmarks.bookmarks = widgetObject.bookmarks.map(buildJsBookmark) + break; + } + unsetWaitCursor(viewId); + } catch (error) { + logError(error, viewId); + } +} export function findPlaces(addressQueryParams: any, symbol: any, popupTemplateObject: any, viewId: string): void { try { setWaitCursor(viewId); @@ -1725,7 +1752,9 @@ async function createWidget(widget: any, viewId: string): Promise view.ui.remove(content); const expand = new Expand({ view, - content: content + content: content, + expanded: widget.expanded, + mode: widget.mode, }); if (hasValue(widget.autoCollapse)) { @@ -1744,6 +1773,14 @@ async function createWidget(widget: any, viewId: string): Promise expand.collapseIconClass = widget.collapseIconClass; } + if (hasValue(widget.expandIcon)) { + expand.expandIcon = widget.expandIcon; + } + + if (hasValue(widget.collapseIcon)) { + expand.collapseIcon = widget.collapseIcon; + } + if (hasValue(widget.expandTooltip)) { expand.expandTooltip = widget.expandTooltip; } @@ -1767,6 +1804,26 @@ async function createWidget(widget: any, viewId: string): Promise icon: widget.icon ?? undefined, }); break; + case 'bookmarks': + const bookmarkWidget = new Bookmarks({ + view: view, + editingEnabled: widget.editingEnabled, + disabled: widget.disabled, + icon: widget.icon, + label: widget.label + }); + if (widget.bookmarks != null) { + bookmarkWidget.bookmarks = widget.bookmarks.map(buildJsBookmark); + } + + bookmarkWidget.on('bookmark-select', (event) => { + widget.dotNetWidgetReference.invokeMethodAsync('OnJavascriptBookmarkSelect', { + bookmark: buildDotNetBookmark(event.bookmark) + }); + }); + + newWidget = bookmarkWidget; + break; default: return null; } @@ -2388,4 +2445,19 @@ export function getCursor(viewId: string): string { export function setCursor(cursorType: string, viewId: string) { let view = arcGisObjectRefs[viewId] as MapView; view.container.style.cursor = cursorType; +} + +export function getWebMapBookmarks(viewId: string) { + let view = arcGisObjectRefs[viewId] as MapView; + if (view != null) { + let webMap = view.map as WebMap; + if (webMap != null) { + let arr = webMap.bookmarks.toArray(); + if (arr instanceof Array) { + let abc = arr.map(buildDotNetBookmark); + return abc; + } + } + } + return null; } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/definitions.d.ts b/src/dymaptic.GeoBlazor.Core/Scripts/definitions.d.ts index 4c52a6e2..21143826 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/definitions.d.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/definitions.d.ts @@ -486,4 +486,39 @@ export interface DotNetAttachment { contentType: string; uploadId: string; data: string; +} + +export interface DotNetBookmark { + name: string; + thumbnail: string; + viewpoint: DotNetViewpoint; + timeExtent: any; +} + +export interface DotNetViewpoint { + rotation: number; + scale: number; + targetGeometry: DotNetGeometry; +} + +export interface DotNetFeatureEffect { + excludedEffect: DotNetEffect[]; + excludedLabelsVisible: boolean; + filter: DotNetFeatureFilter; + includedEffect: DotNetEffect[]; +} + +export interface DotNetEffect { + scale: number; + value: string; +} + +export interface DotNetFeatureFilter { + distance: number; + geometry: DotNetGeometry; + objectIds: number[]; + spatialRelationship: string; + timeExtent: any; + units: string; + where: string; } \ No newline at end of file diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts b/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts index d203746e..c9954dce 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/dotNetBuilder.ts @@ -39,7 +39,9 @@ import { DotNetTextPopupContent, DotNetTextSymbol, DotNetViewHit, - MapCollection + MapCollection, + DotNetBookmark, + DotNetViewpoint } from "./definitions"; import Point from "@arcgis/core/geometry/Point"; import Polyline from "@arcgis/core/geometry/Polyline"; @@ -68,7 +70,7 @@ import ExpressionContent from "@arcgis/core/popup/content/ExpressionContent"; import ElementExpressionInfo from "@arcgis/core/popup/ElementExpressionInfo"; import FeatureLayer from "@arcgis/core/layers/FeatureLayer"; import GraphicsLayer from "@arcgis/core/layers/GraphicsLayer"; -import {arcGisObjectRefs} from "./arcGisJsInterop"; +import { arcGisObjectRefs } from "./arcGisJsInterop"; import SimpleMarkerSymbol from "@arcgis/core/symbols/SimpleMarkerSymbol"; import Symbol from "@arcgis/core/symbols/Symbol"; import Graphic from "@arcgis/core/Graphic"; @@ -628,4 +630,30 @@ export function buildViewExtentUpdate(view: View): any { tilt: view.camera?.tilt } } +} + +export function buildDotNetBookmark(bookmark: any): DotNetBookmark { + return { + name: bookmark.name, + thumbnail: bookmark.thumbnail != null ? bookmark.thumbnail.url : null, + timeExtent: buildDotNetTimeExtent(bookmark.timeExtent), + viewpoint: buildDotNetViewpoint(bookmark.viewpoint) + } as DotNetBookmark; +} + +export function buildDotNetViewpoint(viewpoint: any): DotNetViewpoint | null { + if (viewpoint === null) return null; + return { + rotation: viewpoint.rotation, + scale: viewpoint.scale, + targetGeometry: buildDotNetGeometry(viewpoint.targetGeometry) + } as DotNetViewpoint; +} + +export function buildDotNetTimeExtent(timeExtent: any): any | null { + if (timeExtent === null) return null; + return { + start: timeExtent.start.toISOString(), + end: timeExtent.end.toISOString() + } as any; } \ 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 d5df4a34..45fd625a 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/featureLayerView.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/featureLayerView.ts @@ -1,8 +1,9 @@ import FeatureLayerView from "@arcgis/core/views/layers/FeatureLayerView"; import FeatureEffect from "@arcgis/core/layers/support/FeatureEffect"; +import FeatureFilter from "@arcgis/core/layers/support/FeatureFilter"; import Query from "@arcgis/core/rest/support/Query"; -import {DotNetFeatureSet, DotNetGraphic, DotNetQuery} from "./definitions"; -import {buildJsGraphic, buildJsQuery} from "./jsBuilder"; +import {DotNetFeatureSet, DotNetGraphic, DotNetQuery, DotNetFeatureEffect, DotNetFeatureFilter} from "./definitions"; +import {buildJsGraphic, buildJsQuery, buildJsFeatureEffect, buildJsFeatureFilter} from "./jsBuilder"; import {blazorServer, dotNetRefs, graphicsRefs} from "./arcGisJsInterop"; import Handle = __esri.Handle; import {buildDotNetGeometry, buildDotNetGraphic, buildDotNetSpatialReference} from "./dotNetBuilder"; @@ -21,12 +22,14 @@ export default class FeatureLayerViewWrapper { } } - setFeatureEffect(featureEffect: FeatureEffect): void { + setFeatureEffect(dnfeatureEffect: DotNetFeatureEffect): void { + let featureEffect = buildJsFeatureEffect(dnfeatureEffect); this.featureLayerView.featureEffect = featureEffect; } - setFilter(filter: any): void { - this.featureLayerView.filter = filter; + setFilter(dnDeatureFilter: DotNetFeatureFilter): void { + let featureFilter = buildJsFeatureFilter(dnDeatureFilter); + this.featureLayerView.filter = featureFilter; } setMaximumNumberOfFeatures(maximumNumberOfFeatures: number): void { diff --git a/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts b/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts index dcc8b306..1a793a0c 100644 --- a/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts +++ b/src/dymaptic.GeoBlazor.Core/Scripts/jsBuilder.ts @@ -2,7 +2,7 @@ import Extent from "@arcgis/core/geometry/Extent"; import Graphic from "@arcgis/core/Graphic"; import PopupTemplate from "@arcgis/core/PopupTemplate"; -import {arcGisObjectRefs, triggerActionHandler} from "./arcGisJsInterop"; +import { arcGisObjectRefs, triggerActionHandler } from "./arcGisJsInterop"; import Geometry from "@arcgis/core/geometry/Geometry"; import Point from "@arcgis/core/geometry/Point"; import Polyline from "@arcgis/core/geometry/Polyline"; @@ -12,6 +12,10 @@ import SimpleRenderer from "@arcgis/core/renderers/SimpleRenderer"; import Renderer from "@arcgis/core/renderers/Renderer"; import Field from "@arcgis/core/layers/support/Field"; import Font from "@arcgis/core/symbols/Font"; +import Bookmark from "@arcgis/core/webmap/Bookmark" +import Viewpoint from "@arcgis/core/Viewpoint"; +import FeatureEffect from "@arcgis/core/layers/support/FeatureEffect"; +import FeatureFilter from "@arcgis/core/layers/support/FeatureFilter"; import { DotNetApplyEdits, DotNetAttachmentsEdit, @@ -48,7 +52,11 @@ import { DotNetSymbol, DotNetTextPopupContent, DotNetTextSymbol, - DotNetTopFeaturesQuery + DotNetTopFeaturesQuery, + DotNetBookmark, + DotNetViewpoint, + DotNetFeatureEffect, + DotNetFeatureFilter } from "./definitions"; import PictureMarkerSymbol from "@arcgis/core/symbols/PictureMarkerSymbol"; import Popup from "@arcgis/core/widgets/Popup"; @@ -78,7 +86,7 @@ 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 { buildDotNetGraphic } from "./dotNetBuilder"; import ViewClickEvent = __esri.ViewClickEvent; import PopupOpenOptions = __esri.PopupOpenOptions; import PopupDockOptions = __esri.PopupDockOptions; @@ -103,9 +111,10 @@ import RadioButtonsInput from "@arcgis/core/form/elements/inputs/RadioButtonsInp 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) { - return new SpatialReference({wkid: 4326}); + return new SpatialReference({ wkid: 4326 }); } let jsSpatialRef = new SpatialReference(); if (dotNetSpatialReference.wkid !== null) { @@ -212,7 +221,7 @@ export function buildJsPopupTemplate(popupTemplateObject: DotNetPopupTemplate, v } else { content = async (featureSelection) => { try { - let results : DotNetPopupContent[] | null = await popupTemplateObject.dotNetPopupTemplateReference + let results: DotNetPopupContent[] | null = await popupTemplateObject.dotNetPopupTemplateReference .invokeMethodAsync("OnContentFunction", buildDotNetGraphic(featureSelection.graphic)); return results?.map(r => buildJsPopupContent(r)); } catch (error) { @@ -439,6 +448,34 @@ export function buildJsGeometry(geometry: DotNetGeometry): Geometry | null { return geometry as any; } +export function buildJsBookmark(dnBookmark: DotNetBookmark): Bookmark | null { + if (dnBookmark === undefined || dnBookmark === null) return null; + let bookmark = new Bookmark(); + bookmark.name = dnBookmark.name ?? undefined; + bookmark.timeExtent = dnBookmark.timeExtent ?? undefined; + + if (!(dnBookmark.thumbnail == null)) { + //ESRI has this as an "object" with url property + let thumbnail = { url: dnBookmark.thumbnail }; + bookmark.thumbnail = thumbnail; + } else { + bookmark.thumbnail = undefined; + } + + bookmark.viewpoint = buildJsViewpoint(dnBookmark.viewpoint); + + return bookmark as Bookmark; +} + +export function buildJsViewpoint(dnViewpoint: DotNetViewpoint): Viewpoint | null { + if (dnViewpoint === undefined || dnViewpoint === null) return null; + let viewpoint = new Viewpoint(); + viewpoint.rotation = dnViewpoint.rotation ?? undefined; + viewpoint.scale = dnViewpoint.scale ?? undefined; + viewpoint.targetGeometry = buildJsGeometry(dnViewpoint.targetGeometry); + return viewpoint as Viewpoint; +} + export function buildJsPoint(dnPoint: DotNetPoint): Point | null { if (dnPoint === undefined || dnPoint === null) return null; let point = new Point({ @@ -451,7 +488,7 @@ export function buildJsPoint(dnPoint: DotNetPoint): Point | null { if (hasValue(dnPoint.spatialReference)) { point.spatialReference = buildJsSpatialReference(dnPoint.spatialReference); } else { - point.spatialReference = new SpatialReference({wkid: 4326}); + point.spatialReference = new SpatialReference({ wkid: 4326 }); } return point; @@ -465,7 +502,7 @@ export function buildJsPolyline(dnPolyline: DotNetPolyline): Polyline | null { if (hasValue(dnPolyline.spatialReference)) { polyline.spatialReference = buildJsSpatialReference(dnPolyline.spatialReference); } else { - polyline.spatialReference = new SpatialReference({wkid: 4326}); + polyline.spatialReference = new SpatialReference({ wkid: 4326 }); } return polyline; } @@ -478,7 +515,7 @@ export function buildJsPolygon(dnPolygon: DotNetPolygon): Polygon | null { if (hasValue(dnPolygon.spatialReference)) { polygon.spatialReference = buildJsSpatialReference(dnPolygon.spatialReference); } else { - polygon.spatialReference = new SpatialReference({wkid: 4326}); + polygon.spatialReference = new SpatialReference({ wkid: 4326 }); } return polygon; } @@ -571,7 +608,7 @@ export function buildJsViewClickEvent(dotNetClickEvent: any): ViewClickEvent { button: dotNetClickEvent.button ?? undefined, buttons: dotNetClickEvent.buttons ?? undefined, timestamp: dotNetClickEvent.timestamp ?? undefined - } as ViewClickEvent + } as ViewClickEvent; } export async function buildJsPopup(dotNetPopup: any, viewId: string): Promise { @@ -931,7 +968,7 @@ export function buildJsFormTemplate(dotNetFormTemplate: any): FormTemplate { } function buildJsFormTemplateElement(dotNetFormTemplateElement: any): Element { - switch (dotNetFormTemplateElement.type){ + switch (dotNetFormTemplateElement.type) { case 'group': return new GroupElement({ label: dotNetFormTemplateElement.label ?? undefined, @@ -976,7 +1013,7 @@ function buildJsFormTemplateElement(dotNetFormTemplateElement: any): Element { } function buildJsDomain(dotNetDomain: any): any { - switch (dotNetDomain?.type){ + switch (dotNetDomain?.type) { case 'coded-value': return new CodedValueDomain({ name: dotNetDomain.name ?? undefined, @@ -989,7 +1026,7 @@ function buildJsDomain(dotNetDomain: any): any { minValue: dotNetDomain.minValue ?? undefined }); } - + return undefined; } @@ -1039,7 +1076,7 @@ function buildJsFormInput(dotNetFormInput: any): any { onValue: dotNetFormInput.onValue ?? undefined }); } - + return undefined; } @@ -1079,4 +1116,62 @@ function buildJsPathsOrRings(pathsOrRings: any) { function hasValue(prop: any): boolean { return prop !== undefined && prop !== null; +} + +export function buildJsFeatureEffect(dnFeatureEffect: DotNetFeatureEffect): FeatureEffect | null { + if (dnFeatureEffect === undefined || dnFeatureEffect === null) return null; + let featureEffect = new FeatureEffect(); + + //if there is a single effect, its a string, if there are effects based on scale its an array and has scale and value. + if (dnFeatureEffect.excludedEffect != null) { + if (dnFeatureEffect.excludedEffect.length === 1) { + featureEffect.excludedEffect = buildJsEffect(dnFeatureEffect.excludedEffect[0]); + } else { + featureEffect.excludedEffect = dnFeatureEffect.excludedEffect.map(buildJsEffect); + } + + } else { + featureEffect.excludedEffect = undefined; + } + featureEffect.excludedLabelsVisible = dnFeatureEffect.excludedLabelsVisible ?? undefined; + featureEffect.filter = buildJsFeatureFilter(dnFeatureEffect.filter) ?? undefined; + + if (dnFeatureEffect.includedEffect != null) { + if (dnFeatureEffect.includedEffect.length === 1) { + featureEffect.includedEffect = buildJsEffect(dnFeatureEffect.includedEffect[0]); + } else { + featureEffect.includedEffect = dnFeatureEffect.includedEffect.map(buildJsEffect); + } + + } else { + featureEffect.includedEffect = undefined; + } + + return featureEffect; +} + +export function buildJsFeatureFilter(dnFeatureFilter: DotNetFeatureFilter): FeatureFilter | null { + if (dnFeatureFilter === undefined || dnFeatureFilter === null) return null; + + let featureFilter = new FeatureFilter(); + featureFilter.distance = dnFeatureFilter.distance ?? undefined; + featureFilter.geometry = buildJsGeometry(dnFeatureFilter.geometry); + featureFilter.objectIds = dnFeatureFilter.objectIds ?? undefined; + featureFilter.spatialRelationship = dnFeatureFilter.spatialRelationship ?? undefined; + featureFilter.timeExtent = dnFeatureFilter.timeExtent ?? undefined; + featureFilter.units = dnFeatureFilter.units ?? undefined; + featureFilter.where = dnFeatureFilter.where ?? undefined; + return featureFilter; +} + +export function buildJsEffect(dnEffect: any): any { + + if (dnEffect.scale != null) { + return { + value: dnEffect.value, + scale: dnEffect.scale + }; + } else { + return dnEffect.value; + } } \ No newline at end of file