mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-10 18:36:15 -08:00
fix(workspace): lead header Back to a parent route when the page opened the session (#1830)
* fix(workspace): lead header Back to a parent route when the page opened the session Settings, Discover, actor and in-portal search registered a header Back that only ran Location.back(). As the first entry of the session (deep link, reload, restored view) that did nothing in Electron and left the app in a browser. WorkspaceBackNavigationService.back(resolveParent) keeps Location.back() while the previous entry is an in-app one, and while that is unknown because the Navigation API is missing. Otherwise it opens the page's parent with replaceUrl, so history Back cannot return to the page: - Settings: the first workspace view (resolveDashboardPath()). - Discover: the catalog section it lists (vod for movies, series for TV). - Actor and search: the portal root, which redirects to its default section within the same navigation. The web E2E opens these pages in a fresh tab: a page.goto in the same tab leaves the previous document behind, often at the parent's URL, so history Back passed without the fix. Electron covers settings after a window reload. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(workspace): lead first-entry Back to the parent without the Navigation API Review follow-ups (Greptile): - Without the Navigation API (older Safari and Firefox) back() always called Location.back(), so a page that opened the session still left the app. The service now tracks the router's in-app history depth there (trackRouterHistoryDepth): first navigation 0, push +1, replacement keeps it, a traversal restores the depth recorded for its entry. Depth 0 opens the parent; an unknown depth (an entry from before a reload) keeps Location.back(). - Stalker's Discover (movie/tv section), actor and search pages now have tests that they hand the service the parent under the portal :id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(workspace): adopt the router navigation the Back depth tracker missed Review follow-up (Codex, Greptile): the lazy workspace shell creates the Back service after the first NavigationStart, so the tracker saw only its NavigationEnd, left the depth unknown and counted the next push as the first entry. It now adopts the router's current or last successful navigation when it starts: a first navigation is depth 0, a later one leaves the depth unknown (browser history Back), and a late start of the adopted navigation is not counted again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: 4gray <fourgray@proton.me> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
23 files changed
+940
-55
No files matched your search
+50
-3
@@ -1,18 +1,43 @@
|
||||
import { Location } from '@angular/common';
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { provideRouter } from '@angular/router';
|
||||
import { provideRouter, Router } from '@angular/router';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { of } from 'rxjs';
|
||||
import { WorkspaceBackNavigationService } from '@iptvnator/portal/shared/data-access';
|
||||
import {
|
||||
WORKSPACE_HISTORY_NAVIGATION,
|
||||
WorkspaceBackNavigationService,
|
||||
WorkspaceHistoryNavigation,
|
||||
} from '@iptvnator/portal/shared/data-access';
|
||||
import { WorkspaceStartupPreferencesService } from '@iptvnator/workspace/shell/util';
|
||||
import { SettingsContextService } from '@iptvnator/workspace/shell/util/settings-context';
|
||||
import { WorkspaceSettingsContextPanelComponent } from './workspace-settings-context-panel.component';
|
||||
|
||||
/** Navigation API state of a session that opened on the settings page. */
|
||||
const firstPageHistory = {
|
||||
currentEntry: { index: 0 },
|
||||
entries: () => [{ index: 0, sameDocument: true }],
|
||||
addEventListener: jest.fn(),
|
||||
removeEventListener: jest.fn(),
|
||||
} as unknown as WorkspaceHistoryNavigation;
|
||||
|
||||
describe('WorkspaceSettingsContextPanelComponent', () => {
|
||||
function setup() {
|
||||
const resolveDashboardPath = jest.fn();
|
||||
|
||||
// Without a Navigation API (jsdom's default) history is unknown, so
|
||||
// Back stays the browser's.
|
||||
function setup(history: WorkspaceHistoryNavigation | null = null) {
|
||||
resolveDashboardPath
|
||||
.mockReset()
|
||||
.mockResolvedValue('/workspace/dashboard');
|
||||
TestBed.configureTestingModule({
|
||||
imports: [WorkspaceSettingsContextPanelComponent],
|
||||
providers: [
|
||||
provideRouter([]),
|
||||
{ provide: WORKSPACE_HISTORY_NAVIGATION, useValue: history },
|
||||
{
|
||||
provide: WorkspaceStartupPreferencesService,
|
||||
useValue: { resolveDashboardPath },
|
||||
},
|
||||
{
|
||||
provide: TranslateService,
|
||||
useValue: {
|
||||
@@ -66,11 +91,33 @@ describe('WorkspaceSettingsContextPanelComponent', () => {
|
||||
|
||||
backNavigation.goBack();
|
||||
expect(back).toHaveBeenCalledTimes(1);
|
||||
expect(resolveDashboardPath).not.toHaveBeenCalled();
|
||||
|
||||
fixture.destroy();
|
||||
expect(backNavigation.target()).toBeNull();
|
||||
});
|
||||
|
||||
it('leads to the first workspace view when settings opened the session', async () => {
|
||||
setup(firstPageHistory);
|
||||
const back = jest
|
||||
.spyOn(TestBed.inject(Location), 'back')
|
||||
.mockImplementation(() => undefined);
|
||||
const navigateByUrl = jest
|
||||
.spyOn(TestBed.inject(Router), 'navigateByUrl')
|
||||
.mockResolvedValue(true);
|
||||
resolveDashboardPath.mockResolvedValue('/workspace/sources');
|
||||
|
||||
TestBed.inject(WorkspaceBackNavigationService).goBack();
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
|
||||
// Location.back() would do nothing in Electron or leave the PWA.
|
||||
expect(back).not.toHaveBeenCalled();
|
||||
expect(navigateByUrl).toHaveBeenCalledWith('/workspace/sources', {
|
||||
replaceUrl: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('shows no counts while settings search is idle', () => {
|
||||
const { link } = setup();
|
||||
|
||||
|
||||
+18
-5
@@ -1,10 +1,15 @@
|
||||
import { Location } from '@angular/common';
|
||||
import { Component, inject, ChangeDetectionStrategy } from '@angular/core';
|
||||
import { MatIconModule } from '@angular/material/icon';
|
||||
import { RouterLink, RouterLinkActive } from '@angular/router';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { registerWorkspaceBack } from '@iptvnator/portal/shared/data-access';
|
||||
import { WorkspaceShellContextDrawerService } from '@iptvnator/workspace/shell/util';
|
||||
import {
|
||||
registerWorkspaceBack,
|
||||
WorkspaceBackNavigationService,
|
||||
} from '@iptvnator/portal/shared/data-access';
|
||||
import {
|
||||
WorkspaceShellContextDrawerService,
|
||||
WorkspaceStartupPreferencesService,
|
||||
} from '@iptvnator/workspace/shell/util';
|
||||
import { SettingsContextService } from '@iptvnator/workspace/shell/util/settings-context';
|
||||
|
||||
@Component({
|
||||
@@ -56,7 +61,10 @@ import { SettingsContextService } from '@iptvnator/workspace/shell/util/settings
|
||||
})
|
||||
export class WorkspaceSettingsContextPanelComponent {
|
||||
readonly ctx = inject(SettingsContextService);
|
||||
private readonly location = inject(Location);
|
||||
private readonly backNavigation = inject(WorkspaceBackNavigationService);
|
||||
private readonly startupPreferences = inject(
|
||||
WorkspaceStartupPreferencesService
|
||||
);
|
||||
// Root-provided; optional keeps standalone unit tests light. Section
|
||||
// links are real navigations now, so the phone drawer's NavigationEnd
|
||||
// auto-close fires too — the explicit close just makes the drawer react
|
||||
@@ -72,9 +80,14 @@ export class WorkspaceSettingsContextPanelComponent {
|
||||
// The panel exists exactly while the settings route shows, so it
|
||||
// offers the page's Back in the header. On a phone the toggle for
|
||||
// this drawer stays beside it: the drawer holds the sections.
|
||||
// Opened as the session's first page, Back leads to the first
|
||||
// workspace view (the dashboard, or sources when it is hidden).
|
||||
registerWorkspaceBack({
|
||||
phoneDrawerToggle: 'beside',
|
||||
run: () => this.location.back(),
|
||||
run: () =>
|
||||
this.backNavigation.back(() =>
|
||||
this.startupPreferences.resolveDashboardPath()
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user