import { HttpClient } from '@angular/common/http' ; import { Injectable , NgZone } from '@angular/core' ; import { ExampleData , EXAMPLE_COMPONENTS } from '@sbb-esta/components-examples' ; import stackblitz from '@stackblitz/sdk' ; import { Observable } from 'rxjs' ; import { shareReplay , take } from 'rxjs/operators' ; import { libraryVersion } from './versions' ; /**
 * Path that refers to the docs-content from the "@angular/components-examples" package.
 */ const DOCS_CONTENT_PATH = 'assets/docs-content/examples-source' ; const TEMPLATE_PATH = 'assets/stack-blitz/' ; /**
 * List of boilerplate files for an example StackBlitz.
 * This currently matches files needed for a basic Angular CLI project.
 */ export const TEMPLATE_FILES = [ '.gitignore' , '.stackblitzrc' , 'angular.json' , 'karma.conf.js' , 'package.json' , 'package-lock.json' , 'tsconfig.app.json' , 'tsconfig.json' , 'tsconfig.spec.json' , 'src/favicon.ico' , 'src/index.html' , 'src/main.ts' , 'src/polyfills.ts' , 'src/styles.scss' , 'src/test.ts' , 'src/app/app.module.ts' , 'src/app/sbb.module.ts' , 'src/environments/environment.prod.ts' , 'src/environments/environment.ts' , ] ; const PROJECT_TAGS = [ 'angular' , 'sbb-esta' , 'web' , 'example' ] ; const PROJECT_TEMPLATE = 'node' ; /**
 * Type describing an in-memory file dictionary, representing a
 * directory and its contents.
 */ interface FileDictionary { [ path ] : string ; } /**
 * StackBlitz writer, write example files to StackBlitz.
 */ @ Injectable ( { providedIn : 'root' } ) export class StackBlitzWriter { private _fileCache = new Map < string , Observable < string > > ( ) ; constructor ( private _http , private _ngZone ) { } /** Opens a StackBlitz for the specified example. */ createStackBlitzForExample ( exampleId , data , isTest ) { return this . _ngZone . runOutsideAngular ( async ( ) => { const files = await this . _buildInMemoryFileDictionary ( data , exampleId , isTest ) ; const exampleMainFile = `src/app/${ data . indexFilename }` ; return ( isSbbLean ) => { files [ 'src/index.html' ] = files [ 'src/index.html' ] . replace ( /\${sbbLean}/g , isSbbLean ? ' class="sbb-lean"' : '' ) ; this . _openStackBlitz ( { files , title : `Sbb Angular Library - ${ data . description }` , description : `${ data . description }\n\nAuto-generated from: https://angular.app.sbb.ch` , openFile : exampleMainFile , } ) ; } ; } ) ; } /** Opens a new WebContainer-based StackBlitz for the given files. */ private _openStackBlitz ( { title , description , openFile , files , } ) { stackblitz . openProject ( { title , files , description , template : PROJECT_TEMPLATE , tags : PROJECT_TAGS , } , { openFile } ) ; } /**
     * Builds an in-memory file dictionary representing an CLI project serving
     * the example. The dictionary can then be passed to StackBlitz as project files.
     */ private async _buildInMemoryFileDictionary ( data , exampleId , isTest ) { const result = { } ; const tasks = [ ] ; const liveExample = EXAMPLE_COMPONENTS [ exampleId ] ; const exampleBaseContentPath = `${ DOCS_CONTENT_PATH }/${ liveExample . module . importSpecifier }/${ exampleId }/` ; for ( const relativeFilePath of TEMPLATE_FILES ) { tasks . push ( this . _loadFile ( TEMPLATE_PATH + relativeFilePath ) . then ( ( content ) => this . _replaceExamplePlaceholders ( data , relativeFilePath , content , isTest ) ) . then ( ( content ) => ( result [ relativeFilePath ] = content ) ) ) ; } for ( const relativeFilePath of data . exampleFiles ) { tasks . push ( this . _loadFile ( exampleBaseContentPath + relativeFilePath ) . then ( ( content ) => ( result [ `src/app/${ relativeFilePath }` ] = content ) ) ) ; } await Promise . all ( tasks ) ; return result ; } /**
     * Loads the specified file and returns a promise resolving to its contents.
     */ private _loadFile ( fileUrl ) { let stream = this . _fileCache . get ( fileUrl ) ; if ( ! stream ) { stream = this . _http . get ( fileUrl , { responseType : 'text' } ) . pipe ( shareReplay ( 1 ) ) ; this . _fileCache . set ( fileUrl , stream ) ; } return stream . pipe ( take ( 1 ) ) . toPromise ( ) ; } /**
     * The StackBlitz template assets contain placeholder names for the examples:
     * "<sbb-angular-docs-example>" and "SbbAngularDocsExample".
     * This will replace those placeholders with the names from the example metadata,
     * e.g. "<basic-button-example>" and "BasicButtonExample"
     */ private _replaceExamplePlaceholders ( data , fileName , fileContent , isTest ) { if ( fileName === 'src/index.html' || fileName === 'package.json' ) { fileContent = fileContent . replace ( /\${version}/g , libraryVersion ) ; } if ( fileName === 'src/index.html' ) { fileContent = fileContent . replace ( /sbb-angular-docs-example/g , data . selectorName ) . replace ( /\${title}/g , data . description ) ; } else if ( fileName === '.stackblitzrc' ) { fileContent = fileContent . replace ( /\${startCommand}/ , isTest ? 'turbo test' : 'turbo start' ) ; } else if ( fileName === 'src/app/app.module.ts' ) { const joinedComponentNames = data . componentNames . join ( ', ' ) ; fileContent = fileContent . replace ( /{ SbbAngularDocsExample }/g , `{ ${ joinedComponentNames } }` ) ; fileContent = fileContent . replace ( /declarations: \[SbbAngularDocsExample]/g , `declarations: [${ joinedComponentNames }]` ) ; fileContent = fileContent . replace ( /entryComponents: \[SbbAngularDocsExample]/g , `entryComponents: [${ joinedComponentNames }]` ) ; fileContent = fileContent . replace ( /bootstrap: \[SbbAngularDocsExample]/g , `bootstrap: [${ data . componentNames [ 0 ] }]` ) ; const dotIndex = data . indexFilename . lastIndexOf ( '.' ) ; const importFileName = data . indexFilename . slice ( 0 , dotIndex === - 1 ? undefined : dotIndex ) ; fileContent = fileContent . replace ( /sbb-angular-docs-example/g , importFileName ) ; } return fileContent ; } }