refactor(web): page state with its page, the root keeps the shell's
Only what the header needs from the start stays at the root: the session, my projects for the command palette, the inbox and push for the bell, and their adapters. The home page's facades (my work, the rest of the home, my views, the calendar), Explore's and the proposal form's are provided by their routes, whose injectors keep them for when you come back. The other adapters come with the tracker's routes, now loaded lazily. The web libs declare "sideEffects": false, so a chunk carries what it uses and not everything an index.ts exports: main goes from 1.31 MB to 432 kB of the same initial total, which is still held up by @pi/ui (one module, with all of Material) and the GraphQL documents, both needed by the header. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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 },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -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<Route[]> {
|
||||
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',
|
||||
|
||||
@@ -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: '' },
|
||||
];
|
||||
|
||||
15
apps/web/src/app/tracker.routes.ts
Normal file
15
apps/web/src/app/tracker.routes.ts
Normal file
@@ -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,
|
||||
},
|
||||
];
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
Binary file not shown.
Binary file not shown.
5
libs/web/package.json
Normal file
5
libs/web/package.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "@task-stream/web-libs",
|
||||
"private": true,
|
||||
"sideEffects": false
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
|
||||
@@ -21,7 +21,6 @@ interface ExploreState {
|
||||
|
||||
/** Public projects, readable by anyone (also without signing in). */
|
||||
export const ExploreStore = signalStore(
|
||||
{ providedIn: 'root' },
|
||||
withState<ExploreState>({
|
||||
projects: [],
|
||||
loaded: false,
|
||||
|
||||
@@ -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<HomeState>({
|
||||
mine: [],
|
||||
mineTotal: 0,
|
||||
|
||||
@@ -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<MyHomeState>({ home: null, error: null }),
|
||||
withReducer(
|
||||
on(myHomeApiEvents.loaded, ({ payload }) => ({
|
||||
|
||||
@@ -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<MyViewsState>({
|
||||
views: [],
|
||||
selectedId: null,
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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),
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user