Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 fix(PageStack): GoBackToPageAndReplace has subtle issues #2316

Merged
merged 2 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/Masa.Blazor.Docs/Pages/PageStackPage3.razor
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
</a>
<br />
<a class="text-decoration-underline"
@onclick="@(() => NavController.GoBackToPage("/blazor/examples/page-stack/page2/xyz", "/blazor/examples/page-stack/page2/abc"))">
@onclick="@(() => NavController.GoBackToPageAndReplace("/blazor/examples/page-stack/page2/xyz", "/blazor/examples/page-stack/page2/abc"))">
Go back to page 2 and replace with /abc
</a>

Expand Down
65 changes: 49 additions & 16 deletions src/Masa.Blazor/Presets/PageStack/PPageStack.razor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,19 @@ public partial class PPageStack : PatternPathComponentBase
/// and clear the stack in the next popstate event.
/// </summary>
private string? _uriForPushAndClearStack;

/// <summary>
/// The flag to indicate whether to replace the top page
/// and clear the previous pages in the stack in the next popstate event.
/// </summary>
private (string relativeUri, object? state)? _uriForReplaceAndClearStack;

/// <summary>
/// The flag to indicate whether to go back to a specific page
/// and then replace it with a new page.
/// </summary>
private (string relativeUri, object? state, int delta)? _uriForGoBackToAndReplace;

private string? _lastVisitedTabPath;
private PageType _targetPageType;
private long _lastOnPreviousClickTimestamp;
Expand Down Expand Up @@ -133,7 +139,7 @@ public void Popstate(string absolutePath)

_ = Task.Run(async () =>
{
await Task.Delay(300); // wait for the transition to complete
await Task.Delay(DelayForPageClosingAnimation); // wait for the transition to complete
Pages.RemoveRange(0, Pages.Count - 1);
await InvokeAsync(StateHasChanged);
});
Expand All @@ -144,13 +150,23 @@ public void Popstate(string absolutePath)
if (_uriForReplaceAndClearStack.HasValue)
{
var (relativeUri, state) = _uriForReplaceAndClearStack.Value;

Pages.RemoveRange(0, Pages.Count - 1);
InternalReplaceHandler(relativeUri, state);
_uriForReplaceAndClearStack = null;
return;
}

if (_uriForGoBackToAndReplace.HasValue)
{
var (replaceUri, state, delta) = _uriForGoBackToAndReplace.Value;
NavigationManager.Replace(replaceUri);
replaceUri = GetAbsolutePath(replaceUri);
CloseTopPages(delta, state, replaceUri);
_uriForGoBackToAndReplace = null;
return;
}

var tabbedPattern = _cachedTabbedPatterns.FirstOrDefault(r => r.IsMatch(absolutePath));

if (tabbedPattern is not null)
Expand Down Expand Up @@ -240,9 +256,9 @@ private void InternalStackStackNavManagerOnStackPush(object? sender, PageStackPu
NavigationManager.NavigateTo(e.RelativeUri);
}

private async void InternalStackStackNavManagerOnStackClear(object? sender, PageStackClearEventArgs e)
private void InternalStackStackNavManagerOnStackClear(object? sender, PageStackClearEventArgs e)
{
await Js.InvokeVoidAsync(JsInteropConstants.HistoryGo, -Pages.Count);
_ = Js.InvokeVoidAsync(JsInteropConstants.HistoryGo, -Pages.Count);

var backToLastVisitTab = string.IsNullOrWhiteSpace(e.RelativeUri) ||
_lastVisitedTabPath == GetAbsolutePath(e.RelativeUri);
Expand All @@ -268,17 +284,18 @@ private async void InternalPageStackNavManagerOnStackGoBackTo(object? sender, Pa
return;
}

_popstateByUserAction = true;

await Js.InvokeVoidAsync(JsInteropConstants.HistoryGo, -delta);

CloseTopPages(delta, e.State);

if (!string.IsNullOrWhiteSpace(e.ReplaceUri))
if (string.IsNullOrWhiteSpace(e.ReplaceUri))
{
await Task.Delay(DelayForPageClosingAnimation);
InternalReplaceHandler(e.ReplaceUri, e.State);
_popstateByUserAction = true;

CloseTopPages(delta, e.State);
}
else
{
_uriForGoBackToAndReplace = (e.ReplaceUri, e.State, delta);
}

_ = Js.InvokeVoidAsync(JsInteropConstants.HistoryGo, -delta);
}

private string GetAbsolutePath(string relativeUri) => NavigationManager.ToAbsoluteUri(relativeUri).AbsolutePath;
Expand Down Expand Up @@ -308,29 +325,45 @@ private void HandleOnPrevious()

private void CloseTopPageOfStack(object? state = null) => CloseTopPages(1, state);

private void CloseTopPages(int count, object? state = null)
private void CloseTopPages(int count, object? state = null, string? absolutePath = null)
{
if (!Pages.TryPeek(out var current)) return;

// Set the stacked state to false, which directly determines whether the dialog is displayed
// and will perform a transition animation
current.Stacked = false;

// When the count of pages to be deleted is greater than 1,
// delete the pages between the top of the stack and the target page
// For example, if the stack has 5 pages: [1,2,3,4,5], delete 3(count) pages,
// then delete the page 3 and page 4
if (count > 1)
{
Pages.RemoveRange(Pages.Count - count, count - 1);
}

// The stack is now [1,2,5], update the state of the 2nd page
if (Pages.TryPeekSecondToLast(out var target))
{
target.UpdateState(state);

if (!string.IsNullOrWhiteSpace(absolutePath))
{
target.UpdatePath(absolutePath);
}

target.Activate();
}

InvokeAsync(StateHasChanged);

Task.Run(async () =>
{
await Task.Delay(DelayForPageClosingAnimation); // wait for the transition to complete
// wait for a transition animation from left to right to complete,
// known animation time is 300 ms
await Task.Delay(DelayForPageClosingAnimation);

// Remove page 5 and display page 2
Pages.Pop();

if (Pages.Count == 0)
Expand Down
Loading