From 6e7b3c2aa69d67fef342fd39c69b20bc3b40e004 Mon Sep 17 00:00:00 2001 From: Matthieu Riegler Date: Thu, 23 Oct 2025 18:57:06 +0200 Subject: [PATCH] docs(docs-infra): Support linking to individual playground templates --- adev/shared-docs/interfaces/tutorial.ts | 2 +- .../shared-docs/pipeline/tutorials/routes.mts | 1 + .../playground/playground.component.html | 12 +++++------ .../playground/playground.component.spec.ts | 2 ++ .../playground/playground.component.ts | 21 +++++++++++++++++-- 5 files changed, 29 insertions(+), 9 deletions(-) diff --git a/adev/shared-docs/interfaces/tutorial.ts b/adev/shared-docs/interfaces/tutorial.ts index 0128e18c097..b2c3a2c7e04 100644 --- a/adev/shared-docs/interfaces/tutorial.ts +++ b/adev/shared-docs/interfaces/tutorial.ts @@ -151,7 +151,7 @@ export type PlaygroundRouteData = { starterTemplate?: PlaygroundTemplate; }; -export type PlaygroundTemplate = Required>; +export type PlaygroundTemplate = Required> & {id: string}; // Note: only the fields being used are defined in this type export interface PackageJson { diff --git a/adev/shared-docs/pipeline/tutorials/routes.mts b/adev/shared-docs/pipeline/tutorials/routes.mts index 348091d33ff..3964d24f038 100644 --- a/adev/shared-docs/pipeline/tutorials/routes.mts +++ b/adev/shared-docs/pipeline/tutorials/routes.mts @@ -19,6 +19,7 @@ export async function generatePlaygroundRoutes( const templates = Object.entries(configs).map(([path, config]) => ({ path: `playground/${path}`, label: config.title, + id: path, })); return { diff --git a/adev/src/app/features/playground/playground.component.html b/adev/src/app/features/playground/playground.component.html index 353349be8cb..77f3c78c219 100644 --- a/adev/src/app/features/playground/playground.component.html +++ b/adev/src/app/features/playground/playground.component.html @@ -12,17 +12,17 @@ @if (embeddedEditorComponent) { - + }
    @for (template of templates; track template.path) { -
  • - -
  • +
  • + +
  • }
diff --git a/adev/src/app/features/playground/playground.component.spec.ts b/adev/src/app/features/playground/playground.component.spec.ts index ea9cdad7aa5..7af2d3ccdbe 100644 --- a/adev/src/app/features/playground/playground.component.spec.ts +++ b/adev/src/app/features/playground/playground.component.spec.ts @@ -13,6 +13,7 @@ import {EmbeddedTutorialManager} from '../../editor'; import {NodeRuntimeSandbox} from '../../editor/node-runtime-sandbox.service'; import TutorialPlayground from './playground.component'; +import {provideRouter} from '@angular/router'; describe('TutorialPlayground', () => { let component: TutorialPlayground; @@ -28,6 +29,7 @@ describe('TutorialPlayground', () => { TestBed.configureTestingModule({ imports: [TutorialPlayground], providers: [ + provideRouter([]), { provide: WINDOW, useValue: fakeWindow, diff --git a/adev/src/app/features/playground/playground.component.ts b/adev/src/app/features/playground/playground.component.ts index 8f3a1eafeef..fc8c3064646 100644 --- a/adev/src/app/features/playground/playground.component.ts +++ b/adev/src/app/features/playground/playground.component.ts @@ -15,7 +15,9 @@ import { EnvironmentInjector, PLATFORM_ID, Type, + effect, inject, + input, } from '@angular/core'; import {takeUntilDestroyed} from '@angular/core/rxjs-interop'; import {CdkMenu, CdkMenuItem, CdkMenuTrigger} from '@angular/cdk/menu'; @@ -27,6 +29,7 @@ import {injectNodeRuntimeSandbox} from '../../editor/index'; import type {NodeRuntimeSandbox} from '../../editor/node-runtime-sandbox.service'; import PLAYGROUND_ROUTE_DATA_JSON from '../../../../src/assets/tutorials/playground/routes.json'; +import {ActivatedRoute, Router} from '@angular/router'; @Component({ selector: 'adev-playground', @@ -36,12 +39,16 @@ import PLAYGROUND_ROUTE_DATA_JSON from '../../../../src/assets/tutorials/playgro changeDetection: ChangeDetectionStrategy.OnPush, }) export default class PlaygroundComponent { + readonly templateId = input(); + private readonly changeDetectorRef = inject(ChangeDetectorRef); private readonly environmentInjector = inject(EnvironmentInjector); private readonly destroyRef = inject(DestroyRef); private readonly isServer = isPlatformServer(inject(PLATFORM_ID)); + private readonly router = inject(Router); + private readonly route = inject(ActivatedRoute); - readonly templates = PLAYGROUND_ROUTE_DATA_JSON.templates; + readonly templates: PlaygroundTemplate[] = PLAYGROUND_ROUTE_DATA_JSON.templates; readonly defaultTemplate = PLAYGROUND_ROUTE_DATA_JSON.defaultTemplate; readonly starterTemplate = PLAYGROUND_ROUTE_DATA_JSON.starterTemplate; @@ -54,6 +61,11 @@ export default class PlaygroundComponent { return; } + effect(() => { + const foundTemplate = this.templates.find((t) => t.id === this.templateId()); + this.changeTemplate(foundTemplate ?? this.defaultTemplate); + }); + // If using `async-await`, `this` will be captured until the function is executed // and completed, which can lead to a memory leak if the user navigates away from // the playground component to another page. @@ -66,7 +78,7 @@ export default class PlaygroundComponent { this.nodeRuntimeSandbox = nodeRuntimeSandbox; this.embeddedEditorComponent = embeddedEditorComponent; }), - switchMap(() => this.loadTemplate(this.defaultTemplate.path)), + switchMap(() => this.loadTemplate(this.selectedTemplate.path)), takeUntilDestroyed(this.destroyRef), ) .subscribe(() => { @@ -80,6 +92,11 @@ export default class PlaygroundComponent { } async changeTemplate(template: PlaygroundTemplate): Promise { + this.router.navigate([], { + relativeTo: this.route, + queryParams: {templateId: template.id}, + replaceUrl: true, + }); this.selectedTemplate = template; await this.loadTemplate(template.path); await this.nodeRuntimeSandbox!.reset();