Beispiel #1
0
        public void AddMatch(string shellSegment, string userSegment, object node)
        {
            if (node == null)
            {
                throw new ArgumentNullException(nameof(node));
            }

            switch (node)
            {
            case ShellUriHandler.GlobalRouteItem globalRoute:
                if (globalRoute.IsFinished)
                {
                    _globalRouteMatches.Add(globalRoute.SourceRoute);
                }
                break;

            case Shell shell:
                if (shell == Shell)
                {
                    return;
                }

                Shell = shell;
                break;

            case ShellItem item:
                if (Item == item)
                {
                    return;
                }

                Item = item;
                break;

            case ShellSection section:
                if (Section == section)
                {
                    return;
                }

                Section = section;

                if (Item == null)
                {
                    Item = Section.Parent as ShellItem;
                    _fullSegments.Add(Item.Route);
                }

                break;

            case ShellContent content:
                if (Content == content)
                {
                    return;
                }

                Content = content;
                if (Section == null)
                {
                    Section = Content.Parent as ShellSection;
                    _fullSegments.Add(Section.Route);
                }

                if (Item == null)
                {
                    Item = Section.Parent as ShellItem;
                    _fullSegments.Insert(0, Item.Route);
                }

                break;
            }

            if (Item?.Parent is Shell s)
            {
                Shell = s;
            }

            // if shellSegment == userSegment it means the implicit route is part of the request
            if (Routing.IsUserDefined(shellSegment) || shellSegment == userSegment || shellSegment == NextSegment)
            {
                _matchedSegments.Add(shellSegment);
            }

            _fullSegments.Add(shellSegment);
        }
Beispiel #2
0
        static void SearchPath(
            object node,
            RouteRequestBuilder currentMatchedPath,
            List <string> segments,
            List <RouteRequestBuilder> possibleRoutePaths,
            int depthToStart,
            int myDepth = -1,
            NodeLocation currentLocation = null,
            bool ignoreGlobalRoutes      = true)
        {
            if (node is GlobalRouteItem && ignoreGlobalRoutes)
            {
                return;
            }

            ++myDepth;
            currentLocation = currentLocation ?? new NodeLocation();
            currentLocation.SetNode(node);

            IEnumerable items = null;

            if (depthToStart > myDepth)
            {
                items = GetItems(node);
                if (items == null)
                {
                    return;
                }

                foreach (var nextNode in items)
                {
                    SearchPath(nextNode, null, segments, possibleRoutePaths, depthToStart, myDepth, currentLocation, ignoreGlobalRoutes);
                }
                return;
            }

            string shellSegment = GetRoute(node);
            string userSegment  = null;

            if (currentMatchedPath == null)
            {
                userSegment = segments[0];
            }
            else
            {
                userSegment = currentMatchedPath.NextSegment;
            }

            if (userSegment == null)
            {
                return;
            }

            RouteRequestBuilder builder = null;

            if (shellSegment == userSegment || Routing.IsImplicit(shellSegment))
            {
                if (currentMatchedPath == null)
                {
                    builder = new RouteRequestBuilder(shellSegment, userSegment, node, segments);
                }
                else
                {
                    builder = new RouteRequestBuilder(currentMatchedPath);
                    builder.AddMatch(shellSegment, userSegment, node);
                }

                if (!Routing.IsImplicit(shellSegment) || shellSegment == userSegment)
                {
                    possibleRoutePaths.Add(builder);
                }
            }

            items = GetItems(node);
            if (items == null)
            {
                return;
            }

            foreach (var nextNode in items)
            {
                SearchPath(nextNode, builder, segments, possibleRoutePaths, depthToStart, myDepth, currentLocation, ignoreGlobalRoutes);
            }
        }
Beispiel #3
0
        internal static List <RouteRequestBuilder> GenerateRoutePaths(Shell shell, Uri request, Uri originalRequest, bool enableRelativeShellRoutes)
        {
            var routeKeys = Routing.GetRouteKeys();

            request         = FormatUri(request, shell);
            originalRequest = FormatUri(originalRequest, shell);

            List <RouteRequestBuilder> possibleRoutePaths = new List <RouteRequestBuilder>();

            if (!request.IsAbsoluteUri)
            {
                request = ConvertToStandardFormat(shell, request);
            }

            string localPath = request.LocalPath;

            bool relativeMatch = false;

            if (!originalRequest.IsAbsoluteUri &&
                !originalRequest.OriginalString.StartsWith("//", StringComparison.Ordinal))
            {
                relativeMatch = true;
            }

            var segments = new List <string>(RetrievePaths(localPath));

            var depthStart = 0;

            if (segments[0] == shell?.Route)
            {
                segments.RemoveAt(0);
                depthStart = 1;
            }
            else
            {
                depthStart = 0;
            }

            if (relativeMatch && shell?.CurrentItem != null)
            {
                var result = ProcessRelativeRoute(shell, routeKeys, segments, enableRelativeShellRoutes, originalRequest);
                if (result.Count > 0)
                {
                    return(result);
                }
            }

            possibleRoutePaths.Clear();
            SearchPath(shell, null, segments, possibleRoutePaths, depthStart);

            var bestMatches = GetBestMatches(possibleRoutePaths);

            if (bestMatches.Count > 0)
            {
                return(bestMatches);
            }

            bestMatches.Clear();
            ExpandOutGlobalRoutes(possibleRoutePaths, routeKeys);

            foreach (var possibleRoutePath in possibleRoutePaths)
            {
                if (possibleRoutePath.IsFullMatch)
                {
                    continue;
                }

                var url             = possibleRoutePath.PathFull;
                var currentLocation = possibleRoutePath.GetNodeLocation();

                if (currentLocation.Content == null)
                {
                    continue;
                }

                var globalRouteMatches =
                    SearchForGlobalRoutes(
                        possibleRoutePath.RemainingSegments,
                        new ShellNavigationState(url, false).FullLocation,
                        currentLocation,
                        routeKeys);

                if (globalRouteMatches.Count != 1)
                {
                    continue;
                }

                var globalRouteMatch = globalRouteMatches[0];

                while (possibleRoutePath.NextSegment != null)
                {
                    var matchIndex = globalRouteMatch.SegmentsMatched.IndexOf(possibleRoutePath.NextSegment);
                    if (matchIndex < 0)
                    {
                        break;
                    }

                    possibleRoutePath.AddGlobalRoute(
                        globalRouteMatch.GlobalRouteMatches[matchIndex],
                        globalRouteMatch.SegmentsMatched[matchIndex]);
                }
            }

            possibleRoutePaths = GetBestMatches(possibleRoutePaths);

            if (possibleRoutePaths.Count == 0)
            {
                foreach (var routeKey in routeKeys)
                {
                    if (routeKey == originalRequest.OriginalString)
                    {
                        var builder = new RouteRequestBuilder(routeKey, routeKey, null, new List <string>(1)
                        {
                            routeKey
                        });
                        return(new List <RouteRequestBuilder> {
                            builder
                        });
                    }
                }

                if (!relativeMatch)
                {
                    foreach (var route in routeKeys)
                    {
                        var uri = ConvertToStandardFormat(shell, CreateUri(route));
                        if (uri.Equals(request))
                        {
#if NETSTANDARD2_0
                            var replaced = originalRequest.OriginalString.Replace("//", "");
#else
                            var replaced = originalRequest.OriginalString.Replace("//", "", StringComparison.Ordinal);
#endif
                            throw new Exception($"Global routes currently cannot be the only page on the stack, so absolute routing to global routes is not supported. For now, just navigate to: {replaced}");
                        }
                    }
                }
            }
            return(possibleRoutePaths);
        }
Beispiel #4
0
        internal static Uri FormatUri(Uri path, Shell shell)
        {
            if (path.OriginalString.StartsWith("..", StringComparison.Ordinal) && shell?.CurrentState != null)
            {
                var    pathAndQueryString = path.OriginalString.Split(new[] { '?' }, 2);
                string pathPart           = pathAndQueryString[0];
                string queryString        = (pathAndQueryString.Length > 1) ? $"?{pathAndQueryString[1]}" : String.Empty;

                var pages = ShellNavigationManager.BuildFlattenedNavigationStack(shell);

                List <string> restOfPath    = new List <string>();
                bool          dotsAllParsed = false;
                foreach (var p in pathPart.Split(_pathSeparators))
                {
                    if (p != ".." || dotsAllParsed)
                    {
                        dotsAllParsed = true;
                        restOfPath.Add(p);
                        continue;
                    }

                    var lastPage = pages[pages.Count - 1];
                    if (lastPage == null)
                    {
                        break;
                    }

                    pages.Remove(lastPage);

                    List <string> buildUpPages = new List <string>();

                    foreach (var page in pages)
                    {
                        if (page == null)
                        {
                            continue;
                        }

                        var route = Routing.GetRoute(page);
                        buildUpPages.AddRange(CollapsePath(route, buildUpPages, false));
                    }

                    restOfPath = buildUpPages;
                }

                string[] shellRoutes = new[]
                {
                    shell.CurrentItem.Route,
                    shell.CurrentItem.CurrentItem.Route,
                    shell.CurrentItem.CurrentItem.CurrentItem.Route,
                };

                restOfPath = CollapsePath(restOfPath.ToArray(), shellRoutes, true);
                restOfPath.Insert(0, shell.CurrentItem.CurrentItem.CurrentItem.Route);
                restOfPath.Insert(0, shell.CurrentItem.CurrentItem.Route);
                restOfPath.Insert(0, shell.CurrentItem.Route);

                var result      = $"{String.Join(_pathSeparator, restOfPath)}{queryString}";
                var returnValue = ConvertToStandardFormat("scheme", "host", null, new Uri(result, UriKind.Relative));
                return(new Uri(FormatUri(returnValue.PathAndQuery), UriKind.Relative));
            }

            if (path.IsAbsoluteUri)
            {
                return(new Uri(FormatUri(path.OriginalString), UriKind.Absolute));
            }

            return(new Uri(FormatUri(path.OriginalString), UriKind.Relative));
        }
Beispiel #5
0
        public async Task GoToAsync(ShellNavigationParameters shellNavigationParameters)
        {
            if (shellNavigationParameters.PagePushing != null)
            {
                Routing.RegisterImplicitPageRoute(shellNavigationParameters.PagePushing);
            }

            var  state   = shellNavigationParameters.TargetState ?? new ShellNavigationState(Routing.GetRoute(shellNavigationParameters.PagePushing), false);
            bool?animate = shellNavigationParameters.Animated;
            bool enableRelativeShellRoutes        = shellNavigationParameters.EnableRelativeShellRoutes;
            ShellNavigatingEventArgs deferredArgs = shellNavigationParameters.DeferredArgs;

            var  navigationRequest = ShellUriHandler.GetNavigationRequest(_shell, state.FullLocation, enableRelativeShellRoutes, shellNavigationParameters: shellNavigationParameters);
            bool isRelativePopping = ShellUriHandler.IsTargetRelativePop(shellNavigationParameters);

            ShellNavigationSource source = CalculateNavigationSource(_shell, _shell.CurrentState, navigationRequest);

            // If the deferredArgs are non null that means we are processing a delayed navigation
            // so the user has indicated they want to go forward with navigation
            // This scenario only comes up from UI iniated navigation (i.e. switching tabs)
            if (deferredArgs == null)
            {
                var navigatingArgs = ProposeNavigation(source, state, _shell.CurrentState != null, animate ?? true);

                if (navigatingArgs != null)
                {
                    bool accept = !navigatingArgs.NavigationDelayedOrCancelled;
                    if (navigatingArgs.DeferredTask != null)
                    {
                        accept = await navigatingArgs.DeferredTask;
                    }

                    if (!accept)
                    {
                        return;
                    }
                }
            }

            Routing.RegisterImplicitPageRoutes(_shell);

            _accumulateNavigatedEvents = true;

            var uri         = navigationRequest.Request.FullUri;
            var queryString = navigationRequest.Query;
            var queryData   = ParseQueryString(queryString);

            ApplyQueryAttributes(_shell, queryData, false, false);

            var shellItem           = navigationRequest.Request.Item;
            var shellSection        = navigationRequest.Request.Section;
            var currentShellSection = _shell.CurrentItem?.CurrentItem;
            var nextActiveSection   = shellSection ?? shellItem?.CurrentItem;

            ShellContent shellContent       = navigationRequest.Request.Content;
            bool         modalStackPreBuilt = false;

            // If we're replacing the whole stack and there are global routes then build the navigation stack before setting the shell section visible
            if (navigationRequest.Request.GlobalRoutes.Count > 0 &&
                nextActiveSection != null &&
                navigationRequest.StackRequest == NavigationRequest.WhatToDoWithTheStack.ReplaceIt)
            {
                modalStackPreBuilt = true;

                bool?isAnimated = (nextActiveSection != currentShellSection) ? false : animate;
                await nextActiveSection.GoToAsync(navigationRequest, queryData, isAnimated, isRelativePopping);
            }

            if (shellItem != null)
            {
                ApplyQueryAttributes(shellItem, queryData, navigationRequest.Request.Section == null, false);
                bool navigatedToNewShellElement = false;

                if (shellSection != null && shellContent != null)
                {
                    ApplyQueryAttributes(shellContent, queryData, navigationRequest.Request.GlobalRoutes.Count == 0, isRelativePopping);
                    if (shellSection.CurrentItem != shellContent)
                    {
                        shellSection.SetValueFromRenderer(ShellSection.CurrentItemProperty, shellContent);
                        navigatedToNewShellElement = true;
                    }
                }

                if (shellSection != null)
                {
                    ApplyQueryAttributes(shellSection, queryData, navigationRequest.Request.Content == null, false);
                    if (shellItem.CurrentItem != shellSection)
                    {
                        shellItem.SetValueFromRenderer(ShellItem.CurrentItemProperty, shellSection);
                        navigatedToNewShellElement = true;
                    }
                }

                if (_shell.CurrentItem != shellItem)
                {
                    _shell.SetValueFromRenderer(Shell.CurrentItemProperty, shellItem);
                    navigatedToNewShellElement = true;
                }

                if (!modalStackPreBuilt && currentShellSection?.Navigation.ModalStack.Count > 0)
                {
                    // - navigating to new shell element so just pop everything
                    // - or route contains no global route requests
                    if (navigatedToNewShellElement || navigationRequest.Request.GlobalRoutes.Count == 0)
                    {
                        // remove all non visible pages first so the transition just smoothly goes from
                        // currently visible modal to base page
                        if (navigationRequest.Request.GlobalRoutes.Count == 0)
                        {
                            for (int i = currentShellSection.Stack.Count - 1; i >= 1; i--)
                            {
                                currentShellSection.Navigation.RemovePage(currentShellSection.Stack[i]);
                            }
                        }

                        await currentShellSection.PopModalStackToPage(null, animate);
                    }
                }

                if (navigationRequest.Request.GlobalRoutes.Count > 0 && navigationRequest.StackRequest != NavigationRequest.WhatToDoWithTheStack.ReplaceIt)
                {
                    // TODO get rid of this hack and fix so if there's a stack the current page doesn't display
                    await Device.InvokeOnMainThreadAsync(() =>
                    {
                        return(_shell.CurrentItem.CurrentItem.GoToAsync(navigationRequest, queryData, animate, isRelativePopping));
                    });
                }
                else if (navigationRequest.Request.GlobalRoutes.Count == 0 &&
                         navigationRequest.StackRequest == NavigationRequest.WhatToDoWithTheStack.ReplaceIt &&
                         currentShellSection?.Navigation?.NavigationStack?.Count > 1)
                {
                    // TODO get rid of this hack and fix so if there's a stack the current page doesn't display
                    await Device.InvokeOnMainThreadAsync(() =>
                    {
                        return(_shell.CurrentItem.CurrentItem.GoToAsync(navigationRequest, queryData, animate, isRelativePopping));
                    });
                }
            }
            else
            {
                await _shell.CurrentItem.CurrentItem.GoToAsync(navigationRequest, queryData, animate, isRelativePopping);
            }

            _accumulateNavigatedEvents = false;

            // this can be null in the event that no navigation actually took place!
            if (_accumulatedEvent != null)
            {
                HandleNavigated(_accumulatedEvent);
            }
        }
Beispiel #6
0
        public static void ApplyQueryAttributes(Element element, IDictionary <string, string> query, bool isLastItem, bool isPopping)
        {
            string prefix = "";

            if (!isLastItem)
            {
                var route = Routing.GetRoute(element);
                if (string.IsNullOrEmpty(route) || Routing.IsImplicit(route))
                {
                    return;
                }
                prefix = route + ".";
            }

            //if the lastItem is implicitly wrapped, get the actual ShellContent
            if (isLastItem)
            {
                if (element is IShellItemController shellitem && shellitem.GetItems().FirstOrDefault() is ShellSection section)
                {
                    element = section;
                }
                if (element is IShellSectionController shellsection && shellsection.GetItems().FirstOrDefault() is ShellContent content)
                {
                    element = content;
                }
                if (element is ShellContent shellcontent && shellcontent.Content is Element e)
                {
                    element = e;
                }
            }

            if (!(element is BaseShellItem baseShellItem))
            {
                baseShellItem = element?.Parent as BaseShellItem;
            }

            //filter the query to only apply the keys with matching prefix
            var filteredQuery = new Dictionary <string, string>(query.Count);

            foreach (var q in query)
            {
                if (!q.Key.StartsWith(prefix, StringComparison.Ordinal))
                {
                    continue;
                }
                var key = q.Key.Substring(prefix.Length);
                if (key.Contains("."))
                {
                    continue;
                }
                filteredQuery.Add(key, q.Value);
            }


            if (baseShellItem is ShellContent)
            {
                baseShellItem.ApplyQueryAttributes(MergeData(element, filteredQuery, isPopping));
            }
            else if (isLastItem)
            {
                element.SetValue(ShellContent.QueryAttributesProperty, MergeData(element, query, isPopping));
            }

            IDictionary <string, string> MergeData(Element shellElement, IDictionary <string, string> data, bool isPopping)
            {
                if (!isPopping)
                {
                    return(data);
                }

                var returnValue = new Dictionary <string, string>(data);

                var existing = (IDictionary <string, string>)shellElement.GetValue(ShellContent.QueryAttributesProperty);

                if (existing == null)
                {
                    return(data);
                }

                foreach (var datum in existing)
                {
                    if (!returnValue.ContainsKey(datum.Key))
                    {
                        returnValue[datum.Key] = datum.Value;
                    }
                }

                return(returnValue);
            }
        }