diff --git a/apps/web/src/app/app.config.ts b/apps/web/src/app/app.config.ts index 4408e05..10768f8 100644 --- a/apps/web/src/app/app.config.ts +++ b/apps/web/src/app/app.config.ts @@ -16,7 +16,7 @@ import { } from '@task-stream/web/shared/infrastructure'; import { providePiUi, ToastService } from '@task-stream/web/shared/ui'; import { Notifications } from '@task-stream/web/tracker/application'; -import { provideTrackerInfrastructure } from '@task-stream/web/tracker/infrastructure'; +import { provideTrackerShellInfrastructure } from '@task-stream/web/tracker/infrastructure'; import { appRoutes } from './app.routes'; /* @@ -39,9 +39,9 @@ export const appConfig: ApplicationConfig = { provideApiConfig(apiConfigFromLocation()), provideGraphql(), - // Ports → adapters + // Ports → adapters: the shell's here; the pages' with their lazy routes (tracker.routes.ts). provideIdentityInfrastructure(), - provideTrackerInfrastructure(), + provideTrackerShellInfrastructure(), { provide: Notifications, useExisting: ToastService }, ], }; diff --git a/apps/web/src/app/app.routes.spec.ts b/apps/web/src/app/app.routes.spec.ts index 1f2ed27..8d6cc02 100644 --- a/apps/web/src/app/app.routes.spec.ts +++ b/apps/web/src/app/app.routes.spec.ts @@ -1,15 +1,25 @@ +import { Route } from '@angular/router'; import { authGuard } from '@task-stream/web/identity/presenters'; import { appRoutes } from './app.routes'; +/** The tracker's routes, behind the lazy `''` route (and its providers' parent route). */ +async function trackerChildren(): Promise { + const lazy = appRoutes.find( + (route) => route.path === '' && route.loadChildren, + ); + const loaded = (await lazy?.loadChildren?.()) as Route[]; + expect(loaded[0].providers?.length).toBeGreaterThan(0); + return loaded[0].children ?? []; +} + describe('appRoutes', () => { - it('keeps home behind the auth guard, and public projects and «Explorar» open', () => { + it('keeps home behind the auth guard, and public projects and «Explorar» open', async () => { const paths = appRoutes.map((route) => route.path); expect(paths).toEqual( expect.arrayContaining(['login', 'auth/callback', '', '**']), ); - const app = appRoutes.find((route) => route.path === '' && route.children); - const children = app?.children ?? []; + const children = await trackerChildren(); expect(children.map((route) => route.path)).toEqual([ '', 'portfolio', @@ -27,10 +37,16 @@ describe('appRoutes', () => { expect(children[4].redirectTo).toBe('explore'); }); - it('exposes every project section', () => { - const project = appRoutes - .find((route) => route.path === '') - ?.children?.find((route) => route.path === 'p/:key'); + it('gives each page its own state, not the root', async () => { + const children = await trackerChildren(); + expect(children[0].providers?.length).toBe(4); + expect(children[2].providers?.length).toBe(1); + }); + + it('exposes every project section', async () => { + const project = (await trackerChildren()).find( + (route) => route.path === 'p/:key', + ); expect(project?.children?.map((route) => route.path)).toEqual([ '', 'proponer', diff --git a/apps/web/src/app/app.routes.ts b/apps/web/src/app/app.routes.ts index 210e6f5..ba377d6 100644 --- a/apps/web/src/app/app.routes.ts +++ b/apps/web/src/app/app.routes.ts @@ -1,10 +1,12 @@ import { Route } from '@angular/router'; import { identityRoutes } from '@task-stream/web/identity/presenters'; -import { trackerRoutes } from '@task-stream/web/tracker/presenters'; export const appRoutes: Route[] = [ ...identityRoutes, // Not all behind the guard: public projects are readable without a session. - { path: '', children: trackerRoutes }, + { + path: '', + loadChildren: () => import('./tracker.routes').then((m) => m.routes), + }, { path: '**', redirectTo: '' }, ]; diff --git a/apps/web/src/app/tracker.routes.ts b/apps/web/src/app/tracker.routes.ts new file mode 100644 index 0000000..1de8897 --- /dev/null +++ b/apps/web/src/app/tracker.routes.ts @@ -0,0 +1,15 @@ +import { Route } from '@angular/router'; +import { provideTrackerPagesInfrastructure } from '@task-stream/web/tracker/infrastructure'; +import { trackerRoutes } from '@task-stream/web/tracker/presenters'; + +/** + * The tracker, loaded lazily: its pages, and the adapters only they use, + * come in this chunk instead of the one that boots the app. + */ +export const routes: Route[] = [ + { + path: '', + providers: [provideTrackerPagesInfrastructure()], + children: trackerRoutes, + }, +]; diff --git a/docs/02-arquitectura.md b/docs/02-arquitectura.md index ca1689a..ca16b90 100644 --- a/docs/02-arquitectura.md +++ b/docs/02-arquitectura.md @@ -59,8 +59,11 @@ TrackerPresentersModule.withApplication( ```ts // apps/web/src/app/app.config.ts -provideTrackerInfrastructure(); // puertos → adaptadores GraphQL +provideTrackerShellInfrastructure(); // puertos de la cabecera → adaptadores GraphQL { provide: Notifications, useExisting: ToastService } // puerto de la aplicación → toasts de @pi/ui + +// apps/web/src/app/tracker.routes.ts (en diferido) +{ path: '', providers: [provideTrackerPagesInfrastructure()], children: trackerRoutes } ``` ## 2.2 Flujo completo de una escritura diff --git a/docs/04-cliente-web.md b/docs/04-cliente-web.md index 13ae381..aaceefb 100644 --- a/docs/04-cliente-web.md +++ b/docs/04-cliente-web.md @@ -113,18 +113,30 @@ Qué gana la página: | ---------------- | --------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `AuthFacade` | `AuthStore` | raíz | Sesión, usuario, login (PKCE o email y contraseña de Pireon), renovación del token, restaurar al arrancar | | `ProjectsFacade` | `ProjectsStore` | raíz | Mis proyectos; se actualiza cuando me añaden o quitan (`myProjectsChanged`) | -| `HomeFacade` | `HomeStore` | raíz | «Mi trabajo» en la portada: mis tickets abiertos por categoría (QA fallidos primero), esperando QA y próximas releases; se recarga al volver a la pestaña | -| `MyHomeFacade` | `MyHomeStore` | raíz | El resto de la portada (`myHome`): agenda, actividad, cifras de la semana, sprints, tarjetas, sugerencias, primeros pasos y quién está; se recarga cada minuto y al volver a la pestaña | -| `MyViewsFacade` | `MyViewsStore` | raíz | Mis vistas guardadas de «Mi trabajo» (entre proyectos: proyectos, asignados a mí, que sigo, estados, prioridades, solo bugs, vencidos, texto) y los tickets de la elegida | -| `ExploreFacade` | `ExploreStore` | raíz | Proyectos públicos («Explorar»), también sin sesión | +| `HomeFacade` | `HomeStore` | ruta `/` (portada) | «Mi trabajo» en la portada: mis tickets abiertos por categoría (QA fallidos primero), esperando QA y próximas releases; se recarga al volver a la pestaña | +| `MyHomeFacade` | `MyHomeStore` | ruta `/` (portada) | El resto de la portada (`myHome`): agenda, actividad, cifras de la semana, sprints, tarjetas, sugerencias, primeros pasos y quién está; se recarga cada minuto y al volver a la pestaña | +| `MyViewsFacade` | `MyViewsStore` | ruta `/` (portada) | Mis vistas guardadas de «Mi trabajo» (entre proyectos: proyectos, asignados a mí, que sigo, estados, prioridades, solo bugs, vencidos, texto) y los tickets de la elegida | +| `ExploreFacade` | `ExploreStore` | ruta `explore` | Proyectos públicos («Explorar»), también sin sesión | | `ProjectFacade` | `ProjectStore` | ruta `/p/:key` | Proyecto, miembros, stats, releases, actividad en vivo, búsqueda de usuarios, configuración (`workflow`, `saveSettings`) y estadísticas (`loadAnalytics`, `loadBurnup`) | | `BoardFacade` | `BoardStore` | ruta `board` | Tablero kanban, filtros, movimiento optimista, nuevo ticket, cambios en vivo | | `ListFacade` | `ListStore` | ruta `list` | Todos los tickets del proyecto en filas: filtros (enviados a la API), cambios de estado, prioridad y persona en la fila, edición en bloque; se recarga tras cada cambio propio y con los de otros | | `IssueFacade` | `IssueStore` | ruta `issues/:issueKey` | Ticket, comentarios en vivo, historial y todas sus acciones | -Las de ámbito de ruta se registran con `provideProjectFacade()`, `provideBoardFacade()` o `provideIssueFacade()` en los -`providers` del componente de la página (store + facade) y desaparecen al salir (con sus subscriptions). Las páginas -hijas de `/p/:key` reciben la misma `ProjectFacade`. +En la raíz solo queda lo que usa la cabecera desde el arranque: la sesión (`AuthFacade`), mis proyectos para la paleta +de comandos (`ProjectsFacade`) y la campana (`InboxFacade`, `PushFacade`). Lo demás es de su página: + +- Las de una página que se vuelve a visitar a menudo (portada, Explorar, el formulario de propuestas) van en los + `providers` de su ruta (`provideHomeFacade()`, `provideMyHomeFacade()`, `provideMyViewsFacade()`, + `provideCalendarFacade()`, `provideExploreFacade()`, `provideProposeFacade()`): cargan con la página y el inyector de + la ruta las conserva, así que al volver se ve lo último al instante mientras se recarga. +- Las de un proyecto o un ticket se registran con `provideProjectFacade()`, `provideBoardFacade()` o + `provideIssueFacade()` en los `providers` del componente de la página (store + facade) y desaparecen al salir (con sus + subscriptions). Las páginas hijas de `/p/:key` reciben la misma `ProjectFacade`. + +Los adaptadores siguen el mismo reparto: `provideTrackerShellInfrastructure()` (proyectos, tickets, avisos, push y tiempo +real) va en la raíz, y `provideTrackerPagesInfrastructure()` (el resto) en la ruta del tracker, que se carga en diferido +(`apps/web/src/app/tracker.routes.ts`). Las librerías web declaran `"sideEffects": false` (`libs/web/package.json`), para +que el empaquetador deje en cada trozo solo lo que usa y no todo lo que exporta un `index.ts`. Otros contextos también usan la facade: los stores del tracker leen el usuario de `AuthFacade`, y la infraestructura de identidad (`SessionTokenSource`) le pide el token para el cliente GraphQL. diff --git a/docs/TaskStream-Documentacion.docx b/docs/TaskStream-Documentacion.docx index 9d1eec1..45cbdaf 100644 Binary files a/docs/TaskStream-Documentacion.docx and b/docs/TaskStream-Documentacion.docx differ diff --git a/docs/TaskStream-Documentacion.pdf b/docs/TaskStream-Documentacion.pdf index d8d66f3..9790788 100644 Binary files a/docs/TaskStream-Documentacion.pdf and b/docs/TaskStream-Documentacion.pdf differ diff --git a/libs/web/package.json b/libs/web/package.json new file mode 100644 index 0000000..87e0f41 --- /dev/null +++ b/libs/web/package.json @@ -0,0 +1,5 @@ +{ + "name": "@task-stream/web-libs", + "private": true, + "sideEffects": false +} diff --git a/libs/web/tracker/application/src/lib/facades/calendar.facade.ts b/libs/web/tracker/application/src/lib/facades/calendar.facade.ts index 9e85c83..370c331 100644 --- a/libs/web/tracker/application/src/lib/facades/calendar.facade.ts +++ b/libs/web/tracker/application/src/lib/facades/calendar.facade.ts @@ -1,4 +1,4 @@ -import { inject, Injectable, signal } from '@angular/core'; +import { inject, Injectable, Provider, signal } from '@angular/core'; import { CalendarFeed, CalendarGateway } from '@task-stream/web/tracker/domain'; import { Observable } from 'rxjs'; @@ -7,7 +7,7 @@ import { Observable } from 'rxjs'; * calendar app, made, renewed or dropped here. Per person and seldom * touched, so it lives in this facade alone, without a store. */ -@Injectable({ providedIn: 'root' }) +@Injectable() export class CalendarFacade { private readonly gateway = inject(CalendarGateway); @@ -54,3 +54,8 @@ export class CalendarFacade { this.error.set('No se ha podido. Inténtalo de nuevo en un momento.'); } } + +/** My calendar subscription: provided by the route that shows it, not the root. */ +export function provideCalendarFacade(): Provider[] { + return [CalendarFacade]; +} diff --git a/libs/web/tracker/application/src/lib/facades/explore.facade.ts b/libs/web/tracker/application/src/lib/facades/explore.facade.ts index c0698af..452d3e9 100644 --- a/libs/web/tracker/application/src/lib/facades/explore.facade.ts +++ b/libs/web/tracker/application/src/lib/facades/explore.facade.ts @@ -1,10 +1,10 @@ -import { inject, Injectable } from '@angular/core'; +import { inject, Injectable, Provider } from '@angular/core'; import { injectDispatch } from '@ngrx/signals/events'; import { explorePageEvents } from '../events/explore.events'; import { ExploreStore } from '../stores/explore.store'; /** "Explorar": public projects, for anyone (no sign-in needed). */ -@Injectable({ providedIn: 'root' }) +@Injectable() export class ExploreFacade { private readonly store = inject(ExploreStore); private readonly dispatch = injectDispatch(explorePageEvents); @@ -20,3 +20,8 @@ export class ExploreFacade { this.dispatch.opened(); } } + +/** The public projects page: provided by the route that shows it, not the root. */ +export function provideExploreFacade(): Provider[] { + return [ExploreStore, ExploreFacade]; +} diff --git a/libs/web/tracker/application/src/lib/facades/facades.spec.ts b/libs/web/tracker/application/src/lib/facades/facades.spec.ts index b1c1335..cf3b6fd 100644 --- a/libs/web/tracker/application/src/lib/facades/facades.spec.ts +++ b/libs/web/tracker/application/src/lib/facades/facades.spec.ts @@ -23,7 +23,7 @@ import { boardPageEvents } from '../events/board.events'; import { projectPageEvents } from '../events/project.events'; import { Notifications } from '../notifications'; import { BoardFacade, provideBoardFacade } from './board.facade'; -import { HomeFacade } from './home.facade'; +import { HomeFacade, provideHomeFacade } from './home.facade'; import { InboxFacade } from './inbox.facade'; import { IssueFacade, provideIssueFacade } from './issue.facade'; import { ProjectFacade, provideProjectFacade } from './project.facade'; @@ -427,6 +427,7 @@ describe('HomeFacade', () => { ); TestBed.configureTestingModule({ providers: [ + provideHomeFacade(), { provide: IssuesGateway, useValue: { mine } }, { provide: ReleasesGateway, diff --git a/libs/web/tracker/application/src/lib/facades/home.facade.ts b/libs/web/tracker/application/src/lib/facades/home.facade.ts index 7122e98..a02ed4d 100644 --- a/libs/web/tracker/application/src/lib/facades/home.facade.ts +++ b/libs/web/tracker/application/src/lib/facades/home.facade.ts @@ -1,10 +1,10 @@ -import { inject, Injectable } from '@angular/core'; +import { inject, Injectable, Provider } from '@angular/core'; import { injectDispatch } from '@ngrx/signals/events'; import { homePageEvents } from '../events/home.events'; import { HomeStore } from '../stores/home.store'; /** "My work" on the home page: my tickets, waiting for QA, upcoming releases. */ -@Injectable({ providedIn: 'root' }) +@Injectable() export class HomeFacade { private readonly store = inject(HomeStore); private readonly dispatch = injectDispatch(homePageEvents); @@ -33,3 +33,8 @@ export class HomeFacade { this.dispatch.opened(); } } + +/** «Mi trabajo» on the home page: provided by the route that shows it, not the root. */ +export function provideHomeFacade(): Provider[] { + return [HomeStore, HomeFacade]; +} diff --git a/libs/web/tracker/application/src/lib/facades/my-home.facade.ts b/libs/web/tracker/application/src/lib/facades/my-home.facade.ts index ede87b1..76275a6 100644 --- a/libs/web/tracker/application/src/lib/facades/my-home.facade.ts +++ b/libs/web/tracker/application/src/lib/facades/my-home.facade.ts @@ -1,4 +1,4 @@ -import { inject, Injectable } from '@angular/core'; +import { inject, Injectable, Provider } from '@angular/core'; import { injectDispatch } from '@ngrx/signals/events'; import { myHomePageEvents } from '../events/my-home.events'; import { MyHomeStore } from '../stores/my-home.store'; @@ -7,7 +7,7 @@ import { MyHomeStore } from '../stores/my-home.store'; * The home page beyond «my work»: agenda, activity, the week, sprints, * project cards, suggestions, first steps and who is on what now. */ -@Injectable({ providedIn: 'root' }) +@Injectable() export class MyHomeFacade { private readonly store = inject(MyHomeStore); private readonly dispatch = injectDispatch(myHomePageEvents); @@ -21,3 +21,8 @@ export class MyHomeFacade { this.dispatch.refreshed(); } } + +/** The rest of the home page: provided by the route that shows it, not the root. */ +export function provideMyHomeFacade(): Provider[] { + return [MyHomeStore, MyHomeFacade]; +} diff --git a/libs/web/tracker/application/src/lib/facades/my-views.facade.ts b/libs/web/tracker/application/src/lib/facades/my-views.facade.ts index 88d80ad..1078ddb 100644 --- a/libs/web/tracker/application/src/lib/facades/my-views.facade.ts +++ b/libs/web/tracker/application/src/lib/facades/my-views.facade.ts @@ -1,11 +1,11 @@ -import { inject, Injectable } from '@angular/core'; +import { inject, Injectable, Provider } from '@angular/core'; import { injectDispatch } from '@ngrx/signals/events'; import { MyViewCriteria } from '@task-stream/web/tracker/domain'; import { myViewsPageEvents } from '../events/my-views.events'; import { MyViewsStore, VIEW_LIMIT } from '../stores/my-views.store'; /** My saved views of «my work», across my projects. */ -@Injectable({ providedIn: 'root' }) +@Injectable() export class MyViewsFacade { private readonly store = inject(MyViewsStore); private readonly dispatch = injectDispatch(myViewsPageEvents); @@ -39,3 +39,8 @@ export class MyViewsFacade { this.dispatch.deleteRequested(viewId); } } + +/** My saved views, on the home page: provided by the route that shows it, not the root. */ +export function provideMyViewsFacade(): Provider[] { + return [MyViewsStore, MyViewsFacade]; +} diff --git a/libs/web/tracker/application/src/lib/facades/propose.facade.ts b/libs/web/tracker/application/src/lib/facades/propose.facade.ts index 7f3608b..b6a6fd6 100644 --- a/libs/web/tracker/application/src/lib/facades/propose.facade.ts +++ b/libs/web/tracker/application/src/lib/facades/propose.facade.ts @@ -1,4 +1,4 @@ -import { inject, Injectable } from '@angular/core'; +import { inject, Injectable, Provider } from '@angular/core'; import { ProposalInput, ProposalResult, @@ -7,7 +7,7 @@ import { import { Observable } from 'rxjs'; /** The public form of a project: anyone reports a bug or proposes an idea. */ -@Injectable({ providedIn: 'root' }) +@Injectable() export class ProposeFacade { private readonly proposals = inject(ProposalsGateway); @@ -18,3 +18,8 @@ export class ProposeFacade { return this.proposals.submit(projectKey, proposal); } } + +/** The public proposal form: provided by the route that shows it, not the root. */ +export function provideProposeFacade(): Provider[] { + return [ProposeFacade]; +} diff --git a/libs/web/tracker/application/src/lib/stores/explore.store.ts b/libs/web/tracker/application/src/lib/stores/explore.store.ts index f8cd096..63e0e09 100644 --- a/libs/web/tracker/application/src/lib/stores/explore.store.ts +++ b/libs/web/tracker/application/src/lib/stores/explore.store.ts @@ -21,7 +21,6 @@ interface ExploreState { /** Public projects, readable by anyone (also without signing in). */ export const ExploreStore = signalStore( - { providedIn: 'root' }, withState({ projects: [], loaded: false, diff --git a/libs/web/tracker/application/src/lib/stores/home.store.ts b/libs/web/tracker/application/src/lib/stores/home.store.ts index 69c2eb5..7056558 100644 --- a/libs/web/tracker/application/src/lib/stores/home.store.ts +++ b/libs/web/tracker/application/src/lib/stores/home.store.ts @@ -42,11 +42,11 @@ interface HomeState { /** * "My work" across every project I belong to: my open tickets, tickets - * waiting for QA, the open ones I follow and upcoming releases. Root-scoped, so coming back to the - * home page shows the last data at once while it refreshes. + * waiting for QA, the open ones I follow and upcoming releases. Provided by the home route, + * whose injector outlives the page, so coming back shows the last data at + * once while it refreshes. */ export const HomeStore = signalStore( - { providedIn: 'root' }, withState({ mine: [], mineTotal: 0, diff --git a/libs/web/tracker/application/src/lib/stores/my-home.store.ts b/libs/web/tracker/application/src/lib/stores/my-home.store.ts index 10e5e85..032a0af 100644 --- a/libs/web/tracker/application/src/lib/stores/my-home.store.ts +++ b/libs/web/tracker/application/src/lib/stores/my-home.store.ts @@ -18,11 +18,11 @@ interface MyHomeState { } /** - * The home page beyond «my work». Root-scoped, so coming back shows the - * last reading at once while it refreshes. + * The home page beyond «my work». Provided by the home route, whose injector + * outlives the page, so coming back shows the last reading at once while it + * refreshes. */ export const MyHomeStore = signalStore( - { providedIn: 'root' }, withState({ home: null, error: null }), withReducer( on(myHomeApiEvents.loaded, ({ payload }) => ({ diff --git a/libs/web/tracker/application/src/lib/stores/my-views.store.ts b/libs/web/tracker/application/src/lib/stores/my-views.store.ts index e907bae..9e5fc95 100644 --- a/libs/web/tracker/application/src/lib/stores/my-views.store.ts +++ b/libs/web/tracker/application/src/lib/stores/my-views.store.ts @@ -50,9 +50,8 @@ interface MyViewsState { error: string | null; } -/** My saved views of «my work» and the tickets of the one picked. Root-scoped. */ +/** My saved views of «my work» and the tickets of the one picked. Provided by the home route. */ export const MyViewsStore = signalStore( - { providedIn: 'root' }, withState({ views: [], selectedId: null, diff --git a/libs/web/tracker/infrastructure/src/lib/tracker-infrastructure.providers.ts b/libs/web/tracker/infrastructure/src/lib/tracker-infrastructure.providers.ts index 914912e..46f4bf6 100644 --- a/libs/web/tracker/infrastructure/src/lib/tracker-infrastructure.providers.ts +++ b/libs/web/tracker/infrastructure/src/lib/tracker-infrastructure.providers.ts @@ -45,22 +45,34 @@ import { GraphqlWebhooksGateway } from './adapters/graphql-webhooks.gateway'; import { HttpTransferGateway } from './adapters/http-transfer.gateway'; import { GraphqlAutomationsGateway } from './adapters/graphql-automations.gateway'; -/** Ports → GraphQL adapters (and REST for files). */ -export function provideTrackerInfrastructure(): EnvironmentProviders { +/** + * The adapters the app shell needs from the start (the command palette, the + * notifications bell and live updates): provided at the root. + */ +export function provideTrackerShellInfrastructure(): EnvironmentProviders { return makeEnvironmentProviders([ { provide: ProjectsGateway, useClass: GraphqlProjectsGateway }, { provide: IssuesGateway, useClass: GraphqlIssuesGateway }, + { provide: NotificationsGateway, useClass: GraphqlNotificationsGateway }, + { provide: PushDevice, useClass: BrowserPushDevice }, + { provide: TrackerRealtimeGateway, useClass: GraphqlRealtimeGateway }, + ]); +} + +/** + * Ports → GraphQL adapters (and REST for files) used only by the tracker's + * pages: provided by their lazy route, so they load with them. + */ +export function provideTrackerPagesInfrastructure(): EnvironmentProviders { + return makeEnvironmentProviders([ { provide: CommentsGateway, useClass: GraphqlCommentsGateway }, { provide: ReleasesGateway, useClass: GraphqlReleasesGateway }, { provide: ActivityGateway, useClass: GraphqlActivityGateway }, { provide: AttachmentsGateway, useClass: HttpAttachmentsGateway }, { provide: ProposalsGateway, useClass: HttpProposalsGateway }, { provide: UsersGateway, useClass: GraphqlUsersGateway }, - { provide: NotificationsGateway, useClass: GraphqlNotificationsGateway }, { provide: CalendarGateway, useClass: GraphqlCalendarGateway }, { provide: PresenceGateway, useClass: GraphqlPresenceGateway }, - { provide: PushDevice, useClass: BrowserPushDevice }, - { provide: TrackerRealtimeGateway, useClass: GraphqlRealtimeGateway }, { provide: SavedFiltersGateway, useClass: GraphqlSavedFiltersGateway }, { provide: SprintsGateway, useClass: GraphqlSprintsGateway }, { provide: GitGateway, useClass: GraphqlGitGateway }, diff --git a/libs/web/tracker/presenters/src/lib/tracker.routes.ts b/libs/web/tracker/presenters/src/lib/tracker.routes.ts index 236f456..5eaa7aa 100644 --- a/libs/web/tracker/presenters/src/lib/tracker.routes.ts +++ b/libs/web/tracker/presenters/src/lib/tracker.routes.ts @@ -1,16 +1,33 @@ import { Routes } from '@angular/router'; import { authGuard } from '@task-stream/web/identity/presenters'; +import { + provideCalendarFacade, + provideExploreFacade, + provideHomeFacade, + provideMyHomeFacade, + provideMyViewsFacade, + provideProposeFacade, +} from '@task-stream/web/tracker/application'; /** * The tracker. Home needs a session; project pages and «Explorar» don't: * public projects can be read by anyone (the API decides what each visitor * sees, and pages hide what they cannot change). + * + * Each page's state is provided by its route, not the root: it loads with the + * page, and the route's injector keeps it for when you come back. */ export const trackerRoutes: Routes = [ { path: '', canActivate: [authGuard], title: 'Proyectos · TaskStream', + providers: [ + provideHomeFacade(), + provideMyHomeFacade(), + provideMyViewsFacade(), + provideCalendarFacade(), + ], loadComponent: () => import('./pages/projects.page').then((m) => m.ProjectsPage), }, @@ -24,6 +41,7 @@ export const trackerRoutes: Routes = [ { path: 'explore', title: 'Proyectos públicos · TaskStream', + providers: [provideExploreFacade()], loadComponent: () => import('./pages/explore.page').then((m) => m.ExplorePage), }, @@ -47,6 +65,7 @@ export const trackerRoutes: Routes = [ { path: 'propose', title: 'Proponer · TaskStream', + providers: [provideProposeFacade()], loadComponent: () => import('./pages/propose.page').then((m) => m.ProposePage), },