├── .gitattributes ├── examples └── cli-example │ ├── src │ ├── assets │ │ └── .gitkeep │ ├── app │ │ ├── app.component.css │ │ ├── shared │ │ │ ├── person-bio.component.ts │ │ │ ├── animated.component.sandbox.ts │ │ │ ├── counter.service.ts │ │ │ ├── person-bio.component.sandbox.ts │ │ │ ├── shared.module.ts │ │ │ ├── notice.component.ts │ │ │ ├── animated.component.ts │ │ │ └── notice.component.sandbox.ts │ │ ├── app.component.ts │ │ ├── feature3 │ │ │ ├── star-rating.component.sandbox.ts │ │ │ ├── feature3.module.ts │ │ │ └── star-rating.component.ts │ │ ├── feature1 │ │ │ ├── person-bio.component.other.sandbox.ts │ │ │ ├── person-bio.component.ts │ │ │ ├── person-bio.component.sandbox.ts │ │ │ ├── person-block.component.ts │ │ │ ├── person-details.component.ts │ │ │ ├── person-details.component.sandbox.ts │ │ │ └── feature1.module.ts │ │ ├── app.component.html │ │ ├── feature2 │ │ │ ├── feature2.module.ts │ │ │ ├── info-block.component.sandbox.ts │ │ │ └── info-block.component.ts │ │ └── app.module.ts │ ├── environments │ │ ├── environment.prod.ts │ │ └── environment.ts │ ├── styles.css │ ├── favicon.ico │ ├── __images_snapshots__ │ │ ├── app-shared-animated-component-sandbox-default-1000x800-snap.png │ │ ├── app-shared-animated-component-sandbox-default-401x400-snap.png │ │ ├── app-shared-notice-component-sandbox-long-text-1000x800-snap.png │ │ ├── app-shared-notice-component-sandbox-long-text-401x400-snap.png │ │ ├── app-shared-notice-component-sandbox-short-text-1000x800-snap.png │ │ ├── app-shared-notice-component-sandbox-short-text-401x400-snap.png │ │ ├── app-shared-person-bio-component-sandbox-default-401x400-snap.png │ │ ├── app-feature1-person-bio-component-sandbox-default-401x400-snap.png │ │ ├── app-shared-person-bio-component-sandbox-default-1000x800-snap.png │ │ ├── app-feature1-person-bio-component-sandbox-default-1000x800-snap.png │ │ ├── app-feature3-star-rating-component-sandbox-3-stars-1000x800-snap.png │ │ ├── app-feature3-star-rating-component-sandbox-3-stars-401x400-snap.png │ │ ├── app-shared-person-bio-component-sandbox-no-message-1000x800-snap.png │ │ ├── app-shared-person-bio-component-sandbox-no-message-401x400-snap.png │ │ ├── app-shared-notice-component-sandbox-in-a-green-container-1000x800-snap.png │ │ ├── app-shared-notice-component-sandbox-in-a-green-container-401x400-snap.png │ │ ├── app-shared-notice-component-sandbox-with-custom-provider-1000x800-snap.png │ │ ├── app-shared-notice-component-sandbox-with-custom-provider-401x400-snap.png │ │ ├── app-feature2-info-block-component-sandbox-A-general-example-401x400-snap.png │ │ ├── app-feature2-info-block-component-sandbox-A-warning-example-401x400-snap.png │ │ ├── app-feature2-info-block-component-sandbox-An-error-example-1000x800-snap.png │ │ ├── app-feature2-info-block-component-sandbox-An-error-example-401x400-snap.png │ │ ├── app-shared-notice-component-sandbox-position-absolute-test-1000x800-snap.png │ │ ├── app-shared-notice-component-sandbox-position-absolute-test-401x400-snap.png │ │ ├── app-feature1-person-details-component-sandbox-person-with-name-401x400-snap.png │ │ ├── app-feature2-info-block-component-sandbox-A-general-example-1000x800-snap.png │ │ ├── app-feature2-info-block-component-sandbox-A-warning-example-1000x800-snap.png │ │ └── app-feature1-person-details-component-sandbox-person-with-name-1000x800-snap.png │ ├── index.html │ ├── tslint.json │ ├── main.ts │ ├── test.ts │ └── polyfills.ts │ ├── .angular-playground │ ├── .gitignore │ ├── tsconfig.playground.json │ ├── main.playground.ts │ └── angular-playground.json │ ├── e2e │ ├── tsconfig.json │ ├── src │ │ ├── app.po.ts │ │ └── app.e2e-spec.ts │ └── protractor.conf.js │ ├── tsconfig.app.json │ ├── .editorconfig │ ├── tsconfig.spec.json │ ├── .browserslistrc │ ├── ngsw-config.json │ ├── .gitignore │ ├── tsconfig.json │ ├── README.md │ ├── karma.conf.js │ ├── package.json │ ├── tslint.json │ └── angular.json ├── projects ├── cli │ ├── test │ │ ├── files │ │ │ ├── from-dir-test │ │ │ │ ├── test.json │ │ │ │ ├── test.txt │ │ │ │ ├── test2.json │ │ │ │ ├── sub-dir │ │ │ │ │ └── other.csv │ │ │ │ └── node_modules │ │ │ │ │ ├── other.csv │ │ │ │ │ └── somedir │ │ │ │ │ └── other.csv │ │ │ ├── empty-config.json │ │ │ ├── angular-test-config.json │ │ │ ├── sandboxes-2 │ │ │ │ ├── example4.sandbox.ts │ │ │ │ ├── example5.sandbox.ts │ │ │ │ ├── example3.sandbox.ts │ │ │ │ ├── example6.sandbox.ts │ │ │ │ ├── example1.sandbox.ts │ │ │ │ └── example2.sandbox.ts │ │ │ ├── sandboxes │ │ │ │ ├── example4.sandbox.ts │ │ │ │ ├── example5.sandbox.ts │ │ │ │ ├── example3.sandbox.ts │ │ │ │ ├── example6.sandbox.ts │ │ │ │ ├── angular-playground.js │ │ │ │ ├── example1.sandbox.ts │ │ │ │ └── example2.sandbox.ts │ │ │ └── from-dir-test-multiple │ │ │ │ ├── test.ts │ │ │ │ └── sub-dir │ │ │ │ ├── test2.ts │ │ │ │ └── sub-dir-2 │ │ │ │ └── test3.ts │ │ ├── string-builder.spec.ts │ │ ├── from-dir.spec.ts │ │ ├── error-reporter.spec.ts │ │ └── configure.spec.ts │ ├── src │ │ ├── index.ts │ │ ├── string-builder.ts │ │ ├── start-watch.ts │ │ ├── check-errors │ │ │ ├── reporters │ │ │ │ ├── json-reporter.ts │ │ │ │ └── xml-reporter.ts │ │ │ └── verify-sandboxes.ts │ │ ├── serve-angular-cli.ts │ │ ├── utils.ts │ │ ├── from-dir.ts │ │ ├── build-angular-cli.ts │ │ ├── error-reporter.ts │ │ ├── run.ts │ │ └── build-sandboxes.ts │ └── tsconfig.json ├── schematics │ ├── .editorconfig │ ├── src │ │ ├── ng-add │ │ │ ├── files │ │ │ │ ├── angular-playground.json │ │ │ │ ├── tsconfig.playground.json │ │ │ │ ├── sandboxes.ts │ │ │ │ └── main.playground.ts │ │ │ └── index.ts │ │ ├── migrations │ │ │ ├── 8.0.0 │ │ │ │ ├── files │ │ │ │ │ ├── angular-playground.json │ │ │ │ │ ├── tsconfig.playground.json │ │ │ │ │ ├── sandboxes.ts │ │ │ │ │ └── main.playground.ts │ │ │ │ └── index.ts │ │ │ ├── 7.0.0 │ │ │ │ ├── files │ │ │ │ │ ├── tsconfig.playground.json │ │ │ │ │ └── main.playground.ts │ │ │ │ └── index.ts │ │ │ └── migrations.json │ │ ├── utils │ │ │ ├── paths.ts │ │ │ ├── npm-script.ts │ │ │ └── project.ts │ │ ├── sandbox │ │ │ ├── schema.ts │ │ │ ├── files │ │ │ │ └── __name@dasherize__.component.sandbox.ts │ │ │ ├── schema.json │ │ │ ├── index.ts │ │ │ └── index.spec.ts │ │ └── collection.json │ └── tsconfig.json ├── jest │ └── src │ │ └── jest-puppeteer.config.js └── playground │ ├── src │ ├── lib │ │ ├── core │ │ │ ├── drawer │ │ │ │ ├── drawer.component.html │ │ │ │ ├── drawer.component.ts │ │ │ │ └── drawer.component.css │ │ │ ├── shared │ │ │ │ ├── focus.directive.ts │ │ │ │ ├── sandboxes.ts │ │ │ │ ├── state.service.ts │ │ │ │ ├── fuzzy-search.function.ts │ │ │ │ ├── highlight-search-match.pipe.ts │ │ │ │ ├── url.service.ts │ │ │ │ └── levenshtein-distance.ts │ │ │ ├── logo │ │ │ │ └── logo.component.ts │ │ │ ├── pin │ │ │ │ ├── pin.component.ts │ │ │ │ └── pin.component.html │ │ │ ├── playground-common.module.ts │ │ │ ├── playground.module.ts │ │ │ ├── app.component.html │ │ │ ├── scenario │ │ │ │ └── scenario.component.ts │ │ │ └── app.component.css │ │ └── lib │ │ │ ├── middlewares.ts │ │ │ ├── app-state.ts │ │ │ ├── initialize-playground.ts │ │ │ └── api.ts │ ├── public-api.ts │ └── test.ts │ ├── ng-package.json │ ├── tsconfig.lib.prod.json │ ├── package.json │ ├── tsconfig.spec.json │ ├── tsconfig.lib.json │ ├── README.md │ └── karma.conf.js ├── assets └── angular-playground.png ├── azure-pipelines.yml ├── .editorconfig ├── KUDOS.md ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── tsconfig.json ├── LICENSE ├── angular.json ├── README.md ├── CONTRIBUTING.md └── package.json /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /examples/cli-example/src/assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/app.component.css: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /projects/cli/test/files/from-dir-test/test.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /projects/cli/test/files/from-dir-test/test.txt: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /projects/cli/test/files/from-dir-test/test2.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /projects/cli/test/files/from-dir-test/sub-dir/other.csv: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /projects/cli/test/files/from-dir-test/node_modules/other.csv: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /projects/schematics/.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | indent_size = 2 3 | -------------------------------------------------------------------------------- /examples/cli-example/.angular-playground/.gitignore: -------------------------------------------------------------------------------- 1 | sandboxes.ts 2 | -------------------------------------------------------------------------------- /projects/cli/test/files/from-dir-test/node_modules/somedir/other.csv: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /projects/cli/test/files/empty-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "angularCli": { 3 | 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /projects/jest/src/jest-puppeteer.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testPathIgnorePatterns: [] 3 | }; 4 | -------------------------------------------------------------------------------- /assets/angular-playground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/assets/angular-playground.png -------------------------------------------------------------------------------- /examples/cli-example/src/environments/environment.prod.ts: -------------------------------------------------------------------------------- 1 | export const environment = { 2 | production: true 3 | }; 4 | -------------------------------------------------------------------------------- /examples/cli-example/src/styles.css: -------------------------------------------------------------------------------- 1 | /* You can add global styles to this file, and also import other style files */ 2 | -------------------------------------------------------------------------------- /examples/cli-example/src/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/favicon.ico -------------------------------------------------------------------------------- /projects/playground/src/lib/core/drawer/drawer.component.html: -------------------------------------------------------------------------------- 1 | 4 | Menu 5 | 6 | -------------------------------------------------------------------------------- /projects/cli/test/files/angular-test-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceRoots": ["./src-path/"], 3 | "angularCli": { 4 | "appName": "llama" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /projects/cli/test/files/sandboxes-2/example4.sandbox.ts: -------------------------------------------------------------------------------- 1 | export default sandboxOf(ThreeDviewComponent).add('Default', { 2 | template: `` 3 | }); -------------------------------------------------------------------------------- /projects/cli/test/files/sandboxes/example4.sandbox.ts: -------------------------------------------------------------------------------- 1 | export default sandboxOf(ThreeDviewComponent).add('Default', { 2 | template: `` 3 | }); -------------------------------------------------------------------------------- /projects/playground/src/public-api.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Public API Surface of playground 3 | */ 4 | 5 | export * from './lib/core/playground.module'; 6 | export * from './lib/lib/api'; 7 | -------------------------------------------------------------------------------- /projects/schematics/src/ng-add/files/angular-playground.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceRoots": ["./<%= sourceRoots %>"], 3 | "angularCli": { 4 | "appName": "playground" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /projects/cli/test/files/from-dir-test-multiple/test.ts: -------------------------------------------------------------------------------- 1 | const foo = (bar: string) => { 2 | const n = bar.length; 3 | return Math.pow(n, 2); 4 | }; 5 | 6 | export default foo; 7 | -------------------------------------------------------------------------------- /projects/schematics/src/migrations/8.0.0/files/angular-playground.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceRoots": ["./<%= sourceRoots %>"], 3 | "angularCli": { 4 | "appName": "playground" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /projects/cli/test/files/from-dir-test-multiple/sub-dir/test2.ts: -------------------------------------------------------------------------------- 1 | const foo2 = (bar: string) => { 2 | const n = bar.length; 3 | return Math.pow(n, 2); 4 | }; 5 | 6 | export default foo2; 7 | -------------------------------------------------------------------------------- /projects/cli/test/files/sandboxes-2/example5.sandbox.ts: -------------------------------------------------------------------------------- 1 | export default sandboxOf(ThreeDviewComponent) 2 | .add('Default', { 3 | template: `` 4 | }); -------------------------------------------------------------------------------- /projects/cli/test/files/sandboxes/example5.sandbox.ts: -------------------------------------------------------------------------------- 1 | export default sandboxOf(ThreeDviewComponent) 2 | .add('Default', { 3 | template: `` 4 | }); -------------------------------------------------------------------------------- /projects/cli/test/files/from-dir-test-multiple/sub-dir/sub-dir-2/test3.ts: -------------------------------------------------------------------------------- 1 | const foo3 = (bar: string) => { 2 | const n = bar.length; 3 | return Math.pow(n, 2); 4 | }; 5 | 6 | export default foo3; 7 | -------------------------------------------------------------------------------- /projects/cli/test/files/sandboxes-2/example3.sandbox.ts: -------------------------------------------------------------------------------- 1 | export default sandboxOf(ThreeDviewComponent).add( 2 | 'Default', { 3 | template: `` 4 | } 5 | ); 6 | -------------------------------------------------------------------------------- /projects/cli/test/files/sandboxes/example3.sandbox.ts: -------------------------------------------------------------------------------- 1 | export default sandboxOf(ThreeDviewComponent).add( 2 | 'Default', { 3 | template: `` 4 | } 5 | ); 6 | -------------------------------------------------------------------------------- /projects/cli/test/files/sandboxes-2/example6.sandbox.ts: -------------------------------------------------------------------------------- 1 | export default sandboxOf(ThreeDviewComponent).add( 2 | 'Default', 3 | { 4 | template: `` 5 | } 6 | ); -------------------------------------------------------------------------------- /projects/cli/test/files/sandboxes/example6.sandbox.ts: -------------------------------------------------------------------------------- 1 | export default sandboxOf(ThreeDviewComponent).add( 2 | 'Default', 3 | { 4 | template: `` 5 | } 6 | ); -------------------------------------------------------------------------------- /projects/playground/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist/playground", 4 | "lib": { 5 | "entryFile": "src/public-api.ts" 6 | } 7 | } -------------------------------------------------------------------------------- /projects/schematics/src/utils/paths.ts: -------------------------------------------------------------------------------- 1 | export const constructPath = (parts: string[], isAbsolute = false) => { 2 | const filteredParts = parts.filter(part => !!part); 3 | return `${isAbsolute ? '/' : ''}${filteredParts.join('/')}`; 4 | }; 5 | -------------------------------------------------------------------------------- /projects/playground/tsconfig.lib.prod.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.lib.json", 3 | "compilerOptions": { 4 | "declarationMap": false 5 | }, 6 | "angularCompilerOptions": { 7 | "compilationMode": "partial" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-shared-animated-component-sandbox-default-1000x800-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-shared-animated-component-sandbox-default-1000x800-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-shared-animated-component-sandbox-default-401x400-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-shared-animated-component-sandbox-default-401x400-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-shared-notice-component-sandbox-long-text-1000x800-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-shared-notice-component-sandbox-long-text-1000x800-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-shared-notice-component-sandbox-long-text-401x400-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-shared-notice-component-sandbox-long-text-401x400-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-shared-notice-component-sandbox-short-text-1000x800-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-shared-notice-component-sandbox-short-text-1000x800-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-shared-notice-component-sandbox-short-text-401x400-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-shared-notice-component-sandbox-short-text-401x400-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-shared-person-bio-component-sandbox-default-401x400-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-shared-person-bio-component-sandbox-default-401x400-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-feature1-person-bio-component-sandbox-default-401x400-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-feature1-person-bio-component-sandbox-default-401x400-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-shared-person-bio-component-sandbox-default-1000x800-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-shared-person-bio-component-sandbox-default-1000x800-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-feature1-person-bio-component-sandbox-default-1000x800-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-feature1-person-bio-component-sandbox-default-1000x800-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-feature3-star-rating-component-sandbox-3-stars-1000x800-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-feature3-star-rating-component-sandbox-3-stars-1000x800-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-feature3-star-rating-component-sandbox-3-stars-401x400-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-feature3-star-rating-component-sandbox-3-stars-401x400-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-shared-person-bio-component-sandbox-no-message-1000x800-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-shared-person-bio-component-sandbox-no-message-1000x800-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-shared-person-bio-component-sandbox-no-message-401x400-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-shared-person-bio-component-sandbox-no-message-401x400-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/app/shared/person-bio.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'global-person-bio', 5 | template: `

A global person bio

` 6 | }) 7 | export class PersonBioComponent {} 8 | -------------------------------------------------------------------------------- /projects/cli/test/files/sandboxes/angular-playground.js: -------------------------------------------------------------------------------- 1 | function getSandbox(path) { 2 | /*GET_SANDBOX*/ 3 | /*END_GET_SANDBOX*/ 4 | } 5 | function getSandboxMenuItems() { 6 | /*GET_SANDBOX_MENU_ITEMS*/ 7 | return []; 8 | /*END_GET_SANDBOX_MENU_ITEMS*/ 9 | } 10 | -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-shared-notice-component-sandbox-in-a-green-container-1000x800-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-shared-notice-component-sandbox-in-a-green-container-1000x800-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-shared-notice-component-sandbox-in-a-green-container-401x400-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-shared-notice-component-sandbox-in-a-green-container-401x400-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-shared-notice-component-sandbox-with-custom-provider-1000x800-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-shared-notice-component-sandbox-with-custom-provider-1000x800-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-shared-notice-component-sandbox-with-custom-provider-401x400-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-shared-notice-component-sandbox-with-custom-provider-401x400-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'app-root', 5 | templateUrl: './app.component.html', 6 | styleUrls: ['./app.component.css'] 7 | }) 8 | export class AppComponent { 9 | title = 'app'; 10 | } 11 | -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-feature2-info-block-component-sandbox-A-general-example-401x400-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-feature2-info-block-component-sandbox-A-general-example-401x400-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-feature2-info-block-component-sandbox-A-warning-example-401x400-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-feature2-info-block-component-sandbox-A-warning-example-401x400-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-feature2-info-block-component-sandbox-An-error-example-1000x800-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-feature2-info-block-component-sandbox-An-error-example-1000x800-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-feature2-info-block-component-sandbox-An-error-example-401x400-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-feature2-info-block-component-sandbox-An-error-example-401x400-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-shared-notice-component-sandbox-position-absolute-test-1000x800-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-shared-notice-component-sandbox-position-absolute-test-1000x800-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-shared-notice-component-sandbox-position-absolute-test-401x400-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-shared-notice-component-sandbox-position-absolute-test-401x400-snap.png -------------------------------------------------------------------------------- /projects/playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-playground", 3 | "version": "0.0.1", 4 | "peerDependencies": { 5 | "@angular/common": "^14.0.0", 6 | "@angular/core": "^14.0.0" 7 | }, 8 | "dependencies": { 9 | "tslib": "^2.3.0" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-feature1-person-details-component-sandbox-person-with-name-401x400-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-feature1-person-details-component-sandbox-person-with-name-401x400-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-feature2-info-block-component-sandbox-A-general-example-1000x800-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-feature2-info-block-component-sandbox-A-general-example-1000x800-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-feature2-info-block-component-sandbox-A-warning-example-1000x800-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-feature2-info-block-component-sandbox-A-warning-example-1000x800-snap.png -------------------------------------------------------------------------------- /examples/cli-example/src/app/shared/animated.component.sandbox.ts: -------------------------------------------------------------------------------- 1 | import { sandboxOf } from 'angular-playground'; 2 | import { AnimatedComponent } from './animated.component'; 3 | export default sandboxOf(AnimatedComponent) 4 | .add('default', { 5 | template: `` 6 | }); 7 | -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | 2 | trigger: 3 | - master 4 | 5 | pool: 6 | vmImage: ubuntu-latest 7 | 8 | steps: 9 | - task: NodeTool@0 10 | inputs: 11 | versionSpec: '16.x' 12 | displayName: 'Install Node.js' 13 | 14 | - script: | 15 | npm install 16 | npm test 17 | displayName: 'npm test' 18 | -------------------------------------------------------------------------------- /examples/cli-example/src/__images_snapshots__/app-feature1-person-details-component-sandbox-person-with-name-1000x800-snap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SoCreate/angular-playground/HEAD/examples/cli-example/src/__images_snapshots__/app-feature1-person-details-component-sandbox-person-with-name-1000x800-snap.png -------------------------------------------------------------------------------- /examples/cli-example/e2e/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/e2e", 5 | "module": "commonjs", 6 | "target": "es2018", 7 | "types": [ 8 | "jasmine", 9 | "jasminewd2", 10 | "node" 11 | ] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/feature3/star-rating.component.sandbox.ts: -------------------------------------------------------------------------------- 1 | import { sandboxOf } from 'angular-playground'; 2 | import { StarRatingComponent } from './star-rating.component'; 3 | export default sandboxOf(StarRatingComponent) 4 | .add('3 stars', {template: ''}); 5 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/shared/counter.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from "@angular/core"; 2 | @Injectable() 3 | export class CounterService { 4 | private _count; 5 | 6 | constructor() { 7 | this._count = 0; 8 | } 9 | 10 | increment() { 11 | return ++this._count; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/cli-example/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts" 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 4 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /projects/cli/src/index.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import { run } from './run'; 3 | const chalk = require('chalk'); 4 | 5 | (async () => { 6 | try { 7 | await run(); 8 | } catch (err) { 9 | console.error(chalk.red(err.message)); 10 | process.exit(1); 11 | } 12 | })(); 13 | 14 | -------------------------------------------------------------------------------- /examples/cli-example/.editorconfig: -------------------------------------------------------------------------------- 1 | # Editor configuration, see https://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | 11 | [*.md] 12 | max_line_length = off 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /projects/playground/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/spec", 5 | "types": [ 6 | "jasmine" 7 | ] 8 | }, 9 | "files": [ 10 | "src/test.ts" 11 | ], 12 | "include": [ 13 | "**/*.spec.ts", 14 | "**/*.d.ts" 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /projects/cli/src/string-builder.ts: -------------------------------------------------------------------------------- 1 | export class StringBuilder { 2 | private lines: string[] = []; 3 | 4 | addLine(line: string) { 5 | this.lines.push(line); 6 | } 7 | 8 | dump(): string { 9 | const data = this.lines.join('\n') + '\n'; 10 | this.lines = []; 11 | return data; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/feature1/person-bio.component.other.sandbox.ts: -------------------------------------------------------------------------------- 1 | import {sandboxOf} from 'angular-playground'; 2 | import {PersonBioComponent} from './person-bio.component'; 3 | export default sandboxOf(PersonBioComponent, { 4 | label: 'feature1' 5 | }) 6 | .add('a special case', {template: `

Special Bio

`}); 7 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/feature1/person-bio.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ChangeDetectionStrategy } from '@angular/core'; 2 | @Component({ 3 | selector: 'ex-person-bio', 4 | template: `A big person bio`, 5 | styles: [`:host { display: block; }`], 6 | changeDetection: ChangeDetectionStrategy.OnPush 7 | }) 8 | export class PersonBioComponent {} 9 | -------------------------------------------------------------------------------- /projects/schematics/src/sandbox/schema.ts: -------------------------------------------------------------------------------- 1 | export interface Schema { 2 | /** 3 | * The name of the component. 4 | */ 5 | name: string; 6 | 7 | /** 8 | * The path to create the sandbox. 9 | */ 10 | path?: string; 11 | 12 | /** 13 | * Should the sandbox be created in the component's directory. 14 | */ 15 | flat: boolean; 16 | } 17 | -------------------------------------------------------------------------------- /KUDOS.md: -------------------------------------------------------------------------------- 1 | # Kudos 2 | 3 | * Thanks goes out for the push pin icon from 4 | 5 | 6 | * Thanks goes out for the awesome fuzzy search code by [Nicolás Bevacqua](https://twitter.com/nzgb) 7 | 8 | 9 | * Thanks goes out for Windows/Unix path normalization from -------------------------------------------------------------------------------- /projects/schematics/src/sandbox/files/__name@dasherize__.component.sandbox.ts: -------------------------------------------------------------------------------- 1 | import { sandboxOf } from 'angular-playground'; 2 | import { <%= classify(name) %>Component } from '<%= flat ? '.' : '..' %>/<%= name %>.component'; 3 | 4 | export default sandboxOf(<%= classify(name) %>Component) 5 | .add('default', { 6 | template: `<<%= selector %>>>` 7 | }); 8 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/app.component.html: -------------------------------------------------------------------------------- 1 |

The host app

2 | 3 |
4 | 5 |
6 | Note that this is a sample app 7 | Host app loaded 8 | Ready for showtime! 9 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/feature1/person-bio.component.sandbox.ts: -------------------------------------------------------------------------------- 1 | import {sandboxOf} from 'angular-playground'; 2 | import {PersonBioComponent} from './person-bio.component'; 3 | export default sandboxOf(PersonBioComponent, { 4 | uniqueId: '77d3193c-ffa9-4594-a688-699d0c7dd20e', 5 | label: 'feature1' 6 | }) 7 | .add('default', {template:``}); 8 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/feature3/feature3.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { StarRatingComponent } from './star-rating.component'; 4 | 5 | @NgModule({ 6 | imports: [ 7 | CommonModule 8 | ], 9 | declarations: [StarRatingComponent] 10 | }) 11 | export class Feature3Module { 12 | } 13 | -------------------------------------------------------------------------------- /examples/cli-example/.angular-playground/tsconfig.playground.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "./main.playground.ts", 9 | "../src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "../src/**/*.d.ts", 13 | "../src/**/*.sandbox.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /examples/cli-example/e2e/src/app.po.ts: -------------------------------------------------------------------------------- 1 | import { browser, by, element } from 'protractor'; 2 | 3 | export class AppPage { 4 | navigateTo(): Promise { 5 | return browser.get(browser.baseUrl) as Promise; 6 | } 7 | 8 | getTitleText(): Promise { 9 | return element(by.css('app-root .content span')).getText() as Promise; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /projects/schematics/src/migrations/7.0.0/files/tsconfig.playground.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "src/main.playground.ts", 9 | "src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "src/**/*.d.ts", 13 | "src/**/*.sandbox.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /projects/schematics/src/ng-add/files/tsconfig.playground.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "./main.playground.ts", 9 | "../src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "../src/**/*.d.ts", 13 | "../src/**/*.sandbox.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /examples/cli-example/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CliExample 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/cli-example/tsconfig.spec.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "./out-tsc/spec", 5 | "types": [ 6 | "jasmine", 7 | "node" 8 | ] 9 | }, 10 | "files": [ 11 | "src/test.ts", 12 | "src/polyfills.ts" 13 | ], 14 | "include": [ 15 | "src/**/*.spec.ts", 16 | "src/**/*.d.ts" 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /projects/schematics/src/migrations/8.0.0/files/tsconfig.playground.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../out-tsc/app", 5 | "types": [] 6 | }, 7 | "files": [ 8 | "./main.playground.ts", 9 | "../src/polyfills.ts" 10 | ], 11 | "include": [ 12 | "../src/**/*.d.ts", 13 | "../src/**/*.sandbox.ts" 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /projects/schematics/src/ng-add/files/sandboxes.ts: -------------------------------------------------------------------------------- 1 | // DO NOT MODIFY.... This file is filled in via the Playground CLI. 2 | 3 | export class SandboxesDefined { 4 | getSandbox(path: string): any { 5 | /*GET_SANDBOX*/ 6 | /*END_GET_SANDBOX*/ 7 | } 8 | getSandboxMenuItems(): any { 9 | /*GET_SANDBOX_MENU_ITEMS*/ 10 | return []; 11 | /*END_GET_SANDBOX_MENU_ITEMS*/ 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/feature2/feature2.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { InfoBlockComponent } from './info-block.component'; 4 | 5 | @NgModule({ 6 | imports: [ 7 | CommonModule 8 | ], 9 | declarations: [InfoBlockComponent], 10 | exports: [InfoBlockComponent] 11 | }) 12 | export class Feature2Module { 13 | } 14 | -------------------------------------------------------------------------------- /projects/cli/test/files/sandboxes-2/example1.sandbox.ts: -------------------------------------------------------------------------------- 1 | export default sandboxOf(ExampleComponent, { 2 | imports: [ReactiveFormsModule] 3 | }) 4 | .add('Default', { 5 | template: `` 6 | }) 7 | .add('With Wrapper', { 8 | template: ` 9 |
10 | 11 |
12 | ` 13 | }); 14 | -------------------------------------------------------------------------------- /projects/cli/test/files/sandboxes/example1.sandbox.ts: -------------------------------------------------------------------------------- 1 | export default sandboxOf(ExampleComponent, { 2 | imports: [ReactiveFormsModule] 3 | }) 4 | .add('Default', { 5 | template: `` 6 | }) 7 | .add('With Wrapper', { 8 | template: ` 9 |
10 | 11 |
12 | ` 13 | }); 14 | -------------------------------------------------------------------------------- /projects/schematics/src/migrations/8.0.0/files/sandboxes.ts: -------------------------------------------------------------------------------- 1 | // DO NOT MODIFY.... This file is filled in via the Playground CLI. 2 | 3 | export class SandboxesDefined { 4 | getSandbox(path: string): any { 5 | /*GET_SANDBOX*/ 6 | /*END_GET_SANDBOX*/ 7 | } 8 | getSandboxMenuItems(): any { 9 | /*GET_SANDBOX_MENU_ITEMS*/ 10 | return []; 11 | /*END_GET_SANDBOX_MENU_ITEMS*/ 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/feature3/star-rating.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | @Component({ 3 | selector: 'ex-star-rating', 4 | template: `*` 5 | }) 6 | export class StarRatingComponent { 7 | starNumbers; 8 | 9 | @Input() set stars(value: number) { 10 | this.starNumbers = Array.from(Array(value),(x,i)=>i); 11 | }; 12 | } 13 | -------------------------------------------------------------------------------- /examples/cli-example/src/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tslint.json", 3 | "rules": { 4 | "directive-selector": [ 5 | true, 6 | "attribute", 7 | "app", 8 | "camelCase" 9 | ], 10 | "component-selector": [ 11 | true, 12 | "element", 13 | "app", 14 | "kebab-case" 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /projects/playground/src/lib/core/shared/focus.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, Input, ElementRef } from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: '[apFocus]', 5 | }) 6 | export class FocusDirective { 7 | @Input() set apFocus(value: any) { 8 | if (value) { 9 | this.elementRef.nativeElement.focus(); 10 | } 11 | } 12 | 13 | constructor(private elementRef: ElementRef) {} 14 | } 15 | -------------------------------------------------------------------------------- /examples/cli-example/src/main.ts: -------------------------------------------------------------------------------- 1 | import { enableProdMode } from '@angular/core'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | 4 | import { AppModule } from './app/app.module'; 5 | import { environment } from './environments/environment'; 6 | 7 | if (environment.production) { 8 | enableProdMode(); 9 | } 10 | 11 | platformBrowserDynamic().bootstrapModule(AppModule) 12 | .catch(err => console.log(err)); 13 | -------------------------------------------------------------------------------- /examples/cli-example/src/environments/environment.ts: -------------------------------------------------------------------------------- 1 | // The file contents for the current environment will overwrite these during build. 2 | // The build system defaults to the dev environment which uses `environment.ts`, but if you do 3 | // `ng build --env=prod` then `environment.prod.ts` will be used instead. 4 | // The list of which env maps to which file can be found in `.angular-cli.json`. 5 | 6 | export const environment = { 7 | production: false 8 | }; 9 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/shared/person-bio.component.sandbox.ts: -------------------------------------------------------------------------------- 1 | import { PersonBioComponent } from './person-bio.component'; 2 | import { sandboxOf } from 'angular-playground'; 3 | 4 | export default sandboxOf(PersonBioComponent, {label:'shared'}) 5 | .add('no message', { 6 | template:`` 7 | }) 8 | .add('default', { 9 | template:`Some globally accepted info here!` 10 | }); 11 | -------------------------------------------------------------------------------- /projects/playground/src/lib/core/drawer/drawer.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, EventEmitter, Output } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'ap-drawer', 5 | templateUrl: './drawer.component.html', 6 | styleUrls: ['./drawer.component.css'], 7 | }) 8 | export class DrawerComponent { 9 | @Output() openCommandBarClick = new EventEmitter(); 10 | 11 | openCommandBar() { 12 | this.openCommandBarClick.emit(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /projects/playground/src/lib/core/shared/sandboxes.ts: -------------------------------------------------------------------------------- 1 | // This file is filled in via the Playground CLI. 2 | // Empty functions are the expected initial value. 3 | 4 | export abstract class Sandboxes { 5 | getSandbox(path: string): any { 6 | /*GET_SANDBOX*/ 7 | /*END_GET_SANDBOX*/ 8 | } 9 | getSandboxMenuItems(): any { 10 | /*GET_SANDBOX_MENU_ITEMS*/ 11 | return []; 12 | /*END_GET_SANDBOX_MENU_ITEMS*/ 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /projects/cli/test/files/sandboxes-2/example2.sandbox.ts: -------------------------------------------------------------------------------- 1 | export default sandboxOf(Example2Component, { 2 | imports: [], 3 | declarations: [], 4 | label: 'test' 5 | }) 6 | .add('Other', { 7 | template: `` 8 | }) 9 | // .add('Commented out', { 10 | // template: `` 11 | // }) 12 | .add('An other!', { 13 | template: `` 14 | }); 15 | -------------------------------------------------------------------------------- /projects/cli/test/files/sandboxes/example2.sandbox.ts: -------------------------------------------------------------------------------- 1 | export default sandboxOf(Example2Component, { 2 | imports: [], 3 | declarations: [], 4 | label: 'test' 5 | }) 6 | .add('Other', { 7 | template: `` 8 | }) 9 | // .add('Commented out', { 10 | // template: `` 11 | // }) 12 | .add('An other!', { 13 | template: `` 14 | }); 15 | -------------------------------------------------------------------------------- /projects/schematics/src/migrations/migrations.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../../node_modules/@angular-devkit/schematics/collection-schema.json", 3 | "schematics": { 4 | "migration-01": { 5 | "description": "Upgrade to version 7.0.0", 6 | "version": "6.2.0", 7 | "factory": "./7.0.0/index" 8 | }, 9 | "migration-02": { 10 | "description": "Upgrade to version 8.0.0", 11 | "version": "7.1.1", 12 | "factory": "./8.0.0/index" 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /projects/schematics/src/ng-add/files/main.playground.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | import { PlaygroundModule } from 'angular-playground'; 3 | import { SandboxesDefined } from './sandboxes'; 4 | 5 | platformBrowserDynamic().bootstrapModule(PlaygroundModule 6 | .configure({ 7 | selector: 'app-root', 8 | overlay: false, 9 | modules: [], 10 | sandboxesDefined: SandboxesDefined 11 | })) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /examples/cli-example/.browserslistrc: -------------------------------------------------------------------------------- 1 | # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. 2 | # For additional information regarding the format and rule options, please see: 3 | # https://github.com/browserslist/browserslist#queries 4 | 5 | # You can see what browsers were selected by your queries by running: 6 | # npx browserslist 7 | 8 | > 0.5% 9 | last 2 versions 10 | Firefox ESR 11 | not dead 12 | not IE 9-11 # For IE 9-11 support, remove 'not'. 13 | -------------------------------------------------------------------------------- /projects/schematics/src/migrations/8.0.0/files/main.playground.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | import { PlaygroundModule } from 'angular-playground'; 3 | import { SandboxesDefined } from './sandboxes'; 4 | 5 | platformBrowserDynamic().bootstrapModule(PlaygroundModule 6 | .configure({ 7 | selector: 'app-root', 8 | overlay: false, 9 | modules: [], 10 | sandboxesDefined: SandboxesDefined 11 | })) 12 | .catch(err => console.error(err)); 13 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/feature1/person-block.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ChangeDetectionStrategy } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'ex-person-block', 5 | template: ``, 6 | changeDetection: ChangeDetectionStrategy.OnPush 7 | }) 8 | export class PersonBlockComponent { 9 | person; 10 | 11 | constructor() { 12 | this.person = { fullName: 'Justin Schwartzenberger', twitterHandle: 'schwarty'}; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /examples/cli-example/ngsw-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "index": "/index.html", 3 | "assetGroups": [{ 4 | "name": "app", 5 | "installMode": "prefetch", 6 | "resources": { 7 | "files": [ 8 | "/favicon.ico", 9 | "/index.html", 10 | "/*.css", 11 | "/*.js" 12 | ] 13 | } 14 | }, { 15 | "name": "assets", 16 | "installMode": "lazy", 17 | "updateMode": "prefetch", 18 | "resources": { 19 | "files": [ 20 | "/assets/**" 21 | ] 22 | } 23 | }] 24 | } -------------------------------------------------------------------------------- /projects/cli/src/start-watch.ts: -------------------------------------------------------------------------------- 1 | import { resolve as resolvePath } from 'path'; 2 | import watch from 'node-watch'; 3 | 4 | export function startWatch(sourceRoots: string[], cb: Function) { 5 | const filter = (fn: Function) => { 6 | return (evt: string, filename: string) => { 7 | if (!/node_modules/.test(filename) && /\.sandbox.ts$/.test(filename)) { 8 | fn(filename); 9 | } 10 | }; 11 | }; 12 | 13 | sourceRoots.forEach(sourceRoot => 14 | watch(resolvePath(sourceRoot), { recursive: true }, filter(cb))); 15 | } 16 | -------------------------------------------------------------------------------- /projects/cli/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "", 4 | "declaration": false, 5 | "lib": ["es6", "dom"], 6 | "module": "commonjs", 7 | "moduleResolution": "node", 8 | "allowSyntheticDefaultImports": true, 9 | "outDir": "../../dist/bin", 10 | "sourceMap": false, 11 | "target": "es2015", 12 | "newLine": "LF", 13 | "typeRoots": [ 14 | "../../node_modules/@types" 15 | ], 16 | "types": ["jest"] 17 | }, 18 | "files": [ 19 | "src/index.ts" 20 | ], 21 | "exclude": [ 22 | "**/*.spec.ts", 23 | "**/test/**" 24 | ] 25 | } 26 | -------------------------------------------------------------------------------- /projects/playground/src/lib/lib/middlewares.ts: -------------------------------------------------------------------------------- 1 | import { InjectionToken, Type } from '@angular/core'; 2 | import { Sandboxes } from "../core/shared/sandboxes"; 3 | import { Observable } from 'rxjs'; 4 | 5 | export interface Configuration { 6 | selector?: string; 7 | modules?: any[]; 8 | overlay?: boolean; 9 | sandboxesDefined: Type; 10 | htmlTitle?: string; 11 | } 12 | 13 | export interface Middleware { 14 | selector?: string; 15 | modules?: any[]; 16 | overlay?: boolean; 17 | } 18 | 19 | export const MIDDLEWARE = new InjectionToken>('middleware'); 20 | -------------------------------------------------------------------------------- /examples/cli-example/.angular-playground/main.playground.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | import { PlaygroundModule } from 'angular-playground'; 3 | import { SandboxesDefined } from './sandboxes'; 4 | import { BrowserAnimationsModule } from "@angular/platform-browser/animations"; 5 | 6 | platformBrowserDynamic().bootstrapModule(PlaygroundModule 7 | .configure({ 8 | selector: 'app-root', 9 | overlay: true, 10 | modules: [ 11 | BrowserAnimationsModule 12 | ], 13 | sandboxesDefined: SandboxesDefined 14 | })) 15 | .catch(err => console.error(err)); 16 | -------------------------------------------------------------------------------- /projects/playground/src/lib/core/logo/logo.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'ap-logo', 5 | templateUrl: './logo.component.html', 6 | styles: [` 7 | .command-bar__logo { 8 | height: 30px; 9 | width: 140px; 10 | } 11 | 12 | .command-bar__logo__box { 13 | fill: rgba(255, 255, 255, .1); 14 | transition: fill 0.2s ease-out; 15 | } 16 | 17 | .command-bar__logo__letter { 18 | fill: rgba(255, 255, 255, .5); 19 | transition: fill 0.2s ease-out; 20 | } 21 | `], 22 | }) 23 | export class LogoComponent {} 24 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/feature1/person-details.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ChangeDetectionStrategy, Input } from '@angular/core'; 2 | @Component({ 3 | selector: 'ex-person-details', 4 | template: ` 5 | {{person.fullName}} ({{person.twitterHandle}}) 6 | 7 | Leave a comment: 8 | `, 9 | styles: [` 10 | :host{ font-weight: bold;} 11 | :host input { width: 98%; } 12 | `], 13 | changeDetection: ChangeDetectionStrategy.OnPush 14 | }) 15 | export class PersonDetailsComponent { 16 | @Input() person; 17 | } 18 | -------------------------------------------------------------------------------- /projects/playground/src/lib/core/pin/pin.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'ap-pin', 5 | templateUrl: './pin.component.html', 6 | styles: [` 7 | .command-bar__scenario-icon { 8 | display: inline-block; 9 | fill: white; 10 | height: 20px; 11 | margin: 2px 6px 0 0; 12 | opacity: .2; 13 | width: 20px; 14 | } 15 | 16 | .command-bar__scenario-icon--selected { 17 | fill: #0097fb; 18 | opacity: 1; 19 | } 20 | `], 21 | }) 22 | export class PinComponent { 23 | @Input() selected: boolean; 24 | } 25 | -------------------------------------------------------------------------------- /projects/cli/test/string-builder.spec.ts: -------------------------------------------------------------------------------- 1 | import { StringBuilder } from '../src/string-builder'; 2 | 3 | describe('StringBuilder', () => { 4 | it('should append newlines to each added line', () => { 5 | const builder = new StringBuilder(); 6 | builder.addLine('test'); 7 | builder.addLine('test2'); 8 | expect(builder.dump()).toBe('test\ntest2\n'); 9 | }); 10 | 11 | it('should clear out internal data structure after dumping', () => { 12 | const builder = new StringBuilder(); 13 | builder.addLine('test'); 14 | builder.dump(); 15 | expect(builder.dump()).toBe('\n'); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /projects/schematics/src/collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../../node_modules/@angular-devkit/schematics/collection-schema.json", 3 | "schematics": { 4 | "sandbox": { 5 | "description": "Creates a sandbox file for component in given path", 6 | "factory": "./sandbox", 7 | "schema": "./sandbox/schema.json" 8 | }, 9 | "ng-add": { 10 | "description": "Adds Angular Playground to an application", 11 | "factory": "./ng-add" 12 | }, 13 | "migration-01": { 14 | "description": "Upgrade to version 7.0.0", 15 | "version": "6.2.0", 16 | "factory": "./migrations/7.0.0/index" 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/shared/shared.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { NoticeComponent } from './notice.component'; 4 | import { CounterService } from './counter.service'; 5 | import { PersonBioComponent } from './person-bio.component'; 6 | import { AnimatedComponent } from "./animated.component"; 7 | 8 | @NgModule({ 9 | imports: [CommonModule], 10 | declarations: [ 11 | NoticeComponent, 12 | PersonBioComponent, 13 | AnimatedComponent 14 | ], 15 | exports: [NoticeComponent], 16 | providers: [CounterService] 17 | }) 18 | export class SharedModule { 19 | } 20 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/shared/notice.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ChangeDetectionStrategy } from '@angular/core'; 2 | import { CounterService } from './counter.service'; 3 | @Component({ 4 | selector: 'ex-notice', 5 | template: ` [notice #{{count}}]`, 6 | styles: [` 7 | :host { display: block; color: green; font-style: italic; } 8 | :host-context(.green) { color: gray; } 9 | `], 10 | changeDetection: ChangeDetectionStrategy.OnPush 11 | }) 12 | export class NoticeComponent { 13 | count; 14 | 15 | constructor(public counterService: CounterService) { 16 | this.count = counterService.increment(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /projects/playground/tsconfig.lib.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "outDir": "../../out-tsc/lib", 5 | "target": "es2020", 6 | "declaration": true, 7 | "declarationMap": true, 8 | "inlineSources": true, 9 | "types": [], 10 | "lib": [ 11 | "dom", 12 | "es2020" 13 | ] 14 | }, 15 | "angularCompilerOptions": { 16 | "skipTemplateCodegen": true, 17 | "strictMetadataEmit": true, 18 | "enableResourceInlining": true 19 | }, 20 | "exclude": [ 21 | "src/test.ts", 22 | "**/*.spec.ts" 23 | ] 24 | } 25 | -------------------------------------------------------------------------------- /projects/schematics/src/utils/npm-script.ts: -------------------------------------------------------------------------------- 1 | import { Tree } from '@angular-devkit/schematics'; 2 | 3 | /** 4 | * Adds a script to the package.json 5 | */ 6 | export function addNpmScriptToPackageJson( 7 | host: Tree, 8 | name: string, 9 | command: string, 10 | ): Tree { 11 | if (host.exists('package.json')) { 12 | const sourceText = host.read('package.json')!.toString('utf-8'); 13 | const json = JSON.parse(sourceText); 14 | if (!json.scripts) { 15 | json.scripts = {}; 16 | } 17 | 18 | if (!json.scripts[name]) { 19 | json.scripts[name] = command; 20 | } 21 | 22 | host.overwrite('package.json', JSON.stringify(json, null, 2)); 23 | } 24 | 25 | return host; 26 | } 27 | -------------------------------------------------------------------------------- /examples/cli-example/.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | 8 | # dependencies 9 | /node_modules 10 | 11 | # IDEs and editors 12 | /.idea 13 | .project 14 | .classpath 15 | .c9/ 16 | *.launch 17 | .settings/ 18 | *.sublime-workspace 19 | 20 | # IDE - VSCode 21 | .vscode/* 22 | !.vscode/settings.json 23 | !.vscode/tasks.json 24 | !.vscode/launch.json 25 | !.vscode/extensions.json 26 | 27 | # misc 28 | /.angular/cache 29 | /.sass-cache 30 | /connect.lock 31 | /coverage 32 | /libpeerconnection.log 33 | npm-debug.log 34 | yarn-error.log 35 | testem.log 36 | /typings 37 | 38 | # System Files 39 | .DS_Store 40 | Thumbs.db 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Versions 4 | ``` 5 | 8 | ``` 9 | 10 | ``` 11 | 14 | ``` 15 | 16 | ### Repro steps 17 | 22 | * Step 1 23 | * Step 2 24 | * Step 3 25 | 26 | ### Observed Behavior 27 | ``` 28 | 29 | ``` 30 | 31 | ### Desired Behavior 32 | ``` 33 | 36 | ``` 37 | 38 | ### Any other details that may be useful (optional) 39 | -------------------------------------------------------------------------------- /examples/cli-example/e2e/src/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { AppPage } from './app.po'; 2 | import { browser, logging } from 'protractor'; 3 | 4 | describe('workspace-project App', () => { 5 | let page: AppPage; 6 | 7 | beforeEach(() => { 8 | page = new AppPage(); 9 | }); 10 | 11 | it('should display welcome message', () => { 12 | page.navigateTo(); 13 | expect(page.getTitleText()).toEqual('test-ang-nine app is running!'); 14 | }); 15 | 16 | afterEach(async () => { 17 | // Assert that there are no errors emitted from the browser 18 | const logs = await browser.manage().logs().get(logging.Type.BROWSER); 19 | expect(logs).not.toContain(jasmine.objectContaining({ 20 | level: logging.Level.SEVERE, 21 | } as logging.Entry)); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /examples/cli-example/.angular-playground/angular-playground.json: -------------------------------------------------------------------------------- 1 | { 2 | "sourceRoots": ["./src"], 3 | "angularCli": { 4 | "appName": "playground" 5 | }, 6 | "viewportSizes": [ 7 | { 8 | "width": 401, 9 | "height": 400 10 | }, 11 | { 12 | "width": 1000, 13 | "height": 800 14 | } 15 | ], 16 | "imageSnapshotConfig": { 17 | "customDiffConfig": { 18 | "threshold": 0.1 19 | }, 20 | "diffDirection": "vertical", 21 | "noColors": true 22 | }, 23 | "visualRegressionIgnore": [ 24 | { 25 | "regex": "app-feature1", 26 | "flags": "g" 27 | }, 28 | { 29 | "regex": "app.+?shared.+?emoji" 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /examples/cli-example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "es2020", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2020", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2020", 19 | "dom" 20 | ], 21 | "paths": { 22 | "@angular/*": [ 23 | "./node_modules/@angular/*" 24 | ] 25 | } 26 | }, 27 | "angularCompilerOptions": { 28 | "fullTemplateTypeCheck": true, 29 | "strictInjectionParameters": true 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /projects/schematics/src/sandbox/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/schema", 3 | "$id": "SchematicsPlaygroundSandbox", 4 | "title": "Angular Playground's Sandbox", 5 | "type": "object", 6 | "properties": { 7 | "name": { 8 | "description": "The name of the component to add the sandbox to.", 9 | "type": "string", 10 | "$default": { 11 | "$source": "argv", 12 | "index": 0 13 | } 14 | }, 15 | "path": { 16 | "type": "string", 17 | "format": "path", 18 | "description": "The path to create the sandbox.", 19 | "visible": false 20 | }, 21 | "flat": { 22 | "type": "boolean", 23 | "description": "Flag to indicate if a dir is created.", 24 | "default": false 25 | } 26 | }, 27 | "required": [] 28 | } 29 | -------------------------------------------------------------------------------- /projects/playground/src/lib/core/shared/state.service.ts: -------------------------------------------------------------------------------- 1 | export class StateService { 2 | filter: string; 3 | filterKey = 'angularPlayground.filter'; 4 | 5 | constructor() { 6 | this.filter = sessionStorage.getItem(this.filterKey); 7 | sessionStorage.removeItem(this.filterKey); 8 | 9 | const beforeUnload = () => { 10 | sessionStorage.setItem(this.filterKey, emptyStringIfNull(this.filter)); 11 | return 'unload'; 12 | }; 13 | window.addEventListener('beforeunload', beforeUnload); 14 | } 15 | 16 | getFilter() { 17 | return this.filter; 18 | } 19 | 20 | setFilter(value: string) { 21 | this.filter = value; 22 | } 23 | } 24 | 25 | function emptyStringIfNull(value: string) { 26 | return value ? value : ''; 27 | } 28 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/feature1/person-details.component.sandbox.ts: -------------------------------------------------------------------------------- 1 | import { sandboxOf } from 'angular-playground'; 2 | import { PersonDetailsComponent } from './person-details.component'; 3 | import { PersonBioComponent } from './person-bio.component'; 4 | 5 | export default sandboxOf(PersonDetailsComponent, { 6 | label:'feature1', 7 | declarations: [PersonBioComponent] 8 | }) 9 | .add('person with name and twitter', { 10 | template: '', 11 | context: { 12 | person: { 13 | fullName: 'Tom Hardy', 14 | twitterHandle: 'mjones' 15 | } 16 | } 17 | }) 18 | .add('person with name', { 19 | template: '', 20 | context: {person: {fullName: 'Mike Jones'}} 21 | }); 22 | -------------------------------------------------------------------------------- /examples/cli-example/README.md: -------------------------------------------------------------------------------- 1 | # Playground Angular CLI Example 2 | 3 | ## Development: 4 | 5 | Install dependencies: 6 | ``` 7 | npm i 8 | ``` 9 | Build angular-playground package and link it globally 10 | ``` 11 | npm run clean 12 | npm run build 13 | npm link 14 | ``` 15 | 16 | Install dependencies in example project 17 | ``` 18 | cd examples/cli-example/ 19 | npm i 20 | npm link angular-playground 21 | ``` 22 | 23 | Run the example App 24 | ``` 25 | npm run playground 26 | ``` 27 | # 28 | 29 | To make changes to angular playground source code and see the change you must stop the playground 30 | from running and rebuild the angular-playground then run the example app again. 31 | # 32 | 33 | Rebuild Playground 34 | ``` 35 | Ctrl-C (stop playground) 36 | cd ../.. 37 | npm run rebuild 38 | ``` 39 | 40 | Run the example App 41 | ``` 42 | npm run playground 43 | ``` 44 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See http://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # compiled output 4 | /dist 5 | /tmp 6 | /out-tsc 7 | /_stage 8 | # Only exists if Bazel was run 9 | /bazel-out 10 | 11 | # dependencies 12 | /node_modules 13 | 14 | # profiling files 15 | chrome-profiler-events*.json 16 | speed-measure-plugin*.json 17 | 18 | # IDEs and editors 19 | /.idea 20 | .project 21 | .classpath 22 | .c9/ 23 | *.launch 24 | .settings/ 25 | *.sublime-workspace 26 | 27 | # IDE - VSCode 28 | .vscode/* 29 | !.vscode/settings.json 30 | !.vscode/tasks.json 31 | !.vscode/launch.json 32 | !.vscode/extensions.json 33 | .history/* 34 | 35 | # misc 36 | /.angular/cache 37 | /.sass-cache 38 | /connect.lock 39 | /coverage 40 | /libpeerconnection.log 41 | npm-debug.log 42 | yarn-error.log 43 | testem.log 44 | /typings 45 | 46 | # System Files 47 | .DS_Store 48 | Thumbs.db 49 | -------------------------------------------------------------------------------- /projects/playground/src/lib/core/shared/fuzzy-search.function.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/bevacqua/fuzzysearch 2 | export function fuzzySearch(needle: string, haystack: string) { 3 | let indexes: number[] = []; 4 | let hlen = haystack.length; 5 | let nlen = needle.length; 6 | if (nlen > hlen) { 7 | return null; 8 | } 9 | if (nlen === hlen) { 10 | return (needle === haystack) 11 | ? Array.apply(null, { length: nlen }).map(Number.call, Number) 12 | : null; 13 | } 14 | outer: for (let i = 0, j = 0; i < nlen; i++) { 15 | let nch = needle.charCodeAt(i); 16 | while (j < hlen) { 17 | if (haystack.charCodeAt(j++) === nch) { 18 | indexes.push(j - 1); 19 | continue outer; 20 | } 21 | } 22 | return null; 23 | } 24 | return indexes; 25 | } 26 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "baseUrl": "./", 5 | "outDir": "./dist/out-tsc", 6 | "sourceMap": true, 7 | "declaration": false, 8 | "downlevelIteration": true, 9 | "experimentalDecorators": true, 10 | "module": "es2020", 11 | "moduleResolution": "node", 12 | "importHelpers": true, 13 | "target": "es2015", 14 | "typeRoots": [ 15 | "node_modules/@types" 16 | ], 17 | "lib": [ 18 | "es2018", 19 | "dom" 20 | ], 21 | "paths": { 22 | "playground": [ 23 | "dist/playground/playground", 24 | "dist/playground" 25 | ], 26 | "cli": [ 27 | "dist/cli/cli", 28 | "dist/cli" 29 | ] 30 | } 31 | }, 32 | "angularCompilerOptions": { 33 | "fullTemplateTypeCheck": true, 34 | "strictInjectionParameters": true 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/feature2/info-block.component.sandbox.ts: -------------------------------------------------------------------------------- 1 | import { sandboxOf } from 'angular-playground'; 2 | import { InfoBlockComponent } from './info-block.component'; 3 | 4 | export default sandboxOf(InfoBlockComponent) 5 | .add('A general example', { 6 | template: ` 7 | 8 | This is a general info block example component 9 | in Playground 10 | ` 11 | }) 12 | .add('A warning example', { 13 | template: ` 14 | 15 | This is a "warning" info block example 16 | component in Playground 17 | ` 18 | }) 19 | .add('An error example', { 20 | template: ` 21 | 22 | This is an "error" info block example 23 | component in Playground 24 | ` 25 | }); 26 | -------------------------------------------------------------------------------- /examples/cli-example/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js'; 4 | import 'zone.js/testing'; 5 | import { getTestBed } from '@angular/core/testing'; 6 | import { 7 | BrowserDynamicTestingModule, 8 | platformBrowserDynamicTesting 9 | } from '@angular/platform-browser-dynamic/testing'; 10 | 11 | declare const require: { 12 | context(path: string, deep?: boolean, filter?: RegExp): { 13 | keys(): string[]; 14 | (id: string): T; 15 | }; 16 | }; 17 | 18 | // First, initialize the Angular testing environment. 19 | getTestBed().initTestEnvironment( 20 | BrowserDynamicTestingModule, 21 | platformBrowserDynamicTesting() 22 | ); 23 | // Then we find all the tests. 24 | const context = require.context('./', true, /\.spec\.ts$/); 25 | // And load the modules. 26 | context.keys().map(context); 27 | -------------------------------------------------------------------------------- /projects/playground/src/test.ts: -------------------------------------------------------------------------------- 1 | // This file is required by karma.conf.js and loads recursively all the .spec and framework files 2 | 3 | import 'zone.js/dist/zone'; 4 | import 'zone.js/dist/zone-testing'; 5 | import { getTestBed } from '@angular/core/testing'; 6 | import { 7 | BrowserDynamicTestingModule, 8 | platformBrowserDynamicTesting 9 | } from '@angular/platform-browser-dynamic/testing'; 10 | 11 | declare const require: { 12 | context(path: string, deep?: boolean, filter?: RegExp): { 13 | keys(): string[]; 14 | (id: string): T; 15 | }; 16 | }; 17 | 18 | // First, initialize the Angular testing environment. 19 | getTestBed().initTestEnvironment( 20 | BrowserDynamicTestingModule, 21 | platformBrowserDynamicTesting() 22 | ); 23 | // Then we find all the tests. 24 | const context = require.context('./', true, /\.spec\.ts$/); 25 | // And load the modules. 26 | context.keys().map(context); 27 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/feature1/feature1.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule } from '@angular/common'; 3 | import { PersonBlockComponent } from './person-block.component'; 4 | import { RouterModule } from '@angular/router'; 5 | import { PersonDetailsComponent } from './person-details.component'; 6 | import { PersonBioComponent } from './person-bio.component'; 7 | 8 | const routes = [ 9 | { 10 | path: ':viewperson', 11 | component: PersonBlockComponent, 12 | outlet: 'display' 13 | } 14 | ]; 15 | 16 | @NgModule({ 17 | imports: [ 18 | CommonModule, 19 | RouterModule.forRoot(routes, { relativeLinkResolution: 'legacy' }) 20 | ], 21 | declarations: [ 22 | PersonBlockComponent, 23 | PersonDetailsComponent, 24 | PersonBioComponent 25 | ], 26 | exports: [ 27 | RouterModule 28 | ] 29 | }) 30 | export class Feature1Module { 31 | } 32 | -------------------------------------------------------------------------------- /projects/schematics/src/migrations/7.0.0/files/main.playground.ts: -------------------------------------------------------------------------------- 1 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { Component, NgModule } from '@angular/core'; 4 | import { PlaygroundModule } from 'angular-playground'; 5 | 6 | PlaygroundModule 7 | .configure({ 8 | selector: 'app-root', 9 | overlay: false, 10 | modules: [], 11 | }); 12 | 13 | @Component({ 14 | selector: 'playground-app', 15 | template: '' 16 | }) 17 | export class AppComponent {} 18 | 19 | @NgModule({ 20 | imports: [ 21 | BrowserModule, 22 | PlaygroundModule 23 | ], 24 | declarations: [ 25 | AppComponent 26 | ], 27 | bootstrap: [AppComponent] 28 | }) 29 | export class AppModule { } 30 | 31 | platformBrowserDynamic().bootstrapModule(AppModule) 32 | .catch(err => console.error(err)); 33 | -------------------------------------------------------------------------------- /projects/schematics/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "lib": [ 5 | "es2018", 6 | "dom" 7 | ], 8 | "declaration": true, 9 | "module": "commonjs", 10 | "moduleResolution": "node", 11 | "noEmitOnError": true, 12 | "noFallthroughCasesInSwitch": true, 13 | "noImplicitAny": true, 14 | "noImplicitThis": true, 15 | "noUnusedParameters": true, 16 | "noUnusedLocals": true, 17 | "rootDir": "src/", 18 | "outDir": "../../dist/schematics", 19 | "skipDefaultLibCheck": true, 20 | "skipLibCheck": true, 21 | "sourceMap": true, 22 | "strictNullChecks": true, 23 | "target": "es6", 24 | "types": [ 25 | "node", 26 | "jest" 27 | ] 28 | }, 29 | "include": [ 30 | "src/**/*" 31 | ], 32 | "exclude": [ 33 | "src/*/files/**/*", 34 | "src/*/*/files/**/*", 35 | "src/**/*.spec.ts" 36 | ] 37 | } 38 | -------------------------------------------------------------------------------- /projects/playground/src/lib/core/pin/pin.component.html: -------------------------------------------------------------------------------- 1 | 4 | 6 | -------------------------------------------------------------------------------- /projects/playground/src/lib/lib/app-state.ts: -------------------------------------------------------------------------------- 1 | export interface SelectedSandboxAndScenarioKeys { 2 | sandboxKey: string; 3 | scenarioKey: number; 4 | } 5 | export interface Sandbox { 6 | key: string; 7 | type: any; 8 | imports?: any[]; 9 | declarations?: any[]; 10 | entryComponents?: any[]; 11 | scenarios: Scenario[]; 12 | providers?: any[]; 13 | schemas?: any[]; 14 | declareComponent?: boolean; 15 | } 16 | 17 | export interface Scenario { 18 | key: number; 19 | template: string; 20 | styles?: string[]; 21 | context?: any; 22 | providers?: any[]; 23 | } 24 | 25 | export interface SandboxMenuItem { 26 | key: string; 27 | uniqueId?: string; 28 | searchKey: string; 29 | name: string; 30 | label?: string; 31 | scenarioMenuItems: ScenarioMenuItem[]; 32 | } 33 | 34 | export interface ScenarioMenuItem { 35 | key: number; 36 | description: string; 37 | } 38 | -------------------------------------------------------------------------------- /examples/cli-example/e2e/protractor.conf.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Protractor configuration file, see link for more information 3 | // https://github.com/angular/protractor/blob/master/lib/config.ts 4 | 5 | const { SpecReporter } = require('jasmine-spec-reporter'); 6 | 7 | /** 8 | * @type { import("protractor").Config } 9 | */ 10 | exports.config = { 11 | allScriptsTimeout: 11000, 12 | specs: [ 13 | './src/**/*.e2e-spec.ts' 14 | ], 15 | capabilities: { 16 | browserName: 'chrome' 17 | }, 18 | directConnect: true, 19 | baseUrl: 'http://localhost:4200/', 20 | framework: 'jasmine', 21 | jasmineNodeOpts: { 22 | showColors: true, 23 | defaultTimeoutInterval: 30000, 24 | print: function() {} 25 | }, 26 | onPrepare() { 27 | require('ts-node').register({ 28 | project: require('path').join(__dirname, './tsconfig.json') 29 | }); 30 | jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); 31 | } 32 | }; -------------------------------------------------------------------------------- /projects/playground/src/lib/core/shared/highlight-search-match.pipe.ts: -------------------------------------------------------------------------------- 1 | import { Pipe, PipeTransform } from '@angular/core'; 2 | 3 | @Pipe({ name: 'apHighlightSearchMatch', pure: false }) 4 | export class HighlightSearchMatchPipe implements PipeTransform { 5 | transform(value: string, indexMatches: number[], offset = 0): any { 6 | // Match null and undefined, but not 0 or '' 7 | if (value == null || indexMatches == null) { 8 | return value; 9 | } 10 | 11 | let transformedValue = ''; 12 | let indexes = indexMatches.reduce((a, n) => n >= offset ? [...a, n - offset] : a, []); 13 | 14 | for (let i = 0; i < value.length; i++) { 15 | if (indexes.some(item => item === i)) { 16 | transformedValue += `${value[i]}`; 17 | } else { 18 | transformedValue += value[i]; 19 | } 20 | } 21 | 22 | return transformedValue; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/app.module.ts: -------------------------------------------------------------------------------- 1 | import { BrowserModule } from '@angular/platform-browser'; 2 | import { NgModule } from '@angular/core'; 3 | import { ServiceWorkerModule } from '@angular/service-worker'; 4 | import { AppComponent } from './app.component'; 5 | import { SharedModule } from './shared/shared.module'; 6 | import { Feature1Module } from './feature1/feature1.module'; 7 | import { Feature2Module } from './feature2/feature2.module'; 8 | import { RouterModule } from '@angular/router'; 9 | import { Feature3Module } from './feature3/feature3.module'; 10 | import { environment } from '../environments/environment'; 11 | 12 | @NgModule({ 13 | imports: [ 14 | BrowserModule, 15 | environment.production 16 | ? ServiceWorkerModule.register('/ngsw-worker.js') 17 | : [], 18 | RouterModule, 19 | SharedModule, 20 | Feature1Module, 21 | Feature2Module, 22 | Feature3Module 23 | ], 24 | declarations: [ 25 | AppComponent 26 | ], 27 | providers: [], 28 | bootstrap: [AppComponent] 29 | }) 30 | export class AppModule { } 31 | -------------------------------------------------------------------------------- /projects/playground/src/lib/lib/initialize-playground.ts: -------------------------------------------------------------------------------- 1 | 2 | export const initializePlayground = (elementNameToReplace?: string, htmlTitle: string = 'Playground') => { 3 | document.getElementsByTagName('title')[0].innerHTML = htmlTitle; 4 | if (elementNameToReplace && elementNameToReplace.length > 0) { 5 | let appNode = document.getElementsByTagName(elementNameToReplace)[0]; 6 | if (!appNode) { 7 | throw new Error(`Your configured selector (${elementNameToReplace}) does not match your app root selector.`); 8 | } 9 | appNode.parentNode.replaceChild(document.createElement('playground-app'), appNode); 10 | } 11 | let resetStyles = ` 12 | // Playground reset styles 13 | html { 14 | -ms-text-size-adjust: 100%; 15 | -webkit-text-size-adjust: 100%; 16 | } 17 | body { 18 | margin: 0; 19 | } 20 | `; 21 | let style = document.createElement('style'); 22 | style.type = 'text/css'; 23 | style.appendChild(document.createTextNode(resetStyles)); 24 | document.head.insertBefore(style, document.head.firstChild); 25 | }; 26 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/shared/animated.component.ts: -------------------------------------------------------------------------------- 1 | import { Component } from '@angular/core'; 2 | import { trigger, style, animate, transition, state } from '@angular/animations'; 3 | 4 | @Component({ 5 | selector: 'ex-animated', 6 | template: `
CLICK ME
`, 7 | styles: [` 8 | :host {padding: 1em; display:block;} 9 | div { width:100px; height: 20px; padding:4px; text-align: center; cursor:pointer;} 10 | `], 11 | animations: [ 12 | trigger('itemState', [ 13 | state('inactive', style({ 14 | backgroundColor: '#eee', 15 | transform: 'scale(1)' 16 | })), 17 | state('active', style({ 18 | backgroundColor: '#cfd8dc', 19 | transform: 'scale(1.1)' 20 | })), 21 | transition('inactive => active', animate('100ms ease-in')), 22 | transition('active => inactive', animate('100ms ease-out')) 23 | ]) 24 | ] 25 | }) 26 | export class AnimatedComponent { 27 | state = 'inactive'; 28 | toggle() { 29 | this.state = this.state === 'inactive' ? 'active' : 'inactive'; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /projects/playground/README.md: -------------------------------------------------------------------------------- 1 | # Playground 2 | 3 | This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.0.2. 4 | 5 | ## Code scaffolding 6 | 7 | Run `ng generate component component-name --project playground` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project playground`. 8 | > Note: Don't forget to add `--project playground` or else it will be added to the default project in your `angular.json` file. 9 | 10 | ## Build 11 | 12 | Run `ng build playground` to build the project. The build artifacts will be stored in the `dist/` directory. 13 | 14 | ## Publishing 15 | 16 | After building your library with `ng build playground`, go to the dist folder `cd dist/playground` and run `npm publish`. 17 | 18 | ## Running unit tests 19 | 20 | Run `ng test playground` to execute the unit tests via [Karma](https://karma-runner.github.io). 21 | 22 | ## Further help 23 | 24 | To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md). 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 SoCreate 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/cli-example/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage-istanbul-reporter'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | clearContext: false // leave Jasmine Spec Runner output visible in browser 17 | }, 18 | coverageIstanbulReporter: { 19 | dir: require('path').join(__dirname, './coverage/test-ang-nine'), 20 | reports: ['html', 'lcovonly', 'text-summary'], 21 | fixWebpackSourcePaths: true 22 | }, 23 | reporters: ['progress', 'kjhtml'], 24 | port: 9876, 25 | colors: true, 26 | logLevel: config.LOG_INFO, 27 | autoWatch: true, 28 | browsers: ['Chrome'], 29 | singleRun: false, 30 | restartOnFileChange: true 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/shared/notice.component.sandbox.ts: -------------------------------------------------------------------------------- 1 | import { SharedModule } from './shared.module'; 2 | import { sandboxOf } from 'angular-playground'; 3 | import { CounterService } from './counter.service'; 4 | import { NoticeComponent } from './notice.component'; 5 | 6 | class MockCounterService { 7 | increment() { 8 | return 100; 9 | } 10 | } 11 | 12 | export default sandboxOf(NoticeComponent, {imports: [SharedModule], declareComponent: false}) 13 | .add('short text', { 14 | template: `Notification` 15 | }) 16 | .add('long text', { 17 | template: `This is a really super long notice!` 18 | }) 19 | .add('with emoji', { 20 | template: `👻'` 21 | }) 22 | .add('in a green container', { 23 | template: `
I'm in a green container!
` 24 | }) 25 | .add('with custom provider', { 26 | template: `Notice one hundred`, 27 | providers: [{provide: CounterService, useClass: MockCounterService}] 28 | }) 29 | .add('position absolute test', { 30 | template: `Notice that is absolute positioned` 31 | }); 32 | -------------------------------------------------------------------------------- /projects/cli/src/check-errors/reporters/json-reporter.ts: -------------------------------------------------------------------------------- 1 | import { Reporter } from '../../error-reporter'; 2 | 3 | class JSONStats { 4 | suites = 1; 5 | passes: number; 6 | pending = 0; 7 | duration = 0; 8 | time = 0; 9 | 10 | constructor( 11 | public tests: number, 12 | public failures: number, 13 | public start = 0, 14 | public end = 0, 15 | ) { 16 | this.passes = this.tests - this.failures; 17 | } 18 | 19 | } 20 | 21 | export class JSONReporter implements Reporter { 22 | constructor ( 23 | public errors: any[], 24 | public scenarioNames: string[], 25 | ) {} 26 | 27 | getReport() { 28 | return JSON.stringify({ 29 | stats: new JSONStats(this.scenarioNames.length, this.errors.length), 30 | failures: this.errors.map(failure => { 31 | if (!failure) return; 32 | return { 33 | title: failure.scenario, 34 | err: { 35 | message: failure.descriptions[0], 36 | }, 37 | }; 38 | }), 39 | passes: this.scenarioNames.map(pass => { 40 | return { title: pass }; 41 | }), 42 | skips: [], 43 | }, null, 2); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /projects/playground/src/lib/core/drawer/drawer.component.css: -------------------------------------------------------------------------------- 1 | .link { 2 | align-items: center; 3 | background: #252526; 4 | border-radius: 50%; 5 | bottom: 20px; 6 | box-shadow: 0 0 8px 2px rgba(0, 0, 0, 0.5); 7 | color: rgba(255, 255, 255, .5); 8 | cursor: pointer; 9 | display: flex; 10 | flex-direction: column; 11 | font-family: Arial, sans-serif; 12 | font-size: 8px; 13 | font-weight: bold; 14 | height: 45px; 15 | justify-content: center; 16 | position: fixed; 17 | right: 20px; 18 | text-align: center; 19 | text-transform: uppercase; 20 | transition: transform ease 100ms, opacity ease 500ms; 21 | width: 45px; 22 | z-index: 10000; 23 | } 24 | 25 | .link::before { 26 | background: linear-gradient( 27 | to bottom, 28 | rgba(255, 255, 255, .5), rgba(255, 255, 255, .5) 20%, 29 | transparent 20%, transparent 40%, 30 | rgba(255, 255, 255, .5) 40%, rgba(255, 255, 255, .5) 60%, 31 | transparent 60%, transparent 80%, 32 | rgba(255, 255, 255, .5) 80%, rgba(255, 255, 255, .5) 100% 33 | ); 34 | content: ''; 35 | height: 10px; 36 | margin-bottom: 4px; 37 | width: 16px; 38 | } 39 | 40 | .link:hover { 41 | transform: scale(1.15); 42 | } 43 | 44 | :host(.content__menu--hide) .link { 45 | opacity: 0; 46 | } 47 | -------------------------------------------------------------------------------- /projects/cli/src/serve-angular-cli.ts: -------------------------------------------------------------------------------- 1 | import { spawn } from 'child_process'; 2 | import { Config } from './configure'; 3 | 4 | export async function serveAngularCli(config: Config) { 5 | const args = configureArguments(config); 6 | const ngServe = spawn('node', args); 7 | 8 | const write = (handler: any, data: any) => { 9 | const message = data.toString(); 10 | handler.write(`[ng serve]: ${message}\n`); 11 | }; 12 | 13 | ngServe.stdout.on('data', data => { 14 | write(process.stdout, data); 15 | }); 16 | 17 | ngServe.stderr.on('data', data => { 18 | write(process.stderr, data); 19 | }); 20 | 21 | return Promise.resolve(); 22 | } 23 | 24 | function configureArguments(config: Config): string[] { 25 | let args = [config.angularCliPath, 'serve', config.angularAppName]; 26 | 27 | if (!config.angularAppName) { 28 | throw new Error(`Please provide Playground's appName in your angular-playground.json file.`); 29 | } 30 | args.push(`--host=${config.angularCliHost}`); 31 | args.push(`--port=${config.angularCliPort}`); 32 | // need to set webpack.devServer.public via ng serve --publicHost flag (https://stackoverflow.com/a/43621275) 33 | 34 | if (config.angularCliAdditionalArgs) { 35 | args = args.concat(config.angularCliAdditionalArgs); 36 | } 37 | return args; 38 | } 39 | -------------------------------------------------------------------------------- /projects/cli/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { Browser, ConsoleMessage } from 'puppeteer'; 2 | 3 | export function delay(ms: number) { 4 | return new Promise((resolve, reject) => { 5 | setTimeout(() => { 6 | resolve(); 7 | }, ms); 8 | }); 9 | } 10 | 11 | /** 12 | * Creates a Chromium page and navigates to the host url. 13 | * If Chromium is not able to connect to the provided page, it will issue a series 14 | * of retries before it finally fails. 15 | */ 16 | export async function waitForNgServe(browser: Browser, hostUrl: string, timeoutAttempts: number) { 17 | if (timeoutAttempts === 0) { 18 | await browser.close(); 19 | throw new Error('Unable to connect to Playground.'); 20 | } 21 | 22 | const page = await browser.newPage(); 23 | let ngServeErrors = 0; 24 | 25 | try { 26 | page.on('console', (msg: ConsoleMessage) => { 27 | if (msg.type() === 'error') { 28 | ngServeErrors++; 29 | } 30 | }); 31 | await page.goto(hostUrl); 32 | setTimeout(() => page.close()); // close page to prevent memory leak 33 | } catch (e) { 34 | await page.close(); 35 | await delay(1000); 36 | await waitForNgServe(browser, hostUrl, timeoutAttempts - 1); 37 | } 38 | 39 | if (ngServeErrors > 0) { 40 | throw new Error('ng serve failure'); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /projects/playground/src/lib/core/playground-common.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { CommonModule, Location, LocationStrategy, PathLocationStrategy } from '@angular/common'; 3 | import { ReactiveFormsModule } from '@angular/forms'; 4 | import { ScenarioComponent } from './scenario/scenario.component'; 5 | import { StateService } from './shared/state.service'; 6 | import { FocusDirective } from './shared/focus.directive'; 7 | import { UrlService } from './shared/url.service'; 8 | import { AppComponent } from './app.component'; 9 | import { LevenshteinDistance } from './shared/levenshtein-distance'; 10 | import { HighlightSearchMatchPipe } from './shared/highlight-search-match.pipe'; 11 | import { LogoComponent } from './logo/logo.component'; 12 | import { PinComponent } from './pin/pin.component'; 13 | import { DrawerComponent } from './drawer/drawer.component'; 14 | 15 | @NgModule({ 16 | imports: [ 17 | CommonModule, 18 | ReactiveFormsModule, 19 | ], 20 | providers: [ 21 | Location, 22 | { provide: LocationStrategy, useClass: PathLocationStrategy }, 23 | StateService, 24 | UrlService, 25 | LevenshteinDistance, 26 | ], 27 | declarations: [ 28 | AppComponent, 29 | ScenarioComponent, 30 | FocusDirective, 31 | HighlightSearchMatchPipe, 32 | LogoComponent, 33 | PinComponent, 34 | DrawerComponent, 35 | ] 36 | }) 37 | export class PlaygroundCommonModule {} 38 | -------------------------------------------------------------------------------- /projects/cli/src/from-dir.ts: -------------------------------------------------------------------------------- 1 | import { existsSync, readdirSync, lstatSync } from 'fs'; 2 | import { join as joinPath } from 'path'; 3 | 4 | export function fromDirMultiple(startPaths: string[], filter: RegExp, excludeDirectoryFilter: RegExp | null, callback: Function) { 5 | for (const path of startPaths) { 6 | fromDir(path, path, filter, excludeDirectoryFilter, callback); 7 | } 8 | } 9 | 10 | /** 11 | * Recursively apply callback to files in a directory (and sub-directories) that match the 12 | * provided regular expression 13 | */ 14 | function fromDir(rootPath: string, startPath: string, filter: RegExp, excludeDirectoryFilter: RegExp | null, callback: Function) { 15 | if (!existsSync(startPath)) { 16 | throw new Error(`No Directory Found: ${startPath}`); 17 | } 18 | 19 | const files = readdirSync(startPath); 20 | for (let i = 0; i < files.length; i++) { 21 | const filename = joinPath(startPath, files[i]); 22 | const stat = lstatSync(filename); 23 | 24 | if (stat.isDirectory()) { 25 | if (excludeDirectoryFilter) { 26 | if (!excludeDirectoryFilter.test(filename)) { 27 | fromDir(rootPath, filename, filter, excludeDirectoryFilter, callback); 28 | } 29 | } else { 30 | fromDir(rootPath, filename, filter, excludeDirectoryFilter, callback); 31 | } 32 | } else if (filter.test(filename)) { 33 | callback(filename, rootPath); 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /projects/schematics/src/sandbox/index.ts: -------------------------------------------------------------------------------- 1 | import { 2 | apply, 3 | branchAndMerge, 4 | chain, 5 | mergeWith, 6 | move, 7 | Rule, 8 | SchematicContext, 9 | template, 10 | Tree, 11 | url, 12 | } from '@angular-devkit/schematics'; 13 | import { strings } from '@angular-devkit/core'; 14 | import { getProject, getProjectPath } from '../utils/project'; 15 | import { Schema } from './schema'; 16 | import { parseName } from '@schematics/angular/utility/parse-name'; 17 | import { getWorkspace } from "@schematics/angular/utility/workspace"; 18 | 19 | export default function sandbox(options: Schema): Rule { 20 | // @ts-ignore 21 | return async (tree: Tree, context: SchematicContext) => { 22 | const workspace = await getWorkspace(tree); 23 | if (options.path === undefined) { 24 | options.path = getProjectPath(workspace, options); 25 | } 26 | 27 | const parsedPath = parseName(options.path, options.name); 28 | options.name = parsedPath.name; 29 | options.path = parsedPath.path; 30 | 31 | const targetPath = options.flat 32 | ? options.path 33 | : `${options.path}/${options.name}`; 34 | 35 | const prefix = getProject(workspace, {})?.prefix; 36 | const selector = `${prefix}-${options.name}`; 37 | 38 | const templateSource = apply(url('./files'), [ 39 | template({ 40 | ...strings, 41 | selector, 42 | ...options, 43 | }), 44 | move(targetPath), 45 | ]); 46 | 47 | return chain([ 48 | branchAndMerge(mergeWith(templateSource)), 49 | ]); 50 | }; 51 | } 52 | -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "playground": { 7 | "projectType": "library", 8 | "root": "projects/playground", 9 | "sourceRoot": "projects/playground/src", 10 | "prefix": "lib", 11 | "architect": { 12 | "build": { 13 | "builder": "@angular-devkit/build-angular:ng-packagr", 14 | "options": { 15 | "tsConfig": "projects/playground/tsconfig.lib.json", 16 | "project": "projects/playground/ng-package.json" 17 | }, 18 | "configurations": { 19 | "production": { 20 | "tsConfig": "projects/playground/tsconfig.lib.prod.json" 21 | } 22 | } 23 | }, 24 | "test": { 25 | "builder": "@angular-devkit/build-angular:karma", 26 | "options": { 27 | "main": "projects/playground/src/test.ts", 28 | "tsConfig": "projects/playground/tsconfig.spec.json", 29 | "karmaConfig": "projects/playground/karma.conf.js" 30 | } 31 | }, 32 | "lint": { 33 | "builder": "@angular-devkit/build-angular:tslint", 34 | "options": { 35 | "tsConfig": [ 36 | "projects/playground/tsconfig.lib.json", 37 | "projects/playground/tsconfig.spec.json" 38 | ], 39 | "exclude": [ 40 | "**/node_modules/**" 41 | ] 42 | } 43 | } 44 | } 45 | }}, 46 | "defaultProject": "playground" 47 | } 48 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [![Angular Playground](./assets/angular-playground.png)](./assets/angular-playground.png) 2 | 3 | Build Angular components, directives, and pipes in isolation. 4 | 5 | Playground is a living styleguide for your Angular components, taking the components you already 6 | wrote and providing an environment that makes it easy to visualize changes, document variations, and 7 | fix broken UI. 8 | 9 | [![npm version](https://badge.fury.io/js/angular-playground.svg)](https://badge.fury.io/js/angular-playground) 10 | [![Build Status](https://dev.azure.com/SoCreate/Open%20Source%20Projects/_apis/build/status/Angular-Playground?branchName=master)](https://dev.azure.com/SoCreate/Open%20Source%20Projects/_build/latest?definitionId=31&branchName=master) 11 | 12 | 13 | > [Watch our 2018 Angular Meetup Talk](https://www.youtube.com/watch?v=QfvwQEJVOig&t) 14 | 15 | ## Documentation () 16 | 17 | * [Getting started](http://www.angularplayground.it/docs/getting-started/installation) 18 | 19 | ## Articles 20 | 21 | * [Developing and Running Components in a Sandbox!](https://blog.codewithdan.com/2017/11/21/angular-playground-developing-and-running-components-in-a-sandbox/) 22 | 23 | ## Contributing 24 | 25 | Help Angular Playground by contributing! 26 | 27 | ### [Contributing Guide](./CONTRIBUTING.md) 28 | 29 | Please read our [contributing guide](./CONTRIBUTING.md) to learn about filing issues, submitting PRs, and buliding 30 | Angular Playground. 31 | 32 | ### License 33 | 34 | Angular Playground is [MIT licensed](./LICENSE). 35 | 36 | ## Latest Changes 37 | 38 | [Playground Changelog](./CHANGELOG.md) 39 | -------------------------------------------------------------------------------- /projects/cli/src/check-errors/reporters/xml-reporter.ts: -------------------------------------------------------------------------------- 1 | import { ErrorReport, Reporter } from '../../error-reporter'; 2 | import { ScenarioSummary } from '../verify-sandboxes'; 3 | 4 | export class XMLReporter implements Reporter { 5 | constructor ( 6 | public errors: ErrorReport[], 7 | public scenarios: ScenarioSummary[]) {} 8 | 9 | getReport() { 10 | let result = `\n`; 11 | for (const scenario of this.scenarios) { 12 | const name = this.htmlEncode(scenario.name); 13 | const description = this.htmlEncode(scenario.description); 14 | result += `\n`; 15 | const failure = this.errors.find(error => 16 | error.scenario === scenario.name && error.scenarioTitle === scenario.description); 17 | if (failure) { 18 | const message = this.htmlEncode(failure.descriptions[0]); 19 | result += `${message}\n`; 20 | } 21 | result += '\n'; 22 | } 23 | result += '\n'; 24 | return result; 25 | } 26 | 27 | private htmlEncode(s: string) { 28 | if (!s) { 29 | return ''; 30 | } 31 | return s 32 | .replace(/&/g, '&') 33 | .replace(/"/g, '"') 34 | .replace(/'/g, ''') 35 | .replace(//g, '>'); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /projects/cli/test/from-dir.spec.ts: -------------------------------------------------------------------------------- 1 | import { fromDirMultiple } from '../src/from-dir'; 2 | 3 | describe('fromDir', () => { 4 | const dir = './projects/cli/test/files/from-dir-test/'; 5 | const dir2 = './projects/cli/test/files/from-dir-test-multiple/'; 6 | const excludeRegex = /.*node_modules.*/; 7 | 8 | it('should throw error when directory does not exist', () => { 9 | const t = () => { 10 | fromDirMultiple(['foo'], /test/, excludeRegex, () => {}); 11 | }; 12 | expect(t).toThrow(); 13 | }); 14 | 15 | it('should apply callback to each file process matching regex', () => { 16 | const regex = /\.json$/; 17 | const mockCb = jest.fn(); 18 | 19 | fromDirMultiple([dir], regex, excludeRegex, mockCb); 20 | expect(mockCb.mock.calls.length).toBe(2); 21 | }); 22 | 23 | it('should not apply callback to files that don\' match regex', () => { 24 | const regex = /\.spec.ts$/; 25 | const mockCb = jest.fn(); 26 | 27 | fromDirMultiple([dir], regex, excludeRegex, mockCb); 28 | expect(mockCb.mock.calls.length).toBe(0); 29 | }); 30 | 31 | it('should apply recursively to sub-directories', () => { 32 | const regex = /\.csv$/; 33 | const mockCb = jest.fn(); 34 | 35 | fromDirMultiple([dir], regex, excludeRegex, mockCb); 36 | expect(mockCb.mock.calls.length).toBe(1); 37 | }); 38 | 39 | it('should work for multiple directories', () => { 40 | const regex = /\.(json|ts)$/; 41 | const mockCb = jest.fn(); 42 | 43 | fromDirMultiple([dir, dir2], regex, excludeRegex, mockCb); 44 | expect(mockCb.mock.calls.length).toBe(5); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /projects/playground/src/lib/lib/api.ts: -------------------------------------------------------------------------------- 1 | import { Sandbox } from './app-state'; 2 | 3 | export interface SandboxOfConfig { 4 | uniqueId?: string; 5 | label?: string; 6 | imports?: any[]; 7 | declarations?: any[]; 8 | entryComponents?: any[]; 9 | providers?: any[]; 10 | schemas?: any[]; 11 | declareComponent?: boolean; 12 | } 13 | 14 | export interface ScenarioConfig { 15 | template: string; 16 | styles?: string[]; 17 | context?: any; 18 | providers?: any[]; 19 | } 20 | 21 | export class SandboxBuilder { 22 | private _scenarios: any[] = []; 23 | private _scenarioCounter = 0; 24 | 25 | constructor( 26 | private _type: any, 27 | private _config: SandboxOfConfig = {}, 28 | ) {} 29 | 30 | add(description: string, config: ScenarioConfig) { 31 | let key = ++this._scenarioCounter; 32 | this._scenarios.push(Object.assign({}, config, { key })); 33 | return this; 34 | } 35 | 36 | serialize(sandboxPath: string): Sandbox { 37 | return { 38 | key: sandboxPath, 39 | type: this._type, 40 | scenarios: this._scenarios, 41 | imports: this._config.imports || [], 42 | declarations: this._config.declarations || [], 43 | entryComponents: this._config.entryComponents || [], 44 | providers: this._config.providers || [], 45 | schemas: this._config.schemas || [], 46 | declareComponent: this._config.declareComponent !== undefined ? this._config.declareComponent : true, 47 | }; 48 | } 49 | } 50 | 51 | export function sandboxOf(type: any, config?: SandboxOfConfig): SandboxBuilder { 52 | return new SandboxBuilder(type, config); 53 | } 54 | -------------------------------------------------------------------------------- /projects/playground/src/lib/core/playground.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, Type } from '@angular/core'; 2 | import { BrowserModule } from '@angular/platform-browser'; 3 | import { PlaygroundCommonModule } from './playground-common.module'; 4 | import { Configuration, Middleware, MIDDLEWARE } from '../lib/middlewares'; 5 | import { initializePlayground } from '../lib/initialize-playground'; 6 | import { BehaviorSubject } from 'rxjs'; 7 | import { AppComponent } from "./app.component"; 8 | import { Sandboxes } from "./shared/sandboxes"; 9 | 10 | const sandboxDefinitions = { 11 | getSandbox: (path: string) => { }, 12 | getSandboxMenuItems: () => { } 13 | }; 14 | 15 | const _middleware = new BehaviorSubject({ 16 | selector: null, 17 | overlay: false, 18 | modules: [], 19 | }); 20 | const middleware = _middleware.asObservable(); 21 | 22 | 23 | @NgModule({ 24 | imports: [ 25 | BrowserModule, 26 | PlaygroundCommonModule, 27 | ], 28 | providers: [ 29 | {provide: MIDDLEWARE, useValue: middleware}, 30 | {provide: Sandboxes, useValue: sandboxDefinitions} 31 | ], 32 | bootstrap: [AppComponent] 33 | }) 34 | export class PlaygroundModule { 35 | private static setSandboxDefinitions(sandboxesDefinedType: Type) { 36 | const s = new sandboxesDefinedType(); 37 | sandboxDefinitions.getSandbox = s.getSandbox; 38 | sandboxDefinitions.getSandboxMenuItems = s.getSandboxMenuItems; 39 | } 40 | 41 | static configure(configuration: Configuration) { 42 | PlaygroundModule.setSandboxDefinitions(configuration.sandboxesDefined); 43 | initializePlayground(configuration.selector, configuration.htmlTitle); 44 | _middleware.next({..._middleware.value, ...configuration}); 45 | return this; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /projects/playground/karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration file, see link for more information 2 | // https://karma-runner.github.io/1.0/config/configuration-file.html 3 | 4 | module.exports = function (config) { 5 | config.set({ 6 | basePath: '', 7 | frameworks: ['jasmine', '@angular-devkit/build-angular'], 8 | plugins: [ 9 | require('karma-jasmine'), 10 | require('karma-chrome-launcher'), 11 | require('karma-jasmine-html-reporter'), 12 | require('karma-coverage'), 13 | require('@angular-devkit/build-angular/plugins/karma') 14 | ], 15 | client: { 16 | jasmine: { 17 | // you can add configuration options for Jasmine here 18 | // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html 19 | // for example, you can disable the random execution with `random: false` 20 | // or set a specific seed with `seed: 4321` 21 | }, 22 | clearContext: false // leave Jasmine Spec Runner output visible in browser 23 | }, 24 | jasmineHtmlReporter: { 25 | suppressAll: true // removes the duplicated traces 26 | }, 27 | coverageReporter: { 28 | dir: require('path').join(__dirname, '../../coverage/playground'), 29 | subdir: '.', 30 | reporters: [ 31 | { type: 'html' }, 32 | { type: 'text-summary' } 33 | ] 34 | }, 35 | reporters: ['progress', 'kjhtml'], 36 | port: 9876, 37 | colors: true, 38 | logLevel: config.LOG_INFO, 39 | autoWatch: true, 40 | browsers: ['Chrome'], 41 | singleRun: false, 42 | restartOnFileChange: true 43 | }); 44 | }; 45 | -------------------------------------------------------------------------------- /projects/cli/src/build-angular-cli.ts: -------------------------------------------------------------------------------- 1 | import { exec } from 'child_process'; 2 | 3 | export async function buildAngularCli(appName: string, enableServiceWorker: boolean, baseHref: string, maxBuffer: number | string) { 4 | let isInstalled = await serviceWorkerIsInstalled(); 5 | if (enableServiceWorker && !isInstalled) { 6 | throw new Error('\n\nError: --build-with-service-worker requires @angular/service-worker to be installed locally: \n' + 7 | 'try running "npm install @angular/service-worker" then run "angular-playground --build" \n' + 8 | 'see docs: https://github.com/angular/angular-cli/wiki/build#service-worker\n\n'); 9 | } 10 | 11 | console.log('Building for production with sandboxes...'); 12 | 13 | const options = Number.isInteger(+maxBuffer) ? { maxBuffer: +maxBuffer } : {}; 14 | // Cannot build w/ AOT due to runtime compiler dependency 15 | const flags = [ 16 | `-c=production`, 17 | '--aot=false', 18 | `--base-href=${baseHref}`, 19 | ]; 20 | exec(`ng build ${appName} ${flags.join(' ')}`, 21 | options, 22 | (err, stdout, stderr) => { 23 | if (err) throw err; 24 | console.log(stdout); 25 | }); 26 | } 27 | 28 | async function serviceWorkerIsInstalled(): Promise { 29 | return new Promise((resolve, reject) => { 30 | try { 31 | // Check package is installed (prod) 32 | require.resolve('@angular/service-worker'); 33 | resolve(true); 34 | } catch (err) { 35 | try { 36 | // Check package is installed (dev) 37 | require.resolve('../../../../../examples/cli-example/node_modules/@angular/service-worker'); 38 | resolve(true); 39 | } catch (err2) { 40 | resolve(false); 41 | } 42 | } 43 | }); 44 | } 45 | -------------------------------------------------------------------------------- /projects/cli/src/error-reporter.ts: -------------------------------------------------------------------------------- 1 | import { writeFileSync } from 'fs'; 2 | import { ScenarioSummary } from './check-errors/verify-sandboxes'; 3 | import { JSONReporter } from './check-errors/reporters/json-reporter'; 4 | import { XMLReporter } from './check-errors/reporters/xml-reporter'; 5 | const chalk = require('chalk'); 6 | 7 | export const REPORT_TYPE = { 8 | LOG: 'log', 9 | JSON: 'json', 10 | XML: 'xml', 11 | }; 12 | 13 | export interface Reporter { 14 | getReport: () => string; 15 | } 16 | 17 | export interface ErrorReport { 18 | descriptions: any; 19 | scenario: string; 20 | scenarioTitle: string; 21 | } 22 | 23 | export class ErrorReporter { 24 | private _errors: ErrorReport[] = []; 25 | 26 | constructor( 27 | public scenarios: ScenarioSummary[], 28 | public filename: string, 29 | public type: string) {} 30 | 31 | get errors() { 32 | return this._errors; 33 | } 34 | 35 | addError(descriptions: any, scenario: string, scenarioTitle: string) { 36 | this._errors.push({ descriptions, scenario, scenarioTitle }); 37 | } 38 | 39 | compileReport() { 40 | let reporter: Reporter; 41 | switch (this.type) { 42 | case REPORT_TYPE.LOG: 43 | console.error(chalk.red('Error in the following scenarios')); 44 | this._errors.forEach(e => { 45 | console.log(e.scenario); 46 | console.log(e.descriptions); 47 | }); 48 | break; 49 | case REPORT_TYPE.JSON: 50 | const scenarioNames = this.scenarios.map(s => `${s.name}: ${s.description}`); 51 | reporter = new JSONReporter(this.errors, scenarioNames); 52 | writeFileSync(this.filename, reporter.getReport()); 53 | break; 54 | case REPORT_TYPE.XML: 55 | reporter = new XMLReporter(this.errors, this.scenarios); 56 | writeFileSync(this.filename, reporter.getReport()); 57 | break; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /projects/cli/src/run.ts: -------------------------------------------------------------------------------- 1 | import * as getPort from 'get-port'; 2 | import { configure, Config } from './configure'; 3 | import { buildSandboxes } from './build-sandboxes'; 4 | import { startWatch } from './start-watch'; 5 | import { verifySandboxes } from './check-errors/verify-sandboxes'; 6 | import { serveAngularCli } from './serve-angular-cli'; 7 | import { buildAngularCli } from './build-angular-cli'; 8 | import { checkSnapshots } from './check-snapshots'; 9 | 10 | export async function run() { 11 | const config: Config = configure(process.argv); 12 | 13 | try { 14 | await buildSandboxes(config.sourceRoots, config.chunk, config.verifySandboxes || config.checkVisualRegressions, config.definedSandboxesPath); 15 | } catch (err) { 16 | throw err; 17 | } 18 | 19 | if (config.build || config.buildWithServiceWorker) { 20 | return await buildAngularCli(config.angularAppName, !!config.buildWithServiceWorker, config.baseHref, config.angularCliMaxBuffer); 21 | } 22 | 23 | if (config.verifySandboxes || (config.checkVisualRegressions && !config.deleteSnapshots)) { 24 | config.angularCliPort = await getPort({host: config.angularCliHost}); 25 | } 26 | 27 | if ((config.watch && !config.deleteSnapshots) || config.verifySandboxes || (config.checkVisualRegressions && !config.deleteSnapshots)) { 28 | startWatch(config.sourceRoots, () => 29 | buildSandboxes(config.sourceRoots, config.chunk, config.verifySandboxes || config.checkVisualRegressions, config.definedSandboxesPath)); 30 | } 31 | 32 | if ((config.serve && !config.deleteSnapshots) || config.verifySandboxes || (config.checkVisualRegressions && !config.deleteSnapshots)) { 33 | try { 34 | await serveAngularCli(config); 35 | } catch (err) { 36 | throw err; 37 | } 38 | } 39 | 40 | if (config.verifySandboxes) { 41 | try { 42 | await verifySandboxes(config); 43 | } catch (err) { 44 | throw err; 45 | } 46 | } 47 | 48 | if (config.checkVisualRegressions) { 49 | try { 50 | await checkSnapshots(config); 51 | } catch (err) { 52 | throw err; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /examples/cli-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cli-example", 3 | "version": "0.0.0", 4 | "license": "MIT", 5 | "scripts": { 6 | "ng": "ng", 7 | "start": "ng serve", 8 | "build": "ng build --prod", 9 | "test": "ng test", 10 | "lint": "ng lint", 11 | "e2e": "ng e2e", 12 | "playground": "angular-playground", 13 | "playground:prod": "angular-playground --build", 14 | "playground:prod-service-worker": "angular-playground --build-with-service-worker", 15 | "playground:check-errors": "angular-playground --check-errors", 16 | "verify-sandboxes": "angular-playground --config .angular-playground/angular-playground.json --check-errors --report-path ./result.xml --report-type xml", 17 | "check-snapshots": "angular-playground --config .angular-playground/angular-playground.json --check-visual-regressions --visual-regression-mock-date 1577865600000", 18 | "update-snapshots": "angular-playground --config .angular-playground/angular-playground.json --check-visual-regressions --update-snapshots --path-to-sandboxes app/feature1", 19 | "delete-snapshots": "angular-playground --config .angular-playground/angular-playground.json --check-visual-regressions --delete-snapshots --path-to-sandboxes app/feature1" 20 | }, 21 | "private": true, 22 | "dependencies": { 23 | "@angular/animations": "^14.0.0", 24 | "@angular/common": "^14.0.0", 25 | "@angular/compiler": "^14.0.0", 26 | "@angular/core": "^14.0.0", 27 | "@angular/forms": "^14.0.0", 28 | "@angular/platform-browser": "^14.0.0", 29 | "@angular/platform-browser-dynamic": "^14.0.0", 30 | "@angular/router": "^14.0.0", 31 | "@angular/service-worker": "^14.0.0", 32 | "mockdate": "3.0.5", 33 | "rxjs": "~7.5.0", 34 | "tslib": "^2.3.0", 35 | "zone.js": "~0.11.4" 36 | }, 37 | "devDependencies": { 38 | "@angular-devkit/build-angular": "^14.0.0", 39 | "@angular/cli": "^14.0.0", 40 | "@angular/compiler-cli": "^14.0.0", 41 | "@types/jasmine": "~4.0.0", 42 | "jasmine-core": "~4.1.1", 43 | "karma": "~6.3.0", 44 | "karma-chrome-launcher": "~3.1.0", 45 | "karma-coverage": "~2.2.0", 46 | "karma-jasmine": "~5.0.0", 47 | "karma-jasmine-html-reporter": "~1.7.0", 48 | "typescript": "~4.7.3" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /projects/schematics/src/utils/project.ts: -------------------------------------------------------------------------------- 1 | import { normalize, workspaces } from '@angular-devkit/core'; 2 | import { SchematicsException } from '@angular-devkit/schematics'; 3 | import { ProjectDefinitionCollection } from "@angular-devkit/core/src/workspace/definitions"; 4 | 5 | export function getProjectPath( 6 | workspace: workspaces.WorkspaceDefinition, 7 | options: { project?: string | undefined; path?: string | undefined }, 8 | ) { 9 | const project = getProject(workspace, options); 10 | 11 | if (project?.root.substr(-1) === '/') { 12 | project.root = project.root.substr(0, project.root.length - 1); 13 | } 14 | 15 | if (options.path === undefined) { 16 | const projectDirName = project?.extensions.projectType === 'application' 17 | ? 'app' 18 | : 'lib'; 19 | 20 | const sourceRoot = getSourceRoot(project?.sourceRoot); 21 | return `${project?.root ? `/${project.root}` : ''}/${sourceRoot}/${projectDirName}`; 22 | } 23 | 24 | return options.path; 25 | } 26 | 27 | export function getProject( 28 | workspace: workspaces.WorkspaceDefinition, 29 | options: { project?: string | undefined; path?: string | undefined }, 30 | typeFilter: 'application' | 'library' | null = null, 31 | ): workspaces.ProjectDefinition | undefined { 32 | 33 | if (!options.project) { 34 | // can have no projects if created with `ng new --createApplication=false` 35 | if (workspace.projects.size === 0) { 36 | throw new SchematicsException('Your app must have at least 1 project to use Playground.'); 37 | } 38 | // if type filter is not set, use first project 39 | let firstFilteredProjectName = getFirstProjectName(workspace.projects); 40 | if (typeFilter) { 41 | // apply filter 42 | for (const [projectName, project] of workspace.projects.entries()) { 43 | if (project.extensions.projectType === typeFilter) { 44 | firstFilteredProjectName = projectName; 45 | break; 46 | } 47 | } 48 | } 49 | options.project = firstFilteredProjectName; 50 | } 51 | return workspace.projects.get(options.project || ''); 52 | } 53 | 54 | export const getSourceRoot = (sourceRoot: string | undefined) => 55 | sourceRoot === undefined ? 'src' : normalize(sourceRoot); 56 | 57 | const getFirstProjectName = (projects: ProjectDefinitionCollection): string | undefined => { 58 | let firstProject: string | undefined = undefined; 59 | for (const projectName of projects.keys()) { 60 | firstProject = projectName; 61 | break; 62 | } 63 | return firstProject; 64 | }; 65 | -------------------------------------------------------------------------------- /projects/cli/test/error-reporter.spec.ts: -------------------------------------------------------------------------------- 1 | import { ErrorReporter, REPORT_TYPE } from '../src/error-reporter'; 2 | const chalk = require('chalk'); 3 | const fs = require('fs'); 4 | 5 | describe('ErrorReporter', () => { 6 | const mockScenarios = [{ 7 | name: 'textbox', 8 | description: 'Default', 9 | sandboxKey: 'textbox', 10 | scenarioKey: 1, 11 | }]; 12 | let reporter; 13 | 14 | beforeEach(() => { 15 | reporter = new ErrorReporter(mockScenarios, 'output.json', REPORT_TYPE.LOG); 16 | }); 17 | 18 | it('should initialize with no errors', () => { 19 | expect(reporter.errors.length).toBe(0); 20 | }); 21 | 22 | it('should add descriptions and scenarios separately to errors array', () => { 23 | reporter.addError('LOG Error', 'http://localhost:4201/textbox'); 24 | expect(reporter.errors.length).toBe(1); 25 | expect(reporter.errors).toEqual([{ 26 | descriptions: 'LOG Error', 27 | scenario: 'http://localhost:4201/textbox', 28 | }]); 29 | }); 30 | 31 | it('should console.log on compileReport if report type is log', () => { 32 | const errorSpy = jest.spyOn(console, 'error').mockImplementation(() =>{}); 33 | const consoleSpy = jest.spyOn(console, 'log').mockImplementation(() =>{}); 34 | reporter.addError('LOG Error', 'http://localhost:4201/textbox'); 35 | reporter.addError('Read Error', 'http://localhost:4201/textbox'); 36 | reporter.compileReport(); 37 | expect(errorSpy).toHaveBeenCalledWith(chalk.red('Error in the following scenarios')); 38 | expect(consoleSpy).toHaveBeenCalledTimes(4); 39 | }); 40 | 41 | it('should write to file if report type is json', () => { 42 | const writeFileSpy = jest.spyOn(fs, 'writeFileSync').mockImplementation(() =>{}); 43 | reporter.type = REPORT_TYPE.JSON; 44 | reporter.addError('LOG Error', 'http://localhost:4201/textbox'); 45 | reporter.addError('Read Error', 'http://localhost:4201/textbox'); 46 | reporter.compileReport(); 47 | expect(writeFileSpy).toHaveBeenCalled(); 48 | }); 49 | 50 | it('should write to file if report type is xml', () => { 51 | const writeFileSpy = jest.spyOn(fs, 'writeFileSync').mockImplementation(() =>{}); 52 | reporter.type = REPORT_TYPE.XML; 53 | reporter.addError('LOG Error', 'http://localhost:4201/textbox'); 54 | reporter.addError('Read Error', 'http://localhost:4201/textbox'); 55 | reporter.compileReport(); 56 | expect(writeFileSpy).toHaveBeenCalled(); 57 | }); 58 | }); 59 | -------------------------------------------------------------------------------- /examples/cli-example/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This file includes polyfills needed by Angular and is loaded before the app. 3 | * You can add your own extra polyfills to this file. 4 | * 5 | * This file is divided into 2 sections: 6 | * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. 7 | * 2. Application imports. Files imported after ZoneJS that should be loaded before your main 8 | * file. 9 | * 10 | * The current setup is for so-called "evergreen" browsers; the last versions of browsers that 11 | * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), 12 | * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. 13 | * 14 | * Learn more in https://angular.io/guide/browser-support 15 | */ 16 | 17 | /*************************************************************************************************** 18 | * BROWSER POLYFILLS 19 | */ 20 | 21 | /** IE10 and IE11 requires the following for NgClass support on SVG elements */ 22 | // import 'classlist.js'; // Run `npm install --save classlist.js`. 23 | 24 | /** 25 | * Web Animations `@angular/platform-browser/animations` 26 | * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. 27 | * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). 28 | */ 29 | // import 'web-animations-js'; // Run `npm install --save web-animations-js`. 30 | 31 | /** 32 | * By default, zone.js will patch all possible macroTask and DomEvents 33 | * user can disable parts of macroTask/DomEvents patch by setting following flags 34 | * because those flags need to be set before `zone.js` being loaded, and webpack 35 | * will put import in the top of bundle, so user need to create a separate file 36 | * in this directory (for example: zone-flags.ts), and put the following flags 37 | * into that file, and then add the following code before importing zone.js. 38 | * import './zone-flags'; 39 | * 40 | * The flags allowed in zone-flags.ts are listed here. 41 | * 42 | * The following flags will work for all browsers. 43 | * 44 | * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame 45 | * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick 46 | * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames 47 | * 48 | * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js 49 | * with the following flag, it will bypass `zone.js` patch for IE/Edge 50 | * 51 | * (window as any).__Zone_enable_cross_context_check = true; 52 | * 53 | */ 54 | 55 | /*************************************************************************************************** 56 | * Zone JS is required by default for Angular itself. 57 | */ 58 | import 'zone.js'; // Included with Angular CLI. 59 | 60 | 61 | /*************************************************************************************************** 62 | * APPLICATION IMPORTS 63 | */ 64 | -------------------------------------------------------------------------------- /projects/playground/src/lib/core/shared/url.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@angular/core'; 2 | import { Location } from '@angular/common'; 3 | import { SandboxMenuItem } from '../../lib/app-state'; 4 | import { Sandboxes } from "./sandboxes"; 5 | 6 | @Injectable() 7 | export class UrlService { 8 | private _embed: boolean = null; 9 | private _select: any = null; 10 | 11 | sandboxMenuItems: SandboxMenuItem[]; 12 | 13 | get embed() { 14 | return this._embed; 15 | } 16 | 17 | get select() { 18 | return this._select; 19 | } 20 | 21 | constructor(private location: Location, 22 | private sandBoxes: Sandboxes) { 23 | this.sandboxMenuItems = this.sandBoxes.getSandboxMenuItems(); 24 | const urlPath = location.path(); 25 | this._embed = /[?|&]embed=1/.exec(urlPath) !== null; 26 | this._select = this.parse('scenario', this.sandboxMenuItems, urlPath); 27 | if (this._select) { 28 | if (this._select.sandboxKey === null && this._select.scenarioKey === null) { 29 | this.location.replaceState(''); 30 | return; 31 | } 32 | } 33 | } 34 | 35 | setSelected(sandboxKey: string, scenarioKey: number) { 36 | if (sandboxKey === null && scenarioKey === null) { 37 | this.location.replaceState(''); 38 | return; 39 | } 40 | const sandBoxMenuItem = this.sandboxMenuItems 41 | .find(sandboxMenuItem => sandboxMenuItem.key.toLowerCase() === sandboxKey.toLowerCase()); 42 | const scenarioMenuItem = sandBoxMenuItem.scenarioMenuItems.find(mi => mi.key === scenarioKey); 43 | const key = sandBoxMenuItem.uniqueId ? sandBoxMenuItem.uniqueId : sandboxKey; 44 | this.location.replaceState(`?scenario=${encodeURIComponent(key)}/${encodeURIComponent(scenarioMenuItem.description)}`); 45 | } 46 | 47 | private parse(key: string, sandboxMenuItems: SandboxMenuItem[], urlPath: string) { 48 | const match = new RegExp('[?|&]' + key + '=([^&#]*)').exec(urlPath); 49 | if (match !== null) { 50 | const value = decodeURIComponent(match[1]); 51 | const lastSlash = value.lastIndexOf('/'); 52 | 53 | let sandboxKey = value.substr(0, lastSlash); 54 | const sandboxMenuItem = sandboxMenuItems 55 | .find(smi => smi.key.toLowerCase() === sandboxKey.toLowerCase() 56 | || (smi.uniqueId && smi.uniqueId === sandboxKey)); 57 | sandboxKey = sandboxMenuItem.key; 58 | if (!sandboxMenuItem) { 59 | return { sandboxKey: null, scenarioKey: null }; 60 | } 61 | const scenarioDesc = value.substr(lastSlash + 1, value.length).toLowerCase(); 62 | const scenarioKey = sandboxMenuItem.scenarioMenuItems 63 | .findIndex(scenarioMenuItem => scenarioMenuItem.description.toLowerCase() === scenarioDesc) + 1; 64 | if (scenarioKey <= 0) { 65 | return { sandboxKey: null, scenarioKey: null }; 66 | } 67 | 68 | return { sandboxKey, scenarioKey }; 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /examples/cli-example/src/app/feature2/info-block.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, ChangeDetectionStrategy, Input } from '@angular/core'; 2 | 3 | @Component({ 4 | selector: 'ex-info-block', 5 | template: ` 6 |
7 | 8 | 14 | 15 | 16 | 22 | 23 |
24 | 25 |
26 |
27 | `, 28 | styles: [` 29 | 30 | :host { 31 | display: block; 32 | font-size: 1rem; 33 | margin: 1em; 34 | } 35 | 36 | .message { 37 | align-items: center; 38 | display: flex; 39 | padding: 1em; 40 | } 41 | 42 | .message__icon { 43 | height: 2em; 44 | width: 2em; 45 | } 46 | 47 | .message__content { 48 | padding-left: 0.75em; 49 | } 50 | 51 | /* Info State */ 52 | .message--info { 53 | background: #e7e7e7; 54 | color: #555; 55 | } 56 | 57 | .message--info svg { 58 | fill: #999; 59 | } 60 | 61 | /* Warning State */ 62 | .message--warning { 63 | background: #ff9600; 64 | color: #fff; 65 | } 66 | 67 | .message--warning svg { 68 | fill: #ffcc00; 69 | } 70 | 71 | /* Error State */ 72 | .message--error { 73 | background: #ff0000; 74 | color: #fff; 75 | } 76 | 77 | .message--error svg { 78 | fill: #ffb800; 79 | } 80 | 81 | `], 82 | changeDetection: ChangeDetectionStrategy.OnPush 83 | }) 84 | export class InfoBlockComponent { 85 | @Input('state') state; 86 | } 87 | -------------------------------------------------------------------------------- /projects/playground/src/lib/core/shared/levenshtein-distance.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/gustf/js-levenshtein 2 | export class LevenshteinDistance { 3 | getDistance(a: string, b: string) { 4 | if (a === b) { 5 | return 0; 6 | } 7 | 8 | if (a.length > b.length) { 9 | let tmp = a; 10 | a = b; 11 | b = tmp; 12 | } 13 | 14 | let la = a.length; 15 | let lb = b.length; 16 | 17 | while (la > 0 && (a.charCodeAt(la - 1) === b.charCodeAt(lb - 1))) { 18 | la--; 19 | lb--; 20 | } 21 | 22 | let offset = 0; 23 | 24 | while (offset < la && (a.charCodeAt(offset) === b.charCodeAt(offset))) { 25 | offset++; 26 | } 27 | 28 | la -= offset; 29 | lb -= offset; 30 | 31 | if (la === 0 || lb === 1) { 32 | return lb; 33 | } 34 | 35 | let x; 36 | let y; 37 | let d0; 38 | let d1; 39 | let d2; 40 | let d3; 41 | let dd; 42 | let dy; 43 | let ay; 44 | let bx0; 45 | let bx1; 46 | let bx2; 47 | let bx3; 48 | 49 | // tslint:disable-next-line:no-bitwise 50 | let vector = new Array(la << 1); 51 | 52 | for (y = 0; y < la;) { 53 | vector[la + y] = a.charCodeAt(offset + y); 54 | vector[y] = ++y; 55 | } 56 | 57 | for (x = 0; (x + 3) < lb;) { 58 | bx0 = b.charCodeAt(offset + (d0 = x)); 59 | bx1 = b.charCodeAt(offset + (d1 = x + 1)); 60 | bx2 = b.charCodeAt(offset + (d2 = x + 2)); 61 | bx3 = b.charCodeAt(offset + (d3 = x + 3)); 62 | dd = (x += 4); 63 | for (y = 0; y < la;) { 64 | ay = vector[la + y]; 65 | dy = vector[y]; 66 | d0 = this.minimum(dy, d0, d1, bx0, ay); 67 | d1 = this.minimum(d0, d1, d2, bx1, ay); 68 | d2 = this.minimum(d1, d2, d3, bx2, ay); 69 | dd = this.minimum(d2, d3, dd, bx3, ay); 70 | vector[y++] = dd; 71 | d3 = d2; 72 | d2 = d1; 73 | d1 = d0; 74 | d0 = dy; 75 | } 76 | } 77 | 78 | for (; x < lb;) { 79 | bx0 = b.charCodeAt(offset + (d0 = x)); 80 | dd = ++x; 81 | for (y = 0; y < la; y++) { 82 | dy = vector[y]; 83 | vector[y] = dd = dy < d0 || dd < d0 84 | ? dy > dd ? dd + 1 : dy + 1 85 | : bx0 === vector[la + y] 86 | ? d0 87 | : d0 + 1; 88 | d0 = dy; 89 | } 90 | } 91 | 92 | return dd; 93 | } 94 | 95 | private minimum(d0: number, d1: number, d2: number, bx: number, ay: number) { 96 | return d0 < d1 || d2 < d1 97 | ? d0 > d2 98 | ? d2 + 1 99 | : d0 + 1 100 | : bx === ay 101 | ? d1 102 | : d1 + 1; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /projects/cli/test/configure.spec.ts: -------------------------------------------------------------------------------- 1 | import { applyConfigurationFile, Config } from '../src/configure'; 2 | 3 | describe('configure', () => { 4 | it('should throw error when failing to load a configuration file', () => { 5 | const programMock = { config: './no-config.json' }; 6 | 7 | const t = () => { 8 | applyConfigurationFile(programMock); 9 | }; 10 | expect(t).toThrow(); 11 | }); 12 | 13 | it('should return config object from configuration file', () => { 14 | const programMock = { config: './projects/cli/test/files/angular-test-config.json' }; 15 | const config = applyConfigurationFile(programMock); 16 | expect(config).not.toBeUndefined(); 17 | }); 18 | 19 | describe('default values', () => { 20 | let config: Config; 21 | beforeEach(() => { 22 | // Defaults provided from commander 23 | const programMock = { 24 | config: './projects/cli/test/files/empty-config.json', 25 | src: ['./src/'], 26 | watch: true, 27 | serve: true, 28 | chunk: true, 29 | build: false, 30 | buildWithServiceWorker: false, 31 | ngCliPort: 4201, 32 | ngCliHost: '127.0.0.1', 33 | ngCliCmd: 'node_modules/@angular/cli/bin/ng', 34 | }; 35 | config = applyConfigurationFile(programMock); 36 | }); 37 | 38 | describe('have defaults', () => { 39 | it('should provide default value for source path', () => { 40 | expect(config.sourceRoots).toEqual(['./src/']); 41 | }); 42 | 43 | it('should provide default value for no-watch', () => { 44 | expect(config.watch).toBe(true); 45 | }); 46 | 47 | it('should provide default value for no-serve', () => { 48 | expect(config.serve).toBe(true); 49 | }); 50 | 51 | it('should provide default value for no-chunk', () => { 52 | expect(config.chunk).toBe(true); 53 | }); 54 | 55 | it('should provide default value for build', () => { 56 | expect(config.build).toBe(false); 57 | }); 58 | 59 | it('should provide default value for buildWithServiceWorker', () => { 60 | expect(config.buildWithServiceWorker).toBe(false); 61 | }); 62 | 63 | it('should provide default value for @angular/cli host', () => { 64 | expect(config.angularCliHost).toBe('127.0.0.1'); 65 | }); 66 | 67 | it('should provide default value for @angular/cli port', () => { 68 | expect(config.angularCliPort).toBe(4201); 69 | }); 70 | 71 | it('should provide default value for @angular/cli cmd path', () => { 72 | expect(config.angularCliPath).toBe('node_modules/@angular/cli/bin/ng'); 73 | }); 74 | }); 75 | 76 | describe('no defaults', () => { 77 | it('should not have default value for @angular/cli app name', () => { 78 | expect(config.angularAppName).toBeUndefined(); 79 | }); 80 | 81 | it('should not have default value for @angular/cli additional args', () => { 82 | expect(config.angularCliAdditionalArgs).toBeUndefined(); 83 | }); 84 | }); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Angular Playground 2 | 3 | - [Issues and Bugs](#issue) 4 | - [Request/Add a Feature](#feature) 5 | - [Working on the Source Code](#dev) 6 | 7 | ## Found an Issue? 8 | If you find a bug in the source code or a mistake in the documentation, you can help us by 9 | [submitting an issue][submitissue] to our [GitHub Repository][github]. Even better, 10 | you can [submit a Pull Request](#submit-pr) with a fix. 11 | 12 | ## Want a Feature? 13 | You can *request* a new feature by [submitting an issue][submitissue] to our [GitHub 14 | Repository][github]. If you would like to *implement* a new feature, please submit an issue with 15 | a proposal for your work first, to be sure that we can use it. 16 | Please consider what kind of change it is: 17 | 18 | * For a **Major Feature**, first open an issue and outline your proposal so that it can be 19 | discussed. This will also allow us to better coordinate our efforts, prevent duplication of work, 20 | and help you to craft the change so that it is successfully accepted into the project. 21 | * **Small Features** can be crafted and directly [submitted as a Pull Request](#submit-pr). 22 | 23 | ### Submitting a Pull Request (PR) 24 | Before you submit your Pull Request (PR) consider the following guidelines: 25 | 26 | * Search [GitHub](https://github.com/socreate/angular-playground/pulls) for an open or closed PR 27 | that relates to your submission. You don't want to duplicate effort. 28 | * Fork the repo and make your changes in a new git branch: 29 | 30 | ```shell 31 | git checkout -b my-fix-branch master 32 | ``` 33 | 34 | * Create your patch. 35 | * Commit your changes using a descriptive commit message. 36 | * Push your branch to GitHub: 37 | 38 | ```shell 39 | git push origin my-fix-branch 40 | ``` 41 | 42 | * In GitHub, send a pull request to `angular-playground:master`. 43 | * If we suggest changes then: 44 | * Make the required updates. 45 | * Rebase your branch and force push to your GitHub repository (this will update your Pull Request): 46 | 47 | ```shell 48 | git rebase master -i 49 | git push -f 50 | ``` 51 | 52 | That's it! Thank you for your contribution! 53 | 54 | ## Working on the Playground source code 55 | ### Initial setup 56 | Install dependencies for angular-playground: 57 | 58 | ``` 59 | npm i 60 | ``` 61 | 62 | Build angular-playground package and link it globally 63 | ``` 64 | npm run clean 65 | npm run build 66 | npm link 67 | ``` 68 | 69 | Then install dependencies for the example-app: 70 | ``` 71 | cd ./examples/cli-example/ 72 | npm i 73 | npm link angular-playground 74 | ``` 75 | ### Running the example app 76 | From `./examples/cli-example`: 77 | ``` 78 | npm run playground 79 | ``` 80 | 81 | From this point you can work on the Playground and make changes to the source code. You will need to stop the Playground 82 | from running and build the app again (run `npm run rebuild` from the source code root after your changes). Then run `npm run playground` 83 | in the example app root folder again to see the changes you made to the source code in action. 84 | 85 | Rebuild Playground 86 | ``` 87 | Ctrl-C (stop playground) 88 | cd ../../ 89 | npm run rebuild 90 | ``` 91 | 92 | Run the example App 93 | ``` 94 | cd examples/cli-example/ 95 | npm run playground 96 | ``` 97 | 98 | ### Tooling 99 | To run bash scripts on Windows, add the [Git for Windows](https://git-scm.com/) `/bin/` 100 | folder (defaults to `C:\Program Files\Git\bin`) to your path, or install a terminal emulator like 101 | [Cygwin](https://cygwin.com/). 102 | 103 | 104 | [github]: https://github.com/socreate/angular-playground 105 | [submitissue]: https://github.com/socreate/angular-playground/issues/new 106 | -------------------------------------------------------------------------------- /examples/cli-example/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "tslint:recommended", 3 | "rules": { 4 | "align": { 5 | "options": [ 6 | "parameters", 7 | "statements" 8 | ] 9 | }, 10 | "array-type": false, 11 | "arrow-parens": false, 12 | "arrow-return-shorthand": true, 13 | "deprecation": { 14 | "severity": "warning" 15 | }, 16 | "component-class-suffix": true, 17 | "contextual-lifecycle": true, 18 | "curly": true, 19 | "directive-class-suffix": true, 20 | "directive-selector": [ 21 | true, 22 | "attribute", 23 | "app", 24 | "camelCase" 25 | ], 26 | "component-selector": [ 27 | true, 28 | "element", 29 | "app", 30 | "kebab-case" 31 | ], 32 | "eofline": true, 33 | "import-blacklist": [ 34 | true, 35 | "rxjs/Rx" 36 | ], 37 | "import-spacing": true, 38 | "indent": { 39 | "options": [ 40 | "spaces" 41 | ] 42 | }, 43 | "interface-name": false, 44 | "max-classes-per-file": false, 45 | "max-line-length": [ 46 | true, 47 | 140 48 | ], 49 | "member-access": false, 50 | "member-ordering": [ 51 | true, 52 | { 53 | "order": [ 54 | "static-field", 55 | "instance-field", 56 | "static-method", 57 | "instance-method" 58 | ] 59 | } 60 | ], 61 | "no-consecutive-blank-lines": false, 62 | "no-console": [ 63 | true, 64 | "debug", 65 | "info", 66 | "time", 67 | "timeEnd", 68 | "trace" 69 | ], 70 | "no-empty": false, 71 | "no-inferrable-types": [ 72 | true, 73 | "ignore-params" 74 | ], 75 | "no-non-null-assertion": true, 76 | "no-redundant-jsdoc": true, 77 | "no-switch-case-fall-through": true, 78 | "no-var-requires": false, 79 | "object-literal-key-quotes": [ 80 | true, 81 | "as-needed" 82 | ], 83 | "object-literal-sort-keys": false, 84 | "ordered-imports": false, 85 | "quotemark": [ 86 | true, 87 | "single" 88 | ], 89 | "trailing-comma": false, 90 | "no-conflicting-lifecycle": true, 91 | "no-host-metadata-property": true, 92 | "no-input-rename": true, 93 | "no-inputs-metadata-property": true, 94 | "no-output-native": true, 95 | "no-output-on-prefix": true, 96 | "no-output-rename": true, 97 | "semicolon": { 98 | "options": [ 99 | "always" 100 | ] 101 | }, 102 | "space-before-function-paren": { 103 | "options": { 104 | "anonymous": "never", 105 | "asyncArrow": "always", 106 | "constructor": "never", 107 | "method": "never", 108 | "named": "never" 109 | } 110 | }, 111 | "no-outputs-metadata-property": true, 112 | "template-banana-in-box": true, 113 | "template-no-negated-async": true, 114 | "typedef-whitespace": { 115 | "options": [ 116 | { 117 | "call-signature": "nospace", 118 | "index-signature": "nospace", 119 | "parameter": "nospace", 120 | "property-declaration": "nospace", 121 | "variable-declaration": "nospace" 122 | }, 123 | { 124 | "call-signature": "onespace", 125 | "index-signature": "onespace", 126 | "parameter": "onespace", 127 | "property-declaration": "onespace", 128 | "variable-declaration": "onespace" 129 | } 130 | ] 131 | }, 132 | "use-lifecycle-interface": true, 133 | "use-pipe-transform-interface": true, 134 | "variable-name": { 135 | "options": [ 136 | "ban-keywords", 137 | "check-format", 138 | "allow-pascal-case" 139 | ] 140 | }, 141 | "whitespace": { 142 | "options": [ 143 | "check-branch", 144 | "check-decl", 145 | "check-operator", 146 | "check-separator", 147 | "check-type", 148 | "check-typecast" 149 | ] 150 | } 151 | }, 152 | "rulesDirectory": [ 153 | "codelyzer" 154 | ] 155 | } 156 | -------------------------------------------------------------------------------- /projects/schematics/src/sandbox/index.spec.ts: -------------------------------------------------------------------------------- 1 | import { Tree } from '@angular-devkit/schematics'; 2 | import { strings } from '@angular-devkit/core'; 3 | import { SchematicTestRunner } from '@angular-devkit/schematics/testing'; 4 | import * as path from 'path'; 5 | 6 | const collectionPath = path.join(__dirname, '../collection.json'); 7 | 8 | describe('sandbox', () => { 9 | it('should work when flat is true', async () => { 10 | const tree = Tree.empty(); 11 | const prefix = 'app'; 12 | tree.create('angular.json', createAngularJson({ 13 | root: 'projects/foo', 14 | sourceRoot: 'projects/foo/src', 15 | prefix, 16 | })); 17 | tree.create('src/app/feature1/feature1.component.ts', createTestComponent({ 18 | prefix, 19 | name: 'Feature1', 20 | })); 21 | const runner = new SchematicTestRunner('schematics', collectionPath); 22 | const generateSandboxOptions = { 23 | path: 'projects/foo/src/app/feature1', 24 | name: 'feature1', 25 | flat: true, 26 | }; 27 | const resultTree = await runner.runSchematicAsync('sandbox', generateSandboxOptions, tree).toPromise(); 28 | 29 | const newSandbox = resultTree 30 | .readContent('projects/foo/src/app/feature1/feature1.component.sandbox.ts'); 31 | expect(newSandbox).toMatch(/import { Feature1Component } from '\.\/feature1.component';/); 32 | expect(newSandbox).toMatch(/export default sandboxOf\(Feature1Component\)/); 33 | expect(newSandbox).toMatch(/template: `<\/app-feature1>`/); 34 | }); 35 | it('should work when flat is false', async () => { 36 | const tree = Tree.empty(); 37 | const prefix = 'app'; 38 | tree.create('angular.json', createAngularJson({ 39 | root: '', 40 | sourceRoot: 'src', 41 | prefix, 42 | })); 43 | tree.create('src/app/feature1/sub-feature1/sub-feature1.component.ts', createTestComponent({ 44 | prefix, 45 | name: 'SubFeature1', 46 | })); 47 | const runner = new SchematicTestRunner('schematics', collectionPath); 48 | const generateSandboxOptions = { 49 | path: 'src/app/feature1/sub-feature1', 50 | name: 'sub-feature1', 51 | flat: false, 52 | }; 53 | const resultTree = await runner.runSchematicAsync('sandbox', generateSandboxOptions, tree).toPromise(); 54 | 55 | const newSandbox = resultTree 56 | .readContent('src/app/feature1/sub-feature1/sub-feature1/sub-feature1.component.sandbox.ts'); 57 | expect(newSandbox).toMatch(/import { SubFeature1Component } from '\.\.\/sub-feature1.component';/); 58 | expect(newSandbox).toMatch(/export default sandboxOf\(SubFeature1Component\)/); 59 | expect(newSandbox).toMatch(/template: `<\/app-sub-feature1>`/); 60 | }); 61 | it('should work for any prefix', async () => { 62 | const tree = Tree.empty(); 63 | const prefix = 'fizzbuzz'; 64 | tree.create('angular.json', createAngularJson({ 65 | root: '', 66 | sourceRoot: 'src', 67 | prefix, 68 | })); 69 | tree.create('src/app/feature1/feature1.component.ts', createTestComponent({ 70 | prefix, 71 | name: 'Feature1', 72 | })); 73 | const runner = new SchematicTestRunner('schematics', collectionPath); 74 | const generateSandboxOptions = { 75 | path: 'src/app/feature1', 76 | name: 'feature1', 77 | flat: true, 78 | }; 79 | const resultTree = await runner.runSchematicAsync('sandbox', generateSandboxOptions, tree).toPromise(); 80 | 81 | const newSandbox = resultTree 82 | .readContent('src/app/feature1/feature1.component.sandbox.ts'); 83 | expect(newSandbox).toMatch(/template: `<\/fizzbuzz-feature1>`/); 84 | }); 85 | }); 86 | 87 | const createAngularJson = (config: { root: string, sourceRoot: string, prefix: string }) => `{ 88 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 89 | "version": 1, 90 | "projects": { 91 | "foo": { 92 | "root": "${config.root}", 93 | "sourceRoot": "${config.sourceRoot}", 94 | "projectType": "application", 95 | "prefix": "${config.prefix}" 96 | } 97 | } 98 | }`; 99 | 100 | const createTestComponent = (config: { prefix: string, name: string }) => ` 101 | import { Component } from '@angular/core'; 102 | 103 | @Component({ 104 | selector: '${config.prefix}-${strings.dasherize(config.name)}', 105 | templateUrl: './${strings.dasherize(config.name)}.component.html', 106 | styleUrls: ['./${strings.dasherize(config.name)}.component.css'] 107 | }) 108 | export class ${config.name}Component {} 109 | `; 110 | -------------------------------------------------------------------------------- /projects/playground/src/lib/core/app.component.html: -------------------------------------------------------------------------------- 1 |
2 |
7 | 20 |
21 |
23 |

25 | 27 | 29 |

30 | 54 |
55 |
56 | 58 | 59 | 60 |
61 |
62 | 63 |
64 |
65 |

66 | The playground has {{totalSandboxes}} sandboxed component{{totalSandboxes > 1 ? 's' : ''}} 67 |

68 |

69 | Get started - create your first sandbox! Click here. 70 |

71 |
72 |
73 |
74 | 75 | 76 | {{key}} 77 | 78 |   /   79 | 80 |
81 |
82 | {{shortcut.description}} 83 |
84 |
85 |
86 |
87 |
88 | 89 | 90 | 91 |
92 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-playground", 3 | "version": "10.1.0", 4 | "description": "A drop in app module for working on Angular components in isolation (aka Scenario Driven Development).", 5 | "module": "dist/playground/fesm2015/angular-playground.mjs", 6 | "es2020": "dist/playground/fesm2020/angular-playground.mjs", 7 | "esm2020": "dist/playground/esm2020/angular-playground.mjs", 8 | "fesm2020": "dist/playground/fesm2020/angular-playground.mjs", 9 | "fesm2015": "dist/playground/fesm2015/angular-playground.mjs", 10 | "typings": "dist/playground/index.d.ts", 11 | "exports": { 12 | "./package.json": { 13 | "default": "./package.json" 14 | }, 15 | ".": { 16 | "types": "./dist/playground/index.d.ts", 17 | "esm2020": "./dist/playground/esm2020/angular-playground.mjs", 18 | "es2020": "./dist/playground/fesm2020/angular-playground.mjs", 19 | "es2015": "./dist/playground/fesm2015/angular-playground.mjs", 20 | "node": "./dist/playground/fesm2015/angular-playground.mjs", 21 | "default": "./dist/playground/fesm2020/angular-playground.mjs" 22 | } 23 | }, 24 | "sideEffects": false, 25 | "bin": { 26 | "angular-playground": "dist/bin/index.js" 27 | }, 28 | "files": [ 29 | "dist" 30 | ], 31 | "jest": { 32 | "transform": { 33 | "\\.ts$": "ts-jest" 34 | }, 35 | "testRegex": ".*\\.spec\\.ts$", 36 | "moduleFileExtensions": [ 37 | "ts", 38 | "js", 39 | "json" 40 | ], 41 | "testURL": "http://localhost" 42 | }, 43 | "scripts": { 44 | "build": "npm run playground:build && npm run cli:build && npm run schematics:build && npm run copy-jest-files", 45 | "rebuild": "npm run clean && npm run build", 46 | "clean": "rm -f -r dist", 47 | "playground:build": "ng build playground --configuration production && rm dist/playground/package.json", 48 | "cli:build": "tsc -p ./projects/cli/tsconfig.json", 49 | "copy:schemas": "cp projects/schematics/src/sandbox/schema.json dist/schematics/sandbox", 50 | "copy:schematicFiles": "cd ./projects/schematics/src && cp -r --parent ./**/files/* ../../../dist/schematics && cp -r --parent ./**/**/files/* ../../../dist/schematics", 51 | "copy:sandboxFiles": "cp -r projects/schematics/src/sandbox/files dist/schematics/sandbox", 52 | "copy:collection": "cp projects/schematics/src/collection.json dist/schematics/collection.json", 53 | "copy:migration": "cp projects/schematics/src/migrations/migrations.json dist/schematics/migrations/migrations.json", 54 | "schematics:build": "tsc -p ./projects/schematics/tsconfig.json", 55 | "prepublishOnly": "npm run rebuild", 56 | "test": "jest", 57 | "copy-jest-files": "rm -f -r dist/jest && cd dist && mkdir jest && cd .. && cp projects/jest/src/* dist/jest", 58 | "postbuild": "npm run copy:schemas && npm run copy:sandboxFiles && npm run copy:collection && npm run copy:migration && npm run copy:schematicFiles" 59 | }, 60 | "repository": { 61 | "type": "git", 62 | "url": "git+https://github.com/SoCreate/angular-playground.git" 63 | }, 64 | "keywords": [ 65 | "angular" 66 | ], 67 | "author": { 68 | "name": "SoCreate", 69 | "url": "https://SoCreate.it" 70 | }, 71 | "license": "MIT", 72 | "bugs": { 73 | "url": "https://github.com/SoCreate/angular-playground/issues" 74 | }, 75 | "homepage": "http://www.angularplayground.it", 76 | "schematics": "./dist/schematics/collection.json", 77 | "ng-add": { 78 | "save": "devDependencies" 79 | }, 80 | "ng-update": { 81 | "migrations": "./dist/schematics/migrations/migrations.json" 82 | }, 83 | "peerDependencies": { 84 | "@angular/common": ">=14.0.0", 85 | "@angular/compiler": ">=14.0.0", 86 | "@angular/core": ">=14.0.0", 87 | "@angular/forms": ">=14.0.0", 88 | "@angular/platform-browser": ">=14.0.0", 89 | "@angular/platform-browser-dynamic": ">=14.0.0", 90 | "rxjs": ">=7.5.0", 91 | "tslib": ">=2.3.0", 92 | "zone.js": ">=0.11.4" 93 | }, 94 | "dependencies": { 95 | "@angular-devkit/core": "^14.0.0", 96 | "@angular-devkit/schematics": "^14.0.0", 97 | "@jest/core": "28.1.3", 98 | "@jest/types": "28.1.3", 99 | "@schematics/angular": "^14.0.0", 100 | "async": "^3.2.0", 101 | "chalk": "^4.1.1", 102 | "commander": "^7.2.0", 103 | "get-port": "^5.1.1", 104 | "jest": "28.1.3", 105 | "jest-image-snapshot": "5.2.0", 106 | "mockdate": "^3.0.5", 107 | "node-watch": "^0.7.1", 108 | "puppeteer": "^13.1.2" 109 | }, 110 | "devDependencies": { 111 | "@angular-devkit/build-angular": "14.0.0", 112 | "@angular/cli": "14.0.0", 113 | "@angular/common": "14.0.0", 114 | "@angular/compiler": "14.0.0", 115 | "@angular/compiler-cli": "14.0.0", 116 | "@angular/core": "14.0.0", 117 | "@angular/forms": "14.0.0", 118 | "@angular/language-service": "14.0.0", 119 | "@angular/platform-browser": "14.0.0", 120 | "@angular/platform-browser-dynamic": "14.0.0", 121 | "@types/jest": "28.1.3", 122 | "@types/node": "^15.3.0", 123 | "@types/puppeteer": "^5.4.3", 124 | "jest-environment-jsdom": "28.1.3", 125 | "ng-packagr": "14.0.0", 126 | "rxjs": "~7.5.0", 127 | "ts-jest": "28.0.8", 128 | "tslib": "^2.3.0", 129 | "typescript": "~4.7.2", 130 | "zone.js": "~0.11.4" 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /projects/schematics/src/migrations/7.0.0/index.ts: -------------------------------------------------------------------------------- 1 | import { isJsonArray, normalize, workspaces } from '@angular-devkit/core'; 2 | import { 3 | apply, 4 | chain, 5 | filter, 6 | forEach, 7 | mergeWith, 8 | move, 9 | Rule, 10 | SchematicsException, 11 | template, 12 | Tree, 13 | url, 14 | } from '@angular-devkit/schematics'; 15 | import { getProject, getSourceRoot } from '../../utils/project'; 16 | import { Builders } from '@schematics/angular/utility/workspace-models'; 17 | import { constructPath } from '../../utils/paths'; 18 | import { getWorkspace, updateWorkspace } from "@schematics/angular/utility/workspace"; 19 | 20 | export default function migration(options: any): Rule { 21 | return chain([ 22 | configure(options), 23 | updateFiles(options), 24 | ]); 25 | } 26 | 27 | function updateAppInWorkspaceFile( 28 | options: {stylesExtension: string}, 29 | project: workspaces.ProjectDefinition, name: string): Rule { 30 | 31 | return updateWorkspace((workspace) => { 32 | const projectRoot = normalize(project.root); 33 | const projectRootParts = projectRoot.split('/'); 34 | const sourceRoot = getSourceRoot(project.sourceRoot); 35 | const sourceRootParts = sourceRoot.split('/'); 36 | 37 | workspace.projects.delete(name); 38 | workspace.projects.add({ 39 | name, 40 | root: projectRoot, 41 | sourceRoot, 42 | projectType: 'application', 43 | targets: 44 | { 45 | build: { 46 | builder: Builders.Browser, 47 | options: { 48 | outputPath: constructPath(['dist', 'playground']), 49 | index: constructPath([...sourceRootParts, 'index.html']), 50 | main: constructPath([...sourceRootParts, 'main.playground.ts']), 51 | polyfills: constructPath([...sourceRootParts, 'polyfills.ts']), 52 | tsConfig: constructPath([...projectRootParts, `tsconfig.playground.json`]), 53 | aot: false, 54 | assets: [ 55 | constructPath([...sourceRootParts, 'favicon.ico']), 56 | constructPath([...sourceRootParts, 'assets']), 57 | ], 58 | styles: [ 59 | constructPath([...sourceRootParts, `styles.${options.stylesExtension}`]), 60 | ], 61 | scripts: [], 62 | }, 63 | configurations: { 64 | production: { 65 | fileReplacements: [ 66 | { 67 | replace: constructPath([...sourceRootParts, 'environments', 'environment.ts']), 68 | with: constructPath([...sourceRootParts, 'environments', 'environment.prod.ts']), 69 | }, 70 | ], 71 | optimization: true, 72 | outputHashing: 'all', 73 | sourceMap: false, 74 | extractCss: true, 75 | namedChunks: false, 76 | extractLicenses: true, 77 | vendorChunk: false, 78 | buildOptimizer: true, 79 | }, 80 | }, 81 | }, 82 | serve: { 83 | builder: Builders.DevServer, 84 | options: { 85 | browserTarget: 'playground:build', 86 | port: 4201 87 | }, 88 | } 89 | } 90 | }); 91 | 92 | }); 93 | 94 | } 95 | 96 | function configure(options: any): Rule { 97 | return async (tree: Tree) => { 98 | const workspace = await getWorkspace(tree); 99 | const project = getProject(workspace, options, 'application'); 100 | 101 | if (!project) { 102 | throw new SchematicsException('Your app must have at least 1 project to use Playground.'); 103 | } 104 | 105 | let stylesExtension = 'css'; 106 | const buildTarget = project.targets.get('build'); 107 | if ( 108 | buildTarget 109 | && buildTarget.options 110 | && buildTarget.options.styles 111 | && isJsonArray(buildTarget.options.styles) 112 | ) { 113 | const mainStyle = buildTarget.options.styles 114 | .find((path: string | {input: string}) => typeof path === 'string' 115 | ? path.includes('/styles.') 116 | : path.input.includes('/styles.')); 117 | if (mainStyle) { 118 | // @ts-ignore 119 | const mainStyleString = typeof mainStyle === 'string' ? mainStyle : mainStyle.input; 120 | stylesExtension = mainStyleString.split('.').pop(); 121 | } 122 | } 123 | 124 | return chain([ 125 | updateAppInWorkspaceFile({stylesExtension}, project, 'playground'), 126 | ]); 127 | }; 128 | } 129 | 130 | function updateFiles(options: any): Rule { 131 | return async (tree: Tree) => { 132 | const workspace = await getWorkspace(tree); 133 | const project = getProject(workspace, options); 134 | const sourceRoot = getSourceRoot(project?.sourceRoot); 135 | const tsconfigJsonTemplateSource = apply(url('./files'), [ 136 | filter(path => path.endsWith('tsconfig.playground.json')), 137 | template({}), 138 | overwriteIfExists(tree), 139 | ]); 140 | const playgroundMainTemplateSource = apply(url('./files'), [ 141 | filter(path => path.endsWith('main.playground.ts')), 142 | template({}), 143 | move(sourceRoot), 144 | overwriteIfExists(tree), 145 | ]); 146 | return chain([ 147 | mergeWith(tsconfigJsonTemplateSource), 148 | mergeWith(playgroundMainTemplateSource), 149 | ]); 150 | }; 151 | } 152 | 153 | // Add to resolve issue with mergeWith not working when using MergeStrategy.Overwrite 154 | // When not using this function I get this error: "Error [file] already exists." 155 | // https://github.com/angular/angular-cli/issues/11337#issuecomment-516543220 156 | function overwriteIfExists(tree: Tree): Rule { 157 | return forEach(fileEntry => { 158 | if (tree.exists(fileEntry.path)) { 159 | tree.overwrite(fileEntry.path, fileEntry.content); 160 | return null; 161 | } 162 | return fileEntry; 163 | }); 164 | } 165 | 166 | 167 | -------------------------------------------------------------------------------- /examples/cli-example/angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "cli-example": { 7 | "root": "", 8 | "sourceRoot": "src", 9 | "projectType": "application", 10 | "prefix": "app", 11 | "schematics": {}, 12 | "architect": { 13 | "build": { 14 | "builder": "@angular-devkit/build-angular:browser", 15 | "options": { 16 | "outputPath": "dist/cli-example", 17 | "index": "src/index.html", 18 | "main": "src/main.ts", 19 | "polyfills": "src/polyfills.ts", 20 | "tsConfig": "tsconfig.app.json", 21 | "assets": [ 22 | "src/favicon.ico", 23 | "src/assets" 24 | ], 25 | "styles": [ 26 | "src/styles.css" 27 | ], 28 | "scripts": [], 29 | "vendorChunk": true, 30 | "extractLicenses": false, 31 | "buildOptimizer": false, 32 | "sourceMap": true, 33 | "optimization": false, 34 | "namedChunks": true 35 | }, 36 | "configurations": { 37 | "production": { 38 | "fileReplacements": [ 39 | { 40 | "replace": "src/environments/environment.ts", 41 | "with": "src/environments/environment.prod.ts" 42 | } 43 | ], 44 | "optimization": true, 45 | "outputHashing": "all", 46 | "sourceMap": false, 47 | "namedChunks": false, 48 | "extractLicenses": true, 49 | "vendorChunk": false, 50 | "buildOptimizer": true, 51 | "budgets": [ 52 | { 53 | "type": "initial", 54 | "maximumWarning": "2mb", 55 | "maximumError": "5mb" 56 | }, 57 | { 58 | "type": "anyComponentStyle", 59 | "maximumWarning": "6kb", 60 | "maximumError": "10kb" 61 | } 62 | ] 63 | } 64 | } 65 | }, 66 | "serve": { 67 | "builder": "@angular-devkit/build-angular:dev-server", 68 | "options": { 69 | "browserTarget": "cli-example:build" 70 | }, 71 | "configurations": { 72 | "production": { 73 | "browserTarget": "cli-example:build:production" 74 | } 75 | } 76 | }, 77 | "extract-i18n": { 78 | "builder": "@angular-devkit/build-angular:extract-i18n", 79 | "options": { 80 | "browserTarget": "cli-example:build" 81 | } 82 | }, 83 | "test": { 84 | "builder": "@angular-devkit/build-angular:karma", 85 | "options": { 86 | "main": "src/test.ts", 87 | "polyfills": "src/polyfills.ts", 88 | "tsConfig": "tsconfig.spec.json", 89 | "karmaConfig": "src/karma.conf.js", 90 | "styles": [ 91 | "styles.css" 92 | ], 93 | "scripts": [], 94 | "assets": [ 95 | "src/favicon.ico", 96 | "src/assets" 97 | ] 98 | } 99 | }, 100 | "lint": { 101 | "builder": "@angular-devkit/build-angular:tslint", 102 | "options": { 103 | "tsConfig": [ 104 | "tsconfig.app.json", 105 | "tsconfig.spec.json" 106 | ], 107 | "exclude": [ 108 | "**/node_modules/**" 109 | ] 110 | } 111 | } 112 | } 113 | }, 114 | "cli-example-e2e": { 115 | "root": "e2e/", 116 | "projectType": "application", 117 | "architect": { 118 | "e2e": { 119 | "builder": "@angular-devkit/build-angular:protractor", 120 | "options": { 121 | "protractorConfig": "e2e/protractor.conf.js", 122 | "devServerTarget": "cli-example:serve" 123 | } 124 | }, 125 | "lint": { 126 | "builder": "@angular-devkit/build-angular:tslint", 127 | "options": { 128 | "tsConfig": "e2e/tsconfig.json", 129 | "exclude": [ 130 | "**/node_modules/**" 131 | ] 132 | } 133 | } 134 | } 135 | }, 136 | "playground": { 137 | "root": "", 138 | "sourceRoot": "src", 139 | "projectType": "application", 140 | "architect": { 141 | "build": { 142 | "builder": "@angular-devkit/build-angular:browser", 143 | "options": { 144 | "outputPath": "dist/playground", 145 | "index": "src/index.html", 146 | "main": ".angular-playground/main.playground.ts", 147 | "polyfills": "src/polyfills.ts", 148 | "tsConfig": ".angular-playground/tsconfig.playground.json", 149 | "aot": false, 150 | "assets": [ 151 | "src/favicon.ico", 152 | "src/assets" 153 | ], 154 | "styles": [ 155 | "src/styles.css" 156 | ], 157 | "scripts": [] 158 | }, 159 | "configurations": { 160 | "production": { 161 | "fileReplacements": [ 162 | { 163 | "replace": "src/environments/environment.ts", 164 | "with": "src/environments/environment.prod.ts" 165 | } 166 | ], 167 | "buildOptimizer": false, 168 | "extractLicenses": false, 169 | "outputHashing": "all" 170 | }, 171 | "development": { 172 | "buildOptimizer": false, 173 | "optimization": false, 174 | "vendorChunk": true, 175 | "extractLicenses": false, 176 | "sourceMap": true, 177 | "namedChunks": true 178 | } 179 | }, 180 | "defaultConfiguration": "development" 181 | }, 182 | "serve": { 183 | "builder": "@angular-devkit/build-angular:dev-server", 184 | "options": { 185 | "browserTarget": "playground:build" 186 | } 187 | } 188 | } 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /projects/playground/src/lib/core/scenario/scenario.component.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Component, 3 | Inject, 4 | Input, 5 | NgModule, 6 | NgModuleRef, 7 | NgZone, 8 | OnChanges, 9 | OnDestroy, 10 | OnInit, 11 | SimpleChanges, 12 | ɵresetCompiledComponents as resetCompiledComponents 13 | } from '@angular/core'; 14 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 15 | import { Scenario, SelectedSandboxAndScenarioKeys, Sandbox } from '../../lib/app-state'; 16 | import { BrowserModule } from '@angular/platform-browser'; 17 | import { Middleware, MIDDLEWARE } from '../../lib/middlewares'; 18 | import { takeUntil } from 'rxjs/operators'; 19 | import { Subject } from 'rxjs'; 20 | import { Sandboxes } from "../shared/sandboxes"; 21 | 22 | @Component({ 23 | selector: 'ap-scenario', 24 | template: ` 25 | `, 26 | }) 27 | export class ScenarioComponent implements OnInit, OnChanges, OnDestroy { 28 | /** 29 | * The selected sandbox and scenario provided from the app dropdown 30 | */ 31 | @Input() selectedSandboxAndScenarioKeys: SelectedSandboxAndScenarioKeys; 32 | 33 | /** 34 | * Collection of bootstrapped apps 35 | */ 36 | private activeApps: NgModuleRef[] = []; 37 | 38 | /** 39 | * Modules that are applied across every sandbox instance 40 | */ 41 | private activeMiddleware: Middleware; 42 | 43 | /** 44 | * Unsubscribe all subscriptions on component destroy 45 | */ 46 | private onDestroy = new Subject(); 47 | 48 | constructor(private zone: NgZone, @Inject(MIDDLEWARE) private middleware, private sanboxes: Sandboxes) { 49 | } 50 | 51 | ngOnInit() { 52 | this.middleware 53 | .pipe(takeUntil(this.onDestroy)) 54 | .subscribe(middlewares => this.activeMiddleware = middlewares); 55 | } 56 | 57 | ngOnChanges(changes: SimpleChanges) { 58 | if (changes.selectedSandboxAndScenarioKeys) { 59 | this.bootstrapSandbox(changes.selectedSandboxAndScenarioKeys.currentValue); 60 | } 61 | } 62 | 63 | ngOnDestroy() { 64 | this.onDestroy.next(); 65 | 66 | // this cleans up the DOM when a sandboxed component is loaded and the search bar is then cleared 67 | if (this.activeApps.length > 0) { 68 | // destroy sandboxed app (doesn't remove it from the DOM though) 69 | const app = this.activeApps.pop(); 70 | app.destroy(); 71 | 72 | // remove the sandboxed component's element from the dom 73 | const hostElement = document.querySelector('playground-host'); 74 | if (hostElement && hostElement.children) { 75 | hostElement.children[0].remove(); 76 | } 77 | } 78 | } 79 | 80 | /** 81 | * Bootstrap a new Angular application with the sandbox's required dependencies 82 | */ 83 | private bootstrapSandbox(selectedSandboxAndScenarioKeys: SelectedSandboxAndScenarioKeys) { 84 | this.sanboxes.getSandbox(selectedSandboxAndScenarioKeys.sandboxKey).then(sandbox => { 85 | if (sandbox) { 86 | const scenario = sandbox.scenarios 87 | .find((s: Scenario) => s.key === selectedSandboxAndScenarioKeys.scenarioKey); 88 | 89 | if (scenario) { 90 | if (this.activeApps.length > 0) { 91 | const app = this.activeApps.pop(); 92 | app.destroy(); 93 | } 94 | 95 | // Don't bootstrap a new Angular application within an existing zone 96 | this.zone.runOutsideAngular(() => { 97 | const module = this.createModule(sandbox, scenario); 98 | platformBrowserDynamic().bootstrapModule(module) 99 | .then(app => { 100 | this.activeApps.push(app); 101 | resetCompiledComponents(); 102 | (window as any).isPlaygroundComponentLoaded = () => true; 103 | }) 104 | .catch(err => { 105 | resetCompiledComponents(); 106 | (window as any).isPlaygroundComponentLoadedWithErrors = () => true; 107 | console.error(err); 108 | }); 109 | }); 110 | } 111 | } 112 | }); 113 | } 114 | 115 | /** 116 | * Create a module containing the dependencies of a sandbox 117 | */ 118 | private createModule(sandboxMeta: Sandbox, scenario) { 119 | const hostComp = this.createComponent(scenario); 120 | 121 | class DynamicModule { 122 | ngDoBootstrap(app) { 123 | const hostEl = document.querySelector('playground-host'); 124 | if (!hostEl) { 125 | const compEl = document.createElement('playground-host'); 126 | document.body.appendChild(compEl); 127 | } 128 | app.bootstrap(hostComp); 129 | } 130 | } 131 | 132 | return NgModule({ 133 | imports: [ 134 | BrowserModule, 135 | ...sandboxMeta.imports, 136 | ...this.activeMiddleware.modules, 137 | ], 138 | declarations: [ 139 | hostComp, 140 | sandboxMeta.declareComponent ? sandboxMeta.type : [], 141 | ...sandboxMeta.declarations, 142 | ], 143 | providers: [...sandboxMeta.providers], 144 | entryComponents: [hostComp, ...sandboxMeta.entryComponents], 145 | schemas: [...sandboxMeta.schemas], 146 | })(DynamicModule); 147 | } 148 | 149 | /** 150 | * Construct a component to serve as the host for the provided scenario 151 | */ 152 | private createComponent(scenario: Scenario) { 153 | class DynamicComponent { 154 | constructor() { 155 | Object.assign(this, scenario.context); 156 | } 157 | } 158 | 159 | return Component({ 160 | selector: 'playground-host', 161 | template: scenario.template, 162 | styles: scenario.styles, 163 | providers: scenario.providers, 164 | })(DynamicComponent); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /projects/schematics/src/migrations/8.0.0/index.ts: -------------------------------------------------------------------------------- 1 | import { isJsonArray, normalize, strings, workspaces } from '@angular-devkit/core'; 2 | import { 3 | apply, 4 | branchAndMerge, 5 | chain, 6 | filter, 7 | mergeWith, 8 | move, 9 | Rule, 10 | SchematicContext, 11 | SchematicsException, 12 | template, 13 | Tree, 14 | url, 15 | } from '@angular-devkit/schematics'; 16 | import { getProject, getSourceRoot } from '../../utils/project'; 17 | import { Builders } from '@schematics/angular/utility/workspace-models'; 18 | import { constructPath } from '../../utils/paths'; 19 | import { getWorkspace, updateWorkspace } from "@schematics/angular/utility/workspace"; 20 | 21 | export default function migration(options: any): Rule { 22 | return chain([ 23 | configure(options), 24 | createNewFiles(options), 25 | ]); 26 | } 27 | 28 | function updateAppInWorkspaceFile( 29 | options: {stylesExtension: string}, 30 | project: workspaces.ProjectDefinition, name: string): Rule { 31 | 32 | return updateWorkspace((workspace) => { 33 | const projectRoot = normalize(project.root); 34 | const sourceRoot = getSourceRoot(project.sourceRoot); 35 | const sourceRootParts = sourceRoot.split('/'); 36 | 37 | workspace.projects.delete(name); 38 | workspace.projects.add({ 39 | name, 40 | root: projectRoot, 41 | sourceRoot, 42 | projectType: 'application', 43 | targets: { 44 | build: { 45 | builder: Builders.Browser, 46 | options: { 47 | outputPath: constructPath(['dist', 'playground']), 48 | index: constructPath([...sourceRootParts, 'index.html']), 49 | main: constructPath(['.angular-playground', 'main.playground.ts']), 50 | polyfills: constructPath([...sourceRootParts, 'polyfills.ts']), 51 | tsConfig: constructPath(['.angular-playground', `tsconfig.playground.json`]), 52 | aot: false, 53 | assets: [ 54 | constructPath([...sourceRootParts, 'favicon.ico']), 55 | constructPath([...sourceRootParts, 'assets']), 56 | ], 57 | styles: [ 58 | constructPath([...sourceRootParts, `styles.${options.stylesExtension}`]), 59 | ], 60 | scripts: [], 61 | }, 62 | configurations: { 63 | production: { 64 | fileReplacements: [ 65 | { 66 | replace: constructPath([...sourceRootParts, 'environments', 'environment.ts']), 67 | with: constructPath([...sourceRootParts, 'environments', 'environment.prod.ts']), 68 | }, 69 | ], 70 | buildOptimizer: false, 71 | extractLicenses: false, 72 | outputHashing: 'all' 73 | }, 74 | development: { 75 | buildOptimizer: false, 76 | optimization: false, 77 | vendorChunk: true, 78 | extractLicenses: false, 79 | sourceMap: true, 80 | namedChunks: true 81 | } 82 | }, 83 | defaultConfiguration: 'development' 84 | }, 85 | serve: { 86 | builder: Builders.DevServer, 87 | options: { 88 | browserTarget: 'playground:build', 89 | port: 4201 90 | } 91 | } 92 | } 93 | } 94 | ); 95 | 96 | }); 97 | 98 | } 99 | 100 | function configure(options: any): Rule { 101 | return async (tree: Tree) => { 102 | const workspace = await getWorkspace(tree); 103 | const project = getProject(workspace, options, 'application'); 104 | 105 | if (!project) { 106 | throw new SchematicsException('Your app must have at least 1 project to use Playground.'); 107 | } 108 | 109 | let stylesExtension = 'css'; 110 | const buildTarget = project.targets.get('build'); 111 | if ( 112 | buildTarget 113 | && buildTarget.options 114 | && buildTarget.options.styles 115 | && isJsonArray(buildTarget.options.styles) 116 | ) { 117 | const mainStyle = buildTarget.options.styles 118 | .find((path: string | {input: string}) => typeof path === 'string' 119 | ? path.includes('/styles.') 120 | : path.input.includes('/styles.')); 121 | if (mainStyle) { 122 | // @ts-ignore 123 | const mainStyleString = typeof mainStyle === 'string' ? mainStyle : mainStyle.input; 124 | stylesExtension = mainStyleString.split('.').pop(); 125 | } 126 | } 127 | 128 | return chain([ 129 | updateAppInWorkspaceFile({stylesExtension}, project, 'playground'), 130 | ]); 131 | }; 132 | } 133 | 134 | function createNewFiles(options: any): Rule { 135 | // @ts-ignore 136 | return async (tree: Tree, context: SchematicContext) => { 137 | const playgroundDir = '.angular-playground'; 138 | const workspace = await getWorkspace(tree); 139 | const project = getProject(workspace, options); 140 | 141 | const sourceRoot = getSourceRoot(project?.sourceRoot); 142 | const angularPlaygroundJsonTemplateSource = apply(url('./files'), [ 143 | filter(path => path.endsWith('angular-playground.json')), 144 | template({ 145 | ...strings, 146 | sourceRoots: [sourceRoot], 147 | }), 148 | move(playgroundDir), 149 | ]); 150 | const tsconfigJsonTemplateSource = apply(url('./files'), [ 151 | filter(path => path.endsWith('tsconfig.playground.json')), 152 | template({}), 153 | move(playgroundDir), 154 | ]); 155 | const playgroundMainTemplateSource = apply(url('./files'), [ 156 | filter(path => path.endsWith('main.playground.ts')), 157 | template({}), 158 | move(playgroundDir), 159 | ]); 160 | const sandboxesTemplateSource = apply(url('./files'), [ 161 | filter(path => path.endsWith('sandboxes.ts')), 162 | template({}), 163 | move(playgroundDir), 164 | ]); 165 | return chain([ 166 | branchAndMerge(mergeWith(angularPlaygroundJsonTemplateSource)), 167 | branchAndMerge(mergeWith(tsconfigJsonTemplateSource)), 168 | branchAndMerge(mergeWith(playgroundMainTemplateSource)), 169 | branchAndMerge(mergeWith(sandboxesTemplateSource)), 170 | branchAndMerge(createIfNotExists(`${playgroundDir}/.gitignore`, 'sandboxes.ts')), 171 | branchAndMerge(deleteIfExists('angular-playground.json')), 172 | branchAndMerge(deleteIfExists('tsconfig.playground.json')), 173 | branchAndMerge(deleteIfExists(`${sourceRoot}/main.playground.json`)), 174 | ]); 175 | }; 176 | } 177 | 178 | function deleteIfExists(path: string): Rule { 179 | return (tree: Tree, _context: SchematicContext) => { 180 | if (tree.exists(path)) { 181 | tree.delete(path); 182 | } 183 | return tree; 184 | }; 185 | } 186 | 187 | function createIfNotExists(path: string, content: string): Rule { 188 | return (tree: Tree, _context: SchematicContext) => { 189 | if (!tree.exists(path)) { 190 | tree.create(path, content); 191 | } 192 | return tree; 193 | }; 194 | } 195 | -------------------------------------------------------------------------------- /projects/schematics/src/ng-add/index.ts: -------------------------------------------------------------------------------- 1 | import { isJsonArray, normalize, strings, workspaces } from '@angular-devkit/core'; 2 | import { 3 | apply, 4 | branchAndMerge, 5 | chain, 6 | filter, 7 | mergeWith, 8 | move, 9 | Rule, 10 | SchematicContext, 11 | SchematicsException, 12 | template, 13 | Tree, 14 | url, 15 | } from '@angular-devkit/schematics'; 16 | import { addNpmScriptToPackageJson } from '../utils/npm-script'; 17 | import { getProject, getSourceRoot } from '../utils/project'; 18 | import { Builders } from '@schematics/angular/utility/workspace-models'; 19 | import { constructPath } from '../utils/paths'; 20 | import { getWorkspace, updateWorkspace } from "@schematics/angular/utility/workspace"; 21 | 22 | export default function add(options: any): Rule { 23 | return chain([ 24 | updateNpmConfig(), 25 | configure(options), 26 | createNewFiles(options), 27 | ]); 28 | } 29 | 30 | export function updateNpmConfig(): Rule { 31 | return (host: Tree) => { 32 | addNpmScriptToPackageJson(host, 'playground', 'angular-playground'); 33 | return host; 34 | }; 35 | } 36 | 37 | function addAppToWorkspaceFile(options: {stylesExtension: string}, 38 | project: workspaces.ProjectDefinition, name: string): Rule { 39 | 40 | return updateWorkspace((workspace) => { 41 | if (workspace.projects.has(name)) { 42 | throw new SchematicsException(`Project '${name}' already exists in workspace.`); 43 | } 44 | 45 | const projectRoot = normalize(project.root); 46 | const sourceRoot = getSourceRoot(project.sourceRoot); 47 | const sourceRootParts = sourceRoot.split('/'); 48 | 49 | workspace.projects.add({ 50 | name, 51 | root: projectRoot, 52 | sourceRoot, 53 | projectType: 'application', 54 | targets: { 55 | build: { 56 | builder: Builders.Browser, 57 | options: { 58 | outputPath: constructPath(['dist', 'playground']), 59 | index: constructPath([...sourceRootParts, 'index.html']), 60 | main: constructPath(['.angular-playground', 'main.playground.ts']), 61 | polyfills: constructPath([...sourceRootParts, 'polyfills.ts']), 62 | tsConfig: constructPath(['.angular-playground', `tsconfig.playground.json`]), 63 | aot: false, 64 | assets: [ 65 | constructPath([...sourceRootParts, 'favicon.ico']), 66 | constructPath([...sourceRootParts, 'assets']), 67 | ], 68 | styles: [ 69 | constructPath([...sourceRootParts, `styles.${options.stylesExtension}`]), 70 | ], 71 | scripts: [], 72 | }, 73 | configurations: { 74 | production: { 75 | fileReplacements: [ 76 | { 77 | replace: constructPath([...sourceRootParts, 'environments', 'environment.ts']), 78 | with: constructPath([...sourceRootParts, 'environments', 'environment.prod.ts']), 79 | }, 80 | ], 81 | buildOptimizer: false, 82 | extractLicenses: false, 83 | outputHashing: 'all' 84 | }, 85 | development: { 86 | buildOptimizer: false, 87 | optimization: false, 88 | vendorChunk: true, 89 | extractLicenses: false, 90 | sourceMap: true, 91 | namedChunks: true 92 | } 93 | }, 94 | defaultConfiguration: 'development' 95 | }, 96 | serve: { 97 | builder: Builders.DevServer, 98 | options: { 99 | browserTarget: 'playground:build', 100 | port: 4201 101 | } 102 | } 103 | } 104 | } 105 | ); 106 | 107 | }); 108 | } 109 | 110 | function configure(options: any): Rule { 111 | // @ts-ignore 112 | return async (tree: Tree, context: SchematicContext) => { 113 | 114 | const workspace = await getWorkspace(tree); 115 | const project = getProject(workspace, options, 'application'); 116 | 117 | if (!project) { 118 | throw new SchematicsException('Your app must have at least 1 project to use Playground.'); 119 | } 120 | 121 | let stylesExtension = 'css'; 122 | const buildTarget = project.targets.get('build'); 123 | if ( 124 | buildTarget 125 | && buildTarget.options 126 | && buildTarget.options.styles 127 | && isJsonArray(buildTarget.options.styles) 128 | ) { 129 | const mainStyle = buildTarget.options.styles 130 | .find((path: string | {input: string}) => typeof path === 'string' 131 | ? path.includes('/styles.') 132 | : path.input.includes('/styles.')); 133 | if (mainStyle) { 134 | // @ts-ignore 135 | const mainStyleString = typeof mainStyle === 'string' ? mainStyle : mainStyle.input; 136 | stylesExtension = mainStyleString.split('.').pop(); 137 | } 138 | } 139 | 140 | return chain([ 141 | addAppToWorkspaceFile({stylesExtension}, project, 'playground'), 142 | ]); 143 | }; 144 | } 145 | 146 | function createNewFiles(options: any): Rule { 147 | // @ts-ignore 148 | return async (tree: Tree, context: SchematicContext) => { 149 | const playgroundDir = '.angular-playground'; 150 | const workspace = await getWorkspace(tree); 151 | const project = getProject(workspace, options); 152 | 153 | const sourceRoot = getSourceRoot(project?.sourceRoot); 154 | const angularPlaygroundJsonTemplateSource = apply(url('./files'), [ 155 | filter(path => path.endsWith('angular-playground.json')), 156 | template({ 157 | ...strings, 158 | sourceRoots: [sourceRoot], 159 | }), 160 | move(playgroundDir), 161 | ]); 162 | const tsconfigJsonTemplateSource = apply(url('./files'), [ 163 | filter(path => path.endsWith('tsconfig.playground.json')), 164 | template({}), 165 | move(playgroundDir), 166 | ]); 167 | const playgroundMainTemplateSource = apply(url('./files'), [ 168 | filter(path => path.endsWith('main.playground.ts')), 169 | template({}), 170 | move(playgroundDir), 171 | ]); 172 | const sandboxesTemplateSource = apply(url('./files'), [ 173 | filter(path => path.endsWith('sandboxes.ts')), 174 | template({}), 175 | move(playgroundDir), 176 | ]); 177 | return chain([ 178 | branchAndMerge(mergeWith(angularPlaygroundJsonTemplateSource)), 179 | branchAndMerge(mergeWith(tsconfigJsonTemplateSource)), 180 | branchAndMerge(mergeWith(playgroundMainTemplateSource)), 181 | branchAndMerge(mergeWith(sandboxesTemplateSource)), 182 | branchAndMerge(createIfNotExists(`${playgroundDir}/.gitignore`, 'sandboxes.ts')), 183 | ]); 184 | }; 185 | } 186 | 187 | function createIfNotExists(path: string, content: string): Rule { 188 | return (tree: Tree, _context: SchematicContext) => { 189 | if (!tree.exists(path)) { 190 | tree.create(path, content); 191 | } 192 | return tree; 193 | }; 194 | } 195 | -------------------------------------------------------------------------------- /projects/cli/src/build-sandboxes.ts: -------------------------------------------------------------------------------- 1 | import { writeFile, readFileSync, existsSync, writeFileSync, mkdirSync } from 'fs'; 2 | import { join as joinPath, resolve as resolvePath } from 'path'; 3 | import { fromDirMultiple } from './from-dir'; 4 | import { StringBuilder } from './string-builder'; 5 | 6 | export const SANDBOX_MENU_ITEMS_FILE = resolvePath(__dirname, './sandbox-menu-items.js'); 7 | 8 | export interface SandboxFileInformation { 9 | key: string; 10 | srcPath: string; 11 | searchKey: string; 12 | name: string; 13 | label: string; 14 | scenarioMenuItems: { 15 | key: number; 16 | description: string; 17 | }[]; 18 | } 19 | 20 | export async function buildSandboxes(srcPaths: string[], chunk: boolean, makeSandboxMenuItemFile: boolean, definedSandboxesPath: string): Promise { 21 | const chunkMode = chunk ? 'lazy' : 'eager'; 22 | const homes = srcPaths.map(srcPath => resolvePath(srcPath)); 23 | const sandboxes = findSandboxes(homes); 24 | 25 | if (makeSandboxMenuItemFile) { 26 | await buildSandboxMenuItemFile(sandboxes); 27 | } 28 | const rootPath = resolvePath(definedSandboxesPath); 29 | if (!existsSync(rootPath)) { 30 | mkdirSync(rootPath); 31 | } 32 | return await buildSandboxFileContents(rootPath, sandboxes, chunkMode, writeSandboxContent).then(results => { 33 | console.log('Successfully compiled sandbox files.'); 34 | return results; 35 | }); 36 | } 37 | 38 | export function findSandboxes(homes: string[]): SandboxFileInformation[] { 39 | const sandboxes = []; 40 | 41 | fromDirMultiple(homes, /\.sandbox.ts$/, /.*node_modules.*/, (filename, home) => { 42 | const sandboxPath = filename.replace(home, '.').replace(/.ts$/, '').replace(/\\/g, '/'); 43 | const contents = readFileSync(filename, 'utf8'); 44 | 45 | const matchSandboxOf = /\s?sandboxOf\s*\(\s*([^)]+?)\s*\)/g.exec(contents); 46 | if (matchSandboxOf) { 47 | const typeName = matchSandboxOf[1].split(',')[0].trim(); 48 | const labelText = /label\s*:\s*['"](.+)['"]/g.exec(matchSandboxOf[0]); 49 | const uniqueIdText = /uniqueId\s*:\s*['"](.+)['"]/g.exec(matchSandboxOf[0]); 50 | 51 | const scenarioMenuItems = []; 52 | 53 | // Tested with https://regex101.com/r/mtp2Fy/2 54 | // First scenario: May follow directly after sandboxOf function ).add 55 | // Other scenarios: .add with possible whitespace before. Ignore outcommented lines. 56 | const scenarioRegex = /^(?!\/\/)(?:\s*|.*\))\.add\s*\(\s*['"](.+)['"]\s*,\s*{/gm; 57 | let scenarioMatches; 58 | let scenarioIndex = 1; 59 | while ((scenarioMatches = scenarioRegex.exec(contents)) !== null) { 60 | scenarioMenuItems.push({key: scenarioIndex, description: scenarioMatches[1]}); 61 | scenarioIndex++; 62 | } 63 | 64 | const label = labelText ? labelText[1] : ''; 65 | sandboxes.push({ 66 | key: sandboxPath, 67 | uniqueId: uniqueIdText ? uniqueIdText[1] : undefined, 68 | srcPath: home, 69 | searchKey: `${typeName}${label}`, 70 | name: typeName, 71 | label, 72 | scenarioMenuItems, 73 | }); 74 | } 75 | }); 76 | 77 | return sandboxes; 78 | } 79 | 80 | async function writeSandboxContent(filePath, fileContent): Promise { 81 | return new Promise((resolve, reject) => { 82 | writeFile(filePath, fileContent, err => { 83 | if (err) { 84 | reject(new Error('Unable to compile sandboxes.')); 85 | } 86 | resolve(filePath); 87 | }); 88 | }); 89 | } 90 | 91 | export function buildSandboxFileContents( 92 | rootPath: string, 93 | sandboxes: SandboxFileInformation[], 94 | chunkMode: string, 95 | writeContents: (file, contents) => Promise) { 96 | const promises: Promise[] = []; 97 | 98 | const filename = joinPath(rootPath, 'sandboxes.ts'); 99 | createSandboxesFileIfNotExists(filename); 100 | 101 | let contents = readFileSync(filename, 'utf8'); 102 | 103 | if (contents.indexOf('*GET_SANDBOX*') !== -1) { 104 | const sandboxMenuItemsMethodBody = buildGetSandboxMenuItemMethodBodyContent(sandboxes); 105 | contents = contents.replace( 106 | /(\/\*GET_SANDBOX_MENU_ITEMS\*\/).*?(?=\/\*END_GET_SANDBOX_MENU_ITEMS\*\/)/s, 107 | `$1\n ${sandboxMenuItemsMethodBody} \n` 108 | ); 109 | const sandboxMethodBody = buildGetSandboxMethodBodyContent(sandboxes, chunkMode); 110 | contents = contents.replace(/(\/\*GET_SANDBOX\*\/).*?(?=\/\*END_GET_SANDBOX\*\/)/s, `$1\n ${sandboxMethodBody} \n`); 111 | promises.push(writeContents(filename, contents)); 112 | } 113 | 114 | return Promise.all(promises); 115 | } 116 | 117 | export function createSandboxesFileIfNotExists(filename: string) { 118 | if (!existsSync(filename)) { 119 | const fileContent = ` 120 | // DO NOT MODIFY.... This file is filled in via the Playground CLI. 121 | 122 | export class SandboxesDefined { 123 | getSandbox(path: string): any { 124 | /*GET_SANDBOX*/ 125 | /*END_GET_SANDBOX*/ 126 | } 127 | getSandboxMenuItems(): any { 128 | /*GET_SANDBOX_MENU_ITEMS*/ 129 | return []; 130 | /*END_GET_SANDBOX_MENU_ITEMS*/ 131 | } 132 | }`; 133 | 134 | writeFileSync(filename, fileContent); 135 | } 136 | } 137 | 138 | export function buildGetSandboxMethodBodyContent(sandboxes: SandboxFileInformation[], chunkMode: string): string { 139 | const content = new StringBuilder(); 140 | content.addLine(`switch(path) {`); 141 | 142 | sandboxes.forEach(({key, srcPath}) => { 143 | let fullPath = joinPath(srcPath, key); 144 | // Normalize slash syntax for Windows/Unix filepaths 145 | fullPath = slash(fullPath); 146 | content.addLine(`case '${key}':`); 147 | content.addLine(` return import( /* webpackMode: "${chunkMode}" */ '${fullPath}').then(function(_){ return _.default.serialize('${key}'); });`); 148 | }); 149 | content.addLine(`}`); 150 | 151 | return content.dump(); 152 | } 153 | 154 | export function buildGetSandboxMenuItemMethodBodyContent(sandboxes: SandboxFileInformation[]): string { 155 | return `return ${JSON.stringify(sandboxes)};`; 156 | } 157 | 158 | export async function buildSandboxMenuItemFile(sandboxes: SandboxFileInformation[]) { 159 | const content = new StringBuilder(); 160 | content.addLine(`function getSandboxMenuItems() {`); 161 | content.addLine(`return ${JSON.stringify(sandboxes)};`); 162 | content.addLine(`}`); 163 | content.addLine('exports.getSandboxMenuItems = getSandboxMenuItems;'); 164 | 165 | await writeSandboxContent(SANDBOX_MENU_ITEMS_FILE, content.dump()); 166 | } 167 | 168 | // Turns windows URL string ('c:\\etc\\') into URL node expects ('c:/etc/') 169 | // https://github.com/sindresorhus/slash 170 | export function slash(input: string) { 171 | const isExtendedLengthPath = /^\\\\\?\\/.test(input); 172 | const hasNonAscii = /[^\u0000-\u0080]+/.test(input); 173 | 174 | if (isExtendedLengthPath || hasNonAscii) { 175 | return input; 176 | } 177 | 178 | return input.replace(/\\/g, '/'); 179 | } 180 | -------------------------------------------------------------------------------- /projects/cli/src/check-errors/verify-sandboxes.ts: -------------------------------------------------------------------------------- 1 | import * as puppeteer from 'puppeteer'; 2 | import { ConsoleMessage, Page } from 'puppeteer'; 3 | import { SANDBOX_MENU_ITEMS_FILE, SandboxFileInformation } from '../build-sandboxes'; 4 | import { ErrorReporter, REPORT_TYPE } from '../error-reporter'; 5 | import { Config } from '../configure'; 6 | import { waitForNgServe } from '../utils'; 7 | 8 | const chalk = require('chalk'); 9 | 10 | // Used to tailor the version of headless chromium ran by puppeteer 11 | const CHROME_ARGS = ['--disable-gpu', '--no-sandbox']; 12 | 13 | 14 | export interface ScenarioSummary { 15 | name: string; 16 | description: string; 17 | sandboxKey: string; 18 | scenarioKey: number; 19 | } 20 | 21 | let browser: puppeteer.Browser; 22 | let currentScenario = ''; 23 | let currentScenarioDescription = ''; 24 | let reporter: ErrorReporter; 25 | 26 | // Ensure Chromium instances are destroyed on error 27 | process.on('unhandledRejection', async () => { 28 | if (browser) { 29 | await browser.close(); 30 | } 31 | }); 32 | 33 | export async function verifySandboxes(config: Config) { 34 | await main(config); 35 | } 36 | 37 | ///////////////////////////////// 38 | 39 | async function main(config: Config) { 40 | const hostUrl = `http://localhost:${config.angularCliPort}`; 41 | const timeoutAttempts = config.timeout; 42 | browser = await puppeteer.launch({ 43 | headless: true, 44 | handleSIGINT: false, 45 | args: CHROME_ARGS, 46 | }); 47 | 48 | await waitForNgServe(browser, hostUrl, timeoutAttempts); 49 | 50 | const scenarios = getSandboxMetadata(config, hostUrl, config.randomScenario); 51 | console.log(`Retrieved ${scenarios.length} scenarios.\n`); 52 | 53 | reporter = new ErrorReporter(scenarios, config.reportPath, config.reportType); 54 | const page = await setupPageAndErrorHandling(hostUrl); 55 | for (let i = 0; i < scenarios.length; i++) { 56 | console.log(`Checking [${i + 1}/${scenarios.length}]: ${scenarios[i].name}: ${scenarios[i].description}`); 57 | await openScenario(scenarios[i], page); 58 | } 59 | await page.close(); 60 | await browser.close(); 61 | 62 | const hasErrors = reporter.errors.length > 0; 63 | // always generate report if report type is a file, or if there are errors 64 | if (hasErrors || config.reportType !== REPORT_TYPE.LOG) { 65 | reporter.compileReport(); 66 | } 67 | const exitCode = hasErrors ? 1 : 0; 68 | process.exit(exitCode); 69 | } 70 | 71 | async function setupPageAndErrorHandling(hostUrl): Promise { 72 | const page = await browser.newPage(); 73 | page.on('console', (msg: ConsoleMessage) => onConsoleErr(msg)); 74 | console.log(`Navigating to ${hostUrl}`); 75 | await page.goto(hostUrl, {waitUntil: 'load', timeout: 60000}); 76 | return page; 77 | } 78 | 79 | async function openScenario(scenario: ScenarioSummary, page: Page) { 80 | currentScenario = scenario.name; 81 | currentScenarioDescription = scenario.description; 82 | 83 | // @ts-ignore 84 | await page.evaluate((sandboxKey, scenarioKey) => window.loadScenario(sandboxKey, scenarioKey), 85 | scenario.sandboxKey, scenario.scenarioKey); 86 | // @ts-ignore 87 | await page.waitForFunction(() => window.isPlaygroundComponentLoaded() || window.isPlaygroundComponentLoadedWithErrors()); 88 | 89 | const sleep = (ms) => new Promise(res => setTimeout(res, ms)); 90 | await sleep(100); // sleep for a bit in case page elements are still being rendered 91 | } 92 | 93 | /** 94 | * Retrieves Sandbox scenario URLs, descriptions, and names 95 | * @param config - Configuration 96 | * @param baseUrl - Base URL of scenario path e.g. http://localhost:4201 97 | * @param selectRandomScenario - Whether or not to select one random scenario of all availalble scenarios for a component 98 | */ 99 | function getSandboxMetadata(config: Config, baseUrl: string, selectRandomScenario: boolean): ScenarioSummary[] { 100 | const scenarios: ScenarioSummary[] = []; 101 | try { 102 | loadSandboxMenuItems().forEach((sandboxItem: SandboxFileInformation) => { 103 | if (!config.pathToSandboxes || config.pathToSandboxes.some(vp => sandboxItem.key.includes(vp))) { 104 | if (selectRandomScenario) { 105 | const randomItemKey = getRandomKey(sandboxItem.scenarioMenuItems.length); 106 | for (const item of sandboxItem.scenarioMenuItems) { 107 | if (item.key === randomItemKey) { 108 | scenarios.push( 109 | { 110 | name: sandboxItem.key, 111 | description: item.description, 112 | sandboxKey: sandboxItem.key, 113 | scenarioKey: item.key 114 | } 115 | ); 116 | break; 117 | } 118 | } 119 | } else { 120 | // Grab all scenarios 121 | sandboxItem.scenarioMenuItems 122 | .forEach((item) => { 123 | scenarios.push( 124 | { 125 | name: sandboxItem.key, 126 | description: item.description, 127 | sandboxKey: sandboxItem.key, 128 | scenarioKey: item.key 129 | } 130 | ); 131 | }); 132 | } 133 | } 134 | }); 135 | } catch (err) { 136 | throw new Error(`Error getting sandbox metadata: ${err}`); 137 | } 138 | 139 | return scenarios; 140 | } 141 | 142 | /** 143 | * Attempt to load sandboxes.ts and provide menu items 144 | */ 145 | function loadSandboxMenuItems(): SandboxFileInformation[] { 146 | try { 147 | return require(SANDBOX_MENU_ITEMS_FILE).getSandboxMenuItems(); 148 | } catch (err) { 149 | throw new Error(`Failed to load sandbox menu items. ${err}`); 150 | } 151 | } 152 | 153 | /** 154 | * Callback when Chromium page encounters a console error 155 | */ 156 | function onConsoleErr(msg: ConsoleMessage) { 157 | try { 158 | if (msg.type() === 'error') { 159 | console.error(chalk.red(`Error in ${currentScenario} (${currentScenarioDescription}):`)); 160 | const getErrors = (type: string, getValue: (_: any) => string) => msg.args() 161 | .map(a => (a as any)._remoteObject) 162 | .filter(o => o.type === type) 163 | .map(getValue); 164 | const stackTrace = getErrors('object', o => o.description); 165 | const errorMessage = getErrors('string', o => o.value); 166 | let description = stackTrace.length ? stackTrace : errorMessage; 167 | description = description.length ? description : [msg.text()]; 168 | description.map(d => console.error(d)); 169 | if (description.length) { 170 | reporter.addError(description, currentScenario, currentScenarioDescription); 171 | } 172 | } 173 | } catch (err) { 174 | console.error(`Error handling console errors: ${err}`); 175 | } 176 | } 177 | 178 | /** 179 | * Returns a random value between 1 and the provided length (both inclusive). 180 | * Note: indexing of keys starts at 1, not 0 181 | */ 182 | function getRandomKey(menuItemsLength: number): number { 183 | return Math.floor(Math.random() * menuItemsLength) + 1; 184 | } 185 | -------------------------------------------------------------------------------- /projects/playground/src/lib/core/app.component.css: -------------------------------------------------------------------------------- 1 | /* Globals */ 2 | * { 3 | box-sizing: border-box; 4 | } 5 | 6 | :host { 7 | display: flex; 8 | flex-direction: column; 9 | } 10 | 11 | /* Shield */ 12 | .shield { 13 | height: 100vh; 14 | opacity: 0; 15 | position: absolute; 16 | z-index: 2; 17 | width: 100%; 18 | } 19 | 20 | /* Command Bar */ 21 | .command-bar { 22 | background-color: #252526; 23 | box-shadow: 0 3px 8px 5px black; 24 | color: white; 25 | display: flex; 26 | flex-direction: column; 27 | font-family: Consolas, monospace; 28 | left: 50%; 29 | margin-top: -6px; 30 | max-height: 100vh; 31 | padding-top: 10px; 32 | position: absolute; 33 | transform: translate(-50%, -120%); 34 | transition: transform ease 100ms, opacity ease 100ms; 35 | width: 376px; 36 | z-index: 9999999999999; 37 | } 38 | 39 | .command-bar::before { 40 | border-bottom: solid 1px black; 41 | content: ""; 42 | display: block; 43 | position: absolute; 44 | top: 61px; 45 | width: 100%; 46 | } 47 | 48 | .command-bar--open { 49 | min-height: 60px; 50 | transform: translate(-50%, 0); 51 | } 52 | 53 | .command-bar--preview { 54 | opacity: .7; 55 | } 56 | 57 | .command-bar__filter { 58 | background-color: #3c3c3c; 59 | border: 1px solid #174a6c; 60 | color: white; 61 | font-family: Consolas, monospace; 62 | font-size: 16px; 63 | margin: 6px 0 0 5px; 64 | padding: 8px; 65 | width: 365px; 66 | z-index: 1; 67 | } 68 | 69 | .command-bar__filter::placeholder { 70 | color: #a9a9a9; 71 | } 72 | 73 | .command-bar__filter:-ms-input-placeholder { 74 | color: #a9a9a9; 75 | } 76 | 77 | .command-bar__filter::-moz-focus-inner { 78 | border: 0; 79 | padding: 0; 80 | } 81 | 82 | /* Sandboxes */ 83 | .command-bar__sandboxes { 84 | border-top: solid 1px rgba(255, 255, 255, .1); 85 | margin-top: 9px; 86 | overflow: auto; 87 | position: relative; 88 | max-height: calc(100vh - 109px); 89 | } 90 | 91 | .command-bar__sandboxes::-webkit-scrollbar { 92 | background-color: transparent; 93 | width: 6px; 94 | } 95 | 96 | .command-bar__sandboxes::-webkit-scrollbar-track { 97 | border-left: solid 1px black; 98 | background: rgba(255, 255, 255, 0.1); 99 | } 100 | 101 | .command-bar__sandboxes::-webkit-scrollbar-thumb { 102 | background-color: rgba(255, 255, 255, 0.1); 103 | margin-left: 2px; 104 | width: 4px; 105 | } 106 | 107 | .command-bar__sandbox { 108 | border-bottom: solid 1px black; 109 | border-top: solid 1px rgba(255, 255, 255, .1); 110 | padding: 8px 6px 14px; 111 | } 112 | 113 | .command-bar__sandbox:first-child { 114 | border-top: none; 115 | } 116 | 117 | .command-bar__sandbox:last-child { 118 | border-bottom: none; 119 | padding-bottom: 12px; 120 | } 121 | 122 | .command-bar__title { 123 | align-items: center; 124 | color: rgba(255, 255, 255, .5); 125 | display: flex; 126 | font-family: Consolas, monospace; 127 | font-size: 12px; 128 | font-weight: normal; 129 | justify-content: space-between; 130 | margin: 0 0 5px; 131 | padding: 5px 0 0; 132 | } 133 | 134 | .command-bar__title ::ng-deep mark { 135 | background: transparent; 136 | color: #0097fb; 137 | font-weight: bold; 138 | } 139 | 140 | .command-bar__name { 141 | max-width: 100%; 142 | overflow: hidden; 143 | text-overflow: ellipsis; 144 | white-space: nowrap; 145 | } 146 | 147 | .command-bar__label { 148 | background: rgba(255, 255, 255, .1); 149 | border-radius: 2px; 150 | display: block; 151 | font-size: 10px; 152 | margin-left: 10px; 153 | padding: 4px 5px 3px; 154 | } 155 | 156 | /* Scenarios */ 157 | .command-bar__scenarios { 158 | display: flex; 159 | } 160 | 161 | .command-bar__scenario-link { 162 | align-items: center; 163 | border-radius: 2px; 164 | color: rgba(255, 255, 255, .5); 165 | cursor: pointer; 166 | display: flex; 167 | padding: 4px 3px; 168 | width: 100%; 169 | } 170 | 171 | .command-bar__scenario-link:hover, 172 | .command-bar__scenario-link:active, 173 | .command-bar__scenario-link:focus { 174 | background-color: #0097fb; 175 | color: white; 176 | outline-style: none; 177 | } 178 | 179 | .command-bar__scenario-link:hover .command-bar__scenario-icon, 180 | .command-bar__scenario-link:active .command-bar__scenario-icon, 181 | .command-bar__scenario-link:focus .command-bar__scenario-icon { 182 | opacity: .5; 183 | } 184 | 185 | .command-bar__scenario-label { 186 | line-height: 1; 187 | max-width: calc(100% - 26px); 188 | min-width: calc(100% - 26px); 189 | padding-bottom: 2px; 190 | } 191 | 192 | .command-bar__scenario-link--selected { 193 | background: rgba(255, 255, 255, .1); 194 | color: white; 195 | } 196 | 197 | .command-bar__scenario-link:hover .command-bar__scenario-icon--selected, 198 | .command-bar__scenario-link:active .command-bar__scenario-icon--selected, 199 | .command-bar__scenario-link:focus .command-bar__scenario-icon--selected { 200 | fill: white; 201 | } 202 | 203 | /* Brand */ 204 | .command-bar__brand { 205 | border-top: solid 1px black; 206 | display: block; 207 | position: relative; 208 | padding: 9px 0 3px; 209 | text-align: center; 210 | } 211 | 212 | .command-bar__brand::before { 213 | border-bottom: solid 1px rgba(255, 255, 255, .1); 214 | content: ""; 215 | display: block; 216 | left: 0; 217 | position: absolute; 218 | top: 0; 219 | width: 100%; 220 | } 221 | 222 | .command-bar__brand:hover .command-bar__logo__box { 223 | fill: rgba(255, 255, 255, .2); 224 | } 225 | 226 | .command-bar__brand:hover .command-bar__logo__letter { 227 | fill: rgba(255, 255, 255, .75); 228 | } 229 | 230 | /* Content */ 231 | .content__none { 232 | align-items: center; 233 | border: 0; 234 | display: flex; 235 | min-height: calc(100vh - 4em); 236 | justify-content: center; 237 | padding-top: 2em; 238 | padding-bottom: 2em; 239 | position: relative; 240 | width: 100%; 241 | } 242 | 243 | .content__none-message { 244 | font-family: Arial, sans-serif; 245 | max-width: 50%; 246 | min-width: 450px; 247 | text-align: center; 248 | } 249 | 250 | .content__none-message em { 251 | color: #666; 252 | } 253 | 254 | .content__none-message p { 255 | font-size: 20px; 256 | } 257 | 258 | .content__shortcuts { 259 | border-top: solid 1px #ccc; 260 | margin-top: 2em; 261 | padding: 30px 0 0 100px; 262 | width: 520px; 263 | } 264 | 265 | .content__shortcut { 266 | display: flex; 267 | } 268 | 269 | .content__shortcut-label { 270 | align-items: center; 271 | display: flex; 272 | font-size: 11px; 273 | justify-content: flex-end; 274 | max-width: 150px; 275 | min-width: 150px; 276 | padding: 8px 12px 8px 0; 277 | white-space: nowrap; 278 | } 279 | 280 | .content__shortcut-label code { 281 | background: #eee; 282 | border: solid 1px #ccc; 283 | border-radius: 4px; 284 | padding: 3px 7px; 285 | } 286 | 287 | .content__shortcut-value { 288 | align-items: center; 289 | display: flex; 290 | font-size: 11px; 291 | line-height: 1.75; 292 | text-align: left; 293 | white-space: nowrap; 294 | } 295 | --------------------------------------------------------------------------------