diff --git a/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor b/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor index 1eabf57..07b7832 100644 --- a/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor +++ b/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor @@ -37,22 +37,56 @@
- \ No newline at end of file + + +@code { + RenderFragment RenderNavLink(PageLink page) + { + string linkClass = $"nav-list-link{(page.Pro ? " pro" : "")}"; + return @ + @if (page.IconClass is not null) + { + @(page.Title) + } + else + { + @(page.Title)@(page.Title) + } + ; + } +} \ No newline at end of file diff --git a/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor.cs b/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor.cs index 9611e22..82bd92e 100644 --- a/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor.cs +++ b/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor.cs @@ -6,6 +6,22 @@ namespace dymaptic.GeoBlazor.Core.Sample.Shared.Shared; public partial class NavMenu { + // Category name constants + public static class Categories + { + public const string MapsAndScenes = "Maps & Scenes"; + public const string Layers = "Layers"; + public const string Visualization = "Visualization"; + public const string Widgets = "Widgets"; + public const string Queries = "Queries"; + public const string Interaction = "Interaction"; + public const string Location = "Location"; + } + + protected static readonly string[] GroupOrder = + [Categories.MapsAndScenes, Categories.Layers, Categories.Visualization, + Categories.Widgets, Categories.Queries, Categories.Interaction, Categories.Location]; + [Inject] public required IJSRuntime JsRuntime { get; set; } [Inject] @@ -20,6 +36,31 @@ public partial class NavMenu ? Pages : Pages.Where(p => p.Title.Contains(_searchText, StringComparison.OrdinalIgnoreCase)); + protected IEnumerable UngroupedPages => FilteredPages.Where(p => p.Category is null); + + protected IEnumerable<(string GroupName, IEnumerable Pages)> GroupedFilteredPages => + GroupOrder + .Select(g => (GroupName: g, Pages: FilteredPages.Where(p => p.Category == g))) + .Where(g => g.Pages.Any()); + + protected HashSet ExpandedGroups { get; set; } = new(); + + protected void ToggleGroup(string groupName) + { + if (!ExpandedGroups.Add(groupName)) + { + ExpandedGroups.Remove(groupName); + } + } + + protected bool IsGroupExpanded(string groupName) + { + if (!string.IsNullOrWhiteSpace(_searchText)) + return true; + + return ExpandedGroups.Contains(groupName); + } + protected override async Task OnAfterRenderAsync(bool firstRender) { await base.OnAfterRenderAsync(firstRender); @@ -37,9 +78,16 @@ protected override async Task OnAfterRenderAsync(bool firstRender) if (currentPage != string.Empty) { + string? group = Pages.FirstOrDefault(p => p.Href == currentPage)?.Category; + + if (group is not null) + { + ExpandedGroups.Add(group); + } + await JsRuntime.InvokeVoidAsync("scrollToNav", currentPage); } - + StateHasChanged(); } } @@ -96,58 +144,67 @@ await InvokeAsync(async () => public virtual PageLink[] Pages => [ new("", "Home", "oi-home"), - new("navigation", "Navigation", "oi-compass"), - new("drawing", "Drawing", "oi-pencil"), - new("click-to-add", "Click to Add Point", "oi-map-marker"), - new("many-graphics", "Many Graphics", "oi-calculator"), - new("scene", "Scene & Attributes", "oi-globe"), - new("widgets", "Widgets", "oi-location"), - new("basemaps", "Basemaps", "oi-map"), - new("feature-layers", "Feature Layers", "oi-layers"), - new("map-image-layers", "Map Image Layers", "oi-image"), - new("labels", "Labels", "oi-text"), - new("popups", "Popups", "oi-chat"), - new("popup-actions", "Popup Actions", "oi-bullhorn"), - new("bookmarks", "Bookmarks", "oi-bookmark"), - new("vector-layer", "Vector Layer", "oi-arrow-right"), - new("layer-lists", "Layer Lists", "oi-list"), - new("basemap-layer-lists", "Basemap Layer Lists", "oi-spreadsheet"), - new("csv-layer", "CSV Layers", "oi-grid-four-up"), - new("kmllayers", "KML Layers", "oi-excerpt"), - new("geojson-layers", "GeoJSON Layers", null, "geojson.svg"), - new("georss-layer", "GeoRSS Layer", "oi-rss"), - new("osm-layer", "OpenStreetMaps Layer", null, "osm.webp"), - new("wcslayers", "WCS Layers", "oi-project"), - new("wfslayers", "WFS Layers", null, "wfs.svg"), - new("wmslayers", "WMS Layers", null, "wms.svg"), - new("wmtslayers", "WMTS Layers", null, "wmts.svg"), - new("imagerylayer", "Imagery Layers", "oi-image"), - new("imagery-tile-layer", "Imagery Tile Layers", null, "tile.webp"), - new("web-map", "Web Map", "oi-browser"), - new("web-scene", "Web Scene", "oi-box"), - new("events", "Events", "oi-flash"), - new("reactive-utils", "Reactive Utils", "oi-wrench"), - new("hit-tests", "Hit Tests", "oi-star"), - new("sql-query", "SQL Query", "oi-data-transfer-download"), - new("sql-filter-query", "SQL Filter", "oi-arrow-thick-bottom"), - new("server-side-queries", "Server-Side Queries", "oi-question-mark"), - new("query-related-features", "Query Related Features", "oi-people"), - new("query-top-features", "Query Top Features", "oi-arrow-thick-top"), - new("place-selector", "Place Selector", "oi-arrow-bottom"), - new("service-areas", "Service Areas", "oi-comment-square"), - new("measurement-widgets", "Measurement Widgets", null, "ruler.svg"), - new("calculate-geometries", "Calculate Geometries", "oi-clipboard"), - new("projection", "Display Projection", "oi-sun"), - new("projection-tool", "Projection Tool", "oi-cog"), - new("basemap-projections", "Basemap Projections", "oi-bullhorn"), - new("legends", "Legend", null, "legend.svg"), - new("unique-value", "Unique Renderers", "oi-eyedropper"), - new("marker-rotation", "Marker Rotation", "oi-loop-circular"), - new("graphic-tracking", "Graphic Tracking", "oi-move"), - new("geometry-methods", "Geometry Methods", "oi-task"), - new("locator-methods", "Locator Methods", "oi-task"), - new("search-multi-source", "Search Multiple Sources", "oi-magnifying-glass"), - new("reverse-geolocator", "GeoLocator", "oi-arrow-circle-bottom") + new("navigation", "Navigation", "oi-compass", Category: Categories.MapsAndScenes), + new("scene", "Scene & Attributes", "oi-globe", Category: Categories.MapsAndScenes), + new("basemaps", "Basemaps", "oi-map", Category: Categories.MapsAndScenes), + new("web-map", "Web Map", "oi-browser", Category: Categories.MapsAndScenes), + new("web-scene", "Web Scene", "oi-box", Category: Categories.MapsAndScenes), + + new("feature-layers", "Feature Layers", "oi-layers", Category: Categories.Layers), + new("map-image-layers", "Map Image Layers", "oi-image", Category: Categories.Layers), + new("vector-layer", "Vector Layer", "oi-arrow-right", Category: Categories.Layers), + new("csv-layer", "CSV Layers", "oi-grid-four-up", Category: Categories.Layers), + new("kmllayers", "KML Layers", "oi-excerpt", Category: Categories.Layers), + new("geojson-layers", "GeoJSON Layers", null, "geojson.svg", Category: Categories.Layers), + new("georss-layer", "GeoRSS Layer", "oi-rss", Category: Categories.Layers), + new("osm-layer", "OpenStreetMaps Layer", null, "osm.webp", Category: Categories.Layers), + new("wcslayers", "WCS Layers", "oi-project", Category: Categories.Layers), + new("wfslayers", "WFS Layers", null, "wfs.svg", Category: Categories.Layers), + new("wmslayers", "WMS Layers", null, "wms.svg", Category: Categories.Layers), + new("wmtslayers", "WMTS Layers", null, "wmts.svg", Category: Categories.Layers), + new("imagerylayer", "Imagery Layers", "oi-image", Category: Categories.Layers), + new("imagery-tile-layer", "Imagery Tile Layers", null, "tile.webp", Category: Categories.Layers), + + new("labels", "Labels", "oi-text", Category: Categories.Visualization), + new("unique-value", "Unique Renderers", "oi-eyedropper", Category: Categories.Visualization), + new("marker-rotation", "Marker Rotation", "oi-loop-circular", Category: Categories.Visualization), + + new("widgets", "Widgets", "oi-location", Category: Categories.Widgets), + new("popups", "Popups", "oi-chat", Category: Categories.Widgets), + new("popup-actions", "Popup Actions", "oi-bullhorn", Category: Categories.Widgets), + new("bookmarks", "Bookmarks", "oi-bookmark", Category: Categories.Widgets), + new("layer-lists", "Layer Lists", "oi-list", Category: Categories.Widgets), + new("legends", "Legend", null, "legend.svg", Category: Categories.Widgets), + new("basemap-layer-lists", "Basemap Layer Lists", "oi-spreadsheet", Category: Categories.Widgets), + new("measurement-widgets", "Measurement Widgets", null, "ruler.svg", Category: Categories.Widgets), + new("search-multi-source", "Search Multiple Sources", "oi-magnifying-glass", Category: Categories.Widgets), + + new("sql-query", "SQL Query", "oi-data-transfer-download", Category: Categories.Queries), + new("sql-filter-query", "SQL Filter", "oi-arrow-thick-bottom", Category: Categories.Queries), + new("server-side-queries", "Server-Side Queries", "oi-question-mark", Category: Categories.Queries), + new("query-related-features", "Query Related Features", "oi-people", Category: Categories.Queries), + new("query-top-features", "Query Top Features", "oi-arrow-thick-top", Category: Categories.Queries), + + new("drawing", "Drawing", "oi-pencil", Category: Categories.Interaction), + new("click-to-add", "Click to Add Point", "oi-map-marker", Category: Categories.Interaction), + new("many-graphics", "Many Graphics", "oi-calculator", Category: Categories.Interaction), + new("events", "Events", "oi-flash", Category: Categories.Interaction), + new("reactive-utils", "Reactive Utils", "oi-wrench", Category: Categories.Interaction), + new("hit-tests", "Hit Tests", "oi-star", Category: Categories.Interaction), + new("graphic-tracking", "Graphic Tracking", "oi-move", Category: Categories.Interaction), + + new("place-selector", "Place Selector", "oi-arrow-bottom", Category: Categories.Location), + new("service-areas", "Service Areas", "oi-comment-square", Category: Categories.Location), + new("calculate-geometries", "Calculate Geometries", "oi-clipboard", Category: Categories.Location), + new("projection", "Display Projection", "oi-sun", Category: Categories.Location), + new("projection-tool", "Projection Tool", "oi-cog", Category: Categories.Location), + new("basemap-projections", "Basemap Projections", "oi-bullhorn", Category: Categories.Location), + new("geometry-methods", "Geometry Methods", "oi-task", Category: Categories.Location), + new("locator-methods", "Locator Methods", "oi-task", Category: Categories.Location), + new("reverse-geolocator", "GeoLocator", "oi-arrow-circle-bottom", Category: Categories.Location), ]; - public record PageLink(string Href, string Title, string? IconClass = null, string? ImageFile = null, bool Pro = false); + + public record PageLink( + string Href, string Title, string? IconClass = null, + string? ImageFile = null, bool Pro = false, string? Category = null); } \ No newline at end of file diff --git a/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor.css b/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor.css index 721b2be..bcc1ef4 100644 --- a/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor.css +++ b/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/Shared/NavMenu.razor.css @@ -11,12 +11,12 @@ #lower-nav-container { position: absolute; - top: 9rem; + top: 10rem; left: 0; background-color: var(--background-grey-2); width: 20rem; overflow-y: auto; - height: calc(100vh - 9rem); + height: calc(100vh - 10rem); margin: 0; padding: 1rem 0; } @@ -42,7 +42,7 @@ width: 2rem; font-size: 1.1rem; vertical-align: text-top; - top: 2px; + top: -2px; } .oi img { diff --git a/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/wwwroot/css/site.css b/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/wwwroot/css/site.css index fa39093..52ad2aa 100644 --- a/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/wwwroot/css/site.css +++ b/samples/core/dymaptic.GeoBlazor.Core.Sample.Shared/wwwroot/css/site.css @@ -81,10 +81,14 @@ article .oi { } .nav-list-link .oi { - top: 2px; + top: -2px; height: 18px; } +.nav-list-link .oi img { + vertical-align: top; +} + .container { background-color: unset; } @@ -1019,12 +1023,6 @@ a:not([href]):not([class]), a:not([href]):not([class]):hover { background-color: var(--background-grey-2); } -#nav-container { - overflow-y: auto; - height: calc(100vh - 6rem); - padding: 0.5rem 0; -} - .nav-list { flex: 1 1 auto; display: flex; @@ -1077,7 +1075,7 @@ a:not([href]):not([class]), a:not([href]):not([class]):hover { color: var(--geoblazor-accent-emphasis); } -.nav-list .nav-list-item:has(.nav-list-link.active) { +.nav-list .nav-list-item:has(> .nav-list-link.active) { background-color: var(--background-grey-3); } @@ -1093,26 +1091,87 @@ a:not([href]):not([class]), a:not([href]):not([class]):hover { background-image: unset; } +/* Pinned top-level items (Home) */ +.nav-list .nav-list-item.nav-pinned { + padding: 0.5rem 0.75rem; + margin-bottom: 0.25rem; +} + +/* Group headers */ +.nav-list .nav-list-item.nav-group { + padding: 0; + margin-bottom: 0.25rem; + margin-top: 0.25rem; + background-color: transparent; + border-radius: 0; + border-top: 1px solid var(--background-grey-3); +} + +.nav-list .nav-pinned + .nav-group { + margin-top: 0.5rem; +} + +.nav-list .nav-list-item.nav-group:hover { + background-color: transparent; +} + .nav-list .nav-list-item .nav-list-expander { - color: var(--geoblazor-secondary); + color: var(--text-emphasis); + cursor: pointer; + display: flex; + align-items: center; + gap: 0.75rem; + padding: 0.75rem; + user-select: none; + border-radius: var(--box-radius); + background: none; + border: none; + width: 100%; + font: inherit; + text-align: left; } .nav-list .nav-list-item .nav-list-expander:hover { color: var(--geoblazor-secondary-hover); + background-color: var(--background-grey-1); background-image: unset; } -.nav-list .nav-list-item > .nav-list .nav-list-item .nav-list-link { - color: var(--text-emphasis); +.nav-group-title { + font-family: 'Roboto', sans-serif; font-size: 1rem; + font-weight: 300; } .nav-list .nav-list-item .nav-list-expander svg { + width: 12px; + height: 12px; transform: rotate(0); + transition: transform 0.2s ease; + fill: var(--geoblazor-secondary); + flex-shrink: 0; } .nav-list .nav-list-item.active .nav-list-expander svg { transform: rotate(90deg); + fill: var(--geoblazor-accent); +} + +/* Nested sublist */ +.nav-list .nav-sublist { + padding-left: 1rem; + padding-right: 0; + margin-top: 0; + padding-bottom: 0.25rem; +} + +.nav-list .nav-sublist .nav-list-item { + padding: 0.4rem 0.75rem; + margin-bottom: 0.125rem; +} + +.nav-list .nav-list-item > .nav-list .nav-list-item .nav-list-link { + font-size: 0.9rem; } .side-bar a { @@ -1255,10 +1314,6 @@ a:not([href]):not([class]), a:not([href]):not([class]):hover { height: 8rem; } - #nav-container { - height: calc(100vh - 8rem); - } - .site-nav { padding: 0.5rem; } @@ -1327,7 +1382,11 @@ a:not([href]):not([class]), a:not([href]):not([class]):hover { background-color: var(--background-grey-3); } - .nav-list .nav-list-item:has(.nav-list-link.active) { + .nav-list .nav-list-item .nav-list-expander:hover { + background-color: var(--background-grey-3); + } + + .nav-list .nav-list-item:has(> .nav-list-link.active) { background-color: var(--background-grey-4); } diff --git a/samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Shared/ProNavMenu.cs b/samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Shared/ProNavMenu.cs index fdad07c..d3de2f7 100644 --- a/samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Shared/ProNavMenu.cs +++ b/samples/pro/dymaptic.GeoBlazor.Pro.Sample.Shared/Shared/ProNavMenu.cs @@ -17,34 +17,34 @@ public class ProNavMenu : NavMenu href = $"pro-{href}"; } - return new PageLink(href, p.Title, p.IconClass, p.ImageFile); + return p with { Href = href }; }), - new("imagery-group-blend", "PRO: Imagery Blend", null, "blend.svg", true), - new("sketch-query", "PRO: Sketch Query", "oi-location", null, true), - new("edit-feature-data", "PRO: Edit Data", "oi-map-marker", null, true), - new("popup-edit", "PRO: Popup Edit Data", "oi-pencil", null, true), - new("update-feature-attributes", "PRO: Update Attributes", "oi-brush", null, true), - new("apply-edits", "PRO: Apply Edits", "oi-check", null, true), - new("spatial-relationships", "PRO Relationships", "oi-link-intact", null, true), - new("demographic-data", "PRO: Demographics", "oi-people", null, true), - new("length-and-area", "PRO: Length & Area", "oi-graph", null, true), - new("swipe", "PRO: Swipe Widget", "oi-arrow-thick-left", null, true), - new("time-slider", "PRO: Time Slider", "oi-vertical-align-center", null, true), - new("search-custom-source", "PRO: Custom Search", "oi-magnifying-glass", null, true), - new("clustering", "PRO: Clustering", "oi-fullscreen-exit", null, true), - new("clustering-popups", "PRO: Clustering Popups", "oi-fullscreen-enter", null, true), - new("cluster-pie-charts", "PRO: Pie Chart Clusters", "oi-pie-chart", null, true), - new("binning", "PRO: Binning", "oi-grid-three-up", null, true), - new("routes", "PRO: Routes", "oi-transfer", null, true), - new("graphics-legend", "PRO: Graphics Legend", "oi-list-rich", null, true), - new("group-layers", "PRO: Group Layers", null, "groupLayer.svg", true), - new("ogc-feature-layers", "PRO: OGC Feature Layers", "oi-layers", null, true), - new("wfsutils", "PRO: WFS Utils", "oi-wrench", null, true), - new("print-widget", "PRO: Print Widgets", "oi-print", null, true), - new("custom-popup-content", "PRO: Custom Popup Content", null, "customPopup.svg", true), - new("geojson-styles", "PRO: GeoJSON Styles", "oi-brush", null, true), - new("web-style-symbols", "PRO: Web Style Symbols", "oi-brush", null, true), + new("imagery-group-blend", "PRO: Imagery Blend", null, "blend.svg", true, Categories.Layers), + new("sketch-query", "PRO: Sketch Query", "oi-location", null, true, Categories.Queries), + new("edit-feature-data", "PRO: Edit Data", "oi-map-marker", null, true, Categories.Interaction), + new("popup-edit", "PRO: Popup Edit Data", "oi-pencil", null, true, Categories.Widgets), + new("update-feature-attributes", "PRO: Update Attributes", "oi-brush", null, true, Categories.Interaction), + new("apply-edits", "PRO: Apply Edits", "oi-check", null, true, Categories.Interaction), + new("spatial-relationships", "PRO Relationships", "oi-link-intact", null, true, Categories.Queries), + new("demographic-data", "PRO: Demographics", "oi-people", null, true, Categories.Location), + new("length-and-area", "PRO: Length & Area", "oi-graph", null, true, Categories.Location), + new("swipe", "PRO: Swipe Widget", "oi-arrow-thick-left", null, true, Categories.Widgets), + new("time-slider", "PRO: Time Slider", "oi-vertical-align-center", null, true, Categories.Widgets), + new("search-custom-source", "PRO: Custom Search", "oi-magnifying-glass", null, true, Categories.Widgets), + new("clustering", "PRO: Clustering", "oi-fullscreen-exit", null, true, Categories.Visualization), + new("clustering-popups", "PRO: Clustering Popups", "oi-fullscreen-enter", null, true, Categories.Visualization), + new("cluster-pie-charts", "PRO: Pie Chart Clusters", "oi-pie-chart", null, true, Categories.Visualization), + new("binning", "PRO: Binning", "oi-grid-three-up", null, true, Categories.Visualization), + new("routes", "PRO: Routes", "oi-transfer", null, true, Categories.Location), + new("graphics-legend", "PRO: Graphics Legend", "oi-list-rich", null, true, Categories.Visualization), + new("group-layers", "PRO: Group Layers", null, "groupLayer.svg", true, Categories.Layers), + new("ogc-feature-layers", "PRO: OGC Feature Layers", "oi-layers", null, true, Categories.Layers), + new("wfsutils", "PRO: WFS Utils", "oi-wrench", null, true, Categories.Layers), + new("print-widget", "PRO: Print Widgets", "oi-print", null, true, Categories.Widgets), + new("custom-popup-content", "PRO: Custom Popup Content", null, "customPopup.svg", true, Categories.Widgets), + new("geojson-styles", "PRO: GeoJSON Styles", "oi-brush", null, true, Categories.Visualization), + new("web-style-symbols", "PRO: Web Style Symbols", "oi-brush", null, true, Categories.Visualization), // not ready, bug in Column.Width JsonConverter - // new("highlight-features-by-geometry", "PRO: Highlight by Geometry", "oi-target", null, true) + // new("highlight-features-by-geometry", "PRO: Highlight by Geometry", "oi-target", null, true, Categories.Interaction) ]; } \ No newline at end of file