Repository: codelab-fun/codelab Branch: master Commit: fcd3caf8c656 Files: 1647 Total size: 6.9 MB Directory structure: gitextract_yuiyff9g/ ├── .editorconfig ├── .firebaserc ├── .flooignore ├── .gitattributes ├── .gitignore ├── .nvmrc ├── .prettierignore ├── .prettierrc ├── .travis.yml ├── .vscode/ │ └── extensions.json ├── LICENSE ├── README.md ├── angular.json ├── apps/ │ ├── angular-thirty-seconds/ │ │ ├── browserslist │ │ ├── karma.conf.js │ │ ├── src/ │ │ │ ├── app/ │ │ │ │ ├── app.component.ts │ │ │ │ ├── app.module.ts │ │ │ │ ├── create-snippet/ │ │ │ │ │ ├── create-snippet.component.html │ │ │ │ │ ├── create-snippet.component.scss │ │ │ │ │ ├── create-snippet.component.spec.ts │ │ │ │ │ ├── create-snippet.component.ts │ │ │ │ │ ├── create-snippet.module.ts │ │ │ │ │ ├── snippet-info/ │ │ │ │ │ │ ├── snippet-info.component.html │ │ │ │ │ │ ├── snippet-info.component.scss │ │ │ │ │ │ └── snippet-info.component.ts │ │ │ │ │ ├── snippet-modal/ │ │ │ │ │ │ ├── snippet-overview.component.html │ │ │ │ │ │ ├── snippet-overview.component.scss │ │ │ │ │ │ └── snippet-overview.component.ts │ │ │ │ │ └── snippet-spinner/ │ │ │ │ │ ├── snippet-spinner.component.html │ │ │ │ │ ├── snippet-spinner.component.scss │ │ │ │ │ └── snippet-spinner.component.ts │ │ │ │ ├── pull-requests-list/ │ │ │ │ │ ├── pull-requests-list.component.html │ │ │ │ │ ├── pull-requests-list.component.scss │ │ │ │ │ └── pull-requests-list.component.ts │ │ │ │ └── shared/ │ │ │ │ ├── angular-sample.ts │ │ │ │ ├── constants.ts │ │ │ │ ├── consts.ts │ │ │ │ ├── functions/ │ │ │ │ │ ├── generate-snippet.spec.ts │ │ │ │ │ ├── generate-snippet.ts │ │ │ │ │ ├── parse-snippet.spec.ts │ │ │ │ │ ├── parse-snippet.ts │ │ │ │ │ ├── test-data/ │ │ │ │ │ │ └── snippet.ts │ │ │ │ │ └── validation/ │ │ │ │ │ ├── index.ts │ │ │ │ │ └── validation.ts │ │ │ │ ├── index.ts │ │ │ │ ├── interfaces/ │ │ │ │ │ ├── branch.interface.ts │ │ │ │ │ ├── commit-info.interface.ts │ │ │ │ │ ├── github-auth.interface.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── pull-request.intreface.ts │ │ │ │ │ ├── repo.interface.ts │ │ │ │ │ ├── snippet.ts │ │ │ │ │ └── user.interface.ts │ │ │ │ └── services/ │ │ │ │ ├── github.service.ts │ │ │ │ ├── snippet.service.spec.ts │ │ │ │ └── snippet.service.ts │ │ │ ├── assets/ │ │ │ │ └── .gitkeep │ │ │ ├── environments/ │ │ │ │ ├── environment.prod.ts │ │ │ │ └── environment.ts │ │ │ ├── index.html │ │ │ ├── main.ts │ │ │ ├── polyfills.ts │ │ │ ├── styles.scss │ │ │ └── test.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ ├── blog/ │ │ ├── browserslist │ │ ├── jest.config.js │ │ ├── src/ │ │ │ ├── app/ │ │ │ │ ├── app.component.html │ │ │ │ ├── app.component.scss │ │ │ │ ├── app.component.spec.ts │ │ │ │ ├── app.component.ts │ │ │ │ ├── app.module.ts │ │ │ │ ├── common.ts │ │ │ │ ├── feed/ │ │ │ │ │ ├── feed.component.html │ │ │ │ │ ├── feed.component.scss │ │ │ │ │ ├── feed.component.spec.ts │ │ │ │ │ └── feed.component.ts │ │ │ │ ├── form/ │ │ │ │ │ ├── form.component.html │ │ │ │ │ ├── form.component.scss │ │ │ │ │ ├── form.component.spec.ts │ │ │ │ │ └── form.component.ts │ │ │ │ ├── post/ │ │ │ │ │ ├── post.component.html │ │ │ │ │ ├── post.component.scss │ │ │ │ │ ├── post.component.spec.ts │ │ │ │ │ └── post.component.ts │ │ │ │ ├── post.service.ts │ │ │ │ └── single-post/ │ │ │ │ ├── single-post.component.html │ │ │ │ ├── single-post.component.scss │ │ │ │ └── single-post.component.ts │ │ │ ├── assets/ │ │ │ │ ├── .gitkeep │ │ │ │ └── fonts/ │ │ │ │ └── droid-sans/ │ │ │ │ └── Apache License.txt │ │ │ ├── environments/ │ │ │ │ ├── environment.prod.ts │ │ │ │ └── environment.ts │ │ │ ├── index.html │ │ │ ├── main.ts │ │ │ ├── polyfills.ts │ │ │ ├── styles.scss │ │ │ └── test-setup.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ ├── codelab/ │ │ ├── browserslist │ │ ├── extra-webpack.config.js │ │ ├── karma.conf.js │ │ ├── src/ │ │ │ ├── app/ │ │ │ │ ├── admin/ │ │ │ │ │ ├── admin-routing.module.ts │ │ │ │ │ ├── admin.component.css │ │ │ │ │ ├── admin.component.html │ │ │ │ │ ├── admin.component.spec.ts │ │ │ │ │ ├── admin.component.ts │ │ │ │ │ ├── admin.module.ts │ │ │ │ │ ├── feedback/ │ │ │ │ │ │ ├── feedback-message-table/ │ │ │ │ │ │ │ ├── feedback-message-table.component.ts │ │ │ │ │ │ │ ├── feedback-message-table.css │ │ │ │ │ │ │ └── feedback-message-table.html │ │ │ │ │ │ ├── feedback.component.css │ │ │ │ │ │ ├── feedback.component.html │ │ │ │ │ │ ├── feedback.component.spec.ts │ │ │ │ │ │ ├── feedback.component.ts │ │ │ │ │ │ ├── feedback.module.ts │ │ │ │ │ │ └── github.service.ts │ │ │ │ │ └── users/ │ │ │ │ │ ├── users.component.css │ │ │ │ │ ├── users.component.html │ │ │ │ │ ├── users.component.spec.ts │ │ │ │ │ ├── users.component.ts │ │ │ │ │ └── users.module.ts │ │ │ │ ├── app-routing.module.ts │ │ │ │ ├── app.component.ts │ │ │ │ ├── app.module.ts │ │ │ │ ├── codelabs/ │ │ │ │ │ ├── about/ │ │ │ │ │ │ ├── about.component.html │ │ │ │ │ │ ├── about.component.scss │ │ │ │ │ │ ├── about.component.spec.ts │ │ │ │ │ │ ├── about.component.ts │ │ │ │ │ │ ├── about.module.ts │ │ │ │ │ │ └── samples/ │ │ │ │ │ │ ├── fake-types.d.ts.not-really │ │ │ │ │ │ ├── slides/ │ │ │ │ │ │ │ ├── ng-template.html │ │ │ │ │ │ │ ├── slide-component.html │ │ │ │ │ │ │ └── structural-directive.html │ │ │ │ │ │ └── storing-code/ │ │ │ │ │ │ ├── backticks.html │ │ │ │ │ │ ├── interpolations.ts │ │ │ │ │ │ └── plain.html │ │ │ │ │ ├── angular/ │ │ │ │ │ │ ├── angular-cli/ │ │ │ │ │ │ │ ├── angular-cli.component.css │ │ │ │ │ │ │ ├── angular-cli.component.html │ │ │ │ │ │ │ ├── angular-cli.component.ts │ │ │ │ │ │ │ └── angular-cli.module.ts │ │ │ │ │ │ ├── angular-routing.module.ts │ │ │ │ │ │ ├── angular.module.ts │ │ │ │ │ │ ├── component-tree/ │ │ │ │ │ │ │ ├── component-tree.component.css │ │ │ │ │ │ │ ├── component-tree.component.html │ │ │ │ │ │ │ ├── component-tree.component.ts │ │ │ │ │ │ │ ├── component-tree.module.ts │ │ │ │ │ │ │ ├── components-hierarchy-svg/ │ │ │ │ │ │ │ │ ├── components-hierarchy-svg.component.html │ │ │ │ │ │ │ │ ├── components-hierarchy-svg.component.spec.ts │ │ │ │ │ │ │ │ ├── components-hierarchy-svg.component.ts │ │ │ │ │ │ │ │ └── index.ts │ │ │ │ │ │ │ └── samples/ │ │ │ │ │ │ │ └── module/ │ │ │ │ │ │ │ ├── app.module.ts │ │ │ │ │ │ │ ├── box.component.ts │ │ │ │ │ │ │ ├── circle.component.ts │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ ├── create-first-app/ │ │ │ │ │ │ │ ├── create-first-app.component.css │ │ │ │ │ │ │ ├── create-first-app.component.html │ │ │ │ │ │ │ ├── create-first-app.component.ts │ │ │ │ │ │ │ ├── create-first-app.module.ts │ │ │ │ │ │ │ ├── mode/ │ │ │ │ │ │ │ │ ├── mode.component.css │ │ │ │ │ │ │ │ ├── mode.component.html │ │ │ │ │ │ │ │ ├── mode.component.spec.ts │ │ │ │ │ │ │ │ └── mode.component.ts │ │ │ │ │ │ │ └── samples/ │ │ │ │ │ │ │ ├── app-component/ │ │ │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ │ │ ├── app.module.ts │ │ │ │ │ │ │ │ ├── bootstrap.ts │ │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ │ └── index-html/ │ │ │ │ │ │ │ ├── bootstrap.ts │ │ │ │ │ │ │ └── index.html │ │ │ │ │ │ ├── custom-events/ │ │ │ │ │ │ │ ├── custom-events.component.css │ │ │ │ │ │ │ ├── custom-events.component.html │ │ │ │ │ │ │ ├── custom-events.component.ts │ │ │ │ │ │ │ └── custom-events.module.ts │ │ │ │ │ │ ├── dependency-injection/ │ │ │ │ │ │ │ ├── dependency-injection.component.css │ │ │ │ │ │ │ ├── dependency-injection.component.html │ │ │ │ │ │ │ ├── dependency-injection.component.ts │ │ │ │ │ │ │ └── dependency-injection.module.ts │ │ │ │ │ │ ├── forms/ │ │ │ │ │ │ │ ├── forms.component.css │ │ │ │ │ │ │ ├── forms.component.html │ │ │ │ │ │ │ ├── forms.component.ts │ │ │ │ │ │ │ ├── forms.module.ts │ │ │ │ │ │ │ └── samples/ │ │ │ │ │ │ │ └── basic/ │ │ │ │ │ │ │ ├── app.1.html │ │ │ │ │ │ │ ├── app.2.html │ │ │ │ │ │ │ ├── app.3.html │ │ │ │ │ │ │ ├── app.4.html │ │ │ │ │ │ │ ├── app.5.html │ │ │ │ │ │ │ ├── app.6.html │ │ │ │ │ │ │ ├── app.component.5.ts │ │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ │ ├── app.html │ │ │ │ │ │ │ ├── app.module.6.ts │ │ │ │ │ │ │ ├── app.module.ts │ │ │ │ │ │ │ ├── code.ts │ │ │ │ │ │ │ ├── main.ts │ │ │ │ │ │ │ └── styles.css │ │ │ │ │ │ ├── material/ │ │ │ │ │ │ │ ├── material.component.css │ │ │ │ │ │ │ ├── material.component.html │ │ │ │ │ │ │ ├── material.component.ts │ │ │ │ │ │ │ ├── material.module.ts │ │ │ │ │ │ │ └── samples/ │ │ │ │ │ │ │ ├── basic/ │ │ │ │ │ │ │ │ ├── app.1.html │ │ │ │ │ │ │ │ ├── app.2.html │ │ │ │ │ │ │ │ ├── app.3.html │ │ │ │ │ │ │ │ ├── app.4.html │ │ │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ │ │ ├── app.html │ │ │ │ │ │ │ │ ├── app.module.ts │ │ │ │ │ │ │ │ ├── code.ts │ │ │ │ │ │ │ │ └── main.ts │ │ │ │ │ │ │ ├── step1/ │ │ │ │ │ │ │ │ ├── app.html │ │ │ │ │ │ │ │ └── app.module.ts │ │ │ │ │ │ │ ├── step2/ │ │ │ │ │ │ │ │ ├── app.html │ │ │ │ │ │ │ │ └── app.module.ts │ │ │ │ │ │ │ ├── step3/ │ │ │ │ │ │ │ │ └── app.html │ │ │ │ │ │ │ └── step4/ │ │ │ │ │ │ │ ├── app.html │ │ │ │ │ │ │ └── app.module.ts │ │ │ │ │ │ ├── pipes/ │ │ │ │ │ │ │ ├── pipes.component.css │ │ │ │ │ │ │ ├── pipes.component.html │ │ │ │ │ │ │ ├── pipes.component.ts │ │ │ │ │ │ │ ├── pipes.module.ts │ │ │ │ │ │ │ └── samples/ │ │ │ │ │ │ │ └── pipes/ │ │ │ │ │ │ │ ├── app.component.html │ │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ │ └── app.module.ts │ │ │ │ │ │ ├── playground/ │ │ │ │ │ │ │ ├── angular-sample.ts │ │ │ │ │ │ │ ├── playground.component.css │ │ │ │ │ │ │ ├── playground.component.html │ │ │ │ │ │ │ ├── playground.component.spec.ts │ │ │ │ │ │ │ ├── playground.component.ts │ │ │ │ │ │ │ └── playground.module.ts │ │ │ │ │ │ ├── router/ │ │ │ │ │ │ │ ├── router.component.css │ │ │ │ │ │ │ ├── router.component.html │ │ │ │ │ │ │ ├── router.component.ts │ │ │ │ │ │ │ ├── router.module.ts │ │ │ │ │ │ │ └── samples/ │ │ │ │ │ │ │ └── simple-router/ │ │ │ │ │ │ │ ├── app.component.html │ │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ │ ├── app.module.ts │ │ │ │ │ │ │ ├── code.ts │ │ │ │ │ │ │ ├── components/ │ │ │ │ │ │ │ │ ├── kitten.ts │ │ │ │ │ │ │ │ └── puppy.ts │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ └── main.ts │ │ │ │ │ │ ├── structural-directives/ │ │ │ │ │ │ │ ├── bsod.css │ │ │ │ │ │ │ ├── samples/ │ │ │ │ │ │ │ │ ├── mat-tab-nav-bar/ │ │ │ │ │ │ │ │ │ ├── alert.component.ts │ │ │ │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ │ │ │ ├── app.module.ts │ │ │ │ │ │ │ │ │ └── tab.component.ts │ │ │ │ │ │ │ │ ├── material-tabs/ │ │ │ │ │ │ │ │ │ ├── alert.component.ts │ │ │ │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ │ │ │ ├── app.html │ │ │ │ │ │ │ │ │ ├── app.module.ts │ │ │ │ │ │ │ │ │ ├── app.solved.html │ │ │ │ │ │ │ │ │ ├── break-my-computer.component.ts │ │ │ │ │ │ │ │ │ ├── style.css │ │ │ │ │ │ │ │ │ └── taet-led.component.ts │ │ │ │ │ │ │ │ ├── material-tabs-structural-directive/ │ │ │ │ │ │ │ │ │ ├── alert.component.ts │ │ │ │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ │ │ │ ├── app.html │ │ │ │ │ │ │ │ │ ├── app.module.ts │ │ │ │ │ │ │ │ │ ├── app.solved.html │ │ │ │ │ │ │ │ │ ├── hideme.directive.solved.ts │ │ │ │ │ │ │ │ │ ├── hideme.directive.ts │ │ │ │ │ │ │ │ │ └── ignored.module.ts │ │ │ │ │ │ │ │ ├── micro-syntax/ │ │ │ │ │ │ │ │ │ ├── code.ts │ │ │ │ │ │ │ │ │ ├── ms.spec.ts │ │ │ │ │ │ │ │ │ └── ms.ts │ │ │ │ │ │ │ │ └── structural-directives/ │ │ │ │ │ │ │ │ ├── microsyntax.html │ │ │ │ │ │ │ │ ├── ng-for-after.html │ │ │ │ │ │ │ │ ├── ng-for-before.html │ │ │ │ │ │ │ │ ├── ng-if-after.html │ │ │ │ │ │ │ │ └── ng-if-before.html │ │ │ │ │ │ │ ├── structural-directives.component.css │ │ │ │ │ │ │ ├── structural-directives.component.html │ │ │ │ │ │ │ ├── structural-directives.component.ts │ │ │ │ │ │ │ └── structural-directives.module.ts │ │ │ │ │ │ ├── templates/ │ │ │ │ │ │ │ ├── samples/ │ │ │ │ │ │ │ │ ├── data-binding-extra/ │ │ │ │ │ │ │ │ │ ├── app.component.html │ │ │ │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ │ │ │ ├── app.module.ts │ │ │ │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ │ │ │ └── number-praiser.ts │ │ │ │ │ │ │ │ ├── event-binding/ │ │ │ │ │ │ │ │ │ ├── app.component.html │ │ │ │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ │ │ │ └── app.module.ts │ │ │ │ │ │ │ │ ├── event-binding-shortcuts/ │ │ │ │ │ │ │ │ │ └── app.component.html │ │ │ │ │ │ │ │ └── reference-binding/ │ │ │ │ │ │ │ │ ├── app.component.html │ │ │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ │ │ └── app.module.ts │ │ │ │ │ │ │ ├── templates.component.css │ │ │ │ │ │ │ ├── templates.component.html │ │ │ │ │ │ │ ├── templates.component.ts │ │ │ │ │ │ │ └── templates.module.ts │ │ │ │ │ │ └── typescript/ │ │ │ │ │ │ ├── typescript/ │ │ │ │ │ │ │ ├── code/ │ │ │ │ │ │ │ │ ├── app.ts │ │ │ │ │ │ │ │ ├── code.ts │ │ │ │ │ │ │ │ └── mini-exercise-test.ts │ │ │ │ │ │ │ ├── typescript-svg/ │ │ │ │ │ │ │ │ ├── typescript-svg.component.html │ │ │ │ │ │ │ │ ├── typescript-svg.component.spec.ts │ │ │ │ │ │ │ │ └── typescript-svg.component.ts │ │ │ │ │ │ │ ├── typescript.component.css │ │ │ │ │ │ │ ├── typescript.component.html │ │ │ │ │ │ │ └── typescript.component.ts │ │ │ │ │ │ ├── typescript-routing.module.ts │ │ │ │ │ │ └── typescript.module.ts │ │ │ │ │ ├── codelabs-routing.module.ts │ │ │ │ │ ├── codelabs.module.ts │ │ │ │ │ └── extra/ │ │ │ │ │ ├── code-playground/ │ │ │ │ │ │ ├── code-playground.component.css │ │ │ │ │ │ ├── code-playground.component.html │ │ │ │ │ │ ├── code-playground.component.ts │ │ │ │ │ │ └── code-playground.module.ts │ │ │ │ │ ├── extra-routing.module.ts │ │ │ │ │ ├── extra.module.ts │ │ │ │ │ ├── rating-summary/ │ │ │ │ │ │ ├── rating-summary.component.css │ │ │ │ │ │ ├── rating-summary.component.html │ │ │ │ │ │ ├── rating-summary.component.ts │ │ │ │ │ │ └── rating-summary.module.ts │ │ │ │ │ └── visual-studio-code/ │ │ │ │ │ ├── visual-studio-code.component.css │ │ │ │ │ ├── visual-studio-code.component.html │ │ │ │ │ ├── visual-studio-code.component.ts │ │ │ │ │ └── visual-studio-code.module.ts │ │ │ │ ├── common.ts │ │ │ │ ├── components/ │ │ │ │ │ ├── angular-routes/ │ │ │ │ │ │ ├── angular-routes.component.html │ │ │ │ │ │ ├── angular-routes.component.scss │ │ │ │ │ │ ├── angular-routes.component.ts │ │ │ │ │ │ └── angular-routes.module.ts │ │ │ │ │ ├── angular-test-runner/ │ │ │ │ │ │ ├── angular-test-runner.component.css │ │ │ │ │ │ ├── angular-test-runner.component.html │ │ │ │ │ │ ├── angular-test-runner.component.ts │ │ │ │ │ │ └── tests.ts │ │ │ │ │ ├── babel-test-runner/ │ │ │ │ │ │ ├── babel-helpers.ts │ │ │ │ │ │ ├── babel-test-runner.component.css │ │ │ │ │ │ ├── babel-test-runner.component.html │ │ │ │ │ │ └── babel-test-runner.component.ts │ │ │ │ │ ├── breadcrumb/ │ │ │ │ │ │ ├── breadcrumb.component.css │ │ │ │ │ │ ├── breadcrumb.component.html │ │ │ │ │ │ ├── breadcrumb.component.spec.ts │ │ │ │ │ │ └── breadcrumb.component.ts │ │ │ │ │ ├── buttons-nav-bar/ │ │ │ │ │ │ ├── buttons-nav-bar.component.html │ │ │ │ │ │ ├── buttons-nav-bar.component.scss │ │ │ │ │ │ ├── buttons-nav-bar.component.ts │ │ │ │ │ │ ├── buttons-nav-bar.module.ts │ │ │ │ │ │ ├── menu-fullscreen-widget/ │ │ │ │ │ │ │ ├── menu-fullscreen-widget.component.html │ │ │ │ │ │ │ ├── menu-fullscreen-widget.component.scss │ │ │ │ │ │ │ ├── menu-fullscreen-widget.component.spec.ts │ │ │ │ │ │ │ └── menu-fullscreen-widget.component.ts │ │ │ │ │ │ ├── menu-github-widget/ │ │ │ │ │ │ │ ├── menu-github-widget.component.css │ │ │ │ │ │ │ ├── menu-github-widget.component.html │ │ │ │ │ │ │ ├── menu-github-widget.component.ts │ │ │ │ │ │ │ └── menu-github-widget.module.ts │ │ │ │ │ │ └── menu-shortcut-widget/ │ │ │ │ │ │ ├── menu-shortcut-widget.component.css │ │ │ │ │ │ ├── menu-shortcut-widget.component.html │ │ │ │ │ │ ├── menu-shortcut-widget.component.ts │ │ │ │ │ │ └── menu-shortcut-widget.module.ts │ │ │ │ │ ├── codelab-components.module.ts │ │ │ │ │ ├── codelab-progress-bar/ │ │ │ │ │ │ ├── codelab-progress-bar.component.css │ │ │ │ │ │ ├── codelab-progress-bar.component.html │ │ │ │ │ │ └── codelab-progress-bar.component.ts │ │ │ │ │ ├── css/ │ │ │ │ │ │ └── codelab-styles.scss │ │ │ │ │ ├── exercise/ │ │ │ │ │ │ ├── exercise.component.css │ │ │ │ │ │ ├── exercise.component.html │ │ │ │ │ │ └── exercise.component.ts │ │ │ │ │ ├── exercise-playground/ │ │ │ │ │ │ ├── codelab-exercise-playground.component.css │ │ │ │ │ │ ├── codelab-exercise-playground.component.html │ │ │ │ │ │ └── codelab-exercise-playground.component.ts │ │ │ │ │ ├── exercise-preview/ │ │ │ │ │ │ ├── exercise-preview.component.html │ │ │ │ │ │ └── exercise-preview.component.ts │ │ │ │ │ ├── external-link-directive/ │ │ │ │ │ │ ├── external-link-directive.directive.spec.ts │ │ │ │ │ │ └── external-link-directive.directive.ts │ │ │ │ │ ├── index/ │ │ │ │ │ │ ├── index.component.html │ │ │ │ │ │ ├── index.component.scss │ │ │ │ │ │ ├── index.component.ts │ │ │ │ │ │ └── index.module.ts │ │ │ │ │ ├── login/ │ │ │ │ │ │ ├── login.component.css │ │ │ │ │ │ ├── login.component.html │ │ │ │ │ │ ├── login.component.spec.ts │ │ │ │ │ │ ├── login.component.ts │ │ │ │ │ │ └── login.module.ts │ │ │ │ │ ├── not-found/ │ │ │ │ │ │ ├── not-found.component.html │ │ │ │ │ │ ├── not-found.component.scss │ │ │ │ │ │ ├── not-found.component.ts │ │ │ │ │ │ └── not-found.module.ts │ │ │ │ │ ├── slides/ │ │ │ │ │ │ ├── closing-slide/ │ │ │ │ │ │ │ ├── codelab-closing-slide.component.css │ │ │ │ │ │ │ ├── codelab-closing-slide.component.html │ │ │ │ │ │ │ ├── codelab-closing-slide.component.spec.ts │ │ │ │ │ │ │ └── codelab-closing-slide.component.ts │ │ │ │ │ │ └── title-slide/ │ │ │ │ │ │ ├── ripple-animation/ │ │ │ │ │ │ │ ├── codelab-ripple-animation.component.css │ │ │ │ │ │ │ ├── codelab-ripple-animation.component.html │ │ │ │ │ │ │ ├── codelab-ripple-animation.component.spec.ts │ │ │ │ │ │ │ └── codelab-ripple-animation.component.ts │ │ │ │ │ │ ├── title-slide.component.css │ │ │ │ │ │ ├── title-slide.component.html │ │ │ │ │ │ ├── title-slide.component.spec.ts │ │ │ │ │ │ └── title-slide.component.ts │ │ │ │ │ └── slides-preview/ │ │ │ │ │ ├── codelab-preview.component.html │ │ │ │ │ ├── codelab-preview.component.scss │ │ │ │ │ └── codelab-preview.component.ts │ │ │ │ ├── containers/ │ │ │ │ │ ├── full-layout/ │ │ │ │ │ │ ├── full-layout.component.html │ │ │ │ │ │ ├── full-layout.component.scss │ │ │ │ │ │ ├── full-layout.component.ts │ │ │ │ │ │ ├── full-layout.module.ts │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── directives/ │ │ │ │ │ ├── directives.module.ts │ │ │ │ │ ├── nextSlide.directive.ts │ │ │ │ │ ├── permissions/ │ │ │ │ │ │ ├── abstract-permission.ts │ │ │ │ │ │ ├── can-load-admin/ │ │ │ │ │ │ │ └── can-load-admin.directive.ts │ │ │ │ │ │ └── is-logged-in/ │ │ │ │ │ │ └── is-loggef-in.directive.ts │ │ │ │ │ └── previousSlide.directive.ts │ │ │ │ ├── shared/ │ │ │ │ │ ├── angular-code/ │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ ├── app.module.ts │ │ │ │ │ │ ├── bootstrap.ts │ │ │ │ │ │ ├── code.ts │ │ │ │ │ │ └── index.html │ │ │ │ │ ├── helpers/ │ │ │ │ │ │ ├── codelabFile.ts │ │ │ │ │ │ └── helpers.ts │ │ │ │ │ ├── interfaces/ │ │ │ │ │ │ ├── exercise-config.ts │ │ │ │ │ │ ├── file-config.ts │ │ │ │ │ │ └── test-info.ts │ │ │ │ │ ├── services/ │ │ │ │ │ │ ├── access.service.ts │ │ │ │ │ │ └── guards/ │ │ │ │ │ │ ├── admin-guard.ts │ │ │ │ │ │ └── login-guard.ts │ │ │ │ │ └── shared.module.ts │ │ │ │ └── sync/ │ │ │ │ ├── sync.component.css │ │ │ │ ├── sync.component.html │ │ │ │ ├── sync.component.spec.ts │ │ │ │ ├── sync.component.ts │ │ │ │ └── sync.module.ts │ │ │ ├── assets/ │ │ │ │ └── .gitkeep │ │ │ ├── environments/ │ │ │ │ ├── environment.prod.ts │ │ │ │ └── environment.ts │ │ │ ├── index.html │ │ │ ├── locale/ │ │ │ │ ├── codelab.ru.xtb │ │ │ │ └── messages.xmb │ │ │ ├── main.ts │ │ │ ├── manifest.webmanifest │ │ │ ├── polyfills.ts │ │ │ ├── service-worker.js │ │ │ ├── styles.scss │ │ │ ├── test.ts │ │ │ └── typings.d.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ ├── kirjs/ │ │ ├── browserslist │ │ ├── karma.conf.js │ │ ├── src/ │ │ │ ├── app/ │ │ │ │ ├── app.component.css │ │ │ │ ├── app.component.html │ │ │ │ ├── app.component.spec.ts │ │ │ │ ├── app.component.ts │ │ │ │ ├── cv/ │ │ │ │ │ ├── resume.css │ │ │ │ │ ├── resume.html │ │ │ │ │ └── resume.scss │ │ │ │ ├── kirjs.module.ts │ │ │ │ └── modules/ │ │ │ │ ├── ast/ │ │ │ │ │ ├── ast-preview-runner/ │ │ │ │ │ │ ├── ast-preview-runner.component.css │ │ │ │ │ │ ├── ast-preview-runner.component.html │ │ │ │ │ │ ├── ast-preview-runner.component.ts │ │ │ │ │ │ └── ast-preview-runner.module.ts │ │ │ │ │ ├── ast.component.css │ │ │ │ │ ├── ast.component.html │ │ │ │ │ ├── ast.component.ts │ │ │ │ │ ├── ast.module.ts │ │ │ │ │ ├── babel-highlight/ │ │ │ │ │ │ └── babel-highlight-match.directive.ts │ │ │ │ │ ├── debugger/ │ │ │ │ │ │ ├── debugger.component.css │ │ │ │ │ │ ├── debugger.component.html │ │ │ │ │ │ ├── debugger.component.spec.ts │ │ │ │ │ │ ├── debugger.component.ts │ │ │ │ │ │ └── debugger.ts │ │ │ │ │ ├── new-progress-bar/ │ │ │ │ │ │ ├── new-progress-bar.component.css │ │ │ │ │ │ ├── new-progress-bar.component.html │ │ │ │ │ │ ├── new-progress-bar.component.ts │ │ │ │ │ │ └── new-progress-bar.module.ts │ │ │ │ │ ├── parse-hello-world-ast.ts │ │ │ │ │ ├── samples/ │ │ │ │ │ │ ├── dec-to-bin-with-semicolons.js │ │ │ │ │ │ ├── dec-to-bin.js │ │ │ │ │ │ ├── eslint/ │ │ │ │ │ │ │ ├── eslint.js │ │ │ │ │ │ │ └── eslint.test.js │ │ │ │ │ │ ├── find-console-log/ │ │ │ │ │ │ │ ├── find-console-log-babel.solved.ts │ │ │ │ │ │ │ ├── find-console-log-babel.ts │ │ │ │ │ │ │ ├── find-console-log-regex.solved.js │ │ │ │ │ │ │ ├── find-console-log.js │ │ │ │ │ │ │ ├── find-console-log.test.js │ │ │ │ │ │ │ ├── remove-console-log.solved.ts │ │ │ │ │ │ │ ├── remove-console-log.test.js │ │ │ │ │ │ │ ├── remove-console-log.ts │ │ │ │ │ │ │ ├── traverse-console-log-babel.solved.ts │ │ │ │ │ │ │ ├── traverse-console-log-babel.solved2.ts │ │ │ │ │ │ │ └── traverse-console-log-babel.ts │ │ │ │ │ │ ├── find-debugger/ │ │ │ │ │ │ │ ├── find-debugger-babel.solved.ts │ │ │ │ │ │ │ ├── find-debugger-babel.ts │ │ │ │ │ │ │ ├── find-debugger-regex.solved.js │ │ │ │ │ │ │ ├── find-debugger.js │ │ │ │ │ │ │ ├── find-debugger.test.js │ │ │ │ │ │ │ ├── hint.js │ │ │ │ │ │ │ ├── remove-debugger.solved.ts │ │ │ │ │ │ │ ├── remove-debugger.test.js │ │ │ │ │ │ │ ├── remove-debugger.ts │ │ │ │ │ │ │ ├── traverse-debugger-babel.solved.ts │ │ │ │ │ │ │ └── traverse-debugger-babel.ts │ │ │ │ │ │ ├── find-fit/ │ │ │ │ │ │ │ ├── find-fit.js │ │ │ │ │ │ │ ├── find-fit.solved.js │ │ │ │ │ │ │ └── find-fit.test.js │ │ │ │ │ │ ├── hello-world.json │ │ │ │ │ │ ├── it-lines/ │ │ │ │ │ │ │ ├── it-lines.js │ │ │ │ │ │ │ ├── it-lines.solved.js │ │ │ │ │ │ │ └── it-lines.test.js │ │ │ │ │ │ └── tricky.js │ │ │ │ │ ├── size-picker/ │ │ │ │ │ │ ├── size-picker.component.css │ │ │ │ │ │ ├── size-picker.component.html │ │ │ │ │ │ ├── size-picker.component.spec.ts │ │ │ │ │ │ ├── size-picker.component.ts │ │ │ │ │ │ └── size-picker.module.ts │ │ │ │ │ └── test-set/ │ │ │ │ │ ├── babel-test-runner/ │ │ │ │ │ │ ├── babel-test-runner.component.css │ │ │ │ │ │ ├── babel-test-runner.component.html │ │ │ │ │ │ └── babel-test-runner.component.ts │ │ │ │ │ ├── test-set.component.css │ │ │ │ │ ├── test-set.component.html │ │ │ │ │ ├── test-set.component.spec.ts │ │ │ │ │ └── test-set.component.ts │ │ │ │ ├── binary/ │ │ │ │ │ ├── angular-flags/ │ │ │ │ │ │ ├── angular-flags.component.css │ │ │ │ │ │ ├── angular-flags.component.html │ │ │ │ │ │ ├── angular-flags.component.spec.ts │ │ │ │ │ │ └── angular-flags.component.ts │ │ │ │ │ ├── ascii/ │ │ │ │ │ │ ├── ascii.component.css │ │ │ │ │ │ ├── ascii.component.html │ │ │ │ │ │ ├── ascii.component.spec.ts │ │ │ │ │ │ └── ascii.component.ts │ │ │ │ │ ├── binary-flat/ │ │ │ │ │ │ ├── binary-flat.component.css │ │ │ │ │ │ ├── binary-flat.component.html │ │ │ │ │ │ ├── binary-flat.component.spec.ts │ │ │ │ │ │ └── binary-flat.component.ts │ │ │ │ │ ├── binary-gif/ │ │ │ │ │ │ ├── binary-gif.component.css │ │ │ │ │ │ ├── binary-gif.component.html │ │ │ │ │ │ ├── binary-gif.component.spec.ts │ │ │ │ │ │ └── binary-gif.component.ts │ │ │ │ │ ├── binary-inline/ │ │ │ │ │ │ ├── binary-display/ │ │ │ │ │ │ │ ├── binary-display.component.css │ │ │ │ │ │ │ ├── binary-display.component.html │ │ │ │ │ │ │ ├── binary-display.component.spec.ts │ │ │ │ │ │ │ └── binary-display.component.ts │ │ │ │ │ │ ├── binary-inline.component.css │ │ │ │ │ │ ├── binary-inline.component.html │ │ │ │ │ │ ├── binary-inline.component.spec.ts │ │ │ │ │ │ ├── binary-inline.component.ts │ │ │ │ │ │ └── binary-inline.module.ts │ │ │ │ │ ├── binary-parser-demo/ │ │ │ │ │ │ ├── binary-parser-demo.component.css │ │ │ │ │ │ ├── binary-parser-demo.component.html │ │ │ │ │ │ ├── binary-parser-demo.component.spec.ts │ │ │ │ │ │ └── binary-parser-demo.component.ts │ │ │ │ │ ├── binary-plain/ │ │ │ │ │ │ ├── binary-plain.component.css │ │ │ │ │ │ ├── binary-plain.component.html │ │ │ │ │ │ ├── binary-plain.component.spec.ts │ │ │ │ │ │ └── binary-plain.component.ts │ │ │ │ │ ├── binary-view/ │ │ │ │ │ │ ├── array/ │ │ │ │ │ │ │ ├── array.component.css │ │ │ │ │ │ │ ├── array.component.html │ │ │ │ │ │ │ ├── array.component.spec.ts │ │ │ │ │ │ │ └── array.component.ts │ │ │ │ │ │ ├── binary-parent/ │ │ │ │ │ │ │ ├── binary-parent.component.html │ │ │ │ │ │ │ ├── binary-parent.component.scss │ │ │ │ │ │ │ ├── binary-parent.component.spec.ts │ │ │ │ │ │ │ └── binary-parent.component.ts │ │ │ │ │ │ ├── binary-view.module.ts │ │ │ │ │ │ ├── bits/ │ │ │ │ │ │ │ ├── bits.component.css │ │ │ │ │ │ │ ├── bits.component.html │ │ │ │ │ │ │ ├── bits.component.spec.ts │ │ │ │ │ │ │ └── bits.component.ts │ │ │ │ │ │ ├── block/ │ │ │ │ │ │ │ ├── block.component.css │ │ │ │ │ │ │ ├── block.component.html │ │ │ │ │ │ │ ├── block.component.spec.ts │ │ │ │ │ │ │ └── block.component.ts │ │ │ │ │ │ ├── color/ │ │ │ │ │ │ │ ├── color.component.css │ │ │ │ │ │ │ ├── color.component.html │ │ │ │ │ │ │ ├── color.component.spec.ts │ │ │ │ │ │ │ └── color.component.ts │ │ │ │ │ │ ├── hex/ │ │ │ │ │ │ │ ├── hex.component.css │ │ │ │ │ │ │ ├── hex.component.html │ │ │ │ │ │ │ ├── hex.component.spec.ts │ │ │ │ │ │ │ └── hex.component.ts │ │ │ │ │ │ ├── inline/ │ │ │ │ │ │ │ ├── inline.component.css │ │ │ │ │ │ │ ├── inline.component.html │ │ │ │ │ │ │ ├── inline.component.spec.ts │ │ │ │ │ │ │ └── inline.component.ts │ │ │ │ │ │ ├── inline-root/ │ │ │ │ │ │ │ ├── inline-root.component.css │ │ │ │ │ │ │ ├── inline-root.component.html │ │ │ │ │ │ │ ├── inline-root.component.spec.ts │ │ │ │ │ │ │ └── inline-root.component.ts │ │ │ │ │ │ ├── number/ │ │ │ │ │ │ │ ├── number.component.css │ │ │ │ │ │ │ ├── number.component.html │ │ │ │ │ │ │ ├── number.component.spec.ts │ │ │ │ │ │ │ └── number.component.ts │ │ │ │ │ │ ├── object/ │ │ │ │ │ │ │ ├── object.component.css │ │ │ │ │ │ │ ├── object.component.html │ │ │ │ │ │ │ ├── object.component.spec.ts │ │ │ │ │ │ │ └── object.component.ts │ │ │ │ │ │ └── string/ │ │ │ │ │ │ ├── string.component.css │ │ │ │ │ │ ├── string.component.html │ │ │ │ │ │ ├── string.component.spec.ts │ │ │ │ │ │ └── string.component.ts │ │ │ │ │ ├── binary.component.html │ │ │ │ │ ├── binary.component.scss │ │ │ │ │ ├── binary.component.ts │ │ │ │ │ ├── binary.module.ts │ │ │ │ │ ├── bindec/ │ │ │ │ │ │ ├── bindec.component.css │ │ │ │ │ │ ├── bindec.component.html │ │ │ │ │ │ ├── bindec.component.spec.ts │ │ │ │ │ │ └── bindec.component.ts │ │ │ │ │ ├── bit/ │ │ │ │ │ │ ├── bit.component.css │ │ │ │ │ │ ├── bit.component.html │ │ │ │ │ │ ├── bit.component.spec.ts │ │ │ │ │ │ └── bit.component.ts │ │ │ │ │ ├── bitwise/ │ │ │ │ │ │ ├── bitwise.component.css │ │ │ │ │ │ ├── bitwise.component.html │ │ │ │ │ │ ├── bitwise.component.spec.ts │ │ │ │ │ │ └── bitwise.component.ts │ │ │ │ │ ├── color-indexing/ │ │ │ │ │ │ ├── color-indexing.component.css │ │ │ │ │ │ ├── color-indexing.component.html │ │ │ │ │ │ ├── color-indexing.component.spec.ts │ │ │ │ │ │ └── color-indexing.component.ts │ │ │ │ │ ├── compare/ │ │ │ │ │ │ ├── compare.component.css │ │ │ │ │ │ ├── compare.component.html │ │ │ │ │ │ ├── compare.component.spec.ts │ │ │ │ │ │ └── compare.component.ts │ │ │ │ │ ├── fake-gif/ │ │ │ │ │ │ ├── fake-gif.component.css │ │ │ │ │ │ ├── fake-gif.component.html │ │ │ │ │ │ ├── fake-gif.component.spec.ts │ │ │ │ │ │ ├── fake-gif.component.ts │ │ │ │ │ │ ├── gif-parser.ts │ │ │ │ │ │ └── gif.ts │ │ │ │ │ ├── gif-palette/ │ │ │ │ │ │ ├── gif-palette.component.css │ │ │ │ │ │ ├── gif-palette.component.html │ │ │ │ │ │ ├── gif-palette.component.spec.ts │ │ │ │ │ │ └── gif-palette.component.ts │ │ │ │ │ ├── hexdec/ │ │ │ │ │ │ ├── hexdec.component.css │ │ │ │ │ │ ├── hexdec.component.html │ │ │ │ │ │ ├── hexdec.component.spec.ts │ │ │ │ │ │ └── hexdec.component.ts │ │ │ │ │ ├── html-post/ │ │ │ │ │ │ ├── html-post.component.css │ │ │ │ │ │ ├── html-post.component.html │ │ │ │ │ │ ├── html-post.component.spec.ts │ │ │ │ │ │ └── html-post.component.ts │ │ │ │ │ ├── json/ │ │ │ │ │ │ ├── json.component.html │ │ │ │ │ │ ├── json.component.scss │ │ │ │ │ │ ├── json.component.spec.ts │ │ │ │ │ │ └── json.component.ts │ │ │ │ │ ├── memory/ │ │ │ │ │ │ ├── memory.component.css │ │ │ │ │ │ ├── memory.component.html │ │ │ │ │ │ ├── memory.component.spec.ts │ │ │ │ │ │ └── memory.component.ts │ │ │ │ │ ├── message/ │ │ │ │ │ │ ├── message.component.css │ │ │ │ │ │ ├── message.component.html │ │ │ │ │ │ ├── message.component.spec.ts │ │ │ │ │ │ └── message.component.ts │ │ │ │ │ ├── midi/ │ │ │ │ │ │ ├── midi.component.css │ │ │ │ │ │ ├── midi.component.html │ │ │ │ │ │ ├── midi.component.spec.ts │ │ │ │ │ │ └── midi.component.ts │ │ │ │ │ ├── parser/ │ │ │ │ │ │ ├── binary-parser.spec.ts │ │ │ │ │ │ ├── binary-parser.ts │ │ │ │ │ │ ├── parsers/ │ │ │ │ │ │ │ ├── abstract-parser.ts │ │ │ │ │ │ │ ├── array-parser.spec.ts │ │ │ │ │ │ │ ├── array-parser.ts │ │ │ │ │ │ │ ├── bit-parser.spec.ts │ │ │ │ │ │ │ ├── bit-parser.ts │ │ │ │ │ │ │ ├── choice-parser.spec.ts │ │ │ │ │ │ │ ├── choice-parser.ts │ │ │ │ │ │ │ ├── common.ts │ │ │ │ │ │ │ ├── debugger-parser.ts │ │ │ │ │ │ │ ├── first-bit-parser.spec.ts │ │ │ │ │ │ │ ├── object-parser.spec.ts │ │ │ │ │ │ │ ├── object-parser.ts │ │ │ │ │ │ │ ├── string-parser.spec.ts │ │ │ │ │ │ │ ├── string-parser.ts │ │ │ │ │ │ │ └── var-uint-parser.ts │ │ │ │ │ │ ├── readers/ │ │ │ │ │ │ │ ├── abstract-reader.ts │ │ │ │ │ │ │ └── string-reader.ts │ │ │ │ │ │ └── utils.ts │ │ │ │ │ ├── shared.ts │ │ │ │ │ └── to-read/ │ │ │ │ │ ├── to-read.component.css │ │ │ │ │ ├── to-read.component.html │ │ │ │ │ ├── to-read.component.spec.ts │ │ │ │ │ └── to-read.component.ts │ │ │ │ ├── cellular-automation/ │ │ │ │ │ ├── board/ │ │ │ │ │ │ ├── board.component.css │ │ │ │ │ │ ├── board.component.html │ │ │ │ │ │ ├── board.component.spec.ts │ │ │ │ │ │ └── board.component.ts │ │ │ │ │ ├── cellular-automation-routing.module.ts │ │ │ │ │ ├── cellular-automation.component.css │ │ │ │ │ ├── cellular-automation.component.html │ │ │ │ │ ├── cellular-automation.component.ts │ │ │ │ │ ├── cellular-automation.module.ts │ │ │ │ │ ├── oscilators/ │ │ │ │ │ │ ├── oscilators.component.css │ │ │ │ │ │ ├── oscilators.component.html │ │ │ │ │ │ ├── oscilators.component.spec.ts │ │ │ │ │ │ └── oscilators.component.ts │ │ │ │ │ ├── rule/ │ │ │ │ │ │ ├── rule.component.css │ │ │ │ │ │ ├── rule.component.html │ │ │ │ │ │ ├── rule.component.spec.ts │ │ │ │ │ │ └── rule.component.ts │ │ │ │ │ ├── rule3/ │ │ │ │ │ │ ├── rule3.component.css │ │ │ │ │ │ ├── rule3.component.html │ │ │ │ │ │ ├── rule3.component.ts │ │ │ │ │ │ └── rule4/ │ │ │ │ │ │ ├── rule4.component.css │ │ │ │ │ │ ├── rule4.component.html │ │ │ │ │ │ ├── rule4.component.spec.ts │ │ │ │ │ │ └── rule4.component.ts │ │ │ │ │ └── rule8/ │ │ │ │ │ ├── rule8.component.css │ │ │ │ │ ├── rule8.component.html │ │ │ │ │ └── rule8.component.ts │ │ │ │ ├── gomoku/ │ │ │ │ │ ├── board/ │ │ │ │ │ │ ├── board.component.html │ │ │ │ │ │ ├── board.component.scss │ │ │ │ │ │ ├── board.component.spec.ts │ │ │ │ │ │ ├── board.component.ts │ │ │ │ │ │ └── board.module.ts │ │ │ │ │ ├── gomoku.component.css │ │ │ │ │ ├── gomoku.component.html │ │ │ │ │ ├── gomoku.component.ts │ │ │ │ │ ├── gomoku.module.ts │ │ │ │ │ ├── highlights.spec.ts │ │ │ │ │ ├── highlights.ts │ │ │ │ │ ├── renlib/ │ │ │ │ │ │ ├── I7.lib │ │ │ │ │ │ ├── lines.lib │ │ │ │ │ │ ├── moves.json │ │ │ │ │ │ ├── parse.js │ │ │ │ │ │ ├── ss.lib │ │ │ │ │ │ └── test.lib │ │ │ │ │ └── tools/ │ │ │ │ │ ├── tools.component.css │ │ │ │ │ ├── tools.component.html │ │ │ │ │ ├── tools.component.spec.ts │ │ │ │ │ └── tools.component.ts │ │ │ │ ├── gomoku-print/ │ │ │ │ │ ├── gomoku-print.component.css │ │ │ │ │ ├── gomoku-print.component.html │ │ │ │ │ ├── gomoku-print.component.ts │ │ │ │ │ ├── gomoku-print.module.ts │ │ │ │ │ ├── o/ │ │ │ │ │ │ ├── o.component.css │ │ │ │ │ │ ├── o.component.html │ │ │ │ │ │ ├── o.component.spec.ts │ │ │ │ │ │ └── o.component.ts │ │ │ │ │ └── x/ │ │ │ │ │ ├── x.component.css │ │ │ │ │ ├── x.component.html │ │ │ │ │ ├── x.component.spec.ts │ │ │ │ │ └── x.component.ts │ │ │ │ ├── home/ │ │ │ │ │ ├── home.component.css │ │ │ │ │ ├── home.component.html │ │ │ │ │ ├── home.component.spec.ts │ │ │ │ │ ├── home.component.ts │ │ │ │ │ ├── home.module.spec.ts │ │ │ │ │ ├── home.module.ts │ │ │ │ │ └── polaroid/ │ │ │ │ │ ├── polaroid.component.css │ │ │ │ │ ├── polaroid.component.html │ │ │ │ │ ├── polaroid.component.spec.ts │ │ │ │ │ └── polaroid.component.ts │ │ │ │ ├── msk/ │ │ │ │ │ ├── msk.component.css │ │ │ │ │ ├── msk.component.html │ │ │ │ │ ├── msk.component.spec.ts │ │ │ │ │ ├── msk.component.ts │ │ │ │ │ └── msk.module.ts │ │ │ │ ├── music/ │ │ │ │ │ ├── music.component.css │ │ │ │ │ ├── music.component.html │ │ │ │ │ ├── music.component.spec.ts │ │ │ │ │ ├── music.component.ts │ │ │ │ │ └── music.module.ts │ │ │ │ ├── qna/ │ │ │ │ │ ├── qna.component.css │ │ │ │ │ ├── qna.component.html │ │ │ │ │ ├── qna.component.spec.ts │ │ │ │ │ ├── qna.component.ts │ │ │ │ │ └── qna.module.ts │ │ │ │ ├── regex/ │ │ │ │ │ ├── live/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── live-mock/ │ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ │ ├── live-mock.component.css │ │ │ │ │ │ │ ├── live-mock.component.html │ │ │ │ │ │ │ ├── live-mock.component.spec.ts │ │ │ │ │ │ │ ├── live-mock.component.ts │ │ │ │ │ │ │ └── live-mock.module.ts │ │ │ │ │ │ ├── live.module.ts │ │ │ │ │ │ ├── live.service.spec.ts │ │ │ │ │ │ ├── live.service.ts │ │ │ │ │ │ └── poll/ │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ ├── poll.component.css │ │ │ │ │ │ ├── poll.component.html │ │ │ │ │ │ ├── poll.component.spec.ts │ │ │ │ │ │ ├── poll.component.ts │ │ │ │ │ │ └── poll.module.ts │ │ │ │ │ ├── regex.component.css │ │ │ │ │ ├── regex.component.html │ │ │ │ │ ├── regex.component.spec.ts │ │ │ │ │ ├── regex.component.ts │ │ │ │ │ └── regex.module.ts │ │ │ │ ├── stack/ │ │ │ │ │ ├── simple-stack/ │ │ │ │ │ │ ├── simple-stack.component.css │ │ │ │ │ │ ├── simple-stack.component.html │ │ │ │ │ │ ├── simple-stack.component.spec.ts │ │ │ │ │ │ └── simple-stack.component.ts │ │ │ │ │ ├── stack-game/ │ │ │ │ │ │ ├── stack-function/ │ │ │ │ │ │ │ ├── stack-function.component.css │ │ │ │ │ │ │ ├── stack-function.component.html │ │ │ │ │ │ │ ├── stack-function.component.spec.ts │ │ │ │ │ │ │ └── stack-function.component.ts │ │ │ │ │ │ ├── stack-function-button/ │ │ │ │ │ │ │ ├── stack-function-button.component.css │ │ │ │ │ │ │ ├── stack-function-button.component.html │ │ │ │ │ │ │ ├── stack-function-button.component.spec.ts │ │ │ │ │ │ │ └── stack-function-button.component.ts │ │ │ │ │ │ ├── stack-game.component.css │ │ │ │ │ │ ├── stack-game.component.html │ │ │ │ │ │ ├── stack-game.component.spec.ts │ │ │ │ │ │ └── stack-game.component.ts │ │ │ │ │ ├── stack-routing.module.ts │ │ │ │ │ ├── stack-test/ │ │ │ │ │ │ ├── stack-test.component.html │ │ │ │ │ │ ├── stack-test.component.scss │ │ │ │ │ │ ├── stack-test.component.spec.ts │ │ │ │ │ │ └── stack-test.component.ts │ │ │ │ │ ├── stack.component.css │ │ │ │ │ ├── stack.component.html │ │ │ │ │ ├── stack.component.spec.ts │ │ │ │ │ ├── stack.component.ts │ │ │ │ │ └── stack.module.ts │ │ │ │ ├── streaming/ │ │ │ │ │ ├── common.ts │ │ │ │ │ ├── overlay/ │ │ │ │ │ │ ├── overlay.component.html │ │ │ │ │ │ ├── overlay.component.scss │ │ │ │ │ │ ├── overlay.component.spec.ts │ │ │ │ │ │ └── overlay.component.ts │ │ │ │ │ └── streaming.module.ts │ │ │ │ ├── svg/ │ │ │ │ │ ├── samples/ │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ ├── app.module.ts │ │ │ │ │ │ ├── attr/ │ │ │ │ │ │ │ └── app.component.ts │ │ │ │ │ │ ├── bs.module.ts │ │ │ │ │ │ ├── chart/ │ │ │ │ │ │ │ └── app.component.ts │ │ │ │ │ │ ├── chart2/ │ │ │ │ │ │ │ ├── app.component.solved.ts │ │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ │ ├── app.module.ts │ │ │ │ │ │ │ └── ticks.component.ts │ │ │ │ │ │ ├── chart3/ │ │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ │ ├── app.module.ts │ │ │ │ │ │ │ └── ticks.component.ts │ │ │ │ │ │ ├── chart4/ │ │ │ │ │ │ │ ├── app.component.solved.ts │ │ │ │ │ │ │ ├── app.component.ts │ │ │ │ │ │ │ ├── app.module.ts │ │ │ │ │ │ │ └── ticks.component.ts │ │ │ │ │ │ ├── index.html │ │ │ │ │ │ ├── style.css │ │ │ │ │ │ ├── sub.component.ts │ │ │ │ │ │ └── svg/ │ │ │ │ │ │ └── app.component.ts │ │ │ │ │ ├── svg-demo/ │ │ │ │ │ │ ├── svg-demo.component.css │ │ │ │ │ │ ├── svg-demo.component.html │ │ │ │ │ │ ├── svg-demo.component.spec.ts │ │ │ │ │ │ └── svg-demo.component.ts │ │ │ │ │ ├── svg-playground/ │ │ │ │ │ │ ├── svg-playground.component.css │ │ │ │ │ │ ├── svg-playground.component.html │ │ │ │ │ │ ├── svg-playground.component.spec.ts │ │ │ │ │ │ └── svg-playground.component.ts │ │ │ │ │ ├── svg-together/ │ │ │ │ │ │ ├── svg-together.component.css │ │ │ │ │ │ ├── svg-together.component.html │ │ │ │ │ │ ├── svg-together.component.spec.ts │ │ │ │ │ │ └── svg-together.component.ts │ │ │ │ │ ├── svg-together-result/ │ │ │ │ │ │ ├── svg-together-result.component.css │ │ │ │ │ │ ├── svg-together-result.component.html │ │ │ │ │ │ ├── svg-together-result.component.spec.ts │ │ │ │ │ │ └── svg-together-result.component.ts │ │ │ │ │ ├── svg.component.css │ │ │ │ │ ├── svg.component.html │ │ │ │ │ ├── svg.component.ts │ │ │ │ │ ├── svg.module.ts │ │ │ │ │ └── timer/ │ │ │ │ │ ├── timer.component.css │ │ │ │ │ ├── timer.component.html │ │ │ │ │ ├── timer.component.spec.ts │ │ │ │ │ └── timer.component.ts │ │ │ │ ├── svg-race/ │ │ │ │ │ ├── finish/ │ │ │ │ │ │ ├── finish.component.css │ │ │ │ │ │ ├── finish.component.html │ │ │ │ │ │ ├── finish.component.spec.ts │ │ │ │ │ │ └── finish.component.ts │ │ │ │ │ ├── little-car/ │ │ │ │ │ │ ├── little-car.component.css │ │ │ │ │ │ ├── little-car.component.html │ │ │ │ │ │ ├── little-car.component.spec.ts │ │ │ │ │ │ └── little-car.component.ts │ │ │ │ │ ├── player/ │ │ │ │ │ │ ├── player.component.css │ │ │ │ │ │ ├── player.component.html │ │ │ │ │ │ ├── player.component.spec.ts │ │ │ │ │ │ └── player.component.ts │ │ │ │ │ ├── race/ │ │ │ │ │ │ ├── race.component.css │ │ │ │ │ │ ├── race.component.html │ │ │ │ │ │ ├── race.component.spec.ts │ │ │ │ │ │ └── race.component.ts │ │ │ │ │ ├── svg-race.component.css │ │ │ │ │ ├── svg-race.component.html │ │ │ │ │ ├── svg-race.component.ts │ │ │ │ │ ├── svg-race.module.ts │ │ │ │ │ └── timer/ │ │ │ │ │ ├── timer.component.css │ │ │ │ │ ├── timer.component.html │ │ │ │ │ ├── timer.component.spec.ts │ │ │ │ │ └── timer.component.ts │ │ │ │ ├── sync/ │ │ │ │ │ ├── sync.component.css │ │ │ │ │ ├── sync.component.html │ │ │ │ │ ├── sync.component.spec.ts │ │ │ │ │ ├── sync.component.ts │ │ │ │ │ └── sync.module.ts │ │ │ │ ├── test/ │ │ │ │ │ ├── test.component.css │ │ │ │ │ ├── test.component.html │ │ │ │ │ ├── test.component.spec.ts │ │ │ │ │ ├── test.component.ts │ │ │ │ │ └── test.module.ts │ │ │ │ └── webassembly/ │ │ │ │ ├── ca/ │ │ │ │ │ ├── ca.module.ts │ │ │ │ │ ├── single-cell/ │ │ │ │ │ │ ├── single-cell.component.css │ │ │ │ │ │ ├── single-cell.component.html │ │ │ │ │ │ ├── single-cell.component.spec.ts │ │ │ │ │ │ └── single-cell.component.ts │ │ │ │ │ └── single-grid/ │ │ │ │ │ ├── single-grid.component.css │ │ │ │ │ ├── single-grid.component.html │ │ │ │ │ ├── single-grid.component.spec.ts │ │ │ │ │ └── single-grid.component.ts │ │ │ │ ├── full-screen-runner/ │ │ │ │ │ ├── full-screen-runner.component.css │ │ │ │ │ ├── full-screen-runner.component.html │ │ │ │ │ ├── full-screen-runner.component.spec.ts │ │ │ │ │ ├── full-screen-runner.component.ts │ │ │ │ │ └── full-screen-runner.module.ts │ │ │ │ ├── monaco-wat.ts │ │ │ │ ├── samples/ │ │ │ │ │ ├── answer.wat │ │ │ │ │ ├── base.js │ │ │ │ │ ├── base.wat │ │ │ │ │ └── old.wat │ │ │ │ ├── tests/ │ │ │ │ │ ├── add-tests.ts │ │ │ │ │ ├── common.ts │ │ │ │ │ ├── disable-tests.ts │ │ │ │ │ ├── enable-tests.ts │ │ │ │ │ ├── evolve-cell.ts │ │ │ │ │ ├── evolve-row.ts │ │ │ │ │ ├── evolve.ts │ │ │ │ │ ├── get-cell-score.ts │ │ │ │ │ ├── get-index.ts │ │ │ │ │ ├── load-cell.ts │ │ │ │ │ ├── load-previous-cell.ts │ │ │ │ │ ├── rotate.ts │ │ │ │ │ ├── shift-tests.ts │ │ │ │ │ └── store-cell-tests.ts │ │ │ │ ├── utils.spec.ts │ │ │ │ ├── utils.ts │ │ │ │ ├── wasm-binary/ │ │ │ │ │ ├── test._wasm │ │ │ │ │ ├── wasm-binary.component.css │ │ │ │ │ ├── wasm-binary.component.html │ │ │ │ │ ├── wasm-binary.component.spec.ts │ │ │ │ │ ├── wasm-binary.component.ts │ │ │ │ │ └── wasm-parser.ts │ │ │ │ ├── webassembly-playground/ │ │ │ │ │ ├── error-message/ │ │ │ │ │ │ ├── error-message.component.css │ │ │ │ │ │ ├── error-message.component.html │ │ │ │ │ │ ├── error-message.component.spec.ts │ │ │ │ │ │ └── error-message.component.ts │ │ │ │ │ ├── monaco-directives/ │ │ │ │ │ │ ├── common.ts │ │ │ │ │ │ ├── monaco-js-position.directive.ts │ │ │ │ │ │ ├── monaco-load-answer.directive.ts │ │ │ │ │ │ ├── monaco-scrolling.directive.ts │ │ │ │ │ │ ├── monaco-wat-position.directive.spec.ts │ │ │ │ │ │ └── monaco-wat-position.directive.ts │ │ │ │ │ ├── runners/ │ │ │ │ │ │ └── wasm-test-runner/ │ │ │ │ │ │ ├── runner.js │ │ │ │ │ │ ├── wasm-test-runner.component.html │ │ │ │ │ │ ├── wasm-test-runner.component.scss │ │ │ │ │ │ ├── wasm-test-runner.component.spec.ts │ │ │ │ │ │ └── wasm-test-runner.component.ts │ │ │ │ │ ├── viz/ │ │ │ │ │ │ ├── grid/ │ │ │ │ │ │ │ ├── grid.component.css │ │ │ │ │ │ │ ├── grid.component.html │ │ │ │ │ │ │ ├── grid.component.spec.ts │ │ │ │ │ │ │ └── grid.component.ts │ │ │ │ │ │ ├── viz.component.css │ │ │ │ │ │ ├── viz.component.html │ │ │ │ │ │ ├── viz.component.spec.ts │ │ │ │ │ │ ├── viz.component.ts │ │ │ │ │ │ └── viz.module.ts │ │ │ │ │ ├── wasm-contents/ │ │ │ │ │ │ ├── wasm-contents.component.css │ │ │ │ │ │ ├── wasm-contents.component.html │ │ │ │ │ │ ├── wasm-contents.component.spec.ts │ │ │ │ │ │ └── wasm-contents.component.ts │ │ │ │ │ ├── web-assembly.service.spec.ts │ │ │ │ │ ├── web-assembly.service.ts │ │ │ │ │ ├── webassembly-code-mode/ │ │ │ │ │ │ ├── webassembly-code-mode.component.css │ │ │ │ │ │ ├── webassembly-code-mode.component.html │ │ │ │ │ │ ├── webassembly-code-mode.component.spec.ts │ │ │ │ │ │ └── webassembly-code-mode.component.ts │ │ │ │ │ ├── webassembly-playground.component.html │ │ │ │ │ ├── webassembly-playground.component.scss │ │ │ │ │ ├── webassembly-playground.component.spec.ts │ │ │ │ │ ├── webassembly-playground.component.ts │ │ │ │ │ └── webassembly-runner/ │ │ │ │ │ ├── webassembly-runner.component.css │ │ │ │ │ ├── webassembly-runner.component.html │ │ │ │ │ ├── webassembly-runner.component.spec.ts │ │ │ │ │ ├── webassembly-runner.component.ts │ │ │ │ │ └── webassembly-runner.module.ts │ │ │ │ ├── webassembly.component.html │ │ │ │ ├── webassembly.component.scss │ │ │ │ ├── webassembly.component.spec.ts │ │ │ │ ├── webassembly.component.ts │ │ │ │ └── webassembly.module.ts │ │ │ ├── assets/ │ │ │ │ ├── .gitkeep │ │ │ │ └── runner/ │ │ │ │ └── index.html │ │ │ ├── environments/ │ │ │ │ ├── environment.prod.ts │ │ │ │ └── environment.ts │ │ │ ├── index.html │ │ │ ├── locale/ │ │ │ │ ├── kirjs.ru.xtb │ │ │ │ └── messages.xmb │ │ │ ├── main.ts │ │ │ ├── polyfills.ts │ │ │ ├── styles.css │ │ │ └── test.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ ├── lis/ │ │ ├── browserslist │ │ ├── jest.config.js │ │ ├── src/ │ │ │ ├── app/ │ │ │ │ ├── app.component.css │ │ │ │ ├── app.component.html │ │ │ │ ├── app.component.spec.ts │ │ │ │ ├── app.component.ts │ │ │ │ ├── app.module.ts │ │ │ │ └── modules/ │ │ │ │ └── rxjs/ │ │ │ │ ├── rxjs.component.css │ │ │ │ ├── rxjs.component.html │ │ │ │ ├── rxjs.component.spec.ts │ │ │ │ ├── rxjs.component.ts │ │ │ │ └── rxjs.module.ts │ │ │ ├── assets/ │ │ │ │ └── .gitkeep │ │ │ ├── environments/ │ │ │ │ ├── environment.prod.ts │ │ │ │ └── environment.ts │ │ │ ├── index.html │ │ │ ├── main.ts │ │ │ ├── polyfills.ts │ │ │ ├── styles.css │ │ │ └── test-setup.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ └── playground/ │ ├── browserslist │ ├── jest.config.js │ ├── src/ │ │ ├── app/ │ │ │ ├── app.component.html │ │ │ ├── app.component.scss │ │ │ ├── app.component.spec.ts │ │ │ ├── app.component.ts │ │ │ ├── app.module.ts │ │ │ ├── code-sync/ │ │ │ │ ├── code-sync.component.css │ │ │ │ ├── code-sync.component.html │ │ │ │ ├── code-sync.component.spec.ts │ │ │ │ ├── code-sync.component.ts │ │ │ │ └── code-sync.module.ts │ │ │ └── playground/ │ │ │ ├── angular-sample.ts │ │ │ ├── playground.component.css │ │ │ ├── playground.component.html │ │ │ ├── playground.component.spec.ts │ │ │ ├── playground.component.ts │ │ │ └── playground.module.ts │ │ ├── assets/ │ │ │ └── .gitkeep │ │ ├── environments/ │ │ │ ├── environment.prod.ts │ │ │ └── environment.ts │ │ ├── index.html │ │ ├── main.ts │ │ ├── polyfills.ts │ │ ├── styles.scss │ │ └── test-setup.ts │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.spec.json │ └── tslint.json ├── create-issue.js ├── cypress/ │ ├── fixtures/ │ │ └── example.json │ ├── integration/ │ │ └── codelab/ │ │ └── home.spec.js │ ├── plugins/ │ │ ├── cy-ts-preprocessor.js │ │ └── index.js │ ├── support/ │ │ ├── commands.js │ │ └── index.js │ └── tsconfig.json ├── cypress.json ├── docs/ │ ├── CONTRIBUTING.md │ ├── HOSTING.md │ └── TRANSLATING.md ├── firebase.json ├── jest.config.js ├── libs/ │ ├── angular-ast-viz/ │ │ ├── karma.conf.js │ │ ├── ng-package.json │ │ ├── ng-package.prod.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ ├── lib/ │ │ │ │ ├── angular-ast-viz.module.spec.ts │ │ │ │ ├── angular-ast-viz.module.ts │ │ │ │ ├── app.component.css │ │ │ │ ├── app.component.html │ │ │ │ ├── app.component.spec.ts │ │ │ │ ├── app.component.ts │ │ │ │ └── ast-tree/ │ │ │ │ ├── ast-tree.component.css │ │ │ │ ├── ast-tree.component.html │ │ │ │ ├── ast-tree.component.spec.ts │ │ │ │ ├── ast-tree.component.ts │ │ │ │ ├── ast-tree.module.ts │ │ │ │ ├── short-name-babel.pipe.spec.ts │ │ │ │ └── short-name-babel.pipe.ts │ │ │ └── test.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ ├── angular-slides-to-pdf/ │ │ ├── karma.conf.js │ │ ├── ng-package.json │ │ ├── ng-package.prod.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ ├── lib/ │ │ │ │ ├── angular-slides-to-pdf.component.ts │ │ │ │ ├── angular-slides-to-pdf.module.spec.ts │ │ │ │ └── angular-slides-to-pdf.module.ts │ │ │ └── test.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ ├── browser/ │ │ ├── src/ │ │ │ ├── index.ts │ │ │ ├── lib/ │ │ │ │ ├── browser-window/ │ │ │ │ │ ├── browser-window.component.css │ │ │ │ │ ├── browser-window.component.html │ │ │ │ │ └── browser-window.component.ts │ │ │ │ ├── browser.module.ts │ │ │ │ ├── preview-window/ │ │ │ │ │ ├── preview-window.component.html │ │ │ │ │ ├── preview-window.component.scss │ │ │ │ │ └── preview-window.component.ts │ │ │ │ └── terminal-window/ │ │ │ │ ├── terminal-window.component.css │ │ │ │ ├── terminal-window.component.html │ │ │ │ └── terminal-window.component.ts │ │ │ └── test.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ ├── code-demos/ │ │ ├── assets/ │ │ │ └── runner/ │ │ │ ├── README.md │ │ │ ├── index.html │ │ │ ├── js/ │ │ │ │ ├── mocha.js │ │ │ │ ├── system-config.js │ │ │ │ └── test-bootstrap.js │ │ │ ├── ng-dts/ │ │ │ │ ├── bundler.ts │ │ │ │ └── files.txt │ │ │ ├── ng2/ │ │ │ │ ├── basic.ts │ │ │ │ ├── ng-bundle.js │ │ │ │ ├── ng2-runner.js │ │ │ │ └── rxjs.operators.ts │ │ │ ├── package.json │ │ │ └── tsconfig.json │ │ ├── index.ts │ │ ├── jest.config.js │ │ ├── ng-package.json │ │ ├── ng-package.prod.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ ├── lib/ │ │ │ │ ├── code-demo/ │ │ │ │ │ ├── code-demo.component.css │ │ │ │ │ ├── code-demo.component.html │ │ │ │ │ └── code-demo.component.ts │ │ │ │ ├── code-demo-editor/ │ │ │ │ │ ├── code-demo-editor.component.ts │ │ │ │ │ ├── code-demo-editor.injector.ts │ │ │ │ │ ├── directives/ │ │ │ │ │ │ ├── code-demo-editor.auto-folding.directive.spec.ts │ │ │ │ │ │ ├── code-demo-editor.auto-folding.directive.ts │ │ │ │ │ │ ├── code-demo-editor.highlight.directive.ts │ │ │ │ │ │ └── code-demo-editor.line-change.directive.ts │ │ │ │ │ ├── editor.component.css │ │ │ │ │ ├── themes/ │ │ │ │ │ │ └── devtools.json │ │ │ │ │ └── utils/ │ │ │ │ │ ├── utils.spec.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── code-demo-runner/ │ │ │ │ │ ├── code-demo-runner.component.css │ │ │ │ │ ├── code-demo-runner.component.html │ │ │ │ │ ├── code-demo-runner.component.spec.ts │ │ │ │ │ └── code-demo-runner.component.ts │ │ │ │ ├── code-demo.module.ts │ │ │ │ ├── code-demos.module.spec.ts │ │ │ │ ├── file-path/ │ │ │ │ │ ├── file-path.component.css │ │ │ │ │ ├── file-path.component.html │ │ │ │ │ ├── file-path.component.spec.ts │ │ │ │ │ └── file-path.component.ts │ │ │ │ ├── index.ts │ │ │ │ ├── multitab-editor/ │ │ │ │ │ ├── editor-from-model/ │ │ │ │ │ │ ├── editor-from-model.component.css │ │ │ │ │ │ ├── editor-from-model.component.html │ │ │ │ │ │ ├── editor-from-model.component.spec.ts │ │ │ │ │ │ └── editor-from-model.component.ts │ │ │ │ │ ├── multitab-editor.component.css │ │ │ │ │ ├── multitab-editor.component.html │ │ │ │ │ ├── multitab-editor.component.spec.ts │ │ │ │ │ └── multitab-editor.component.ts │ │ │ │ ├── realtime-eval/ │ │ │ │ │ ├── realtime-eval.component.css │ │ │ │ │ ├── realtime-eval.component.html │ │ │ │ │ ├── realtime-eval.component.spec.ts │ │ │ │ │ └── realtime-eval.component.ts │ │ │ │ ├── runner/ │ │ │ │ │ ├── compile-ts-files.ts │ │ │ │ │ └── prepare-templates.ts │ │ │ │ └── shared/ │ │ │ │ ├── deps-order.service.spec.ts │ │ │ │ ├── deps-order.service.ts │ │ │ │ ├── helpers.ts │ │ │ │ ├── index.ts │ │ │ │ ├── loop-protection.service.spec.ts │ │ │ │ ├── loop-protection.service.ts │ │ │ │ ├── monaco-config.service.spec.ts │ │ │ │ ├── monaco-config.service.ts │ │ │ │ ├── monaco-replay.ts │ │ │ │ ├── sandbox.ts │ │ │ │ ├── script-loader.service.ts │ │ │ │ ├── types-not-really.d.ts │ │ │ │ ├── types.ts │ │ │ │ ├── utils.ts │ │ │ │ └── visitor.ts │ │ │ └── test-setup.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ ├── console/ │ │ ├── karma.conf.js │ │ ├── src/ │ │ │ ├── index.ts │ │ │ ├── lib/ │ │ │ │ ├── console.component.css │ │ │ │ ├── console.component.html │ │ │ │ ├── console.component.spec.ts │ │ │ │ ├── console.component.ts │ │ │ │ ├── console.module.ts │ │ │ │ └── display-dynamic.component/ │ │ │ │ ├── display-dynamic-component.component.css │ │ │ │ ├── display-dynamic-component.component.html │ │ │ │ ├── display-dynamic-component.component.spec.ts │ │ │ │ └── display-dynamic-component.component.ts │ │ │ └── test.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ ├── feedback/ │ │ ├── karma.conf.js │ │ ├── ng-package.json │ │ ├── ng-package.prod.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ ├── lib/ │ │ │ │ ├── feedback-issue-dropdown/ │ │ │ │ │ ├── feedback-issue-dropdown.component.html │ │ │ │ │ ├── feedback-issue-dropdown.component.scss │ │ │ │ │ └── feedback-issue-dropdown.component.ts │ │ │ │ ├── feedback-rating/ │ │ │ │ │ ├── feedback-rating.component.css │ │ │ │ │ ├── feedback-rating.component.html │ │ │ │ │ └── feedback-rating.component.ts │ │ │ │ ├── feedback-widget/ │ │ │ │ │ ├── feedback-widget.component.html │ │ │ │ │ ├── feedback-widget.component.scss │ │ │ │ │ └── feedback-widget.component.ts │ │ │ │ ├── feedback.module.spec.ts │ │ │ │ ├── feedback.module.ts │ │ │ │ ├── feedback.service.spec.ts │ │ │ │ ├── feedback.service.ts │ │ │ │ └── message.ts │ │ │ └── test.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ ├── firebase/ │ │ ├── README.md │ │ ├── jest.config.js │ │ ├── src/ │ │ │ ├── index.ts │ │ │ ├── lib/ │ │ │ │ ├── firebase.module.spec.ts │ │ │ │ ├── firebase.module.ts │ │ │ │ ├── sync-fire-store.directive.spec.ts │ │ │ │ └── sync-fire-store.directive.ts │ │ │ └── test-setup.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ ├── firebase-login/ │ │ ├── index.ts │ │ ├── karma.conf.js │ │ ├── ng-package.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ ├── lib/ │ │ │ │ ├── firebase-login.module.spec.ts │ │ │ │ ├── firebase-login.module.ts │ │ │ │ ├── index.ts │ │ │ │ ├── login-widget/ │ │ │ │ │ ├── login-widget.component.css │ │ │ │ │ ├── login-widget.component.html │ │ │ │ │ ├── login-widget.component.spec.ts │ │ │ │ │ └── login-widget.component.ts │ │ │ │ ├── login.service.spec.ts │ │ │ │ └── login.service.ts │ │ │ └── test.ts │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ ├── intro/ │ │ ├── README.md │ │ ├── generate/ │ │ │ ├── generate-thumbnails.ts │ │ │ ├── helpers/ │ │ │ │ ├── const.ts │ │ │ │ ├── slides.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils.ts │ │ │ ├── slides.json │ │ │ └── tsconfig.json │ │ ├── jest.config.js │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ ├── lib/ │ │ │ │ ├── intro.component.css │ │ │ │ ├── intro.component.html │ │ │ │ ├── intro.component.ts │ │ │ │ └── intro.module.ts │ │ │ └── test-setup.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.lib.prod.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ ├── live/ │ │ ├── README.md │ │ ├── jest.config.js │ │ ├── ng-package.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ └── lib/ │ │ │ ├── live-service.service.spec.ts │ │ │ └── live-service.service.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ ├── slides/ │ │ ├── README.md │ │ ├── jest.config.js │ │ ├── ng-package.json │ │ ├── package.json │ │ ├── src/ │ │ │ ├── index.ts │ │ │ ├── lib/ │ │ │ │ ├── arrows/ │ │ │ │ │ ├── slides-arrows.component.css │ │ │ │ │ ├── slides-arrows.component.html │ │ │ │ │ └── slides-arrows.component.ts │ │ │ │ ├── deck/ │ │ │ │ │ ├── deck.component.html │ │ │ │ │ ├── deck.component.scss │ │ │ │ │ ├── deck.component.spec.ts │ │ │ │ │ └── deck.component.ts │ │ │ │ ├── full-screen-mode/ │ │ │ │ │ ├── full-screen-mode.service.spec.ts │ │ │ │ │ ├── full-screen-mode.service.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── routing/ │ │ │ │ │ ├── slide-routes.ts │ │ │ │ │ ├── slides-routing.directive.spec.ts │ │ │ │ │ └── slides-routing.directive.ts │ │ │ │ ├── shortcuts/ │ │ │ │ │ ├── shortcuts.directive.spec.ts │ │ │ │ │ └── shortcuts.directive.ts │ │ │ │ ├── slide/ │ │ │ │ │ ├── slide.directive.spec.ts │ │ │ │ │ └── slide.directive.ts │ │ │ │ ├── slides.module.spec.ts │ │ │ │ └── slides.module.ts │ │ │ └── test-setup.ts │ │ ├── tsconfig.json │ │ ├── tsconfig.lib.json │ │ ├── tsconfig.lib.prod.json │ │ ├── tsconfig.spec.json │ │ └── tslint.json │ └── utils/ │ ├── karma.conf.js │ ├── src/ │ │ ├── index.ts │ │ ├── lib/ │ │ │ ├── analytics/ │ │ │ │ ├── analytics.service.spec.ts │ │ │ │ └── analytics.service.ts │ │ │ ├── differ/ │ │ │ │ ├── diffFilesResolver.ts │ │ │ │ ├── differ.spec.ts │ │ │ │ ├── differ.ts │ │ │ │ └── fileHelpers.ts │ │ │ ├── github-PR-service/ │ │ │ │ ├── github.module.ts │ │ │ │ ├── github.service.ts │ │ │ │ └── index.ts │ │ │ ├── i18n/ │ │ │ │ └── i18n-tools.ts │ │ │ ├── index.ts │ │ │ ├── loaders/ │ │ │ │ └── loaders.ts │ │ │ ├── loading-indicator/ │ │ │ │ ├── loading-indicator/ │ │ │ │ │ ├── loading-indicator.component.css │ │ │ │ │ ├── loading-indicator.component.html │ │ │ │ │ ├── loading-indicator.component.spec.ts │ │ │ │ │ └── loading-indicator.component.ts │ │ │ │ └── loading-indicator.module.ts │ │ │ ├── pipes/ │ │ │ │ ├── pipes.module.ts │ │ │ │ └── safeHtml.pipe.ts │ │ │ ├── sandbox-runner/ │ │ │ │ ├── common.spec.ts │ │ │ │ ├── common.ts │ │ │ │ ├── runners/ │ │ │ │ │ ├── runner.ts │ │ │ │ │ ├── webworker.spec.ts │ │ │ │ │ └── webworker.ts │ │ │ │ ├── sandbox-runner.module.ts │ │ │ │ ├── test-runner-service.service.spec.ts │ │ │ │ ├── test-runner.component.html │ │ │ │ ├── test-runner.component.scss │ │ │ │ ├── test-runner.component.spec.ts │ │ │ │ ├── test-runner.component.ts │ │ │ │ ├── test-runner.service.ts │ │ │ │ ├── test-runner.spec.ts │ │ │ │ ├── test-runner.ts │ │ │ │ ├── typescript-checker-runner/ │ │ │ │ │ ├── typescript-checker-runner.component.css │ │ │ │ │ ├── typescript-checker-runner.component.html │ │ │ │ │ ├── typescript-checker-runner.component.spec.ts │ │ │ │ │ ├── typescript-checker-runner.component.ts │ │ │ │ │ └── typescript-checker-runner.module.ts │ │ │ │ └── typescript-test-runner/ │ │ │ │ ├── typescript-test-runner.component.css │ │ │ │ ├── typescript-test-runner.component.html │ │ │ │ ├── typescript-test-runner.component.spec.ts │ │ │ │ └── typescript-test-runner.component.ts │ │ │ ├── sync/ │ │ │ │ ├── common.ts │ │ │ │ ├── components/ │ │ │ │ │ ├── configure-sync/ │ │ │ │ │ │ ├── configure-sync.component.css │ │ │ │ │ │ ├── configure-sync.component.html │ │ │ │ │ │ ├── configure-sync.component.spec.ts │ │ │ │ │ │ ├── configure-sync.component.ts │ │ │ │ │ │ └── configure-sync.module.ts │ │ │ │ │ ├── online-indicator/ │ │ │ │ │ │ ├── online-indicator.component.css │ │ │ │ │ │ ├── online-indicator.component.html │ │ │ │ │ │ ├── online-indicator.component.spec.ts │ │ │ │ │ │ ├── online-indicator.component.ts │ │ │ │ │ │ └── online-indicator.module.ts │ │ │ │ │ ├── poll/ │ │ │ │ │ │ ├── common/ │ │ │ │ │ │ │ ├── bar-chart/ │ │ │ │ │ │ │ │ ├── bar-chart.component.html │ │ │ │ │ │ │ │ ├── bar-chart.component.scss │ │ │ │ │ │ │ │ ├── bar-chart.component.spec.ts │ │ │ │ │ │ │ │ ├── bar-chart.component.ts │ │ │ │ │ │ │ │ └── bar-chart.module.ts │ │ │ │ │ │ │ ├── common.ts │ │ │ │ │ │ │ ├── stars/ │ │ │ │ │ │ │ │ ├── stars.component.css │ │ │ │ │ │ │ │ ├── stars.component.html │ │ │ │ │ │ │ │ ├── stars.component.spec.ts │ │ │ │ │ │ │ │ ├── stars.component.ts │ │ │ │ │ │ │ │ └── stars.module.ts │ │ │ │ │ │ │ ├── sync-poll.service.spec.ts │ │ │ │ │ │ │ └── sync-poll.service.ts │ │ │ │ │ │ ├── sync-poll-admin/ │ │ │ │ │ │ │ ├── sync-poll-admin.component.css │ │ │ │ │ │ │ ├── sync-poll-admin.component.html │ │ │ │ │ │ │ ├── sync-poll-admin.component.spec.ts │ │ │ │ │ │ │ └── sync-poll-admin.component.ts │ │ │ │ │ │ ├── sync-poll-presenter/ │ │ │ │ │ │ │ ├── choice-presenter/ │ │ │ │ │ │ │ │ ├── choice-presenter.component.css │ │ │ │ │ │ │ │ ├── choice-presenter.component.html │ │ │ │ │ │ │ │ ├── choice-presenter.component.spec.ts │ │ │ │ │ │ │ │ └── choice-presenter.component.ts │ │ │ │ │ │ │ ├── leaderboard/ │ │ │ │ │ │ │ │ ├── leaderboard-presenter/ │ │ │ │ │ │ │ │ │ ├── leaderboard-presenter.component.css │ │ │ │ │ │ │ │ │ ├── leaderboard-presenter.component.html │ │ │ │ │ │ │ │ │ ├── leaderboard-presenter.component.spec.ts │ │ │ │ │ │ │ │ │ └── leaderboard-presenter.component.ts │ │ │ │ │ │ │ │ ├── leaderboard-viewer/ │ │ │ │ │ │ │ │ │ ├── leaderboard-viewer.component.css │ │ │ │ │ │ │ │ │ ├── leaderboard-viewer.component.html │ │ │ │ │ │ │ │ │ ├── leaderboard-viewer.component.spec.ts │ │ │ │ │ │ │ │ │ └── leaderboard-viewer.component.ts │ │ │ │ │ │ │ │ ├── leaderboard.component.css │ │ │ │ │ │ │ │ ├── leaderboard.component.html │ │ │ │ │ │ │ │ ├── leaderboard.component.spec.ts │ │ │ │ │ │ │ │ ├── leaderboard.component.ts │ │ │ │ │ │ │ │ └── leaderboard.module.ts │ │ │ │ │ │ │ ├── stars-presenter/ │ │ │ │ │ │ │ │ ├── stars-presenter.component.css │ │ │ │ │ │ │ │ ├── stars-presenter.component.html │ │ │ │ │ │ │ │ ├── stars-presenter.component.spec.ts │ │ │ │ │ │ │ │ └── stars-presenter.component.ts │ │ │ │ │ │ │ ├── sync-poll-presenter.component.css │ │ │ │ │ │ │ ├── sync-poll-presenter.component.html │ │ │ │ │ │ │ ├── sync-poll-presenter.component.spec.ts │ │ │ │ │ │ │ └── sync-poll-presenter.component.ts │ │ │ │ │ │ ├── sync-poll-viewer/ │ │ │ │ │ │ │ ├── sync-poll-viewer-choice/ │ │ │ │ │ │ │ │ ├── sync-poll-viewer-choice.component.css │ │ │ │ │ │ │ │ ├── sync-poll-viewer-choice.component.html │ │ │ │ │ │ │ │ ├── sync-poll-viewer-choice.component.spec.ts │ │ │ │ │ │ │ │ └── sync-poll-viewer-choice.component.ts │ │ │ │ │ │ │ ├── sync-poll-viewer.component.css │ │ │ │ │ │ │ ├── sync-poll-viewer.component.html │ │ │ │ │ │ │ ├── sync-poll-viewer.component.spec.ts │ │ │ │ │ │ │ └── sync-poll-viewer.component.ts │ │ │ │ │ │ ├── sync-poll.component.css │ │ │ │ │ │ ├── sync-poll.component.html │ │ │ │ │ │ ├── sync-poll.component.spec.ts │ │ │ │ │ │ ├── sync-poll.component.ts │ │ │ │ │ │ └── sync-poll.module.ts │ │ │ │ │ ├── questions/ │ │ │ │ │ │ ├── common/ │ │ │ │ │ │ │ ├── common.ts │ │ │ │ │ │ │ ├── question/ │ │ │ │ │ │ │ │ ├── question.component.html │ │ │ │ │ │ │ │ ├── question.component.scss │ │ │ │ │ │ │ │ ├── question.component.spec.ts │ │ │ │ │ │ │ │ └── question.component.ts │ │ │ │ │ │ │ ├── question-list/ │ │ │ │ │ │ │ │ ├── question-list.component.css │ │ │ │ │ │ │ │ ├── question-list.component.html │ │ │ │ │ │ │ │ ├── question-list.component.spec.ts │ │ │ │ │ │ │ │ └── question-list.component.ts │ │ │ │ │ │ │ ├── questions.service.spec.ts │ │ │ │ │ │ │ └── questions.service.ts │ │ │ │ │ │ ├── questions-admin/ │ │ │ │ │ │ │ ├── questions-admin.component.css │ │ │ │ │ │ │ ├── questions-admin.component.html │ │ │ │ │ │ │ ├── questions-admin.component.spec.ts │ │ │ │ │ │ │ └── questions-admin.component.ts │ │ │ │ │ │ ├── questions-presenter/ │ │ │ │ │ │ │ ├── questions-presenter.component.css │ │ │ │ │ │ │ ├── questions-presenter.component.html │ │ │ │ │ │ │ ├── questions-presenter.component.spec.ts │ │ │ │ │ │ │ └── questions-presenter.component.ts │ │ │ │ │ │ ├── questions-viewer/ │ │ │ │ │ │ │ ├── questions-viewer.component.css │ │ │ │ │ │ │ ├── questions-viewer.component.html │ │ │ │ │ │ │ ├── questions-viewer.component.spec.ts │ │ │ │ │ │ │ └── questions-viewer.component.ts │ │ │ │ │ │ ├── questions.component.css │ │ │ │ │ │ ├── questions.component.html │ │ │ │ │ │ ├── questions.component.spec.ts │ │ │ │ │ │ ├── questions.component.ts │ │ │ │ │ │ └── questions.module.ts │ │ │ │ │ ├── registration/ │ │ │ │ │ │ ├── registration-admin/ │ │ │ │ │ │ │ ├── registration-admin.component.css │ │ │ │ │ │ │ ├── registration-admin.component.html │ │ │ │ │ │ │ ├── registration-admin.component.spec.ts │ │ │ │ │ │ │ └── registration-admin.component.ts │ │ │ │ │ │ ├── registration-presenter/ │ │ │ │ │ │ │ ├── registration-presenter.component.css │ │ │ │ │ │ │ ├── registration-presenter.component.html │ │ │ │ │ │ │ ├── registration-presenter.component.spec.ts │ │ │ │ │ │ │ └── registration-presenter.component.ts │ │ │ │ │ │ ├── registration-viewer/ │ │ │ │ │ │ │ ├── registration-viewer.component.css │ │ │ │ │ │ │ ├── registration-viewer.component.html │ │ │ │ │ │ │ ├── registration-viewer.component.spec.ts │ │ │ │ │ │ │ └── registration-viewer.component.ts │ │ │ │ │ │ ├── registration.component.css │ │ │ │ │ │ ├── registration.component.html │ │ │ │ │ │ ├── registration.component.spec.ts │ │ │ │ │ │ ├── registration.component.ts │ │ │ │ │ │ ├── sync-registration.module.ts │ │ │ │ │ │ ├── sync-registration.service.spec.ts │ │ │ │ │ │ └── sync-registration.service.ts │ │ │ │ │ ├── sync-code-game/ │ │ │ │ │ │ ├── sync-code-game-admin/ │ │ │ │ │ │ │ ├── sync-code-game-admin.component.css │ │ │ │ │ │ │ ├── sync-code-game-admin.component.html │ │ │ │ │ │ │ ├── sync-code-game-admin.component.spec.ts │ │ │ │ │ │ │ └── sync-code-game-admin.component.ts │ │ │ │ │ │ ├── sync-code-game-presenter/ │ │ │ │ │ │ │ ├── sync-code-game-presenter.component.css │ │ │ │ │ │ │ ├── sync-code-game-presenter.component.html │ │ │ │ │ │ │ ├── sync-code-game-presenter.component.spec.ts │ │ │ │ │ │ │ └── sync-code-game-presenter.component.ts │ │ │ │ │ │ ├── sync-code-game-viewer/ │ │ │ │ │ │ │ ├── sync-code-game-viewer.component.css │ │ │ │ │ │ │ ├── sync-code-game-viewer.component.html │ │ │ │ │ │ │ ├── sync-code-game-viewer.component.spec.ts │ │ │ │ │ │ │ └── sync-code-game-viewer.component.ts │ │ │ │ │ │ ├── sync-code-game.component.css │ │ │ │ │ │ ├── sync-code-game.component.html │ │ │ │ │ │ ├── sync-code-game.component.spec.ts │ │ │ │ │ │ ├── sync-code-game.component.ts │ │ │ │ │ │ ├── sync-code-game.module.ts │ │ │ │ │ │ ├── sync-code-game.service.spec.ts │ │ │ │ │ │ ├── sync-code-game.service.ts │ │ │ │ │ │ └── tests.ts │ │ │ │ │ ├── sync-join-instructions/ │ │ │ │ │ │ ├── sync-join-instructions.component.css │ │ │ │ │ │ ├── sync-join-instructions.component.html │ │ │ │ │ │ ├── sync-join-instructions.component.spec.ts │ │ │ │ │ │ ├── sync-join-instructions.component.ts │ │ │ │ │ │ └── sync-join-instructions.module.ts │ │ │ │ │ └── sync-sessions/ │ │ │ │ │ ├── sync-sessions.component.css │ │ │ │ │ ├── sync-sessions.component.html │ │ │ │ │ ├── sync-sessions.component.spec.ts │ │ │ │ │ ├── sync-sessions.component.ts │ │ │ │ │ └── sync-sessions.module.ts │ │ │ │ ├── directives/ │ │ │ │ │ ├── all-viewer-values.directive.ts │ │ │ │ │ ├── is-status.directive.ts │ │ │ │ │ ├── sync-directives.module.ts │ │ │ │ │ ├── sync-is-presenting.directive.spec.ts │ │ │ │ │ ├── sync-presenter-value.directive.ts │ │ │ │ │ ├── sync-user-value.directive.ts │ │ │ │ │ └── sync-viewer-value.directive.ts │ │ │ │ ├── services/ │ │ │ │ │ ├── common.ts │ │ │ │ │ ├── firebase-info.service.ts │ │ │ │ │ ├── sync-data.service.ts │ │ │ │ │ ├── sync-db-wrapper.service.spec.ts │ │ │ │ │ ├── sync-db.service.ts │ │ │ │ │ ├── sync-session.service.spec.ts │ │ │ │ │ └── sync-session.service.ts │ │ │ │ ├── sync-button/ │ │ │ │ │ ├── sync-button.component.css │ │ │ │ │ ├── sync-button.component.html │ │ │ │ │ ├── sync-button.component.spec.ts │ │ │ │ │ ├── sync-button.component.ts │ │ │ │ │ └── sync-button.module.ts │ │ │ │ ├── sync-playground/ │ │ │ │ │ ├── sync-playground-presenter/ │ │ │ │ │ │ ├── sync-playground-presenter.component.css │ │ │ │ │ │ ├── sync-playground-presenter.component.html │ │ │ │ │ │ ├── sync-playground-presenter.component.spec.ts │ │ │ │ │ │ └── sync-playground-presenter.component.ts │ │ │ │ │ ├── sync-playground-test/ │ │ │ │ │ │ ├── sync-playground-test.component.css │ │ │ │ │ │ ├── sync-playground-test.component.html │ │ │ │ │ │ ├── sync-playground-test.component.spec.ts │ │ │ │ │ │ └── sync-playground-test.component.ts │ │ │ │ │ ├── sync-playground.component.html │ │ │ │ │ ├── sync-playground.component.scss │ │ │ │ │ ├── sync-playground.component.spec.ts │ │ │ │ │ └── sync-playground.component.ts │ │ │ │ └── sync.module.ts │ │ │ ├── test-results/ │ │ │ │ ├── common.ts │ │ │ │ ├── file-aware-description/ │ │ │ │ │ ├── file-aware-description.component.css │ │ │ │ │ ├── file-aware-description.component.html │ │ │ │ │ ├── file-aware-description.component.spec.ts │ │ │ │ │ └── file-aware-description.component.ts │ │ │ │ ├── simple-tests-progress/ │ │ │ │ │ ├── simple-tests-progress.component.css │ │ │ │ │ ├── simple-tests-progress.component.html │ │ │ │ │ ├── simple-tests-progress.component.spec.ts │ │ │ │ │ ├── simple-tests-progress.component.ts │ │ │ │ │ └── simple-tests-progress.module.ts │ │ │ │ ├── test-results/ │ │ │ │ │ ├── test-results.component.html │ │ │ │ │ ├── test-results.component.scss │ │ │ │ │ ├── test-results.component.spec.ts │ │ │ │ │ └── test-results.component.ts │ │ │ │ ├── test-results.module.ts │ │ │ │ └── test-run-results/ │ │ │ │ ├── test-run-results.component.html │ │ │ │ ├── test-run-results.component.scss │ │ │ │ ├── test-run-results.component.spec.ts │ │ │ │ └── test-run-results.component.ts │ │ │ ├── testing/ │ │ │ │ └── mocks/ │ │ │ │ ├── angular-fire.ts │ │ │ │ └── sync-db-service.ts │ │ │ └── tracking/ │ │ │ ├── tracking.directive.ts │ │ │ └── tracking.module.ts │ │ └── test.ts │ ├── tsconfig.json │ ├── tsconfig.lib.json │ ├── tsconfig.spec.json │ └── tslint.json ├── ng2ts/ │ ├── .prettierignore │ ├── api.service.ts │ ├── app.component.ts │ ├── app.html │ ├── app.module.ts │ ├── code.ts │ ├── context/ │ │ ├── context.component.ts │ │ ├── context.html │ │ └── context.service.ts │ ├── data-binding/ │ │ ├── DataBinding.ts │ │ └── DataBindingModule.ts │ ├── fuzzy-pipe/ │ │ └── fuzzy.pipe.ts │ ├── main.ts │ ├── ng2ts.ts │ ├── search/ │ │ ├── search.component.html │ │ └── search.component.ts │ ├── style.css │ ├── tests/ │ │ ├── ThumbsComponentCreateTest.ts │ │ ├── ThumbsComponentUseTest.ts │ │ ├── bootstrapTest.ts │ │ ├── codelabTest.ts │ │ ├── contextComponentUseTest.ts │ │ ├── createComponentTest.ts │ │ ├── createModuleTest.ts │ │ ├── diInjectServiceTest.ts │ │ ├── diInjectServiceTestBabel.ts │ │ ├── formsTest.ts │ │ ├── fuzzyPipeCreateTest.ts │ │ ├── fuzzyPipeUseTest.ts │ │ ├── materialTest.ts │ │ ├── routerTest.ts │ │ ├── templateAddActionTest.ts │ │ ├── templateAllVideosTest.ts │ │ ├── templatePageSetupTest.ts │ │ ├── test.ts │ │ ├── togglePanelComponentCreateTest.ts │ │ ├── togglePanelComponentUseTest.ts │ │ ├── videoComponentCreateTest.ts │ │ └── videoComponentUseTest.ts │ ├── thumbs/ │ │ ├── thumbs.component.ts │ │ └── thumbs.html │ ├── thumbs.app.module.ts │ ├── toggle-panel/ │ │ ├── toggle-panel.component.ts │ │ └── toggle-panel.html │ ├── toggle-panel.app.module.ts │ ├── typescript-intro/ │ │ ├── Codelab.ts │ │ ├── Guest.ts │ │ └── Main.ts │ ├── upload/ │ │ ├── upload.component.html │ │ └── upload.component.ts │ ├── video/ │ │ ├── video-item.ts │ │ ├── video-materialized.component.html │ │ ├── video-wrapper.component.ts │ │ ├── video.component.html │ │ ├── video.component.ts │ │ ├── video.index.html │ │ └── video.service.ts │ ├── video.app.module.ts │ └── wrapper.component.ts ├── nx.json ├── package.json ├── tools/ │ ├── schematics/ │ │ └── slide/ │ │ ├── README.md │ │ ├── files/ │ │ │ ├── code.bs │ │ │ └── template.component.html │ │ ├── index.ts │ │ └── schema.json │ └── tsconfig.tools.json ├── tsconfig.json └── tslint.json ================================================ FILE CONTENTS ================================================ ================================================ FILE: .editorconfig ================================================ # Editor configuration, see http://editorconfig.org root = true [*] charset = utf-8 indent_style = space indent_size = 2 insert_final_newline = true trim_trailing_whitespace = true [*.md] max_line_length = off trim_trailing_whitespace = false ================================================ FILE: .firebaserc ================================================ { "projects": { "default": "angular-presentation", "kirjs": "kirjs-kirjs" }, "targets": { "angular-presentation": { "hosting": { "codelab": [ "angular-presentation" ], "kirjs": [ "kirjs-home" ], "codelab-next": [ "codelab-next" ], "lis": [ "lis-lis" ], "angular-ivy": [ "angular-ivy" ] } } } } ================================================ FILE: .flooignore ================================================ extern node_modules tmp vendor .idea/workspace.xml .idea/misc.xml ================================================ FILE: .gitattributes ================================================ src/assets/monaco linguist-vendored ================================================ FILE: .gitignore ================================================ # See http://help.github.com/ignore-files/ for more about ignoring files. # compiled output /dist /tmp /out-tsc # dependencies /node_modules yarn.lock # IDEs and editors /.idea .project .classpath .c9/ *.launch .settings/ *.sublime-workspace # IDE - VSCode .vscode/* !.vscode/settings.json !.vscode/tasks.json !.vscode/launch.json !.vscode/extensions.json # misc /.sass-cache /connect.lock /coverage /typings .firebase *.log .floo # e2e /e2e/*.js /e2e/*.map # OS generated files .DS_Store .DS_Store? ._* .Spotlight-V100 .Trashes ehthumbs.db Thumbs.db ================================================ FILE: .nvmrc ================================================ 10.17.0 ================================================ FILE: .prettierignore ================================================ ng2ts dist coverage libs/code-demos/assets/runner/ng2/ng-bundle.js libs/code-demos/assets/runner/ng-dts/files.txt ================================================ FILE: .prettierrc ================================================ { "singleQuote": true } ================================================ FILE: .travis.yml ================================================ dist: trusty language: node_js node_js: - "10" env: matrix: - TRIGGER="format:check" - TRIGGER="lint" - TRIGGER="ng build -- --prod --source-map=false --build-optimizer=false" script: - npm run $TRIGGER notifications: email: false cache: npm: true directories: - node_modules deploy: provider: firebase project: "angular-presentation" token: secure: "ed9U/81s4sR9ihLEogOdbXlKin0/vhJdk9XaLLTv6jVxGG/n40tcwwsCze6F3sBTibjq90OJHZn1ip+4uDdF+BzdzJ+hjU0SCR4B/Fi3OtxW73vz//ou1BktVrkHh+kzyS+3AvLQ9xvkz1h57vfzgfW8V/Jhy3PviZXvoPO4ttOQs2HwbO3C0BvwIaneRlNDudRz/rIQDHxHPaR6Hh3gG69vBc6D0eWhbMbFcWr6kHN3t2ki3APFR3omtNhZIdrFF9icSkeQ1fwiMKKfQaxmHXT1HZhxSCfvcqYwIqqidcFWTpABlt+2MNpWjnPLJV0l4D2HGSVF4RWtLFqfHhm/cyJTB/1ejDB92U4bA5W/tSGGgry5mgvD2pGrRHqHt+awtP9G4hgrZB3oADj3Q0nbj4VRDMgAYbAvrRYSFzxeXg50j7NwogGh02osAGpBhZzJA1I93c2yEPmcu+ysG1FkCnggh65LYX3Zjyu2eRyuPj0kaT+5OhFUanpCYO2cM/pGFMnG8+InvwLKUKfAtgUrP6yefd5CGJZaWbLGemNue6teAyMVWye4Ep2swY3DW0di5Ikn5lrV2V33yzeNd2OoSQbCaBbFLEYrlHw1+/7yNf928jj4vG0TYJwdUhlZDWdkogFIGx5cyu7dNEa5UnlzhJFkqIoo77kQrD0PKCyoAVg=" before_deploy: - npm run pre-deploy on: branch: release ================================================ FILE: .vscode/extensions.json ================================================ { "recommendations": [ "nrwl.angular-console", "angular.ng-template", "esbenp.prettier-vscode" ] } ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2017 Kirill Cherkashin Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ [![Join our Slack, #codelab channel](https://img.shields.io/badge/slack-%23codelab-yellowgreen)](https://nycjsorg.now.sh) [![Check out the demo](https://img.shields.io/badge/see%20the-Demo-brightgreen)](https://codelab.fun) # Angular Codelab - [codelab.fun](https://codelab.fun) Angular Codelab is a self-paced interactive Angular course: interactive exercises - 🔥 Everything in the browser - **no setup needed** - 🔥 **Interactive code samples** - Change the code and see the result immediately! - 🔥 Hands-on **Exercises** to solidify your knowledge - 🔥 **Free** - Made for Angular enthusiasts by Angular enthusiasts - 🔥 Written in Angular and **open source** Try it out yourself at [codelab.fun](https://codelab.fun) ## Help us make Codelab.fun better - [Contribute to the codebase](#contribute-to-the-codebase) - [Host a live Angular Codelab event in your city](#host-a-live-angular-codelab-event-in-your-city) - [Translate Codelab to your language](#translate-codelab-to-your-language) ### Contribute to the codebase Codelab.fun is an Angular app! It uses [angular-cli](https://cli.angular.io/) with [nx](https://nx.dev) and all materials are in a form of Angular components. ![image](https://user-images.githubusercontent.com/2545357/66277059-0edab300-e867-11e9-863e-340e6d888ea5.png) See what [Good first issues](https://github.com/codelab-fun/codelab/issues?q=is%3Aissue+is%3Aopen+label%3Agood-first-issue) are available. See [CONTRIBUTING.md](./docs/CONTRIBUTING.md) for detailed instructions. ### Host a live Angular Codelab event in your city We have hosted multiple live codelab events in 10+ cities, and you can help us host one in yours! See how in [HOSTING.md](./docs/HOSTING.md) People learning angular ### Translate Codelab to your language Currently codelab.fun is availble in English and Russian. See [TRANSLATING.md](./docs/TRANSLATING.md) for how you can help us translate the codelab into your language. Thanks to amazing [PoEditor](https://poeditor.com) for providing us with an open source licence ================================================ FILE: angular.json ================================================ { "$schema": "./node_modules/@angular/cli/lib/config/schema.json", "version": 1, "newProjectRoot": "", "projects": { "codelab": { "root": "apps/codelab", "sourceRoot": "apps/codelab/src", "projectType": "application", "architect": { "build": { "builder": "@angular-builders/custom-webpack:browser", "options": { "customWebpackConfig": { "path": "apps/codelab/extra-webpack.config.js" }, "outputPath": "dist/apps/codelab", "index": "apps/codelab/src/index.html", "main": "apps/codelab/src/main.ts", "tsConfig": "apps/codelab/tsconfig.app.json", "polyfills": "apps/codelab/src/polyfills.ts", "aot": true, "assets": [ "apps/codelab/src/assets", "apps/codelab/src/favicon.ico", "apps/codelab/src/manifest.webmanifest", { "glob": "**/*", "input": "node_modules/monaco-editor/", "output": "./assets/monaco/" }, { "glob": "**/*", "input": "libs/intro/assets/", "output": "./assets/intro/" }, { "glob": "**/*", "input": "libs/code-demos/assets/runner/", "output": "./assets/runner/" } ], "styles": ["apps/codelab/src/styles.scss"], "scripts": [] }, "configurations": { "ru": { "optimization": true, "outputHashing": "all", "sourceMap": false, "extractCss": true, "namedChunks": true, "aot": true, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, "baseHref": "/ru/", "deployUrl": "/ru/", "fileReplacements": [ { "replace": "apps/codelab/src/environments/environment.ts", "with": "apps/codelab/src/environments/environment.prod.ts" } ], "outputPath": "dist/apps/codelab/ru", "i18nFile": "apps/codelab/src/locale/codelab.ru.xtb", "i18nFormat": "xtb", "i18nLocale": "ru" }, "production": { "optimization": true, "outputHashing": "all", "sourceMap": false, "extractCss": true, "namedChunks": false, "aot": true, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, "fileReplacements": [ { "replace": "apps/codelab/src/environments/environment.ts", "with": "apps/codelab/src/environments/environment.prod.ts" } ], "serviceWorker": false } } }, "serve": { "builder": "@angular-builders/custom-webpack:dev-server", "options": { "browserTarget": "codelab:build" }, "configurations": { "production": { "browserTarget": "codelab:build:production" }, "ru": { "browserTarget": "codelab:build:ru" } } }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "browserTarget": "codelab:build" } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "main": "apps/codelab/src/test.ts", "karmaConfig": "apps/codelab/karma.conf.js", "polyfills": "apps/codelab/src/polyfills.ts", "tsConfig": "apps/codelab/tsconfig.spec.json", "scripts": [], "styles": ["apps/codelab/src/styles.scss"], "assets": [ "apps/codelab/src/assets", "apps/codelab/src/favicon.ico", "apps/codelab/src/manifest.webmanifest" ] } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "apps/codelab/tsconfig.app.json", "apps/codelab/tsconfig.spec.json" ], "exclude": [] } } } }, "browser": { "root": "libs/browser", "sourceRoot": "libs/browser/src", "projectType": "library", "prefix": "codelab", "architect": { "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "libs/browser/tsconfig.lib.json", "libs/browser/tsconfig.spec.json" ], "exclude": ["**/node_modules/**"] } } } }, "console": { "root": "libs/console", "sourceRoot": "libs/console/src", "projectType": "library", "prefix": "codelab", "architect": { "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "main": "libs/console/src/test.ts", "tsConfig": "libs/console/tsconfig.spec.json", "karmaConfig": "libs/console/karma.conf.js" } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "libs/console/tsconfig.lib.json", "libs/console/tsconfig.spec.json" ], "exclude": ["**/node_modules/**"] } } } }, "utils": { "root": "libs/utils", "sourceRoot": "libs/utils/src", "projectType": "library", "prefix": "codelab", "architect": { "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "main": "libs/utils/src/test.ts", "tsConfig": "libs/utils/tsconfig.spec.json", "karmaConfig": "libs/utils/karma.conf.js" } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "libs/utils/tsconfig.lib.json", "libs/utils/tsconfig.spec.json" ], "exclude": ["**/node_modules/**"] } } } }, "kirjs": { "root": "apps/kirjs/", "sourceRoot": "apps/kirjs/src", "projectType": "application", "prefix": "kirjs", "schematics": {}, "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "aot": true, "outputPath": "dist/apps/kirjs", "index": "apps/kirjs/src/index.html", "main": "apps/kirjs/src/main.ts", "polyfills": "apps/kirjs/src/polyfills.ts", "tsConfig": "apps/kirjs/tsconfig.app.json", "assets": [ { "glob": "**/*", "input": "node_modules/monaco-editor/", "output": "./assets/monaco/" }, "apps/kirjs/src/favicon.ico", "apps/kirjs/src/assets", { "glob": "**/*", "input": "libs/code-demos/assets/runner/", "output": "./assets/runner/" } ], "styles": ["apps/kirjs/src/styles.css"], "scripts": [] }, "configurations": { "ru": { "outputPath": "dist/apps/kirjs/ru", "aot": true, "i18nFile": "apps/kirjs/src/locale/kirjs.ru.xtb", "i18nFormat": "xtb", "i18nLocale": "ru" }, "production": { "fileReplacements": [ { "replace": "apps/kirjs/src/environments/environment.ts", "with": "apps/kirjs/src/environments/environment.prod.ts" } ], "optimization": true, "outputHashing": "all", "sourceMap": false, "extractCss": true, "namedChunks": false, "aot": true, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true } } }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "browserTarget": "kirjs:build" }, "configurations": { "production": { "browserTarget": "kirjs:build:production" }, "ru": { "browserTarget": "kirjs:build:ru" } } }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "browserTarget": "kirjs:build" }, "configurations": { "ru": { "outputPath": "locale/", "outFile": "messages.ru.untranslated.xlf", "i18nFormat": "xlf", "i18nLocale": "ru" } } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "main": "apps/kirjs/src/test.ts", "polyfills": "apps/kirjs/src/polyfills.ts", "tsConfig": "apps/kirjs/tsconfig.spec.json", "karmaConfig": "apps/kirjs/karma.conf.js", "styles": ["apps/kirjs/src/styles.css"], "scripts": [], "assets": ["apps/kirjs/src/favicon.ico", "apps/kirjs/src/assets"] } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "apps/kirjs/tsconfig.app.json", "apps/kirjs/tsconfig.spec.json" ], "exclude": ["**/node_modules/**"] } } } }, "angular-ast-viz": { "root": "libs/angular-ast-viz", "sourceRoot": "libs/angular-ast-viz/src", "projectType": "library", "prefix": "codelab", "architect": { "build": { "builder": "@nrwl/angular:package", "options": { "tsConfig": "libs/angular-ast-viz/tsconfig.lib.json", "project": "libs/angular-ast-viz/ng-package.json" }, "configurations": { "production": { "project": "libs/angular-ast-viz/ng-package.prod.json" } } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "main": "libs/angular-ast-viz/src/test.ts", "tsConfig": "libs/angular-ast-viz/tsconfig.spec.json", "karmaConfig": "libs/angular-ast-viz/karma.conf.js" } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "libs/angular-ast-viz/tsconfig.lib.json", "libs/angular-ast-viz/tsconfig.spec.json" ], "exclude": ["**/node_modules/**"] } } } }, "angular-slides-to-pdf": { "root": "libs/angular-slides-to-pdf", "sourceRoot": "libs/angular-slides-to-pdf/src", "projectType": "library", "prefix": "codelab", "architect": { "build": { "builder": "@nrwl/angular:package", "options": { "tsConfig": "libs/angular-slides-to-pdf/tsconfig.lib.json", "project": "libs/angular-slides-to-pdf/ng-package.json" }, "configurations": { "production": { "project": "libs/angular-slides-to-pdf/ng-package.prod.json" } } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "main": "libs/angular-slides-to-pdf/src/test.ts", "tsConfig": "libs/angular-slides-to-pdf/tsconfig.spec.json", "karmaConfig": "libs/angular-slides-to-pdf/karma.conf.js" } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "libs/angular-slides-to-pdf/tsconfig.lib.json", "libs/angular-slides-to-pdf/tsconfig.spec.json" ], "exclude": ["**/node_modules/**"] } } } }, "feedback": { "root": "libs/feedback", "sourceRoot": "libs/feedback/src", "projectType": "library", "prefix": "codelab", "architect": { "build": { "builder": "@nrwl/angular:package", "options": { "tsConfig": "libs/feedback/tsconfig.lib.json", "project": "libs/feedback/ng-package.json" } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "main": "libs/feedback/src/test.ts", "tsConfig": "libs/feedback/tsconfig.spec.json", "karmaConfig": "libs/feedback/karma.conf.js" } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "libs/feedback/tsconfig.lib.json", "libs/feedback/tsconfig.spec.json" ], "exclude": ["**/node_modules/**"] } } } }, "code-demos": { "root": "libs/code-demos", "sourceRoot": "libs/code-demos/src", "projectType": "library", "prefix": "codelab", "architect": { "build": { "builder": "@nrwl/angular:package", "options": { "tsConfig": "libs/code-demos/tsconfig.lib.json", "project": "libs/code-demos/ng-package.json" }, "configurations": { "production": { "project": "libs/code-demos/ng-package.prod.json" } } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "libs/code-demos/tsconfig.lib.json", "libs/code-demos/tsconfig.spec.json" ], "exclude": ["**/node_modules/**"] } }, "test": { "builder": "@nrwl/jest:jest", "options": { "jestConfig": "libs/code-demos/jest.config.js", "tsConfig": "libs/code-demos/tsconfig.spec.json", "setupFile": "libs/code-demos/src/test-setup.ts" } } } }, "live": { "root": "libs/live", "sourceRoot": "libs/live/src", "projectType": "library", "prefix": "live", "architect": { "build": { "builder": "@nrwl/angular:package", "options": { "tsConfig": "libs/live/tsconfig.lib.json", "project": "libs/live/ng-package.json" } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "libs/live/tsconfig.lib.json", "libs/live/tsconfig.spec.json" ], "exclude": ["**/node_modules/**"] } }, "test": { "builder": "@nrwl/jest:jest", "options": { "jestConfig": "libs/live/jest.config.js", "tsConfig": "libs/live/tsconfig.spec.json" } } }, "schematics": {} }, "firebase-login": { "root": "libs/firebase-login", "sourceRoot": "libs/firebase-login/src", "projectType": "library", "prefix": "angular-presentation", "architect": { "build": { "builder": "@nrwl/angular:package", "options": { "tsConfig": "libs/firebase-login/tsconfig.lib.json", "project": "libs/firebase-login/ng-package.json" } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "main": "libs/firebase-login/src/test.ts", "tsConfig": "libs/firebase-login/tsconfig.spec.json", "karmaConfig": "libs/firebase-login/karma.conf.js" } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "libs/firebase-login/tsconfig.lib.json", "libs/firebase-login/tsconfig.spec.json" ], "exclude": ["**/node_modules/**"] } } } }, "blog": { "root": "apps/blog/", "sourceRoot": "apps/blog/src", "projectType": "application", "prefix": "codelab", "schematics": { "@nrwl/schematics:component": { "style": "scss" } }, "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "outputPath": "dist/apps/blog", "index": "apps/blog/src/index.html", "main": "apps/blog/src/main.ts", "polyfills": "apps/blog/src/polyfills.ts", "tsConfig": "apps/blog/tsconfig.app.json", "assets": ["apps/blog/src/favicon.ico", "apps/blog/src/assets"], "styles": ["apps/blog/src/styles.scss"], "scripts": [] }, "configurations": { "production": { "fileReplacements": [ { "replace": "apps/blog/src/environments/environment.ts", "with": "apps/blog/src/environments/environment.prod.ts" } ], "optimization": true, "outputHashing": "all", "sourceMap": false, "extractCss": true, "namedChunks": false, "aot": true, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, "budgets": [ { "type": "initial", "maximumWarning": "2mb", "maximumError": "20mb" } ] } } }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "browserTarget": "blog:build" }, "configurations": { "production": { "browserTarget": "blog:build:production" } } }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "browserTarget": "blog:build" } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "apps/blog/tsconfig.app.json", "apps/blog/tsconfig.spec.json" ], "exclude": ["**/node_modules/**"] } }, "test": { "builder": "@nrwl/jest:jest", "options": { "jestConfig": "apps/blog/jest.config.js", "tsConfig": "apps/blog/tsconfig.spec.json", "setupFile": "apps/blog/src/test-setup.ts" } } } }, "angular-thirty-seconds": { "root": "apps/angular-thirty-seconds/", "sourceRoot": "apps/angular-thirty-seconds/src", "projectType": "application", "prefix": "codelab", "schematics": { "@nrwl/schematics:component": { "style": "scss" } }, "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "outputPath": "dist/apps/codelab/30", "index": "apps/angular-thirty-seconds/src/index.html", "main": "apps/angular-thirty-seconds/src/main.ts", "polyfills": "apps/angular-thirty-seconds/src/polyfills.ts", "tsConfig": "apps/angular-thirty-seconds/tsconfig.app.json", "assets": [ { "glob": "**/*", "input": "node_modules/monaco-editor/", "output": "./assets/monaco/" }, "apps/angular-thirty-seconds/src/favicon.ico", "apps/angular-thirty-seconds/src/assets", { "glob": "**/*", "input": "libs/code-demos/assets/runner/", "output": "./assets/runner/" } ], "styles": [ "apps/angular-thirty-seconds/src/styles.scss", "node_modules/prismjs/themes/prism-okaidia.css", "node_modules/prismjs/plugins/line-numbers/prism-line-numbers.css" ], "scripts": [ "node_modules/prismjs/prism.js", "node_modules/prismjs/components/prism-typescript.min.js", "node_modules/prismjs/plugins/line-numbers/prism-line-numbers.js" ] }, "configurations": { "production": { "fileReplacements": [ { "replace": "apps/angular-thirty-seconds/src/environments/environment.ts", "with": "apps/angular-thirty-seconds/src/environments/environment.prod.ts" } ], "baseHref": "/30/", "deployUrl": "/30/", "optimization": true, "outputHashing": "all", "sourceMap": false, "extractCss": true, "namedChunks": false, "aot": true, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, "budgets": [ { "type": "initial", "maximumWarning": "60mb", "maximumError": "70mb" } ] } } }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "browserTarget": "angular-thirty-seconds:build" }, "configurations": { "production": { "browserTarget": "angular-thirty-seconds:build:production" } } }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "browserTarget": "angular-thirty-seconds:build" } }, "test": { "builder": "@angular-devkit/build-angular:karma", "options": { "main": "apps/angular-thirty-seconds/src/test.ts", "polyfills": "apps/angular-thirty-seconds/src/polyfills.ts", "tsConfig": "apps/angular-thirty-seconds/tsconfig.spec.json", "karmaConfig": "apps/angular-thirty-seconds/karma.conf.js", "styles": ["apps/angular-thirty-seconds/src/styles.scss"], "scripts": [], "assets": [ "apps/angular-thirty-seconds/src/favicon.ico", "apps/angular-thirty-seconds/src/assets" ] } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "apps/angular-thirty-seconds/tsconfig.app.json", "apps/angular-thirty-seconds/tsconfig.spec.json" ], "exclude": ["**/node_modules/**"] } } } }, "lis": { "projectType": "application", "schematics": {}, "root": "apps/lis", "sourceRoot": "apps/lis/src", "prefix": "codelab", "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "outputPath": "dist/apps/lis", "index": "apps/lis/src/index.html", "main": "apps/lis/src/main.ts", "polyfills": "apps/lis/src/polyfills.ts", "tsConfig": "apps/lis/tsconfig.app.json", "aot": false, "assets": [ "apps/lis/src/favicon.ico", "apps/lis/src/assets", { "glob": "**/*", "input": "node_modules/monaco-editor/", "output": "./assets/monaco/" }, { "glob": "**/*", "input": "libs/code-demos/assets/runner/", "output": "./assets/runner/" } ], "styles": ["apps/lis/src/styles.css"], "scripts": [] }, "configurations": { "production": { "fileReplacements": [ { "replace": "apps/lis/src/environments/environment.ts", "with": "apps/lis/src/environments/environment.prod.ts" } ], "optimization": true, "outputHashing": "all", "sourceMap": false, "extractCss": true, "namedChunks": false, "aot": true, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, "budgets": [ { "type": "initial", "maximumWarning": "2mb", "maximumError": "5mb" }, { "type": "anyComponentStyle", "maximumWarning": "6kb", "maximumError": "10kb" } ] } } }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "browserTarget": "lis:build" }, "configurations": { "production": { "browserTarget": "lis:build:production" } } }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "browserTarget": "lis:build" } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "apps/lis/tsconfig.app.json", "apps/lis/tsconfig.spec.json" ], "exclude": ["**/node_modules/**", "!apps/lis/**"] } }, "test": { "builder": "@nrwl/jest:jest", "options": { "jestConfig": "apps/lis/jest.config.js", "tsConfig": "apps/lis/tsconfig.spec.json", "setupFile": "apps/lis/src/test-setup.ts" } } } }, "playground": { "projectType": "application", "schematics": { "@nrwl/angular:component": { "style": "scss" } }, "root": "apps/playground", "sourceRoot": "apps/playground/src", "prefix": "codelab", "architect": { "build": { "builder": "@angular-devkit/build-angular:browser", "options": { "outputPath": "dist/apps/playground", "index": "apps/playground/src/index.html", "main": "apps/playground/src/main.ts", "polyfills": "apps/playground/src/polyfills.ts", "tsConfig": "apps/playground/tsconfig.app.json", "aot": true, "assets": [ "apps/playground/src/favicon.ico", "apps/playground/src/assets", { "glob": "**/*", "input": "node_modules/monaco-editor/", "output": "./assets/monaco/" } ], "styles": ["apps/playground/src/styles.scss"], "scripts": [] }, "configurations": { "production": { "fileReplacements": [ { "replace": "apps/playground/src/environments/environment.ts", "with": "apps/playground/src/environments/environment.prod.ts" } ], "optimization": true, "outputHashing": "all", "sourceMap": false, "extractCss": true, "namedChunks": false, "aot": true, "extractLicenses": true, "vendorChunk": false, "buildOptimizer": true, "budgets": [ { "type": "initial", "maximumWarning": "20mb", "maximumError": "25mb" }, { "type": "anyComponentStyle", "maximumWarning": "6kb", "maximumError": "10kb" } ] } } }, "serve": { "builder": "@angular-devkit/build-angular:dev-server", "options": { "browserTarget": "playground:build" }, "configurations": { "production": { "browserTarget": "playground:build:production" } } }, "extract-i18n": { "builder": "@angular-devkit/build-angular:extract-i18n", "options": { "browserTarget": "playground:build" } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "apps/playground/tsconfig.app.json", "apps/playground/tsconfig.spec.json" ], "exclude": ["**/node_modules/**", "!apps/playground/**"] } }, "test": { "builder": "@nrwl/jest:jest", "options": { "jestConfig": "apps/playground/jest.config.js", "tsConfig": "apps/playground/tsconfig.spec.json", "setupFile": "apps/playground/src/test-setup.ts" } } } }, "firebase": { "projectType": "library", "root": "libs/firebase", "sourceRoot": "libs/firebase/src", "prefix": "codelab", "architect": { "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "libs/firebase/tsconfig.lib.json", "libs/firebase/tsconfig.spec.json" ], "exclude": ["**/node_modules/**", "!libs/firebase/**"] } }, "test": { "builder": "@nrwl/jest:jest", "options": { "jestConfig": "libs/firebase/jest.config.js", "tsConfig": "libs/firebase/tsconfig.spec.json", "setupFile": "libs/firebase/src/test-setup.ts" } } }, "schematics": {} }, "intro": { "projectType": "library", "root": "libs/intro", "sourceRoot": "libs/intro/src", "prefix": "codelab", "architect": { "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "libs/intro/tsconfig.lib.json", "libs/intro/tsconfig.spec.json" ], "exclude": ["**/node_modules/**", "!libs/intro/**"] } }, "test": { "builder": "@nrwl/jest:jest", "options": { "jestConfig": "libs/intro/jest.config.js", "tsConfig": "libs/intro/tsconfig.spec.json", "setupFile": "libs/intro/src/test-setup.ts" } } }, "schematics": {} }, "slides": { "projectType": "library", "root": "libs/slides", "sourceRoot": "libs/slides/src", "prefix": "codelab", "architect": { "build": { "builder": "@nrwl/angular:package", "options": { "tsConfig": "libs/slides/tsconfig.lib.json", "project": "libs/slides/ng-package.json" }, "configurations": { "production": { "tsConfig": "libs/slides/tsconfig.lib.prod.json" } } }, "lint": { "builder": "@angular-devkit/build-angular:tslint", "options": { "tsConfig": [ "libs/slides/tsconfig.lib.json", "libs/slides/tsconfig.spec.json" ], "exclude": ["**/node_modules/**", "!libs/slides/**"] } }, "test": { "builder": "@nrwl/jest:jest", "options": { "jestConfig": "libs/slides/jest.config.js", "tsConfig": "libs/slides/tsconfig.spec.json", "setupFile": "libs/slides/src/test-setup.ts" } } }, "schematics": {} } }, "defaultProject": "codelab", "schematics": { "@schematics/angular:component": { "prefix": "slides", "style": "css" }, "@schematics/angular:directive": { "prefix": "slides" }, "@nrwl/schematics:library": { "unitTestRunner": "karma", "framework": "angular" }, "@nrwl/schematics:application": { "unitTestRunner": "karma", "e2eTestRunner": "protractor" }, "@nrwl/schematics:node-application": { "framework": "express" }, "@nrwl/angular:application": { "unitTestRunner": "jest", "e2eTestRunner": "cypress" }, "@nrwl/angular:library": { "unitTestRunner": "jest" } }, "cli": { "defaultCollection": "@nrwl/angular", "analytics": "2d33a6dc-15e8-4227-b14e-dedd96805cec" } } ================================================ FILE: apps/angular-thirty-seconds/browserslist ================================================ # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers # For additional information regarding the format and rule options, please see: # https://github.com/browserslist/browserslist#queries # # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed > 0.5% last 2 versions Firefox ESR not dead not IE 9-11 ================================================ FILE: apps/angular-thirty-seconds/karma.conf.js ================================================ // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html module.exports = function(config) { config.set({ basePath: '../', frameworks: ['jasmine', '@angular-devkit/build-angular'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), require('@angular-devkit/build-angular/plugins/karma') ], client: { clearContext: false // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { dir: require('path').join(__dirname, '../coverage'), reports: ['html', 'lcovonly'], fixWebpackSourcePaths: true }, reporters: ['progress', 'kjhtml'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], singleRun: false }); }; ================================================ FILE: apps/angular-thirty-seconds/src/app/app.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'codelab-root', template: `
`, styles: [ ` .wrapper { margin: 0 60px; padding: 0 20px; } :host ::ng-deep { font-family: 'Helvetica Neue', sans-serif; font-weight: 300; padding: 0 20px; display: block; } ` ] }) export class AppComponent {} ================================================ FILE: apps/angular-thirty-seconds/src/app/app.module.ts ================================================ import { MatButtonModule } from '@angular/material/button'; import { MatTableModule } from '@angular/material/table'; import { BrowserModule } from '@angular/platform-browser'; import { APP_INITIALIZER, NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { RouterModule, Routes } from '@angular/router'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { monacoReady } from '@codelab/code-demos'; import { HttpClientModule } from '@angular/common/http'; import { CreateSnippetComponent } from './create-snippet/create-snippet.component'; import { CreateSnippetModule } from './create-snippet/create-snippet.module'; import { environment } from '../../../codelab/src/environments/environment'; import { AngularFireModule } from '@angular/fire'; import { PullRequestsListComponent } from './pull-requests-list/pull-requests-list.component'; export const angularFire = AngularFireModule.initializeApp( environment.firebaseConfig ); const routes: Routes = [ { path: '', redirectTo: 'list', pathMatch: 'full' }, { path: 'list', component: PullRequestsListComponent }, { path: 'new/:repoName/:repoOwner', component: CreateSnippetComponent }, { path: 'new/:repoName/:repoOwner/:pullNumber', component: CreateSnippetComponent } ]; @NgModule({ imports: [ angularFire, BrowserModule, BrowserAnimationsModule, RouterModule.forRoot(routes), MatButtonModule, MatTableModule, HttpClientModule, CreateSnippetModule ], declarations: [AppComponent, PullRequestsListComponent], providers: [ { provide: APP_INITIALIZER, useValue: monacoReady, multi: true } ], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/angular-thirty-seconds/src/app/create-snippet/create-snippet.component.html ================================================

{{ isEditing ? '' : 'New' }} Snippet

Maximum 120 symbols
This field is required
beginner intermediate advanced
This field is required
{{ tag }} {{ tag }} Maximum 5 tags
This field is required
{{ snippetForm.get('tags').errors['tagsError'] }}
This field is required
{{ snippetForm.get('content').errors['linesError'] }}

Add Bonus section Remove Bonus section

Add Links section Remove Links section

Add Demo section Remove Demo section

================================================ FILE: apps/angular-thirty-seconds/src/app/create-snippet/create-snippet.component.scss ================================================ :host { label { color: #3e515b; } textarea { width: 100%; height: 280px; } mat-form-field { width: 100%; } section { display: flex; margin: 30px 0; } .icon { justify-content: center; align-items: center; font-size: 15px; &-plus:before { content: '\FF0B'; border: 1px solid #444; border-radius: 50%; color: #444; } &-minus:before { content: '\FF0D'; border: 1px solid #444; border-radius: 50%; color: #444; } } .btn-submit { float: right; font-weight: 400; text-align: center; vertical-align: middle; border: 1px solid #213451; font-size: 14px; color: #fff; background-color: #213451; cursor: pointer; } .container { max-width: 850px; padding: 0 15px 30px 15px; margin: 0 auto; font-family: Helvetica Neue, Helvetica, Arial, sans-serif; } .input-info { display: block; height: 10px; line-height: 10px; font-size: 0.625em; margin-top: -15px; color: #8b8b8b; } .markdown-info { width: 50%; padding: 15px 35px; border: 1px solid #a4b7c1; margin-left: 30px; } .input-block { width: 300px; div { margin-bottom: 25px; width: 100%; } } .required { color: red; } .cursor-pointer { cursor: pointer; } .w { &-100 { width: 100%; } &-50 { width: 50%; } } .validation-message { color: red; position: absolute; width: 100%; height: 1em; margin-top: 5px; line-height: 1em; font-size: 0.75em; text-align: left; } } ================================================ FILE: apps/angular-thirty-seconds/src/app/create-snippet/create-snippet.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { CreateSnippetComponent } from './create-snippet.component'; import { CreateSnippetModule } from './create-snippet.module'; import { ActivatedRoute } from '@angular/router'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { SnippetService } from '../shared/services/snippet.service'; import SpyObj = jasmine.SpyObj; import { of } from 'rxjs'; describe('CreateSnippetComponent', () => { let component: CreateSnippetComponent; let fixture: ComponentFixture; let snippetService: SpyObj; let activatedRoute: Partial; beforeEach(async(() => { activatedRoute = { snapshot: { params: {} } } as any; snippetService = jasmine.createSpyObj('snippetService', ['fetchPR']); TestBed.configureTestingModule({ imports: [CreateSnippetModule, NoopAnimationsModule], providers: [ { provide: ActivatedRoute, useFactory: () => activatedRoute }, { provide: SnippetService, useValue: snippetService } ] }).compileComponents(); })); beforeEach(() => {}); xit('should create', () => { const repoName = 'name'; const repoOwner = 'PIKACHU'; const pullNumber = 689; activatedRoute = { snapshot: { params: { pullNumber, repoName, repoOwner } } } as any; snippetService.fetchPR.and.returnValue(of({})); fixture = TestBed.createComponent(CreateSnippetComponent); component = fixture.componentInstance; fixture.detectChanges(); expect(snippetService.fetchPR).toHaveBeenCalledWith( repoName, repoOwner, pullNumber ); }); }); ================================================ FILE: apps/angular-thirty-seconds/src/app/create-snippet/create-snippet.component.ts ================================================ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef, OnDestroy, ViewChild } from '@angular/core'; import { FormBuilder, Validators } from '@angular/forms'; import { ActivatedRoute } from '@angular/router'; import { MatAutocomplete, MatAutocompleteSelectedEvent } from '@angular/material/autocomplete'; import { MatChipInputEvent } from '@angular/material/chips'; import { MatDialog } from '@angular/material/dialog'; import { COMMA, ENTER } from '@angular/cdk/keycodes'; import { Observable } from 'rxjs/internal/Observable'; import { finalize, map } from 'rxjs/operators'; import { ReplaySubject } from 'rxjs/internal/ReplaySubject'; import { SnippetOverviewComponent } from './snippet-modal/snippet-overview.component'; import { angularSampleCode, LINKS_PLACEHOLDER, MARKDOWN_PLACEHOLDER, TAGS_LIST } from '../shared'; import { SnippetService } from '../shared/services/snippet.service'; import { markFormControlsAsTouched, validatorMaxLines, validatorMaxTags } from '../shared/functions/validation'; import { parseSnippet } from '../shared/functions/parse-snippet'; import { SEPARATOR } from '../shared/consts'; interface SnippetFileInfo { sha: string; fileName: string; snippet: string; branchName: string; } function importSnippet(snippet) { const result = { ...snippet }; result.links = (result.links || []).join(SEPARATOR); return result; } @Component({ selector: 'codelab-create-snippet', changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: './create-snippet.component.html', styleUrls: ['./create-snippet.component.scss'] }) export class CreateSnippetComponent implements OnDestroy { @ViewChild('tagInput', { static: false }) tagInput: ElementRef< HTMLInputElement >; @ViewChild('auto', { static: false }) matAutocomplete: MatAutocomplete; repoName: string; repoOwner: string; destroy = new ReplaySubject(1); isLoading = false; isEditing = false; snippetFileInfo: SnippetFileInfo; TAGS_LIST = TAGS_LIST; tags: string[] = ['tip']; filteredTags: Observable; snippetForm = this.fb.group({ title: ['', Validators.required], twitter: [''], level: ['beginner', Validators.required], tags: [this.tags, [Validators.required, validatorMaxTags(5)]], content: [ MARKDOWN_PLACEHOLDER, [Validators.required, validatorMaxLines(25)] ], bonus: [''], links: [LINKS_PLACEHOLDER], demo: [angularSampleCode] }); hasBonus = false; hasLinks = false; hasDemo = false; separatorKeysCodes: number[] = [ENTER, COMMA]; constructor( private fb: FormBuilder, private cd: ChangeDetectorRef, private activatedRoute: ActivatedRoute, private snippetService: SnippetService, public dialog: MatDialog ) { const pullNumber = this.activatedRoute.snapshot.params['pullNumber']; this.repoName = this.activatedRoute.snapshot.params['repoName']; this.repoOwner = this.activatedRoute.snapshot.params['repoOwner']; if (pullNumber) { this.isEditing = true; this.fetchPR(pullNumber); } this.filteredTags = this.snippetForm .get('tags') .valueChanges.pipe( map((tags: string) => tags ? this._filterTags(tags.slice(-1)[0]) : this.TAGS_LIST.slice() ) ); } fetchPR(pullNumber: number) { this.isLoading = true; this.snippetService .fetchPR(this.repoName, this.repoOwner, pullNumber) .pipe( finalize(() => { this.isLoading = false; this.cd.markForCheck(); }) ) .subscribe((snippetFileInfo: SnippetFileInfo) => { this.snippetFileInfo = snippetFileInfo; const snippet = importSnippet(parseSnippet(snippetFileInfo.snippet)); if (snippet.demo) { this.hasDemo = true; } if (snippet.bonus) { this.hasBonus = true; } if (snippet.links) { this.hasLinks = true; } this.snippetForm.patchValue(snippet); }); } ngOnDestroy() { this.destroy.next(null); this.destroy.complete(); } openPreview() { if (this.snippetForm.valid) { this.dialog.open(SnippetOverviewComponent, { data: { formValue: this.getPreparedFormValue(this.snippetForm.value), isEditing: this.isEditing, fileInfo: this.snippetFileInfo ? { sha: this.snippetFileInfo['sha'], fileName: this.snippetFileInfo['fileName'], branchName: this.snippetFileInfo['branchName'] } : null, repoName: this.repoName, repoOwner: this.repoOwner } }); } else { markFormControlsAsTouched(this.snippetForm); } } getPreparedFormValue(value) { Object.keys(value['demo']).forEach(x => { const isChangedAndNotEmpty = this.hasDemo && value.demo[x] && value.demo[x] !== angularSampleCode[x]; value['demo'][x] = isChangedAndNotEmpty ? value.demo[x] : null; }); value['bonus'] = this.hasBonus ? value['bonus'] : null; value['links'] = this.hasLinks ? value['links'] : null; return value; } addTag(event: MatChipInputEvent): void { if (!this.matAutocomplete.isOpen) { const input = event.input; const value = event.value; if ((value || '').trim()) { this.tags.push(value.trim()); } if (input) { input.value = ''; } this.snippetForm.get('tags').patchValue(this.tags); } } removeTag(tag: string): void { const index = this.tags.indexOf(tag); if (index >= 0) { this.tags.splice(index, 1); } this.snippetForm.get('tags').patchValue(this.tags); } selectedTags(event: MatAutocompleteSelectedEvent): void { this.tags.push(event.option.viewValue); this.tagInput.nativeElement.value = ''; this.snippetForm.get('tags').patchValue(this.tags); } private _filterTags(value: string): string[] { const filterValue = value ? value.toLowerCase() : null; return this.TAGS_LIST.filter( tag => tag.toLowerCase().indexOf(filterValue) === 0 ); } } ================================================ FILE: apps/angular-thirty-seconds/src/app/create-snippet/create-snippet.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { CreateSnippetComponent } from './create-snippet.component'; import { SnippetInfoComponent } from './snippet-info/snippet-info.component'; import { ReactiveFormsModule } from '@angular/forms'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { MatButtonModule } from '@angular/material/button'; import { MatChipsModule } from '@angular/material/chips'; import { MatDialogModule } from '@angular/material/dialog'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { MatSelectModule } from '@angular/material/select'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MarkdownModule } from 'ngx-markdown'; import { CodeDemoModule } from '@codelab/code-demos'; import { SnippetOverviewComponent } from './snippet-modal/snippet-overview.component'; import { SnippetSpinnerComponent } from './snippet-spinner/snippet-spinner.component'; import { AngularFireAuthModule } from '@angular/fire/auth'; import { RouterModule } from '@angular/router'; const MAT_MODULES = [ MatButtonModule, MatFormFieldModule, MatChipsModule, MatAutocompleteModule, MatSelectModule, MatInputModule, MatDialogModule, MatSnackBarModule ]; @NgModule({ declarations: [ CreateSnippetComponent, SnippetInfoComponent, SnippetOverviewComponent, SnippetSpinnerComponent ], entryComponents: [SnippetOverviewComponent], imports: [ ...MAT_MODULES, CommonModule, ReactiveFormsModule, AngularFireAuthModule, MarkdownModule.forRoot(), CodeDemoModule, RouterModule ] }) export class CreateSnippetModule {} ================================================ FILE: apps/angular-thirty-seconds/src/app/create-snippet/snippet-info/snippet-info.component.html ================================================
This form will generate a snippet request for you.
================================================ FILE: apps/angular-thirty-seconds/src/app/create-snippet/snippet-info/snippet-info.component.scss ================================================ :host { .snippets-info { float: right; top: 40px; max-width: 50%; padding: 35px 35px 15px 35px; margin-left: 30px; } .nav { &-fixed-wrapper { position: fixed; right: 30px; bottom: 30px; z-index: 2000; } &-btn { border-radius: 50%; width: 45px; height: 45px; font-weight: bold; display: table; text-align: center; box-shadow: 0 3px 7px rgba(0, 0, 0, 0.9); &-go-up { background: #213451; color: white; cursor: pointer; } p { vertical-align: middle; display: table-cell; } } } .animated { animation-duration: 1s; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .fadeIn { animation-name: fadeIn; } } ================================================ FILE: apps/angular-thirty-seconds/src/app/create-snippet/snippet-info/snippet-info.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'codelab-snippet-info', templateUrl: './snippet-info.component.html', styleUrls: ['./snippet-info.component.scss'] }) export class SnippetInfoComponent {} ================================================ FILE: apps/angular-thirty-seconds/src/app/create-snippet/snippet-modal/snippet-overview.component.html ================================================

Snippet Markdown

================================================ FILE: apps/angular-thirty-seconds/src/app/create-snippet/snippet-modal/snippet-overview.component.scss ================================================ :host { h1 { color: #444; } .snippet-modal { &-footer { float: right; } } .btn-submit { float: right; font-weight: 400; text-align: center; vertical-align: middle; border: 1px solid #213451; font-size: 14px; color: #fff; background-color: #213451; cursor: pointer; } } ================================================ FILE: apps/angular-thirty-seconds/src/app/create-snippet/snippet-modal/snippet-overview.component.ts ================================================ import { Component, Inject, OnDestroy, OnInit } from '@angular/core'; import { Router } from '@angular/router'; import { AngularFireAuth } from '@angular/fire/auth'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { MatSnackBar } from '@angular/material/snack-bar'; import { auth } from 'firebase/app'; import { finalize, switchMap, take, takeUntil } from 'rxjs/operators'; import { ReplaySubject } from 'rxjs/internal/ReplaySubject'; import { SnippetService } from '../../shared/services/snippet.service'; import { GitHubService } from '../../shared/services/github.service'; import { generateSnippet } from '../../shared/functions/generate-snippet'; import { SEPARATOR } from '../../shared/consts'; interface SnippetOverviewData { formValue: object; isEditing: boolean; fileInfo: { sha: string; fileName: string; branchName: string; }; repoName: string; repoOwner: string; } function exportSnippet(snippet) { const result = { ...snippet }; result.links = result.links ? result.links.split(SEPARATOR) : undefined; result.author = result.author || '** Your github username will be here **'; result.bonus = result.bonus || undefined; return result; } @Component({ selector: 'codelab-snippet-overview', templateUrl: './snippet-overview.component.html', styleUrls: ['./snippet-overview.component.scss'] }) export class SnippetOverviewComponent implements OnInit, OnDestroy { destroy = new ReplaySubject(1); githubAuth; isPRCreating = false; isEditing: boolean; snippet: string; snippetWithFormat: string; constructor( public dialogRef: MatDialogRef, private afAuth: AngularFireAuth, private snippetService: SnippetService, private githubService: GitHubService, private _snackBar: MatSnackBar, private router: Router, @Inject(MAT_DIALOG_DATA) public data: SnippetOverviewData ) {} ngOnInit() { this.isEditing = this.data.isEditing; this.snippet = generateSnippet(exportSnippet(this.data.formValue)); // This is a temporary hack. // The version of markdown requires new lines between meta values, but github does not. this.snippetWithFormat = this.snippet.replace( /\n(title|author|twitter|level|tags|links):/g, '\n\n$1:' ); } ngOnDestroy() { this.destroy.next(null); this.destroy.complete(); } async onSubmit() { console.log('You can copy the snippet here:\n', this.snippet); this.isPRCreating = true; if (!(this.githubAuth && this.githubAuth.credential)) { await this.login(); } if (this.isEditing) { this.snippetService .updatePR( this.githubAuth, this.snippet, this.data.fileInfo, this.data.repoName ) .pipe( finalize(() => (this.isPRCreating = false)), takeUntil(this.destroy) ) .subscribe(res => this.navigateAndShowSnackBar( 'Success', 'Snippet updated', res['commit']['html_url'] ) ); } else { this.snippetService .createPR( this.githubAuth, this.snippet, this.data.formValue['title'], this.data.repoName, this.data.repoOwner ) .pipe( switchMap(res => this.githubService.addLinkToEditForm( this.data.repoOwner, this.data.repoName, res['number'] ) ), switchMap(res => this.githubService.addSnippetLabel( this.data.repoOwner, this.data.repoName, res['number'] ) ), finalize(() => (this.isPRCreating = false)), takeUntil(this.destroy) ) .subscribe(res => this.navigateAndShowSnackBar( 'Pull request created', res['title'].replace('Add - new snippet: ', ''), res['html_url'] ) ); } } navigateAndShowSnackBar(text: string, linkLabel: string, linkUrl: string) { this.dialogRef.close(); this.router.navigate(['list']); const snakeBarRef = this._snackBar.open(text, linkLabel, { duration: 20000 }); snakeBarRef .onAction() .pipe(take(1)) .subscribe(() => window.open(linkUrl)); } async login() { const provider = new auth.GithubAuthProvider().addScope('repo'); this.githubAuth = await this.afAuth.auth.signInWithPopup(provider); this.data.formValue['author'] = this.githubAuth.additionalUserInfo.username; this.snippet = generateSnippet(exportSnippet(this.data.formValue)); } } ================================================ FILE: apps/angular-thirty-seconds/src/app/create-snippet/snippet-spinner/snippet-spinner.component.html ================================================
================================================ FILE: apps/angular-thirty-seconds/src/app/create-snippet/snippet-spinner/snippet-spinner.component.scss ================================================ .spinner-back-ground { position: fixed; width: 100%; left: 0; right: 0; top: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.2); z-index: 9999; } .centered-spinner { display: flex; align-items: center; justify-content: center; width: 100vw; height: 100vh; } .preload { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); /*change these sizes to fit into your project*/ width: 100px; height: 100px; } .preload div { border: 0; margin: 0; width: 40%; height: 40%; position: absolute; border-radius: 50%; animation: spin 2s ease infinite; } .preload :first-child { background: #19a68c; animation-delay: -1.5s; } .preload :nth-child(2) { background: #f63d3a; animation-delay: -1s; } .preload :nth-child(3) { background: #fda543; animation-delay: -0.5s; } .preload :last-child { background: #193b48; } @keyframes spin { 0%, 100% { transform: translate(0); } 25% { transform: translate(160%); } 50% { transform: translate(160%, 160%); } 75% { transform: translate(0, 160%); } } ================================================ FILE: apps/angular-thirty-seconds/src/app/create-snippet/snippet-spinner/snippet-spinner.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'codelab-snippet-spinner', templateUrl: './snippet-spinner.component.html', styleUrls: ['./snippet-spinner.component.scss'] }) export class SnippetSpinnerComponent {} ================================================ FILE: apps/angular-thirty-seconds/src/app/pull-requests-list/pull-requests-list.component.html ================================================

Open pull requests

# {{ item.number }} Title {{ item.title }} Author {{ item.user.login }} Creation Date {{ item.created_at | date: 'MM/dd/yyyy' }} Action
loading ... ================================================ FILE: apps/angular-thirty-seconds/src/app/pull-requests-list/pull-requests-list.component.scss ================================================ h1 { color: #444; } .container { max-width: 850px; padding: 0 15px 30px 15px; margin: 0 auto; font-family: Helvetica Neue, Helvetica, Arial, sans-serif; } table { width: 100%; } .mat-cell-padding { padding: 0 5px; } .flex-header { display: flex; justify-content: space-between; } .align-center { align-self: center; } ================================================ FILE: apps/angular-thirty-seconds/src/app/pull-requests-list/pull-requests-list.component.ts ================================================ import { Component } from '@angular/core'; import { Router } from '@angular/router'; import { GitHubService } from '../shared/services/github.service'; const REPO_OWNER = 'nycJSorg'; const REPO_NAME = '30-seconds-of-angular'; @Component({ selector: 'codelab-pull-requests-list', templateUrl: './pull-requests-list.component.html', styleUrls: ['./pull-requests-list.component.scss'] }) export class PullRequestsListComponent { repoOwner = REPO_OWNER; repoName = REPO_NAME; pullsList$ = this.githubService.getPullsList(this.repoOwner, this.repoName); displayedColumns = ['number', 'title', 'login', 'created_at', 'action']; constructor(private router: Router, private githubService: GitHubService) {} } ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/angular-sample.ts ================================================ export const angularSampleCode = { 'app.component.ts': `import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: \`

Edit me

\` }) export class AppComponent {}`, 'app.module.ts': `import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; @NgModule({ imports: [BrowserModule], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {}`, 'main.ts': `import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app.module'; platformBrowserDynamic().bootstrapModule(AppModule); `, 'index.html': '' }; ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/constants.ts ================================================ export const MARKDOWN_PLACEHOLDER = ` You can use markdown here.\n Highlight \`important terms\` with backticks.\n For examples use: \`\`\`typescript const language = 'English'; function theLanguageISpeak(language) { // English? No, only typescript! return 'typescript' } \`\`\``; export const TAGS_LIST = [ 'components', 'tip', 'forms', 'templates', 'styling', 'routing', 'performance' ]; export const LINKS_PLACEHOLDER = `https://angular.io/ https://www.typescriptlang.org/`; ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/consts.ts ================================================ export const SEPARATOR = '\n'; ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/functions/generate-snippet.spec.ts ================================================ import { testSnippetMd, testSnippetParsed } from './test-data/snippet'; import { generateSnippet } from './generate-snippet'; describe('GenerateSnippet', () => { it('generates a simple snippet', () => { const actual = generateSnippet(testSnippetParsed); expect(actual).toEqual(testSnippetMd); }); it('generates a snippet without demo', () => { const testSnippet = { ...testSnippetParsed }; delete testSnippet.demo; const actual = generateSnippet(testSnippet); expect(actual).not.toContain('file:'); }); it('generates a snippet without bonus', () => { const testSnippet = { ...testSnippetParsed }; delete testSnippet.bonus; const actual = generateSnippet(testSnippet); expect(actual).not.toContain('bonus'); }); it('generates a snippet without links', () => { const testSnippet = { ...testSnippetParsed }; delete testSnippet.links; const actual = generateSnippet(testSnippet); expect(actual).not.toContain('links'); }); }); ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/functions/generate-snippet.ts ================================================ import { Snippet } from '../interfaces/snippet'; import { angularSampleCode } from '../angular-sample'; import { SEPARATOR } from '../consts'; const config = { header: ['title', 'author', 'twitter', 'level', 'links', 'tags'], body: ['content', 'bonus'] }; function arrayToMarkdownList(tagsArray: Array): string { return tagsArray .filter(a => a) .map(x => `- ${x}`) .join(`\n`); } function generateMdHeader(keys: string[], snippet: Snippet) { return keys .map(key => ({ key, value: snippet[key] })) .filter(({ value }) => !!value) .map(({ value, key }) => { if (typeof value === 'string') { return `${key}: ${value}`; } if (Array.isArray(value)) { return `${key}: ${arrayToMarkdownList(value)}`; } throw new Error(key + 'is not a real key'); }) .join(SEPARATOR); } const ucFirst = s => { if (typeof s !== 'string') { return ''; } return s.charAt(0).toUpperCase() + s.slice(1); }; const extensionTolanguage = { ts: 'typescript', js: 'javascript' }; function getFileLanguage(fileName) { const fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1, fileName.length) || fileName; return extensionTolanguage[fileExtension] || fileExtension; } /** * Drop markdown "```language```" from the code */ function addMarkdownLanguageMark(code: string, filename: string) { return `\`\`\`${getFileLanguage(filename)} ${code} \`\`\``; } function generateMdBody(keys: string[], snippet: Snippet) { return keys .map(key => ({ key, value: snippet[key] })) .filter(({ value, key }) => !!value) .map(({ value, key }) => { return `# ${ucFirst(key)} ${value} `; }) .join(SEPARATOR); } function generateDemo(snippet) { if (!snippet.demo) { return ''; } return ( Object.entries(snippet.demo) .filter(([key, value]) => value && value !== angularSampleCode[key]) .map(([key, value]) => { return `# file:${key} ${addMarkdownLanguageMark(value.toString(), key)}`; }) .join(SEPARATOR) + SEPARATOR ); } export function generateSnippet(snippet: Snippet) { const header = generateMdHeader(config.header, snippet); const body = generateMdBody(config.body, snippet); const demo = generateDemo(snippet); return `--- ${header} --- ${body} ${demo}`; } ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/functions/parse-snippet.spec.ts ================================================ import { parseSnippet } from './parse-snippet'; import { testSnippetEdgeCases, testSnippetMd, testSnippetMinimal, testSnippetParsed } from './test-data/snippet'; describe('ParseSnippet', () => { it('parses a simple snippet', () => { const actual = parseSnippet(testSnippetMd); expect(actual).toEqual(testSnippetParsed); }); it('testSnippetMinimal', () => { const actual = parseSnippet(testSnippetMinimal); expect(actual.bonus).toBe(''); expect(actual.links).toEqual(undefined); expect(actual.demo).toEqual(undefined); expect(actual.content).toEqual('Content'); }); it('works when file names have spaces', () => { const actual = parseSnippet(testSnippetEdgeCases); expect(actual.demo).toEqual({ 'app.component.ts': "import { Component } from '@angular/core';\n\n@Component({\n selector: 'my-app',\n template: `

Edit me

`\n})\nexport class AppComponent {}", 'app.module.ts': "import { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { AppComponent } from './app.component';\n" + '\n@NgModule({\n imports: [BrowserModule],\n declarations: [AppComponent],\n' + ' bootstrap: [AppComponent]\n})\nexport class AppModule {}', 'main.ts': "import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\nimport { AppModule } from './app.module';" + '\n\nplatformBrowserDynamic().bootstrapModule(AppModule);\n', 'index.html': '', 'app.svg': '' }); }); }); ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/functions/parse-snippet.ts ================================================ import { angularSampleCode } from '../angular-sample'; // @ts-ignore // If you delete this you get a run time error. // This is needed for gray-matter window.Buffer = { from() {} }; // @ts-ignore const matter = require('gray-matter'); /** * * Takes markdown and returns content. * e.g. input: * * # LOL * 1 * # HI * 2 * * result: * * {LOL: "1", HI: "2"} */ function extractHeaders(str) { const match = ('\n' + str + '\n#').match(/\n#+.*\n[\s\S]*?(?=\n#)/g); return !match ? { content: str } : match.reduce((result, a) => { const [, header, content] = a.match(/^\n#+(.*)\n([\s\S]*)$/); result[header.trim().toLocaleLowerCase()] = content.trim(); return result; }, {}); } /** * * Takes markdown and returns content. * e.g. input: * * --- * title: Hello * tags: * - tips * - good-to-know * --- * * # LOL * 1 * # HI * 2 * * result: * * {title: "Hello", tags: ["tips", "good-to-know"], LOL: "1", HI: "2"} * */ function mdTextToJson(snippet: string) { const metaData = matter(snippet); return { ...extractHeaders(metaData.content), ...metaData.data }; } /** * Drop markdown "```language```" from the code */ function stripMarkdownLanguageMark(code = '') { return code.replace(/```\w+\n/, '').replace(/\n```/, ''); } function normalize(text) { return text ? text.replace(/↵/g, '\n') : ''; } export function parseSnippet(snippetBody: string) { const snippet = mdTextToJson(snippetBody); snippet.content = normalize(snippet.content); snippet.bonus = normalize(snippet.bonus); const demoFiles = Object.entries(snippet) .filter(([key]) => key.startsWith('file:')) .reduce((files, [key, value]) => { files[key.replace(/^file:/, '').trim()] = stripMarkdownLanguageMark( value.toString() ); return files; }, {}); if (Object.keys(demoFiles).length) { snippet.demo = { ...angularSampleCode, ...demoFiles }; } return snippet; } ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/functions/test-data/snippet.ts ================================================ export const testSnippetMd = `--- title: title author: author twitter: kirjs level: intermediate links: - gogel.com - 123.com tags: - tip --- # Content Content # Bonus Wow bonus # file:app.component.ts \`\`\`typescript import { Component } from '@angular/core'; @Component({ selector: 'my-app', templateUrl: './app.svg' }) export class AppComponent {} \`\`\` # file:app.svg \`\`\`svg \`\`\` `; export const testSnippetParsed = { content: 'Content', bonus: 'Wow bonus', 'file:app.component.ts': "```typescript\nimport { Component } from '@angular/core';\n @Component({\n selector: 'my-app',\n templateUrl: './app.svg'\n})\nexport class AppComponent {}\n```", 'file:app.svg': '```svg\n\n```', title: 'title', author: 'author', twitter: 'kirjs', level: 'intermediate', links: ['gogel.com', '123.com'], tags: ['tip'], demo: { 'app.component.ts': "import { Component } from '@angular/core';\n @Component({\n selector: 'my-app',\n templateUrl: './app.svg'\n})\nexport class AppComponent {}", 'app.module.ts': "import { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\nimport { AppComponent } from './app.component';\n" + '\n@NgModule({\n imports: [BrowserModule],\n declarations: [AppComponent],\n bootstrap: [AppComponent]\n})\nexport class AppModule {}', 'main.ts': "import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\nimport { AppModule } from './app.module';\n\nplatformBrowserDynamic().bootstrapModule(AppModule);\n", 'index.html': '', 'app.svg': '' } }; export const testSnippetMinimal = `--- title: title author: author twitter: kirjs level: intermediate tags: - tip --- # Content Content`; export const testSnippetEdgeCases = `--- title: title author: author twitter: kirjs level: intermediate tags: - tip --- # Content Content # file: app.svg \`\`\`svg \`\`\` `; ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/functions/validation/index.ts ================================================ export * from './validation'; ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/functions/validation/validation.ts ================================================ import { FormArray, FormControl, FormGroup } from '@angular/forms'; import { AbstractControl } from '@angular/forms'; export function markFormControlsAsTouched( formGroup: FormGroup | FormArray ): void { Object.values(formGroup.controls).forEach(control => { if (control instanceof FormControl) { control.markAsTouched({ onlySelf: true }); } else if (control instanceof FormGroup || control instanceof FormArray) { markFormControlsAsTouched(control); } }); } export function validatorMaxTags(maximumTags: number) { return (control: AbstractControl) => { return Array.isArray(control.value) && control.value.length > maximumTags ? { tagsError: `Number of tags should be below ${maximumTags + 1}` } : null; }; } export function validatorMaxLines(lines: number) { return (control: AbstractControl) => { return control.value.split('\n').length > lines ? { linesError: `This field shouldn't have more than ${lines} lines` } : null; }; } ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/index.ts ================================================ export * from './angular-sample'; export * from './constants'; ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/interfaces/branch.interface.ts ================================================ export interface Branch { ref: string; node_id: string; url: string; object: { type: string; sha: string; url: string; }; } ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/interfaces/commit-info.interface.ts ================================================ export interface CommitInfo { message: string; content: string; branchName: string; filePath: string; } ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/interfaces/github-auth.interface.ts ================================================ import { User } from './user.interface'; export interface GithubAuth { additionalUserInfo: { profile: User; }; credential: { accessToken: string; }; } ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/interfaces/index.ts ================================================ export * from './branch.interface'; export * from './github-auth.interface'; export * from './repo.interface'; export * from './user.interface'; export * from './pull-request.intreface'; export * from './commit-info.interface'; ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/interfaces/pull-request.intreface.ts ================================================ export interface CreatePullRequest { title: string; body: string; branchName: string; labels?: Array; } export interface PullRequest { title: string; html_url: string; } ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/interfaces/repo.interface.ts ================================================ export interface Repo { name: string; full_name: string; sha: string; url: string; git_refs_url: string; } ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/interfaces/snippet.ts ================================================ export type Snippet = Record; ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/interfaces/user.interface.ts ================================================ export interface User { login: string; repos_url: string; } ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/services/github.service.ts ================================================ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { MatSnackBar } from '@angular/material/snack-bar'; import { Observable } from 'rxjs/internal/Observable'; import { catchError, map } from 'rxjs/operators'; import { throwError } from 'rxjs/internal/observable/throwError'; import { MonoTypeOperatorFunction } from 'rxjs/internal/types'; import { Branch, CommitInfo, CreatePullRequest, Repo, User } from '../interfaces'; // TODO work on github api names // Here is an example link: https://github.com/github-tools/github @Injectable({ providedIn: 'root' }) export class GitHubService { private apiGithubUrl = 'https://api.github.com'; private options: object; constructor(private http: HttpClient, private _snackBar: MatSnackBar) {} showSnackbarOnError(message: string): MonoTypeOperatorFunction { return catchError(() => { this._snackBar.open(message, '', { duration: 10000 }); return throwError(new Error(message)); }); } setToken(token: string) { this.options = { headers: { Authorization: `token ${token}` } }; } getRepo(owner: string, repoName: string): Observable { requires(owner, 'Owner is required'); requires(repoName, 'Repo name is required'); const requestUrl = `${this.apiGithubUrl}/repos/${owner}/${repoName}`; return this.http .get(requestUrl, this.options) .pipe(this.showSnackbarOnError("Can't get repo")); } getMyRepos(user: User): Observable { requires(user, 'User is required'); return this.http .get(user.repos_url, this.options) .pipe(this.showSnackbarOnError("Can't fetch user repos")); } forkRepo(repo: Repo): Observable { requires(repo, 'Repository is required'); const requestUrl = `${this.apiGithubUrl}/repos/${repo.full_name}/forks`; return this.http .post(requestUrl, {}, this.options) .pipe(this.showSnackbarOnError("Can't fork 30 secs repo")); } getMasterBranch(repo: Repo): Observable { requires(repo, 'Repository is required'); const requestUrl = `${this.apiGithubUrl}/repos/${repo.full_name}/git/refs/heads/master`; return this.http .get(requestUrl, this.options) .pipe( this.showSnackbarOnError( `Can't fetch master branch of ${repo.full_name}` ) ); } createBranch( repo: Repo, baseBranch: Branch, branchName: string ): Observable { requires(repo, 'Repository is required'); requires(baseBranch, 'Base branch is required'); requires(branchName, 'Branch name is required'); const requestUrl = `${this.apiGithubUrl}/repos/${repo.full_name}/git/refs`; const branchRef = `refs/heads/${branchName}`; const requestData = { ref: branchRef, sha: baseBranch.object.sha }; return this.http .post(requestUrl, requestData, this.options) .pipe( this.showSnackbarOnError( `Can't create branch ${branchName} of base branch ${baseBranch.object.url}` ) ); } createCommit(repo: Repo, commitInfo: CommitInfo): Observable { requires(repo, 'Repository is required'); requires(commitInfo, 'Commit is required'); const requestUrl = `${this.apiGithubUrl}/repos/${repo.full_name}/${commitInfo.filePath}`; const requestData = { message: commitInfo.message, branch: commitInfo.branchName, content: commitInfo.content }; return this.http .put(requestUrl, requestData, this.options) .pipe(this.showSnackbarOnError("Can't create commit")); } createPullRequest( repo: Repo, user: User, pullRequest: CreatePullRequest ): Observable { requires(repo, 'Repository is required'); requires(user, 'User is required'); requires(pullRequest, 'Pull request is required'); const requestUrl = `${this.apiGithubUrl}/repos/${repo.full_name}/pulls`; const requestData = { title: pullRequest.title, head: `${user.login}:${pullRequest.branchName}`, base: 'master', body: pullRequest.body, labels: pullRequest.labels }; return this.http .post(requestUrl, requestData, this.options) .pipe(this.showSnackbarOnError("Can't create pull request")); } getPullsList(owner: string, repoName: string): Observable { return this.http .get( `${this.apiGithubUrl}/repos/${owner}/${repoName}/pulls`, this.options ) .pipe( map(res => res.filter( x => x['labels'].length && x['labels'].map(y => y['name']).indexOf('snippet') > -1 ) ), this.showSnackbarOnError("Can't fetch user repos") ); } getPullByPullNumber( owner: string, repoName: string, pullNumber: number ): Observable { return this.http .get( `${this.apiGithubUrl}/repos/${owner}/${repoName}/pulls/${pullNumber}`, this.options ) .pipe(this.showSnackbarOnError("Can't get pull request")); } addLinkToEditForm( owner: string, repoName: string, pullNumber: number ): Observable { return this.http .patch( `${this.apiGithubUrl}/repos/${owner}/${repoName}/pulls/${pullNumber}`, { body: `Here you can edit snippet content: https://30.codelab.fun/new/${pullNumber}` }, this.options ) .pipe(this.showSnackbarOnError("Can't update pull request")); } addSnippetLabel(owner: string, repoName: string, issueNumber: number) { return this.http .patch( `${this.apiGithubUrl}/repos/${owner}/${repoName}/issues/${issueNumber}`, { labels: ['snippet'] }, this.options ) .pipe(this.showSnackbarOnError("Can't get issues list")); } getPullFileByPullNumber( owner: string, repoName: string, pullNumber: number ): Observable { return this.http .get( `${this.apiGithubUrl}/repos/${owner}/${repoName}/pulls/${pullNumber}/files`, this.options ) .pipe(this.showSnackbarOnError("Can't get pull request file")); } getSnippetBody(url) { return this.http .get(url, this.options) .pipe(this.showSnackbarOnError("Can't get snippet body")); } updateFile(repoFullName, snippetData, fileInfo): Observable { const requestUrl = `${this.apiGithubUrl}/repos/${repoFullName}/contents/${fileInfo['fileName']}`; const requestPayload = { message: `Snippet Update`, content: btoa(snippetData), sha: fileInfo['sha'], branch: fileInfo['branchName'] }; return this.http .put(requestUrl, requestPayload, this.options) .pipe(this.showSnackbarOnError('Cannot update file')); } } function requires(expression: any, message: string) { if (!expression) { throw new Error(message); } } ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/services/snippet.service.spec.ts ================================================ import { TestBed } from '@angular/core/testing'; import { SnippetService } from './snippet.service'; import { GitHubService } from './github.service'; import { of } from 'rxjs'; import SpyObj = jasmine.SpyObj; describe('SnippetService', () => { let gitHubService: SpyObj; const repoName = '30seconds'; const repoOwner = 'PIKACHU'; const pullNumber = 689; beforeEach(() => { gitHubService = jasmine.createSpyObj('gitHubService', [ 'getPullByPullNumber', 'getPullFileByPullNumber', 'getSnippetBody' ]); TestBed.configureTestingModule({ providers: [ { provide: GitHubService, useValue: gitHubService } ] }); }); it('should be created', () => { const snippet = 'pirojok'; const fileName = 'john'; const contents_url = 'LOL'; const branchName = 'branch'; const service: SnippetService = TestBed.inject(SnippetService); gitHubService.getPullByPullNumber.and.returnValue( of({ head: { ref: branchName } }) ); const sha = 'sa sha'; gitHubService.getPullFileByPullNumber.and.returnValue( of([ { contents_url, sha, filename: fileName } ]) ); gitHubService.getSnippetBody.and.returnValue( of({ content: btoa(snippet) }) ); service.fetchPR(repoName, repoOwner, pullNumber).subscribe(result => { expect(result).toEqual({ branchName, fileName, sha, snippet }); }); }); }); ================================================ FILE: apps/angular-thirty-seconds/src/app/shared/services/snippet.service.ts ================================================ import { Injectable } from '@angular/core'; import { combineLatest, Observable, of } from 'rxjs'; import { debounceTime, map, switchMap } from 'rxjs/operators'; import slugify from 'slugify'; import { GitHubService } from './github.service'; import { Branch, CommitInfo, CreatePullRequest, GithubAuth, PullRequest, Repo, User } from '../interfaces'; @Injectable({ providedIn: 'root' }) export class SnippetService { constructor(private githubService: GitHubService) {} fetchPR(repoName: string, repoOwner: string, pullNumber: number) { // todo move it to service later const pr$ = this.githubService.getPullByPullNumber( repoOwner, repoName, pullNumber ); const file$ = this.githubService .getPullFileByPullNumber(repoOwner, repoName, pullNumber) .pipe( switchMap(([file]) => { return this.githubService.getSnippetBody(file['contents_url']).pipe( map(res => { const body = atob(res.content); return { ...res[0], body, sha: file['sha'], fileName: file['filename'] }; }) ); }) ); return combineLatest([file$, pr$]).pipe( map(([file, pr]) => { return { sha: file['sha'], fileName: file['fileName'], snippet: file['body'] as string, branchName: pr['head']['ref'] }; }) ); } updatePR( githubAuth: GithubAuth, snippetData: string, fileInfo: object, repoName: string ): Observable { this.githubService.setToken(githubAuth.credential.accessToken); const user: User = githubAuth.additionalUserInfo.profile; return this.githubService.getMyRepos(user).pipe( switchMap((repos: Repo[]) => { const repo = repos.find(r => r.name === repoName); return this.githubService.updateFile( repo.full_name, snippetData, fileInfo ); }) ); } createPR( githubAuth: GithubAuth, snippetData: string, title: string, repoName: string, repoOwner: string ): Observable { requires(githubAuth, 'Github auth is required'); requires(snippetData, 'Snippet is required'); requires(title, 'Snippet title is required'); this.githubService.setToken(githubAuth.credential.accessToken); const branchName = `new_snippet_${this.toLowerCaseAndSlugify(title)}`; const filePath = `contents/snippets/${this.toLowerCaseAndSlugify( title )}.md`; const user: User = githubAuth.additionalUserInfo.profile; return this.githubService.getRepo(repoOwner, repoName).pipe( switchMap((baseRepo: Repo) => { return this.githubService.getMyRepos(user).pipe( switchMap((repos: Repo[]) => { const repo = repos.find(r => r.name === repoName); return repo ? of(repo) : this.githubService.forkRepo(baseRepo).pipe(debounceTime(5000)); }), switchMap((userRepo: Repo) => { return this.githubService.getMasterBranch(userRepo).pipe( switchMap((masterBranch: Branch) => { return this.githubService.createBranch( userRepo, masterBranch, branchName ); }), switchMap(() => { const commit: CommitInfo = { message: 'I have added awesome snippet. Look at my awesome snippet!', content: btoa(snippetData), branchName: branchName, filePath: filePath }; return this.githubService.createCommit(userRepo, commit); }), switchMap(() => { const pullRequest: CreatePullRequest = { title: `Add - new snippet: ${title}`, body: 'Here is a new snippet. Hope you like it :)', labels: ['snippet'], branchName: branchName }; return this.githubService.createPullRequest( baseRepo, user, pullRequest ); }) ); }) ); }) ); } private toLowerCaseAndSlugify(str: string) { return slugify(str.toLowerCase()); } } function requires(expression: any, message: string) { if (!expression) { throw new Error(message); } } ================================================ FILE: apps/angular-thirty-seconds/src/assets/.gitkeep ================================================ ================================================ FILE: apps/angular-thirty-seconds/src/environments/environment.prod.ts ================================================ export const environment = { production: true }; ================================================ FILE: apps/angular-thirty-seconds/src/environments/environment.ts ================================================ // This file can be replaced during build by using the `fileReplacements` array. // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. // The list of file replacements can be found in `angular.json`. export const environment = { production: false }; /* * For easier debugging in development mode, you can import the following file * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. * * This import should be commented out in production mode because it will have a negative impact * on performance if an error is thrown. */ // import 'zone.js/dist/zone-error'; // Included with Angular CLI. ================================================ FILE: apps/angular-thirty-seconds/src/index.html ================================================ AngularThirtySeconds ================================================ FILE: apps/angular-thirty-seconds/src/main.ts ================================================ import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; if (environment.production) { enableProdMode(); } platformBrowserDynamic() .bootstrapModule(AppModule) .catch(err => console.error(err)); ================================================ FILE: apps/angular-thirty-seconds/src/polyfills.ts ================================================ /** * This file includes polyfills needed by Angular and is loaded before the app. * You can add your own extra polyfills to this file. * * This file is divided into 2 sections: * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. * 2. Application imports. Files imported after ZoneJS that should be loaded before your main * file. * * The current setup is for so-called "evergreen" browsers; the last versions of browsers that * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. * * Learn more in https://angular.io/guide/browser-support */ /*************************************************************************************************** * BROWSER POLYFILLS */ /** IE10 and IE11 requires the following for NgClass support on SVG elements */ // import 'classlist.js'; // Run `npm install --save classlist.js`. /** * Web Animations `@angular/platform-browser/animations` * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). */ // import 'web-animations-js'; // Run `npm install --save web-animations-js`. /** * By default, zone.js will patch all possible macroTask and DomEvents * user can disable parts of macroTask/DomEvents patch by setting following flags * because those flags need to be set before `zone.js` being loaded, and webpack * will put import in the top of bundle, so user need to create a separate file * in this directory (for example: zone-flags.ts), and put the following flags * into that file, and then add the following code before importing zone.js. * import './zone-flags.ts'; * * The flags allowed in zone-flags.ts are listed here. * * The following flags will work for all browsers. * * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames * * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js * with the following flag, it will bypass `zone.js` patch for IE/Edge * * (window as any).__Zone_enable_cross_context_check = true; * */ /*************************************************************************************************** * Zone JS is required by default for Angular itself. */ import 'zone.js/dist/zone'; // Included with Angular CLI. /*************************************************************************************************** * APPLICATION IMPORTS */ ================================================ FILE: apps/angular-thirty-seconds/src/styles.scss ================================================ @import '~@angular/material/prebuilt-themes/indigo-pink.css'; html, body { margin: 0; padding: 0; } ================================================ FILE: apps/angular-thirty-seconds/src/test.ts ================================================ // This file is required by karma.conf.js and loads recursively all the .spec and framework files import 'zone.js/dist/zone-testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; declare const require: any; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); // Then we find all the tests. const context = require.context('./', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context); ================================================ FILE: apps/angular-thirty-seconds/tsconfig.app.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../../dist/out-tsc", "types": [] }, "exclude": ["test.ts", "**/*.spec.ts"], "include": ["**/*.ts"] } ================================================ FILE: apps/angular-thirty-seconds/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "types": ["jasmine"] } } ================================================ FILE: apps/angular-thirty-seconds/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../../dist/out-tsc", "types": ["jasmine", "node"] }, "files": ["src/test.ts", "src/polyfills.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: apps/angular-thirty-seconds/tslint.json ================================================ { "extends": "../../tslint.json", "rules": { "directive-selector": [true, "attribute", "codelab", "camelCase"], "component-selector": [true, "element", "codelab", "kebab-case"] } } ================================================ FILE: apps/blog/browserslist ================================================ # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers # For additional information regarding the format and rule options, please see: # https://github.com/browserslist/browserslist#queries # # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed > 0.5% last 2 versions Firefox ESR not dead not IE 9-11 ================================================ FILE: apps/blog/jest.config.js ================================================ module.exports = { name: 'blog', preset: '../../jest.config.js', coverageDirectory: '../../coverage/apps/blog/' }; ================================================ FILE: apps/blog/src/app/app.component.html ================================================

Angular Codelab Newsletter

================================================ FILE: apps/blog/src/app/app.component.scss ================================================ h2 { color: white; text-align: center; height: 70px; background-image: linear-gradient(to right, red, white); width: 90%; } div { width: 100%; display: flex; } span { margin: auto; display: inline-block; vertical-align: middle; line-height: normal; margin-top: 20px; } img { height: 100px; margin-left: auto; } ================================================ FILE: apps/blog/src/app/app.component.spec.ts ================================================ import { TestBed, async } from '@angular/core/testing'; import { AppComponent } from './app.component'; import { RouterTestingModule } from '@angular/router/testing'; describe('AppComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [RouterTestingModule], declarations: [AppComponent] }).compileComponents(); })); it('should create the app', () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app).toBeTruthy(); }); it(`should have as title 'blog'`, () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app.title).toEqual('blog'); }); it('should render title in a h1 tag', () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.debugElement.nativeElement; expect(compiled.querySelector('h1').textContent).toContain( 'Welcome to blog!' ); }); }); ================================================ FILE: apps/blog/src/app/app.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'codelab-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent { title = 'blog'; } ================================================ FILE: apps/blog/src/app/app.module.ts ================================================ import { HttpClientModule } from '@angular/common/http'; import { NgModule } from '@angular/core'; import { AngularFireModule } from '@angular/fire'; import { AngularFireAuthModule } from '@angular/fire/auth'; import { AngularFireDatabaseModule } from '@angular/fire/database'; import { ReactiveFormsModule } from '@angular/forms'; import { MatCardModule } from '@angular/material/card'; import { MatSnackBarModule } from '@angular/material/snack-bar'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatSelectModule } from '@angular/material/select'; import { BrowserModule } from '@angular/platform-browser'; import { RouterModule, Routes } from '@angular/router'; import { MarkdownModule } from 'ngx-markdown'; import { environment } from '../../../../apps/codelab/src/environments/environment'; import { AppComponent } from './app.component'; import { FeedComponent } from './feed/feed.component'; import { FormComponent } from './form/form.component'; import { PostService } from './post.service'; import { PostComponent } from './post/post.component'; import { SinglePostComponent } from './single-post/single-post.component'; export const angularFire = AngularFireModule.initializeApp( environment.firebaseConfig ); const appRoutes: Routes = [ { path: 'post/:id', component: PostComponent }, { path: '', component: FeedComponent }, { path: 'form', component: FormComponent } ]; @NgModule({ declarations: [ AppComponent, FormComponent, FeedComponent, PostComponent, SinglePostComponent ], imports: [ BrowserModule, MarkdownModule.forRoot(), MatFormFieldModule, MatSelectModule, ReactiveFormsModule, HttpClientModule, AngularFireDatabaseModule, angularFire, MatCardModule, AngularFireAuthModule, MatSnackBarModule, RouterModule.forRoot(appRoutes, { initialNavigation: 'enabled' }) ], providers: [PostService], bootstrap: [AppComponent], exports: [FormComponent] }) export class AppModule {} ================================================ FILE: apps/blog/src/app/common.ts ================================================ export interface Post { key?: string; title: string; author: string; text: string; date: string; hidden: boolean; } ================================================ FILE: apps/blog/src/app/feed/feed.component.html ================================================ Tell us what you have done!
================================================ FILE: apps/blog/src/app/feed/feed.component.scss ================================================ ================================================ FILE: apps/blog/src/app/feed/feed.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { FeedComponent } from './feed.component'; describe('FeedComponent', () => { let component: FeedComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [FeedComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(FeedComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/blog/src/app/feed/feed.component.ts ================================================ import { ChangeDetectionStrategy, Component } from '@angular/core'; import { PostService } from '../post.service'; import { Observable } from 'rxjs'; import { Post } from '../common'; import { map } from 'rxjs/operators'; @Component({ selector: 'codelab-feed', templateUrl: './feed.component.html', changeDetection: ChangeDetectionStrategy.OnPush, styleUrls: ['./feed.component.scss'] }) export class FeedComponent { posts$: Observable; constructor(private postService: PostService) { this.posts$ = this.postService.repo$.snapshotChanges().pipe( map(items => { return items .map(a => { return { ...a.payload.val(), key: a.payload.key }; }) .reverse(); }) ); } } ================================================ FILE: apps/blog/src/app/form/form.component.html ================================================

Tell us what's new

Back ================================================ FILE: apps/blog/src/app/form/form.component.scss ================================================ .container { display: flex; flex-direction: column; width: 40%; } input, textarea { width: 600px; } .form-control { margin: 2px; } textarea { overflow: auto; resize: vertical; } ================================================ FILE: apps/blog/src/app/form/form.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { FormComponent } from './form.component'; describe('FormComponent', () => { let component: FormComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [FormComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(FormComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/blog/src/app/form/form.component.ts ================================================ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { Observable } from 'rxjs'; import { FormControl, FormGroup, Validators } from '@angular/forms'; import { PostService } from '../post.service'; import { Router } from '@angular/router'; import { Post } from '../common'; @Component({ selector: 'codelab-form', templateUrl: './form.component.html', styleUrls: ['./form.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) export class FormComponent { title = new FormControl('', Validators.required); author = new FormControl('', Validators.required); text = new FormControl('', Validators.required); date: Date; post: Observable; myform = new FormGroup({ title: this.title, author: this.author, text: this.text }); statusMessage = ''; error = false; constructor( private http: HttpClient, private postService: PostService, private router: Router ) {} onSubmit() { const formValues: any = this.myform.getRawValue(); this.postService .addPost(formValues) .then(({ key }) => { this.myform.reset(); this.router.navigateByUrl(`post/${key}`); }) .catch(() => { this.statusMessage = 'Error'; this.error = true; }); } } ================================================ FILE: apps/blog/src/app/post/post.component.html ================================================ ================================================ FILE: apps/blog/src/app/post/post.component.scss ================================================ .text { overflow: hidden; max-height: 100px; } ================================================ FILE: apps/blog/src/app/post/post.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { PostComponent } from './post.component'; describe('PostComponent', () => { let component: PostComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [PostComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(PostComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/blog/src/app/post/post.component.ts ================================================ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { Post } from '../common'; import { PostService } from '../post.service'; import { ActivatedRoute } from '@angular/router'; import { MarkdownModule } from 'ngx-markdown'; @Component({ selector: 'codelab-post', templateUrl: './post.component.html', styleUrls: ['./post.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) export class PostComponent implements OnInit { post$: Observable; key: string; constructor( private postService: PostService, private route: ActivatedRoute ) {} ngOnInit() { this.key = this.route.snapshot.params['id']; this.post$ = this.postService.getPost(this.key); } } ================================================ FILE: apps/blog/src/app/post.service.ts ================================================ import { Injectable } from '@angular/core'; import { AngularFireDatabase, AngularFireList } from '@angular/fire/database'; import { Observable } from 'rxjs'; import { Post } from './common'; import { database } from 'firebase/app'; @Injectable({ providedIn: 'root' }) export class PostService { repo$: AngularFireList = this.database.list('/posts', ref => { return ref.orderByChild('hidden').equalTo(null); }); constructor(private database: AngularFireDatabase) {} getPostById(id: string) { return this.database.object(`posts/${id}`); } removePost(id: string) { return this.getPostById(id).remove(); } updatePost(id: string, post: Partial) { return this.getPostById(id).update(post); } addPost(post: Post): any { return this.repo$.push({ ...post, date: database.ServerValue.TIMESTAMP as string }); } getPost(id: string): Observable { return this.getPostById(id).valueChanges(); } } ================================================ FILE: apps/blog/src/app/single-post/single-post.component.html ================================================

{{ post.title }} by {{ post.author }} on {{ post.date | date: 'short' }}

More
================================================ FILE: apps/blog/src/app/single-post/single-post.component.scss ================================================ .date { float: right; margin-right: 5px; } .title { margin-top: 0px; margin-top: 0px; padding-left: 5px; } mat-card { margin-bottom: 10px; padding-bottom: 10px; } .text { overflow: hidden; max-height: 100px; } .gradient { position: absolute; height: 130px; bottom: 0; width: 100%; } .container { position: relative; } ================================================ FILE: apps/blog/src/app/single-post/single-post.component.ts ================================================ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { AccessService } from '../../../../codelab/src/app/shared/services/access.service'; import { Post } from '../common'; import { PostService } from '../post.service'; import { Router } from '@angular/router'; import { MatSnackBar } from '@angular/material/snack-bar'; @Component({ selector: 'codelab-single-post', templateUrl: './single-post.component.html', styleUrls: ['./single-post.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) export class SinglePostComponent { @Input() post: Post; @Input() full: boolean; @Input() key = ''; constructor( private postService: PostService, private accessService: AccessService, private router: Router, private snackBar: MatSnackBar ) {} delete() { this.accessService.oldIsAdmin$.subscribe(); this.post.hidden = true; this.postService .updatePost(this.key, this.post) .then(() => { this.router.navigateByUrl(``); }) .catch(err => { this.snackBar.open(`ERR: ${err}`); }); } } ================================================ FILE: apps/blog/src/assets/.gitkeep ================================================ ================================================ FILE: apps/blog/src/assets/fonts/droid-sans/Apache License.txt ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: apps/blog/src/environments/environment.prod.ts ================================================ export const environment = { production: true }; ================================================ FILE: apps/blog/src/environments/environment.ts ================================================ // This file can be replaced during build by using the `fileReplacements` array. // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. // The list of file replacements can be found in `angular.json`. export const environment = { production: false }; /* * For easier debugging in development mode, you can import the following file * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. * * This import should be commented out in production mode because it will have a negative impact * on performance if an error is thrown. */ // import 'zone.js/dist/zone-error'; // Included with Angular CLI. ================================================ FILE: apps/blog/src/index.html ================================================ Blog ================================================ FILE: apps/blog/src/main.ts ================================================ import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; if (environment.production) { enableProdMode(); } platformBrowserDynamic() .bootstrapModule(AppModule) .catch(err => console.error(err)); ================================================ FILE: apps/blog/src/polyfills.ts ================================================ /** * This file includes polyfills needed by Angular and is loaded before the app. * You can add your own extra polyfills to this file. * * This file is divided into 2 sections: * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. * 2. Application imports. Files imported after ZoneJS that should be loaded before your main * file. * * The current setup is for so-called "evergreen" browsers; the last versions of browsers that * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. * * Learn more in https://angular.io/guide/browser-support */ /*************************************************************************************************** * BROWSER POLYFILLS */ /** IE10 and IE11 requires the following for NgClass support on SVG elements */ // import 'classlist.js'; // Run `npm install --save classlist.js`. /** * Web Animations `@angular/platform-browser/animations` * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). */ // import 'web-animations-js'; // Run `npm install --save web-animations-js`. /** * By default, zone.js will patch all possible macroTask and DomEvents * user can disable parts of macroTask/DomEvents patch by setting following flags * because those flags need to be set before `zone.js` being loaded, and webpack * will put import in the top of bundle, so user need to create a separate file * in this directory (for example: zone-flags.ts), and put the following flags * into that file, and then add the following code before importing zone.js. * import './zone-flags.ts'; * * The flags allowed in zone-flags.ts are listed here. * * The following flags will work for all browsers. * * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick * (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames * * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js * with the following flag, it will bypass `zone.js` patch for IE/Edge * * (window as any).__Zone_enable_cross_context_check = true; * */ /*************************************************************************************************** * Zone JS is required by default for Angular itself. */ import 'zone.js/dist/zone'; // Included with Angular CLI. /*************************************************************************************************** * APPLICATION IMPORTS */ ================================================ FILE: apps/blog/src/styles.scss ================================================ /* You can add global styles to this file, and also import other style files */ @import '~@angular/material/prebuilt-themes/indigo-pink.css'; @font-face { font-family: 'Droid Sans'; src: url('/assets/fonts/droid-sans/DroidSans.ttf') format('opentype'); } @font-face { font-family: 'Droid Sans Bold'; src: url('/assets/fonts/droid-sans/DroidSans-Bold.ttf') format('opentype'); } body { font-family: 'Droid Sans', 'Droid Sans Bold'; } ================================================ FILE: apps/blog/src/test-setup.ts ================================================ import 'jest-preset-angular'; ================================================ FILE: apps/blog/tsconfig.app.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "types": [] }, "exclude": ["src/test-setup.ts", "**/*.spec.ts"] } ================================================ FILE: apps/blog/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "types": ["node", "jest"] }, "include": ["**/*.ts"], "angularCompilerOptions": { "enableIvy": true } } ================================================ FILE: apps/blog/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "types": ["jest", "node"] }, "files": ["src/test-setup.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: apps/blog/tslint.json ================================================ { "extends": "../../tslint.json", "rules": { "directive-selector": [true, "attribute", "codelab", "camelCase"], "component-selector": [true, "element", "codelab", "kebab-case"] } } ================================================ FILE: apps/codelab/browserslist ================================================ # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. # For additional information regarding the format and rule options, please see: # https://github.com/browserslist/browserslist#queries # You can see what browsers were selected by your queries by running: # npx browserslist > 0.5% last 2 versions Firefox ESR not dead not IE 9-11 # For IE 9-11 support, remove 'not'. ================================================ FILE: apps/codelab/extra-webpack.config.js ================================================ const webpack = require('webpack'); const MonacoWebpackPlugin = require('monaco-editor-webpack-plugin'); const findLoader = (webpackConfig, regex) => { return webpackConfig.module.rules .filter(rule => !!rule.use) .find(rule => rule.use.find(it => !!it.loader && regex.test(it.loader))); }; module.exports = (webpackConfig, cliConfig) => { if (cliConfig.buildOptimizer) { const loader = findLoader( webpackConfig, /@angular-devkit\/build-optimizer.*\/webpack-loader/ ); const originalTest = loader.test; loader.test = file => { const isMonaco = !!file.match('node_modules/monaco-editor'); return !isMonaco && !!file.match(originalTest); }; } webpackConfig.plugins.push(new MonacoWebpackPlugin()); return webpackConfig; }; ================================================ FILE: apps/codelab/karma.conf.js ================================================ // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html module.exports = function(config) { config.set({ basePath: '../', frameworks: ['jasmine', '@angular-devkit/build-angular'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), require('@angular-devkit/build-angular/plugins/karma') ], client: { clearContext: false // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { dir: require('path').join(__dirname, '../coverage'), reports: ['html', 'lcovonly'], fixWebpackSourcePaths: true }, reporters: ['progress', 'kjhtml'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], singleRun: false }); }; ================================================ FILE: apps/codelab/src/app/admin/admin-routing.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { AdminComponent } from './admin.component'; import { UsersComponent } from './users/users.component'; import { FeedbackComponent } from './feedback/feedback.component'; const routes = [ { path: '', component: AdminComponent, children: [ { path: 'users', component: UsersComponent }, { path: 'feedback', component: FeedbackComponent } ] } ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class AdminRoutingModule {} ================================================ FILE: apps/codelab/src/app/admin/admin.component.css ================================================ :host { display: block; max-width: 1000px; margin: 0 auto; } .wrapper { margin-top: 20px; } ================================================ FILE: apps/codelab/src/app/admin/admin.component.html ================================================

Angular Codelab 🔥 Admin

================================================ FILE: apps/codelab/src/app/admin/admin.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { AdminComponent } from './admin.component'; import { AdminModule } from './admin.module'; import { getMockAngularFireProviders } from '@codelab/utils/src/lib/testing/mocks/angular-fire'; import { RouterModule } from '@angular/router'; describe('AdminComponent', () => { let component: AdminComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [AdminModule, RouterModule.forRoot([])], providers: [...getMockAngularFireProviders()] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(AdminComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/codelab/src/app/admin/admin.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'codelab-admin', templateUrl: './admin.component.html', styleUrls: ['./admin.component.css'] }) export class AdminComponent { readonly links = [ { link: 'users', name: 'Users' }, { link: 'feedback', name: 'Feedback' } ]; } ================================================ FILE: apps/codelab/src/app/admin/admin.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatCardModule } from '@angular/material/card'; import { MatTabsModule } from '@angular/material/tabs'; import { AdminComponent } from './admin.component'; import { AdminRoutingModule } from './admin-routing.module'; import { FeedbackModule } from './feedback/feedback.module'; import { UsersModule } from './users/users.module'; @NgModule({ imports: [ AdminRoutingModule, CommonModule, FeedbackModule, UsersModule, MatCardModule, MatTabsModule ], declarations: [AdminComponent] }) export class AdminModule {} ================================================ FILE: apps/codelab/src/app/admin/feedback/feedback-message-table/feedback-message-table.component.ts ================================================ import { Component, Input, ViewChild, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; import { MatMenuTrigger } from '@angular/material/menu'; import { MatTableDataSource } from '@angular/material/table'; import { MatSort } from '@angular/material/sort'; import { Message } from '@codelab/feedback/src/lib/message'; const clearTags = (value: string) => value.replace(/<[^>]+>/g, '').replace(/Angular Codelab \/ /, ''); const clearAllTags = (values: Message[]): Message[] => values.map((m: Message) => ({ ...m, header: clearTags(m.header || 'No header') })); const sortingDataAccessor = (item, property) => { switch (property) { case 'timestamp': return new Date(item.timestamp).toISOString(); default: return item[property]; } }; @Component({ selector: 'codelab-feedback-message-table', templateUrl: './feedback-message-table.html', styleUrls: ['./feedback-message-table.css'], changeDetection: ChangeDetectionStrategy.OnPush }) export class FeedbackMessageTableComponent { @ViewChild(MatSort, { static: true }) sort: MatSort; @Input('dataSource') set dataSourceSetter(values: Message[]) { this.dataSource.data = clearAllTags(values); this.dataSource.sortingDataAccessor = sortingDataAccessor; this.dataSource.sort = this.sort; } dataSource = new MatTableDataSource([]); closeReasons = [ { name: '[Duplicate]', reason: '[Duplicate]' }, { name: '[No fix]', reason: '[No fix]' }, { name: '[Done]', reason: '[Done]' }, { name: '[Nice message]', reason: '[Nice message, though not a real bug]' }, { name: "[Can't reproduce]", reason: "[Can't reproduce]" } ]; tableColumns = ['comment', 'name', 'header', 'timestamp', 'actions']; } ================================================ FILE: apps/codelab/src/app/admin/feedback/feedback-message-table/feedback-message-table.css ================================================ .done-row { background: #fcfcfc; color: #666; } .mat-column-comment { width: 55%; padding-right: 5px; } .mat-column-name { width: 10%; padding-right: 5px; } .mat-column-header { width: 20%; padding-right: 5px; } .mat-column-timestamp { width: 10%; max-width: 120px; padding-right: 5px; } .mat-column-actions { width: 5%; max-width: 20px; } table { width: 100%; overflow: hidden; } ================================================ FILE: apps/codelab/src/app/admin/feedback/feedback-message-table/feedback-message-table.html ================================================
comment {{element.comment}} name {{element.name}} header {{ element.header}} href {{element.href}} timestamp {{element.timestamp|date}}
================================================ FILE: apps/codelab/src/app/admin/feedback/feedback.component.css ================================================ .panel { margin-bottom: 10px; } mat-form-field { margin-left: 15px; margin-top: 5px; width: 120px; } ================================================ FILE: apps/codelab/src/app/admin/feedback/feedback.component.html ================================================
Feedback All Done Undone Group by Do not group Slide URL Name

{{ item.key }}

================================================ FILE: apps/codelab/src/app/admin/feedback/feedback.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { FeedbackComponent } from './feedback.component'; import { FeedbackModule } from './feedback.module'; import { getMockAngularFireProviders } from '@codelab/utils/src/lib/testing/mocks/angular-fire'; import { GithubService } from './github.service'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; describe('FeedbackComponent', () => { let component: FeedbackComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [FeedbackModule, NoopAnimationsModule], providers: [ ...getMockAngularFireProviders(), { provide: GithubService, useValue: {} } ] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(FeedbackComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/codelab/src/app/admin/feedback/feedback.component.ts ================================================ import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core'; import { AngularFireDatabase, AngularFireList } from '@angular/fire/database'; import { Message } from '@codelab/feedback/src/lib/message'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; type Filter = 'all' | 'done' | 'notDone'; type Grouping = 'nothing' | 'href' | 'name'; function groupBy(feedback: Array, grouping: Grouping) { const result = feedback.reduce((comment, item) => { const groupName = item[grouping]; comment[groupName] = comment[groupName] || []; comment[groupName].push(item); return comment; }, {}); return Object.keys(result).map(key => ({ key, value: result[key] })); } function normalize(feedback: Array) { return feedback.map(item => ({ ...(item.payload && item.payload.val()), key: item.key })); } function group([feedback, grouping]) { if (grouping === 'nothing') { return [ { key: 'Messages', value: feedback } ]; } if (grouping === 'name' || grouping === 'href') { return groupBy(feedback, grouping); } throw new Error('Unknown grouping: ' + grouping); } function filter([feedback, filterName, [fromDate, toDate]]) { let result; if (filterName === 'all') { result = feedback; } if (filterName === 'done') { result = feedback.filter(message => message.isDone); } if (filterName === 'notDone') { result = feedback.filter(message => !message.isDone); } const fromMs = fromDate ? new Date(fromDate).getTime() : null; const toMs = toDate ? new Date(toDate).getTime() + 86400000 : null; // add 24hrs to include the day of upper bound result = result.filter(msg => { const timestampMs = new Date(msg.timestamp).getTime(); return ( (fromMs ? timestampMs >= fromMs : true) && (toMs ? timestampMs <= toMs : true) ); }); return result; } @Component({ selector: 'codelab-feedback', templateUrl: './feedback.component.html', styleUrls: ['./feedback.component.css'], changeDetection: ChangeDetectionStrategy.OnPush }) export class FeedbackComponent implements OnInit { messages$: Observable<{ key: string; value: Message }[]>; filter$ = new BehaviorSubject('notDone'); dateFilter$ = new BehaviorSubject<[string, string]>(['', '']); group$ = new BehaviorSubject('nothing'); datesForFilter = { dateFrom: '', dateTo: '' }; constructor(private database: AngularFireDatabase) {} ngOnInit() { const feedback$: AngularFireList = this.database.list('/feedback'); const filteredMessages$ = combineLatest([ feedback$.snapshotChanges().pipe(map(normalize)), this.filter$, this.dateFilter$ ]).pipe(map(filter)); this.messages$ = combineLatest([filteredMessages$, this.group$]).pipe( map(group) ); } changeDate(clearDates = false) { if (clearDates) { this.datesForFilter = { dateFrom: '', dateTo: '' }; } this.dateFilter$.next([ this.datesForFilter.dateFrom || '', this.datesForFilter.dateTo || '' ]); } clearDate() { this.changeDate(true); } } ================================================ FILE: apps/codelab/src/app/admin/feedback/feedback.module.ts ================================================ import { AngularFireDatabaseModule } from '@angular/fire/database'; import { AngularFireAuthModule } from '@angular/fire/auth'; import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { CommonModule } from '@angular/common'; import { MatCardModule } from '@angular/material/card'; import { MatMenuModule } from '@angular/material/menu'; import { MatButtonModule } from '@angular/material/button'; import { MatSelectModule } from '@angular/material/select'; import { MatTableModule } from '@angular/material/table'; import { MatIconModule } from '@angular/material/icon'; import { MatDatepickerModule } from '@angular/material/datepicker'; import { MatInputModule } from '@angular/material/input'; import { MatExpansionModule } from '@angular/material/expansion'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatSortModule } from '@angular/material/sort'; import { MatNativeDateModule } from '@angular/material/core'; import { SlidesModule } from '@ng360/slides'; import { BrowserWindowModule } from '@codelab/browser'; import { FeedbackMessageTableComponent } from './feedback-message-table/feedback-message-table.component'; import { FeedbackComponent } from './feedback.component'; import { FeedbackModule as FeedbackLibModule } from '@codelab/feedback'; @NgModule({ imports: [ RouterModule, AngularFireDatabaseModule, AngularFireAuthModule, BrowserWindowModule, FormsModule, ReactiveFormsModule, CommonModule, SlidesModule, MatButtonModule, MatCardModule, MatMenuModule, MatSelectModule, MatTableModule, MatIconModule, MatDatepickerModule, MatNativeDateModule, MatFormFieldModule, MatInputModule, MatExpansionModule, MatSortModule, FeedbackLibModule ], declarations: [FeedbackComponent, FeedbackMessageTableComponent], exports: [FeedbackComponent], entryComponents: [FeedbackComponent] }) export class FeedbackModule {} ================================================ FILE: apps/codelab/src/app/admin/feedback/github.service.ts ================================================ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; @Injectable({ providedIn: 'root' }) export class GithubService { repo = 'codelab-fun/codelab'; constructor(private http: HttpClient) {} createIssue(issueData, accessToken) { const headers = { Authorization: 'token ' + accessToken }; const options = { headers }; return this.http.post( `https://api.github.com/repos/${this.repo}/issues`, issueData, options ); } closeIssue(changes, issueId, accessToken) { const headers = { Authorization: 'token ' + accessToken }; const options = { headers }; return this.http.patch( `https://api.github.com/repos/${this.repo}/issues/${issueId}`, changes, options ); } } ================================================ FILE: apps/codelab/src/app/admin/users/users.component.css ================================================ table { width: 100%; } ================================================ FILE: apps/codelab/src/app/admin/users/users.component.html ================================================
Key {{ admin.key }} You {{ admin.isCurrentUser ? '⛱' : '' }}
================================================ FILE: apps/codelab/src/app/admin/users/users.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { UsersComponent } from './users.component'; import { UsersModule } from './users.module'; import { SyncDbService } from '@codelab/utils/src/lib/sync/services/sync-db.service'; import { of } from 'rxjs'; import { LoginService } from '@codelab/firebase-login'; describe('UsersComponent', () => { let component: UsersComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [UsersModule], providers: [ { provide: SyncDbService, useValue: { list: () => ({ snapshots$: of([]) }) } }, { provide: LoginService, useValue: {} } ] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(UsersComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/codelab/src/app/admin/users/users.component.ts ================================================ import { Component, OnInit } from '@angular/core'; import { LoginService } from '@codelab/firebase-login'; import { SyncDbService } from '@codelab/utils/src/lib/sync/services/sync-db.service'; import { map } from 'rxjs/operators'; import { firebaseToValuesWithKey } from '@codelab/utils/src/lib/sync/common'; import { combineLatest, Observable } from 'rxjs'; import { Permissions } from '../../shared/services/access.service'; export interface AdminDb { key: string; permissions: Record; } export interface Admin extends AdminDb { isCurrentUser: boolean; } export interface UserDb { admin: AdminDb[]; } @Component({ selector: 'codelab-users', templateUrl: './users.component.html', styleUrls: ['./users.component.css'] }) export class UsersComponent { readonly displayedColumns = ['isCurrentUser', 'key']; readonly admins = this.dbService.list('admin'); private readonly allAdmins$ = this.admins.snapshots$.pipe( map(firebaseToValuesWithKey) ); readonly admins$: Observable = combineLatest([ this.allAdmins$, this.loginService.uid$ ]).pipe( map(([admins, currentUserUid]) => { return admins.map(admin => ({ ...admin, isCurrentUser: admin.key === currentUserUid })); }) ); constructor( private readonly loginService: LoginService, private readonly dbService: SyncDbService ) {} } ================================================ FILE: apps/codelab/src/app/admin/users/users.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { UsersComponent } from './users.component'; import { MatCardModule } from '@angular/material/card'; import { MatTableModule } from '@angular/material/table'; @NgModule({ declarations: [UsersComponent], entryComponents: [UsersComponent], imports: [CommonModule, MatTableModule, MatCardModule] }) export class UsersModule {} ================================================ FILE: apps/codelab/src/app/app-routing.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { LoginComponent } from './components/login/login.component'; import { NotFoundComponent } from './components/not-found/not-found.component'; import { AdminGuard } from './shared/services/guards/admin-guard'; import { LoginGuard } from './shared/services/guards/login-guard'; const routes: Routes = [ { path: '', loadChildren: () => import('./codelabs/codelabs.module').then(m => m.CodelabsModule) }, { path: 'login', component: LoginComponent, canActivate: [LoginGuard] }, { path: 'admin', loadChildren: () => import('./admin/admin.module').then(m => m.AdminModule), canActivate: [AdminGuard] }, { path: 'sync', loadChildren: () => import('./sync/sync.module').then(m => m.SyncAdminModule) }, { path: '**', component: NotFoundComponent } ]; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule {} ================================================ FILE: apps/codelab/src/app/app.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'codelab-root', template: '' }) export class AppComponent {} ================================================ FILE: apps/codelab/src/app/app.module.ts ================================================ import { APP_INITIALIZER, NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { monacoReady } from '@codelab/code-demos'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { AngularFireDatabaseModule } from '@angular/fire/database'; import { AngularFireModule } from '@angular/fire'; import { AngularFireAuthModule } from '@angular/fire/auth'; import { AppComponent } from './app.component'; import { AppRoutingModule } from './app-routing.module'; import { LoginModule } from './components/login/login.module'; import { menuRoutes } from './codelabs/angular/angular-routing.module'; import { MENU_ROUTES } from './common'; import { environment } from '../environments/environment'; import { NotFoundModule } from './components/not-found/not-found.module'; import { MatButtonModule } from '@angular/material/button'; import { DirectivesModule } from './directives/directives.module'; import { HttpClientModule } from '@angular/common/http'; @NgModule({ imports: [ AppRoutingModule, LoginModule, BrowserModule, BrowserAnimationsModule, HttpClientModule, AngularFireModule.initializeApp(environment.firebaseConfig), AngularFireAuthModule, AngularFireDatabaseModule, NotFoundModule, MatButtonModule, DirectivesModule ], declarations: [AppComponent], providers: [ { provide: MENU_ROUTES, useValue: menuRoutes }, { provide: APP_INITIALIZER, useValue: monacoReady, multi: true } ], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/codelab/src/app/codelabs/about/about.component.html ================================================
codelab.fun
What is codelab.fun
Online interactive angular tutorial no setup
Interactive code samples
Hands on exercises
All open source/Angular Ivy/on Github
Regular offline contributors meetings

20+ Live-events in 10+ countries

~1000 users a month

Almost 50 contributors

Live-Stream "Звуки программистов"

@kirjs and @thekiba_io meet on weekends and do long technical streams in russian, https://www.twitch.tv/kirjs

Nov 11, 2016
Angular 2.1
Kirjs did not know Angular
Move exercises to the browser
Running tests (mocha / babel + traversing AST)
- The goal is to quickly assess whether user's code does what we want.
- Initially the plan was to run unit tests in an iframe with TestBed, mocha + chai (code TBD)
Types
There's no types (TODO: Explain)
Next level
Creating slides library with Angular

What's an ideal API for a presentation?

Can we use ng-template?

Can we use ng-template?

Highlights

How to implement this?

We need a popup which

  • plays well with Monaco
  • Updates when the code is updated
  • Not obstructive

We don't. We use highlight instead

We don't. We use highlight instead

Find position by regex or string

Add classes using Monaco's deltaDecorations

Storing code samples

This can't be hard!

Can we use multiline strings?

Custom interpolation to the rescue?

Can we use ng-template?

Can we use ngNonBindable?

Can we use Script tag?

Can we use Textarea?

Textarea + ngNonBindable

Code in the component

raw-loader - code in files

Can we use figure out the types now?

Let's fake it!

Then webstorm started shouting at me.

Let's fake it!

Types work!

So we kinda ready for prime time, except nothing really works

People will probably get really mad?

Let's add feedback feature

We use firebase

We want the feedback to be as specific as possible, so we need to identify the slide

We Already use routing, and each slide has a #

We can't generate and ID

We have to give an ID manually

TADA

i18n

We use standard Angular i18n

But how can we translate code samples?

  • Have strings in template with ids
  • Then query it in the Component
{{ "@ViewChild('translations', { static: true }) translation;" }}
How to we translate it in a way that scales?

Codelab uses PoEditor

Thanks PoEditor for providing us with an Open Source license!

Anybody can help with translation
- Already translated into Russian
Let's make it faster
- изначальные проблемы:
очень долго пересобиралось, потому что пересоздавался iframe
типы задавались вручную, поэтому было проблемно подключать дополнительные библиотеки
- что сделано:
iframe стал переиспользоваться
инстанс ts compiler был переведен на watch режим
реализован bundler, который собирает типы в один файл
оптимизация бандла с пакетами, чтобы все сжималось
- что получили:
перезапуск вместо 0.5-1 сек стало почти мгновенно (показать демонстрацию)
размер бандла уменьшился в 2 раза (todo: посмотреть размер)
появилась возможность подключать любые библиотеки на этапе компиляции
Running tests (mocha / babel + traversing AST)
- The goal is to quickly assess whether user's code does what we want.
- Initially the plan was to run unit tests in an iframe with TestBed, mocha + chai (code TBD)
- Phase 2 - We started reusing iframe (code TBD)
- Special case testing bootstrapping the app (code TBD)
- We also added babel tests (babel can parse TypeScript) (code TBD)
- This works, we look at the code, not at the result.
Monaco
- The goal is to give users a type aware editor with SyntaxHighlighting quick, extensible
- We chose monaco
- Bundle size/It's own AMD loader/Have to be included in the assets
- Adding types to monaco
- Highlighting
- Autofolding
- Init on app load
Experiments/What's next?
Sync lib
- Codelab is not just an angular course it is a platform
- Demo
New runner
- What we want:
- Generic runner (not just angular)
- Does not need types/libraries bundled (fetched them asynchronously from cdn)
- Where we are?
- We don't know how to
How/Why to contribute?
Is is hard?
How to start
Why to contribute?
What's next?
================================================ FILE: apps/codelab/src/app/codelabs/about/about.component.scss ================================================ .screenshot { /*background: url("images/sss");*/ } [cover] ::ng-deep { line-height: 100vh; text-align: center; background: #6cae00; color: white; font-size: 100px; font-weight: 300; } [text] ::ng-deep { line-height: 100vh; text-align: center; background: #6cae00; color: white; font-size: 100px; font-weight: 300; } [pic] { background-size: contain; background-repeat: no-repeat; width: 100%; height: 100%; } [pic-label] ::ng-deep { line-height: 20vh; text-align: center; color: black; font-size: 100px; font-weight: 280; } .types-error { background-image: url('./images/types-error.png'); } .types-missing { background-image: url('./images/types-missing.png'); } .types-work { background-image: url('./images/types-work.png'); } .i18n-poeditor { background-image: url('./images/i18n-poeditor.png'); } .highlight-slides { background-image: url('./images/highlight-slides.png'); } .img-todo { content: 'lol'; border: 50px white dotted; margin-top: 20px; background: #d68200; box-sizing: border-box; &:before { line-height: 100%; font-size: 200px; content: 'IMAGE-TBD'; padding: 100px 0; text-align: center; display: block; color: white; } } h2 { padding: 20px; } .no-setup { background-image: url('./images/no-setup.png'); } .examples { background-image: url('./images/examples.gif'); } .exercises { background-image: url('./images/exercises.gif'); } .github { background-image: url('./images/github.png'); } .issues { background-image: url('./images/issues.gif'); } .community { background-image: url('./images/codelab.jpeg'); } ================================================ FILE: apps/codelab/src/app/codelabs/about/about.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { AboutComponent } from './about.component'; describe('AboutComponent', () => { let component: AboutComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [AboutComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(AboutComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/codelab/src/app/codelabs/about/about.component.ts ================================================ import { Component, OnInit } from '@angular/core'; declare const require; @Component({ selector: 'about', templateUrl: './about.component.html', styleUrls: ['./about.component.scss'] }) export class AboutComponent implements OnInit { code = { fakeTypes: require('!!raw-loader!./samples/fake-types.d.ts.not-really'), slides: { template: require('!!raw-loader!./samples/slides/ng-template.html'), component: require('!!raw-loader!./samples/slides/slide-component.html'), directive: require('!!raw-loader!./samples/slides/structural-directive.html') }, storingCode: { plain: require('!!raw-loader!./samples/storing-code/plain.html'), backticks: require('!!raw-loader!./samples/storing-code/backticks.html'), backticksMatch: [/{{`/, /`}}/], interpolation: { 'bootstrap.ts': require('!!raw-loader!./samples/storing-code/interpolations.ts') } }, highlights: { find: require('!!raw-loader!@codelab/code-demos/src/lib/code-demo-editor/utils/utils') } }; constructor() {} ngOnInit() {} } ================================================ FILE: apps/codelab/src/app/codelabs/about/about.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { CodeDemoModule } from '@codelab/code-demos'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { AboutComponent } from './about.component'; const routes = RouterModule.forChild(SlidesRoutes.get(AboutComponent)); @NgModule({ declarations: [AboutComponent], imports: [CommonModule, SlidesModule, routes, CodeDemoModule, FormsModule] }) export class AboutModule {} ================================================ FILE: apps/codelab/src/app/codelabs/about/samples/fake-types.d.ts.not-really ================================================ interface RouteConfig { path: string; component: any; } declare module '@angular/core' { export class EventEmitter { emit: (param: T) => void; } export interface DirectiveConfig { selector: string; } export interface ComponentConfig { selector: string; template?: string; templateUrl?: string; } export interface PipeConfig { name: string; } export function enableProdMode(); export function Component(config: ComponentConfig); export function Directive(config: DirectiveConfig); export class TemplateRef { } export class ViewContainerRef { clear: () => void; createEmbeddedView: (ref: TemplateRef, context: any) => void; } export interface AfterViewInit { ngAfterViewInit: () => void; } export interface OnInit { ngOnInit: () => void; } export interface NgModuleConfig { imports?: any[]; declarations?: any[]; providers?: any[]; bootstrap?: any[]; exports?: any[]; } export function NgModule(config: NgModuleConfig); export function Injectable(); export function Output(); export function Input(); export interface OnChanges { ngOnChanges: (simpleChanges: SimpleChange[]) => void; } export interface SimpleChange { [key: string]: any; } export function Pipe(config: PipeConfig); export interface PipeTransform { transform(value: string); } } declare module '@angular/forms' { export class FormsModule { } } declare module '@angular/platform-browser' { export class BrowserModule { } } declare module '@angular/platform-browser/animations' { export class NoopAnimationsModule { } } declare module '@angular/platform-browser-dynamic' { export class Platform { bootstrapModule: (module: any, config?: any) => void; } export function platformBrowserDynamic(): Platform; } declare module '@angular/compiler' { export class ResourceLoader { } } declare class Observable { subscribe: (T) => void; } declare module '@angular/router' { export type Routes = Array; export class RouterModule { static forRoot: (routes: Routes) => any; } } declare module '@angular/common' { export class NgIf { constructor(v: any, t: any); } } declare module '@angular/material' { export class MatTab { position: number; } export class MatTabGroup { selectedIndex: number; selectChange: Observable; } export class MatInputModule { } export class MatTabsModule { } export class MatToolbarModule { } export class MatCardModule { } export class MatButtonModule { } } ================================================ FILE: apps/codelab/src/app/codelabs/about/samples/slides/ng-template.html ================================================

Awesome slide

Code sample

================================================ FILE: apps/codelab/src/app/codelabs/about/samples/slides/slide-component.html ================================================

Awesome slide

Code sample

================================================ FILE: apps/codelab/src/app/codelabs/about/samples/slides/structural-directive.html ================================================

Awesome slide

Code sample

================================================ FILE: apps/codelab/src/app/codelabs/about/samples/storing-code/backticks.html ================================================
{{` import { Component } from '@angular/core'; @Component({ selector: 'hello-world', template: `

Hello I'm an Angular app!

Very soon you will learn how to create and bootstrap me!

` }) export class AppComponent {} `}}
================================================ FILE: apps/codelab/src/app/codelabs/about/samples/storing-code/interpolations.ts ================================================ import { Component, NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; @Component({ selector: 'app-root', template: ` 🐶 1 + 2 🦊 `, interpolation: ['🐶', '🦊'] }) export class AppComponent {} @NgModule({ imports: [BrowserModule], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {} platformBrowserDynamic() .bootstrapModule(AppModule) .catch(err => console.error(err)); ================================================ FILE: apps/codelab/src/app/codelabs/about/samples/storing-code/plain.html ================================================
import { Component } from '@angular/core'; @Component({ selector: 'hello-world', template: `

Hello I'm an Angular app!

Very soon you will learn how to create and bootstrap me!

` }) export class AppComponent {}
================================================ FILE: apps/codelab/src/app/codelabs/angular/angular-cli/angular-cli.component.css ================================================ ================================================ FILE: apps/codelab/src/app/codelabs/angular/angular-cli/angular-cli.component.html ================================================
Intro

Angular CLI is a command line tool that can be used to quickly get up to speed with running your Angular app.

$ ng hello Hi, I'm angular-cli, I'm a command line tool! It may sound scary, but I'm really easy to use!
node

Before you start, make sure you have node.js on your machine.

Open terminal and type in: node -v

$ node -v v6.11.3
installing

Run npm install -g @angular/cli to install Angular cli on your machine

      $ sudo npm install -g @angular/cli
      … A bunch of output omitted for brevety …
      │ ├─┬ strip-ansi@3.0.1
      │ │ └── ansi-regex@2.1.1
      │ ├─┬ supports-color@3.2.3
      │ │ └── has-flag@1.0.0
      │ └─┬ yargs@6.6.0
      │ ├── camelcase@3.0.0
      │ ├── os-locale@1.4.0
      │ ├─┬ string-width@1.0.2
      │ │ ├── code-point-at@1.1.0
      │ │ └─┬ is-fullwidth-code-point@1.0.0
      │ │ └── number-is-nan@1.0.1
      │ ├── which-module@1.0.0
      │ └── yargs-parser@4.2.1
      ├── webpack-merge@4.1.0
      ├── webpack-sources@1.0.1
      └── zone.js@0.8.18
      
Creating new Angular app

To create a new Angular app run: ng new awesome-app, then cd awesome-app

      $ ng new awesome-app

      create awesome-app/README.md (1026 bytes)
      create awesome-app/.angular-cli.json (1288 bytes)
      create awesome-app/.editorconfig (245 bytes)
        … A bunch of output omitted for brevety …
      create awesome-app/src/app/app.component.spec.ts (986 bytes)
      create awesome-app/src/app/app.component.ts (207 bytes)
      
Installing packages for tooling via npm. Installed packages for tooling via npm. Successfully initialized git. Project 'awesome-app' successfully created.
Starting the app

Once you're in the app folder, start the app with ng serve

>
      $ ng serve
      ** NG Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **
      Date: 2017-10-22T03:25:09.430Z - Hash: 6af162d0da3cc77873d4
      Time: 6417ms
      chunk {{'{'}}inline{{'}'}} inline.bundle.js, inline.bundle.js.map (inline) 5.83 kB [entry] [rendered]
      chunk {{'{'}}main{{'}'}} main.bundle.js, main.bundle.js.map (main) 8.63 kB {{'{'}}vendor{{'}'}} [initial]
      [rendered]
      chunk {{'{'}}polyfills{{'}'}} polyfills.bundle.js, polyfills.bundle.js.map (polyfills) 199 kB {{'{'}}inline{{'}'}}
        [initial] [rendered]
      chunk {{'{'}}styles{{'}'}} styles.bundle.js, styles.bundle.js.map (styles) 11.3 kB {{'{'}}inline{{'}'}} [initial]
      [rendered]
      chunk {{'{'}}vendor{{'}'}} vendor.bundle.js, vendor.bundle.js.map (vendor) 2.32 MB [initial] [rendered]
      
      webpack: Compiled successfully.
      
    

Once the app is started just open http://localhost:4200/ in your browser

Generating components

You can easily generate new components ng generate component my-component

      $ ng generate component my-component
      create src/app/my-component/my-component.component.css (0 bytes)
      create src/app/my-component/my-component.component.html (31 bytes)
      create src/app/my-component/my-component.component.spec.ts (664 bytes)
      create src/app/my-component/my-component.component.ts (292 bytes)
      update src/app/app.module.ts (418 bytes)
    

You can also generate modules, services and pipes

You can use a shorter version: ng g c my-component
End of The Angular-cli Milestone

This is the end of the codelab, but it's just the beginning of your Angular journey. Below are some links that can help you continue learning.

  • Angular.ioFind features, docs and events listed here
  • Angular CLI makes it easy to create an application that already works, right out of the box and generate new components! It also takes care of the build system for you
================================================ FILE: apps/codelab/src/app/codelabs/angular/angular-cli/angular-cli.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'codelab-slides-angular-cli', templateUrl: './angular-cli.component.html', styleUrls: [ '../../../components/css/codelab-styles.scss', './angular-cli.component.css' ] }) export class AngularCliComponent {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/angular-cli/angular-cli.module.ts ================================================ import { AngularCliComponent } from './angular-cli.component'; import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { FeedbackModule } from '@codelab/feedback'; import { CommonModule } from '@angular/common'; import { BrowserWindowModule } from '@codelab/browser'; import { CodelabComponentsModule } from '../../../components/codelab-components.module'; import { FormsModule } from '@angular/forms'; import { ExternalLinkDirectiveDirective } from '../../../components/external-link-directive/external-link-directive.directive'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; const routes = RouterModule.forChild([ ...SlidesRoutes.get(AngularCliComponent) ]); @NgModule({ imports: [ routes, FeedbackModule, CommonModule, BrowserWindowModule, CodelabComponentsModule, SlidesModule, FormsModule ], declarations: [AngularCliComponent, ExternalLinkDirectiveDirective], exports: [AngularCliComponent] }) export class AngularCliModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/angular-routing.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { AngularRoutesComponent } from '../../components/angular-routes/angular-routes.component'; import { FullLayoutComponent } from '../../containers/full-layout'; import { environment } from '../../../environments/environment'; import { MENU_ROUTES, MenuRoutes } from '../../common'; const routes: MenuRoutes = [ { path: '', component: FullLayoutComponent, children: [ { path: '', component: AngularRoutesComponent }, { path: 'intro', loadChildren: () => import('@codelab/intro').then(m => m.IntroModule), name: 'Introduction', description: `Learn more about Angular`, page: 'main', prod: true }, { path: 'typescript', loadChildren: () => import('./typescript/typescript.module').then( m => m.TypeScriptModule ), name: 'TypeScript', description: 'Angular is written in TypeScript, a superset of JavaScript. Learn TypeScript', page: 'typescript', prod: true }, { path: 'create-first-app', loadChildren: () => import('./create-first-app/create-first-app.module').then( m => m.CreateFirstAppModule ), name: 'Create your first Angular app', description: 'Learn how to create and bootstrap your first Angular application', page: 'main', prod: true, translationIds: ['createFirstNgApp', 'learnHowToBootstrapApp'] }, { path: 'templates', loadChildren: () => import('./templates/templates.module').then(m => m.TemplatesModule), name: 'Templates', description: 'Learn how to use Angular templates', page: 'main', prod: true, translationIds: ['templates', 'learnUsingTemplates'] }, { path: 'dependency-injection', loadChildren: () => import('./dependency-injection/dependency-injection.module').then( m => m.DependencyInjectionModule ), name: 'Dependency-Injection', description: 'Learn how to provide dependencies to your code instead of hard-coding them', page: 'main', prod: true, translationIds: ['dependencyInjection', 'learnToProvideDependencies'] }, { path: 'component-tree', loadChildren: () => import('./component-tree/component-tree.module').then( m => m.ComponentTreeModule ), name: 'Component-Tree', description: 'Learn how to structure your app with reusable components', page: 'main', prod: true, translationIds: [ 'componentTree', 'learnToStructureAppWithReusableComponents' ] }, // { // path: 'custom-events', // loadChildren: './custom-events/custom-events.module#CustomEventsModule', // name: 'Custom-Events (work in progress)', // description: 'Learn to bind to events.', // page: 'bonus', // translationIds: ['customEvents', 'learnToBindToEvents'] // }, { path: 'router', loadChildren: () => import('./router/router.module').then(m => m.RouterCodelabModule), name: 'Angular Router', description: 'Learn how to add routes to your Angular application', page: 'main', prod: true }, { path: 'material', loadChildren: () => import('./material/material.module').then( m => m.MaterialCodelabModule ), name: 'Angular Material', description: 'Learn how to use Angular Material', page: 'main', prod: true }, { path: 'forms', loadChildren: () => import('./forms/forms.module').then(m => m.FormsCodelabModule), name: 'Forms', description: 'Learn how to add Forms to your app', page: 'main', prod: true }, { path: 'angular-cli', loadChildren: () => import('./angular-cli/angular-cli.module').then( m => m.AngularCliModule ), name: 'Angular-cli', description: 'Learn how to quickly start working with angular', page: 'main', prod: true }, { path: 'pipes', loadChildren: () => import('./pipes/pipes.module').then(m => m.PipesModule), name: 'Pipes', description: 'Learn how pipes transform input values to output values for display in a view', page: 'bonus', prod: false }, { path: 'structural-directives', loadChildren: () => import('./structural-directives/structural-directives.module').then( m => m.StructuralDirectivesModule ), name: 'Structural Directives', description: 'Learn about structural directives in angular', page: 'bonus', prod: true }, { path: 'playground', loadChildren: () => import('./playground/playground.module').then( m => m.PlaygroundModule ), page: 'bonus', prod: true } ] } ]; const isProd = environment.production; export const menuRoutes = routes[0].children .filter(x => x.page === 'main') // Hide non-prod routes in prod .filter(x => !isProd || x.prod); @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule], providers: [{ provide: MENU_ROUTES, useValue: menuRoutes }] }) export class AngularRoutingModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/angular.module.ts ================================================ import { NgModule } from '@angular/core'; import { AngularRoutingModule } from './angular-routing.module'; import { FullLayoutModule } from '../../containers/full-layout/full-layout.module'; import { AngularRoutesModule } from '../../components/angular-routes/angular-routes.module'; @NgModule({ imports: [AngularRoutingModule, FullLayoutModule, AngularRoutesModule] }) export class AngularModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/component-tree/component-tree.component.css ================================================ ================================================ FILE: apps/codelab/src/app/codelabs/angular/component-tree/component-tree.component.html ================================================
video/video.component.ts: Add the '@Component' decorator and set its selector property to 'my-video'.
app.module.ts: Add VideoComponent to the AppModule 'declarations'.
video/video.component.ts: Set the templateUrl to load the appropriate html file
video/video.component.ts: Add a video property and decorate it with @Input()
video/video.component.html: Display the video title
video/video.component.html: Display the video thumbnail (src)
video/video.component.html: Display the video description
video/video.component.html: Display the video date
video/video.component.html: Display the number of video views
video/video.component.html: Display the number video likes
app.html: Replace existing title and thumbnail with our shiny new my-video component
app.html: Use the data binding to pass the video object to the component (don't forget the square brackets)
Component Tree

So far we have only one component, but as your app grows it will form a tree of components

Parent and Child

Any component can render another one by using an HTML element that matches the selector of the other component

Passing Data from Parent to Child

Parent component passes its data to the child component via properties

Change the size to 100 and color to red to recreate the Japanese flag.
Passing Data from Parent to Child

The child class must decorate its properties with a special @Input() decorator

This is the first time we're applying decorators to properties (as opposed to classes).
Exercise 1

We already know how to create a component. Let's move all the video-related information into a new component called VideoComponent.

We will bootstrap the component for you; the result will be as follows:

Cute kitten

Date 2016-11-25
Views 100
Likes 49329
Description todo
Parent and Child component

Components won't know about each other unless they're declared in the same module

Review
Exercise 2

In the next exercise you will use the newly created component

================================================ FILE: apps/codelab/src/app/codelabs/angular/component-tree/component-tree.component.ts ================================================ import { AfterViewInit, Component, ViewChild } from '@angular/core'; import { ExerciseConfigTemplate, Ng2TsExercises, SlideTemplate } from '../../../../../../../ng2ts/ng2ts'; import { extractMessages } from '@codelab/utils/src/lib/i18n/i18n-tools'; import { boxAndCircle, circleAndBox } from '../../../shared/helpers/helpers'; const circleAndBoxCode = circleAndBox(); declare const require; @Component({ selector: 'codelab-slides-component-tree', templateUrl: './component-tree.component.html', styleUrls: [ '../../../components/css/codelab-styles.scss', './component-tree.component.css' ] }) export class ComponentTreeComponent implements AfterViewInit { t: { [key: string]: string }; @ViewChild('translations', { static: false }) translation; exercise: ExerciseConfigTemplate | SlideTemplate; exercise2: ExerciseConfigTemplate | SlideTemplate; title = 'Component Tree'; description = ''; prereqs = ''; code = { parentComponentSkeleton: { match: /slides-circle/, code: `import { Component } from '@angular/core'; @Component({ selector: 'slides-hello', template: \`

Hello

\` }) export class HelloComponent {} `, path: 'parent.component.ts', type: 'typescript' }, childComponentSkeleton: { code: `import { Component } from '@angular/core'; @Component({ selector: 'slides-world', template: '

World

' }) export class WorldComponent {} `, path: 'child.component.ts', type: 'typescript' }, appModule: { code: { 'app.module.ts': require('!!raw-loader!./samples/module/app.module.ts'), 'circle.component.ts': require('!!raw-loader!./samples/module/circle.component.ts'), 'box.component.ts': require('!!raw-loader!./samples/module/box.component.ts'), 'bootstrap.ts': require('!!raw-loader!./../../../shared/angular-code/bootstrap.ts'), 'index.html': require('!!raw-loader!./samples/module/index.html') }, files: ['app.module.ts'], highlights: { 'app.module.ts': /declarations.*/ } }, parentComponent: { code: `import { Component } from '@angular/core'; import { Result } from './result.model'; @Component({ selector: 'parent', template: ' ' }) export class Parent { results(): Result[] {...} }`, path: 'parent.component.ts', type: 'typescript' }, childComponent: { code: `import { Component, Input } from '@angular/core'; import { Result } from './result.model'; @Component({ selector: 'child', template: '

{{result}}

' }) export class Child { @Input() data: Result[]; }`, path: 'child.component.ts', type: 'typescript' }, boxAndCircle: boxAndCircle(), circleAndBox: circleAndBoxCode, passingDataToChildHighlights: { 'circle.component.ts': [/@Input\(\) size/, /@Input\(\) color/] } }; constructor(private exercises: Ng2TsExercises) { this.exercise = exercises.getExercises(4, 1); this.exercise2 = exercises.getExercises(4, 2); } ngAfterViewInit() { this.t = extractMessages(this.translation); } } ================================================ FILE: apps/codelab/src/app/codelabs/angular/component-tree/component-tree.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { CodeDemoModule } from '@codelab/code-demos'; import { FeedbackModule } from '@codelab/feedback'; import { BrowserWindowModule } from '@codelab/browser'; import { ComponentTreeComponent } from './component-tree.component'; import { ComponentsHierarchySvgComponent } from './components-hierarchy-svg'; import { Ng2TsExercises } from '../../../../../../../ng2ts/ng2ts'; import { CodelabComponentsModule } from '../../../components/codelab-components.module'; const routes = RouterModule.forChild([ ...SlidesRoutes.get(ComponentTreeComponent) ]); @NgModule({ imports: [ routes, BrowserWindowModule, CodeDemoModule, FeedbackModule, CodelabComponentsModule, SlidesModule, FormsModule ], providers: [Ng2TsExercises], declarations: [ComponentTreeComponent, ComponentsHierarchySvgComponent], exports: [ComponentTreeComponent] }) export class ComponentTreeModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/component-tree/components-hierarchy-svg/components-hierarchy-svg.component.html ================================================ Child Child Child Parent ================================================ FILE: apps/codelab/src/app/codelabs/angular/component-tree/components-hierarchy-svg/components-hierarchy-svg.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentsHierarchySvgComponent } from './components-hierarchy-svg.component'; describe('ComponentsHierarchySvgComponent', () => { let component: ComponentsHierarchySvgComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ComponentsHierarchySvgComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(ComponentsHierarchySvgComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/codelab/src/app/codelabs/angular/component-tree/components-hierarchy-svg/components-hierarchy-svg.component.ts ================================================ import { ChangeDetectionStrategy, Component } from '@angular/core'; @Component({ selector: 'codelab-components-hierarchy-svg', templateUrl: './components-hierarchy-svg.component.html', changeDetection: ChangeDetectionStrategy.OnPush }) export class ComponentsHierarchySvgComponent {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/component-tree/components-hierarchy-svg/index.ts ================================================ export * from './components-hierarchy-svg.component'; ================================================ FILE: apps/codelab/src/app/codelabs/angular/component-tree/samples/module/app.module.ts ================================================ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { BoxComponent } from './box.component'; import { CircleComponent } from './circle.component'; @NgModule({ imports: [BrowserModule], declarations: [BoxComponent, CircleComponent], bootstrap: [BoxComponent] }) export class AppModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/component-tree/samples/module/box.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: `
` }) export class BoxComponent { circleColor = 'green'; } ================================================ FILE: apps/codelab/src/app/codelabs/angular/component-tree/samples/module/circle.component.ts ================================================ import { Component, Input } from '@angular/core'; @Component({ selector: 'slides-circle', template: '
' }) export class CircleComponent { @Input() size: number; @Input() color: string; } ================================================ FILE: apps/codelab/src/app/codelabs/angular/component-tree/samples/module/index.html ================================================ Loading... ================================================ FILE: apps/codelab/src/app/codelabs/angular/create-first-app/create-first-app.component.css ================================================ ================================================ FILE: apps/codelab/src/app/codelabs/angular/create-first-app/create-first-app.component.html ================================================
@Component is an Angular decorator
No semicolon here (as it attaches itself to the class below
The Decorator goes directly above the decorated entity (class in this case)
Component name is the class name (AppComponent).
Create a class called 'AppComponent'
Create a class called 'AppModule'
All set! Bootstrap your application
Export the class
Add a Component decorator for the class
Add a selector to the component decorator and set it to 'my-app'
Add a template that contains: h1 with a text "Hello MewTube!"
Add a NgModule decorator for the class
Add 'BrowserModule' to the NgModule decorator imports
Add 'AppComponent' to the 'declarations' property of the decorator
Add 'AppComponent' to the 'bootstrap' property of the decorator
What is Angular?

Angular is a development platform for building mobile and desktop applications. Angular lets you extend HTML's syntax to express your application's components clearly and succinctly. Angular's binding and Dependency Injection eliminate much of the code you would otherwise have to write.

Intro

Given an HTML file:

Let's create an Angular app which replaces the hello-world HTML element with the app's contents.

This can be done with 3 simple steps.

Intro

The 3 steps are:

  1. Create an Angular component
  2. Create an Angular module
  3. Bootstrap the module
Step 1

Start by creating an Angular Component. Components in Angular are responsible for the visual part of the app

An Angular component is just a class. Properties and behavior can be added inside.

Decorators

The class is adorned with a @Component decorator

Decorators attach Angular specific information to the class.
Decorators

Decorators are a new feature of TypeScript. They attach metadata to a class, function, property or variable

TypeScript decorators are inspired by a similar feature in the Python language.
Selector

Selectors define the location of the component. When Angular renders this component, it'll find a hello-world HTML element in the document and render the component inside of it

Inline Template

Template defines the HTML code that the component generates

If the amount of HTML grows out of hand, it's possible (and recommended) to use a templateUrl instead and provide a path to the HTML file.
Exercise

In the next slide you'll create your first Angular component! We'll do all the wiring for you. The result will look like this:

Create first Angular component!

Step 2

Next step is to declare the component in an NgModule.

NgModule does not have any visual representation and is used exclusively for grouping Angular building blocks together

We will learn more about NgModules in the future milestones
Module Class

Like a component, Angular module is just a class

NgModule Decorator

Like a component, Angular module is adorned with a decorator providing metadata

Browser Module
Declarations

The declarations array specifies components belonging to the AppModule

Bootstrap

The component passed into the bootstrap array will be created and displayed in your index.html file

Exercise

In the next slide you'll create your first Angular module! We'll use the component from the previous exercises and do all the wiring for you. The result will look like this:

Create first NgModule.

Bootstrapping

We have everything ready, so now it's time to start (bootstrap) the app!

Passing your AppModule to the bootstrapModule method will start up all the components from that module's bootstrap section

For most simple apps, you can just copy/paste the code above "as is"
Bootstrapping 1

How does bootstrapping work in Angular?


1. Kicks off execution environment. platformBrowserDynamic() tells Angular that we are operating in the browser

Bootstrapping 2

2. Angular initializes the component from the bootstrap array in app.module.ts (HelloWorldComponent in this case)

Bootstrapping 3

3. Angular looks in the document for an element matching the selector defined in HelloWorldComponent ('hello-world' in our case) and inserts the component inside that element

Exercise

All set! In the next page you'll bootstrap your first Angular app!

Now that we've got both NgModule and the component ready, let's bootstrap the app!

Review

Loading order: index -> main -> app.module -> app.component

While Angular is loading, the contents of the element will stay the same (Loading...) in this case
End of Bootstrap Section

Well done! This is the end of the milestone!

================================================ FILE: apps/codelab/src/app/codelabs/angular/create-first-app/create-first-app.component.ts ================================================ import { Component, OnInit, ViewChild } from '@angular/core'; import { ng2tsConfig } from '../../../../../../../ng2ts/ng2ts'; import { extractMessages } from '@codelab/utils/src/lib/i18n/i18n-tools'; declare const require; @Component({ selector: 'codelab-slides-create-first-app', templateUrl: './create-first-app.component.html', styleUrls: [ '../../../components/css/codelab-styles.scss', './create-first-app.component.css' ] }) export class CreateFirstAppComponent implements OnInit { t: { [key: string]: string }; // TODO(kirjs): we can't access tanslation in OnInit hook iwht static set to false // need to consider changing how we set code @ViewChild('translations', { static: true }) translation; // Exercises exercises = [ ng2tsConfig.milestones[1].exercises[1], ng2tsConfig.milestones[1].exercises[2], ng2tsConfig.milestones[1].exercises[3], { name: 'Create a component', description: '

Create first Angular component!

', files: [ { bootstrap: false, excludeFromTesting: false, type: 'typescript', path: 'app.component.ts', template: `import {Component} from '@angular/core';\n\n`, moduleName: 'app.component', code: `import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: '

Hello MewTube!

', }) export class AppComponent { } `, solution: `import { Component } from '@angular/core';\n\n@Component({\n selector: 'my-app',\n template: '

Hello MewTube!

',\n})\nexport class AppComponent {\n}\n`, after: 'export function evalJs( js ){ return eval(js);}' }, { bootstrap: false, excludeFromTesting: false, type: 'typescript', path: 'app.module.ts', template: `import { BrowserWindowModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\n import { AppComponent } from './app.component';\n\n@NgModule({\n imports: [BrowserWindowModule],\n declarations: [AppComponent], \n bootstrap: [AppComponent]\n})\nexport class AppModule {\n}\n`, moduleName: 'app.module', code: `import { BrowserWindowModule } from '@angular/platform-browser';\nimport {NgModule} from '@angular/core';\n import { AppComponent } from './app.component';\n\n@NgModule({\n imports: [BrowserWindowModule],\n declarations: [AppComponent], \n bootstrap: [AppComponent]\n})\nexport class AppModule {\n}\n`, readonly: true, collapsed: true }, { bootstrap: true, excludeFromTesting: true, type: 'typescript', path: 'main.ts', template: `import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\nimport {AppModule} from './app.module';\n\n const platform = platformBrowserDynamic();\nplatform.bootstrapModule(AppModule);\n`, moduleName: 'main', code: `import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\nimport {AppModule} from './app.module';\n\n const platform = platformBrowserDynamic();\nplatform.bootstrapModule(AppModule);\n`, readonly: true, collapsed: true } ] } ]; // tslint:disable:max-line-length TODO: Clean up exercises and remove this comment. private code: any = {}; // tslint:enable:max-line-length ngOnInit() { this.t = extractMessages(this.translation); this.code = { indexHtml: { 'index.html': require('!!raw-loader!./samples/index-html/index.html'), 'bootstrap.ts': require('!!raw-loader!./samples/index-html/bootstrap.ts') }, angularApp: { 'index.html': require('!!raw-loader!./samples/app-component/index.html'), 'bootstrap.ts': require('!!raw-loader!./samples/app-component/bootstrap.ts'), 'app.component.ts': require('!!raw-loader!./samples/app-component/app.component.ts'), 'app.module.ts': require('!!raw-loader!./samples/app-component/app.module.ts') }, indexHtmlMatches: { 'index.html': // }, helloMatches: { 'app.component.ts': /hello-world/ }, componentMatches: { 'app.component.ts': /export.*/ }, decoratorsMatches: { 'app.component.ts': /@C[^]*?\)[^]/ }, selectorMatches: { 'app.component.ts': /selector.*'.*'/ }, templateMatches: { 'app.component.ts': /template: `[^]*?`[^]/ }, exportMatches: { 'app.module.ts': /export.*/ }, ngModuleMatches: { 'app.module.ts': /@N[^]*?\)[^]/ }, declarationsMatches: { 'app.module.ts': /declarations.*/ }, bootstrapMatches: { 'app.module.ts': /bootstrap.*/ }, bootstrapPlatformMatches: { 'bootstrap.ts': /platformBrowserDynamic\(\).*/ }, decorators: { code: `import {Component} from '@angular/core'; // ${this.t.componentIsDecorator} @Component({ // metadata }) // ${this.t.noSemicolon} export class AppComponent { // ${this.t.decoratorGoesAboveEntity} // ${this.t.componentNameIsClassName} }` } }; } } ================================================ FILE: apps/codelab/src/app/codelabs/angular/create-first-app/create-first-app.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { FeedbackModule } from '@codelab/feedback'; import { CommonModule } from '@angular/common'; import { BrowserWindowModule } from '@codelab/browser'; import { CodeDemoModule } from '@codelab/code-demos'; import { FormsModule } from '@angular/forms'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { CodelabComponentsModule } from '../../../components/codelab-components.module'; import { ModeComponent } from './mode/mode.component'; import { CreateFirstAppComponent } from './create-first-app.component'; const routes = RouterModule.forChild([ ...SlidesRoutes.get(CreateFirstAppComponent) ]); @NgModule({ imports: [ routes, FeedbackModule, CommonModule, CodeDemoModule, BrowserWindowModule, CodelabComponentsModule, CodeDemoModule, SlidesModule, FormsModule ], declarations: [CreateFirstAppComponent, ModeComponent], exports: [CreateFirstAppComponent] }) export class CreateFirstAppModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/create-first-app/mode/mode.component.css ================================================ .mode { background-size: cover; cursor: pointer; background-repeat: no-repeat; width: 60px; height: 40px; padding: 5px; margin-right: 10px; filter: grayscale(100%); } .mode.mode-current { filter: none; } .mode:hover { border-bottom: 5px solid gray; } .mode-web { background-image: url(./pics/browsericon.png); } .mode-mobile { background-image: url(./pics/phoneicon.png); } .mode-vr { background-image: url(./pics/vricon.png); } .angular { width: 100px; } .module-options { display: flex; } .box-row { display: flex; } .box-row-fill { width: 50vw; } ================================================ FILE: apps/codelab/src/app/codelabs/angular/create-first-app/mode/mode.component.html ================================================

Because we're building a browser web app, we need to pass BrowserModule to the imports array

Hello World

With Angular we build mobile apps using NativeScript or Ionic.

With Angular you can build VR apps with A-FRAME or WEBGL.

Angular is not just for web apps anymore; you can also use it to create native phone apps and even VR scenes.
================================================ FILE: apps/codelab/src/app/codelabs/angular/create-first-app/mode/mode.component.spec.ts ================================================ import { ComponentFixture, TestBed } from '@angular/core/testing'; import { NO_ERRORS_SCHEMA } from '@angular/core'; import { ModeComponent } from './mode.component'; describe('ModeComponent', () => { let component: ModeComponent; let fixture: ComponentFixture; beforeEach(() => { TestBed.configureTestingModule({ schemas: [NO_ERRORS_SCHEMA], declarations: [ModeComponent] }); fixture = TestBed.createComponent(ModeComponent); component = fixture.componentInstance; }); it('can load instance', () => { expect(component).toBeTruthy(); }); it(`modes defaults to: ['web', 'mobile', 'vr']`, () => { expect(component.modes).toEqual(['web', 'mobile', 'vr']); }); }); ================================================ FILE: apps/codelab/src/app/codelabs/angular/create-first-app/mode/mode.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'codelab-slides-ng-module-mode', templateUrl: './mode.component.html', styleUrls: ['./mode.component.css'] }) export class ModeComponent implements OnInit { modes = ['web', 'mobile', 'vr']; mode = this.modes[0]; code = { moduleAnatomy: { // Module Anatomy - Milestone #1 code: `/* Imports */ @NgModule({ imports: [ BrowserModule ], declarations: [ HelloWorldComponent ], bootstrap: [ HelloWorldComponent ], }) export class AppModule {}`, codeMobile: `/* Imports */ @NgModule({ imports: [ NativeScriptModule ], declarations: [ HelloWorldComponent ], bootstrap: [ HelloWorldComponent ], }) export class AppModule {}`, codeVR: `/* Imports */ @NgModule({ imports: [ SomeMagicVRModule ], declarations: [ HelloWorldComponent ], bootstrap: [ HelloWorldComponent ], }) export class AppModule {}`, matches: { exportClass: /export.*/, ngModule: /@N[^]*?\)[^]/, importsArr: /imports.*/ }, readonly: true, path: 'module.anatomy.ts', type: 'typescript' } }; constructor() {} ngOnInit() {} } ================================================ FILE: apps/codelab/src/app/codelabs/angular/create-first-app/samples/app-component/app.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'hello-world', template: `

Hello I'm an Angular app!

Very soon you will learn how to create and bootstrap me!

` }) export class AppComponent {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/create-first-app/samples/app-component/app.module.ts ================================================ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; @NgModule({ imports: [BrowserModule], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/create-first-app/samples/app-component/bootstrap.ts ================================================ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app.module'; platformBrowserDynamic().bootstrapModule(AppModule); ================================================ FILE: apps/codelab/src/app/codelabs/angular/create-first-app/samples/app-component/index.html ================================================ Loading... ================================================ FILE: apps/codelab/src/app/codelabs/angular/create-first-app/samples/index-html/bootstrap.ts ================================================ import { Component, NgModule } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { BrowserModule } from '@angular/platform-browser'; @Component({ // tslint:disable-next-line:component-selector selector: 'hello-world', template: `

Hello I'm an Angular app!

Very soon you will learn how to create and bootstrap me!

` }) export class AppComponent {} @NgModule({ imports: [BrowserModule], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {} platformBrowserDynamic().bootstrapModule(AppModule); ================================================ FILE: apps/codelab/src/app/codelabs/angular/create-first-app/samples/index-html/index.html ================================================ Loading... ================================================ FILE: apps/codelab/src/app/codelabs/angular/custom-events/custom-events.component.css ================================================ ================================================ FILE: apps/codelab/src/app/codelabs/angular/custom-events/custom-events.component.html ================================================

Custom Events / Intro

In this milestone, you will learn to use @Output() to communicate from child to parent component and handle custom events.

Custom Events / Passing data from child to parent

To 'listen' to a child's event, we bind to the child's event childDidSomething.

To let the parent know that there is an event. We emit an event from the child.

Custom Events / Exercise - Intro

Let's implement a ThumbsUp and ThumbsDown button for the VideoComponent you worked on before.

================================================ FILE: apps/codelab/src/app/codelabs/angular/custom-events/custom-events.component.ts ================================================ import { Component } from '@angular/core'; import { ng2tsConfig } from '../../../../../../../ng2ts/ng2ts'; @Component({ selector: 'codelab-slides-custom-events', templateUrl: './custom-events.component.html', styleUrls: ['./custom-events.component.css'] }) export class CustomEventsComponent { code = { exercise1a: { // parent code: ` import { Component } from '@angular/core'; @Component({ selector: 'parent', template: \` \` }) export class ParentComponent { parentDoSomething() { console.log('child did something'); } } `, path: 'test.ts', type: 'typescript', match: /childDidSomething/ }, exercise1b: { // child code: ` import { Component } from '@angular/core'; @Component({ selector: 'child', template: \` \` }) export class ChildComponent { @Output() childDidSomething = new EventEmitter(); buttonClicked() { this.childDidSomething.emit(); } } `, path: 'test.ts', type: 'typescript', match: `` } }; exercises = [ng2tsConfig.milestones[5].exercises[1]]; // constructor(private exercises: Ng2TsExercises) { // // this.exercise = exercises.getExercises(4, 1); // // this.exercise2 = exercises.getExercises(4, 2); // } } ================================================ FILE: apps/codelab/src/app/codelabs/angular/custom-events/custom-events.module.ts ================================================ import { CustomEventsComponent } from './custom-events.component'; import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { FeedbackModule } from '@codelab/feedback'; import { CommonModule } from '@angular/common'; import { BrowserWindowModule } from '@codelab/browser'; import { CodelabComponentsModule } from '../../../components/codelab-components.module'; import { FormsModule } from '@angular/forms'; import { CodeDemoModule } from '@codelab/code-demos'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; const routes = RouterModule.forChild([ ...SlidesRoutes.get(CustomEventsComponent) ]); @NgModule({ imports: [ routes, FeedbackModule, CommonModule, BrowserWindowModule, CodeDemoModule, CodelabComponentsModule, SlidesModule, FormsModule ], declarations: [CustomEventsComponent], exports: [CustomEventsComponent] }) export class CustomEventsModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/dependency-injection/dependency-injection.component.css ================================================ .content-container { display: flex; flex-direction: row; width: 100%; margin-top: 20px; } .center-preview { margin: 0 auto; } .mewtube-preview { height: 100%; padding: 2vw; overflow: auto; } .comparision { padding: 0.5em; } .diagram-container { height: 500px; width: 100%; max-width: 800px; margin: 2rem auto 0; } .di-diagram { height: 100%; width: 100%; background: url(./pics/deps.svg) no-repeat; background-size: contain; } ================================================ FILE: apps/codelab/src/app/codelabs/angular/dependency-injection/dependency-injection.component.html ================================================
* TypeScript shorthand makes 'profession' * available to component instance.
assuming Job has property '.title'
video.service.ts: Add @Injectable() decorator to the class
app.module.ts: Add VideoService to the NgModule providers property
app.component.ts: Get rid of FAKE_VIDEOS
app.component.ts: Inject 'VideoService' in the component constructor as 'videoService'
app.component.ts: Update the app component's search method to use videoService's search method
Example of a dependency

We display a list of hard-coded videos in our component, but in the real world we'd load them from the server.

The code for fetching the data would live in a separate class (service) called VideoService

Therefore our component will depend on the VideoService:

AppComponent VideoService
Intro

As our app grows, number of dependencies will increase. Dependencies, in order, will have their own dependencies. Managing all of them manually becomes increasingly harder.

To simplify that Angular provides a mechanism called Dependency injection

Comparison

Without Dependency Injection, Profession has to be instantiated in the Person class

With Dependency Injection, Person class just "requires" an instance of Job in the constructor, and Angular takes care of instantiating it

Parameters

Without Dependency Injection, you have to figure out all the parameters yourself

With Dependency Injection, Angular takes care of it

Testing

Also Dependency Injection simplifies testing a lot, because you can just pass mock dependencies as constructor parameters

Example

Let's say we have an existing UnitConverterService and we want to start using it in UnitConversionComponent. It will take 3 simple steps:

  1. Mark dependency as @Injectable()
  2. Provide in the module
  3. Require in the component
Step 1

Mark the class as @Injectable(). This lets Angular know that this class is part of Angular Dependency Injection system

If a service class is marked as injectable, it can require other services in its constructor.
Step 2

Provide the injectable to the providers section of NgModule

Now, this service becomes available for every Component and other service in this NgModule.
Step 3

Consume the Injectable in the component

Because of the private access modifier the service becomes accessible across the class as this.converter.
Exercise

In the next slide you'll use videoService which has even more cats!!! The result will look like this:

================================================ FILE: apps/codelab/src/app/codelabs/angular/dependency-injection/dependency-injection.component.ts ================================================ import { Component, OnInit, ViewChild } from '@angular/core'; import { Ng2TsExercises } from '../../../../../../../ng2ts/ng2ts'; import { extractMessages } from '@codelab/utils/src/lib/i18n/i18n-tools'; @Component({ selector: 'codelab-slides-dependency-injection', templateUrl: './dependency-injection.component.html', styleUrls: ['./dependency-injection.component.css'] }) export class DependencyInjectionComponent implements OnInit { t: { [key: string]: string }; exercise; // TODO(kirjs): we can't access tanslation in OnInit hook iwht static set to false // need to consider changing how we set code @ViewChild('translations', { static: true }) translation; code = {}; constructor(private exercises: Ng2TsExercises) { this.exercise = exercises.getExercises(3, 1); } ngOnInit() { this.t = extractMessages(this.translation); this.code = { withOutDI: { code: `export class Person { profession: Job; constructor() { this.profession = new Job(); } }`, code2: `import {ProfessionsEnum} from './professions'; export class Person { profession: Job; constructor() { const Schedule = new Schedule(ProfessionsEnum.ENGINEER); this.profession = new Job(ProfessionsEnum.ENGINEER, Schedule, /* TODO: Find how to inject salary*/); } }`, matches: { noDI: /this.*/ }, readonly: true, path: 'person-noDI.ts', type: 'typescript' }, withDI: { code: `export class Person { /** ${this.t.shorthandMakesProfessionAvailable} */ constructor(public profession: Job) {} }`, matches: { constructor: /constructor.*/ }, readonly: true, path: 'personDI.ts', type: 'typescript' }, withDITesting: { code: `const mockProfession = new Job('lawyer'); it('should create a Person with the right profession', () => { const person = new Person(mockProfession); // ${this.t.assumingJobHasPropTitle} expect(person.profession.title).toEqual('lawyer'); }); `, matches: { constructor: /constructor.*/ }, readonly: true, path: 'personDI.spec.ts', type: 'typescript' }, classAsInjectable: { code: `import { Injectable } from '@angular/core'; @Injectable() export class UnitConverterService { // ... }`, matches: { injectable: /@I[^]*?\)[^]/ }, readonly: true, type: 'typescript' }, provideInjectable: { code: `import { NgModule } from '@angular/core'; import { UnitConverterService } from '../services/unit-converter.service'; import { UnitConversionComponent } from './unit-conversion.component'; @NgModule({ declarations: [ UnitConversionComponent ], providers: [ UnitConverterService ] }) export class AppModule {}`, matches: { providers: /providers.*/ }, readonly: true, type: 'typescript' }, consumeInjectable: { code: `import { Component } from '@angular/core'; import { UnitConverterService } from '../services/unit-converter.service'; @Component({...}) export class UnitConversionComponent { constructor(private converter: UnitConverterService) {} }`, matches: { constructor: /constructor.*/ } } }; } } ================================================ FILE: apps/codelab/src/app/codelabs/angular/dependency-injection/dependency-injection.module.ts ================================================ import { NgModule } from '@angular/core'; import { DependencyInjectionComponent } from './dependency-injection.component'; import { RouterModule } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { FeedbackModule } from '@codelab/feedback'; import { BrowserWindowModule } from '@codelab/browser'; import { CodeDemoModule } from '@codelab/code-demos'; import { CodelabComponentsModule } from '../../../components/codelab-components.module'; import { Ng2TsExercises } from '../../../../../../../ng2ts/ng2ts'; const routes = RouterModule.forChild([ ...SlidesRoutes.get(DependencyInjectionComponent) ]); @NgModule({ imports: [ routes, FeedbackModule, BrowserWindowModule, CodeDemoModule, CodelabComponentsModule, SlidesModule, FormsModule ], providers: [Ng2TsExercises], declarations: [DependencyInjectionComponent], exports: [DependencyInjectionComponent] }) export class DependencyInjectionModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/forms/forms.component.css ================================================ ================================================ FILE: apps/codelab/src/app/codelabs/angular/forms/forms.component.html ================================================
¬
app.module.ts: Add FormsModule and MatInputModule, to the imports.
upload/upload.component.html: Add a new input element
upload/upload.component.html: Bind the input to the title property using ngModel
upload/upload.component.html: Add "description" textarea bound to the description property of the component
Simple Form

We have this simple form, let's find out how to map input values with the ones from our Angular component!

Simple Form

First we have to add FormsModule to our NgModule.

NgModel

Now let's use ngModel to bind the inputs to the appropriate fields on the component.

Try changing the inputs and see the values updated.

[(NgModel)] - Banana in the box is a simple mnemonic for the braces order.
Validation

It's really easy to add validation for your inputs using predefined Angular directives.

Let's make username required

If you clear the input it'll be marked with an error, however no error will be displayed. We'll learn how to display it in the next slide
Displaying Validation Errors

Now we let's display validation error. It takes 2 steps:

  • Get ahold of the input ngModel using #name="ngModel" binding
  • Use usernameModel's errors property.
Try clearing the username input and see the error displayed.
Validators

Here's the list of built-in validators that Angular provides out of the box

  • min
  • max
  • required
  • requiredTrue
  • email
  • minLength
  • maxLength
  • pattern
It's also possible to create custom validators, but it's out of scope for this milestone, you can read more: here
Error Always Displayed

There's one small issue though, if we don't have initial values, the error is displayed right away.

Touched & Dirty

Luckily we can check if the user interacted with an input using touched property.

  • dirty is true if the user changed the value of the input.
  • touched is true if the user focused on the input, and then blured without changing the value.
Try focusing/bluring the username field, or adding some value and then removing to see the error.
Material Inputs (Bonus!)

Now let's make our forms beautiful using Material Design inputs! Main building blocks are:

  • mat-form-field - Wraps the input
  • matInput - has to be put on the input
  • mat-error - Smarter error wrapper
Note that we don't need to #name the input anymore.
Exercise

In the next slide there is an exercise. We're going to add a form to the upload page.

Note: you'd have to manually click on the "upload link" to see the result, we're working on the fix.
End of The Forms Milestone

Well done! This is the end of the milestone!

There are more than one way to handle forms in Angular. While Advanced forms milestone is in works, check out Angular docs

Next: Learn how to start working on your angular app in the Angular-cli Milestone

================================================ FILE: apps/codelab/src/app/codelabs/angular/forms/forms.component.ts ================================================ import { CodelabFile } from '../../../shared/helpers/codelabFile'; import { AfterViewInit, Component, ViewChild } from '@angular/core'; import { ExerciseConfigTemplate, Ng2TsExercises } from '../../../../../../../ng2ts/ng2ts'; import { extractMessages } from '@codelab/utils/src/lib/i18n/i18n-tools'; declare const require; interface FileHighlights { appModule?: RegExp | RegExp[]; appHtml?: RegExp | RegExp[]; appComponent?: RegExp | RegExp[]; } function formsConfig(code, highlights: FileHighlights = {}) { const files = { appHtml: require('!!raw-loader!./samples/basic/app.1.html'), appModule: require('!!raw-loader!./samples/basic/app.module.ts'), appComponent: require('!!raw-loader!./samples/basic/app.component.ts'), ...code }; return { files: [ CodelabFile.Html('app') .setCode(files.appHtml) .withHighlight(highlights.appHtml), CodelabFile.TypeScriptFile('app.module') .setCode(files.appModule) .withHighlight(highlights.appModule), CodelabFile.TypeScriptFile('app.component') .setCode(files.appComponent) .withHighlight(highlights.appComponent), CodelabFile.TypeScriptFile('bootstrap') .setCode(require('!!raw-loader!./samples/basic/main.ts')) .makeBootstrappable(), CodelabFile.Css('styles').setCode( require('!!raw-loader!@angular/material/prebuilt-themes/indigo-pink.css') ), CodelabFile.Css('extra').setCode( require('!!raw-loader!./samples/basic/styles.css') ) ] }; } @Component({ selector: 'codelab-slides-forms', templateUrl: './forms.component.html', styleUrls: ['./forms.component.css'] }) export class FormsComponent implements AfterViewInit { @ViewChild('translations', { static: false }) translations; exercise: ExerciseConfigTemplate; samples = { basicForm: formsConfig( { appHtml: require('!!raw-loader!./samples/basic/app.1.html') }, { appModule: /FormsModule]/ } ), ngModel: formsConfig( { appHtml: require('!!raw-loader!./samples/basic/app.2.html') }, { appHtml: [/ngModel/g] } ), ngValidation1: formsConfig( { appHtml: require('!!raw-loader!./samples/basic/app.3.html') }, { appHtml: [/required/] } ), ngValidation2: formsConfig( { appHtml: require('!!raw-loader!./samples/basic/app.4.html') }, { appHtml: [/#usernameModel="ngModel"/, /
/] } ), touched: formsConfig( { appHtml: require('!!raw-loader!./samples/basic/app.4.html'), appComponent: require('!!raw-loader!./samples/basic/app.component.5.ts') }, { appComponent: /username = ''/ } ), touched2: formsConfig( { appHtml: require('!!raw-loader!./samples/basic/app.5.html'), appComponent: require('!!raw-loader!./samples/basic/app.component.5.ts') }, { appHtml: /(usernameModel.touched \|\| usernameModel.dirty)/ } ), ngMaterial: formsConfig( { appHtml: require('!!raw-loader!./samples/basic/app.6.html'), appModule: require('!!raw-loader!./samples/basic/app.module.6.ts').replace( 'component.5', 'component' /*Stupid hack*/ ) }, { appHtml: [ //, /<\/mat-form-field>/, /matInput/, /.*<\/mat-error>/ ] } ) }; private t: Record; constructor(private exercises: Ng2TsExercises) { this.exercise = exercises.getExercises(7, 0); } ngAfterViewInit() { this.t = extractMessages(this.translations); } } ================================================ FILE: apps/codelab/src/app/codelabs/angular/forms/forms.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { FeedbackModule } from '@codelab/feedback'; import { BrowserWindowModule } from '@codelab/browser'; import { FormsComponent } from './forms.component'; import { Ng2TsExercises } from '../../../../../../../ng2ts/ng2ts'; import { CodelabComponentsModule } from '../../../components/codelab-components.module'; const routes = RouterModule.forChild([...SlidesRoutes.get(FormsComponent)]); @NgModule({ imports: [ routes, FeedbackModule, CommonModule, BrowserWindowModule, CodelabComponentsModule, SlidesModule, FormsModule ], declarations: [FormsComponent], exports: [FormsComponent], providers: [Ng2TsExercises] }) export class FormsCodelabModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/forms/samples/basic/app.1.html ================================================
*name:
email:
================================================ FILE: apps/codelab/src/app/codelabs/angular/forms/samples/basic/app.2.html ================================================
*name:
email:
Username: {{username}}
email: {{email}}
================================================ FILE: apps/codelab/src/app/codelabs/angular/forms/samples/basic/app.3.html ================================================
*name:
email:
Username: {{username}}
email: {{email}}
================================================ FILE: apps/codelab/src/app/codelabs/angular/forms/samples/basic/app.4.html ================================================
*name:
Username is required
email:
Username: {{username}}
email: {{email}}
================================================ FILE: apps/codelab/src/app/codelabs/angular/forms/samples/basic/app.5.html ================================================
*name:
Username is required
email:
Username: {{username}}
email: {{email}}
================================================ FILE: apps/codelab/src/app/codelabs/angular/forms/samples/basic/app.6.html ================================================

Username is required

Username: {{username}}
email: {{email}}
================================================ FILE: apps/codelab/src/app/codelabs/angular/forms/samples/basic/app.component.5.ts ================================================ import { Component } from '@angular/core'; /* tslint:disable */ @Component({ selector: 'my-app', templateUrl: './app.html' }) export class AppComponent { username = ''; email = 'pirojok@angular.io'; } ================================================ FILE: apps/codelab/src/app/codelabs/angular/forms/samples/basic/app.component.ts ================================================ import { Component } from '@angular/core'; /* tslint:disable */ @Component({ selector: 'my-app', templateUrl: 'app.html' }) export class AppComponent { username = 'Pirojok'; email = 'pirojok@angular.io'; } ================================================ FILE: apps/codelab/src/app/codelabs/angular/forms/samples/basic/app.html ================================================ ================================================ FILE: apps/codelab/src/app/codelabs/angular/forms/samples/basic/app.module.6.ts ================================================ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component.5'; import { FormsModule } from '@angular/forms'; import { MatInputModule } from '@angular/material/input'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; @NgModule({ imports: [BrowserModule, NoopAnimationsModule, MatInputModule, FormsModule], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/forms/samples/basic/app.module.ts ================================================ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { FormsModule } from '@angular/forms'; @NgModule({ imports: [BrowserModule, FormsModule], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/forms/samples/basic/code.ts ================================================ // I'm ignored export const hi = 'hi'; ================================================ FILE: apps/codelab/src/app/codelabs/angular/forms/samples/basic/main.ts ================================================ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app.module'; import { ResourceLoader } from '@angular/compiler'; import * as code from './code'; class MyResourceLoader extends ResourceLoader { get(url: string): Promise { const templateId = Object.keys(code).find(key => key.includes(url.replace(/[\/\.-]/gi, '_')) ); const template = code[templateId]; if (!template) { console.log(template); // tslint:disable-next-line:no-debugger debugger; } return Promise.resolve(template); } } platformBrowserDynamic().bootstrapModule(AppModule, { providers: [{ provide: ResourceLoader, useClass: MyResourceLoader, deps: [] }] } as any); ================================================ FILE: apps/codelab/src/app/codelabs/angular/forms/samples/basic/styles.css ================================================ body { padding: 0; margin: 0; } .error { color: red; font-size: 12px; margin-top: 6px; } .field { padding: 10px; border-bottom: 1px #ddd solid; width: 200px; } .field input { float: right; } .values { margin-top: 20px; } ================================================ FILE: apps/codelab/src/app/codelabs/angular/material/material.component.css ================================================ .examples { width: 100%; } ================================================ FILE: apps/codelab/src/app/codelabs/angular/material/material.component.html ================================================
¬
app.module.ts: Add MatCardModule and MatToolbarModule, to the imports.
app.html: Add material toolbar containing the title
video/video.component.html: Use material card to display the data
video/video.component.html: Add mat-card-title (inside of the mat-card) to display video title
video/video.component.html: Add mat-card-subtitle (inside of the mat-card) to display video description
video/video.component.html: Mark img with mat-card-image attribute (inside of the mat-card) so that it takes full card size
video/video.component.html: move date/views/likes info inside of mat-card-content (inside of the mat-card)

Now let's use Angular Material to make our app look beautiful ✨✨

Angular Material provides you with a set of Material Design components for Angular:

  • Fast and Consistent
  • Versatile
  • Accessible
  • Optimized for Angular
  • Look great on mobile

See the list of components here

MatToolBar

Adding a toolbar takes 2 steps:

  • Add MatToolbarModule to the imports
  • Use the component in the template
Note that Angular animations come in a separate module. We're including NoopAnimationsModule here, but could have used BrowserAnimationsModule instead, to have full-fledged animations.
Card

Now let's add material card.

Buttons

Adding some actions....

Note that for the button we're using mat-button attribute instead of a separate tag.
Themes

All Angular Material components allow to apply themes. Try different themes by clicking the buttons below:

Read more on theming Angular Material in this guide
Exercise

In the next slide there is an exercise. We're going to materialize our application

Note: the final app won't be super beautiful, we're still working on that.
End of The Material Milestone

Well done! This is the end of the milestone!

Check out Angular Material "Getting Started" Guide

================================================ FILE: apps/codelab/src/app/codelabs/angular/material/material.component.ts ================================================ import { AfterViewInit, Component, ViewChild, ViewEncapsulation } from '@angular/core'; import { ExerciseConfigTemplate, Ng2TsExercises } from '../../../../../../../ng2ts/ng2ts'; import { extractMessages } from '@codelab/utils/src/lib/i18n/i18n-tools'; import { CodelabFile } from '../../../shared/helpers/codelabFile'; declare const require; interface FileHighlights { appModule?: RegExp; appHtml?: RegExp; } function matExercise( modules, html, highlights: FileHighlights = {}, theme = 'purple' ) { const moduleCode = require('!!raw-loader!./samples/basic/app.module.ts').replace( /MatCardModule, MatToolbarModule/g, modules ); return { files: [ CodelabFile.TypeScriptFile('app.module') .setCode(moduleCode) .withHighlight(highlights.appModule), CodelabFile.Html('app') .setCode(html) .withHighlight(highlights.appHtml), CodelabFile.TypeScriptFile('app.component').setCode( require('!!raw-loader!./samples/basic/app.component.ts') ), CodelabFile.TypeScriptFile('bootstrap') .setCode(require('!!raw-loader!./samples/basic/main.ts')) .makeBootstrappable(), CodelabFile.Css('styles').setCode( require('!!raw-loader!@angular/material/prebuilt-themes/indigo-pink.css') ), CodelabFile.Css('extra').setCode('body {padding: 0; margin: 0;}') ] }; } @Component({ selector: 'codelab-slides-material', templateUrl: './material.component.html', styleUrls: ['./material.component.css'], encapsulation: ViewEncapsulation.None }) export class MaterialComponent implements AfterViewInit { exercise: ExerciseConfigTemplate; @ViewChild('themePlayground', { static: false }) themePlayground; @ViewChild('translations', { static: false }) translations; themes = { indigo: require('!!raw-loader!@angular/material/prebuilt-themes/indigo-pink.css'), deeppurple: require('!!raw-loader!@angular/material/prebuilt-themes/deeppurple-amber.css'), pink: require('!!raw-loader!@angular/material/prebuilt-themes/pink-bluegrey.css'), purple: require('!!raw-loader!@angular/material/prebuilt-themes/purple-green.css') }; code = { material: { step1: { code: { 'app.component.ts': require('!!raw-loader!./samples/basic/app.component.ts'), 'app.module.ts': require('!!raw-loader!./samples/step1/app.module.ts'), 'app.html': require('!!raw-loader!./samples/step1/app.html'), 'bootstrap.ts': require('!!raw-loader!./samples/basic/main.ts'), 'styles.css': this.themes.indigo }, files: ['app.module.ts', 'app.html'], highlights: { 'app.module.ts': 'MatToolbarModule,', 'app.html': // } }, step2: { code: { 'app.component.ts': require('!!raw-loader!./samples/basic/app.component.ts'), 'app.module.ts': require('!!raw-loader!./samples/step2/app.module.ts'), 'app.html': require('!!raw-loader!./samples/step2/app.html'), 'bootstrap.ts': require('!!raw-loader!./samples/basic/main.ts'), 'styles.css': this.themes.indigo }, files: ['app.module.ts', 'app.html'], highlights: { 'app.module.ts': /MatCardModule,/, 'app.html': // } }, step3: { code: { 'app.component.ts': require('!!raw-loader!./samples/basic/app.component.ts'), 'app.module.ts': require('!!raw-loader!./samples/step2/app.module.ts'), 'app.html': require('!!raw-loader!./samples/step3/app.html'), 'bootstrap.ts': require('!!raw-loader!./samples/basic/main.ts'), 'styles.css': this.themes.indigo }, files: ['app.html'], highlights: { 'app.html': // } }, step4: { code: { 'app.component.ts': require('!!raw-loader!./samples/basic/app.component.ts'), 'app.module.ts': require('!!raw-loader!./samples/step4/app.module.ts'), 'app.html': require('!!raw-loader!./samples/step4/app.html'), 'bootstrap.ts': require('!!raw-loader!./samples/basic/main.ts'), 'styles.css': this.themes.indigo }, files: ['app.module.ts', 'app.html'], highlights: { 'app.module.ts': /MatButtonModule\n/, 'app.html': // } }, themes: { code: { 'app.component.ts': require('!!raw-loader!./samples/basic/app.component.ts'), 'app.module.ts': require('!!raw-loader!./samples/step4/app.module.ts'), 'app.html': require('!!raw-loader!./samples/step4/app.html'), 'bootstrap.ts': require('!!raw-loader!./samples/basic/main.ts'), 'styles.css': this.themes.indigo }, files: ['app.html', 'styles.css'] }, theme: matExercise( `MatToolbarModule,\n MatCardModule,\n MatButtonModule`, require('!!raw-loader!./samples/basic/app.4.html') ) } }; private theme = 'indigo'; private t: Record; constructor(private exercises: Ng2TsExercises) { this.exercise = exercises.getExercises(6, 0); } ngAfterViewInit() { this.t = extractMessages(this.translations); } setTheme(theme) { this.theme = theme; this.code.material.themes.code['styles.css'] = this.themes[theme]; this.code.material.themes.code = { ...this.code.material.themes.code }; } } ================================================ FILE: apps/codelab/src/app/codelabs/angular/material/material.module.ts ================================================ import { MaterialComponent } from './material.component'; import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatInputModule } from '@angular/material/input'; import { FormsModule } from '@angular/forms'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { FeedbackModule } from '@codelab/feedback'; import { BrowserWindowModule } from '@codelab/browser'; import { CodeDemoModule } from '@codelab/code-demos'; import { Ng2TsExercises } from '../../../../../../../ng2ts/ng2ts'; import { CodelabComponentsModule } from '../../../components/codelab-components.module'; const routes = RouterModule.forChild([...SlidesRoutes.get(MaterialComponent)]); @NgModule({ imports: [ routes, FeedbackModule, CommonModule, BrowserWindowModule, MatButtonModule, MatCardModule, MatInputModule, CodelabComponentsModule, SlidesModule, FormsModule, CodeDemoModule ], declarations: [MaterialComponent], exports: [MaterialComponent], providers: [Ng2TsExercises] }) export class MaterialCodelabModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/material/samples/basic/app.1.html ================================================ Hi, I'm material toolbar ================================================ FILE: apps/codelab/src/app/codelabs/angular/material/samples/basic/app.2.html ================================================ Hi, I'm material toolbar

The Shiba Inu is the smallest of the six original and distinct spitz breeds of dog from Japan. A small, agile dog that copes very well with mountainous terrain, the Shiba Inu was originally bred for hunting.

================================================ FILE: apps/codelab/src/app/codelabs/angular/material/samples/basic/app.3.html ================================================ Hi, I'm material toolbar
Shiba Inu Dog Breed
Photo of a Shiba Inu

The Shiba Inu is the smallest of the six original and distinct spitz breeds of dog from Japan. A small, agile dog that copes very well with mountainous terrain, the Shiba Inu was originally bred for hunting.

================================================ FILE: apps/codelab/src/app/codelabs/angular/material/samples/basic/app.4.html ================================================ Header
Shiba Inu Dog Breed
Photo of a Shiba Inu

The Shiba Inu is the smallest of the six original and distinct spitz breeds of dog from Japan. A small, agile dog that copes very well with mountainous terrain, the Shiba Inu was originally bred for hunting.

================================================ FILE: apps/codelab/src/app/codelabs/angular/material/samples/basic/app.component.ts ================================================ import { Component } from '@angular/core'; /* tslint:disable */ @Component({ selector: 'my-app', templateUrl: './app.html' }) export class AppComponent {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/material/samples/basic/app.html ================================================ Header
Shiba Inu Dog Breed
Photo of a Shiba Inu

The Shiba Inu is the smallest of the six original and distinct spitz breeds of dog from Japan. A small, agile dog that copes very well with mountainous terrain, the Shiba Inu was originally bred for hunting.

================================================ FILE: apps/codelab/src/app/codelabs/angular/material/samples/basic/app.module.ts ================================================ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatToolbarModule } from '@angular/material/toolbar'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { AppComponent } from './app.component'; @NgModule({ imports: [ BrowserModule, NoopAnimationsModule, MatToolbarModule, MatCardModule, MatButtonModule ], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/material/samples/basic/code.ts ================================================ // I'm ignored export const hi = 'hi'; ================================================ FILE: apps/codelab/src/app/codelabs/angular/material/samples/basic/main.ts ================================================ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app.module'; import { ResourceLoader } from '@angular/compiler'; import * as code from './code'; class MyResourceLoader extends ResourceLoader { get(url: string): Promise { const templateId = Object.keys(code).find(key => key.includes(url.replace(/[\/\.-]/gi, '_')) ); const template = code[templateId]; if (!template) { console.log(template); // tslint:disable-next-line:no-debugger debugger; } return Promise.resolve(template); } } platformBrowserDynamic().bootstrapModule(AppModule, [ { providers: [ { provide: ResourceLoader, useFactory: () => new MyResourceLoader(), deps: [] } ] } ]); ================================================ FILE: apps/codelab/src/app/codelabs/angular/material/samples/step1/app.html ================================================ Hi, I'm material toolbar ================================================ FILE: apps/codelab/src/app/codelabs/angular/material/samples/step1/app.module.ts ================================================ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatToolbarModule } from '@angular/material/toolbar'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { AppComponent } from './app.component'; @NgModule({ imports: [ BrowserModule, NoopAnimationsModule, MatToolbarModule, MatCardModule, MatButtonModule ], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/material/samples/step2/app.html ================================================ Hi, I'm material toolbar

The Shiba Inu is the smallest of the six original and distinct spitz breeds of dog from Japan. A small, agile dog that copes very well with mountainous terrain, the Shiba Inu was originally bred for hunting.

================================================ FILE: apps/codelab/src/app/codelabs/angular/material/samples/step2/app.module.ts ================================================ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatToolbarModule } from '@angular/material/toolbar'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { AppComponent } from './app.component'; @NgModule({ imports: [ BrowserModule, NoopAnimationsModule, MatToolbarModule, MatCardModule, MatButtonModule ], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/material/samples/step3/app.html ================================================ Hi, I'm material toolbar
Shiba Inu Dog Breed
Photo of a Shiba Inu

The Shiba Inu is the smallest of the six original and distinct spitz breeds of dog from Japan. A small, agile dog that copes very well with mountainous terrain, the Shiba Inu was originally bred for hunting.

================================================ FILE: apps/codelab/src/app/codelabs/angular/material/samples/step4/app.html ================================================ Header
Shiba Inu Dog Breed
Photo of a Shiba Inu

The Shiba Inu is the smallest of the six original and distinct spitz breeds of dog from Japan. A small, agile dog that copes very well with mountainous terrain, the Shiba Inu was originally bred for hunting.

================================================ FILE: apps/codelab/src/app/codelabs/angular/material/samples/step4/app.module.ts ================================================ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatToolbarModule } from '@angular/material/toolbar'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { AppComponent } from './app.component'; @NgModule({ imports: [ BrowserModule, NoopAnimationsModule, MatToolbarModule, MatCardModule, MatButtonModule ], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/pipes/pipes.component.css ================================================ ================================================ FILE: apps/codelab/src/app/codelabs/angular/pipes/pipes.component.html ================================================

Introduction

Every application starts out with what seems like a simple task: get data, transform it, and show it to users. Getting data could be as simple as creating a local variable or as complex as streaming data over a WebSocket.

An Angular pipe is a function that transforms input values to output values for display in a view. Let's see how it works.

How do pipes work?

A pipe takes in data as input and transforms it to a desired output.

Inside the interpolation expression, we use the pipe ( | ) operator to pass the App Component's 'dob' through the the 'date' pipe on the right, transforming a long timestamp into a more human-readable output. All pipes follow this signature.

Built-in Pipes

Angular comes with a variety of built-in pipes that can be used in any template or binding expression. Some of these pipes are:

  • DatePipe
  • UpperCasePipe
  • LowerCasePipe
  • CurrencyPipe
  • PercentPipe

You can check out all the pipes in the docs.

Chained Pipes

Translate Dali's birthday to a more human-friendly format. While you're at it, make his birthday uppercase, too.

Hint: dob ... date ... uppercase

Pipes with arguments:

To add parameters to a pipe, append a colon ( : ) followed by the parameters' values e.g. currency:'AUD'

Using custom pipes to filter data

Creating a pipe

A pipe is a class decorated with pipe metadata.

Creating a pipe

The pipe class implements the PipeTransform interface.

Creating a pipe

The transform method accepts an input value followed by optional parameters and returns the transformed value.

Creating a pipe

The @Pipe decorator allows you to define the pipe name that you'll use within template expressions.

Don't forget to import the @Pipe decorator from the core Angular library.

End of the Pipes Section

Well done! This is the end of the milestone!

================================================ FILE: apps/codelab/src/app/codelabs/angular/pipes/pipes.component.ts ================================================ import { Component } from '@angular/core'; import { displayAngularComponent } from '../../../shared/helpers/helpers'; @Component({ selector: 'codelab-slides-pipes', templateUrl: './pipes.component.html', styleUrls: ['./pipes.component.css'] }) export class PipesComponent { code = { chainedPipesExample: { template: displayAngularComponent(`import {Component} from '@angular/core'; @Component({ selector: 'my-app', template: \`

Salvador's Dali DOB

{{ dob }}

\` }) export class AppComponent { dob = new Date(1904, 4, 11); }`) }, workingPipes: { template: displayAngularComponent(`import {Component} from '@angular/core'; @Component({ selector: 'my-app', template: \`

Salvador's Dali DOB

{{dob}}

{{ dob | date }}

\` }) export class AppComponent { dob = new Date(1904, 4, 11); }`), matches: { pipeOperator: '|' } }, argumentPipes: { template: `

Your budget is {{budget | currency:'AUD'}}

Your truncated name is {{name | substring:1:4}}

`, readonly: true, path: 'argument.pipe.html' }, filterPipes: { template: `
  • {{player.name}}
`, readonly: true, path: 'filter.pipe.html' }, creatingAPipe: { template: `import { Pipe, PipeTransform } from '@angular/core'; @Pipe({name: 'substring'}) export class SubstringPipe implements PipeTransform { transform(value: string, start: number, end: number): string { return (value || '').slice(start, end); } }`, matches: { exportClass: /export.*/, decorator: /@Pipe/, pipeTransform: /PipeTransform/, method: /transform[^]*?\)[^]/ }, readonly: true, path: 'substring.pipe.ts', type: 'typescript' } }; constructor() {} } ================================================ FILE: apps/codelab/src/app/codelabs/angular/pipes/pipes.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { FeedbackModule } from '@codelab/feedback'; import { BrowserWindowModule } from '@codelab/browser'; import { CodeDemoModule } from '@codelab/code-demos'; import { PipesComponent } from './pipes.component'; import { CodelabComponentsModule } from '../../../components/codelab-components.module'; const routes = RouterModule.forChild(SlidesRoutes.get(PipesComponent)); @NgModule({ imports: [ routes, CodeDemoModule, BrowserWindowModule, FeedbackModule, CodelabComponentsModule, SlidesModule, FormsModule ], declarations: [PipesComponent], exports: [PipesComponent] }) export class PipesModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/pipes/samples/pipes/app.component.html ================================================ Wow, check out this terribly formatted date:

{{ birthday }}

================================================ FILE: apps/codelab/src/app/codelabs/angular/pipes/samples/pipes/app.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'my-app', templateUrl: './app.component.html' }) export class AppComponent { birthday = new Date('2012-01-19'); } ================================================ FILE: apps/codelab/src/app/codelabs/angular/pipes/samples/pipes/app.module.ts ================================================ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; // Useless app module to prevent angular from complaining @NgModule({ imports: [BrowserModule], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/playground/angular-sample.ts ================================================ export const angularSampleCode = { 'app.component.ts': `import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: \`

Edit me

\` }) export class AppComponent {}`, 'app.module.ts': `import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; @NgModule({ imports: [BrowserModule], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {}`, 'main.ts': `import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app.module'; platformBrowserDynamic().bootstrapModule(AppModule); `, 'index.html': '' }; ================================================ FILE: apps/codelab/src/app/codelabs/angular/playground/playground.component.css ================================================ ================================================ FILE: apps/codelab/src/app/codelabs/angular/playground/playground.component.html ================================================ ================================================ FILE: apps/codelab/src/app/codelabs/angular/playground/playground.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { PlaygroundComponent } from './playground.component'; import { PlaygroundModule } from './playground.module'; import { ActivatedRoute, Router } from '@angular/router'; describe('PlaygroundComponent', () => { let component: PlaygroundComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [PlaygroundModule], providers: [ { provide: ActivatedRoute, useValue: { snapshot: { queryParams: { code: '' } } } }, { provide: Router, useValue: { navigate: jasmine.createSpy('navigate') } } ] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(PlaygroundComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create!', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/codelab/src/app/codelabs/angular/playground/playground.component.ts ================================================ import { Component } from '@angular/core'; import { angularSampleCode } from './angular-sample'; import { ActivatedRoute, Router } from '@angular/router'; @Component({ selector: 'codelab-playground', templateUrl: './playground.component.html', styleUrls: ['./playground.component.css'] }) export class PlaygroundComponent { code = angularSampleCode; constructor( private readonly activatedRoute: ActivatedRoute, private readonly router: Router ) { const code = activatedRoute.snapshot.queryParams.code; if (code) { try { this.code = { ...angularSampleCode, ...JSON.parse(atob(code)) }; } catch (e) { console.log('can not parse code', code); } } } handleUpdate(code: any) { const encoded = btoa(JSON.stringify(code)); this.router.navigate([], { relativeTo: this.activatedRoute, queryParams: { code: encoded } }); } } ================================================ FILE: apps/codelab/src/app/codelabs/angular/playground/playground.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { PlaygroundComponent } from './playground.component'; import { RouterModule } from '@angular/router'; import { CodeDemoModule } from '@codelab/code-demos'; import { FormsModule } from '@angular/forms'; @NgModule({ declarations: [PlaygroundComponent], imports: [ RouterModule.forChild([{ path: '', component: PlaygroundComponent }]), CodeDemoModule, CommonModule, FormsModule ] }) export class PlaygroundModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/router/router.component.css ================================================ .router-preview { display: flex; } .router-preview codelab-exercise-preview { margin: 10px; } .router-preview ::ng-deep .url { color: black !important; background: #f8ff15 !important; } ================================================ FILE: apps/codelab/src/app/codelabs/angular/router/router.component.html ================================================
app.module.ts: Using RouterModule.forRoot provide an empty array to the module
app.module.ts: Add a route with an empty ('') path to display SearchComponent
app.module.ts: Add a route with path of "upload" to display UploadComponent
app.html: Add router-outlet
app.html: Add a menu item with a text Search pointing to "/"
app.html: Add a menu item with a text Upload pointing to "/upload"
Intro

Router is used to give URLs to different parts of your app.

Kittens

Puppies

Intro

It takes 3 steps to set up routing in Angular:

  1. Configure the mapping and specify which component to display for which URL
  2. Create the menu with navigation links
  3. Find a place to display selected component
Configuration

Routes are configured by defining an array of mapping between URL path and a component

Configuration

Then we have to pass the config to our module.

Note that we're using RouterModule.forRoot which creates an Angular Module out of our configuration.
router-outlet

Now we have to find a place where the router would display selected component.

It can be marked by placing router-outlet tag

Exercise

In the next slide there is an exercise. We're going to add 2 routes: Search and Upload, and a menu for switching between them.

Note: We have already created empty Upload component and SearchComponent containing search logic.
End of The Routing Milestone

Well done! This is the end of the milestone!

Check out Angular Router Guide for more fun and advanced techniques.

================================================ FILE: apps/codelab/src/app/codelabs/angular/router/router.component.ts ================================================ import { CodelabFile } from '../../../shared/helpers/codelabFile'; import { AfterViewInit, Component, ViewChild } from '@angular/core'; import { ExerciseConfigTemplate, Ng2TsExercises } from '../../../../../../../ng2ts/ng2ts'; import { extractMessages } from '@codelab/utils/src/lib/i18n/i18n-tools'; declare const require; interface FileHighlights { appModule?: RegExp | RegExp[]; appHtml?: RegExp | RegExp[]; } @Component({ selector: 'codelab-slides-router', templateUrl: './router.component.html', styleUrls: ['./router.component.css'] }) export class RouterComponent implements AfterViewInit { @ViewChild('translations', { static: false }) translations; private t: Record; exercise: ExerciseConfigTemplate; code = { files: { 'app.module.ts': require('!!raw-loader!./samples/simple-router/app.module.ts'), 'app.component.html': require('!!raw-loader!./samples/simple-router/app.component.html'), 'app.component.ts': require('!!raw-loader!./samples/simple-router/app.component.ts'), 'components/kitten.ts': require('!!raw-loader!./samples/simple-router/components/kitten.ts'), 'components/puppy.ts': require('!!raw-loader!./samples/simple-router/components/puppy.ts'), 'bootstrap.ts': require('!!raw-loader!./samples/simple-router/main.ts'), 'index.html': require('!!raw-loader!./samples/simple-router/index.html'), 'components/.html': require('!!raw-loader!./samples/simple-router/index.html') }, config: { files: ['app.module.ts'], highlights: { 'app.module.ts': /const routes[\s\S]*?];[\s\S]/ } }, configPass: { files: ['app.module.ts'], highlights: { 'app.module.ts': /RouterModule.forRoot\(routes\)/ } }, routerOutlet: { files: ['app.component.html'], highlights: { 'app.component.html': /<\/router-outlet>/ } }, menu: { files: ['app.component.html'], highlights: { 'app.component.html': // } } }; constructor(private exercises: Ng2TsExercises) { this.exercise = exercises.getExercises(5, 0); } ngAfterViewInit() { this.t = extractMessages(this.translations); } } ================================================ FILE: apps/codelab/src/app/codelabs/angular/router/router.module.ts ================================================ import { RouterComponent } from './router.component'; import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { CommonModule } from '@angular/common'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { FeedbackModule } from '@codelab/feedback'; import { BrowserWindowModule } from '@codelab/browser'; import { CodeDemoModule } from '@codelab/code-demos'; import { Ng2TsExercises } from '../../../../../../../ng2ts/ng2ts'; import { CodelabComponentsModule } from '../../../components/codelab-components.module'; const routes = RouterModule.forChild([...SlidesRoutes.get(RouterComponent)]); @NgModule({ imports: [ routes, FeedbackModule, CommonModule, BrowserWindowModule, CodelabComponentsModule, SlidesModule, FormsModule, CodeDemoModule ], declarations: [RouterComponent], providers: [Ng2TsExercises], exports: [RouterComponent] }) export class RouterCodelabModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/router/samples/simple-router/app.component.html ================================================

Puppies And Kittens

Puppies | Kittens ================================================ FILE: apps/codelab/src/app/codelabs/angular/router/samples/simple-router/app.component.ts ================================================ import { Component } from '@angular/core'; @Component({ // tslint:disable-next-line selector: 'my-app', templateUrl: './app.component.html' }) export class AppComponent {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/router/samples/simple-router/app.module.ts ================================================ import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { RouterModule, Routes } from '@angular/router'; import { PuppyComponent } from './components/puppy'; import { KittenComponent } from './components/kitten'; import { BrowserModule } from '@angular/platform-browser'; const routes: Routes = [ { path: '', component: PuppyComponent }, { path: 'kittens', component: KittenComponent } ]; @NgModule({ declarations: [AppComponent, PuppyComponent, KittenComponent], imports: [BrowserModule, RouterModule.forRoot(routes)], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/router/samples/simple-router/code.ts ================================================ // I'm ignored export const hi = 'hi'; ================================================ FILE: apps/codelab/src/app/codelabs/angular/router/samples/simple-router/components/kitten.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'codelab-i-am-not-very-important', template: '

Kittens

' }) export class KittenComponent {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/router/samples/simple-router/components/puppy.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'codelab-i-will-be-ignored', template: '

Puppies!

' }) export class PuppyComponent {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/router/samples/simple-router/index.html ================================================
================================================ FILE: apps/codelab/src/app/codelabs/angular/router/samples/simple-router/main.ts ================================================ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app.module'; import { ResourceLoader } from '@angular/compiler'; import * as code from './code'; class MyResourceLoader extends ResourceLoader { get(url: string): Promise { const templateId = Object.keys(code).find(key => key.includes(url.replace(/[\/\.-]/gi, '_')) ); const template = code[templateId]; if (!template) { console.log(template); // tslint:disable-next-line:no-debugger debugger; } return Promise.resolve(template); } } platformBrowserDynamic().bootstrapModule(AppModule, { providers: [ { provide: ResourceLoader, useFactory: () => new MyResourceLoader(), deps: [] } ] }); ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/bsod.css ================================================ @import url(https://fonts.googleapis.com/css?family=Press+Start+2p); .container { display: flex; font-family: 'Press Start 2P', cursive; align-items: center; width: 100%; height: 100%; } .msg { padding-left: 100px; padding-right: 100px; } p { text-align: left; } .continue { text-align: center; } .highlight { color: rgb(1, 2, 172); background-color: rgb(172, 173, 168); padding: 3px; text-align: center; width: 150px; } .blink { animation: blink 1s steps(2, start) infinite; } @keyframes blink { to { visibility: hidden; } } #bsod, #bsod-2, #bsod-beautiful { color: white; background: #084fdd; display: inline-block; height: 100vh; } #bsod-2 .alfred-upset { position: absolute; left: 0; top: 0; width: 263px; height: 420px; background: url(./pics/Alfred_Sisley_photo_full.jpg); } #bsod-beautiful { background: url(./pics/bebbf81f5402fbbec1cff6eb687aa90a.jpg) no-repeat center center fixed; background-size: 100%; color: black; } ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/mat-tab-nav-bar/alert.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: '' + 'app-alert', template: ` Hi ALert ` }) export class AlertComponent { constructor() { alert('Hello'); } } ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/mat-tab-nav-bar/app.component.ts ================================================ import { Component } from '@angular/core'; /* tslint:disable */ @Component({ selector: 'my-app', template: ` ` }) export class AppComponent { tabs = [ { link: '', label: 'Tab 1' }, { link: 'danger', label: 'Danger' } ]; } ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/mat-tab-nav-bar/app.module.ts ================================================ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { AlertComponent } from './alert.component'; import { TabComponent } from './tab.component'; import { RouterModule } from '@angular/router'; import { APP_BASE_HREF } from '@angular/common'; import { MatTabsModule } from '@angular/material/tabs'; const routes = [ { path: '', component: TabComponent }, { path: 'danger', component: AlertComponent } ]; @NgModule({ imports: [BrowserModule, MatTabsModule, RouterModule.forRoot(routes)], declarations: [AppComponent, AlertComponent, TabComponent], bootstrap: [AppComponent], providers: [{ provide: APP_BASE_HREF, useValue: '/assets/runner/' }] }) export class AppModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/mat-tab-nav-bar/tab.component.ts ================================================ import { Component } from '@angular/core'; /* tslint:disable */ @Component({ selector: 'my-app', template: `
` }) export class TabComponent {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/material-tabs/alert.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: '' + 'app-alert', template: ` This is AlertComponent! ` }) export class AlertComponent { constructor() { alert('Hello'); } } ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/material-tabs/app.component.ts ================================================ import { Component } from '@angular/core'; /* tslint:disable */ @Component({ selector: 'my-app', templateUrl: './app.html' }) export class AppComponent {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/material-tabs/app.html ================================================

Alfred Sisley

TBD
================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/material-tabs/app.module.ts ================================================ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { MatTabsModule } from '@angular/material/tabs'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { AlertComponent } from './alert.component'; import { BreakMyComputerComponent } from './break-my-computer.component'; import { TaetLedComponent } from './taet-led.component'; @NgModule({ imports: [BrowserModule, MatTabsModule, NoopAnimationsModule], declarations: [ AppComponent, AlertComponent, BreakMyComputerComponent, TaetLedComponent ], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/material-tabs/app.solved.html ================================================

Alfred Sisley

================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/material-tabs/break-my-computer.component.ts ================================================ import { Component } from '@angular/core'; /* tslint:disable */ @Component({ selector: 'break-my-computer', template: ` I'll break your computer ` }) export class BreakMyComputerComponent { constructor() { alert('Congratulations! Your computer has been broken successfully!'); } } ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/material-tabs/style.css ================================================ body, html { width: 100%; height: 100%; } body, h1, p, div { font-family: 'Helvetica Neue', sans-serif; font-weight: 300; } ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/material-tabs/taet-led.component.ts ================================================ import { Component } from '@angular/core'; /* tslint:disable */ @Component({ selector: 'play-terrible-russian-pop-music', template: ` ` }) export class TaetLedComponent { constructor() {} } ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/material-tabs-structural-directive/alert.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: '' + 'app-alert', template: ` Hi ALert ` }) export class AlertComponent { constructor() { alert('Hello'); } } ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/material-tabs-structural-directive/app.component.ts ================================================ import { Component } from '@angular/core'; /* tslint:disable */ @Component({ selector: 'my-app', templateUrl: './app.html' }) export class AppComponent {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/material-tabs-structural-directive/app.html ================================================

Alfred Sisley

Content 2
================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/material-tabs-structural-directive/app.module.ts ================================================ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { MatTabsModule } from '@angular/material/tabs'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { AlertComponent } from './alert.component'; import { HideMeDirective } from './hideme.directive'; @NgModule({ imports: [BrowserModule, MatTabsModule, NoopAnimationsModule], declarations: [AppComponent, AlertComponent, HideMeDirective], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/material-tabs-structural-directive/app.solved.html ================================================
================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/material-tabs-structural-directive/hideme.directive.solved.ts ================================================ import { AfterViewInit, Directive, TemplateRef, ViewContainerRef } from '@angular/core'; import { MatTabGroup, MatTab } from '@angular/material/tabs'; /* tslint:disable */ @Directive({ selector: '[matHideMe]' }) export class HideMeDirective implements AfterViewInit { constructor( private parentTab: MatTab, private tabs: MatTabGroup, private viewContainer: ViewContainerRef, private templateRef: TemplateRef ) { (tabs as any).selectChange.subscribe(({ tab }: { tab: MatTab }) => { this.toggleContentDisplay(tab === parentTab); }); } toggleContentDisplay(isDisplayed: boolean) { if (isDisplayed) { this.viewContainer.createEmbeddedView(this.templateRef); } else { this.viewContainer.clear(); } } ngAfterViewInit() { this.toggleContentDisplay( this.parentTab.position === this.tabs.selectedIndex ); } } ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/material-tabs-structural-directive/hideme.directive.ts ================================================ import { Directive } from '@angular/core'; /* tslint:disable */ @Directive({ selector: '[matHideMe]' }) export class HideMeDirective { constructor() {} } ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/material-tabs-structural-directive/ignored.module.ts ================================================ import { NgModule } from '@angular/core'; import { HideMeDirective } from './hideme.directive.solved'; // This is needed because angular cli wants the directive to be in a module // https://github.com/angular/angular/issues/13590 @NgModule({ declarations: [HideMeDirective] }) export class IgnoredModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/micro-syntax/code.ts ================================================ // Needed for type checking, export const app_html = ''; ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/micro-syntax/ms.spec.ts ================================================ import { parseTemplate } from './ms'; describe('micro-syntax', () => { it('parses ngIF', () => { expect(parseTemplate('
{{hero.name}}
')) .toBe(`
{{hero.name}}
`); }); it('parses ngIf multi line', () => { expect( parseTemplate(`
{{hero.name}}
`) ).toBe(`
{{hero.name}}
`); }); xit('parses ngFor', () => { expect( parseTemplate(`
{{hero.name}}
`) ).toBe(`
{{hero.name}}
`); }); }); ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/micro-syntax/ms.ts ================================================ import * as code from './code'; export function parseTemplate(template: string) { const x = template.match( /<([^ ]+)\s+\*(\w+)\s*="([^"]+)"\s*>([\s\S]*)<\/\w+>/ ); if (x) { const [_, tag, directive, value, contents] = x; return ` <${tag}>${contents} `; } } const pre = document.createElement('pre'); document.body.appendChild(pre); pre.innerText = parseTemplate(code.app_html); ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/structural-directives/microsyntax.html ================================================ *:prefix="( :let | :expression ) (';' | ',')? ( :let | :as | :keyExp )*" :prefix: HTML attribute key. :key: HTML attribute key. :local: local variable name used in the template. :export: value exported by the directive under a given name. :experession: standard Angular expression :keyExp = :key ":"? :expression ("as" :local)? ";"? :let = "let" :local "=" :export ";"? :as = :export "as" :local ";"? ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/structural-directives/ng-for-after.html ================================================
({{i}}) {{hero.name}}
================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/structural-directives/ng-for-before.html ================================================
({{i}}) {{hero.name}}
================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/structural-directives/ng-if-after.html ================================================
{{hero.name}}
================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/samples/structural-directives/ng-if-before.html ================================================
{{hero.name}}
================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/structural-directives.component.css ================================================ #intro .bg { background: #fff; opacity: 0.5; padding: 2vw 5vw; margin: 5vw 0; } #intro h1 { font-size: 10vw; } #intro h2 { font-size: 6vw; } :host /deep/ .slide { background: transparent; } #intro { background: url(./pics/bridge-at-villeneuve-la-garenne-1872.jpg) no-repeat; display: inline-block; height: 100vh; } #we-need-tabs { background: url(./pics/alfred-sisley-9485226-1-402.jpg) no-repeat black; display: inline-block; height: 100vh; } #we-need-tabs h1 { color: #ffffee; font-size: 8vw; background: #000; opacity: 0.5; } :host /deep/ .runner, :host /deep/ .runner iframe { display: block; width: 100%; height: 100%; } /* Specificity */ :host /deep/ .side.side.side { display: block; } .font-size:hover { opacity: 1; } .font-size { display: block; opacity: 0.1; background-color: #eeeeee; border-radius: 50%; font-size: 30px; width: 30px; height: 30px; cursor: pointer; text-align: center; } .twitter { position: absolute; left: 20px; bottom: 20px; color: #400; height: 50px; z-index: 100; font-size: 50px; font-family: Monaco, 'Lucida Console', monospace; opacity: 1; width: auto; } ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/structural-directives.component.html ================================================
+
-

Structural directives

By @kirjs

We need tabs

Windows

A fatal exception 0E has occured at 028:C00068F8 in VxD VMM(01) 000059F8. The current application will be terminated.

* Press any key to terminate the application.

* Press CTRL+ALT+DEL to restart your computer. You will lose any unsaved information in all aplications.


Press any key to continue _

Windows

A fatal exception 0E has occured at 028:C00068F8 in VxD VMM(01) 000059F8. The current application will be terminated.

* Press any key to terminate the application.

* Press CTRL+ALT+DEL to restart your computer. You will lose any unsaved information in all aplications.


Press any key to continue _

Windows

A fatal exception 0E has occured at 028:C00068F8 in VxD VMM(01) 000059F8. The current application will be terminated.

* Press any key to terminate the application.

* Press CTRL+ALT+DEL to restart your computer. You will lose any unsaved information in all aplications.


Press any key to continue _

Structural directives ngIf



Desugars into



Structural directives ngFor



Desugars into



Angular MicroSyntax

More details

Russian pop music

@kirjs

================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/structural-directives.component.ts ================================================ import { Component } from '@angular/core'; import { bootstrap, builder, exercise, html, stylesheet } from '../../../shared/helpers/helpers'; declare const require; @Component({ selector: 'codelab-slides-structural-directives', templateUrl: './structural-directives.component.html', styleUrls: ['./structural-directives.component.css', './bsod.css'] }) export class StructuralDirectivesComponent { fontSize = 18; code = { materialTabs: { files: [ html( 'app', require('!!raw-loader!./samples/material-tabs/app.html'), require('!!raw-loader!./samples/material-tabs/app.solved.html') ), exercise( 'app.component', require('!!raw-loader!./samples/material-tabs/app.component.ts') ), exercise( 'alert.component', require('!!raw-loader!./samples/material-tabs/alert.component.ts') ), exercise( 'taet-led.component', require('!!raw-loader!./samples/material-tabs/taet-led.component.ts') ), exercise( 'app.module', require('!!raw-loader!./samples/material-tabs/app.module.ts') ), exercise( 'break-my-computer.component', require('!!raw-loader!./samples/material-tabs/break-my-computer.component.ts') ), stylesheet(require('!!raw-loader!./samples/material-tabs/style.css')), bootstrap('main', builder.bootstrap()) ] }, materialTabsStructuralDirective: [ html( 'app', require('!!raw-loader!./samples/material-tabs-structural-directive/app.html'), require('!!raw-loader!./samples/material-tabs-structural-directive/app.solved.html') ), exercise( 'app.component', require('!!raw-loader!./samples/material-tabs-structural-directive/app.component.ts') ), exercise( 'hideme.directive', require('!!raw-loader!./samples/material-tabs-structural-directive/hideme.directive.ts'), require('!!raw-loader!./samples/material-tabs-structural-directive/hideme.directive.solved.ts') ), exercise( 'alert.component', require('!!raw-loader!./samples/material-tabs-structural-directive/alert.component.ts') ), exercise( 'app.module', require('!!raw-loader!./samples/material-tabs-structural-directive/app.module.ts') ), stylesheet(require('!!raw-loader!./samples/material-tabs/style.css')), bootstrap('main', builder.bootstrap()) ], microSyntax: [ html('app', `
`), bootstrap('main', require('!!raw-loader!./samples/micro-syntax/ms.ts')) ], mdTabNavBar: [ exercise( 'app.component', require('!!raw-loader!./samples/mat-tab-nav-bar/app.component.ts') ), exercise( 'alert.component', require('!!raw-loader!./samples/mat-tab-nav-bar/alert.component.ts') ), exercise( 'tab.component', require('!!raw-loader!./samples/mat-tab-nav-bar/tab.component.ts') ), stylesheet(require('!!raw-loader!./samples/material-tabs/style.css')), exercise( 'app.module', require('!!raw-loader!./samples/mat-tab-nav-bar/app.module.ts') ), bootstrap('main', builder.bootstrap()) ], structuralDirectives: { ngIfBefore: require('!!raw-loader!./samples/structural-directives/ng-if-before.html'), ngIfAfter: require('!!raw-loader!./samples/structural-directives/ng-if-after.html'), ngForBefore: require('!!raw-loader!./samples/structural-directives/ng-for-before.html'), ngForAfter: require('!!raw-loader!./samples/structural-directives/ng-for-after.html'), microSyntax: require('!!raw-loader!./samples/structural-directives/microsyntax.html') } }; constructor() {} updateFontSize(diff) { this.fontSize += diff; } } ================================================ FILE: apps/codelab/src/app/codelabs/angular/structural-directives/structural-directives.module.ts ================================================ import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { FeedbackModule } from '@codelab/feedback'; import { CodeDemoModule } from '@codelab/code-demos'; import { BrowserWindowModule } from '@codelab/browser'; import { StructuralDirectivesComponent } from './structural-directives.component'; import { CodelabComponentsModule } from '../../../components/codelab-components.module'; const routes = RouterModule.forChild( SlidesRoutes.get(StructuralDirectivesComponent) ); @NgModule({ imports: [ routes, CodeDemoModule, BrowserWindowModule, FeedbackModule, CodelabComponentsModule, SlidesModule, FormsModule ], declarations: [StructuralDirectivesComponent], exports: [StructuralDirectivesComponent] }) export class StructuralDirectivesModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/templates/samples/data-binding-extra/app.component.html ================================================

Here we bind value of the input to the name property

A shortcut applying (or not) class name based on the value of isSpecial

Wow, I'm special!

Shortcut for binding styles

It also works with custom components

================================================ FILE: apps/codelab/src/app/codelabs/angular/templates/samples/data-binding-extra/app.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'my-app', templateUrl: './app.component.html' }) export class AppComponent { readonly name = 'Camille Pissarro'; isSpecial = true; } ================================================ FILE: apps/codelab/src/app/codelabs/angular/templates/samples/data-binding-extra/app.module.ts ================================================ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { BirthdayCardComponent } from './number-praiser'; import { AppComponent } from './app.component'; @NgModule({ imports: [BrowserModule], declarations: [AppComponent, BirthdayCardComponent], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/templates/samples/data-binding-extra/index.html ================================================ Loading... ================================================ FILE: apps/codelab/src/app/codelabs/angular/templates/samples/data-binding-extra/number-praiser.ts ================================================ import { Component, Input } from '@angular/core'; @Component({ // tslint:disable-next-line:component-selector selector: 'number-praiser', template: `

🎈 {{ number }}   🎈 What an amazing number!!! 🎖

` }) export class BirthdayCardComponent { @Input() number = 0; } ================================================ FILE: apps/codelab/src/app/codelabs/angular/templates/samples/event-binding/app.component.html ================================================

Message: {{ message }}

================================================ FILE: apps/codelab/src/app/codelabs/angular/templates/samples/event-binding/app.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'my-app', templateUrl: './app.component.html' }) export class AppComponent { message = 'no message'; } ================================================ FILE: apps/codelab/src/app/codelabs/angular/templates/samples/event-binding/app.module.ts ================================================ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; // Useless app module to prevent angular from complaining @NgModule({ imports: [BrowserModule], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/templates/samples/event-binding-shortcuts/app.component.html ================================================

Message: {{ message }}

================================================ FILE: apps/codelab/src/app/codelabs/angular/templates/samples/reference-binding/app.component.html ================================================

Message: {{ message }}

================================================ FILE: apps/codelab/src/app/codelabs/angular/templates/samples/reference-binding/app.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'my-app', templateUrl: './app.component.html' }) export class AppComponent { message = 'No message'; } ================================================ FILE: apps/codelab/src/app/codelabs/angular/templates/samples/reference-binding/app.module.ts ================================================ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; // Useless app module to prevent angular from complaining @NgModule({ imports: [BrowserModule], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/templates/templates.component.css ================================================ ================================================ FILE: apps/codelab/src/app/codelabs/angular/templates/templates.component.html ================================================
This is valid HTML syntax.
It works on attribute syntax.
It allows to conditionally bind a class
Or style properties
And works with custom components!
When user clicks the button, it calls the "saveUser" function on the component instance and passes the underlying event.
You can also create events for custom components. Here we have a depleted event, and it's going to call the "soundAlarm" function on the component instance when it fires.
There are also shortcut event bindings! The submit function on the component instance will be called when the user presses control and enter (this is an Angular feature).
userName has a reference to the input element
Try changing to true!
Need to repeat puppies here
app.component.ts: Add a 'videos' property, set the value as empty array.
app.component.ts: Inside of the 'search' method assign FAKE_VIDEOS, to the component 'videos' property.
app.html: Add an H1 header, display the 'title' property of the AppComponent inside
app.component.ts: Add a 'search' method on the component, that takes a 'searchString' parameter.
app.html: Add a click handler to the button, call 'search' method and pass the input value (Actual search functionality will be implemented in the next exercise)
app.html: Add a message saying 'no videos' which is displayed only when the videos array is empty
#Bonus app.component.ts: Right now it takes pressing a search button to display the videos. Instead display all videos by default.
app.component.ts: Inside of the 'search' method filter FAKE_VIDEOS and only return videos with the title containing searchString. (hint: use .includes or .indexOf string methods)
app.html: Also display a thumbnail
app.html: Iterate over the videos using '*ngFor', and display a title for each
app.html: Add a button tag with a text 'search'
app.html: Add an input tag with a 'placeholder' attribute set to 'video'
Intro

Angular has a very expressive template system, which takes HTML as a base, and extends it with custom elements

Interpolation

Double curlies include the appropriate component property value

Backticks ` `, are magic quotes that allow multi-line strings and text interpolation.
Interpolation

Simple expressions are also allowed, you can run a component method (like fullName() below), or calculate 323213+34234

Exercise

In the next slide you'll edit a component template to create a simple header and search form. The result will look like this:

Properties

String interpolation {{ curlies }} can also be used to pass a value to a child element's attribute

Property Binding

Better option is to use property binding [attribute] = property

You can use arbitrary expressions in the binding.
Data binding extras

Angular supports more advanced property bindings than just attribute name

Event binding: (event)

For handling user actions we can use event bindings. To do that we wrap the event name in parentheses, and pass an expression performing required action:

While parentheses are used for event binding: (event), "on-" can also be used, e.g. on-click is the same as (click).
Event binding shortcuts

When we need to access an HTML element or an Angular component from the template, we can mark it with #name, and it becomes available as name everywhere in the template:

We'll learn a better way to work with inputs in Forms milestone.
Event binding shortcuts

Angular also provides a shortcut for handling keyboard shortcuts. Try updating the message by pressing Control + Enter in the input.

Conditional Display (*ngIf)

This conditional expression will add or remove an element from the DOM if it evaluates as a truthy

Exercise 2

In the next slide you'll add a click handler to the search button, and display a message for the case where no videos were found. The result will look like this:

Repeating elements

Let's say you have an array of puppies, and want to display all of them on the page. Angular has a special syntax for that called *ngFor, let's see how it works on the next slide

Repeating elements (*ngFor)

Here *ngFor repeats HTML element it's attached to (li in this case) for every single puppy in the puppies array

HTML attributes in Angular are case sensitive: *ngfor won't work, *ngFor will
Exercise 3

In the next slide you'll finally display the videos! The result will look like this:

================================================ FILE: apps/codelab/src/app/codelabs/angular/templates/templates.component.ts ================================================ import { Component, OnInit, ViewChild } from '@angular/core'; import { ng2tsConfig } from '../../../../../../../ng2ts/ng2ts'; import { displayAngularComponent, displayAngularComponentWithHtml } from '../../../shared/helpers/helpers'; import { extractMessages } from '@codelab/utils/src/lib/i18n/i18n-tools'; declare const require; const baseCode = 'TODO'; @Component({ selector: 'codelab-slides-templates', templateUrl: './templates.component.html', styleUrls: ['./templates.component.css'] }) export class TemplatesComponent implements OnInit { t: { [key: string]: string }; exercises = [ ng2tsConfig.milestones[2].exercises[1], ng2tsConfig.milestones[2].exercises[2], ng2tsConfig.milestones[2].exercises[3] ]; curlies = '{{ property }}'; // TODO(kirjs): we can't access tanslation in OnInit hook iwht static set to false // need to consider changing how we set code @ViewChild('translations', { static: true }) translation; code: any = {}; constructor() {} ngOnInit() { this.t = extractMessages(this.translation); this.code = { template: { intro: displayAngularComponent(`import {Component} from '@angular/core'; @Component({ selector: 'my-app', template: '

Hello World!

' }) export class AppComponent { }`), matches: { curlies: { 'app.component.ts': [/{{.*}}/, /firstName = .*/] }, curliesFullName: { 'app.component.ts': [/{{.*}}/, /fullName\(\){/] }, curliesAttribute: { 'app.component.ts': [/"{{.*}}"/, /avatar = .*/] }, template: { 'app.component.ts': /

.*<\/h1>/ }, squares: { 'app.component.ts': /\[.*]/ } }, interpolation: displayAngularComponent( `import {Component} from '@angular/core'; @Component({ selector: 'my-app', template: \`

Hello {{firstName}}!

\` }) export class AppComponent { firstName = 'Pierre-Auguste'; lastName = 'Renoir'; }`, '' ), interpolationMethod: displayAngularComponent(`import {Component} from '@angular/core'; @Component({ selector: 'my-app', template: \`

Hello {{fullName()}}!

\` }) export class AppComponent { firstName = 'Pierre-Auguste'; lastName = 'Renoir'; fullName(){ return this.firstName + " " + this.lastName } }`), dataBindingPre: displayAngularComponent(`import {Component} from '@angular/core'; @Component({ selector: 'my-app', template: \`

Hello {{fullName()}}!

\` }) export class AppComponent { firstName = 'Pierre-Auguste'; lastName = 'Renoir'; avatar = 'assets/images/renoir.jpg'; fullName(){ return this.firstName + " " + this.lastName } }`), dataBinding: displayAngularComponent(`import {Component} from '@angular/core'; @Component({ selector: 'my-app', template: \`

Hello {{fullName()}}!

\` }) export class AppComponent { firstName = 'Pierre-Auguste'; lastName = 'Renoir'; avatar = 'assets/images/renoir.jpg'; fullName(){ return this.firstName + " " + this.lastName } }`), dataBindingExtra: { code: { 'app.component.html': require('!!raw-loader!./samples/data-binding-extra/app.component.html'), 'app.component.ts': require('!!raw-loader!./samples/data-binding-extra/app.component.ts'), 'bootstrap.ts': require('!!raw-loader!./../../../shared/angular-code/bootstrap.ts'), 'app.module.ts': require('!!raw-loader!./samples/data-binding-extra/app.module.ts'), 'number-praiser.ts': require('!!raw-loader!./samples/data-binding-extra/number-praiser.ts'), 'index.html': require('!!raw-loader!./samples/data-binding-extra/index.html') }, files: ['app.component.html', 'app.component.ts'] } }, ngIfDirective: { template: displayAngularComponent(`import {Component} from '@angular/core'; @Component({ selector: 'my-app', template: \`

Hello {{firstName}}!

\` }) export class AppComponent { firstName = 'Pierre-Auguste'; avatar = 'assets/images/renoir.jpg'; onDisplay(){ return false } // ${this.t.tryChangingToTrue} }`), matches: { ngIf: { 'app.component.ts': /\*ngIf/ } } }, ngForDirectivePre: { template: displayAngularComponent(`import {Component} from '@angular/core'; @Component({ selector: 'my-app', template: \`

Puppies names:

??? \` }) export class AppComponent { puppies = ['Schumann', 'Mendelssohn', 'Bach']; }`), matches: { 'app.component.ts': ['???', /puppies.*;/] } }, ngForDirective: { template: displayAngularComponent( `import {Component} from '@angular/core'; @Component({ selector: 'my-app', template: \`

Puppies names:

  • {{puppy}}
\` }) export class AppComponent { puppies = ['Schumann', 'Mendelssohn', 'Bach']; }`, ` import {AppComponent} from './app.component'; describe('AppComponent', ()=>{ it('Add one more puppy to the list', ()=>{ const app = new AppComponent(); chai.expect(app.puppies.length).equals(4); }) }) ` ), matches: { ngFor: { 'app.component.ts': '*ngFor' } } }, templateInterpolation: `

Profile for {{person.name}}

Profile for {{person.getBiography()}}

Photo of {{person.name}} `, templateInterpolationMatch: /{{person.name}}/, templateInterpolationExercise: displayAngularComponentWithHtml( baseCode, `

Hello, {{user.firstName}}

` ), templateInterpolationExerciseMatch: /user.firstName/, bindingPropMatch: /person.photoUrl/, bindingPropExercise: displayAngularComponentWithHtml( baseCode, `

` ), bindingPropExerciseMatch: /user.pic/, eventBinding: { code: { 'app.component.html': require('!!raw-loader!./samples/event-binding/app.component.html'), 'app.component.ts': require('!!raw-loader!./samples/event-binding/app.component.ts'), 'bootstrap.ts': require('!!raw-loader!./../../../shared/angular-code/bootstrap.ts'), 'app.module.ts': require('!!raw-loader!./../../../shared/angular-code/app.module.ts'), 'index.html': require('!!raw-loader!./../../../shared/angular-code/index.html') }, files: ['app.component.html'], highlights: { 'app.component.html': '(click)' } }, referenceBinding: { code: { 'app.component.html': require('!!raw-loader!./samples/reference-binding/app.component.html'), 'app.component.ts': require('!!raw-loader!./samples/reference-binding/app.component.ts'), 'bootstrap.ts': require('!!raw-loader!./../../../shared/angular-code/bootstrap.ts'), 'app.module.ts': require('!!raw-loader!./../../../shared/angular-code/app.module.ts'), 'index.html': require('!!raw-loader!./../../../shared/angular-code/index.html') }, files: ['app.component.html'], highlights: { 'app.component.html': ['#input', 'input.value'] } }, eventBindingShortcuts: { code: { 'app.component.html': require('!!raw-loader!./samples/event-binding-shortcuts/app.component.html'), 'app.component.ts': require('!!raw-loader!./samples/reference-binding/app.component.ts'), 'bootstrap.ts': require('!!raw-loader!./../../../shared/angular-code/bootstrap.ts'), 'app.module.ts': require('!!raw-loader!./../../../shared/angular-code/app.module.ts'), 'index.html': require('!!raw-loader!./../../../shared/angular-code/index.html') }, files: ['app.component.html'], highlights: { 'app.component.html': '(keydown.control.enter)' } }, eventBindingExercise: displayAngularComponentWithHtml( baseCode, `` ), conditionalDisplay: `
Howdy!
`, conditionalDisplayMatch: /ngIf/, conditionalDisplayExercise: displayAngularComponentWithHtml( baseCode, `` ), conditionalDisplayFor: `
  • {{puppy.name}}
`, conditionalDisplayForMatch: /ngFor/, conditionalDisplayForExercise: displayAngularComponentWithHtml( baseCode, `` ) }; } } ================================================ FILE: apps/codelab/src/app/codelabs/angular/templates/templates.module.ts ================================================ import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { FeedbackModule } from '@codelab/feedback'; import { CodeDemoModule } from '@codelab/code-demos'; import { TemplatesComponent } from './templates.component'; import { CodelabComponentsModule } from '../../../components/codelab-components.module'; const routes = RouterModule.forChild([...SlidesRoutes.get(TemplatesComponent)]); @NgModule({ imports: [ routes, CodeDemoModule, FeedbackModule, CodelabComponentsModule, SlidesModule, FormsModule ], declarations: [TemplatesComponent], exports: [TemplatesComponent] }) export class TemplatesModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/typescript/typescript/code/app.ts ================================================ export const value = { value: 4 }; ================================================ FILE: apps/codelab/src/app/codelabs/angular/typescript/typescript/code/code.ts ================================================ export { ts } from '../../../../../../../../../ng2ts/code'; export const app_ts_AST = {}; ================================================ FILE: apps/codelab/src/app/codelabs/angular/typescript/typescript/code/mini-exercise-test.ts ================================================ import { value } from './app'; import { app_ts_AST, ts } from './code'; declare const it, describe; function getFunctionNode(code) { let functionNode; /** * Fancy: Require the actual source code, and search in it. */ function findFunctionNode(node) { if ( node.kind === ts.SyntaxKind.FunctionDeclaration && node.name.text === 'add' ) { functionNode = node; } ts.forEachChild(node, findFunctionNode); } findFunctionNode(code); return functionNode; } describe('value', () => { it(`@@specifyTheTypeForB`, () => { const func = getFunctionNode(app_ts_AST); chai.assert( func.parameters[1].type && func.parameters[1].type.kind === ts.SyntaxKind.NumberKeyword, 'Test failed: b is not a number' ); }); it(`@@typescriptHighlightsErrorFix224`, () => { chai.expect(value.value).equals(4); }); }); ================================================ FILE: apps/codelab/src/app/codelabs/angular/typescript/typescript/typescript-svg/typescript-svg.component.html ================================================ ES7 Decorators Types TypeScript Classes Modules More... ES6 ES5 ================================================ FILE: apps/codelab/src/app/codelabs/angular/typescript/typescript/typescript-svg/typescript-svg.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TypeScriptSvgComponent } from './typescript-svg.component'; describe('TypeScriptSvgComponent', () => { let component: TypeScriptSvgComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [TypeScriptSvgComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TypeScriptSvgComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/codelab/src/app/codelabs/angular/typescript/typescript/typescript-svg/typescript-svg.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'codelab-typescript-svg', templateUrl: './typescript-svg.component.html' }) export class TypeScriptSvgComponent implements OnInit { constructor() {} ngOnInit() {} } ================================================ FILE: apps/codelab/src/app/codelabs/angular/typescript/typescript/typescript.component.css ================================================ ================================================ FILE: apps/codelab/src/app/codelabs/angular/typescript/typescript/typescript.component.html ================================================
Or use shorthand function notation.
Error: this is clearly not a puppy
This is a number
Or use shorthand function notation.
(Also called arrow function)
Actually TypeScript can infer number here;
TypeScript can infer it's a string.
Can't add number and boolean
Can't slice a number
But can slice a string!
Works!
Type[] does the same thing.
This is a method.
This a common russian dog name.
That's how russian dogs talk.
Now we can instantiate (create) it
And use its methods
Later we'll have code here
Let's create more puppies
Var is still allowed but not recommended.
Let should be used instead of var.
Unlike var let is unavailable outside of this if.
Const is like let, but if you try to change it, TS will give you an error.
okay, definitely a boolean
Create a class called 'Codelab'
Export the class
Add a constructor
Make constructor take a parameter 'guests'
Specify the type for the guests parameter (hint: it's an array of a type Guest)
Make the parameter public (note that now you can access it anywhere in the class using this.guests)
Create new method 'getGuestsComing'
"b" in the code below is highlighted, because TypeScript is missing the type. Specify the type for b.
With this information TypeScript can highlight the error. Fix it, make 2 + 2 = 4 again!
Modify getGuestsComing to filter the guests array return an array of guests with the 'coming' property set to true.
Why TypeScript

JavaScript is a great language, but there's space for improvement:

  • JS is not type safe which makes it harder to develop large scale applications
  • New features of the latest versions of JS standards (ES2018, ES2019) are not supported well across all the browsers
ES stands for ECMAScript, which is the name of the JavaScript language specification (standard)
TypeScript

This is why TypeScript has been created. Since TypeScript can be compiled to JavaScript, it can be used in any modern browser.

  • TypeScript extends the latest version of JavaScript
  • TypeScript adds new features from the next version of JavaScript
  • On top of it, TypeScript adds an optional type system and decorators
Decorator looks like @twitter_handles, we'll learn more about them later
Type System

Below we have an add function, and we're adding 2 and 2. What could go wrong?

Turns out it's possible to pass a string to this function and we get 22 instead of 4. Let's see how TypeScript can help address this issue on the next slide

Type System

TypeScript uses ":" to specify the type information (e.g. n: number). Both a and b should be numbers. We specified the type for a, now it's your turn!

The code above is editable!
Primitives (strings, numbers, etc...)

Below are more types we can use

Interfaces

TypeScript Interfaces allow to specify properties and methods for an object.

Here, realPuppy is an implementation of the Puppy Interface.
Arrays

Array types are defined as Array{{ '<' }}Type{{ '>' }} or Type[]

Here, each element in the betterCats array is an instance of the Cat Interface.
Classes

TypeScript has classes, and Angular uses them heavily.

They are similar to classes in other languages, and are used to group methods and properties together

Constructor

There's a special method on the class called constructor. It's run when the class is instantiated and allows the class to take parameters

Access Modifiers

Constructor parameters marked as public (or private, or protected), become class properties accessible as this.ParameterName within the class

private or protected properties are not visible outside of the class.
Export

By the way, did you notice the export keyword before class? It is used to share information between files. In the next slide, we'll show you how to import and use this class in a different file

Import

Now we can use the Puppy class in the other file

import and export keywords are not just for classes. They work with variables, functions and other things!
Filter (One last thing)

"filter" is an Array method that allows you to generate a new array keeping only values that satisfy the condition

More

TypeScript supports lots of other cool features such as:

We won't cover them in detail, check out the TypeScript website!

Exercise

In the next slide we have a TypeScript exercise

Your task is to build a TypeScript class called Codelab which will take a list of guests, and will have a method to output only the ones who are coming.

The result will be as follows:

Milestone Completed

Now you should know enough TypeScript to start learning Angular! Read more about TypeScript on TypeScript web site

================================================ FILE: apps/codelab/src/app/codelabs/angular/typescript/typescript/typescript.component.ts ================================================ import { Component, OnInit, ViewChild } from '@angular/core'; import { extractMessages } from '@codelab/utils/src/lib/i18n/i18n-tools'; import { ng2tsConfig } from '../../../../../../../../ng2ts/ng2ts'; import { javaScriptWithConsoleLog, typeScriptWithConsoleLog } from '../../../../shared/helpers/helpers'; declare const require; @Component({ selector: 'codelab-slides-typescript', templateUrl: './typescript.component.html', styleUrls: ['./typescript.component.css'] }) export class TypeScriptComponent implements OnInit { t: { [key: string]: string }; // need to consider changing how we set code @ViewChild('translations', { static: true }) translation; // TODO(kirjs): we can't access tanslation in OnInit hook iwht static set to false private exercises = [ng2tsConfig.milestones[0].exercises[1]]; private code: any = {}; ngOnInit(): void { this.t = extractMessages(this.translation); this.code = { filter: typeScriptWithConsoleLog(`const numbers = [12,23,62,34,19,40,4,9]; console.log(numbers.filter(function(n: number){ return n > 30; })); // ${this.t.useShorthandNotation} // ${this.t.calledArrowFunction} console.log( numbers.filter(n => n > 30) );`), moreTypes: { codeInterfaces: `interface Puppy { name: string; age: number; }; const realPuppy: Puppy = { name: 'Pikachu', age: 1 }; const notRealPuppy: Puppy = { says: 'meow' // ${this.t.errorNotAPuppy} }`, codeArraysMatch: /Array/, codeArrays: typeScriptWithConsoleLog(`// Array const cats: Array = ['Simba', 'Aslan']; // ${this.t.typeDoesSameThing} const cats2: string[] = ['Simba', 'Aslan']; interface Cat { name: string, age: number } const betterCats: Cat[] = [ {name: 'Simba', age: 22}, {name: 'Aslan', age: 9999} ]; console.log(betterCats);`), code: `const price: number = 100; // ${this.t.thisIsNumber} const tax = 20; // ${this.t.typescriptCanInferNumber} const productName = 'pikachu'; // ${this.t.typescriptCanInferString} const isHungry = true; // Boolean const weird = tax + isHungry; // ${this.t.cantAddNumAndBool} tax.slice(1,5); // ${this.t.cantSliceNum} productName.slice(1,5); // ${this.t.canSliceString} const total = price + tax; // ${this.t.works}` }, varDeclaration: { code: `// ${this.t.varAllowedNotRecommended} var v = 1; // ${this.t.letInsteadOfVar} let l = 1; if(true){ let ll = 1; // ${this.t.letUnavailableOutsideIfUnlikeIf} } console.log(ll); // undefined // ${this.t.constLikeLet} const x = 1; x = 2;` }, stringType: { code: `let fullName: string = 'Bob Bobbington'; let sentence: string = \`Hello, my name is \${ fullName }.\`;` }, stringType2: { code: `let sentence: string = "Hello, my name is " + fullName + "."` }, anyType: { code: `let notSure: any = 4; notSure = "maybe a string instead"; notSure = false; // ${this.t.definitelyBoolean}` }, classDescription: { code: typeScriptWithConsoleLog(`export class Puppy { // ${this.t.commonDogName} name = 'Bar Boss'; // ${this.t.thisIsMethod} bark(){ // ${this.t.thatsHowRussianDogsTalk} return this.name + ': Gav gav!!'; } } // ${this.t.nowWeCanInstantiate} var hotdog = new Puppy(); // ${this.t.andUseItsMethods} console.log(hotdog.bark()); `), codeConstructor: typeScriptWithConsoleLog(`export class Puppy { constructor(public name: string){ // ${this.t.laterWeWillHaveCode} } bark(){ return 'Gav! my name is ' + this.name; } } var hotdog = new Puppy('Édouard'); console.log(hotdog.bark()); // ${this.t.letsCreateMorePuppies} var oscar = new Puppy('Oscar-Claude'); console.log(oscar.bark());`), codeExport: typeScriptWithConsoleLog(`export class Puppy { constructor(public name: string){} bark(){ return 'Gav! my name is ' + this.name; } }`), codeImport: typeScriptWithConsoleLog( `import {Puppy} from './puppy'; var hotdog = new Puppy('Édouard'); console.log(hotdog.bark()); // ${this.t.letsCreateMorePuppies} var oscar = new Puppy('Oscar-Claude'); console.log(oscar.bark());`, 'import "./app";', undefined, `export class Puppy { constructor(public name: string){} bark(){ return 'Gav! my name is ' + this.name; } }` ), matches: { classPuppyMatch: { 'app.ts': /class Puppy/ }, classMatch: /class/, exportMatch: /export/, importMatch: { 'puppy.ts': /export/, 'app.ts': /import/ }, arrayMatch: { 'app.ts': [/Array/, /string\[]/] }, constants: /const /, constructorMatch: { 'app.ts': [/(public name: string)/, /Édouard/] }, modifierMatch: { 'app.ts': [/public name/, /this.name/] }, oscarMatch: /Oscar-Claude/ } }, tsExercise: typeScriptWithConsoleLog( `function add(a: number, b){ return a+b; }; console.log(add(2, '2'));`, undefined, require(`!raw-loader!./code/mini-exercise-test.ts`) .replace('@@specifyTheTypeForB', this.t.specifyTheTypeForB) .replace( '@@typescriptHighlightsErrorFix224', this.t.typescriptHighlightsErrorFix224 ) ), js2And2: (() => { const code = javaScriptWithConsoleLog( `function add(a, b){ return a+b; }; console.log(add(2, '2'));` ); (code.files[2] as any).bootstrap = false; return code; })(), tsExerciseMatch: { 'app.ts': /'.*'/ } }; } } ================================================ FILE: apps/codelab/src/app/codelabs/angular/typescript/typescript-routing.module.ts ================================================ import { Component, NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { SlidesRoutes } from '@ng360/slides'; import { TypeScriptComponent } from './typescript/typescript.component'; @Component({ // tslint:disable-next-line:component-selector selector: 'ignored', template: '' }) export class EmptyTypeScriptComponent {} const routes = [ { path: '', component: EmptyTypeScriptComponent, children: [...SlidesRoutes.get(TypeScriptComponent)] } ]; @NgModule({ declarations: [EmptyTypeScriptComponent], entryComponents: [EmptyTypeScriptComponent], imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class TypeScriptRoutingModule {} ================================================ FILE: apps/codelab/src/app/codelabs/angular/typescript/typescript.module.ts ================================================ import { NgModule } from '@angular/core'; import { TypeScriptComponent } from './typescript/typescript.component'; import { TypeScriptRoutingModule } from './typescript-routing.module'; import { TypeScriptSvgComponent } from './typescript/typescript-svg/typescript-svg.component'; import { SharedModule } from '../../../shared/shared.module'; @NgModule({ declarations: [TypeScriptComponent, TypeScriptSvgComponent], imports: [SharedModule, TypeScriptRoutingModule] }) export class TypeScriptModule {} ================================================ FILE: apps/codelab/src/app/codelabs/codelabs-routing.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { IndexComponent } from '../components/index/index.component'; const routes: Routes = [ { path: '', component: IndexComponent }, { path: 'angular', loadChildren: () => import('./angular/angular.module').then(m => m.AngularModule) }, { path: 'extra', loadChildren: () => import('./extra/extra.module').then(m => m.ExtraModule) }, { path: 'about', loadChildren: () => import('./about/about.module').then(m => m.AboutModule) } ]; @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class CodelabsRoutingModule {} ================================================ FILE: apps/codelab/src/app/codelabs/codelabs.module.ts ================================================ import { NgModule } from '@angular/core'; import { CodelabsRoutingModule } from './codelabs-routing.module'; import { IndexModule } from '../components/index/index.module'; @NgModule({ imports: [CodelabsRoutingModule, IndexModule] }) export class CodelabsModule {} ================================================ FILE: apps/codelab/src/app/codelabs/extra/code-playground/code-playground.component.css ================================================ .vscode-gifs img { width: 100%; height: auto; max-height: 100%; } .feature-title { text-align: center; font-style: italic; margin: 20px 1px 1px 1px; } .vscode-gifs { display: inline-block; padding: 5px; } ================================================ FILE: apps/codelab/src/app/codelabs/extra/code-playground/code-playground.component.html ================================================ ================================================ FILE: apps/codelab/src/app/codelabs/extra/code-playground/code-playground.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'codelab-code-playground', templateUrl: './code-playground.component.html', styleUrls: ['./code-playground.component.css'] }) export class CodePlaygroundComponent {} // // // // 1. code- components // // // // // // // // // // // // // // // // // // // // // // // // // /// TypeScript // /// Javasdcript // // @Component({ // selector: 'codelab-exercise', // template: '', // providers: [CodeSource] // }) // class CodelabExercise { // constructor(cs: CodeSource) { // cs.addBeforeStep((cide) => { // code.before = 1 // }) // } // // getRoot(){ // return this.parent || this; // } // // addCode(code: Record) { // this.code = code; // } // // getCode() { // if (this.code) { // return this.code; // } // return this.parent.getCode(); // } // // getDeps() { // return this.parent && this.parent.getDeps() + this.deps; // } // // } // // // @Component({ // selector: 'code-group', // template: '', // providers: [CodeSource] // }) // class CodeGroup { // // { "main.ts": "console.log('')" } // @Input() code: Record; // // { "main.ts": "console.log('Hello')" } // @Input() solution: Record; // // // "main.ts" // // @Input() bootstrap: string; // // "angular, react" // @Input() deps: string; // @Input() compile: (code: Record) => Record; // // "browser, tests" // @Input() ui: string; // } // // @Component({ // selector: 'code-preview', // template: '', // providers: [CodeSource] // }) // class CodePreview { // constructor(private source: CodeSource) { // } // } ================================================ FILE: apps/codelab/src/app/codelabs/extra/code-playground/code-playground.module.ts ================================================ import { RouterModule } from '@angular/router'; import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { FeedbackModule } from '@codelab/feedback'; import { CodePlaygroundComponent } from './code-playground.component'; const routes = RouterModule.forChild(SlidesRoutes.get(CodePlaygroundComponent)); @NgModule({ imports: [routes, SlidesModule, FeedbackModule, CommonModule], declarations: [CodePlaygroundComponent], exports: [CodePlaygroundComponent] }) export class CodePlaygroundModule {} ================================================ FILE: apps/codelab/src/app/codelabs/extra/extra-routing.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { environment } from '../../../environments/environment'; import { FullLayoutComponent } from '../../containers/full-layout'; let routes = [ { path: '', component: FullLayoutComponent, children: [ { path: 'code-playground', loadChildren: () => import('./code-playground/code-playground.module').then( m => m.CodePlaygroundModule ), name: 'code-playground', description: 'Learn how pipes transform input values to output values for display in a view', page: 'extra' }, { path: 'rating-summary', loadChildren: () => import('./rating-summary/rating-summary.module').then( m => m.RatingSummaryModule ), name: 'rating-summary', description: 'Learn how pipes transform input values to output values for display in a view', page: 'extra' } ] } ]; if (environment.production) { routes = routes.filter(r => r['prod']); } @NgModule({ imports: [RouterModule.forChild(routes)], exports: [RouterModule] }) export class ExtraRoutingModule {} ================================================ FILE: apps/codelab/src/app/codelabs/extra/extra.module.ts ================================================ import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { HttpClientModule } from '@angular/common/http'; import { CommonModule } from '@angular/common'; import { SharedModule } from '../../shared/shared.module'; import { ExtraRoutingModule } from './extra-routing.module'; import { FullLayoutModule } from '../../containers/full-layout/full-layout.module'; @NgModule({ imports: [ SharedModule, ExtraRoutingModule, CommonModule, HttpClientModule, FormsModule, FullLayoutModule ] }) export class ExtraModule {} ================================================ FILE: apps/codelab/src/app/codelabs/extra/rating-summary/rating-summary.component.css ================================================ .ratingsummary { text-align: center; margin-left: auto; margin-right: auto; margin-top: 60px; border: 1px solid slategray; padding: 20px; } ================================================ FILE: apps/codelab/src/app/codelabs/extra/rating-summary/rating-summary.component.html ================================================
================================================ FILE: apps/codelab/src/app/codelabs/extra/rating-summary/rating-summary.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'codelab-slides-rating-summary', templateUrl: './rating-summary.component.html', styleUrls: ['./rating-summary.component.css'] }) export class RatingSummaryComponent implements OnInit { ngOnInit() {} } ================================================ FILE: apps/codelab/src/app/codelabs/extra/rating-summary/rating-summary.module.ts ================================================ import { AngularFireModule } from '@angular/fire'; import { AngularFireDatabaseModule } from '@angular/fire/database'; import { AngularFireAuthModule } from '@angular/fire/auth'; import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CommonModule } from '@angular/common'; import { HttpClientModule } from '@angular/common/http'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { FeedbackModule } from '@codelab/feedback'; import { BrowserWindowModule } from '@codelab/browser'; import { RatingSummaryComponent } from './rating-summary.component'; import { environment } from '../../../../environments/environment'; const routes = RouterModule.forChild(SlidesRoutes.get(RatingSummaryComponent)); export const angularFire = AngularFireModule.initializeApp( environment.firebaseConfig ); @NgModule({ imports: [ routes, BrowserWindowModule, angularFire, CommonModule, HttpClientModule, FeedbackModule, SlidesModule, AngularFireDatabaseModule, AngularFireAuthModule ], declarations: [RatingSummaryComponent], providers: [], exports: [RatingSummaryComponent] }) export class RatingSummaryModule {} ================================================ FILE: apps/codelab/src/app/codelabs/extra/visual-studio-code/visual-studio-code.component.css ================================================ .vscode-gifs img { width: 100%; height: auto; max-height: 100%; } .feature-title { text-align: center; font-style: italic; margin: 20px 1px 1px 1px; } .vscode-gifs { display: inline-block; padding: 5px; } ================================================ FILE: apps/codelab/src/app/codelabs/extra/visual-studio-code/visual-studio-code.component.html ================================================

What is Visual Studio Code?

Visual Studio Code is a free, open-source code editor developed by Microsoft

  • built with Github Electron, and written in HTML, CSS,and Javascript
  • offers a robust extensible architecture and is highly customizable
  • built-in perks include:
    • Source Control
    • Extensions Marketplace
    • Debugger
    • Built-in Terminal

Why Use Visual Studio Code?

It has awesome tooling support and works especially well with TypeScript.

It comes with Intellisense, which provides code completion and code info.

IntelliSense demo gif
IntelliSense demo gif

No Really, Why Use Visual Studio Code?

Missing Reference? Syntax Error? No problem!

Look for the lightbulb to shed some light on the issue and maybe even automatically fix it for you.

IntelliSense demo gif

OK, How Do I Get VSCode?

Interested? Curious?

Ready to join the cult?

  1. Click here to download.
  2. Run the installer
  3. Enjoy!
  4. Profit???

Popular extensions

  • are many extensions available for VSCode. Some good ones include:

Common Keyboard Shortcuts

  • Formats your code (Shift+Alt+F)
  • Open New Window (Ctrl+Shift+N)
  • Open Project Folder (Ctrl+K,Ctrl+O)
  • Go to File (Ctrl+P)
  • Expand Selection (Shift+Alt+Right)
  • Search multiple File (Ctrl+Shift+F)
  • Go to Definition (F12)
  • Rename (F2)
  • Multiple Cursor(Alt+Mouse)
  • See more here

Miss your old editor keymap? Try out of one of these extensions: Vim, Atom, Sublime

What!? you forgot all the shortcuts already?

No problem, VSCode has your back. Hit (F1) or (ctrl+shift+p) and type the command you want to perform

IntelliSense demo

End of Visual Studio Code Section

Well done! This is the end of the milestone!

================================================ FILE: apps/codelab/src/app/codelabs/extra/visual-studio-code/visual-studio-code.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'codelab-slides-visual-studio-code', templateUrl: './visual-studio-code.component.html', styleUrls: ['./visual-studio-code.component.css'] }) export class VisualStudioCodeComponent implements OnInit { constructor() {} ngOnInit() {} } ================================================ FILE: apps/codelab/src/app/codelabs/extra/visual-studio-code/visual-studio-code.module.ts ================================================ import { RouterModule } from '@angular/router'; import { FeedbackModule } from '@codelab/feedback'; import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { VisualStudioCodeComponent } from './visual-studio-code.component'; import { CodelabComponentsModule } from '../../../components/codelab-components.module'; const routes = RouterModule.forChild( SlidesRoutes.get(VisualStudioCodeComponent) ); @NgModule({ imports: [ SlidesModule, CodelabComponentsModule, routes, FeedbackModule, CommonModule ], declarations: [VisualStudioCodeComponent], exports: [VisualStudioCodeComponent] }) export class VisualStudioCodeModule {} ================================================ FILE: apps/codelab/src/app/common.ts ================================================ import { InjectionToken } from '@angular/core'; import { Route } from '@angular/router'; export type MenuRoutes = MenuRoute[]; interface MenuRoute extends Route { name?: string; description?: string; page?: string; prod?: boolean; translationIds?: string[]; children?: MenuRoutes; } export const MENU_ROUTES = new InjectionToken('menuRoutes'); ================================================ FILE: apps/codelab/src/app/components/angular-routes/angular-routes.component.html ================================================

Lessons

  1. {{ route.name }}

    {{ route.description }}
================================================ FILE: apps/codelab/src/app/components/angular-routes/angular-routes.component.scss ================================================ :host { display: flex; flex-direction: column; align-items: center; margin: 2rem; font-family: 'Helvetica Neue', sans-serif; font-size: 30px; ol { margin-block-start: 0; h2 { font-size: 1.2em; margin-block-end: 0.5em; } } } @media screen and (max-width: 500px) { :host { font-size: 20px; } } ================================================ FILE: apps/codelab/src/app/components/angular-routes/angular-routes.component.ts ================================================ import { ChangeDetectionStrategy, Component, Inject } from '@angular/core'; import { MENU_ROUTES } from '../../common'; @Component({ selector: 'codelab-angular-routes', templateUrl: 'angular-routes.component.html', styleUrls: ['angular-routes.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) export class AngularRoutesComponent { constructor(@Inject(MENU_ROUTES) readonly menuRoutes) {} } ================================================ FILE: apps/codelab/src/app/components/angular-routes/angular-routes.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CommonModule } from '@angular/common'; import { AngularRoutesComponent } from './angular-routes.component'; @NgModule({ imports: [CommonModule, RouterModule], declarations: [AngularRoutesComponent], exports: [AngularRoutesComponent] }) export class AngularRoutesModule {} ================================================ FILE: apps/codelab/src/app/components/angular-test-runner/angular-test-runner.component.css ================================================ .runner { display: none; } ================================================ FILE: apps/codelab/src/app/components/angular-test-runner/angular-test-runner.component.html ================================================
================================================ FILE: apps/codelab/src/app/components/angular-test-runner/angular-test-runner.component.ts ================================================ import { AfterViewInit, ChangeDetectorRef, Component, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core'; import { createSystemJsSandbox } from '@codelab/code-demos/src/lib/shared/sandbox'; import { ScriptLoaderService } from '@codelab/code-demos/src/lib/shared/script-loader.service'; import babel_traverse from '@babel/traverse'; import * as babylon from 'babylon'; import * as babel_types from 'babel-types'; import { BehaviorSubject } from 'rxjs/internal/BehaviorSubject'; import { Subscription } from 'rxjs/internal/Subscription'; import { getTypeScript } from '@codelab/utils/src/lib/loaders/loaders'; import { TestRunResult } from '@codelab/utils/src/lib/test-results/common'; import { handleTestMessage } from './tests'; const ts = getTypeScript(); // TODO(kirjs): This is a duplicate export function addMetaInformation(sandbox, files: { [key: string]: string }) { sandbox.evalJs(`System.registry.delete(System.normalizeSync('./code'));`); (sandbox.iframe.contentWindow as any).System.register('code', [], function( exports ) { return { setters: [], execute: function() { exports('ts', ts); exports('babylon', babylon); exports('babel_traverse', babel_traverse); exports('babel_types', babel_types); Object.entries(files) .filter(([moduleName]) => moduleName.match(/\.ts$/)) .forEach(([path, code]) => { exports(path.replace(/[\/.-]/gi, '_'), code); exports( path.replace(/[\/.-]/gi, '_') + '_AST', ts.createSourceFile(path, code, ts.ScriptTarget.ES5) ); }); Object.entries(files) .filter(([moduleName]) => moduleName.match(/\.html/)) .forEach(([path, code]) => { const templatePath = path.replace(/[\/.-]/gi, '_'); exports(templatePath, code); }); } }; }); } @Component({ selector: 'codelab-simple-angular-test-runner', templateUrl: './angular-test-runner.component.html', styleUrls: ['./angular-test-runner.component.css'] }) export class SimpleAngularTestRunnerComponent implements OnInit, OnChanges, AfterViewInit, OnDestroy { handleMessageBound: any; @Output() solved: EventEmitter = new EventEmitter(); @Output() public selectFile: EventEmitter = new EventEmitter< string >(); @Input() translations: { [key: string]: string } = {}; @Input() code: any; @Input() bootstrap: string; @ViewChild('runner', { static: false }) runnerElement: ElementRef; changedFilesSubject = new BehaviorSubject>({}); tests: any; private subscription: Subscription; result: TestRunResult = { tests: [] }; constructor( private scriptLoaderService: ScriptLoaderService, private cd: ChangeDetectorRef ) {} ngOnInit() { this.handleMessageBound = message => { this.tests = handleTestMessage(message, this.tests || []); this.result = { tests: this.tests.map(test => { const name = this.translations[test.title.replace('@@', '')] || test.title; return { ...test, name, error: test.result }; }) }; if (message.data.type === 'testEnd') { this.solved.emit( this.tests.length > 0 && this.tests.every(test => test.pass) ); } this.cd.markForCheck(); }; window.addEventListener('message', this.handleMessageBound, false); } ngOnChanges(changes: SimpleChanges) { if (changes.code) { this.changedFilesSubject.next(changes.code.currentValue); } } async ngAfterViewInit() { const sandbox = await createSystemJsSandbox( this.runnerElement.nativeElement, { id: 'testing', url: '/assets/runner' } ); sandbox.setHtml( this.code['index.html'] || '
' ); sandbox.evalJs(this.scriptLoaderService.getScript('chai')); sandbox.evalJs(this.scriptLoaderService.getScript('mocha')); sandbox.evalJs(this.scriptLoaderService.getScript('test-bootstrap')); sandbox.evalJs(this.scriptLoaderService.getScript('shim')); sandbox.evalJs(this.scriptLoaderService.getScript('zone')); sandbox.evalJs(this.scriptLoaderService.getScript('system-config')); sandbox.evalJs(this.scriptLoaderService.getScript('ng-bundle')); this.subscription = this.changedFilesSubject.subscribe(files => { const hasErrors = Object.entries(files) .filter(([path]) => path.match(/\.js$/)) .map(([path, code]) => { try { sandbox.evalJs( `System.registry.delete(System.normalizeSync('./${path.replace( '.js', '' )}'));` ); addMetaInformation(sandbox, this.code); sandbox.evalJs(code); } catch (e) { console.groupCollapsed(e.message); console.log(e); console.groupEnd(); return true; } return false; }) .some(a => a); if (!hasErrors) { sandbox.evalJs(`System.import('${this.bootstrap}')`); } }); } ngOnDestroy() { if (this.subscription) { this.subscription.unsubscribe(); this.subscription = null; } } } ================================================ FILE: apps/codelab/src/app/components/angular-test-runner/tests.ts ================================================ export function handleTestMessage(message, tests) { if (!message.data || !message.data.type) { return tests; } if (message.data.type === 'testList') { return message.data.tests.map(test => ({ title: test })); } if (message.data.type === 'testEnd') { return tests; } if (message.data.type === 'testResult') { return tests.map(test => { if (test.title === message.data.test.title) { test.pass = message.data.pass; test.result = message.data.result; } return test; }); } return tests; } ================================================ FILE: apps/codelab/src/app/components/babel-test-runner/babel-helpers.ts ================================================ import * as T from 'babel-types'; import * as babylon from 'babylon'; import babel_traverse from '@babel/traverse'; import { getTypeScript } from '@codelab/utils/src/lib/loaders/loaders'; import * as TsTypes from 'typescript'; const ts = getTypeScript(); function matchesValue(actual, expected) { if (!actual) { return false; } if (expected instanceof RegExp) { return expected.test(actual); } return actual.trim() === expected.trim(); } export const expectClass = name => ({ node, parent }) => T.isIdentifier(node, { name }) && T.isClassDeclaration(parent, { superClass: null }); export const expectExportedClass = name => ({ node, parent, parentPath }) => expectClass(name)({ node, parent }) && T.isExportNamedDeclaration(parentPath.parent); export const expectDecorator = name => ({ node }) => T.isDecorator(node) && node.expression.callee.name === name; export const expectDecoratorPropertyStringValue = ( decoratorName, keyName, value ) => path => { function matchesTemplateLiteral() { return ( T.isTemplateLiteral(path.node) && matchesValue(path.node.quasis[0].value.raw, value) ); } function matchesStringLiteral() { return T.isStringLiteral(path.node) && matchesValue(path.node.value, value); } return ( (matchesTemplateLiteral() || matchesStringLiteral()) && T.isObjectProperty(path.parent) && path.parent.key.name === keyName && path.findParent(T.isDecorator).node.expression.callee.name === decoratorName ); }; export function babelTestSuite(filePath, tests) { return function test(files) { const results = tests.map(({ title }) => ({ title, pass: false })); const code = files[filePath]; const ast = babylon.parse(code, { sourceType: 'module', plugins: ['decorators'] }); babel_traverse(ast, { enter(path) { tests.forEach((testConfig, index) => { try { results[index].pass = results[index].pass || testConfig.condition(path); } catch (e) { console.log(e); } }); } }); return results; }; } export type Predicate = (node: TsTypes.Node) => boolean; export class MiniTsQuery { constructor(private readonly ast: TsTypes.Node) {} some(predicate: Predicate) { let result = false; this.traverse(this.ast, node => { if (predicate(node)) { result = true; } }); return result; } findOne(predicate) { let result; this.traverse(this.ast, node => { if (!result && predicate(node)) { result = node; } }); return result; } hasOne(predicate) { return !!this.findOne(predicate); } hasIdentifier(identifier: string) { return !!this.getIdentifier(identifier); } getIdentifier(identifier: string) { return this.findOne( node => ts.isIdentifier(node) && node.text === identifier ); } hasConstructorParam(identifier: string, type: string) { const node = this.findOne( node => ts.isIdentifier(node) && node.text === identifier ); return node ? node.parent.type.typeName.text === type : undefined; } hasVariableDeclaration(identifier: string) { const node = this.getIdentifier(identifier); return node && ts.isVariableDeclaration(node.parent) ? node : undefined; } hasDecorator(type: string) { return !!this.getDecorator(type); } hasDecoratorValue(decoratorType: string, property: string, type: string) { const decorator = this.getDecorator(decoratorType); if ( decorator.expression && ts.isCallExpression(decorator.expression) && decorator.expression.arguments && decorator.expression.arguments[0] && ts.isObjectLiteralExpression(decorator.expression.arguments[0]) ) { return (decorator.expression.arguments[0] as any).properties .filter(prop => prop && prop.name && prop.name.text === property) .some(arrayValue => arrayValue.initializer.elements.some(a => a.text === type) ); } return false; } hasProvider(type: string) { return this.hasDecoratorValue('NgModule', 'providers', 'VideoService'); } getDecorator(type: string): TsTypes.Decorator { return this.findOne(node => { return ( ts.isDecorator(node) && node.expression && ts.isCallExpression(node.expression) && node.expression.expression && ts.isIdentifier(node.expression.expression) && node.expression.expression.text === type ); }); } private traverse(node, callback) { callback(node); ts.forEachChild(node, node => this.traverse(node, callback)); } } export function tsAstTestSuite(tests) { return function test(files) { const results = tests.map(({ title }) => ({ title, pass: false })); const astCache = {}; function getAst(filePath) { if (!astCache[filePath]) { astCache[filePath] = new MiniTsQuery( ts.createSourceFile( filePath, files[filePath], ts.ScriptTarget.ES2015, /*setParentNodes */ true ) ); } return astCache[filePath]; } tests.forEach((testConfig, index) => { try { if (!testConfig.file) { throw new Error('test must specify a file'); } results[index].pass = results[index].pass || testConfig.condition(getAst(testConfig.file)); } catch (e) { console.log(e); } }); return results; }; } ================================================ FILE: apps/codelab/src/app/components/babel-test-runner/babel-test-runner.component.css ================================================ .runner { display: none; } ================================================ FILE: apps/codelab/src/app/components/babel-test-runner/babel-test-runner.component.html ================================================ ================================================ FILE: apps/codelab/src/app/components/babel-test-runner/babel-test-runner.component.ts ================================================ import { AfterViewInit, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { TestInfo } from '../../shared/interfaces/test-info'; import { FileConfig } from '../../shared/interfaces/file-config'; import { TestRunResult } from '@codelab/utils/src/lib/test-results/common'; declare const require; @Component({ selector: 'codelab-babel-test-runner', templateUrl: './babel-test-runner.component.html', styleUrls: ['./babel-test-runner.component.css'] }) export class BabelTestRunnerComponent implements AfterViewInit, OnChanges { @Input() bootstrap: string; tests: Array = []; @Input() translations: { [key: string]: string } = {}; @Input() code: any; @Output() solved: EventEmitter = new EventEmitter(); constructor() {} result: TestRunResult = { tests: [] }; ngOnChanges(changes: SimpleChanges) { if (changes.code) { this.run(this.code); } } run(files: any) { const test = files[this.bootstrap + '.ts.execute']; try { this.tests = test(files); this.result = { tests: this.tests.map(test => { const name = this.translations[test.title.replace('@@', '')] || test.title; return { ...test, name, error: test.result, pass: !!test.pass }; }) }; const isSolved = this.result.tests.every(a => a.pass); this.solved.emit(isSolved); } catch (e) { this.tests.find(t => !t.pass).result = '[Parsing error]' + e; } } selectFile(file: FileConfig) { // this.parent.currentFile = file; } ngAfterViewInit(): void { // this.parent.files$.subscribe(files => requestAnimationFrame(() => this.run(files))); } } ================================================ FILE: apps/codelab/src/app/components/breadcrumb/breadcrumb.component.css ================================================ a { text-decoration: none; font-weight: 300; } span { cursor: pointer; } span.arrow { font-size: 0.5em; vertical-align: middle; } ================================================ FILE: apps/codelab/src/app/components/breadcrumb/breadcrumb.component.html ================================================

Angular Codelab {{ separator }} {{ active }} {{ separator }}

{{ milestone.name }} ================================================ FILE: apps/codelab/src/app/components/breadcrumb/breadcrumb.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BreadcrumbComponent } from './breadcrumb.component'; import { ActivatedRoute } from '@angular/router'; import { MENU_ROUTES } from '../../common'; import { CodelabComponentsModule } from '../codelab-components.module'; import { RouterTestingModule } from '@angular/router/testing'; describe('BreadcrumbComponent', () => { let component: BreadcrumbComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [CodelabComponentsModule, RouterTestingModule], providers: [ { provide: ActivatedRoute, useValue: { pathFromRoot: [{ routeConfig: { name: 'lol' } }] } }, { provide: MENU_ROUTES, useValue: [] } ] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BreadcrumbComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/codelab/src/app/components/breadcrumb/breadcrumb.component.ts ================================================ import { Component, Inject } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { MENU_ROUTES } from '../../common'; @Component({ selector: 'codelab-breadcrumb', templateUrl: './breadcrumb.component.html', styleUrls: ['./breadcrumb.component.css'] }) export class BreadcrumbComponent { active: string; readonly separator = '/'; constructor( private activatedRoute: ActivatedRoute, @Inject(MENU_ROUTES) readonly menuRoutes ) { this.active = this.activatedRoute.pathFromRoot.find( route => route.routeConfig && route.routeConfig['name'] ).routeConfig['name']; } } ================================================ FILE: apps/codelab/src/app/components/buttons-nav-bar/buttons-nav-bar.component.html ================================================
================================================ FILE: apps/codelab/src/app/components/buttons-nav-bar/buttons-nav-bar.component.scss ================================================ :host ::ng-deep { .btn-bar { z-index: 110; } button:focus { outline: 0; } .menu-bar-btn { width: 45px; height: 45px; border-radius: 50%; background: no-repeat center white; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); margin-left: 10px; cursor: pointer; border: none; padding: 0; } } ::ng-deep { .modal-title { margin: 0 0 10px 0; } .buttons-nav-bar-modal-content-wrapper { padding: 8px; font-weight: 300; font-family: 'Helvetica Neue', sans-serif; } @media screen and (max-width: 350px) { .cdk-overlay-pane { width: 100%; } .buttons-nav-bar-modal-content-wrapper { width: 100%; } } } ================================================ FILE: apps/codelab/src/app/components/buttons-nav-bar/buttons-nav-bar.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'codelab-buttons-nav-bar', templateUrl: './buttons-nav-bar.component.html', styleUrls: ['./buttons-nav-bar.component.scss'] }) export class ButtonsNavBarComponent {} ================================================ FILE: apps/codelab/src/app/components/buttons-nav-bar/buttons-nav-bar.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ButtonsNavBarComponent } from './buttons-nav-bar.component'; import { FeedbackModule } from '@codelab/feedback'; import { FirebaseLoginModule } from '@codelab/firebase-login'; import { MenuGithubWidgetModule } from './menu-github-widget/menu-github-widget.module'; import { MenuShortcutWidgetModule } from './menu-shortcut-widget/menu-shortcut-widget.module'; import { MenuFullscreenWidgetComponent } from './menu-fullscreen-widget/menu-fullscreen-widget.component'; import { DirectivesModule } from '../../directives/directives.module'; @NgModule({ imports: [ CommonModule, FeedbackModule, FirebaseLoginModule, MenuGithubWidgetModule, MenuShortcutWidgetModule, DirectivesModule ], declarations: [ButtonsNavBarComponent, MenuFullscreenWidgetComponent], exports: [ ButtonsNavBarComponent, FeedbackModule, FirebaseLoginModule, MenuGithubWidgetModule, MenuShortcutWidgetModule ] }) export class ButtonsNavBarModule {} ================================================ FILE: apps/codelab/src/app/components/buttons-nav-bar/menu-fullscreen-widget/menu-fullscreen-widget.component.html ================================================ ================================================ FILE: apps/codelab/src/app/components/buttons-nav-bar/menu-fullscreen-widget/menu-fullscreen-widget.component.scss ================================================ .menu-bar-btn { width: 45px; height: 45px; border-radius: 50%; background: no-repeat center #ecf0f1; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); margin-left: 10px; cursor: pointer; border: none; padding: 0; img { width: 60%; } } ================================================ FILE: apps/codelab/src/app/components/buttons-nav-bar/menu-fullscreen-widget/menu-fullscreen-widget.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { MenuFullscreenWidgetComponent } from './menu-fullscreen-widget.component'; describe('MenuFullscreenWidgetComponent', () => { let component: MenuFullscreenWidgetComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [MenuFullscreenWidgetComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(MenuFullscreenWidgetComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/codelab/src/app/components/buttons-nav-bar/menu-fullscreen-widget/menu-fullscreen-widget.component.ts ================================================ import { Component } from '@angular/core'; import { FullScreenModeService } from '@ng360/slides'; @Component({ selector: 'codelab-menu-fullscreen-widget', templateUrl: './menu-fullscreen-widget.component.html', styleUrls: ['./menu-fullscreen-widget.component.scss'] }) export class MenuFullscreenWidgetComponent { constructor(private fullScreenService: FullScreenModeService) {} openFullScreen() { this.fullScreenService.toggleFullScreen(); } } ================================================ FILE: apps/codelab/src/app/components/buttons-nav-bar/menu-github-widget/menu-github-widget.component.css ================================================ .menu-bar-btn.github-widget { background-color: white; } img { width: 100%; } .octocat { float: left; width: 224px; margin: 0 auto; height: 224px; background: url(images/octocat.png) no-repeat; margin-right: 16px; } .content { float: right; width: 250px; } @media screen and (max-width: 350px) { .octocat { float: none; margin: 5px auto; display: block; } .content { margin: 0 auto; float: none; text-align: center; } } h3 { font-weight: 300; } ================================================ FILE: apps/codelab/src/app/components/buttons-nav-bar/menu-github-widget/menu-github-widget.component.html ================================================

It's 100% open-source and is available on Github

Please ⭐ the repo if you find it useful.

================================================ FILE: apps/codelab/src/app/components/buttons-nav-bar/menu-github-widget/menu-github-widget.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'codelab-menu-github-widget', templateUrl: './menu-github-widget.component.html', styleUrls: ['./menu-github-widget.component.css'] }) export class MenuGithubWidgetComponent {} ================================================ FILE: apps/codelab/src/app/components/buttons-nav-bar/menu-github-widget/menu-github-widget.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatMenuModule } from '@angular/material/menu'; import { MenuGithubWidgetComponent } from './menu-github-widget.component'; @NgModule({ imports: [CommonModule, MatMenuModule], declarations: [MenuGithubWidgetComponent], exports: [MenuGithubWidgetComponent] }) export class MenuGithubWidgetModule {} ================================================ FILE: apps/codelab/src/app/components/buttons-nav-bar/menu-shortcut-widget/menu-shortcut-widget.component.css ================================================ .menu-bar-btn.index-menu { background-color: #ff594b; } ul, li { margin: 0; padding: 0; list-style-type: none; } a { text-decoration: none; cursor: pointer; display: block; padding: 6px 10px; } a:hover { background-color: #982a2a; color: white; } ================================================ FILE: apps/codelab/src/app/components/buttons-nav-bar/menu-shortcut-widget/menu-shortcut-widget.component.html ================================================ ================================================ FILE: apps/codelab/src/app/components/buttons-nav-bar/menu-shortcut-widget/menu-shortcut-widget.component.ts ================================================ import { Component, Inject } from '@angular/core'; import { MENU_ROUTES } from '../../../common'; @Component({ selector: 'codelab-menu-shortcut-widget', templateUrl: './menu-shortcut-widget.component.html', styleUrls: ['./menu-shortcut-widget.component.css'] }) export class MenuShortcutWidgetComponent { constructor(@Inject(MENU_ROUTES) readonly menuRoutes) {} } ================================================ FILE: apps/codelab/src/app/components/buttons-nav-bar/menu-shortcut-widget/menu-shortcut-widget.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { MatMenuModule } from '@angular/material/menu'; import { MenuShortcutWidgetComponent } from './menu-shortcut-widget.component'; @NgModule({ imports: [CommonModule, RouterModule, MatMenuModule], declarations: [MenuShortcutWidgetComponent], exports: [MenuShortcutWidgetComponent] }) export class MenuShortcutWidgetModule {} ================================================ FILE: apps/codelab/src/app/components/codelab-components.module.ts ================================================ import { NgModule } from '@angular/core'; import { CodelabExerciseComponent } from './exercise/exercise.component'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatMenuModule } from '@angular/material/menu'; import { MatSelectModule } from '@angular/material/select'; import { RouterModule } from '@angular/router'; import { DirectivesModule } from '../directives/directives.module'; import { TitleSlideComponent } from './slides/title-slide/title-slide.component'; import { CodelabClosingSlideComponent } from './slides/closing-slide/codelab-closing-slide.component'; import { CodelabExercisePreviewComponent } from './exercise-preview/exercise-preview.component'; import { CodelabExercisePlaygroundComponent } from './exercise-playground/codelab-exercise-playground.component'; import { CodelabProgressBarComponent } from './codelab-progress-bar/codelab-progress-bar.component'; import { BabelTestRunnerComponent } from './babel-test-runner/babel-test-runner.component'; import { CodelabRippleAnimationComponent } from './slides/title-slide/ripple-animation/codelab-ripple-animation.component'; import { SimpleAngularTestRunnerComponent } from './angular-test-runner/angular-test-runner.component'; import { CodeDemoModule } from '@codelab/code-demos'; import { CodelabPreviewComponent } from './slides-preview/codelab-preview.component'; import { BreadcrumbComponent } from './breadcrumb/breadcrumb.component'; import { TestResultsModule } from '@codelab/utils/src/lib/test-results/test-results.module'; @NgModule({ imports: [ CommonModule, FormsModule, RouterModule, CodeDemoModule, MatButtonModule, MatMenuModule, MatSelectModule, TestResultsModule, DirectivesModule ], declarations: [ SimpleAngularTestRunnerComponent, TitleSlideComponent, BabelTestRunnerComponent, BreadcrumbComponent, CodelabExerciseComponent, CodelabPreviewComponent, CodelabClosingSlideComponent, CodelabExercisePreviewComponent, CodelabExercisePlaygroundComponent, CodelabProgressBarComponent, CodelabRippleAnimationComponent ], exports: [ SimpleAngularTestRunnerComponent, TitleSlideComponent, BabelTestRunnerComponent, BreadcrumbComponent, CodelabExerciseComponent, CodelabPreviewComponent, CodelabClosingSlideComponent, CodelabExercisePreviewComponent, CodelabExercisePlaygroundComponent, CodelabProgressBarComponent, CodelabRippleAnimationComponent ] }) export class CodelabComponentsModule {} ================================================ FILE: apps/codelab/src/app/components/codelab-progress-bar/codelab-progress-bar.component.css ================================================ .progress-bar { position: absolute; top: 0; left: 0; right: 0; z-index: 1; } .points-container { display: flex; height: 25px; } .points-container:hover .slide-block { transition-duration: 0.5s; height: 15px; } .slide-block { flex-grow: 1; height: 3px; transition-duration: 0.5s; background: #abb7b7; } .slide-block:hover { background: #dadfe1; } .exercise-block { background: #d35400; } .exercise-block:hover { background: #f9690e; } .completed-slide { background: #f89406; } .completed-slide:hover { background: #e9d460; } .completed-slide.exercise-block { background: #e67e22; } ================================================ FILE: apps/codelab/src/app/components/codelab-progress-bar/codelab-progress-bar.component.html ================================================
================================================ FILE: apps/codelab/src/app/components/codelab-progress-bar/codelab-progress-bar.component.ts ================================================ import { AfterViewInit, Component } from '@angular/core'; import { SlidesDeckComponent } from '@ng360/slides'; @Component({ selector: 'codelab-progress-bar', templateUrl: './codelab-progress-bar.component.html', styleUrls: ['./codelab-progress-bar.component.css'] }) export class CodelabProgressBarComponent implements AfterViewInit { slides = []; activeSlideIndex = 0; tempSlideId = 0; constructor(public deck: SlidesDeckComponent) {} ngAfterViewInit() { // Change detection complains if updating it right away. requestAnimationFrame(() => { this.slides = this.deck.slides; this.activeSlideIndex = this.deck.activeSlideIndex; }); this.deck.slideChange.subscribe(index => { this.activeSlideIndex = index; }); } previewSlide(index) { this.tempSlideId = this.activeSlideIndex; this.deck.goToSlide(index); } goToSlide(index) { this.deck.goToSlide(index); this.tempSlideId = this.activeSlideIndex; } } ================================================ FILE: apps/codelab/src/app/components/css/codelab-styles.scss ================================================ code-demo-file-path { display: block; margin: 1px 0; } ================================================ FILE: apps/codelab/src/app/components/exercise/exercise.component.css ================================================ .quarter { width: 50vw; height: 50vh; } .row { display: flex; } .tests { padding: 0.5vw; box-sizing: border-box; overflow-y: auto; } .solved-message { margin-top: 16px; } .solved-message img { display: block; height: 280px; margin-top: 16px; } ================================================ FILE: apps/codelab/src/app/components/exercise/exercise.component.html ================================================
Happy dogs celebrating your success
================================================ FILE: apps/codelab/src/app/components/exercise/exercise.component.ts ================================================ import { ChangeDetectorRef, Component, Input } from '@angular/core'; import { convertExerciseToMap } from '../../../../../../ng2ts/ng2ts'; import { CodeDemoComponent } from '@codelab/code-demos/src/lib/code-demo/code-demo.component'; function filterByFileType(type: string, files: Record) { return Object.entries(files).reduce((changedFiles, [path, code]) => { if (path.match(new RegExp(`\\\.${type}$`))) { changedFiles[path] = code; } return changedFiles; }, {}); } export function extractSolutions(files: any[]) { return files.reduce((result, file) => { if (file.solution) { result[file.path] = file.solution || file.template; } return result; }, {}); } export function getChanges(current, previous) { return Object.keys(current).reduce((changedFiles, path) => { if (current[path] !== previous[path]) { changedFiles[path] = current[path]; } return changedFiles; }, {}); } @Component({ selector: 'codelab-exercise', templateUrl: 'exercise.component.html', styleUrls: ['./exercise.component.css'] }) export class CodelabExerciseComponent extends CodeDemoComponent { isSolved = false; constructor(private readonly cdr: ChangeDetectorRef) { super(); } showDogs() { this.cdr.markForCheck(); } @Input() set exercise(exercise) { const map = convertExerciseToMap(exercise); if (!this.highlights) { this.highlights = map.highlights; } this.bootstrap = map.bootstrap; this.bootstrapTest = map.bootstrapTest; if (!this.files) { this.files = [exercise.files[this.openFileIndex].path]; } this.filesConfig = exercise; this.solutions = extractSolutions(this.filesConfig.files); this.code = map.code; this.update(map.code); } retrieveFile(file, code) { const f = this.filesConfig.files.find(f => f.path === file); return (f.before || '') + code + (f.after || ''); } } ================================================ FILE: apps/codelab/src/app/components/exercise-playground/codelab-exercise-playground.component.css ================================================ :host { display: flex; flex-direction: column; } ================================================ FILE: apps/codelab/src/app/components/exercise-playground/codelab-exercise-playground.component.html ================================================
{{ path }}
================================================ FILE: apps/codelab/src/app/components/exercise-playground/codelab-exercise-playground.component.ts ================================================ import { Component, Input } from '@angular/core'; import { CodelabExerciseComponent } from '../exercise/exercise.component'; import { PreviewWindowType } from '@codelab/browser/src/lib/preview-window/preview-window.component'; @Component({ selector: 'codelab-exercise-playground', templateUrl: 'codelab-exercise-playground.component.html', styleUrls: ['codelab-exercise-playground.component.css'] }) export class CodelabExercisePlaygroundComponent extends CodelabExerciseComponent { @Input() allowSwitchingFiles = false; @Input() path = ''; @Input() ui: PreviewWindowType = 'browser'; } ================================================ FILE: apps/codelab/src/app/components/exercise-preview/exercise-preview.component.html ================================================ ================================================ FILE: apps/codelab/src/app/components/exercise-preview/exercise-preview.component.ts ================================================ import { Component, Input } from '@angular/core'; import { CodelabExerciseComponent, extractSolutions } from '../exercise/exercise.component'; import { convertExerciseToMap } from '../../../../../../ng2ts/ng2ts'; @Component({ selector: 'codelab-exercise-preview', templateUrl: 'exercise-preview.component.html' // styleUrls: ['../exercise/code-demo-code-demo.component.css'], }) export class CodelabExercisePreviewComponent extends CodelabExerciseComponent { @Input() set exercise(exercise) { const map = convertExerciseToMap(exercise); this.filesConfig = exercise; this.bootstrap = map.bootstrap; this.code = extractSolutions(exercise.files); this.update(this.code); } } ================================================ FILE: apps/codelab/src/app/components/external-link-directive/external-link-directive.directive.spec.ts ================================================ import { ExternalLinkDirectiveDirective } from './external-link-directive.directive'; describe('ExternalLinkDirectiveDirective', () => { it('should create an instance', () => { const directive = new ExternalLinkDirectiveDirective({ nativeElement: document.createElement('a') }); expect(directive).toBeTruthy(); }); }); ================================================ FILE: apps/codelab/src/app/components/external-link-directive/external-link-directive.directive.ts ================================================ import { Directive, Input, ElementRef } from '@angular/core'; @Directive({ // tslint:disable-next-line selector: '[href]' }) // TODO(meinou): Remove the second postfix export class ExternalLinkDirectiveDirective { constructor({ nativeElement }: ElementRef) { nativeElement.target = '_blank'; } } ================================================ FILE: apps/codelab/src/app/components/index/index.component.html ================================================
Create your first Angular app
Templates
Dependency-Injection
Component-Tree
Custom-Events
Angular is written in TypeScript, a superset of JavaScript. Learn TypeScript.
Learn how to create and bootstrap your first Angular application
Learn how to use Angular templates
Learn how to provide dependencies to your code instead of hard-coding them
Learn how to structure your app with reusable components
Learn to bind to events.

Angular Codelab

Welcome to the interactive Angular Codelab. Here you can learn the basics of Angular!

================================================ FILE: apps/codelab/src/app/components/index/index.component.scss ================================================ .intro li a:hover { text-decoration: underline; } .intro li a { text-decoration: none; color: #888; } .intro ul { margin: 0; padding: 0; } .intro li { margin: 0; padding: 0; padding-left: 21px; font-size: 1.3vw; margin-bottom: 16px; } .intro li h2 { font-size: 2vw; } .feedback-shift { position: fixed; right: 107px; bottom: 1vh; } img .angular { width: 30vw; } .learn-box:hover { background: #eee; } .title { color: #444; } .hint { color: #666; font-size: 3vw; } .learn-box.typescript { opacity: 0.8; } .learn-box { display: flex; width: 100%; font-size: 7vw; justify-content: space-between; background: #fafafa; padding: 2vw; margin-bottom: 2vw; cursor: pointer; border: 1px #999 solid; border-radius: 10px; } .learn-box img { width: 10vw; height: 10vw; font-size: 10px; } ::ng-deep .slide { display: block; } .play { margin: 4vw 0; } .contents { border-bottom: 1px #999 dashed; display: inline-block; cursor: pointer; min-height: 20px; } ::ng-deep { .btn-bar { z-index: 110; } .menu-bar-btn { width: 45px; height: 45px; border-radius: 50%; background: no-repeat center #3498db; box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); margin-left: 10px; cursor: pointer; border: none; padding: 0; } } ================================================ FILE: apps/codelab/src/app/components/index/index.component.ts ================================================ import { Component, ViewChild } from '@angular/core'; @Component({ selector: 'codelab-slides-index', templateUrl: './index.component.html', styleUrls: ['./index.component.scss'] }) export class IndexComponent { @ViewChild('translations', { static: false }) translations; showContents: boolean; } ================================================ FILE: apps/codelab/src/app/components/index/index.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CommonModule } from '@angular/common'; import { MatCardModule } from '@angular/material/card'; import { SlidesModule } from '@ng360/slides'; import { IndexComponent } from './index.component'; import { ButtonsNavBarModule } from '../buttons-nav-bar/buttons-nav-bar.module'; import { CodelabComponentsModule } from '../codelab-components.module'; import { SyncModule } from '../../sync/sync.module'; import { AngularRoutesModule } from '../angular-routes/angular-routes.module'; @NgModule({ imports: [ CommonModule, RouterModule, ButtonsNavBarModule, CodelabComponentsModule, SlidesModule, MatCardModule, SyncModule, AngularRoutesModule ], declarations: [IndexComponent], exports: [IndexComponent] }) export class IndexModule {} ================================================ FILE: apps/codelab/src/app/components/login/login.component.css ================================================ ================================================ FILE: apps/codelab/src/app/components/login/login.component.html ================================================
user: {{ user.displayName || user.email }}
uid: {{ loginService.uid$ | async }}
================================================ FILE: apps/codelab/src/app/components/login/login.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { getMockAngularFireProviders } from '@codelab/utils/src/lib/testing/mocks/angular-fire'; import { LoginComponent } from './login.component'; import { LoginModule } from './login.module'; describe('LoginComponent', () => { let component: LoginComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [LoginModule], providers: [...getMockAngularFireProviders()] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(LoginComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/codelab/src/app/components/login/login.component.ts ================================================ import { Component } from '@angular/core'; import { AngularFireAuth } from '@angular/fire/auth'; import { LoginService } from '@codelab/firebase-login'; import { auth } from 'firebase/app'; @Component({ selector: 'codelab-login', templateUrl: './login.component.html', styleUrls: ['./login.component.css'] }) export class LoginComponent { constructor( private auth: AngularFireAuth, readonly loginService: LoginService ) {} login() { this.auth.auth.signInWithPopup(new auth.GoogleAuthProvider()); } } ================================================ FILE: apps/codelab/src/app/components/login/login.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { RouterModule } from '@angular/router'; import { LoginComponent } from './login.component'; @NgModule({ imports: [CommonModule, MatCardModule, RouterModule, MatButtonModule], declarations: [LoginComponent] }) export class LoginModule {} ================================================ FILE: apps/codelab/src/app/components/not-found/not-found.component.html ================================================

404

Oops! You're lost

The page you are looking for was not found.

================================================ FILE: apps/codelab/src/app/components/not-found/not-found.component.scss ================================================ :host { display: flex; flex-direction: column; justify-content: center; align-items: center; height: 100%; font-family: 'Helvetica Neue', sans-serif; .message { display: flex; justify-content: center; align-items: center; .error-code { font-size: 70px; margin-right: 20px; } .error-message { .header { font-size: 20px; font-weight: 600; } .subheader { font-size: 14px; } } } nav { max-width: 100%; width: 400px; ul { display: flex; justify-content: space-around; width: 100%; padding-inline-start: 0; li { list-style-type: none; } } } } @media screen and (max-width: 500px) { .container .message { flex-direction: column; text-align: center; } } ================================================ FILE: apps/codelab/src/app/components/not-found/not-found.component.ts ================================================ import { Component, ChangeDetectionStrategy } from '@angular/core'; @Component({ selector: 'codelab-not-found', templateUrl: './not-found.component.html', styleUrls: ['./not-found.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) export class NotFoundComponent { public routes = [ { path: '', name: 'Home' }, { path: '/angular/typescript', name: 'Typescript' }, { path: '/angular', name: 'Angular' } ]; } ================================================ FILE: apps/codelab/src/app/components/not-found/not-found.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CommonModule } from '@angular/common'; import { ButtonsNavBarModule } from '../buttons-nav-bar/buttons-nav-bar.module'; import { NotFoundComponent } from './not-found.component'; @NgModule({ imports: [CommonModule, RouterModule, ButtonsNavBarModule], declarations: [NotFoundComponent] }) export class NotFoundModule {} ================================================ FILE: apps/codelab/src/app/components/slides/closing-slide/codelab-closing-slide.component.css ================================================ ================================================ FILE: apps/codelab/src/app/components/slides/closing-slide/codelab-closing-slide.component.html ================================================
This codelab is written in Angular and available on Github. Please ⭐ if you enjoyed it.
================================================ FILE: apps/codelab/src/app/components/slides/closing-slide/codelab-closing-slide.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { CodelabClosingSlideComponent } from './codelab-closing-slide.component'; describe('CodelabClosingSlideComponent', () => { let component: CodelabClosingSlideComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [CodelabClosingSlideComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(CodelabClosingSlideComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/codelab/src/app/components/slides/closing-slide/codelab-closing-slide.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'codelab-closing-slide', templateUrl: './codelab-closing-slide.component.html', styleUrls: ['./codelab-closing-slide.component.css'] }) export class CodelabClosingSlideComponent implements OnInit { @Input() header: String; @Input() body: String; @Input() footer: String; constructor() {} ngOnInit() {} } ================================================ FILE: apps/codelab/src/app/components/slides/title-slide/ripple-animation/codelab-ripple-animation.component.css ================================================ @keyframes circle { from { transform: scale(0); } to { transform: scale(0.5); } } .circle { margin: 0 auto; width: 25vw; height: 25vw; border: 1vw solid rgba(255, 89, 75, 0.1); border-radius: 50%; position: absolute; left: 0; top: 0; } .one { animation: circle 4s infinite linear; } .two { animation: circle 3s infinite linear; } .three { animation: circle 2s infinite linear; } ================================================ FILE: apps/codelab/src/app/components/slides/title-slide/ripple-animation/codelab-ripple-animation.component.html ================================================
================================================ FILE: apps/codelab/src/app/components/slides/title-slide/ripple-animation/codelab-ripple-animation.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { CodelabRippleAnimationComponent } from './codelab-ripple-animation.component'; describe('CodelabRippleAnimationComponent', () => { let component: CodelabRippleAnimationComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [CodelabRippleAnimationComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(CodelabRippleAnimationComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/codelab/src/app/components/slides/title-slide/ripple-animation/codelab-ripple-animation.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'codelab-ripple-animation', templateUrl: './codelab-ripple-animation.component.html', styleUrls: ['./codelab-ripple-animation.component.css'] }) export class CodelabRippleAnimationComponent implements OnInit { constructor() {} ngOnInit() {} } ================================================ FILE: apps/codelab/src/app/components/slides/title-slide/title-slide.component.css ================================================ :host { height: 100%; } .title-slide { height: 100%; display: flex; flex-direction: column; } .header { flex: 0.5; display: flex; justify-content: flex-end; flex-direction: column; background: #982a2a; color: white; font-size: 10vw; padding: 2vw; } .contents { padding: 2vw; display: flex; flex-direction: column; flex: 0.5; } .description { font-size: 5vw; } .instructions { flex-grow: 1; font-size: 4vw; color: #666; margin-top: 20px; } .left-half-ripple { overflow: hidden; margin: 0; height: 25vw; width: calc(25vw / 2 + 2vw); position: absolute; top: calc(50% - 25vw / 2); right: 0; } ================================================ FILE: apps/codelab/src/app/components/slides/title-slide/title-slide.component.html ================================================
{{ title }}
{{ description }}
Use and on your keyboard to navigate the slides.
Prerequisites: {{ prereqs }}
================================================ FILE: apps/codelab/src/app/components/slides/title-slide/title-slide.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { By } from '@angular/platform-browser'; import { TitleSlideComponent } from './title-slide.component'; import { CodelabRippleAnimationComponent } from './ripple-animation/codelab-ripple-animation.component'; describe('TitleSlideComponent', () => { let component: TitleSlideComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [CodelabRippleAnimationComponent, TitleSlideComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TitleSlideComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); it('should render a title', () => { component.title = 'awesome title'; fixture.detectChanges(); const el = fixture.debugElement.query(By.css('.header')); expect(el.nativeElement.textContent).toContain('awesome title'); }); it('should render a description', () => { component.description = 'hello world!'; fixture.detectChanges(); const el = fixture.debugElement.query(By.css('.description')); expect(el.nativeElement.textContent).toContain('hello world!'); }); it('should render prerequisites', () => { component.prereqs = 'do this first'; fixture.detectChanges(); const el = fixture.debugElement.query(By.css('.prereqs')); expect(el.nativeElement.textContent).toContain('do this first'); expect(el.nativeElement.textContent).toContain('Prerequisites'); }); it('should not show "Prerequisites" if undefined', () => { component.prereqs = undefined; fixture.detectChanges(); const el = fixture.debugElement.query(By.css('.prereqs')); expect(el.nativeElement.textContent).not.toContain('Prerequisites'); }); }); ================================================ FILE: apps/codelab/src/app/components/slides/title-slide/title-slide.component.ts ================================================ import { Component, Input } from '@angular/core'; @Component({ selector: 'codelab-title-slide', templateUrl: './title-slide.component.html', styleUrls: ['./title-slide.component.css'] }) export class TitleSlideComponent { @Input() title: string; @Input() description: string; @Input() prereqs: string; } ================================================ FILE: apps/codelab/src/app/components/slides-preview/codelab-preview.component.html ================================================
================================================ FILE: apps/codelab/src/app/components/slides-preview/codelab-preview.component.scss ================================================ .codelab-preview { position: relative; width: 100%; height: 100%; } .codelab-preview.expanded { position: fixed; top: 0; left: 0; right: 0; bottom: 0; z-index: 100; background-color: #fff; } .preview-header { position: absolute; right: 1px; top: 1px; } .button-expand { outline: none; width: 35px; height: 35px; padding: 0; border: 0; background-image: url('/assets/images/expand.png'); background-color: #fff; background-size: 100% 100%; cursor: pointer; &:hover { background-color: #eee; } } iframe { box-shadow: 0 0 1px #dddddd; border: 0 solid; } .body, iframe { width: 100%; height: 100%; } ================================================ FILE: apps/codelab/src/app/components/slides-preview/codelab-preview.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; import { ActivatedRoute, Router } from '@angular/router'; import { Location } from '@angular/common'; @Component({ selector: 'codelab-preview', templateUrl: './codelab-preview.component.html', styleUrls: ['./codelab-preview.component.scss'] }) export class CodelabPreviewComponent implements OnInit { @Input() milestone; public isExpanded: boolean; private iframeSource; constructor( private sanitizer: DomSanitizer, private router: Router, private activatedRoute: ActivatedRoute, private location: Location ) {} ngOnInit() { const url = this.location.prepareExternalUrl( this.router .createUrlTree(['.'], { relativeTo: this.activatedRoute, queryParams: { milestone: this.milestone, hideControls: true, resize: true }, queryParamsHandling: 'merge' }) .toString() ); this.iframeSource = this.sanitizer.bypassSecurityTrustResourceUrl(url); } expandToggle() { return (this.isExpanded = !this.isExpanded); } } ================================================ FILE: apps/codelab/src/app/containers/full-layout/full-layout.component.html ================================================ ================================================ FILE: apps/codelab/src/app/containers/full-layout/full-layout.component.scss ================================================ ================================================ FILE: apps/codelab/src/app/containers/full-layout/full-layout.component.ts ================================================ import { ChangeDetectionStrategy, Component, Optional } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'codelab-full-layout', changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: './full-layout.component.html', styleUrls: ['./full-layout.component.scss'] }) export class FullLayoutComponent { displayButtons: boolean; constructor(@Optional() route: ActivatedRoute) { this.displayButtons = !route.snapshot.queryParams.milestone; } } ================================================ FILE: apps/codelab/src/app/containers/full-layout/full-layout.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { FullLayoutComponent } from './full-layout.component'; import { SyncModule } from '../../sync/sync.module'; import { ButtonsNavBarModule } from '../../components/buttons-nav-bar/buttons-nav-bar.module'; @NgModule({ imports: [RouterModule, SyncModule, ButtonsNavBarModule], declarations: [FullLayoutComponent] }) export class FullLayoutModule {} ================================================ FILE: apps/codelab/src/app/containers/full-layout/index.ts ================================================ export * from './full-layout.component'; ================================================ FILE: apps/codelab/src/app/containers/index.ts ================================================ export * from './full-layout'; ================================================ FILE: apps/codelab/src/app/directives/directives.module.ts ================================================ import { NgModule } from '@angular/core'; import { IsLoggedInDirective } from './permissions/is-logged-in/is-loggef-in.directive'; import { CanLoadAdminDirective } from './permissions/can-load-admin/can-load-admin.directive'; import { NextSlideDirective } from './nextSlide.directive'; import { PreviousSlideDirective } from './previousSlide.directive'; @NgModule({ declarations: [ IsLoggedInDirective, CanLoadAdminDirective, NextSlideDirective, PreviousSlideDirective ], exports: [ IsLoggedInDirective, CanLoadAdminDirective, NextSlideDirective, PreviousSlideDirective ] }) export class DirectivesModule {} ================================================ FILE: apps/codelab/src/app/directives/nextSlide.directive.ts ================================================ import { Directive, HostListener } from '@angular/core'; import { SlidesDeckComponent } from '@ng360/slides'; @Directive({ selector: '[nextSlide]' }) export class NextSlideDirective { constructor(public deck: SlidesDeckComponent) {} @HostListener('click') onClick() { this.deck.nextSlide(); } } ================================================ FILE: apps/codelab/src/app/directives/permissions/abstract-permission.ts ================================================ import { TemplateRef, ViewContainerRef, OnDestroy, Injectable } from '@angular/core'; import { AccessService } from '../../shared/services/access.service'; import { ReplaySubject, Observable } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; @Injectable() export class AbstractPermission implements OnDestroy { public destroy = new ReplaySubject(1); constructor( public templateRef: TemplateRef, public viewContainer: ViewContainerRef, public accessService: AccessService ) {} ngOnDestroy() { this.destroy.next(null); this.destroy.complete(); } public render(observable: Observable): void { observable.pipe(takeUntil(this.destroy)).subscribe(hasPermission => { if (hasPermission) { this.viewContainer.createEmbeddedView(this.templateRef); } else { this.viewContainer.clear(); } }); } } ================================================ FILE: apps/codelab/src/app/directives/permissions/can-load-admin/can-load-admin.directive.ts ================================================ import { Directive, OnInit, TemplateRef, ViewContainerRef } from '@angular/core'; import { AccessService, Permissions } from '../../../shared/services/access.service'; import { AbstractPermission } from '../abstract-permission'; @Directive({ selector: '[canLoadAdmin]' }) export class CanLoadAdminDirective extends AbstractPermission implements OnInit { constructor( templateRef: TemplateRef, viewContainer: ViewContainerRef, accessService: AccessService ) { super(templateRef, viewContainer, accessService); } ngOnInit() { this.render(this.accessService.can(Permissions.CAN_LOAD_ADMIN)); } } ================================================ FILE: apps/codelab/src/app/directives/permissions/is-logged-in/is-loggef-in.directive.ts ================================================ import { Directive, OnInit, TemplateRef, ViewContainerRef } from '@angular/core'; import { AccessService } from '../../../shared/services/access.service'; import { AbstractPermission } from '../abstract-permission'; @Directive({ selector: '[isLoggedIn]' }) export class IsLoggedInDirective extends AbstractPermission implements OnInit { constructor( templateRef: TemplateRef, viewContainer: ViewContainerRef, accessService: AccessService ) { super(templateRef, viewContainer, accessService); } ngOnInit() { this.render(this.accessService.oldIsAdmin$); } } ================================================ FILE: apps/codelab/src/app/directives/previousSlide.directive.ts ================================================ import { Directive, HostListener } from '@angular/core'; import { SlidesDeckComponent } from '@ng360/slides'; @Directive({ selector: '[previousSlide]' }) export class PreviousSlideDirective { constructor(public deck: SlidesDeckComponent) {} @HostListener('click') onClick() { this.deck.previousSlide(); } } ================================================ FILE: apps/codelab/src/app/shared/angular-code/app.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: 'Hi!' }) export class AppComponent {} ================================================ FILE: apps/codelab/src/app/shared/angular-code/app.module.ts ================================================ import { NgModule } from '@angular/core'; import { BrowserModule } from '@angular/platform-browser'; import { AppComponent } from './app.component'; @NgModule({ imports: [BrowserModule], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/codelab/src/app/shared/angular-code/bootstrap.ts ================================================ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { ResourceLoader } from '@angular/compiler'; import * as code from './code'; import { AppModule } from './app.module'; class MyResourceLoader extends ResourceLoader { get(url: string): Promise { const templateId = Object.keys(code).find(key => key.includes(url.replace(/[\/\.-]/gi, '_')) ); const template = code[templateId]; if (!template) { console.log(template); // tslint:disable-next-line:no-debugger debugger; } return Promise.resolve(template); } } platformBrowserDynamic().bootstrapModule(AppModule, [ { providers: [ { provide: ResourceLoader, useFactory: () => new MyResourceLoader(), deps: [] } ] } ]); ================================================ FILE: apps/codelab/src/app/shared/angular-code/code.ts ================================================ export const code = ''; ================================================ FILE: apps/codelab/src/app/shared/angular-code/index.html ================================================ Loading... ================================================ FILE: apps/codelab/src/app/shared/helpers/codelabFile.ts ================================================ import { FileConfig } from '../interfaces/file-config'; export enum FileType { TypeScript = 'typescript', Html = 'html', Css = 'css' } const fileConfig = { [FileType.TypeScript]: { extension: '.ts' }, [FileType.Html]: { extension: '.html' }, [FileType.Css]: { extension: '.css' } }; export class CodelabFile implements FileConfig { highlight?: RegExp | RegExp[]; after: string; public code: string; public template: string; public solution: string; public bootstrap = false; public path: string; public excludeFromTesting = false; public test = false; public before: string; public hidden = false; static TypeScriptFile(name: string): CodelabFile { return new CodelabFile(FileType.TypeScript, name).setAfter( 'export function evalJs( js ){ return eval(js);}' ); } static TypeScriptTest(name: string): CodelabFile { return new CodelabFile(FileType.TypeScript, name + 'Test').makeTest(); } static Html(name: string): CodelabFile { return new CodelabFile(FileType.Html, name); } static Css(name: string): CodelabFile { return new CodelabFile(FileType.Css, name); } constructor( public readonly type: FileType, public readonly moduleName: string ) { this.path = moduleName + fileConfig[type].extension; } public setAfter(after: string): CodelabFile { this.after = after; return this; } public clone(): CodelabFile { return Object.assign(Object.create(CodelabFile), this); } public makeBootstrappable(): CodelabFile { this.bootstrap = true; this.excludeFromTesting = true; return this; } public makeHidden(): CodelabFile { this.hidden = true; return this; } public makeTest(): CodelabFile { this.test = true; this.before = 'mochaBefore();'; this.after = 'mochaAfter();'; this.excludeFromTesting = false; this.makeHidden(); this.makeBootstrappable(); return this; } public setSolution(solution: string): CodelabFile { this.solution = solution; return this; } public withHighlight(highlight: RegExp | RegExp[]) { this.highlight = highlight; return this; } public setCode(code: string): CodelabFile { this.code = code; this.template = code; if (!this.solution) { this.solution = code; } return this; } } ================================================ FILE: apps/codelab/src/app/shared/helpers/helpers.ts ================================================ import { FileConfig } from '../interfaces/file-config'; function exerciseWithConsoleLog(moduleName: string, code: any, code2: any) { return { ...exercise(moduleName, code, code2), before: ` export const value = window.value = window.value || {}; function wrap(context, prop, callback){ if(!context[prop].patched){ const originalMethod = context[prop]; context[prop] = function(...args){ callback(...args); return originalMethod.apply(context, args); } context[prop].patched = true; } } /* TODO: Get rid of the CSS hack */ wrap(console, 'log', (v)=>{ value.value = v; document.body.innerHTML += '
' +
       ' ' + JSON.stringify(v, null, '  ') + '
' + '
' document.body.scrollTop = document.body.scrollHeight; }) ` }; } export function exercise( moduleName: string, template: string, solution?: string ): FileConfig { solution = solution || template; return { bootstrap: false, excludeFromTesting: false, type: 'typescript', path: moduleName + '.ts', template, code: template, moduleName: moduleName, solution, after: `export function evalJs( js ){ return eval(js);}` }; } export function test(moduleName: string, template: string): FileConfig { return { path: moduleName + 'Test.ts', type: 'typescript', template, code: template, moduleName: moduleName + 'Test', excludeFromTesting: false, test: true, bootstrap: true, before: 'mochaBefore();', after: 'mochaAfter();', hidden: true }; } interface SimpleImport { name: string; path: string; } export const builder = { imports(imports: Array) { return imports.map(i => `import {${i.name}} from '${i.path}';`).join('\n'); }, listOfComponents: (components: Array) => { return `[${components.map(c => c.name).join(',')}]`; }, ngModule( declarations: Array = [ { name: 'AppComponent', path: './app.component' } ], bootstrapComponent?: Array ) { bootstrapComponent = bootstrapComponent || declarations; return `import {BrowserModule} from \'@angular/platform-browser\'; import {NgModule} from \'@angular/core\'; ${this.imports(declarations)} @NgModule({ imports: [BrowserModule], declarations: ${this.listOfComponents(declarations)}, bootstrap: ${this.listOfComponents(bootstrapComponent)} }) export class AppModule {}`; }, bootstrap( module: SimpleImport = { name: 'AppModule', path: './app.module' } ): string { return `import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; ${this.imports([module])} import {ResourceLoader} from '@angular/compiler'; class MyResourceLoader extends ResourceLoader { get(url: string): Promise { const templateId = Object.keys(code).find(key => key.includes(url.replace(/[\\/\\.-]/gi, '_'))); const template = code[templateId]; if (!template) { console.log(template); // tslint:disable-next-line:no-debugger debugger; } return Promise.resolve(template); }; } const platform = platformBrowserDynamic(); platform.bootstrapModule(${module.name}, [ { providers: [ {provide: ResourceLoader, useFactory: () => new MyResourceLoader(), deps: []} ] } ]); `; } }; export function html(path = 'app', code, solution = '') { return { code, path: path + '.html', solution: solution, type: 'html' }; } export function stylesheet(code, solution = '') { return { code, path: 'style.css', solution: solution || code, type: 'css' }; } export function bootstrap( moduleName: string, template: string, solution?: string ) { solution = solution || template; return { bootstrap: true, excludeFromTesting: true, type: 'typescript', path: moduleName + '.ts', template, code: template, moduleName: moduleName, solution }; } export function circleAndBox() { const result = boxAndCircle(); const temp = result.files[0]; result.files[0] = result.files[1]; result.files[1] = temp; return result; } // That's me being plain lazy, we need export function boxAndCircle() { const moduleCode = `import {BrowserModule} from \'@angular/platform-browser\'; import {NgModule} from '@angular/core'; import {BoxComponent} from './box.component'; import {CircleComponent} from './circle.component'; @NgModule({ imports: [BrowserModule], declarations: [CircleComponent, BoxComponent], bootstrap: [BoxComponent] }) export class AppModule {}`; const circleCode = `import { Component, Input } from '@angular/core'; @Component({ selector: 'slides-circle', template: '
' }) export class CircleComponent { @Input() size: number; @Input() color: string; }`; const boxCode = `import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: \`
\` }) export class BoxComponent { circleColor="green" }`; const bootstrapCode = `import {platformBrowserDynamic} from '@angular/platform-browser-dynamic'; import {AppModule} from './app.module'; platformBrowserDynamic().bootstrapModule(AppModule) `; return { other: { boxNoParams: `import { Component } from '@angular/core'; @Component({ selector: 'slides-box', template: \`
\` }) export class BoxComponent { circleColor="green" }` }, files: [ exercise('box.component', boxCode), exercise('circle.component', circleCode), exercise('app.module', moduleCode), bootstrap('main', bootstrapCode, bootstrapCode), { type: 'css', path: 'styles.css', template: ` my-app > div { width: 300px; height: 200px; border: 1px #ddd solid; } .circle { margin-left: 100px; margin-top: 50px; border-radius: 50%; } ` } ] }; } export function displayAngularComponent( componentCode: string, testCode?: string ) { // tslint:disable-next-line:max-line-length TODO: Clean up next line and remove this comment. const moduleCode = builder.ngModule(); const bootstrapCode = builder.bootstrap(); return { files: [ exercise('app.component', componentCode), exercise('app.module', moduleCode), bootstrap('main', bootstrapCode, bootstrapCode), { type: 'css', path: 'styles.css', code: ` body, html { margin: 0; padding: 2vw; font-family: sans-serif; } h1, h2 { margin: 0; padding: 0; } h1 {font-size: 6vw;} h2 {font-size: 4vw;} ` }, ...(testCode ? [test('test', testCode)] : []) ] }; } export function typeScriptWithConsoleLog( code: string, bootstrapCode = 'import "./app";', testCode = '', otherCode = '' ) { const files = [ exerciseWithConsoleLog('app', code, code), bootstrap('main', bootstrapCode, bootstrapCode), test('test', testCode), { path: 'main.css', type: 'css', code: `` } ]; if (otherCode !== '') { files.push(exercise('puppy', otherCode)); } return { files }; } export function javaScriptWithConsoleLog( code: string, bootstrapCode = 'import "./app";', testCode = '', otherCode = '' ) { const result = typeScriptWithConsoleLog( code, bootstrapCode, testCode, otherCode ); (result.files[0] as FileConfig).editorType = 'javascript'; return result; } export function displayAngularComponentWithHtml( componentCode: string, code: string ) { return { files: [ { code, path: 'app/app.html', solution: '', type: 'html' }, ...displayAngularComponent(componentCode).files ] }; } export function solve(exerciseConfig) { return { ...exerciseConfig, files: exerciseConfig.files.map(file => ({ ...file, code: file.solution || file.code })) }; } ================================================ FILE: apps/codelab/src/app/shared/interfaces/exercise-config.ts ================================================ import { FileConfig } from './file-config'; import { TestInfo } from './test-info'; export interface ExerciseConfig { name: string; description: string; runner?: string; files: Array; skipTests?: boolean; tests?: Array; } ================================================ FILE: apps/codelab/src/app/shared/interfaces/file-config.ts ================================================ export interface FileConfig { opened?: boolean; /** * typescript or html. */ type?: string; /** * Source code of the file. */ code?: string; template: string; /** * Source code of the file. */ solution?: string; /** * TS code to run before running the file. */ before?: string; /** * TS code to run after running the file. */ after?: string; /** * Usually the same as fileName without .ts postfix. * Currently gets inferred from filename. */ moduleName?: string; /** * Actual filename. */ path: string; /** * If this is true; the file will be included in the preview iframe. */ ui?: boolean; /** * If this is true */ bootstrap?: boolean; excludeFromTesting?: boolean; /** * if this is true; the file will be displayed in read only mode. */ readonly?: boolean; /** * If this is true the file will be included in the test iframe. */ test?: boolean; /** * If this is true; the file will be hidden. */ hidden?: boolean; /** * File dependencies, need for proper highlighting in monaco. */ editorType?: string; // If this is set, this will be executed as a test. // This is a hack and will be removed. execute?: any; } ================================================ FILE: apps/codelab/src/app/shared/interfaces/test-info.ts ================================================ import { FileConfig } from './file-config'; export interface TestInfo { title: string; file: FileConfig; pass?: boolean; result?: string; filename?: string; } ================================================ FILE: apps/codelab/src/app/shared/services/access.service.ts ================================================ import { Injectable } from '@angular/core'; import { LoginService } from '@codelab/firebase-login'; import { SyncDbService } from '@codelab/utils/src/lib/sync/services/sync-db.service'; import { filter, switchMap } from 'rxjs/operators'; import { Observable } from 'rxjs'; import { SyncDb } from '@codelab/utils/src/lib/sync/services/sync-data.service'; export enum Permissions { MANAGE_USERS = 'manage_users', CAN_LOAD_ADMIN = 'can_load_admin' } @Injectable({ providedIn: 'root' }) export class AccessService { readonly oldIsAdmin$ = this.loginService.uid$.pipe( switchMap(uid => { return this.dbService .object('authorized_users') .object(uid) .withDefault(false) .valueChanges(); }) ); private readonly adminPermissions = this.dbService .object('admin') .object(this.loginService.uid$) .object('permissions'); constructor( private readonly loginService: LoginService, private readonly dbService: SyncDbService ) {} can(p: Permissions): Observable { return ( this.adminPermissions // TODO(kirjs): default: false .object(p) .valueChanges() .pipe(filter(a => a !== null)) ); } } ================================================ FILE: apps/codelab/src/app/shared/services/guards/admin-guard.ts ================================================ import { Injectable } from '@angular/core'; import { Router, RouterStateSnapshot, ActivatedRouteSnapshot, CanActivate } from '@angular/router'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { AccessService, Permissions } from '../access.service'; @Injectable({ providedIn: 'root' }) export class AdminGuard implements CanActivate { constructor(private _route: Router, private accessService: AccessService) {} canActivate( route: ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable | boolean { return true; return this.accessService.can(Permissions.CAN_LOAD_ADMIN).pipe( map(hasAccess => { if (!hasAccess) { this._route.navigate(['login']); return false; } return true; }) ); } } ================================================ FILE: apps/codelab/src/app/shared/services/guards/login-guard.ts ================================================ import { Injectable } from '@angular/core'; import { Router, RouterStateSnapshot, CanActivate } from '@angular/router'; import { LoginService } from '@codelab/firebase-login'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class LoginGuard implements CanActivate { constructor(private _route: Router, private loginService: LoginService) {} canActivate( ActivatedRouteSnapshot, state: RouterStateSnapshot ): Observable | boolean { return this.loginService.isAnonymous$.pipe( map(res => { if (res) { // user is anonymous return true; } // user is logged in, navigate to home this._route.navigate(['/']); return false; }) ); } } ================================================ FILE: apps/codelab/src/app/shared/shared.module.ts ================================================ import { NgModule } from '@angular/core'; import { HttpClientModule } from '@angular/common/http'; import { FormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { SlidesModule } from '@ng360/slides'; import { CodeDemoModule } from '@codelab/code-demos'; import { CodelabComponentsModule } from '../components/codelab-components.module'; import { ButtonsNavBarModule } from '../components/buttons-nav-bar/buttons-nav-bar.module'; import { SyncModule } from '../sync/sync.module'; @NgModule({ imports: [ HttpClientModule, FormsModule, RouterModule, CodeDemoModule, CodelabComponentsModule, SlidesModule, ButtonsNavBarModule, SyncModule ], exports: [ HttpClientModule, FormsModule, CodeDemoModule, CodelabComponentsModule, SlidesModule, ButtonsNavBarModule ] }) export class SharedModule {} ================================================ FILE: apps/codelab/src/app/sync/sync.component.css ================================================ .survey { opacity: 0.98; position: absolute; top: 20px; left: 20px; bottom: 20px; right: 20px; border: 1px solid #000; background-color: #fff; padding: 20px; z-index: 1000; font-family: sans-serif; } h1 { text-align: center; } ================================================ FILE: apps/codelab/src/app/sync/sync.component.html ================================================

Quick Survey

{{ userId$ | async }} ================================================ FILE: apps/codelab/src/app/sync/sync.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SyncComponent } from './sync.component'; import { SyncModule } from './sync.module'; import { AngularFireDatabase } from '@angular/fire/database'; import { AngularFireAuth } from '@angular/fire/auth'; import { getMockAngularFireProviders, MockAngularFireAuth, MockAngularFireDatabase } from '@codelab/utils/src/lib/testing/mocks/angular-fire'; describe('SyncComponent', () => { let component: SyncComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [SyncModule], providers: [getMockAngularFireProviders()] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SyncComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/codelab/src/app/sync/sync.component.ts ================================================ import { Component, Input } from '@angular/core'; import { SyncPollConfig } from '@codelab/utils/src/lib/sync/components/poll/common/common'; import { SyncDataService } from '@codelab/utils/src/lib/sync/services/sync-data.service'; import { SyncSessionService } from '@codelab/utils/src/lib/sync/services/sync-session.service'; import { switchMap } from 'rxjs/operators'; import { canWritePresenterData, SyncStatus } from '@codelab/utils/src/lib/sync/common'; import { of } from 'rxjs'; @Component({ selector: 'codelab-sync-survey', templateUrl: './sync.component.html', styleUrls: ['./sync.component.css'] }) export class SyncComponent { @Input() admin = false; readonly userId$ = this.syncSessionService.viewerId$; readonly isPresentationEnabled$ = this.syncDataService .getPresenterObject('enabled') .withDefault(false) .valueChanges(); readonly shouldShowPresentation$ = this.syncSessionService.status$.pipe( switchMap(s => { if (canWritePresenterData(s)) { return of(true); } if (s === SyncStatus.OFF) { return this.admin ? this.syncSessionService.canStartSession$ : of(false); } return this.isPresentationEnabled$; }) ); polls: SyncPollConfig[] = [ { key: 'js', type: 'stars', question: 'How well do you know JavaScript' }, { key: 'ts', type: 'stars', question: 'How well do you know TypeScript' }, { key: 'angularjs', type: 'stars', question: 'How well do you know AngularJS (Old version)' }, { key: 'angular', type: 'stars', question: 'How well do you know Angular (The new version we are learning today)' } ]; constructor( private readonly syncDataService: SyncDataService, private readonly syncSessionService: SyncSessionService ) {} } ================================================ FILE: apps/codelab/src/app/sync/sync.module.ts ================================================ import { Component, NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CommonModule } from '@angular/common'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { SyncPollModule } from '@codelab/utils/src/lib/sync/components/poll/sync-poll.module'; import { SyncButtonModule } from '@codelab/utils/src/lib/sync/sync-button/sync-button.module'; import { SyncDirectivesModule } from '@codelab/utils/src/lib/sync/directives/sync-directives.module'; import { ConfigureSyncModule } from '@codelab/utils/src/lib/sync/components/configure-sync/configure-sync.module'; import { SyncComponent } from './sync.component'; @NgModule({ declarations: [SyncComponent], exports: [SyncComponent], imports: [ CommonModule, SlidesModule, SyncPollModule, SyncButtonModule, SyncDirectivesModule, ConfigureSyncModule ] }) export class SyncModule {} @Component({ selector: 'codelab-sync-admin-wrapper', template: '' }) export class SyncAdminWrapperComponent {} const routes = RouterModule.forChild( SlidesRoutes.get(SyncAdminWrapperComponent) ); @NgModule({ declarations: [SyncAdminWrapperComponent], imports: [SyncModule, routes] }) export class SyncAdminModule {} ================================================ FILE: apps/codelab/src/assets/.gitkeep ================================================ ================================================ FILE: apps/codelab/src/environments/environment.prod.ts ================================================ export const environment = { production: true, // Firebase // TODO: Create a new firebase app/config for prod deploy. firebaseConfig: { apiKey: 'AIzaSyBiY1Lg2RIcKtbgqzfE6Vrg28Zjal6ZWHs', authDomain: 'angular-presentation.firebaseapp.com', databaseURL: 'https://angular-presentation.firebaseio.com', projectId: 'angular-presentation', storageBucket: 'angular-presentation.appspot.com', messagingSenderId: '1087862173437', appId: '1:1087862173437:web:0bb7fe324b62580bb31894' } }; ================================================ FILE: apps/codelab/src/environments/environment.ts ================================================ // The file contents for the current environment will overwrite these during build. // The build system defaults to the dev environment which uses `environment.ts`, but if you do // `ng build --env=prod` then `environment.prod.ts` will be used instead. // The list of which env maps to which file can be found in `.angular-cli.json`. export const environment = { production: false, // Firebase firebaseConfig: { apiKey: 'AIzaSyBiY1Lg2RIcKtbgqzfE6Vrg28Zjal6ZWHs', authDomain: 'angular-presentation.firebaseapp.com', databaseURL: 'https://angular-presentation.firebaseio.com', projectId: 'angular-presentation', storageBucket: 'angular-presentation.appspot.com', messagingSenderId: '1087862173437', appId: '1:1087862173437:web:0bb7fe324b62580bb31894' } }; ================================================ FILE: apps/codelab/src/index.html ================================================ Angular Codelab
================================================ FILE: apps/codelab/src/locale/codelab.ru.xtb ================================================ ]> Angular Codelab Милый котенок В следующем упражнении вы будете использовать созданный ранее компонент Здесь перечислены документация, описание функций и событий Предположим, имеется HTML файл: Это может быть сделано в три этапа. Три этапа для создания приложения: Создание Angular компонента Создание Angular модуля Запуск (Bootstrap) модуля Компонент определяется декоратором <b>@Component</b> у класса Создайте первый Angular компонент! порядок загрузки: index -> main -> app.module -> app.component Конец раздела загрузки (Bootstrap) Отлично сработано! Упражнение выполнено! Внедрение зависимостей Узнайте про систему внедрения зависимости в Angular Отметить зависимость декоратором @Injectable() Передать модулю Запросить в компоненте Запрашиваем внедряемый сервис в компоненте При привязке можно использовать произвольные выражения Почему TypeScript? TypeScript В TypeScript есть<b> классы</b>, и Angular часто их использует. Теперь мы можем использовать класс <b>Puppy</b> в другом файле. Интерфейсы Перечисляемые типы (enums) Асинхронность / Ожидания Деструктурирование И много чего еще! На следующем слайде будет упражнение по TypeScript Выглядеть будет вот так: Или пользуйтесь сокращенной натацией Ошибка: очевидно, что это не собачка Это число (number) Дерево компонентов Родительские и дочерние компоненты Родительские и дочерние компоненты Обзор Упражнение 2 @Component это декоратор Angular Декоратор указывается над сущностью (классом) Имя компонента это имя класса (AppComponent) Что такое Angular Селектор Встроенный шаблон Модуль Декоратор NgModule BrowserModule Declarations Bootstrap Создание первого Ng модуля Bootstrapping Bootstrapping 1 Bootstrapping 2 Bootstrapping 3 *Сокращения в Typescript делают 'profession' доступным в объекте компонента предполагая, что Job имеет свойство '.title' Параметры Тестирование Пример Это рабочий HTML синтаксис. Так можно привязать значение аттрибутов Это позволяет условно сделать привязку к классу Или CSS Все работает с пользовательскими компонентами При нажатии на кнопку, вызовется метод компонента "saveUser" и передаст соответствующий event. Вы также можете создавать события для собственных компонентов. Здесь у нас есть событие "depleted", которое вызовет "soundAlarm" когда сработает Еще есть упрощенный способ привязки событий! Если пользователь нажмет CTRL+ENTER, запустится метод submit (это возможность Angular) в userName есть ссылка на input Попробуйте изменить на true! Необходимо повторить собачку тут Интерполирование Cвойства Привязка свойства Продвинутая привязка данных Привязка событий Повторяющиеся элементы Повторение элементов (*ngFor) Упражнение 3 Или используйте сокращенную нотацию для функций (стрелочные функции) TypeScript догадается, что это число TypeScript догадается, что это строка (string) Невозможно сложить число с логическим значением У прототипа Number нет метода slice Но можно для строки Работает! Type[] делает тоже самое. Это метод Гав гав Теперь мы можем создать экземпляр класса Puppy И вызвать его методы Потом добавим здесь код Создадим еще собачек Var по-прежнему разрешен, но не рекомендуется. Let рекомендуется для использования взамен var. В отличие от var let недоступен вне this. Const похож на let, но если вы попробуете изменить его, то TS выдаст ошибку. хорошо, определенно boolean TypeScript Массивы Конструктор Модификатор доступа Экспорт Импорт Фильтр (последнее) Еще... Раздел завершен Тут не должно быть точки с запятой. Декоратор привязывается к классу. Первое приложение на Angular Вы узнаете как создать Angular компонент, добавить его в модуль и запустить приложение. Знание основ TypeScript Шаблоны Узнайте больше о шаблонах в Angular! Создайте класс с именем 'Codelab' Основы JavaScript. video/video.component.ts: Отметьте компонент декоратором '@Component' и добавьте в него селектор 'my-video'. app.module.ts: Объявите компонент VideoComponent в свойстве 'declarations' модуля AppModule. video.component.ts Добавьте ссылку на соответствующий HTML файл с шаблоном в свойство templateUrl , чтобы загрузить шаблон в компонент video/video.component.ts: Добавьте свойство video и отметьте его декоратором @Input() video/video.component.html: Отобразите название (title) видео video/video.component.html: Отобразите превью (src) видео video/video.component.html: Отобразите описание(description) видео video/video.component.html: Выведите дату видео video/video.component.html: Отобразите количество просмотров видео(views) video/video.component.html: Отобразите количество отметок "нравится"(likes) app.html: Замените текущие название и превью видео на наш чудесный компонент <my-video> app.html: С помощью биндинга передайте объект "video" в компонент my-video (не забудьте про квадратные скобки) Создайте класс 'AppComponent' Создайте класс 'AppModule' Всё готово! Загружайте приложение Экспортируйте класс Добавьте классу декоратор @Component Добавьте селектор 'my-app' в @Component декоратор компонента Добавьте шаблон, содержащий тег h1 с текстом "Hello MewTube!" Добавьте классу декоратор NgModule Добавьте 'BrowserModule' в свойство 'imports' декоратора NgModule Добавьте компонент 'AppComponent' к свойству 'declarations' у декоратора Добавьте компонент 'AppComponent' в свойство 'bootstrap' декоратора video.service.ts: Добавьте классу декоратор @Injectable() app.module.ts: Добавьте VideoService в свойство providers вашего NgModule app.component.ts: Избавьтесь от константы FAKE_VIDEOS app.component.ts: Внедрите 'VideoService' в конструктор компонента как 'videoService' app.component.ts: Сделайте так, чтобы метод 'search' компонента использовал метод 'search' сервиса 'videoService' app.component.ts: Добавьте свойство 'videos', и установите пустой массив в качестве значения. app.component.ts: Внутри метода 'search' установите переменную FAKE_VIDEOS в качестве значения свойства 'videos'. app.html: Добавьте заголовок h1, отобразите там свойство 'title' AppComponent'а app.component.ts: Добавьте в компонент метод 'search', который принимает параметр 'searchString' app.html: Добавьте обработчик нажатия мышки на кнопку, вызовите метод 'search' и передайте значение поля ввода (Сам поиск мы сделаем позже) app.html: Добавьте сообщение 'no videos', которое появляется только когда массив videos пуст #Bonus app.component.ts: Сейчас вам придется нажать кнопку поиска, чтобы отобразились видео. Поправьте код так, чтобы видео отображались по умолчанию, без нажатия кнопки. app.component.ts: Внутри метода 'search' отфильтруйте FAKE_VIDEOS так, чтобы возвращались только видео, название которых содержит искомую строку searchString. (подсказка: используйте .includes или .indexOf строковый метод) app.html: Отобразите превью app.html: Пройдитесь по массиву videos с помощью '*ngFor' и выведите название каждого видео app.html: Добавьте кнопку (<button>) с текстом 'search' (Поиск) app.html: Добавьте тег input с атрибутом 'placeholder' = 'video' Добавьте конструктор Примите значение 'guests' в качестве параметра конструктора Укажите тип параметра guests (подсказка: массив типа Guest) Отметьте параметр ключевым словом public (обратите внимание, что теперь вы можете получить к нему доступ в классе, обратившись к this.guests) Создайте метод 'getGuestsComing' Измените метод getGuestsComing так, чтобы он возвращал массив из элементов 'guests', у которых свойство 'coming' равно true. Создайте ваше первое Angular приложение Пользовательские события Узнайте как создать и запустить ваше первое Angular приложение Узнайте как пользоваться шаблонами в Angular Узнайте как использовать систему внедрения звисимостей вместо того, чтобы хардкодить их Узнайте как лучше всего организовать ваше приложение с помощью пользовательских компонентов. Узнайте как привязывать события Следующий шаг - определить компонент в <b><b>NgModule</b></b>. С Angular мы создаем мобильные приложения, используя NativeScript или Ionic С Angular мы можем создать VR приложения с помощью A-FRAME или WEDGL Конец раздела Формы Переменная 'b' в коде ниже отмечена как ошибка, так как тип отсутствует. Укажите тип переменной 'b'. Благодаря этой информации, TypeScript может указать нам на ошибку. Исправьте eё, чтобы 2 + 2 снова стало равно 4! Другие типы, которые можно использовать TypeScript Пропустите, если вы уже знакомы с TypeScript Angular Или нажмите сюда чтобы показать все разделы Это конец курса от Codelab, но только начало вашего путешествия в Angular. Ниже приведены некоторые ссылки, которые могут помочь вам продолжить изучение. Пока в нашем приложении только один компонент, но по мере роста приложения и добавления новых компонентов, у нас получится дерево компонентов Внутри компонент может отобразить любой другой с помощью HTML тега, который соответствует селектору выбранного компонента Родительский компонент передает свои данные дочернему через свойства Измените <b><b> размер</b></b> на <b><b> 100 </b></b> и <b><b> цвет </b></b> на <b><b> красный </b></b>, чтобы получить японский флаг Свойства дочернего класса должны быть декорированы специальным <b><b> @Input() </b></b> декоратором В этом случае мы впервые применяем декораторы к свойствам, а не к классам В первом разделе мы изучили как создавать компоненты. Давайте создадим новый компонент VideoComponent и опишем там информацию, связанную с видео. Результат будет отображаться автоматически. В итоге должно получиться следующее: Компоненты не будут знать друг о друге, если они не задекларированы в общем модуле Angular - это <b>платформа разработки</b> для создания приложений под мобильные телефоны и компьютеры. Angular позволяет <b>расширить HTML синтаксис</b> , чтобы кратко и удобно разрабатывать компоненты приложения. Привязка данных (Data Binding) и внедрение зависимостей (Dependency Injection) позволяют использовать код в существенно меньшем объеме. Давайте создадим Angular приложение, которое заменит <b><b>hello-world</b></b> HTML элемент некоторым наполнением. Компонент в Angular - это просто класс. Свойства и поведение описываются внутри этого класса. Декораторы - новая функциональность TypeScript. Они описывают мета-данные к классу, функции, свойству класса или переменной. 'Selector' привязывает компонент к соответствующему элементу в HTML структуре документа. Когда Angular находит <b><b>hello-world</b></b> HTML тег в документе, он отрисовывает шаблон HelloWorldComponent'а внутри этого тега Шаблон (template) определяется HTML кодом, который генерируется компонентом. Если количество тегов в HTML растет, можно (и рекомендуется) использовать <b><b> templateUrl</b></b> вместо этого, указав путь к файлу HTML с шаблоном. На следующем слайде вы создадите свой первый <b><b>Angular</b></b>-компонент! Подготовьте компонент и опишите его поведение по предложенным инструкциям, результат будет отображен автоматически. В итоге должно получиться следующее: <b><b>NgModule</b></b> не имеет визуального представления и используется исключительно для группировки функциональных элементов Angular Мы узнаем больше о NgModule в следующих секциях В списке <b><b>declarations</b></b> определяются компоненты, принадлежащие AppModule Компонент, указываемый в списке <b><b>bootstrap</b></b> будет создан и показан в файле <b><b>index.html</b></b> На следующем слайде вы создадите свой первый <b><b>Angular</b></b>-модуль. Подготовьте модуль и опишите его поведение по предложенным инструкциям, результат будет отображен автоматически. В итоге получится следующее: У нас всё готово, самое время начать (загрузить) приложение Передайте <b><b>AppModule</b></b> в метод <b><b>bootstrapModule</b></b>, и он запустит все компоненты из раздела bootstrap этого модуля Для большинства простых приложений вы можете просто скопировать и вставить код выше «как есть» Как работает начальная загрузка в Angular? 1. Добавим среду исполнения. <b><b>platformBrowserDynamic()</b></b> указывает Angular, что мы работаем в браузере Узнайте больше о корневом модуле и начальной загрузке в Angular 2. Angular инициализирует компонент из списка <b><b>bootstrap</b></b>, указанного в <b><b>app.module.ts</b></b> (в нашем случае это <b><b>HelloWorldComponent</b></b>) 3. Angular ищет в документе элемент, соответствующий селектору, определенному в <b><b>HelloWorldComponent</b></b> (в нашем случае это <b><b>'hello-world'</b></b>) и вставляет компонент внутрь этого элемента Готово! На следующей странице вы запустите свое первое <b><b>Angular</b></b>-приложение Теперь, когда у нас есть и NgModule, и готовый компонент, давайте загрузим приложение! Пока Angular загружается, содержимое элемента остается неизменным: в нашем случае, "<b><b>Loading...</b></b>" Мы пишем браузерное веб-приложение, поэтому в списке <b><b>imports</b></b> необходимо указать <b><b>BrowserModule</b></b> <div><div> Angular применяется не только для веб-приложений. Вы также можете создавать мобильные приложения и даже VR-зарисовки. </div></div> <div><div> <a><a></a></a> </div></div> Без системы внедрения зависимостей (Dependency Injection), свойство <b><b>profession</b></b> должно быть инициализировано внутри класса <b><b>Person</b></b> При использовании системы внедрения зависимостей (Dependency Injection), класс <b><b>Person</b></b> просто запрашивает объект <b><b>Job</b></b> в конструкторе. Angular инстанциирует зависимость и передает результат классу. При использовании системы внедрения зависимостей Angular сделает это за вас. Также внедрение зависимостей (Dependency Injection) значительно упрощает тестирование, так как для этого необходимо просто подать "ложные" зависимости (Mock Dependecies) в параметры конструктора Предположим, что у нас есть существующий <b><b>UnitConverterService</b></b> и мы хотели бы использовать его в <b><b>UnitConversionComponent</b></b>. Для этого необходимо проделать 3 простых шага: Мы помечаем класс декоратором <b><b>@Injectable()</b></b>, что позволяет Angular понять, что этот класс будет использован в системе внедрения зависимостей. Если сервисный класс отмечен декоратором @Injectable(), он может запрашивать другие сервисы в конструкторе Передайте все экспортируемые (injectable) зависимости в секцию <b>providers</b> вашего <b>NgModule</b> Теперь этот сервис становится доступным для всех <b>компонентов</b> и других сервисов в <b>NgModule</b>. Благодаря <b><b> private</b></b> модификатору доступа, сервис становится доступным через класс как <b><b>this.converter</b></b> На следующем слайде вы используете (и внедрите) videoService, в котором будет еще больше котиков!!! Результат будет выглядеть вот так: [(ngModel)] - <b><b>Banana in the box</b></b> — мнемоника для такого порядка скобок У Angular очень выразительная система шаблонов, основанная на HTML, которую можно расширить новыми элементами В двойные фигурные скобки записывается нужное свойство компонента Обратные кавычки <b><b>` `</b></b> — волшебные кавычки, позволяющие делать переносы строк и использовать строковую интерполяцию Также можно использовать простые выражения: вы можете вызвать метод компонента (как fullName() ниже) или вычислить <code><code>323213+34234</code></code> На следующем слайде отредактируйте шаблон компонента, чтобы создать простой заголовок и форму поиска. Результат должен выглядеть следующим образом: Строковая интерполяция <b><b>{{ curlies }}{{ curlies }}</b></b> также позволяет передавать значения в атрибуты дочерних элементов Но лучше использовать property binding (привязку свойств) <b><b>[attribute]="property"</b></b> Angular поддерживает более продвинутые привязки свойств, чем просто имя атрибута Это условное выражение добавляет или удаляет DOM элемент по условию На следующем слайде добавьте обработчик нажатий кнопки поиска и отображение сообщения для случая, когда ни одно видео не было найдено. Результат должен выглядеть следующим образом: Допустим, у нас есть список щенков, и мы хотим отобразить их на странице. Для этого в Angular есть специальный синтаксис — <b><b>*ngFor</b></b> Давайте посмотрим, как это работает на следующем слайде Для каждого щенка в массиве всех щенков<b><b>*ngFor</b></b> повторяет тот HTML-элемент, в котором и указан (в этом случае li) HTML-атрибуты в <b><b>Angular</b></b> регистрозависимы: <b><b><s><s>*ngfor</s></s></b></b> не сработает, а <b><b>*ngFor</b></b> сработает На следующем слайде вы, наконец, покажете видео! Результат будет выглядеть следующим образом: <b><b>JavaScript</b></b> – отличный язык, однако, при его использовании есть некоторая специфика: <b><b>ES</b></b> расшифровывается как <a><a><b><b>ECMAScript</b></b></a></a> - стандарт языка JavaScript. Кроме этого, TypeScript расширяет систему типов и декораторов Декораторы выглядят как @twitter_handles, мы изучим их позже Ниже приведена функция <b><b>add</b></b>, где мы складываем 2 + 2. Что может пойти не так? Оказывается, можно передать строку в качестве параметра и получить <b><b>22</b></b> вместо <b><b>4</b></b>. Давайте посмотрим, как TypeScript поможет нам это предотвратить. В TypeScript для указания типа используется "<b><b>:</b></b>" (например, <b><b>n: number</b></b>). Как <b><b>a</b></b>, так и <b><b>b</b></b> должны быть числами. Мы указали тип <b><b>a</b></b>, теперь ваша очередь! Код выше можно редактировать Здесь каждый элемент в массиве <b><b>betterCats</b></b> — экземпляр интерфейса <b><b>Cat</b></b>. Они похожи на классы в других языках программирования и используются для группировки методов и свойств В классе есть специальный метод: <b><b>constructor</b></b>, который вызывается при создании класса и позволяет записать полученные параметры в свойства класса. Параметры конструктора, отмеченные как <b><b> public </b></b> (или private, или protected), записываются в свойства класса. Они будут доступны как <b><b> this.ParameterName </b></b> внутри класса. Свойства 'private' или 'protected' не могут быть использованы вне класса. Возможно, вы заметили оператор <b><b> export </b></b> перед классом. Он используется для того, чтобы сделать класс доступным в других файлах. На следующем слайде мы покажем вам, как импортировать и использовать тот класс в другом файле. "<b><b>filter</b></b>" - метод массива, который позволяет генерировать новый массив, выбрав только значения, удовлетворяющие условию TypeScript поддерживает множество других интересных и полезных штук, например: Стрелочные функции Мы не будем рассматривать их подробно, вы можете ознакомиться с ними на сайте <a><a> TypeScript </a></a> Ваша задача - создать TypeScript класс и назвать его CodeLab. При создании он получит список гостей. Также мы создадим метод, который выведет список тех, кто придет. ES7 Декораторы Типы TypeScript Классы Модули Далее... Используйте клавиши <b><b></b></b> и <b><b></b></b> на клавиатурe для навигации по слайдам. Необходимые знания Опишите ошибку, или поделитесь идеями Отлично Хорошо Норм Не очень Оцените этот урок ... Узнайте как быстро начать работу над вашим Angular приложением Дерево компонентов Научитесь выстраивать взаимосвязь между компонентами Формы Добавьте несложные формы в ваше приложение Material Design Узнайте как использовать библиотеку Angular Material Маршрутизация Узнайте как добавить маршрутизацию в ваше приложение Этот курс написан на Angular и <a><a>доступен на Github</a></a>. Пожалуйста, поставьте ⭐, если вам понравилось! :) Привет, я - <b><b>angular-cli</b></b>, я инструмент для командой строки! Прежде всего убедитесь, что на вашем компьютере есть node.js. Откройте командную строку и введите: <code><code>node -v</code></code> Если увидите ошибку, следуйте <a><a>инструкциям по настройке Node.js</a></a> Перейдите в папку с только что созданным приложением и запустите его командой <code><code>ng serve</code></code> Когда приложение запустится, откройте <a><a>http://localhost:4200/</a></a> в вашем браузере Вы можете сгенерировать новые компоненты командой <code><code>ng generate component my-component</code></code> Вы также можете генерировать модули, сервисы и пайпы Можно использовать упрощенную версию: <code><code>ng g c my-component</code></code> У нас есть несложная форма, давайте разберемся как привязать ее значения к параметрам компонента Сначала необходимо добавить <b><b>FormsModule</b></b> в наш NgModule Далее мы можем использовать ngModel, чтобы привязать поля ввода к соответствующим параметрам компонента. Пропробуйте поменять значение полей ввода, чтобы обновить значения Давайте сделаем поле "username" обязательным (<b><b>required</b></b>) Теперь отобразим валидационную ошибку. Для этого нужно проделать следующее: Получить доступ к модели (ngModel) поля ввода с помощью <b><b>#name="ngModel"</b></b> Использовать свойство <b><b> errors </b></b> у свойства <b><b>usernameModel</b></b> Попробуйте очистить поле username, чтобы увидеть ошибку Ниже представлен список <a><a>встроеных валидаторов</a></a>, которые используются в Angular min - Минимальное значение (для чисел) max - Максимальное значение (для чисел) required - Обязательное значение requiredTrue - Обязательное правдивое значение для флага email - Соответствие регулярному выражению email'а minLength - Минимальная длина текстового поля maxLength - Максимальная длина текстового поля pattern - Регулярное выражение для текстового поля Можно также создавать собственные валидаторы, узнайте больше: <a><a>здесь</a></a> Осталось решить небольшую проблему: если не указывать изначальные значение полей ввода, ошибка будет отображена сразу Мы можем проверить, взаимодействовал ли пользователь с полем ввода с помощью свойства <b><b>touched</b></b>. Значение <b><b>touched</b></b> положительно, если юзер сфокусировался на поле ввода и после убрал фокус, не меняя значение. Попробуйте добавить/убрать фокус с поля ввода, или добавить/удалить значение, чтобы увидеть ошибку. Теперь давайте сделаем нашу форму красивой с помощью Material Design. Основные строительные блоки: <b><b>mat-form-field</b></b> - Обертка вокруг поля ввода <b><b>matInput</b></b> - Аттрибут должен быть добавлен на поле ввода <b><b>mat-error</b></b> - Умная обертка для ошибок Обратите внимание, что нам больше не нужно добавлять <b><b>#name</b></b> на поле ввода. На следующем слайде будет упражнение, в котором мы добавим форму на страницу загрузки. Обратите внимание: вам нужно будет вручную нажать на кнопку upload, чтобы увидеть результат Есть два основных способа работы с формами в Angular. Пока раздел <b><b>Продвинутых форм</b></b> в разработке, прочитайте <a><a> документацию Angular </a></a> Быстрые Гибкие в использовании Поддерживают Accessibility(доступность в использовании) Оптимизированы для Angular Отлично выглядят на мобильных устройствах Вы можете посмотреть список компонентов <a><a>здесь</a></a> Добавьте <b><b>MatToolbarModule</b></b> в импорты Используйте компонент в шаблоне Обратите внимание, что анимации в Angular вынесены в отдельный модуль. Мы добавляем <b><b>NoopAnimationsModule</b></b> здесь, но могли бы использовать <b><b>BrowserAnimationsModule</b></b> вместо того, чтобы получить полноценные анимации. Теперь давайте добавим material card Заголовок и картинка Добавим пару кнопок Обратите внимание, что мы используем аттрибут <b><b>mat-button</b></b> вместо отдельного тега. Indigo Deep purple Pink Purple На следующем слайде будет упражнение. Мы применим Angular Material к нашему приложению. Примечание: В результате наше приложение не будет выглядеть так прекрасно как могло бы, но мы работаем над этим. Котятки Щеночки Настройте машрутизацию, привязав компоненты к соответствующим URL Создайте меню Выберите место, где отобразить выбранный компонент Потом передадим конфигурацию в наш модуль Обратите внимание, что мы используем <b><b>RouterModule.forRoot</b></b>, который превращает нашу конфигурацию в Модуль Angular. Теперь выберем место, где роутер отобразит выбранный компонент. Мы можем сделать это положив тег <b><b>router-outlet</b></b> в нужное место в нужном компоненте Осталось создать меню Используйте директиву <b><b>routerLink</b></b> чтобы передать ссылку На следующем слайде будет упражнение, в котором мы добавим два роута: <b><b>Search</b></b> и <b><b>Upload</b></b> и меню для переключения между ними Мы уже создали пустой компонент Upload и SearchComponent, содержащий логику для поиска. Изучите <a><a>Angular Router Guide</a></a> чтобы подняться на более продвинутый уровень. Без системы внедрения зависимостей, вам придется разбираться со всеми параметрами самостоятельно. Добро пожаловать в интерактивный курс по Angular. Здесь вы сможете выучить основы <a><a>Angular</a></a>! app.module.ts: Добавьте MatCardModule и MatToolbarModule в свойство "imports". app.html: Добавьте material toolbar, содержащий значение свойства title video/video.component.html: Используйте material card, чтобы отобразить дату video/video.component.html: Добавьте mat-card-title (внутри of the mat-card), чтобы отобразить заголовок видео (title) video/video.component.html: Добавьте mat-card-subtitle (внутри mat-card), чтобы отобразить описание (description) video/video.component.html: Добавьте к картинке (img) аттрибут mat-card-image и ее ширина увеличится до ширины mat-card video/video.component.html: Перенесите дату/информацию о просмотрах/лайках в mat-card-content (внутри mat-card) app.module.ts: Используя RouterModule.forRoot, передайте пустой массив в модуль app.module.ts: Добавьте роут с пустым ('') путем, отображающий SearchComponent app.module.ts: Добавьте роут с путем ('upload'), отображающий UploadComponent app.html: Добавьте router-outlet app.html: Добавьте пункт меню с текстом "Search", ведущий на "/" app.html: Добавьте пункт меню с текстом "Upload", ведущий на "/upload" Следующий слайд Показать только следующий шаг Введение Установка Создание нового приложение на Angular Запускаем приложение Генерируем компоненты Упражнение 1 Узнайте, как настроить роутинг в вашем Angular приложении Шаг 1 Декораторы Упражнение Шаг 2 К разделу "Шаблоны" Пример зависимости Сравнение Шаг 3 Узнайте, как комбинировать компоненты Простая форма NgModel Валидация Отображение ошибки валидации Валидаторы Свойства Touched & Dirty Material инпуты (бонус!) Теперь сделаем наше приложение более привлекательным при помощи <a><a>Angular Material</a></a> ✨✨ MatCard Заголовок MatCard MatButton Темы оформления Узнайте, как создавать простые формы в Angular Конфигурация Меню Узнайте, как создавать красивые интерфейсы с помощью Angular Material Узнайте, как использовать Angular Dependency Injection Система типов Классы Узнайте, как создать ваше первое Angular приложение! Angular CLI — инструмент для командной строки, который можно использовать для ускорения работы с вашим Angular приложением Передача данных от родительского компонента к дочернему Начнем с создания Angular <b><b> Component </b></b>'а. Компоненты в Angular отвечают за визуальную часть приложения. Как и компонент, модуль в <b><b>Angular</b></b> - это просто класс 👍(JavaScript class) Как и компонент, модуль в <b><b>Angular</b></b> оборачивается в декоратор, в котором задается конфигурация модуля При помощи готовых директив, включенных в Angular, можно легко добавить валидацию инпутов Если вы очистите инпут, он будет помечен ошибкой, однако при этом никакой ошибки показано не будет. На следующем слайде мы узнаем, как их отображать. Прочитайте больше о темах Angular Material <a><a>в этом руководстве</a></a> Роуты настраиваются с помощью создания списка, привязывающего URL-адресами к соответствующим компонентам router-outlet В этом примере, <b><b>realPuppy</b></b> — экземпляр интерфейса <b><b>Puppy</b></b>. Angular-cli Ошибка всегда показана Angular написан на TypeScript, который, в свою очередь, является расширением JavaScript. Узнайте больше о TypeScript. Angular написан на TypeScript. Узнайте больше про основы языка. JavaScript не поддерживает строгую типизацию, что затрудняет разработку крупных приложений TypeScript добавляет новую функциональность из следующей версии JavaScript node позволяет легко создавать работающее приложение прямо из коробки и генерировать новые компоненты! Он также настраивает конфигурацию сборки. Далее: Angular Material предоставляет набор Angular компонентов в стиле <a><a>Material Design</a></a>, которые имеют ряд преимуществ: MatToolBar Смотрите также <a><a>руководство для начала работы с Angular Material</a></a> Таким образом, наш компонент будет зависеть от VideoService Далее... Показать все шаги Декораторы в <b><b> Angular </b></b> добавляют специальную информацию к классу. Условное отображение (*ngIf) Этот курс написан на Angular Поставьте нам ⭐ если вы выучили что-то полезное Не надо меня бояться, меня легко использовать Если в результате увидите число(номер версии), то все в порядке, переходите на следующий слайд. Декораторы в TypeScript были созданы по образу и подобию аналогичной функциональности в языке Python. Мы показываем захардкоженый список видеозаписей в нашем компоненте, но в реальном приложении мы бы загрузили их с сервера Код для получения данных был бы выделен в отдельный класс (сервис) с именем VideoService С ростом нашего приложения, увеличивается и число зависимостей. В свою очередь, зависимости будут обрастать собственными зависимостями. Держать это все под контролем вручную становится все сложнее и сложнее Чтобы упростить это, у <b><b>Angular</b></b> есть механизм, который называется <b><b>Dependency injection</b></b> app.module.ts: Добавьте FormsModule и MatInputModule в свойство "imports". upload/upload.component.html: Добавьте поле ввода "title" и привяжите его к свойству title компонента upload/upload.component.html: Добавьте текстовое поле (textarea) "description" и привяжите к свойству description компонента Значение <b><b>dirty</b></b> положительно, если пользователь изменил значение текстового поля. Узнайте, как начать работу с Angular приложением в разделе "Angular-cli" Все компоненты Angular Material позволяют применять темы. Попробуйте различные темы, нажимая на кнопки ниже: Router is used to give <b><b>URLs</b></b> to different parts of your app. Новая функциональность последних версий стандарта JavaScript часто может не поддерживаться в некоторых браузерах Поэтому был создан <b><b>TypeScript</b></b>. Так как TypeScript может быть скомпилирован в JavaScript, он поддерживает все версии современных браузеров. TypeScript расширяет последнюю версию JavaScript Интерфейсы в Typescript позволяют указывать, какие свойства и методы должны быть у объекта Типы массивов указываются с помощью <b><b>Array{{ '<' }}{{ '<' }} тип {{ '>' }}{{ '>' }}</b></b> или <b><b>Type[]</b></b> <b><b>import</b></b> и <b><b>export</b></b> предназначены не только для классов. Они работают и с переменными, функциями и другими конструкциями! Accessors (Getters / Setters) Имя Email (не обязателен и будет спрятан) Отправить Исходный код открыт на 100% и доступен на <a><a> Github </a></a> Примитивные типы (string, number, и прочие...) Теперь вы знаете достаточно <b><b>TypeScript</b></b> чтобы начать изучение <b><b>Angular</b></b>! Узнайте больше о TypeScript на <a><a>TypeScript веб-сайт</a></a> Дополнительные возможности привязки событий Мы познакомимся с более удобным способом работы с текстовыми полями в разделе "Формы" Настроить маршрутизацию в Angular можно в 3 шага: Конец раздела "Маршрутизация" Вы можете добавить material toolbar в два шага: Конец раздела "angular-cli" Выполните <code><code>npm install -g @angular/cli</code></code>, чтобы установить <b><b>Angular cli</b></b> на ваш компьютер Чтобы создать новое Angular приложение, выполните: <code><code>ng new awesome-app</code></code>, затем <code><code>cd awesome-app</code></code> Конец раздела "angular-cli" Для обработки действий пользователя можно использовать привязку событий (event-binding). Для этого мы оборачиваем имя события в скобки и передаем выражение-обработчик: В качестве альтернативы использования скобок для обозначения привязки событий (<b><b>(event)</b></b>), может быть использован префикс "on-", например <b><b>on-click</b></b> это то же самое что и <b><b>(click)</b></b>. Angular также предоставляет удобный способ обработки горячих клавиш. Попробуйте обновить сообщение, нажав Control + Enter в текстовом поле. Чтобы получить доступ к HTML элементу или Angular компоненту из шаблона, можно пометить этот элемент как <b><b>#name</b></b>, и он станет доступным как <b><b>name</b></b> во всём шаблоне: ================================================ FILE: apps/codelab/src/locale/messages.xmb ================================================ ]> src/app/components/index/index.component.html:2,4 Create your first Angular app src/app/components/index/index.component.html:5src/app/codelabs/angular/templates/templates.component.html:119Templates src/app/components/index/index.component.html:6,8src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:40 Dependency-Injection src/app/components/index/index.component.html:9Component-Tree src/app/components/index/index.component.html:10Custom-Events src/app/components/index/index.component.html:11,14 Angular is written in TypeScript, a superset of JavaScript. Learn TypeScript. src/app/components/index/index.component.html:15,17 Learn how to create and bootstrap your first Angular application src/app/components/index/index.component.html:18,20 Learn how to use Angular templates src/app/components/index/index.component.html:21,23 Learn how to provide dependencies to your code instead of hard-coding them src/app/components/index/index.component.html:27,29 Learn how to structure your app with reusable components src/app/components/index/index.component.html:30,32 Learn to bind to events. src/app/components/index/index.component.html:40Angular Codelab src/app/components/index/index.component.html:41,44 Welcome to the interactive Angular Codelab. Here you can learn the basics of <a><a>Angular</a></a>! src/app/components/index/index.component.html:52Learn TypeScript src/app/components/index/index.component.html:53Skip if you're familiar with TypeScript src/app/components/index/index.component.html:59Learn Angular src/app/components/index/index.component.html:68,70 Or click here to see full contents... src/app/components/slides/title-slide/title-slide.component.html:6,8 Use <b><b></b></b> and <b><b></b></b> on your keyboard to navigate the slides. src/app/components/slides/title-slide/title-slide.component.html:11Prerequisites: src/app/components/slides/closing-slide/codelab-closing-slide.component.html:5,10 This codelab is written in Angular and <a><a>available on Github</a></a>. Please ⭐ if you enjoyed it. ../../libs/slides/src/lib/arrows/slides-arrows.component.html:12Next slide ../../libs/feedback/src/lib/feedback-widget/feedback-widget.component.html:41Enter your name ../../libs/feedback/src/lib/feedback-widget/feedback-widget.component.html:52,53Email is optional and, will not displayed ../../libs/feedback/src/lib/feedback-widget/feedback-widget.component.html:57Describe your issue or share your ideas ../../libs/feedback/src/lib/feedback-widget/feedback-widget.component.html:70,72 Send ../../libs/feedback/src/lib/feedback-rating/feedback-rating.component.html:2Perfect ../../libs/feedback/src/lib/feedback-rating/feedback-rating.component.html:3good ../../libs/feedback/src/lib/feedback-rating/feedback-rating.component.html:4ok ../../libs/feedback/src/lib/feedback-rating/feedback-rating.component.html:5Hoped for more ../../libs/feedback/src/lib/feedback-rating/feedback-rating.component.html:12Rate this lesson ... src/app/components/buttons-nav-bar/menu-github-widget/menu-github-widget.component.html:9This Codelab is written in Angular! src/app/components/buttons-nav-bar/menu-github-widget/menu-github-widget.component.html:10,15 It's 100% open-source and is available on <a><a> Github </a></a> src/app/components/buttons-nav-bar/menu-github-widget/menu-github-widget.component.html:16Please ⭐ the repo if you find it useful. src/app/codelabs/angular/typescript/typescript/typescript.component.html:2,4 Or use shorthand function notation. src/app/codelabs/angular/typescript/typescript/typescript.component.html:5,7 Error: this is clearly not a puppy src/app/codelabs/angular/typescript/typescript/typescript.component.html:8This is a number src/app/codelabs/angular/typescript/typescript/typescript.component.html:9,11 Or use shorthand function notation. src/app/codelabs/angular/typescript/typescript/typescript.component.html:12,14 (Also called arrow function) src/app/codelabs/angular/typescript/typescript/typescript.component.html:15,17 Actually TypeScript can infer number here; src/app/codelabs/angular/typescript/typescript/typescript.component.html:18,20 TypeScript can infer it's a string. src/app/codelabs/angular/typescript/typescript/typescript.component.html:21,23 Can't add number and boolean src/app/codelabs/angular/typescript/typescript/typescript.component.html:24Can't slice a number src/app/codelabs/angular/typescript/typescript/typescript.component.html:25But can slice a string! src/app/codelabs/angular/typescript/typescript/typescript.component.html:26Works! src/app/codelabs/angular/typescript/typescript/typescript.component.html:27,29 Type[] does the same thing. src/app/codelabs/angular/typescript/typescript/typescript.component.html:30This is a method. src/app/codelabs/angular/typescript/typescript/typescript.component.html:31,33 That's how russian dogs talk. src/app/codelabs/angular/typescript/typescript/typescript.component.html:34,36 Now we can instantiate (create) it src/app/codelabs/angular/typescript/typescript/typescript.component.html:37And use its methods src/app/codelabs/angular/typescript/typescript/typescript.component.html:38,40 Later we'll have code here src/app/codelabs/angular/typescript/typescript/typescript.component.html:41,43 Let's create more puppies src/app/codelabs/angular/typescript/typescript/typescript.component.html:44,46 Var is still allowed but not recommended. src/app/codelabs/angular/typescript/typescript/typescript.component.html:47,49 Let should be used instead of var. src/app/codelabs/angular/typescript/typescript/typescript.component.html:53,55 Unlike var let is unavailable outside of this if. src/app/codelabs/angular/typescript/typescript/typescript.component.html:56,58 Const is like let, but if you try to change it, TS will give you an error. src/app/codelabs/angular/typescript/typescript/typescript.component.html:59,61 okay, definitely a boolean src/app/codelabs/angular/typescript/typescript/typescript.component.html:62,64 Create a class called 'Codelab' src/app/codelabs/angular/typescript/typescript/typescript.component.html:65src/app/codelabs/angular/create-first-app/create-first-app.component.html:24Export the class src/app/codelabs/angular/typescript/typescript/typescript.component.html:66Add a constructor src/app/codelabs/angular/typescript/typescript/typescript.component.html:70,72 Make constructor take a parameter 'guests' src/app/codelabs/angular/typescript/typescript/typescript.component.html:73,76 Specify the type for the guests parameter (hint: it's an array of a type Guest) src/app/codelabs/angular/typescript/typescript/typescript.component.html:77,80 Make the parameter public (note that now you can access it anywhere in the class using this.guests) src/app/codelabs/angular/typescript/typescript/typescript.component.html:84,86 Create new method 'getGuestsComing' src/app/codelabs/angular/typescript/typescript/typescript.component.html:88,91 "b" in the code below is highlighted, because TypeScript is missing the type. Specify the type for b. src/app/codelabs/angular/typescript/typescript/typescript.component.html:95,98 With this information TypeScript can highlight the error. Fix it, make 2 + 2 = 4 again! src/app/codelabs/angular/typescript/typescript/typescript.component.html:103,106 Modify getGuestsComing to filter the guests array return an array of guests with the 'coming' property set to true. src/app/codelabs/angular/typescript/typescript/typescript.component.html:115TypeScript src/app/codelabs/angular/typescript/typescript/typescript.component.html:117Angular is written in TypeScript. Learn more about the language basics. src/app/codelabs/angular/typescript/typescript/typescript.component.html:119Basic understanding of JavaScript is required. src/app/codelabs/angular/typescript/typescript/typescript.component.html:125Why TypeScript src/app/codelabs/angular/typescript/typescript/typescript.component.html:126,128 <b><b>JavaScript</b></b> is a great language, but there's space for improvement: src/app/codelabs/angular/typescript/typescript/typescript.component.html:130,133 JS is not type safe which makes it harder to develop large scale applications src/app/codelabs/angular/typescript/typescript/typescript.component.html:134,137 New features of the latest versions of JS standards (ES2018, ES2019) are not supported well across all the browsers src/app/codelabs/angular/typescript/typescript/typescript.component.html:139,143 <b><b>ES</b></b> stands for <a><a><b><b>ECMAScript</b></b></a></a>, which is the name of the JavaScript language specification (standard) src/app/codelabs/angular/typescript/typescript/typescript.component.html:147TypeScript src/app/codelabs/angular/typescript/typescript/typescript.component.html:148,151 This is why <b><b>TypeScript</b></b> has been created. Since TypeScript can be compiled to JavaScript, it can be used in any modern browser. src/app/codelabs/angular/typescript/typescript/typescript.component.html:155TypeScript extends the latest version of JavaScript src/app/codelabs/angular/typescript/typescript/typescript.component.html:156,158 TypeScript adds new features from the next version of JavaScript src/app/codelabs/angular/typescript/typescript/typescript.component.html:159,161 On top of it, TypeScript adds an optional type system and decorators src/app/codelabs/angular/typescript/typescript/typescript.component.html:168,170 Decorator looks like @twitter_handles, we'll learn more about them later src/app/codelabs/angular/typescript/typescript/typescript.component.html:180src/app/codelabs/angular/typescript/typescript/typescript.component.html:200Type System src/app/codelabs/angular/typescript/typescript/typescript.component.html:181,184 Below we have an <b><b>add</b></b> function, and we're adding 2 and 2. What could go wrong? src/app/codelabs/angular/typescript/typescript/typescript.component.html:191,195 Turns out it's possible to pass a string to this function and we get <b><b>22</b></b> instead of <b><b>4</b></b>. Let's see how TypeScript can help address this issue on the next slide src/app/codelabs/angular/typescript/typescript/typescript.component.html:201,205 TypeScript uses "<b><b>:</b></b>" to specify the type information (e.g. <b><b>n: number</b></b>). Both <b><b>a</b></b> and <b><b>b</b></b> should be numbers. We specified the type for <b><b>a</b></b>, now it's your turn! src/app/codelabs/angular/typescript/typescript/typescript.component.html:211The code above is editable! src/app/codelabs/angular/typescript/typescript/typescript.component.html:216,217Primitives (strings, numbers, etc...) src/app/codelabs/angular/typescript/typescript/typescript.component.html:218Below are more types we can use src/app/codelabs/angular/typescript/typescript/typescript.component.html:232Interfaces src/app/codelabs/angular/typescript/typescript/typescript.component.html:233,236 TypeScript Interfaces allow to specify properties and methods for an object. src/app/codelabs/angular/typescript/typescript/typescript.component.html:246,248 Here, <b><b>realPuppy</b></b> is an implementation of the <b><b>Puppy</b></b> Interface. src/app/codelabs/angular/typescript/typescript/typescript.component.html:252Arrays src/app/codelabs/angular/typescript/typescript/typescript.component.html:253,256 Array types are defined as <b><b>Array{{ '<' }}{{ '<' }}Type{{ '>' }}{{ '>' }}</b></b> or <b><b>Type[]</b></b> src/app/codelabs/angular/typescript/typescript/typescript.component.html:263,266 Here, each element in the <b><b>betterCats</b></b> array is an instance of the <b><b>Cat</b></b> Interface. src/app/codelabs/angular/typescript/typescript/typescript.component.html:271Classes src/app/codelabs/angular/typescript/typescript/typescript.component.html:272TypeScript has <b><b>classes</b></b>, and Angular uses them heavily. src/app/codelabs/angular/typescript/typescript/typescript.component.html:273,276 They are similar to classes in other languages, and are used to group methods and properties together src/app/codelabs/angular/typescript/typescript/typescript.component.html:286Constructor src/app/codelabs/angular/typescript/typescript/typescript.component.html:287,290 There's a special method on the class called <b><b>constructor</b></b>. It's run when the class is instantiated and allows the class to take parameters src/app/codelabs/angular/typescript/typescript/typescript.component.html:300Access Modifiers src/app/codelabs/angular/typescript/typescript/typescript.component.html:301,305 Constructor parameters marked as <b><b>public</b></b> (or private, or protected), become class properties accessible as <b><b>this.ParameterName</b></b> within the class src/app/codelabs/angular/typescript/typescript/typescript.component.html:312,314 private or protected properties are not visible outside of the class. src/app/codelabs/angular/typescript/typescript/typescript.component.html:318Export src/app/codelabs/angular/typescript/typescript/typescript.component.html:319,323 By the way, did you notice the <b><b>export</b></b> keyword before class? It is used to share information between files. In the next slide, we'll show you how to import and use this class in a different file src/app/codelabs/angular/typescript/typescript/typescript.component.html:338Import src/app/codelabs/angular/typescript/typescript/typescript.component.html:339Now we can use the <b><b>Puppy</b></b> class in the other file src/app/codelabs/angular/typescript/typescript/typescript.component.html:347,350 <b><b>import</b></b> and <b><b>export</b></b> keywords are not just for classes. They work with variables, functions and other things! src/app/codelabs/angular/typescript/typescript/typescript.component.html:354Filter (One last thing) src/app/codelabs/angular/typescript/typescript/typescript.component.html:355,358 "<b><b>filter</b></b>" is an Array method that allows you to generate a new array keeping only values that satisfy the condition src/app/codelabs/angular/typescript/typescript/typescript.component.html:366More src/app/codelabs/angular/typescript/typescript/typescript.component.html:367TypeScript supports lots of other cool features such as: src/app/codelabs/angular/typescript/typescript/typescript.component.html:374Enums src/app/codelabs/angular/typescript/typescript/typescript.component.html:382Async / Await src/app/codelabs/angular/typescript/typescript/typescript.component.html:390Accessors (Getters / Setters) src/app/codelabs/angular/typescript/typescript/typescript.component.html:398Destructuring src/app/codelabs/angular/typescript/typescript/typescript.component.html:406Arrow functions src/app/codelabs/angular/typescript/typescript/typescript.component.html:409And more! src/app/codelabs/angular/typescript/typescript/typescript.component.html:411,415 We won't cover them in detail, check out the <a><a>TypeScript</a></a> website! src/app/codelabs/angular/typescript/typescript/typescript.component.html:419src/app/codelabs/angular/create-first-app/create-first-app.component.html:196src/app/codelabs/angular/create-first-app/create-first-app.component.html:304src/app/codelabs/angular/create-first-app/create-first-app.component.html:417src/app/codelabs/angular/templates/templates.component.html:170src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:278src/app/codelabs/angular/router/router.component.html:125src/app/codelabs/angular/material/material.component.html:181src/app/codelabs/angular/forms/forms.component.html:183Exercise src/app/codelabs/angular/typescript/typescript/typescript.component.html:420In the next slide we have a TypeScript exercise src/app/codelabs/angular/typescript/typescript/typescript.component.html:421,425 Your task is to build a TypeScript class called Codelab which will take a list of guests, and will have a method to output only the ones who are coming. src/app/codelabs/angular/typescript/typescript/typescript.component.html:426The result will be as follows: src/app/codelabs/angular/typescript/typescript/typescript.component.html:446src/app/codelabs/angular/templates/templates.component.html:377src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:303src/app/codelabs/angular/component-tree/component-tree.component.html:266Milestone Completed src/app/codelabs/angular/typescript/typescript/typescript.component.html:449,453 Now you should know enough <b><b>TypeScript</b></b> to start learning <b><b>Angular</b></b>! Read more about TypeScript on <a><a>TypeScript web site</a></a> src/app/codelabs/angular/typescript/typescript/typescript.component.html:459src/app/codelabs/angular/create-first-app/create-first-app.component.html:510src/app/codelabs/angular/templates/templates.component.html:382src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:308src/app/codelabs/angular/router/router.component.html:168src/app/codelabs/angular/material/material.component.html:227src/app/codelabs/angular/forms/forms.component.html:224Next: src/app/codelabs/angular/typescript/typescript/typescript.component.html:460,462 Learn how to create your first Angular app! src/app/codelabs/angular/typescript/typescript/typescript-svg/typescript-svg.component.html:66,68 ES7 src/app/codelabs/angular/typescript/typescript/typescript-svg/typescript-svg.component.html:78,80 Decorators src/app/codelabs/angular/typescript/typescript/typescript-svg/typescript-svg.component.html:90,92 Types src/app/codelabs/angular/typescript/typescript/typescript-svg/typescript-svg.component.html:102,104 TypeScript src/app/codelabs/angular/typescript/typescript/typescript-svg/typescript-svg.component.html:114,116 Classes src/app/codelabs/angular/typescript/typescript/typescript-svg/typescript-svg.component.html:126,128 Modules src/app/codelabs/angular/typescript/typescript/typescript-svg/typescript-svg.component.html:138,140 More... src/app/components/tests/simple-tests.component.html:27see only next step src/app/components/tests/simple-tests.component.html:28see all steps src/app/codelabs/angular/create-first-app/create-first-app.component.html:2,4 @Component is an Angular decorator src/app/codelabs/angular/create-first-app/create-first-app.component.html:5,7 No semicolon here (as it attaches itself to the class below src/app/codelabs/angular/create-first-app/create-first-app.component.html:8,10 The Decorator goes directly above the decorated entity (class in this case) src/app/codelabs/angular/create-first-app/create-first-app.component.html:11,13 Component name is the class name (AppComponent). src/app/codelabs/angular/create-first-app/create-first-app.component.html:14,16 Create a class called 'AppComponent' src/app/codelabs/angular/create-first-app/create-first-app.component.html:17,19 Create a class called 'AppModule' src/app/codelabs/angular/create-first-app/create-first-app.component.html:20,22 All set! Bootstrap your application src/app/codelabs/angular/create-first-app/create-first-app.component.html:25,27 Add a Component decorator for the class src/app/codelabs/angular/create-first-app/create-first-app.component.html:28,30 Add a selector to the component decorator and set it to 'my-app' src/app/codelabs/angular/create-first-app/create-first-app.component.html:31,33 Add a template that contains: h1 with a text "Hello MewTube!" src/app/codelabs/angular/create-first-app/create-first-app.component.html:34,36 Add a NgModule decorator for the class src/app/codelabs/angular/create-first-app/create-first-app.component.html:37,39 Add 'BrowserModule' to the NgModule decorator imports src/app/codelabs/angular/create-first-app/create-first-app.component.html:43,45 Add 'AppComponent' to the 'declarations' property of the decorator src/app/codelabs/angular/create-first-app/create-first-app.component.html:46,48 Add 'AppComponent' to the 'bootstrap' property of the decorator src/app/codelabs/angular/create-first-app/create-first-app.component.html:56Create your first Angular app src/app/codelabs/angular/create-first-app/create-first-app.component.html:58You will learn how to create your first Angular component, put it in a module, and bootstrap the app. src/app/codelabs/angular/create-first-app/create-first-app.component.html:60Knowing TypeScript basics would help a lot src/app/codelabs/angular/create-first-app/create-first-app.component.html:67What is Angular? src/app/codelabs/angular/create-first-app/create-first-app.component.html:69,75 Angular is a <b><b>development platform</b></b> for building mobile and desktop applications. Angular lets you <b><b>extend HTML's syntax</b></b> to express your application's components clearly and succinctly. Angular's binding and Dependency Injection <b><b>eliminate much of the code</b></b> you would otherwise have to write. src/app/codelabs/angular/create-first-app/create-first-app.component.html:81src/app/codelabs/angular/create-first-app/create-first-app.component.html:97src/app/codelabs/angular/templates/templates.component.html:129src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:107src/app/codelabs/angular/router/router.component.html:39src/app/codelabs/angular/router/router.component.html:61src/app/codelabs/angular/angular-cli/angular-cli.component.html:16Intro src/app/codelabs/angular/create-first-app/create-first-app.component.html:82Given an HTML file: src/app/codelabs/angular/create-first-app/create-first-app.component.html:88,91 Let's create an Angular app which replaces the <b><b>hello-world</b></b> HTML element with the app's contents. src/app/codelabs/angular/create-first-app/create-first-app.component.html:92This can be done with 3 simple steps. src/app/codelabs/angular/create-first-app/create-first-app.component.html:98The 3 steps are: src/app/codelabs/angular/create-first-app/create-first-app.component.html:100Create an Angular component src/app/codelabs/angular/create-first-app/create-first-app.component.html:101Create an Angular module src/app/codelabs/angular/create-first-app/create-first-app.component.html:102Bootstrap the module src/app/codelabs/angular/create-first-app/create-first-app.component.html:108src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:224Step 1 src/app/codelabs/angular/create-first-app/create-first-app.component.html:109,112 Start by creating an Angular <b><b>Component</b></b>. Components in Angular are responsible for the visual part of the app src/app/codelabs/angular/create-first-app/create-first-app.component.html:118,121 An Angular component is just a class. Properties and behavior can be added inside. src/app/codelabs/angular/create-first-app/create-first-app.component.html:126src/app/codelabs/angular/create-first-app/create-first-app.component.html:142Decorators src/app/codelabs/angular/create-first-app/create-first-app.component.html:134The class is adorned with a <b><b>@Component</b></b> decorator src/app/codelabs/angular/create-first-app/create-first-app.component.html:135,137 Decorators attach <b><b>Angular</b></b> specific information to the class. src/app/codelabs/angular/create-first-app/create-first-app.component.html:143,146 Decorators are a new feature of TypeScript. They attach metadata to a class, function, property or variable src/app/codelabs/angular/create-first-app/create-first-app.component.html:153,156 TypeScript decorators are inspired by a similar feature in the Python language. src/app/codelabs/angular/create-first-app/create-first-app.component.html:161Selector src/app/codelabs/angular/create-first-app/create-first-app.component.html:162,166 Selectors define the location of the component. When Angular renders this component, it'll find a <b><b>hello-world</b></b> HTML element in the document and render the component inside of it src/app/codelabs/angular/create-first-app/create-first-app.component.html:179Inline Template src/app/codelabs/angular/create-first-app/create-first-app.component.html:180Template defines the HTML code that the component generates src/app/codelabs/angular/create-first-app/create-first-app.component.html:188,191 If the amount of HTML grows out of hand, it's possible (and recommended) to use a <b><b>templateUrl</b></b> instead and provide a path to the HTML file. src/app/codelabs/angular/create-first-app/create-first-app.component.html:197,200 In the next slide you'll create your first <b><b>Angular</b></b> component! We'll do all the wiring for you. The result will look like this: src/app/codelabs/angular/create-first-app/create-first-app.component.html:216Create first Angular component! src/app/codelabs/angular/create-first-app/create-first-app.component.html:222src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:242Step 2 src/app/codelabs/angular/create-first-app/create-first-app.component.html:223Next step is to declare the component in an <b><b>NgModule</b></b>. src/app/codelabs/angular/create-first-app/create-first-app.component.html:224,227 <b><b>NgModule</b></b> does not have any visual representation and is used exclusively for grouping Angular building blocks together src/app/codelabs/angular/create-first-app/create-first-app.component.html:228,230 We will learn more about NgModules in the future milestones src/app/codelabs/angular/create-first-app/create-first-app.component.html:235Module Class src/app/codelabs/angular/create-first-app/create-first-app.component.html:236Like a component, <b><b>Angular</b></b> module is just a class src/app/codelabs/angular/create-first-app/create-first-app.component.html:249NgModule Decorator src/app/codelabs/angular/create-first-app/create-first-app.component.html:250,253 Like a component, <b><b>Angular</b></b> module is adorned with a decorator providing metadata src/app/codelabs/angular/create-first-app/create-first-app.component.html:266Browser Module src/app/codelabs/angular/create-first-app/create-first-app.component.html:272Declarations src/app/codelabs/angular/create-first-app/create-first-app.component.html:273,276 The <b><b>Declarations array</b></b> specifies components belonging to the AppModule src/app/codelabs/angular/create-first-app/create-first-app.component.html:288Bootstrap src/app/codelabs/angular/create-first-app/create-first-app.component.html:289,292 The component passed into the <b><b>bootstrap</b></b> array will be created and displayed in your <b><b>index.html</b></b> file src/app/codelabs/angular/create-first-app/create-first-app.component.html:305,309 In the next slide you'll create your first <b><b>Angular</b></b> module! We'll use the component from the previous exercises and do all the wiring for you. The result will look like this: src/app/codelabs/angular/create-first-app/create-first-app.component.html:324Create first NgModule. src/app/codelabs/angular/create-first-app/create-first-app.component.html:330Bootstrapping src/app/codelabs/angular/create-first-app/create-first-app.component.html:331,333 We have everything ready, so now it's time to start (bootstrap) the app! src/app/codelabs/angular/create-first-app/create-first-app.component.html:334,337 Passing your <b><b>AppModule</b></b> to the <b><b>bootstrapModule</b></b> method will start up all the components from that module's bootstrap section src/app/codelabs/angular/create-first-app/create-first-app.component.html:344,346 For most simple apps, you can just copy/paste the code above "as is" src/app/codelabs/angular/create-first-app/create-first-app.component.html:350Bootstrapping 1 src/app/codelabs/angular/create-first-app/create-first-app.component.html:351How does bootstrapping work in Angular? src/app/codelabs/angular/create-first-app/create-first-app.component.html:353,356 1. Kicks off execution environment. <b><b>platformBrowserDynamic()</b></b> tells Angular that we are operating in the browser src/app/codelabs/angular/create-first-app/create-first-app.component.html:367,368src/app/codelabs/angular/create-first-app/create-first-app.component.html:388,389src/app/codelabs/angular/create-first-app/create-first-app.component.html:411,412Read more about root module and bootstrapping in Angular src/app/codelabs/angular/create-first-app/create-first-app.component.html:373Bootstrapping 2 src/app/codelabs/angular/create-first-app/create-first-app.component.html:374,377 2. Angular initializes the component from the <b><b>bootstrap</b></b> array in <b><b>app.module.ts</b></b> (<b><b>HelloWorldComponent</b></b> in this case) src/app/codelabs/angular/create-first-app/create-first-app.component.html:394Bootstrapping 3 src/app/codelabs/angular/create-first-app/create-first-app.component.html:395,399 3. Angular looks in the document for an element matching the selector defined in <b><b>HelloWorldComponent</b></b> (<b><b>'hello-world'</b></b> in our case) and inserts the component inside that element src/app/codelabs/angular/create-first-app/create-first-app.component.html:418,420 All set! In the next page you'll bootstrap your first <b><b>Angular</b></b> app! src/app/codelabs/angular/create-first-app/create-first-app.component.html:436,439 Now that we've got both NgModule and the component ready, let's bootstrap the app! src/app/codelabs/angular/create-first-app/create-first-app.component.html:447src/app/codelabs/angular/component-tree/component-tree.component.html:201Review src/app/codelabs/angular/create-first-app/create-first-app.component.html:448Loading order: index -> main -> app.module -> app.component src/app/codelabs/angular/create-first-app/create-first-app.component.html:494,497 While Angular is loading, the contents of the element will stay the same (<b><b>Loading...</b></b>) in this case src/app/codelabs/angular/create-first-app/create-first-app.component.html:503End of Bootstrap Section src/app/codelabs/angular/create-first-app/create-first-app.component.html:506src/app/codelabs/angular/router/router.component.html:159src/app/codelabs/angular/material/material.component.html:214src/app/codelabs/angular/forms/forms.component.html:217Well done! This is the end of the milestone! src/app/codelabs/angular/create-first-app/create-first-app.component.html:511,513 Go to the templates Milestone src/app/codelabs/angular/create-first-app/mode/mode.component.html:2,5 Because we're building a browser web app, we need to pass <b><b>BrowserModule</b></b> to the <b><b>imports</b></b> array src/app/codelabs/angular/create-first-app/mode/mode.component.html:26With Angular we build mobile apps using NativeScript or Ionic. src/app/codelabs/angular/create-first-app/mode/mode.component.html:42With Angular you can build VR apps with A-FRAME or WEBGL. src/app/codelabs/angular/create-first-app/mode/mode.component.html:57,70 <div><div> Angular is not just for web apps anymore; you can also use it to create native phone apps and even VR scenes. </div></div> <div><div> <a><a></a></a> </div></div> src/app/codelabs/angular/templates/templates.component.html:2,4 This is valid HTML syntax. src/app/codelabs/angular/templates/templates.component.html:5,7 It works on attribute syntax. src/app/codelabs/angular/templates/templates.component.html:11,13 It allows to conditionally bind a class src/app/codelabs/angular/templates/templates.component.html:14Or style properties src/app/codelabs/angular/templates/templates.component.html:15,17 And works with custom components! src/app/codelabs/angular/templates/templates.component.html:21,24 When user clicks the button, it calls the "saveUser" function on the component instance and passes the underlying event. src/app/codelabs/angular/templates/templates.component.html:28,32 You can also create events for custom components. Here we have a depleted event, and it's going to call the "soundAlarm" function on the component instance when it fires. src/app/codelabs/angular/templates/templates.component.html:36,40 There are also shortcut event bindings! The submit function on the component instance will be called when the user presses control and enter (this is an Angular feature). src/app/codelabs/angular/templates/templates.component.html:41,43 userName has a reference to the input element src/app/codelabs/angular/templates/templates.component.html:44,46 Try changing to true! src/app/codelabs/angular/templates/templates.component.html:47,49 Need to repeat puppies here src/app/codelabs/angular/templates/templates.component.html:50,52 app.component.ts: Add a 'videos' property, set the value as empty array. src/app/codelabs/angular/templates/templates.component.html:53,56 app.component.ts: Inside of the 'search' method assign FAKE_VIDEOS, to the component 'videos' property. src/app/codelabs/angular/templates/templates.component.html:57,60 app.html: Add an H1 header, display the 'title' property of the AppComponent inside src/app/codelabs/angular/templates/templates.component.html:62,65 app.component.ts: Add a 'search' method on the component, that takes a 'searchString' parameter. src/app/codelabs/angular/templates/templates.component.html:69,73 app.html: Add a click handler to the button, call 'search' method and pass the input value (Actual search functionality will be implemented in the next exercise) src/app/codelabs/angular/templates/templates.component.html:74,77 app.html: Add a message saying 'no videos' which is displayed only when the videos array is empty src/app/codelabs/angular/templates/templates.component.html:82,85 #Bonus app.component.ts: Right now it takes pressing a search button to display the videos. Instead display all videos by default. src/app/codelabs/angular/templates/templates.component.html:89,93 app.component.ts: Inside of the 'search' method filter FAKE_VIDEOS and only return videos with the title containing searchString. (hint: use .includes or .indexOf string methods) src/app/codelabs/angular/templates/templates.component.html:94,96 app.html: Also display a thumbnail src/app/codelabs/angular/templates/templates.component.html:100,103 app.html: Iterate over the videos using '*ngFor', and display a title for each src/app/codelabs/angular/templates/templates.component.html:105,107 app.html: Add a button tag with a text 'search' src/app/codelabs/angular/templates/templates.component.html:108,110 app.html: Add an input tag with a 'placeholder' attribute set to 'video' src/app/codelabs/angular/templates/templates.component.html:121Learn more about Angular templates! src/app/codelabs/angular/templates/templates.component.html:130,133 Angular has a very expressive template system, which takes HTML as a base, and extends it with custom elements src/app/codelabs/angular/templates/templates.component.html:142src/app/codelabs/angular/templates/templates.component.html:158Interpolation src/app/codelabs/angular/templates/templates.component.html:143,145 Double curlies include the appropriate component property value src/app/codelabs/angular/templates/templates.component.html:150,153 Backticks <b><b>` `</b></b>, are magic quotes that allow multi-line strings and text interpolation. src/app/codelabs/angular/templates/templates.component.html:159,162 Simple expressions are also allowed, you can run a component method (like fullName() below), or calculate <code><code>323213+34234</code></code> src/app/codelabs/angular/templates/templates.component.html:171,174 In the next slide you'll edit a component template to create a simple header and search form. The result will look like this: src/app/codelabs/angular/templates/templates.component.html:193Properties src/app/codelabs/angular/templates/templates.component.html:194,197 String interpolation <b><b>{{ curlies }}{{ curlies }}</b></b> can also be used to pass a value to a child element's attribute src/app/codelabs/angular/templates/templates.component.html:206Property Binding src/app/codelabs/angular/templates/templates.component.html:207,209 Better option is to use property binding <b><b>[attribute] = property</b></b> src/app/codelabs/angular/templates/templates.component.html:214,216 You can use arbitrary expressions in the binding. src/app/codelabs/angular/templates/templates.component.html:221Data binding extras src/app/codelabs/angular/templates/templates.component.html:222,224 Angular supports more advanced property bindings than just attribute name src/app/codelabs/angular/templates/templates.component.html:233Event binding: (event) src/app/codelabs/angular/templates/templates.component.html:234,238 For handling user actions we can use event bindings. To do that we wrap the event name in parentheses, and pass an expression performing required action: src/app/codelabs/angular/templates/templates.component.html:245,248 While parentheses are used for event binding: <b><b>(event)</b></b>, "on-" can also be used, e.g. <b><b>on-click</b></b> is the same as <b><b>(click)</b></b>. src/app/codelabs/angular/templates/templates.component.html:252src/app/codelabs/angular/templates/templates.component.html:270Event binding shortcuts src/app/codelabs/angular/templates/templates.component.html:253,257 When we need to access an HTML element or an Angular component from the template, we can mark it with <b><b>#name</b></b>, and it becomes available as <b><b>name</b></b> everywhere in the template: src/app/codelabs/angular/templates/templates.component.html:264,266 We'll learn a better way to work with inputs in Forms milestone. src/app/codelabs/angular/templates/templates.component.html:271,274 Angular also provides a shortcut for handling keyboard shortcuts. Try updating the message by pressing Control + Enter in the input. src/app/codelabs/angular/templates/templates.component.html:285Conditional Display (*ngIf) src/app/codelabs/angular/templates/templates.component.html:286,289 This conditional expression will add or remove an element from the DOM if it evaluates as a truthy src/app/codelabs/angular/templates/templates.component.html:297src/app/codelabs/angular/component-tree/component-tree.component.html:244Exercise 2 src/app/codelabs/angular/templates/templates.component.html:298,302 In the next slide you'll add a click handler to the search button, and display a message for the case where no videos were found. The result will look like this: src/app/codelabs/angular/templates/templates.component.html:320Repeating elements src/app/codelabs/angular/templates/templates.component.html:321,325 Let's say you have an array of puppies, and want to display all of them on the page. Angular has a special syntax for that called <b><b>*ngFor</b></b>, let's see how it works on the next slide src/app/codelabs/angular/templates/templates.component.html:333Repeating elements (*ngFor) src/app/codelabs/angular/templates/templates.component.html:334,337 Here <b><b>*ngFor</b></b> repeats HTML element it's attached to (li in this case) for every single puppy in the puppies array src/app/codelabs/angular/templates/templates.component.html:343,346 HTML attributes in <b><b>Angular</b></b> are case sensitive: <b><b><s><s>*ngfor</s></s></b></b> won't work, <b><b>*ngFor</b></b> will src/app/codelabs/angular/templates/templates.component.html:350Exercise 3 src/app/codelabs/angular/templates/templates.component.html:353,356 In the next slide you'll finally display the videos! The result will look like this: src/app/codelabs/angular/templates/templates.component.html:383,385 Learn how to use Angular Dependency Injection src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:5,7 * TypeScript shorthand makes 'profession' * available to component instance. src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:8,10 assuming Job has property '.title' src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:11,13 video.service.ts: Add @Injectable() decorator to the class src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:15,17 app.module.ts: Add VideoService to the NgModule providers property src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:18,20 app.component.ts: Get rid of FAKE_VIDEOS src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:21,24 app.component.ts: Inject 'VideoService' in the component constructor as 'videoService' src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:28,31 app.component.ts: Update the app component's search method to use videoService's search method src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:42Learn more about Angular's powerful Dependency Injection system src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:50Example of a dependency src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:51,54 We display a list of hard-coded videos in our component, but in the real world we'd load them from the server. src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:55,58 The code for fetching the data would live in a separate class (service) called VideoService src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:59Therefore our component will depend on the VideoService: src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:109,113 As our app grows, number of dependencies will increase. Dependencies, in order, will have their own dependencies. Managing all of them manually becomes increasingly harder. src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:114,117 To simplify that <b><b>Angular</b></b> provides a mechanism called <b><b>Dependency injection</b></b> src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:124Comparison src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:133,136 Without Dependency Injection, <b><b>Profession</b></b> has to be instantiated in the <b><b>Person</b></b> class src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:145,149 With Dependency Injection, <b><b>Person</b></b> class just "requires" an instance of <b><b>Job</b></b> in the constructor, and Angular takes care of instantiating it src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:155Parameters src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:164,167 Without Dependency Injection, you have to figure out all the parameters yourself src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:176,178 With Dependency Injection, Angular takes care of it src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:184Testing src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:185,188 Also Dependency Injection simplifies testing a lot, because you can just pass mock dependencies as constructor parameters src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:209Example src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:210,214 Let's say we have an existing <b><b>UnitConverterService</b></b> and we want to start using it in <b><b>UnitConversionComponent</b></b>. It will take 3 simple steps: src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:216Mark dependency as @Injectable() src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:217Provide in the module src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:218Require in the component src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:225,228 Mark the class as <b><b>@Injectable()</b></b>. This lets Angular know that this class is part of Angular Dependency Injection system src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:234,237 If a service class is marked as injectable, it can require other services in its constructor. src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:243,245 Provide the injectable to the <b><b>providers</b></b> section of <b><b>NgModule</b></b> src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:254,257 Now, this service becomes available for every <b><b>Component</b></b> and other service in this <b><b>NgModule</b></b>. src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:262Step 3 src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:263Consume the Injectable in the component src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:271,274 Because of the <b><b>private</b></b> access modifier the service becomes accessible across the class as <b><b>this.converter</b></b>. src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:279,282 In the next slide you'll use videoService which has even more cats!!! The result will look like this: src/app/codelabs/angular/dependency-injection/dependency-injection.component.html:309,311 Learn how to combine components together src/app/codelabs/angular/component-tree/component-tree.component.html:5,8 video/video.component.ts: Add the '@Component' decorator and set its selector property to 'my-video'. src/app/codelabs/angular/component-tree/component-tree.component.html:9,11 app.module.ts: Add VideoComponent to the AppModule 'declarations'. src/app/codelabs/angular/component-tree/component-tree.component.html:16,18 video/video.component.ts: Set the templateUrl to load the appropriate html file src/app/codelabs/angular/component-tree/component-tree.component.html:22,24 video/video.component.ts: Add a video property and decorate it with @Input() src/app/codelabs/angular/component-tree/component-tree.component.html:25,27 video/video.component.html: Display the video title src/app/codelabs/angular/component-tree/component-tree.component.html:28,30 video/video.component.html: Display the video thumbnail (src) src/app/codelabs/angular/component-tree/component-tree.component.html:31,33 video/video.component.html: Display the video description src/app/codelabs/angular/component-tree/component-tree.component.html:34,36 video/video.component.html: Display the video date src/app/codelabs/angular/component-tree/component-tree.component.html:37,39 video/video.component.html: Display the number of video views src/app/codelabs/angular/component-tree/component-tree.component.html:40,42 video/video.component.html: Display the number video likes src/app/codelabs/angular/component-tree/component-tree.component.html:43,46 app.html: Replace existing title and thumbnail with our shiny new my-video component src/app/codelabs/angular/component-tree/component-tree.component.html:50,53 app.html: Use the data binding to pass the video object to the component (don't forget the square brackets) src/app/codelabs/angular/component-tree/component-tree.component.html:67src/app/codelabs/angular/component-tree/component-tree.component.html:76Component Tree src/app/codelabs/angular/component-tree/component-tree.component.html:69Combine components together src/app/codelabs/angular/component-tree/component-tree.component.html:77,80 So far we have only one component, but as your app grows it will form a tree of components src/app/codelabs/angular/component-tree/component-tree.component.html:86Parent and Child src/app/codelabs/angular/component-tree/component-tree.component.html:87,90 Any component can render another one by using an HTML element that matches the selector of the other component src/app/codelabs/angular/component-tree/component-tree.component.html:121src/app/codelabs/angular/component-tree/component-tree.component.html:138Passing Data from Parent to Child src/app/codelabs/angular/component-tree/component-tree.component.html:123,125 Parent component passes its data to the child component via properties src/app/codelabs/angular/component-tree/component-tree.component.html:126,129 Change the <b><b>size</b></b> to <b><b>100</b></b> and <b><b>color</b></b> to <b><b>red</b></b> to recreate the Japanese flag. src/app/codelabs/angular/component-tree/component-tree.component.html:140,143 The child class must decorate its properties with a special <b><b>@Input()</b></b> decorator src/app/codelabs/angular/component-tree/component-tree.component.html:148,151 This is the first time we're applying decorators to properties (as opposed to classes). src/app/codelabs/angular/component-tree/component-tree.component.html:155Exercise 1 src/app/codelabs/angular/component-tree/component-tree.component.html:156,159 We already know how to create a component. Let's move all the video-related information into a new component called VideoComponent. src/app/codelabs/angular/component-tree/component-tree.component.html:160,162 We will bootstrap the component for you; the result will be as follows: src/app/codelabs/angular/component-tree/component-tree.component.html:165Cute kitten src/app/codelabs/angular/component-tree/component-tree.component.html:188Parent and Child component src/app/codelabs/angular/component-tree/component-tree.component.html:189,192 Components won't know about each other unless they're declared in the same module src/app/codelabs/angular/component-tree/component-tree.component.html:245In the next exercise you will use the newly created component src/app/codelabs/angular/component-tree/component-tree.component.html:271Next: src/app/codelabs/angular/component-tree/component-tree.component.html:272,274 Learn how to set up routing in your Angular app src/app/codelabs/angular/router/router.component.html:2,5 app.module.ts: Using RouterModule.forRoot provide an empty array to the module src/app/codelabs/angular/router/router.component.html:6,9 app.module.ts: Add a route with an empty (\'\') path to display SearchComponent src/app/codelabs/angular/router/router.component.html:10,12 app.module.ts: Add a route with path of "upload" to display UploadComponent src/app/codelabs/angular/router/router.component.html:13,15 app.html: Add router-outlet src/app/codelabs/angular/router/router.component.html:16,18 app.html: Add a menu item with a text Search pointing to "/" src/app/codelabs/angular/router/router.component.html:19,21 app.html: Add a menu item with a text Upload pointing to "/upload" src/app/codelabs/angular/router/router.component.html:32Routing src/app/codelabs/angular/router/router.component.html:33Learn how to add routing to your app src/app/codelabs/angular/router/router.component.html:40,42 Router is used to give <b><b>URLs</b></b> to different parts of your app. src/app/codelabs/angular/router/router.component.html:49Kittens src/app/codelabs/angular/router/router.component.html:56Puppies src/app/codelabs/angular/router/router.component.html:62It takes 3 steps to set up routing in Angular: src/app/codelabs/angular/router/router.component.html:64,67 Configure the mapping and specify which component to display for which URL src/app/codelabs/angular/router/router.component.html:68Create the menu src/app/codelabs/angular/router/router.component.html:69Find a place to display selected component src/app/codelabs/angular/router/router.component.html:74src/app/codelabs/angular/router/router.component.html:86Configuration src/app/codelabs/angular/router/router.component.html:75,78 Routes are configured by defining an array of mapping between URL path and a component src/app/codelabs/angular/router/router.component.html:87Then we have to pass the config to our module. src/app/codelabs/angular/router/router.component.html:92,95 Note that we're using <b><b>RouterModule.forRoot</b></b> which creates an Angular Module out of our configuration. src/app/codelabs/angular/router/router.component.html:99router-outlet src/app/codelabs/angular/router/router.component.html:100,103 Now we have to find a place where the router would display selected component. src/app/codelabs/angular/router/router.component.html:104It can be marked by placing <b><b>router-outlet tag</b></b> src/app/codelabs/angular/router/router.component.html:114Menu src/app/codelabs/angular/router/router.component.html:115The last part is creating the menu. src/app/codelabs/angular/router/router.component.html:116Use <b><b>routerLink</b></b> directive to provide the URL src/app/codelabs/angular/router/router.component.html:126,129 In the next slide there is an exercise. We're going to add 2 routes: <b><b>Search</b></b> and <b><b>Upload</b></b>, and a menu for switching between them. src/app/codelabs/angular/router/router.component.html:135,138 Note: We have already created empty Upload component and SearchComponent containing search logic. src/app/codelabs/angular/router/router.component.html:155,156End of The Routing Milestone src/app/codelabs/angular/router/router.component.html:160,164 Check out <a><a>Angular Router Guide</a></a> for more fun and advanced techniques. src/app/codelabs/angular/router/router.component.html:169,171 Learn how to create beautiful UIs with Angular Material src/app/codelabs/angular/material/material.component.html:3,5 app.module.ts: Add MatCardModule and MatToolbarModule, to the imports. src/app/codelabs/angular/material/material.component.html:6,8 app.html: Add material toolbar containing the title src/app/codelabs/angular/material/material.component.html:9,11 video/video.component.html: Use material card to display the data src/app/codelabs/angular/material/material.component.html:12,15 video/video.component.html: Add mat-card-title (inside of the mat-card) to display video title src/app/codelabs/angular/material/material.component.html:16,19 video/video.component.html: Add mat-card-subtitle (inside of the mat-card) to display video description src/app/codelabs/angular/material/material.component.html:20,23 video/video.component.html: Mark img with mat-card-image attribute (inside of the mat-card) so that it takes full card size src/app/codelabs/angular/material/material.component.html:24,27 video/video.component.html: move date/views/likes info inside of mat-card-content (inside of the mat-card) src/app/codelabs/angular/material/material.component.html:39Material design src/app/codelabs/angular/material/material.component.html:40Learn how to use Angular Material src/app/codelabs/angular/material/material.component.html:45,49 Now let's use <a><a>Angular Material</a></a> to make our app look beautiful ✨✨ src/app/codelabs/angular/material/material.component.html:50,54 Angular Material provides you with a set of <a><a>Material Design</a></a> components for Angular: src/app/codelabs/angular/material/material.component.html:58Fast and Consistent src/app/codelabs/angular/material/material.component.html:59Versatile src/app/codelabs/angular/material/material.component.html:60Accessible src/app/codelabs/angular/material/material.component.html:61Optimized for Angular src/app/codelabs/angular/material/material.component.html:62Look great on mobile src/app/codelabs/angular/material/material.component.html:65,72 See the list of components <a><a>here</a></a> src/app/codelabs/angular/material/material.component.html:76MatToolBar src/app/codelabs/angular/material/material.component.html:77Adding a toolbar takes 2 steps: src/app/codelabs/angular/material/material.component.html:79Add <b><b>MatToolbarModule</b></b> to the imports src/app/codelabs/angular/material/material.component.html:80Use the component in the template src/app/codelabs/angular/material/material.component.html:87,91 Note that Angular animations come in a separate module. We're including <b><b>NoopAnimationsModule</b></b> here, but could have used <b><b>BrowserAnimationsModule</b></b> instead, to have full-fledged animations. src/app/codelabs/angular/material/material.component.html:95Card src/app/codelabs/angular/material/material.component.html:96Now let's add material card. src/app/codelabs/angular/material/material.component.html:104Card Header src/app/codelabs/angular/material/material.component.html:105Header and an image. src/app/codelabs/angular/material/material.component.html:113Buttons src/app/codelabs/angular/material/material.component.html:114Adding some actions.... src/app/codelabs/angular/material/material.component.html:119,122 Note that for the button we're using <b><b>mat-button</b></b> attribute instead of a separate tag. src/app/codelabs/angular/material/material.component.html:126Themes src/app/codelabs/angular/material/material.component.html:127,130 All Angular Material components allow to apply themes. Try different themes by clicking the buttons below: src/app/codelabs/angular/material/material.component.html:138,140 Indigo src/app/codelabs/angular/material/material.component.html:147,149 Deep purple src/app/codelabs/angular/material/material.component.html:156,158 Pink src/app/codelabs/angular/material/material.component.html:165,167 Purple src/app/codelabs/angular/material/material.component.html:174,177 Read more on theming Angular Material <a><a>in this guide</a></a> src/app/codelabs/angular/material/material.component.html:182,185 In the next slide there is an exercise. We're going to materialize our application src/app/codelabs/angular/material/material.component.html:191,193 Note: the final app won't be super beautiful, we're still working on that. src/app/codelabs/angular/material/material.component.html:210,211End of The Material Milestone src/app/codelabs/angular/material/material.component.html:215,223 Check out Angular Material <a><a>"Getting Started"</a></a> Guide src/app/codelabs/angular/material/material.component.html:228,230 Learn how to create simple forms with Angular src/app/codelabs/angular/forms/forms.component.html:3,5 app.module.ts: Add FormsModule and MatInputModule, to the imports. src/app/codelabs/angular/forms/forms.component.html:6,9 upload/upload.component.html: Add "title" input bound to the title property of the component src/app/codelabs/angular/forms/forms.component.html:10,13 upload/upload.component.html: Add "description" textarea bound to the description property of the component src/app/codelabs/angular/forms/forms.component.html:24Forms src/app/codelabs/angular/forms/forms.component.html:25Adding basic forms into your app src/app/codelabs/angular/forms/forms.component.html:30src/app/codelabs/angular/forms/forms.component.html:41Simple Form src/app/codelabs/angular/forms/forms.component.html:31,34 We have this simple form, let's find out how to map input values with the ones from our Angular component! src/app/codelabs/angular/forms/forms.component.html:42First we have to add <b><b>FormsModule</b></b> to our NgModule. src/app/codelabs/angular/forms/forms.component.html:51NgModel src/app/codelabs/angular/forms/forms.component.html:52,55 Now let's use ngModel to bind the inputs to the appropriate fields on the component. src/app/codelabs/angular/forms/forms.component.html:56Try changing the inputs and see the values updated. src/app/codelabs/angular/forms/forms.component.html:60,63 [(NgModel)] - <b><b>Banana in the box</b></b> is a simple mnemonic for the braces order. src/app/codelabs/angular/forms/forms.component.html:67Validation src/app/codelabs/angular/forms/forms.component.html:68,71 It's really easy to add validation for your inputs using predefined Angular directives. src/app/codelabs/angular/forms/forms.component.html:72Let's make username <b><b>required</b></b> src/app/codelabs/angular/forms/forms.component.html:76,79 If you clear the input it'll be marked with an error, however no error will be displayed. We'll learn how to display it in the next slide src/app/codelabs/angular/forms/forms.component.html:83Displaying Validation Errors src/app/codelabs/angular/forms/forms.component.html:84Now we let's display validation error. It takes 2 steps: src/app/codelabs/angular/forms/forms.component.html:86,88 Get ahold of the input ngModel using <b><b>#name="ngModel"</b></b> binding src/app/codelabs/angular/forms/forms.component.html:89Use <b><b>usernameModel</b></b>'s <b><b>errors</b></b> property. src/app/codelabs/angular/forms/forms.component.html:94,96 Try clearing the username input and see the error displayed. src/app/codelabs/angular/forms/forms.component.html:99Validators src/app/codelabs/angular/forms/forms.component.html:100,106 Here's the list of <a><a>built-in validators</a></a> that Angular provides out of the box src/app/codelabs/angular/forms/forms.component.html:108min src/app/codelabs/angular/forms/forms.component.html:109max src/app/codelabs/angular/forms/forms.component.html:110required src/app/codelabs/angular/forms/forms.component.html:111requiredTrue src/app/codelabs/angular/forms/forms.component.html:112email src/app/codelabs/angular/forms/forms.component.html:113minLength src/app/codelabs/angular/forms/forms.component.html:114maxLength src/app/codelabs/angular/forms/forms.component.html:115pattern src/app/codelabs/angular/forms/forms.component.html:117,123 It's also possible to create custom validators, but it's out of scope for this milestone, you can read more: <a><a>here</a></a> src/app/codelabs/angular/forms/forms.component.html:127Error Always Displayed src/app/codelabs/angular/forms/forms.component.html:128,131 There's one small issue though, if we don't have initial values, the error is displayed right away. src/app/codelabs/angular/forms/forms.component.html:139Touched & Dirty src/app/codelabs/angular/forms/forms.component.html:140,143 Luckily we can check if the user interacted with an input using <b><b>touched</b></b> property. src/app/codelabs/angular/forms/forms.component.html:145,147 <b><b>dirty</b></b> is true if the user changed the value of the input. src/app/codelabs/angular/forms/forms.component.html:148,151 <b><b>touched</b></b> is true if the user focused on the input, and then blured without changing the value. src/app/codelabs/angular/forms/forms.component.html:156,159 Try focusing/bluring the <b><b>username</b></b> field, or adding some value and then removing to see the error. src/app/codelabs/angular/forms/forms.component.html:163Material Inputs (Bonus!) src/app/codelabs/angular/forms/forms.component.html:164,167 Now let's make our forms beautiful using Material Design inputs! Main building blocks are: src/app/codelabs/angular/forms/forms.component.html:169<b><b>mat-form-field</b></b> - Wraps the input src/app/codelabs/angular/forms/forms.component.html:170<b><b>matInput</b></b> - has to be put on the input src/app/codelabs/angular/forms/forms.component.html:171<b><b>mat-error</b></b> - Smarter error wrapper src/app/codelabs/angular/forms/forms.component.html:177,179 Note that we don't need to <b><b>#name</b></b> the input anymore. src/app/codelabs/angular/forms/forms.component.html:184,187 In the next slide there is an exercise. We're going to add a form to the upload page. src/app/codelabs/angular/forms/forms.component.html:195,198 Note: you'd have to manually click on the "upload link" to see the result, we're working on the fix. src/app/codelabs/angular/forms/forms.component.html:214End of The Forms Milestone src/app/codelabs/angular/forms/forms.component.html:218,222 There are more than one way to handle forms in Angular. While <b><b>Advanced forms</b></b> milestone is in works, check out <a><a>Angular docs</a></a> src/app/codelabs/angular/forms/forms.component.html:225,228 Learn how to start working on your angular app in the Angular-cli Milestone src/app/codelabs/angular/angular-cli/angular-cli.component.html:9Angular-cli src/app/codelabs/angular/angular-cli/angular-cli.component.html:11Learn how to quickly start working on your Angular app src/app/codelabs/angular/angular-cli/angular-cli.component.html:17,20 Angular CLI is a command line tool that can be used to quickly get up to speed with running your Angular app. src/app/codelabs/angular/angular-cli/angular-cli.component.html:24,26 Hi, I'm <b><b>angular-cli</b></b>, I'm a command line tool! src/app/codelabs/angular/angular-cli/angular-cli.component.html:27 It may sound scary, but I'm really easy to use! src/app/codelabs/angular/angular-cli/angular-cli.component.html:33node src/app/codelabs/angular/angular-cli/angular-cli.component.html:34Before you start, make sure you have node.js on your machine. src/app/codelabs/angular/angular-cli/angular-cli.component.html:35Open terminal and type in: <code><code>node -v</code></code> src/app/codelabs/angular/angular-cli/angular-cli.component.html:37,40 If there is an error, follow the <a><a>NodeJS setup instructions</a></a> src/app/codelabs/angular/angular-cli/angular-cli.component.html:41,43 If the output is a number, you're good, move to the next slide. src/app/codelabs/angular/angular-cli/angular-cli.component.html:51installing src/app/codelabs/angular/angular-cli/angular-cli.component.html:52,55 Run <code><code>npm install -g @angular/cli</code></code> to install <b><b>Angular cli</b></b> on your machine src/app/codelabs/angular/angular-cli/angular-cli.component.html:81Creating new Angular app src/app/codelabs/angular/angular-cli/angular-cli.component.html:82,85 To create a new Angular app run: <code><code>ng new awesome-app</code></code>, then <code><code>cd awesome-app</code></code> src/app/codelabs/angular/angular-cli/angular-cli.component.html:107Starting the app src/app/codelabs/angular/angular-cli/angular-cli.component.html:108,110 Once you're in the app folder, start the app with <code><code>ng serve</code></code> src/app/codelabs/angular/angular-cli/angular-cli.component.html:132,136 Once the app is started just open <a><a>http://localhost:4200/</a></a> in your browser src/app/codelabs/angular/angular-cli/angular-cli.component.html:140Generating components src/app/codelabs/angular/angular-cli/angular-cli.component.html:141,144 You can easily generate new components <code><code>ng generate component my-component</code></code> src/app/codelabs/angular/angular-cli/angular-cli.component.html:157You can also generate modules, services and pipes src/app/codelabs/angular/angular-cli/angular-cli.component.html:158,160 You can use a shorter version: <code><code>ng g c my-component</code></code> src/app/codelabs/angular/angular-cli/angular-cli.component.html:167,168End of The Angular-cli Milestone src/app/codelabs/angular/angular-cli/angular-cli.component.html:171,175 This is <b><b>the end</b></b> of the codelab, but it's just the beginning of your Angular journey. Below are some links that can help you continue learning. src/app/codelabs/angular/angular-cli/angular-cli.component.html:179Find features, docs and events listed here src/app/codelabs/angular/angular-cli/angular-cli.component.html:183,187 makes it easy to create an application that already works, right out of the box and generate new components! It also takes care of the build system for you ================================================ FILE: apps/codelab/src/main.ts ================================================ import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { environment } from './environments/environment'; import { AppModule } from './app/app.module'; if (environment.production) { enableProdMode(); } platformBrowserDynamic().bootstrapModule(AppModule); ================================================ FILE: apps/codelab/src/manifest.webmanifest ================================================ { "name": "Codelab", "short_name": "Codelab", "theme_color": "#1976d2", "background_color": "#fafafa", "display": "standalone", "scope": "/", "start_url": "/", "icons": [ { "src": "assets/icons/icon-72x72.png", "sizes": "72x72", "type": "image/png" }, { "src": "assets/icons/icon-96x96.png", "sizes": "96x96", "type": "image/png" }, { "src": "assets/icons/icon-128x128.png", "sizes": "128x128", "type": "image/png" }, { "src": "assets/icons/icon-144x144.png", "sizes": "144x144", "type": "image/png" }, { "src": "assets/icons/icon-152x152.png", "sizes": "152x152", "type": "image/png" }, { "src": "assets/icons/icon-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "assets/icons/icon-384x384.png", "sizes": "384x384", "type": "image/png" }, { "src": "assets/icons/icon-512x512.png", "sizes": "512x512", "type": "image/png" } ] } ================================================ FILE: apps/codelab/src/polyfills.ts ================================================ import 'fullscreen-api-polyfill'; import 'zone.js/dist/zone'; // Included with Angular CLI. import '@angular/localize/init'; // Needed for babel :( (window as any).Buffer = {}; (window as any).process = { env: { DEBUG: undefined }, argv: { indexOf() { return 0; } }, getuid() { return 0; } }; ================================================ FILE: apps/codelab/src/service-worker.js ================================================ ================================================ FILE: apps/codelab/src/styles.scss ================================================ @import '~@angular/material/prebuilt-themes/indigo-pink.css'; body, html { margin: 0; padding: 0; height: 100%; } .presentation.size-600 li { font-size: 12px; margin-bottom: 5px; } .space-left { margin-left: 20px; } [row] { flex-direction: row; display: flex; } /* GRID TO USE */ .col-1 { width: 8.33%; } .col-2 { width: 16.66%; } .col-3 { width: 25%; } .col-4 { width: 33.33%; } .col-5 { width: 41.66%; } .col-6 { width: 50%; } .col-7 { width: 58.33%; } .col-8 { width: 66.66%; } .col-9 { width: 75%; } .col-10 { width: 83.33%; } .col-11 { width: 91.66%; } .col-12 { width: 100%; } .presentation.presentation.presentation [no-padding] .slide { padding: 0; } .browser-frame, .runner, .browser-frame, .frame { } .slide [large-font] { font-size: 30px; } .slide [p20] { padding-top: 20px; } .slide [m20] { margin-top: 20px; } .slide [m40] { margin-top: 40px; } .slide [column-5] { width: 50%; padding: 0 1vw; } .slide [column-4] { width: 40%; } .slide [column-2] { width: 20%; } .slide [column-1] { width: 10%; } .slide [flex] { display: flex; } .slide .browser-frame h1 { font-size: 60px; } .slide .browser-frame h2 { font-size: 20px; } /* TODO(kirjs): Support themes */ .slide h1 { font-size: 20px; } .centered-vertically { display: flex; flex-direction: column; flex-grow: 1; justify-content: center; } .popup-message { padding: 5px 5px 0 5px; background: black; color: white; font-size: 20px; } .popup-arrow { width: 0; height: 0; border-left: 0 solid transparent; border-right: 20px solid transparent; border-top: 8px solid black; } .monaco-editor.vs .grayed-out-code.grayed-out-code.grayed-out-code.grayed-out-code { } .monaco-editor.vs .loc.loc.loc.loc { background: yellow; } .monaco-editor.vs .highlighted-code.highlighted-code.highlighted-code.highlighted-code { background: yellow; } p { margin: 0; margin-bottom: 10px; padding: 0; } .info:before { content: 'ⓘ'; margin-right: 10px; } a { color: #a03800; font-weight: 400; } .info { font-weight: 300; margin-top: 20px; background: #d3fffd; padding: 12px; font-size: 30px; } /* BUTTONS BAR CONTAINER */ .btn-bar { z-index: 2; position: fixed; bottom: 1%; right: 4%; display: flex; flex-direction: row; } .exercise { font-size: 30px; padding: 10px; margin-top: 20px; font-weight: 300; border: 1px #ddd solid; } .exercise:before { content: '💡'; display: inline-block; width: 20px; height: 20px; margin-right: 10px; } .exercise.solved { color: #90dd59; } .exercise.solved:before { content: '✔'; background: none; } /* context: https://github.com/Microsoft/monaco-editor/issues/113 */ .monaco-editor .inputarea { position: fixed !important; top: 0 !important; left: 0 !important; } .preload { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); /*change these sizes to fit into your project*/ width: 100px; height: 100px; } .preload div { border: 0; margin: 0; width: 40%; height: 40%; position: absolute; border-radius: 50%; animation: spin 2s ease infinite; } .preload :first-child { background: #19a68c; animation-delay: -1.5s; } .preload :nth-child(2) { background: #f63d3a; animation-delay: -1s; } .preload :nth-child(3) { background: #fda543; animation-delay: -0.5s; } .preload :last-child { background: #193b48; } @keyframes spin { 0%, 100% { transform: translate(0); } 25% { transform: translate(160%); } 50% { transform: translate(160%, 160%); } 75% { transform: translate(0, 160%); } } .mt-60 { margin-top: 60px; } ================================================ FILE: apps/codelab/src/test.ts ================================================ // This file is required by karma.conf.js and loads recursively all the .spec and framework files import 'zone.js/dist/long-stack-trace-zone'; import 'zone.js/dist/proxy.js'; import 'zone.js/dist/sync-test'; import 'zone.js/dist/jasmine-patch'; import 'zone.js/dist/async-test'; import 'zone.js/dist/fake-async-test'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any. declare var __karma__: any; declare var require: any; // Prevent Karma from running prematurely. __karma__.loaded = function() {}; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); // Then we find all the tests. const context = require.context('./', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context); // Finally, start Karma to run the tests. __karma__.start(); ================================================ FILE: apps/codelab/src/typings.d.ts ================================================ /* SystemJS module definition */ declare var module: NodeModule; declare const chai; interface NodeModule { id: string; } ================================================ FILE: apps/codelab/tsconfig.app.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "types": [] }, "files": ["./src/main.ts", "./src/polyfills.ts"], "exclude": ["test.ts", "**/*.spec.ts"] } ================================================ FILE: apps/codelab/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "types": ["jasmine", "node"] }, "angularCompilerOptions": { "enableIvy": true } } ================================================ FILE: apps/codelab/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "types": ["jasmine", "node"] }, "files": ["src/test.ts", "src/polyfills.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: apps/codelab/tslint.json ================================================ { "extends": "../../tslint.json", "linterOptions": { "exclude": ["src/**/*.json"] }, "rules": {} } ================================================ FILE: apps/kirjs/browserslist ================================================ # This file is currently used by autoprefixer to adjust CSS to support the below specified browsers # For additional information regarding the format and rule options, please see: # https://github.com/browserslist/browserslist#queries # # For IE 9-11 support, please remove 'not' from the last line of the file and adjust as needed > 0.5% last 2 versions Firefox ESR not dead not IE 9-11 ================================================ FILE: apps/kirjs/karma.conf.js ================================================ // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html const { join } = require('path'); const getBaseKarmaConfig = require('../../karma.conf'); module.exports = function(config) { const baseConfig = getBaseKarmaConfig(); config.set({ ...baseConfig, coverageIstanbulReporter: { ...baseConfig.coverageIstanbulReporter, dir: join(__dirname, '../../coverage/apps/kirjs') } }); }; ================================================ FILE: apps/kirjs/src/app/app.component.css ================================================ .fire:hover { opacity: 1; cursor: pointer; background: #fff; box-shadow: 0 0 2px #333333; } .fire { opacity: 0.8; position: fixed; right: 10px; top: 10px; background: #ddd; border-radius: 50%; width: 20px; height: 20px; font-size: 12px; text-decoration: none; text-align: center; text-indent: 3px; z-index: 100; } ================================================ FILE: apps/kirjs/src/app/app.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/app.component.spec.ts ================================================ import { TestBed, async } from '@angular/core/testing'; import { AppComponent } from './app.component'; describe('AppComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [AppComponent] }).compileComponents(); })); it('should create the app', async(() => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app).toBeTruthy(); })); it(`should have as title 'kirjs'`, async(() => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app.title).toEqual('kirjs'); })); it('should render title in a h1 tag', async(() => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.debugElement.nativeElement; expect(compiled.querySelector('h1').textContent).toContain( 'Welcome to kirjs!' ); })); }); ================================================ FILE: apps/kirjs/src/app/app.component.ts ================================================ import { Component, HostListener } from '@angular/core'; import { Router } from '@angular/router'; @Component({ selector: 'kirjs-main', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'kirjs'; @HostListener(`window:keydown.meta.'`) HandleLinker() { const key = 'linker'; if (localStorage.getItem(key)) { localStorage.removeItem(key); alert(`Linker removed!`); } else { const path = window.location.pathname; localStorage.setItem(key, path); alert(`Linker stored ${path}!`); } } constructor(private router: Router) { const path: string = localStorage.getItem('linker'); if (path && path !== window.location.pathname) { router.navigate([path]); } } } ================================================ FILE: apps/kirjs/src/app/cv/resume.css ================================================ ul { margin: 0; padding: 0; list-style: none; } ul li { margin: 0; padding: 0; } /*.......| Resume |.......................*/ #resume { max-width: 800px; margin: 0 auto; font-family: verdana, sans-serif; } #resume #header { width: 250px; float: left; } #resume #header .title { width: 90px; } #resume #info { margin-left: 300px; } #resume h1 { font-size: 1.4em; text-transform: uppercase; margin-top: 30px; margin-bottom: 20px; } #resume h2 { color: #900; font-size: 1.4em; font-weight: 400; padding-top: 0px; padding-bottom: 10px; margin-bottom: 0; } #resume h3 { margin: 0; font-size: 0.8em; font-weight: 600; } #resume h4 { font-size: 0.8em; color: #444; font-weight: 400; padding: 0; margin: 0; } #resume a { color: #990000; } #resume .line { font-size: 0.8em; overflow: hidden; padding-top: 12px; } #resume .title { color: #444; width: 130px; display: block; float: left; } #resume dl { line-height: 30px; } #resume dt { width: 190px; float: left; } #resume .spanstart:after { content: ' - '; } #resume .date_duration { font-weight: 400; } #resume .vcard > h1 { font-size: 1.6em; padding: 0; margin: 0; font-weight: 400; padding-bottom: 10px; } #resume .experience { padding-bottom: 20px; } #resume .experience:last-child { padding-bottom: 0; } #resume .experience-header { overflow: hidden; } #resume .experience-hgroup { float: left; } #resume .vcalendar li { font-size: 0.8em; } #resume .description { list-style: circle; color: #666; margin-left: 20px; padding-left: 20px; line-height: 25px; } #resume .skills { overflow: hidden; margin: 0; } #resume .skills li { float: left; } ================================================ FILE: apps/kirjs/src/app/cv/resume.html ================================================

Professional experience

Front-End Developer April 2013 April 2015

Lab49

  • Designed and contributed to the development of an Angular single page app for generating reports
  • Developed Backbone-based form handling framework, for a CRUD Single Page application
  • Organized and led internal hackathons, debates and presentations

System Administrator March 2011 April 2013

International Medical Corps, Washington DC

  • Provided IT support for 50 users
  • Created a single page Backbone app, utilizing YouTube API
  • Developed Active Directory management system with C#

Freelancer March 2011 April 2013

Freelancing

  • Developed WordPress web sites for CIPE, ALA group, Taxpayers for Common Sense, Rosebar Lounge.

Education

Standardization of Technical Devices 2004 2011

Moscow University of Radio Engineering, Moscow RU

================================================ FILE: apps/kirjs/src/app/cv/resume.scss ================================================ ul { li { margin: 0; padding: 0; } margin: 0; padding: 0; list-style: none; } /*.......| Resume |.......................*/ #resume { #header { width: 250px; float: left; .title { width: 90px; } } #info { margin-left: 300px; } h1 { font-size: 1.4em; text-transform: uppercase; margin-top: 30px; margin-bottom: 20px; } h2 { color: #900; font-size: 1.4em; font-weight: 400; padding-top: 0px; padding-bottom: 10px; margin-bottom: 0; } h3 { margin: 0; font-size: 0.8em; font-weight: 600; } h4 { font-size: 0.8em; color: #444; font-weight: 400; padding: 0; margin: 0; } a { color: #990000; } .line { font-size: 0.8em; overflow: hidden; padding-top: 12px; } .title { color: #444; width: 130px; display: block; float: left; } max-width: 800px; margin: 0 auto; font-family: verdana, sans-serif; dl { line-height: 30px; } dt { width: 190px; float: left; } .spanstart:after { content: ' - '; } .date_duration { font-weight: 400; } .vcard > h1 { font-size: 1.6em; padding: 0; margin: 0; font-weight: 400; padding-bottom: 10px; } .experience { &:last-child { padding-bottom: 0; } padding-bottom: 20px; } .experience-header { overflow: hidden; } .experience-hgroup { float: left; } .vcalendar li { font-size: 0.8em; } .description { list-style: circle; color: #666; margin-left: 20px; padding-left: 20px; line-height: 25px; } .skills { overflow: hidden; margin: 0; li { float: left; } } } ================================================ FILE: apps/kirjs/src/app/kirjs.module.ts ================================================ import { BrowserModule } from '@angular/platform-browser'; import { APP_INITIALIZER, NgModule } from '@angular/core'; import { monacoReady } from '@codelab/code-demos'; import { AppComponent } from './app.component'; import { RouterModule } from '@angular/router'; import { environment } from '../../../codelab/src/environments/environment'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { AngularFireModule } from '@angular/fire'; import { AngularFireAuthModule } from '@angular/fire/auth'; import { AngularFireDatabaseModule } from '@angular/fire/database'; import { OverlayComponent } from './modules/streaming/overlay/overlay.component'; import { MatCardModule } from '@angular/material/card'; import { MarkdownModule } from 'ngx-markdown'; export const angularFire = AngularFireModule.initializeApp( environment.firebaseConfig ); const routes = [ { path: 'binary', loadChildren: () => import('./modules/binary/binary.module').then(m => m.BinaryModule), name: 'Binary', description: 'Learn about Binary in JS', page: 'bonus', prod: true }, { path: 'gomoku', loadChildren: () => import('./modules/gomoku/gomoku.module').then(m => m.GomokuModule), name: 'Gomoku', description: 'Gomoku', page: 'bonus', prod: true }, { path: 'cellular-automation', loadChildren: () => import( './modules/cellular-automation/cellular-automation-routing.module' ).then(m => m.CellularAutomationRoutingModule), name: 'Image inclusion', description: 'Image inclusion' }, { path: 'music', loadChildren: () => import('./modules/music/music.module').then(m => m.MusicModule), name: 'Music', description: 'Music' }, { path: 'webassembly', loadChildren: () => import('./modules/webassembly/webassembly.module').then( m => m.WebassemblyModule ), name: 'webassembly', description: 'webassembly' }, { path: 'svg', loadChildren: () => import('./modules/svg/svg.module').then(m => m.SvgModule), name: 'Svg + Angular', description: 'SVG ' }, { path: 'regex', loadChildren: () => import('./modules/regex/regex.module').then(m => m.RegexModule), name: 'Regex', description: 'Regex ' }, { path: 'ast', loadChildren: () => import('./modules/ast/ast.module').then(m => m.AstModule), name: 'Ast + Angular', description: 'SVG ' }, { path: 'svg-race', loadChildren: () => import('./modules/svg-race/svg-race.module').then(m => m.SvgRaceModule), name: 'SVG Race', description: 'SVG ' }, { path: '', loadChildren: () => import('./modules/home/home.module').then(m => m.HomeModule), name: 'Home', description: 'Home' }, { path: 'streaming', loadChildren: () => import('./modules/streaming/streaming.module').then( m => m.StreamingModule ), name: 'Home', description: 'Home' }, { path: 'sync', loadChildren: () => import('./modules/sync/sync.module').then(m => m.SyncModule), name: 'Sync', description: 'Sync Session' }, { path: 'stack', loadChildren: () => import('./modules/stack/stack.module').then(m => m.StackModule), name: 'Stack Module', description: 'stack' }, { path: 'sync', loadChildren: () => import('./modules/sync/sync.module').then(m => m.SyncModule), name: 'Sync', description: 'Sync Session' }, { path: 'test', loadChildren: () => import('./modules/test/test.module').then(m => m.TestModule), name: 'Home', description: 'Home' }, { path: 'qna', loadChildren: () => import('./modules/qna/qna.module').then(m => m.QnaModule), name: 'Q&A' }, { path: 'msk', loadChildren: () => import('./modules/msk/msk.module').then(m => m.MskModule), name: 'Angular Moscow Meetup' }, { path: 'stack', loadChildren: () => import('./modules/stack/stack-routing.module').then( m => m.StackRoutingModule ), name: 'Stack Module', description: 'stack' } ]; @NgModule({ declarations: [AppComponent, OverlayComponent], imports: [ BrowserModule, BrowserAnimationsModule, RouterModule.forRoot(routes), AngularFireAuthModule, AngularFireDatabaseModule, angularFire, MatCardModule, MarkdownModule ], providers: [ { provide: APP_INITIALIZER, useValue: monacoReady, multi: true }, { provide: 'ROUTES', useValue: [] }, { provide: APP_INITIALIZER, useValue: monacoReady, multi: true } ], bootstrap: [AppComponent] }) export class KirjsModule {} ================================================ FILE: apps/kirjs/src/app/modules/ast/ast-preview-runner/ast-preview-runner.component.css ================================================ .error { color: red; font-size: 50px; } :host ::ng-deep .header.header.header.header { font-size: inherit; } ================================================ FILE: apps/kirjs/src/app/modules/ast/ast-preview-runner/ast-preview-runner.component.html ================================================
Parsing error
================================================ FILE: apps/kirjs/src/app/modules/ast/ast-preview-runner/ast-preview-runner.component.ts ================================================ import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { parse } from 'babylon'; declare const require; @Component({ selector: 'kirjs-ast-preview-runner', templateUrl: './ast-preview-runner.component.html', styleUrls: ['./ast-preview-runner.component.css'] }) export class AstPreviewRunnerComponent implements OnChanges { ast: any; hasError = false; @Input() program = true; @Input() code = ''; @Output() highlight = new EventEmitter(); update() { this.run(); } selectNode({ loc }) { this.highlight.emit([ loc.start.line, loc.start.column + 1, loc.end.line, loc.end.column + 1 ]); } run() { this.hasError = false; try { this.ast = parse(this.code); if (this.program) { this.ast = this.ast.program.body; } } catch (e) { this.hasError = true; } } ngOnChanges(changes: SimpleChanges) { this.run(); } constructor() {} } ================================================ FILE: apps/kirjs/src/app/modules/ast/ast-preview-runner/ast-preview-runner.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { AstPreviewRunnerComponent } from './ast-preview-runner.component'; import { FormsModule } from '@angular/forms'; import { AngularAstVizModule } from '@codelab/angular-ast-viz'; @NgModule({ imports: [CommonModule, FormsModule, AngularAstVizModule], declarations: [AstPreviewRunnerComponent], exports: [AstPreviewRunnerComponent] }) export class AstPreviewRunnerModule {} ================================================ FILE: apps/kirjs/src/app/modules/ast/ast.component.css ================================================ .bg { background: #fff; opacity: 0.7; padding: 2vw 5vw; margin: 5vw 0; border: 1px solid #000; } #intro h1 { font-size: 9vw; font-weight: 100; font-family: 'Helvetica Neue', sans-serif; } a { color: #9c2600; } #intro h2, #outro h2 { font-size: 12vw; } #intro h2, #outro h1 { font-size: 6vw; } :host /deep/ .slide > div { width: 100%; } :host /deep/ .slide { background: transparent; } #sausage-pre { background: url(pics/sausage-pre.jpg) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; font-size: 5vw; } #not-like-sausage { background: url(pics/sausage2.jpg) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; font-size: 5vw; } #babel-types { background: url(pics/park.jpg) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; font-size: 5vw; } #babel-traverse .bg, #babel-transform .bg, #renoir .bg { margin: 30vw 0; } #babel-traverse { background: url(pics/girl-reading-1890.jpg) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; font-size: 5vw; } #babel-transform { background: url(pics/doggie.jpg) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; font-size: 5vw; } #debugger-pre { background: url(pics/debugger.jpg) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; font-size: 5vw; } #renoir { background: url(pics/renoir.png) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; font-size: 5vw; } #babel-remove-debugger { background: url(pics/cat.jpg) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; font-size: 5vw; } #babel-fit { background: url(pics/the-garden.jpg) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; font-size: 5vw; } #parser { background: url(pics/parser.png) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; font-size: 5vw; } #links-5-cool-things { background: url(pics/bg-links.jpg) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; font-size: 5vw; } #panda { background: url(pics/panda.webp) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; font-size: 5vw; } .henry-zhu { background: url(pics/patreon.png) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; font-size: 5vw; width: 70vw; } .babylon { background: url(pics/babylon.png) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; font-size: 5vw; width: 70vw; } .babylon { background: url(pics/babylon.png) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; font-size: 5vw; width: 70vw; } .success-complete-disco { margin-top: 20px; width: 500px; height: 300px; background: url(pics/success/disco.webp); background-repeat: no-repeat; background-size: cover; } .success-complete-husky { margin-top: 20px; width: 500px; height: 300px; background: url(pics/success/happy.webp); background-repeat: no-repeat; background-size: cover; } .success-complete-dancing { margin-top: 20px; width: 500px; height: 300px; background: url(pics/success/dancing-dog.webp); background-repeat: no-repeat; background-size: cover; } .github { background: url(pics/github.png) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; font-size: 5vw; width: 70vw; } .opencollective { background: url(pics/opencollective.png) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; font-size: 5vw; width: 70vw; } #eslint { background: url(pics/debugger.jpg) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; font-size: 5vw; } #kirjs { background: url(pics/kirjs.jpg) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; } #intro, #outro { background: url(pics/landscape-with-trees-1.jpg) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; } .bad-tree { background: url(pics/tree.gif) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; } #venok img { width: 384px; height: 288px; margin: 0 auto; } .ast-explorer-link:hover { font-size: 5vw; } .ast-explorer-link { padding-top: 25px; font-size: 10px; } .description.description.description { margin: 0; margin-top: 20px; padding: 0; font-size: 0; } #debugger-regex /deep/ .solution:hover:before { color: red; font-size: 3vw; } :host /deep/ #debugger-regex .solution { } .ast-preview-helper-list li:hover { color: #444; } .ast-preview-helper-list li { font-size: 20px; text-decoration: dashed; cursor: pointer; color: #999; border-bottom: 1px #ddd dashed; } .ast-preview { font-size: 30px; } .two-button { background: url(pics/button-1.png) no-repeat; background-size: 50% auto; display: inline-block; height: 50vh; font-size: 5vw; border: 0 solid; padding: 0; margin: 0; } .two-button2 { background: url(pics/two-button2.gif) no-repeat; background-size: 50% auto; display: inline-block; height: 50vh; font-size: 5vw; border: 0 solid; padding: 0; margin: 0; } .two-button3 { background: url(pics/two-button3.gif) no-repeat; background-size: 100% auto; display: inline-block; height: 100vh; font-size: 5vw; border: 0 solid; padding: 0; margin: 0; } .link-number { display: inline-block; font-size: 8vw; border-radius: 50%; width: 10vw; height: 10vw; text-align: center; margin-bottom: 10vh; } .collie.display { background: url(pics/collie.webp) no-repeat; } .collie { background-size: 100% auto; display: inline-block; height: 40vh; font-size: 5vw; width: 50vw; } .btn-bar { line-height: 3vw; } .btn-bar:hover .font-size { display: block; font-size: 4vw; } .btn-bar .font-size { display: none; } .twitter { color: #444; font-size: 3vw; margin: 2vw; } ================================================ FILE: apps/kirjs/src/app/modules/ast/ast.component.html ================================================

Абстрактные Синтаксические Деревья в JavaScript

@kirjs

@kirjs

Абстрактные синтаксические деревья (АСД)

Это выступление о том как научить код понимать и трансформировать другой код.

Начнем сразу с примера

Как определить что из файла забыли вырезать console.log

Парсим с помощью babylon:

19 Мая babylon был переименован в @babel/parser



Пример Абстрактного Синтаксического Дерева:

  • log
  • console.log
  • log()
  • console.log()
  • console.log(1)
  • console.log(1, a.b)
  • All literals
Обход AST с помощью babel-traverse
Упрощаем работу с AST с помощью babel-types
Трансформируем АСД с помощью babel-traverse
Давайте найдем в коде debugger
Давайте напишем плагин для ESLint (на AstExplorer)

Где еще полезны АСД?

  • Больше контроля надо кодом, проще рефакторинг, codemods
  • Меньше споров про оформление кода
  • Создание игровых проэктов
  • Лучшее понимание JavaScript

Эта презентация: https://codelab.fun/ast/intro

Картинки: @artist_renoir

Мой твиттер: @kirjs

Thank you!

================================================ FILE: apps/kirjs/src/app/modules/ast/ast.component.ts ================================================ import { Component } from '@angular/core'; import { parse } from 'babylon'; import { findHighlightsObjectProp, isBody, isLoc, isType, processCode, removeDoubleWhiteLines, removeLoc } from './parse-hello-world-ast'; import { exercise } from '../../../../../codelab/src/app/shared/helpers/helpers'; declare const require; const helloWorld = require('./samples/hello-world.json'); function jsify(program) { return 'const ast = ' + JSON.stringify(program, null, ' '); } const helloWorldCodePre = jsify(helloWorld); const angularCodeBefore = `import { Component } from '@angular/core'; @Component({ selector: 'amazing-component', template: './app.html', }) export class SimpleEditorComponent implements ControlValueAccessor { }`; const angularCodeAfter = `import { Component, forwardRef, } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @Component({ selector: 'amazing-component', template: './app.html', providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SimpleEditorComponent), multi: true } ], }) export class SimpleEditorComponent implements ControlValueAccessor { registerOnTouched(fn: any): void { } registerOnChange(onChange: (code: string) => void): void { this.change.subscribe(onChange) } writeValue(value: string): void { console.log(value) } }`; const consoleLog = `console.log('hello')`; const consoleLogAst = parse(consoleLog); const consoleLogCode = JSON.stringify(consoleLogAst, null, ' '); const consoleLogBodyAst = consoleLogAst.program.body; const consoleLogBodyAstCode = JSON.stringify(consoleLogBodyAst, null, ' '); const consoleLogNoLocCode = removeDoubleWhiteLines( processCode(consoleLogBodyAstCode, { remove: [removeLoc] }) ); @Component({ selector: 'kirjs-ast', templateUrl: './ast.component.html', styleUrls: ['./ast.component.css'] }) export class AstComponent { fontSize = 28; displayCallee = false; code = { parser: `import { parse } from 'babylon'; parse("console.log('🐶🐶🐶')");`, astPreview: 'log', astPreviewDebugger: 'console.log();\ndebugger\n123', types: { identifier: ` // Before path.node.property.type === 'Identifier' // with babel types import {isIdentifier} from 'babel-types'; isIdentifier(path.node.property)`, name: ` // Before path.node.property.type === 'Identifier' path.node.property.name === 'log' // with babel types import {isIdentifier} from 'babel-types'; isIdentifier(path.node.property, {name: log})` }, astHello: 'hello(console.log)', matches: { loc: /"loc": \{[\s\S]*?\{[\s\S]*?\}[\s\S]*?\{[\s\S]*?\}[\s\S]*?\},/ }, astExampleFull: processCode(helloWorldCodePre, { remove: [] }), astExample: removeDoubleWhiteLines( processCode(helloWorldCodePre, { remove: [removeLoc] }) ), astExampleNoBody: removeDoubleWhiteLines( processCode(jsify(helloWorld.program.body), { remove: [removeLoc] }) ), consoleLog: consoleLog, consoleLogAst: consoleLogCode, consoleLogAstJs: 'const ast = ' + consoleLogCode, consoleLogBodyAstCode, consoleLogNoLocCode, angularCodeBefore, angularCodeAfter, angularMatchesAfter: [/a/], findConsoleLog: [ exercise( 'find-console-log', require('!!raw-loader!./samples/find-console-log/find-console-log.js'), require('!!raw-loader!./samples/find-console-log/find-console-log-regex.solved.js') ), exercise( 'find-console-log.test', require('!!raw-loader!./samples/find-console-log/find-console-log.test.js') ) ], findDebuggerBabel: [ exercise( 'find-debugger-babel', require('!!raw-loader!./samples/find-debugger/find-debugger-babel.ts'), require('!!raw-loader!./samples/find-debugger/find-debugger-babel.solved.ts') ), exercise( 'find-debugger.test', require('!!raw-loader!./samples/find-debugger/find-debugger.test.js') ) ], traverseConsoleLogBabel: [ exercise( 'traverse-console-log-babel', require('!!raw-loader!./samples/find-console-log/traverse-console-log-babel.ts'), require('!!raw-loader!./samples/find-console-log/traverse-console-log-babel.solved.ts') ), exercise( 'find-console-log.test', require('!!raw-loader!./samples/find-console-log/find-console-log.test.js') ) ], traverseConsoleLogBabel2: [ exercise( 'traverse-console-log-babel', require('!!raw-loader!./samples/find-console-log/traverse-console-log-babel.solved.ts'), require('!!raw-loader!./samples/find-console-log/traverse-console-log-babel.solved2.ts') ), exercise( 'find-console-log.test', require('!!raw-loader!./samples/find-console-log/find-console-log.test.js') ) ], removeConsoleLogBabel: [ exercise( 'remove-console-log', require('!!raw-loader!./samples/find-console-log/remove-console-log.ts'), require('!!raw-loader!./samples/find-console-log/remove-console-log.solved.ts') ), exercise( 'remove-console-log.test', require('!!raw-loader!./samples/find-console-log/remove-console-log.test.js') ) ], findfIt: [ exercise( 'find-fit', require('!!raw-loader!./samples/find-fit/find-fit.js'), require('!!raw-loader!./samples/find-fit/find-fit.solved.js') ), exercise( 'find-fit.test', require('!!raw-loader!./samples/find-fit/find-fit.test.js') ) ], itLines: [ exercise( 'it-lines', require('!!raw-loader!./samples/it-lines/it-lines.js'), require('!!raw-loader!./samples/it-lines/it-lines.solved.js') ), exercise( 'it-lines.test', require('!!raw-loader!./samples/it-lines/it-lines.test.js') ) ], decToBin: require('!!raw-loader!./samples/dec-to-bin.js'), decToBinFixed: require('!!raw-loader!./samples/dec-to-bin-with-semicolons.js') }; constructor() {} matchTreePartsLoc(code) { return findHighlightsObjectProp(code, [isLoc]); } matchTreePartsBody(code) { return findHighlightsObjectProp(code, [isBody]); } matchTreePartsType(code) { return findHighlightsObjectProp(code, [isType]); } updateFontSize(diff) { this.fontSize += diff; } } ================================================ FILE: apps/kirjs/src/app/modules/ast/ast.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { FormsModule } from '@angular/forms'; import { MatCardModule } from '@angular/material/card'; import { CommonModule } from '@angular/common'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { AngularSlidesToPdfModule } from '@codelab/angular-slides-to-pdf'; import { BrowserWindowModule } from '@codelab/browser'; import { FeedbackModule } from '@codelab/feedback'; import { CodeDemoModule } from '@codelab/code-demos'; import { AstPreviewRunnerModule } from './ast-preview-runner/ast-preview-runner.module'; import { NewProgressBarModule } from './new-progress-bar/new-progress-bar.module'; import { AstComponent } from './ast.component'; import { DebuggerComponent } from './debugger/debugger.component'; import { TestSetComponent } from './test-set/test-set.component'; import { SizePickerModule } from './size-picker/size-picker.module'; import { BabelTestRunnerComponent } from './test-set/babel-test-runner/babel-test-runner.component'; const routes = RouterModule.forChild(SlidesRoutes.get(AstComponent)); @NgModule({ imports: [ routes, CommonModule, AstPreviewRunnerModule, SlidesModule, BrowserWindowModule, FeedbackModule, CodeDemoModule, FormsModule, SizePickerModule, MatCardModule, NewProgressBarModule, AngularSlidesToPdfModule ], declarations: [ AstComponent, BabelTestRunnerComponent, DebuggerComponent, TestSetComponent ], exports: [AstComponent] }) export class AstModule {} ================================================ FILE: apps/kirjs/src/app/modules/ast/babel-highlight/babel-highlight-match.directive.ts ================================================ import { AfterViewInit, Directive, Input, NgModule } from '@angular/core'; import { CodeDemoEditorComponent } from '@codelab/code-demos/src/lib/code-demo-editor/code-demo-editor.component'; // TODO(kirjs): Uncommit @Directive({ // tslint:disable-next-line:all TODO: Fix linter warnings on the selector and delete this comment. selector: '[slidesBabelHighlightMatch]' }) export class BabelHighlightDirective implements AfterViewInit { // tslint:disable-next-line:all TODO: Fix linter warnings on the next line and delete this comment. @Input('slidesBabelHighlightMatch') callback; constructor(private editorComponent: CodeDemoEditorComponent) {} ngAfterViewInit(): void { if (this.callback) { const matches = this.callback(this.editorComponent.code) || []; const decorations = matches.map(match => { return { range: new this.editorComponent.monacoConfigService.monaco.Range( ...match.loc ), options: { inlineClassName: match.className } }; }); this.editorComponent.editor.deltaDecorations([], decorations); } } } @NgModule({ declarations: [BabelHighlightDirective] }) export class MockaADASDASDASDASDASDModule {} ================================================ FILE: apps/kirjs/src/app/modules/ast/debugger/debugger.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/ast/debugger/debugger.component.html ================================================

(Example from wtfjs)
================================================ FILE: apps/kirjs/src/app/modules/ast/debugger/debugger.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { DebuggerComponent } from './debugger.component'; describe('DebuggerComponent', () => { let component: DebuggerComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [DebuggerComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(DebuggerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/ast/debugger/debugger.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; declare const require; const code = require('!!raw-loader!./debugger.ts'); @Component({ selector: 'kirjs-debugger-sample', templateUrl: './debugger.component.html', styleUrls: ['./debugger.component.css'] }) export class DebuggerComponent implements OnInit { @Input() fontSize = 20; code = code; constructor() {} run() { eval(this.code + ';weird()'); } ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/ast/debugger/debugger.ts ================================================ function weird() { parseInt('Infinity', 10); parseInt('Infinity', 18); parseInt('Infinity', 19); parseInt('Infinity', 23); parseInt('Infinity', 24); parseInt('Infinity', 29); parseInt('Infinity', 30); parseInt('Infinity', 34); parseInt('Infinity', 35); parseInt('Infinity', 36); parseInt('Infinity', 37); } ================================================ FILE: apps/kirjs/src/app/modules/ast/new-progress-bar/new-progress-bar.component.css ================================================ .progress-bar { position: fixed; bottom: 0; left: 0; width: 100vw; z-index: 100; background-color: #fff; border-top: 1px solid #000; padding: 0.5vw; opacity: 0.3; } .progress-bar:hover .font-size { visibility: visible; } .progress-bar:hover { opacity: 1; } .font-size { visibility: hidden; } .slide-block.completed-slide { background: #ddd; } .slide-block.completed-slide { background: #000; } .slide-block:hover { opacity: 1; cursor: pointer; } .slide-block.current-slide { background: lime; } .slide-block { margin-top: 0.1vw; opacity: 0.7; width: 0.5vw; height: 0.5vw; border: 1px solid #000; border-radius: 50%; margin-right: 0.3vw; } .handle { font-family: 'Helvetica Neue', sans-serif; position: fixed; right: 0; bottom: 0; color: #444444; font-size: 3vw; z-index: 101; padding: 0.5vw; padding-right: 2vw; text-shadow: 0px 3px 20px #fff, 0px -3px 20px #fff, -3px 0px 20px #fff, 3px 0px 20px #fff; text-decoration: none; } kirjs-size-picker { margin-right: 200px; } ================================================ FILE: apps/kirjs/src/app/modules/ast/new-progress-bar/new-progress-bar.component.html ================================================
{{ title }}
sli.do/armadajs | @kirjs ================================================ FILE: apps/kirjs/src/app/modules/ast/new-progress-bar/new-progress-bar.component.ts ================================================ import { AfterViewInit, Component, EventEmitter, Input, Output } from '@angular/core'; import { SlidesDeckComponent } from '@ng360/slides'; @Component({ selector: 'kirjs-new-progress-bar', templateUrl: './new-progress-bar.component.html', styleUrls: ['./new-progress-bar.component.css'] }) export class NewProgressBarComponent implements AfterViewInit { @Input() fontSize = 28; @Input() title = 'JavaScript AST'; @Output() fontSizeChange = new EventEmitter(); slides = []; activeSlideIndex = 0; tempSlideId = 0; constructor(public deck: SlidesDeckComponent) {} ngAfterViewInit() { // Change detection complains if updating it right away. requestAnimationFrame(() => { this.slides = this.deck.slides; this.activeSlideIndex = this.deck.activeSlideIndex; }); this.deck.slideChange.subscribe(index => { this.activeSlideIndex = index; }); } previewSlide(index) { this.tempSlideId = this.activeSlideIndex; this.deck.goToSlide(index); } goToSlide(index) { this.deck.goToSlide(index); this.tempSlideId = this.activeSlideIndex; } } ================================================ FILE: apps/kirjs/src/app/modules/ast/new-progress-bar/new-progress-bar.module.ts ================================================ import { NgModule } from '@angular/core'; import { NewProgressBarComponent } from './new-progress-bar.component'; import { CommonModule } from '@angular/common'; import { SizePickerModule } from '../size-picker/size-picker.module'; @NgModule({ imports: [CommonModule, SizePickerModule], declarations: [NewProgressBarComponent], exports: [NewProgressBarComponent] }) export class NewProgressBarModule {} ================================================ FILE: apps/kirjs/src/app/modules/ast/parse-hello-world-ast.ts ================================================ import { parse } from 'babylon'; import generate from '@babel/generator'; import babel_traverse from '@babel/traverse'; export function isLoc(node) { return ( node.key.value === 'loc' || node.key.value === 'start' || node.key.value === 'end' || node.key.value === 'extra' ); } export function isType(node) { return node.key.value === 'type'; } export function isBody(node) { return node.key.value === 'body'; } export function removeLoc(path) { if (isLoc(path.node)) { path.remove(); } } export function removeExtra(path) { if (isLoc(path.node)) { path.remove(); } } export function locToMonacoLoc(loc, className) { return { loc: [ loc.start.line, loc.start.column + 1, loc.end.line, loc.end.column + 1 ], className }; } export function parseCode(code) { return parse(code); } export function processCode(code, { remove = [] }: any) { const ast = parse(code, { sourceType: 'module', plugins: ['decorators'] }); babel_traverse(ast, { ObjectProperty(path) { remove.forEach(callback => callback(path)); } }); return generate(ast, {}).code; } export function findHighlightsObjectProp(code: string, matchers: Array) { const ast = parse(code, { sourceType: 'module', plugins: ['decorators'] }); const highlights = []; babel_traverse(ast, { ObjectProperty({ node }) { if (matchers.some(matcher => matcher(node))) { highlights.push(locToMonacoLoc(node.loc, 'loc')); } } }); return highlights; } export function findHighlightsAll(code: string, matcher) { const ast = parse(code, { sourceType: 'module', plugins: ['decorators'] }); const highlights = []; babel_traverse(ast, { enter({ node }) { const className = matcher(node); if (className) { highlights.push(locToMonacoLoc(node.loc, className)); } } }); return highlights; } export function removeDoubleWhiteLines(code) { return code.replace(/\n\n/gi, '\n'); } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/dec-to-bin-with-semicolons.js ================================================ function bin6BitsToDec(bin) { switch (bin) { case '000000': return 0; case '000001': return 1; case '000010': return 2; case '000011': return 3; case '000100': return 4; case '000101': return 5; case '000110': return 6; case '000111': return 7; case '001000': return 8; case '001001': return 9; case '001010': return 10; case '001011': return 11; case '001100': return 12; case '001101': return 13; case '001110': return 14; case '001111': return 15; case '010000': return 16; case '010001': return 17; case '010010': return 18; case '010011': return 19; case '010100': return 20; case '010101': return 21; case '010110': return 22; case '010111': return 23; case '011000': return 24; case '011001': return 25; case '011010': return 26; case '011011': return 27; case '011100': return 28; case '011101': return 29; case '011110': return 30; case '011111': return 31; case '100000': return 32; case '100001': return 33; case '100010': return 34; case '100011': return 35; case '100100': return 36; case '100101': return 37; case '100110': return 38; case '100111': return 39; case '101000': return 40; case '101001': return 41; case '101010': return 42; case '101011': return 43; case '101100': return 44; case '101101': return 45; case '101110': return 46; case '101111': return 47; case '110000': return 48; case '110001': return 49; case '110010': return 50; case '110011': return 51; case '110100': return 52; case '110101': return 53; case '110110': return 54; case '110111': return 55; case '111000': return 56; case '111001': return 57; case '111010': return 58; case '111011': return 59; case '111100': return 60; case '111101': return 61; case '111110': return 62; case '111111': return 63; } } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/dec-to-bin.js ================================================ function bin6BitsToDec(bin) { switch (bin) { case '000000': return 0; case '000001': return 1; case '000010': return 2; case '000011': return 3; case '000100': return 4; case '000101': return 5; case '000110': return 6; case '000111': return 7; case '001000': return 8; case '001001': return 9; case '001010': return 10; case '001011': return 11; case '001100': return 12; case '001101': return 13; case '001110': return 14; case '001111': return 15; case '010000': return 16; case '010001': return 17; case '010010': return 18; case '010011': return 19; case '010100': return 20; case '010101': return 21; case '010110': return 22; case '010111': return 23; case '011000': return 24; case '011001': return 25; case '011010': return 26; case '011011': return 27; case '011100': return 28; case '011101': return 29; case '011110': return 30; case '011111': return 31; case '100000': return 32; case '100001': return 33; case '100010': return 34; case '100011': return 35; case '100100': return 36; case '100101': return 37; case '100110': return 38; case '100111': return 39; case '101000': return 40; case '101001': return 41; case '101010': return 42; case '101011': return 43; case '101100': return 44; case '101101': return 45; case '101110': return 46; case '101111': return 47; case '110000': return 48; case '110001': return 49; case '110010': return 50; case '110011': return 51; case '110100': return 52; case '110101': return 53; case '110110': return 54; case '110111': return 55; case '111000': return 56; case '111001': return 57; case '111010': return 58; case '111011': return 59; case '111100': return 60; case '111101': return 61; case '111110': return 62; case '111111': return 63; } } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/eslint/eslint.js ================================================ function findFit(code, { babylon, babelTraverse, babelGenerator, log }) { const ast = babylon.parse(code); babelTraverse(ast); log(babelGenerator(ast).code); return babelGenerator(ast).code; } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/eslint/eslint.test.js ================================================ function testThings(findDebugger, callback, args) { const log = args.log; const tests = [ { title: `describe('test', ()=>{ fit('test', ()=>{ expect(true).toBe(true); }) })`, test(func, args) { let result = func(this.title, args); return result && result.includes && !result.includes('fit'); } }, { title: `describe('test', ()=>{ fit('fits?', ()=>{ let fit = true; expect(fit).toBe(true); }) })`, test(func, args) { let result = func(this.title, args); return ( result && result.includes && !result.includes('fit(') && result.includes('let fit') ); } } ]; let results = []; let failed = false; for (let i in tests) { if (tests.hasOwnProperty(i)) { let test = tests[i]; if (!failed) { const logs = []; args.log = value => { logs.push(value); }; let pass = test.test(findDebugger, args); results.push({ title: test.title, pass: pass }); if (!pass) { failed = true; logs.map(log); } } else { results.push({ title: test.title, pass: false }); } } } callback(results); } /** return code .replace(/\/\/.*|'.*?[^\\]'|".*"|`[\s\S]+`/) .match(/\bdebugger\b/) */ ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-console-log/find-console-log-babel.solved.ts ================================================ // tslint:ignore function findConsoleLogSolved(code, { babylon, babelTraverse, log }) { return babylon .parse(code) .program.body.some(node => node.type === 'ConsoleLogStatement'); } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-console-log/find-console-log-babel.ts ================================================ function findConsoleLogBabel(code, { babylon, babelTraverse, log }) { // Find debugger!! } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-console-log/find-console-log-regex.solved.js ================================================ function findConsoleLog(code) { return code .replace(/\/\/.*|'.*?[^\\]'|".*?"|`[\s\S]*`|\/\*[\s\S]*\*\//) .match(/\bconsole\s*.log\(/); } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-console-log/find-console-log.js ================================================ function findConsoleLog(code) {} ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-console-log/find-console-log.test.js ================================================ function testThings(findConsoleLog, callback, args) { const log = args.log; const tests = [ { code: `console.log('🐶🐶🐶')`, expected: true }, { code: `lolconsole.log()`, expected: false }, { code: `// don't use console.log();`, expected: false }, { code: `'console.log()'`, expected: false }, { code: `'hi'; console.log(123); 'bye'`, expected: true }, { code: `console .log()`, expected: true }, { code: `'Fake //'; console.log(123); `, expected: true }, { code: `"console.log(123)"`, expected: false }, { code: `\` console.log(123)\``, expected: false }, { code: `' \\' console.log(123)'`, expected: false }, { code: `console.lol()`, expected: false }, { code: `"hello".log()`, expected: false }, { code: `/* \` */ console.log(123); \` \` + ''`, expected: true }, { code: `function hello() { console.log(123); }`, expected: true }, { code: `console.log({ hello: 123 })`, expected: true }, { code: `console.log`, expected: false }, { code: `hello(console.log)`, expected: false } ]; let results = []; let failed = false; for (let i in tests) { if (tests.hasOwnProperty(i)) { let test = tests[i]; if (!failed) { const logs = []; args.log = value => { logs.push(value); }; const result = !!findConsoleLog(test.code, args); const pass = result === test.expected; results.push({ code: test.code, expected: test.expected, result, pass }); if (!pass) { failed = true; logs.map(log); } } else { results.push({ code: test.code, pass: false }); } } } callback(results); } /** return code .replace(/\/\/.*|'.*?[^\\]'|".*"|`[\s\S]+`/) .match(/\bdebugger\b/) */ ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-console-log/remove-console-log.solved.ts ================================================ function removeConsoleLogSolved( code, { babelGenerator, babylon, babelTraverse, types } ) { const ast = babylon.parse(code); babelTraverse(ast, { MemberExpression(path) { if ( types.isIdentifier(path.node.object, { name: 'console' }) && types.isIdentifier(path.node.property, { name: 'log' }) && types.isCallExpression(path.parent) && path.parentKey === 'callee' ) { path.parentPath.remove(); } } }); return babelGenerator(ast).code; } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-console-log/remove-console-log.test.js ================================================ function testThings(findConsoleLog, callback, args) { const log = args.log; const tests = [ { code: `console.log('🐶🐶🐶')`, expected: '' }, { code: `lolconsole.log();`, expected: 'lolconsole.log();' }, { code: `console.lol();`, expected: `console.lol();` }, { code: `// don't use console.log();`, expected: `// don't use console.log();` }, { code: `'console.log()';`, expected: `'console.log()';` }, { code: `'hi'; console.log(123); 'bye';`, expected: `'hi';\n'bye';` }, { code: `console .log()`, expected: `` }, { code: `"console.log(123)";`, expected: `"console.log(123)";` }, { code: `\` console.log(1234)\`;`, expected: `\` console.log(1234)\`;` }, { code: `' \\' console.log(123)';`, expected: `' \\' console.log(123)';` }, { code: `/* \` */ console.log(123); \` \` + '';`, expected: `\` \` + ''; /* \` */` }, { code: `hello(console.log);`, expected: `hello(console.log);` } ]; let results = []; let failed = false; for (let i in tests) { if (tests.hasOwnProperty(i)) { let test = tests[i]; if (!failed) { const logs = []; args.log = value => { logs.push(value); }; let result = false; let error; try { result = findConsoleLog(test.code, args); } catch (e) { error = e; } const pass = result === test.expected; results.push({ code: test.code, expected: test.expected, result, pass, error }); if (!pass) { failed = true; logs.map(log); } } else { results.push({ code: test.code, pass: false }); } } } callback(results); } /** return code .replace(/\/\/.*|'.*?[^\\]'|".*"|`[\s\S]+`/) .match(/\bdebugger\b/) */ ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-console-log/remove-console-log.ts ================================================ function removeConsoleLog( code, { babelGenerator, babylon, babelTraverse, types } ) { const ast = babylon.parse(code); let hasConsoleLog = false; babelTraverse(ast, { MemberExpression(path) { if ( types.isIdentifier(path.node.object, { name: 'console' }) && types.isIdentifier(path.node.property, { name: 'log' }) && types.isCallExpression(path.parent) && path.parentKey === 'callee' ) { hasConsoleLog = true; } } }); return hasConsoleLog; } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-console-log/traverse-console-log-babel.solved.ts ================================================ function traverseConsoleLogSolved( code, { babylon, babelTraverse, types, log } ) { const ast = babylon.parse(code); let hasConsoleLog = false; babelTraverse(ast, { MemberExpression(path) { if ( path.node.property.type === 'Identifier' && path.node.property.name === 'log' && path.node.object.type === 'Identifier' && path.node.object.name === 'console' && path.parent.type === 'CallExpression' && path.key === 'callee' ) { hasConsoleLog = true; } } }); return hasConsoleLog; } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-console-log/traverse-console-log-babel.solved2.ts ================================================ function traverseConsoleLogSolved2(code, { babylon, babelTraverse, types }) { const ast = babylon.parse(code); let hasConsoleLog = false; babelTraverse(ast, { MemberExpression(path) { if ( types.isIdentifier(path.node.object, { name: 'console' }) && types.isIdentifier(path.node.property, { name: 'log' }) && types.isCallExpression(path.parent) && path.parentKey === 'callee' ) { hasConsoleLog = true; } } }); return hasConsoleLog; } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-console-log/traverse-console-log-babel.ts ================================================ function traverseConsoleLog(code, { babylon, babelTraverse, log }) { const ast = babylon.parse(code); } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-debugger/find-debugger-babel.solved.ts ================================================ // tslint:ignore function findDebuggerSolved(code, { babylon, babelTraverse, log }) { return babylon .parse(code) .program.body.some(node => node.type === 'DebuggerStatement'); } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-debugger/find-debugger-babel.ts ================================================ function findDebuggerBabel(code, { babylon, babelTraverse, log }) { // Find debugger!! } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-debugger/find-debugger-regex.solved.js ================================================ function findDebugger(code) { return code .replace(/\/\/.*|'.*?[^\\]'|".*?"|`[\s\S]*`|\/\*[\s\S]*\*\//) .match(/\bdebugger\b/); } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-debugger/find-debugger.js ================================================ function findDebugger(code) {} ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-debugger/find-debugger.test.js ================================================ function testThings(findDebugger, callback, args) { const log = args.log; const tests = [ { title: `debugger`, shouldPass: true, test(func, args) { return !!func(this.title, args); } }, { title: `debuggerStart()`, shouldPass: false, test(func, args) { return !func(this.title, args); } }, { title: `//debugger console.log('hi');`, shouldPass: false, test(func, args) { return !func(this.title, args); } }, { title: `'debugger'`, shouldPass: false, test(func, args) { return !func(this.title, args); } }, { title: `'hi'; debugger; 'bye'`, shouldPass: true, test(func, args) { return func(this.title, args); } }, { title: `'Fake //'; debugger; `, shouldPass: true, test(func, args) { return func(this.title, args); } }, { title: `"debugger"`, shouldPass: false, test(func, args) { return !func(this.title, args); } }, { title: `\` debugger\``, shouldPass: false, test(func, args) { return !func(this.title, args); } }, { title: `' \\' debugger'`, test(func, args) { return !func(this.title, args); }, shouldPass: false }, { title: `/* \` */ debugger; \` \` + ''`, test(func, args) { return func(this.title, args); } }, { title: `function hello(){ debugger; } class Rectangle { constructor(height, width) { this.height = height; this.width = width; } } `, test(func, args) { return func(this.title, args); } } ]; let results = []; let failed = false; for (let i in tests) { if (tests.hasOwnProperty(i)) { let test = tests[i]; if (!failed) { const logs = []; args.log = value => { logs.push(value); }; let pass = test.test(findDebugger, args); results.push({ shouldPass: test.shouldPass, title: test.title, pass: pass }); if (!pass) { failed = true; logs.map(log); } } else { results.push({ shouldPass: test.shouldPass, title: test.title, pass: false }); } } } callback(results); } /** return code .replace(/\/\/.*|'.*?[^\\]'|".*"|`[\s\S]+`/) .match(/\bdebugger\b/) */ ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-debugger/hint.js ================================================ debugger; function hello() { debugger; } // There function hello3() { return 'debugger in a string'; } // There is no debugger function hello2() {} // there's no debugger function hello3() {} ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-debugger/remove-debugger.solved.ts ================================================ function removeDebuggerSolved( code, { babylon, babelTraverse, babelGenerator, log } ) { const ast = babylon.parse(code); babelTraverse(ast, { DebuggerStatement: node => { node.remove(); } }); return babelGenerator(ast).code; } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-debugger/remove-debugger.test.js ================================================ function testThings(findDebugger, callback, args) { const log = args.log; const tests = [ { title: `debugger`, test(func, args) { return '' === func(this.title, args); } }, { title: `debuggerStart();`, test(func, args) { return this.title === func(this.title, args); } }, { title: `//debugger console.log('hi');`, test(func, args) { return this.title === func(this.title, args); } }, { title: `'debugger';`, test(func, args) { return this.title === func(this.title, args); } }, { title: `'hi'; debugger; 'bye';`, test(func, args) { return ( `'hi'; 'bye';` === func(this.title, args) ); } }, { title: `'Fake //'; debugger; `, test(func, args) { return `'Fake //';` === func(this.title, args); } }, { title: `"debugger";`, test(func, args) { return this.title === func(this.title, args); } }, { title: `\` debugger\`;`, test(func, args) { return this.title === func(this.title, args); } }, { title: `' \\' debugger';`, test(func, args) { return this.title === func(this.title, args); } }, { title: `/* \` */ debugger; \` \` + '';`, test(func, args) { return !func(this.title, args).includes('debugger'); } }, { title: `function hello(){ debugger; }`, test(func, args) { return `function hello() {}` === func(this.title, args); } } ]; let results = []; let failed = false; for (let i in tests) { if (tests.hasOwnProperty(i)) { let test = tests[i]; if (!failed) { const logs = []; args.log = value => { logs.push(value); }; let pass = test.test(findDebugger, args); results.push({ title: test.title, pass: pass }); if (!pass) { failed = true; logs.map(log); } } else { results.push({ title: test.title, pass: false }); } } } callback(results); } /** return code .replace(/\/\/.*|'.*?[^\\]'|".*"|`[\s\S]+`/) .match(/\bdebugger\b/) */ ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-debugger/remove-debugger.ts ================================================ function removeDebugger(code, { babylon, babelTraverse, babelGenerator, log }) { const ast = babylon.parse(code); let hasDebugger = false; babelTraverse(ast, { DebuggerStatement: node => { hasDebugger = true; } }); return hasDebugger; } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-debugger/traverse-debugger-babel.solved.ts ================================================ function traverseDebuggerSolved(code, { babylon, babelTraverse, log }) { const ast = babylon.parse(code); let hasCode = false; babelTraverse(ast, { DebuggerStatement: () => { hasCode = true; } }); return hasCode; } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-debugger/traverse-debugger-babel.ts ================================================ function traverseDebugger(code, { babylon, babelTraverse, log }) { const ast = babylon.parse(code); } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-fit/find-fit.js ================================================ function findFit(code, { babylon, babelTraverse, babelGenerator, log }) { const ast = babylon.parse(code); babelTraverse(ast); return babelGenerator(ast).code; } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-fit/find-fit.solved.js ================================================ function findFit(code, { babylon, babelTraverse, babelGenerator, log }) { const ast = babylon.parse(code); babelTraverse(ast, { Identifier: ({ node, parentPath }) => { if (node.name === 'fit' && parentPath.isCallExpression()) { node.name = 'it'; } } }); return babelGenerator(ast).code; } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/find-fit/find-fit.test.js ================================================ function testThings(findDebugger, callback, args) { const log = args.log; const tests = [ { title: `describe('test', ()=>{ fit('test', ()=>{ expect(true).toBe(true); }) })`, test(func, args) { let result = func(this.title, args); return result && result.includes && !result.includes('fit'); } }, { title: `describe('test', ()=>{ fit('fits?', ()=>{ let fit = true; expect(fit).toBe(true); }) })`, test(func, args) { let result = func(this.title, args); return ( result && result.includes && !result.includes('fit(') && result.includes('let fit') ); } } ]; let results = []; let failed = false; for (let i in tests) { if (tests.hasOwnProperty(i)) { let test = tests[i]; if (!failed) { const logs = []; args.log = value => { logs.push(value); }; let pass = test.test(findDebugger, args); results.push({ title: test.title, pass: pass }); if (!pass) { failed = true; logs.map(log); } } else { results.push({ title: test.title, pass: false }); } } } callback(results); } /** return code .replace(/\/\/.*|'.*?[^\\]'|".*"|`[\s\S]+`/) .match(/\bdebugger\b/) */ ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/hello-world.json ================================================ { "type": "File", "start": 0, "end": 8, "loc": { "start": { "line": 1, "column": 0 }, "end": { "line": 1, "column": 8 } }, "program": { "type": "Program", "start": 0, "end": 8, "loc": { "start": { "line": 1, "column": 0 }, "end": { "line": 1, "column": 8 } }, "sourceType": "script", "body": [ { "type": "DebuggerStatement", "start": 0, "end": 8, "loc": { "start": { "line": 1, "column": 0 }, "end": { "line": 1, "column": 8 } } } ], "directives": [] }, "comments": [], "tokens": [ { "type": { "label": "debugger", "keyword": "debugger", "beforeExpr": false, "startsExpr": false, "rightAssociative": false, "isLoop": false, "isAssign": false, "prefix": false, "postfix": false, "binop": null, "updateContext": null }, "value": "debugger", "start": 0, "end": 8, "loc": { "start": { "line": 1, "column": 0 }, "end": { "line": 1, "column": 8 } } }, { "type": { "label": "eof", "beforeExpr": false, "startsExpr": false, "rightAssociative": false, "isLoop": false, "isAssign": false, "prefix": false, "postfix": false, "binop": null, "updateContext": null }, "start": 8, "end": 8, "loc": { "start": { "line": 1, "column": 8 }, "end": { "line": 1, "column": 8 } } } ] } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/it-lines/it-lines.js ================================================ function addItByLine( code, line, { babylon, babelTraverse, babelGenerator, log } ) { const ast = babylon.parse(code); babelTraverse(ast, { Identifier: ({ node, parentPath }) => { if (node.name === 'fit' && parentPath.isCallExpression()) { node.name = 'it'; } } }); return babelGenerator(ast).code; } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/it-lines/it-lines.solved.js ================================================ function addItByLine( code, line, { babylon, babelTraverse, babelGenerator, log } ) { const ast = babylon.parse(code); babelTraverse(ast, { Identifier: ({ node, parentPath }) => { if (node.name === 'it' && parentPath.isCallExpression()) { if (node.loc.start.line === line) { node.name = 'fit'; } } } }); return babelGenerator(ast).code; } ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/it-lines/it-lines.test.js ================================================ function testThings(findDebugger, callback, args) { const log = args.log; const tests = [ { title: `// Line 3 describe('test', ()=>{ it('test', ()=>{ expect(true).toBe(true); }) })`, test(func, args) { let result = func(this.title, 3, args); return result && result.includes && result.includes('fit'); } }, { title: `// Line 22 describe('test', ()=>{ it('test', ()=>{ expect(true).toBe(true); }) })`, test(func, args) { let result = func(this.title, 1, args); return result && result.includes && !result.includes('fit'); } } ]; let results = []; let failed = false; for (let i in tests) { if (tests.hasOwnProperty(i)) { let test = tests[i]; if (!failed) { const logs = []; args.log = value => { logs.push(value); }; let pass = test.test(findDebugger, args); results.push({ title: test.title, pass: pass }); if (!pass) { failed = true; logs.map(log); } } else { results.push({ title: test.title, pass: false }); } } } callback(results); } /** return code .replace(/\/\/.*|'.*?[^\\]'|".*"|`[\s\S]+`/) .match(/\bdebugger\b/) */ ================================================ FILE: apps/kirjs/src/app/modules/ast/samples/tricky.js ================================================ debugger; debuggerStart(); //debugger console.log('hi'); console.log('debugger'); `'hi'; debugger; 'bye'`; let x = 'Fake //'; ('debugger'); ` debugger\` ' \\' debugger' `; /* \` */ debugger; ` \` + ''`; function hello() { debugger; } console.log( 1, function hello() { console.log(function hi() { console.log(123); }); }, 3, 4, 5, 6 ); call(1, 2, 3, 4, 5, 5); describe('test', () => { fit('test', () => { expect(true).toBe(true); }); }); describe('test', () => { fit('fits?', () => { let fit = true; expect(fit).toBe(true); }); }); ================================================ FILE: apps/kirjs/src/app/modules/ast/size-picker/size-picker.component.css ================================================ .button:hover { background: #444444; color: white; } .button { border: 1px solid #000; border-radius: 50%; height: 1vw; width: 1vw; cursor: pointer; display: inline-block; text-align: center; line-height: 1vw; } ================================================ FILE: apps/kirjs/src/app/modules/ast/size-picker/size-picker.component.html ================================================ - {{ size }} + ================================================ FILE: apps/kirjs/src/app/modules/ast/size-picker/size-picker.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SizePickerComponent } from './size-picker.component'; describe('SizePickerComponent', () => { let component: SizePickerComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SizePickerComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SizePickerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/ast/size-picker/size-picker.component.ts ================================================ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; @Component({ selector: 'kirjs-size-picker', templateUrl: './size-picker.component.html', styleUrls: ['./size-picker.component.css'] }) export class SizePickerComponent { @Input() size = 28; @Input() step = 2; @Output() sizeChange = new EventEmitter(); } ================================================ FILE: apps/kirjs/src/app/modules/ast/size-picker/size-picker.module.ts ================================================ import { NgModule } from '@angular/core'; import { SizePickerComponent } from './size-picker.component'; @NgModule({ declarations: [SizePickerComponent], exports: [SizePickerComponent] }) export class SizePickerModule {} ================================================ FILE: apps/kirjs/src/app/modules/ast/test-set/babel-test-runner/babel-test-runner.component.css ================================================ :host { height: 100%; display: flex; } .runner { display: none; } .test { margin-bottom: 5px; background: #ff9a86; padding: 5px 10px; font-size: 3vw; } .test.pass { background: #b2ff37; } .mini { font-size: 30%; display: block; } pre { margin: 0; } .test.future.future { background: #e8e8e8; color: #ddd; } .wrapper { display: flex; height: 100%; } .test-point:hover { border: 3px black solid; } .test-point { width: 32px; height: 32px; border-radius: 50%; display: inline-block; margin-left: 5px; background: #999999; } .test-point.pass { background: #b2ff37; border: 1px solid #444444; } .test-point.current { background: #ff9a86; border: 1px solid #444444; } code.failing { color: #ff4e3d; } code { font-size: 3vw; margin-top: 4px; display: block; margin-bottom: 24px; } mat-card { width: 100%; height: 100%; } .label { color: #444; font-size: 1.5vw; } .summary .label { margin-bottom: 12px; } .summary { padding-bottom: 24px; } .results { padding-top: 24px; } .ast-full { background: #ffffff; position: fixed; left: 0; top: 0; width: 100vw; height: 100vh; padding: 20px; } kirjs-size-picker { margin-left: 20px; } ================================================ FILE: apps/kirjs/src/app/modules/ast/test-set/babel-test-runner/babel-test-runner.component.html ================================================
{{ log | json }}

Running {{ tests.length }} tests.

For the code:
{{ test.error }}
For the code:
{{test.code.trim()}}
Expected:
{{ test.expected | json }}
Actual:
{{ test.result | json }}
AST:
{{test.code.trim()}}
================================================ FILE: apps/kirjs/src/app/modules/ast/test-set/babel-test-runner/babel-test-runner.component.ts ================================================ import { Component, Input } from '@angular/core'; import * as babylon from 'babylon'; import * as types from 'babel-types'; import babelTraverse from '@babel/traverse'; import babelGenerator from '@babel/generator'; import { TestInfo } from '../../../../../../../codelab/src/app/shared/interfaces/test-info'; declare const require; @Component({ selector: 'kirjs-babel-test-runner', templateUrl: './babel-test-runner.component.html', styleUrls: ['./babel-test-runner.component.css'] }) export class BabelTestRunnerComponent { tests: Array = []; logs = []; @Input() files: any[]; @Input() showAst = false; scale = 10; showFull = false; firstFailing: TestInfo; displayedTest: TestInfo; run(files: Array) { this.logs = []; const args = { babylon, babelTraverse, babelGenerator, types, log: value => { this.logs.push(value); } }; const callback = result => { if (result) { this.tests = result; this.firstFailing = this.tests.find(test => !test.pass); } }; try { // tslint:disable const func = eval('(' + files[0].code + ')'); eval('(' + files[1].code + ')')(func, callback, args); } catch (e) { console.log(e); } } firstFailingIndex() { const firstFailing = this.tests.findIndex(i => !i.pass); return firstFailing === -1 ? this.tests.length : firstFailing; } runTests() { this.run(this.files); } ngOnInit() { this.runTests(); } } ================================================ FILE: apps/kirjs/src/app/modules/ast/test-set/test-set.component.css ================================================ .show-solution .icon { visibility: hidden; } .show-solution:hover .icon { visibility: visible; } ================================================ FILE: apps/kirjs/src/app/modules/ast/test-set/test-set.component.html ================================================
🙀
================================================ FILE: apps/kirjs/src/app/modules/ast/test-set/test-set.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TestSetComponent } from './test-set.component'; describe('TestSetComponent', () => { let component: TestSetComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [TestSetComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TestSetComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/ast/test-set/test-set.component.ts ================================================ import { Component, Input } from '@angular/core'; @Component({ selector: 'kirjs-test-set', templateUrl: './test-set.component.html', styleUrls: ['./test-set.component.css'] }) export class TestSetComponent { @Input() files = []; @Input() fontSize = 30; @Input() showAst = false; } ================================================ FILE: apps/kirjs/src/app/modules/binary/angular-flags/angular-flags.component.css ================================================ .flags { font-size: 2vw; white-space: pre-wrap; } ================================================ FILE: apps/kirjs/src/app/modules/binary/angular-flags/angular-flags.component.html ================================================
{{ flag.name }} = {{ flag.value }}

{{ number }}

================================================ FILE: apps/kirjs/src/app/modules/binary/angular-flags/angular-flags.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { AngularFlagsComponent } from './angular-flags.component'; describe('AngularFlagsComponent', () => { let component: AngularFlagsComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [AngularFlagsComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(AngularFlagsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/angular-flags/angular-flags.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-angular-flags', templateUrl: './angular-flags.component.html', styleUrls: ['./angular-flags.component.css'] }) export class AngularFlagsComponent implements OnInit { number = 124; flags = [ { checked: false, name: 'None', value: 0 }, { checked: false, name: 'TypeElement', value: 1 }, { checked: false, name: 'TypeText', value: 2 }, { checked: false, name: 'ProjectedTemplate', value: 4 }, { checked: false, name: 'CatRenderNode', value: 3 }, { checked: false, name: 'TypeNgContent', value: 8 }, { checked: false, name: 'TypePipe', value: 16 }, { checked: false, name: 'TypePureArray', value: 32 }, { checked: false, name: 'TypePureObject', value: 64 }, { checked: false, name: 'TypePurePipe', value: 128 }, { checked: false, name: 'CatPureExpression', value: 224 }, { checked: false, name: 'TypeValueProvider', value: 256 }, { checked: false, name: 'TypeClassProvider', value: 512 }, { checked: false, name: 'TypeFactoryProvider', value: 1024 }, { checked: false, name: 'TypeUseExistingProvider', value: 2048 }, { checked: false, name: 'LazyProvider', value: 4096 }, { checked: false, name: 'PrivateProvider', value: 8192 }, { checked: false, name: 'TypeDirective', value: 16384 }, { checked: false, name: 'Component', value: 32768 }, { checked: false, name: 'CatProviderNoDirective', value: 3840 }, { checked: false, name: 'CatProvider', value: 20224 }, { checked: false, name: 'OnInit', value: 65536 }, { checked: false, name: 'OnDestroy', value: 131072 }, { checked: false, name: 'DoCheck', value: 262144 }, { checked: false, name: 'OnChanges', value: 524288 }, { checked: false, name: 'AfterContentInit', value: 1048576 }, { checked: false, name: 'AfterContentChecked', value: 2097152 }, { checked: false, name: 'AfterViewInit', value: 4194304 }, { checked: false, name: 'AfterViewChecked', value: 8388608 }, { checked: false, name: 'EmbeddedViews', value: 16777216 }, { checked: false, name: 'ComponentView', value: 33554432 }, { checked: false, name: 'TypeContentQuery', value: 67108864 }, { checked: false, name: 'TypeViewQuery', value: 134217728 }, { checked: false, name: 'StaticQuery', value: 268435456 }, { checked: false, name: 'DynamicQuery', value: 536870912 }, { checked: false, name: 'TypeModuleProvider', value: 1073741824 }, { checked: false, name: 'CatQuery', value: 201326592 }, { checked: false, name: 'Types', value: 201347067 } ]; checked: string[]; constructor() { this.syncCheckBoxes(); } syncCheckBoxes() { this.flags.forEach(f => (f.checked = !!(this.number & f.value))); } ngOnInit() {} handleClick(checked: boolean, value: number) { this.number = this.number | value; this.syncCheckBoxes(); } } ================================================ FILE: apps/kirjs/src/app/modules/binary/ascii/ascii.component.css ================================================ .parent { display: grid; grid-template-columns: repeat(12, 1fr); } .item { background: #fff; border-right: 1px #444 dotted; border-bottom: 1px #444 dotted; padding: 0.5vw; display: flex; justify-content: space-between; } .key { vertical-align: top; font-size: 2vw; color: #666; } .value { font-size: 3.5vw; } select { border: 1px #444 solid; background: #ffffff; font-size: 3vw; } ================================================ FILE: apps/kirjs/src/app/modules/binary/ascii/ascii.component.html ================================================

Display character table for:

{{ item.key }} {{ item.value }}
================================================ FILE: apps/kirjs/src/app/modules/binary/ascii/ascii.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { AsciiComponent } from './ascii.component'; describe('AsciiComponent', () => { let component: AsciiComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [AsciiComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(AsciiComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/ascii/ascii.component.ts ================================================ import { Component, Input, OnChanges, OnInit, SimpleChanges } from '@angular/core'; function encode(from: number, to: number, encoding: string) { return new TextDecoder(encoding) .decode(new Uint8Array(to - from).map((a, i) => i + from).buffer as any) .split('') .map((value, i) => ({ key: i + from, value })); } const layouts = {}; @Component({ selector: 'kirjs-ascii', templateUrl: './ascii.component.html', styleUrls: ['./ascii.component.css'] }) export class AsciiComponent implements OnChanges { @Input() param: string; encodings = [ { key: 'ascii', value: encode(33, 128, 'ascii') }, { key: 'ascii - Page 2', value: encode(128, 255, 'ascii') }, { key: 'windows-1251', value: encode(128, 255, 'windows-1251') }, { key: 'KOI8-R', value: encode(128, 255, 'KOI8-R') }, { key: 'utf-8', value: encode(1000, 1255, 'utf-16') } ]; encoding = this.encodings[0]; constructor() { // d = new TextDecoder('windows-125').decode(new Uint8Array(255).map((a,i)=>i).buffer) } ngOnChanges(changes: SimpleChanges): void { if ('param' in changes) { this.encoding = this.encodings.find(a => a.key === this.param); } } } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-flat/binary-flat.component.css ================================================ .wrapper { word-break: break-all; padding: 40px; } .binary { letter-spacing: 2vw; } .item, .detail-item { font-size: 3vw; } .item-1 { background: #eee; } .detail-item { padding: 20px; } .detail { width: 60%; word-break: break-all; } .field-header { font-size: 2vw; } .raw-value { width: 40%; font-size: 2vw; word-break: break-all; padding-right: 1vw; margin-right: 1vw; border-right: 1px #444 solid; } button { display: block; width: 100%; background: #e51400; color: #fff; padding: 1vw; font-size: 4vw; } h3 { font-size: 2vw; } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-flat/binary-flat.component.html ================================================
{{ item.rawValue }}
{{ item.rawValue }}
{{ item.type }}
{{ item.name }}

{{ item.value }}

================================================ FILE: apps/kirjs/src/app/modules/binary/binary-flat/binary-flat.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BinaryFlatComponent } from './binary-flat.component'; describe('BinaryFlatComponent', () => { let component: BinaryFlatComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [BinaryFlatComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BinaryFlatComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-flat/binary-flat.component.ts ================================================ import { Component, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges } from '@angular/core'; import { BinaryParser } from '../parser/binary-parser'; import { StringBinaryReader } from '../parser/readers/string-reader'; import { BinaryParentComponent } from '../binary-view/binary-parent/binary-parent.component'; export function flatten( structure: any[], nesting = 0, parent = null, path = [] ) { return structure.reduce((result, item) => { if (item.type === 'object' || item.type === 'array') { item.data = false; item.nesting = nesting; item.className = 'tbd'; result = result .concat(item) .concat( flatten( item.value, nesting + 1, item, item.name ? [...path, item.name] : [...path] ) ); } else { item.data = true; item.nesting = nesting; item.parent = parent || item; item.root = item.parent.root || item.parent; item.path = path; item.className = path.map(p => 'parent-' + p).join(' '); result.push(item); } return result; }, []); } @Component({ selector: 'kirjs-binary-flat', templateUrl: './binary-flat.component.html', styleUrls: ['./binary-flat.component.css'] }) export class BinaryFlatComponent implements OnChanges { @Input() parser: BinaryParser; @Input() binary: string; @Output() updateBinary = new EventEmitter(); detailIndex = 30; structure: { start: number }; constructor(private readonly root: BinaryParentComponent) {} update(event, item) { this.root.update(item, event.target.textContent); } ngOnChanges(changes: SimpleChanges): void { if ('binary' in changes) { this.structure = flatten( this.parser.readOrdered(new StringBinaryReader(this.binary)).value ); } } } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-gif/binary-gif.component.css ================================================ img { image-rendering: pixelated !important; } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-gif/binary-gif.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-gif/binary-gif.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BinaryGifComponent } from './binary-gif.component'; describe('BinaryGifComponent', () => { let component: BinaryGifComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [BinaryGifComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BinaryGifComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-gif/binary-gif.component.ts ================================================ import { Component, Input } from '@angular/core'; @Component({ selector: 'kirjs-binary-gif', templateUrl: './binary-gif.component.html', styleUrls: ['./binary-gif.component.css'] }) export class BinaryGifComponent { gif: string; @Input() set binary(binary: string) { const binaries = binary.match(/.{8}/g); const recombined = new Uint8Array(binaries.map(a => parseInt(a, 2))); const b64encoded = btoa( Array.from(recombined) .map(a => String.fromCharCode(a)) .join('') ); this.gif = 'data:image/gif;base64,' + b64encoded; } } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-inline/binary-display/binary-display.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-inline/binary-display/binary-display.component.html ================================================
{{ bin }}
================================================ FILE: apps/kirjs/src/app/modules/binary/binary-inline/binary-display/binary-display.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BinaryDisplayComponent } from './binary-display.component'; describe('BinaryDisplayComponent', () => { let component: BinaryDisplayComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [BinaryDisplayComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BinaryDisplayComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-inline/binary-display/binary-display.component.ts ================================================ import { Component, Input } from '@angular/core'; @Component({ selector: 'kirjs-binary-display', templateUrl: './binary-display.component.html', styleUrls: ['./binary-display.component.css'] }) export class BinaryDisplayComponent { binaries = []; @Input() set binary(binary: string) { this.binaries = binary.match(/.{8}/g); } } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-inline/binary-inline.component.css ================================================ .wrapper { display: grid; grid-template-columns: 200px 100px 100%; font-size: 20px; } .bin-block { word-break: break-all; } [name='section-type'] { font-size: 30px; font-weight: 300; } .cell { padding: 10px; } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-inline/binary-inline.component.html ================================================
{{ item.displayValue || item.value }}
{{ item.name }}
description:{{ item.description }}
================================================ FILE: apps/kirjs/src/app/modules/binary/binary-inline/binary-inline.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BinaryInlineComponent } from './binary-inline.component'; describe('BinaryInlineComponent', () => { let component: BinaryInlineComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [BinaryInlineComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BinaryInlineComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-inline/binary-inline.component.ts ================================================ import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; import { flatten } from '../binary-flat/binary-flat.component'; import { StringBinaryReader } from '../parser/readers/string-reader'; import { BinaryParser } from '../parser/binary-parser'; @Component({ selector: 'kirjs-binary-inline', templateUrl: './binary-inline.component.html', styleUrls: ['./binary-inline.component.css'] }) export class BinaryInlineComponent implements OnChanges { @Input() filterClassName = /./; @Input() parser: BinaryParser; @Input() binary: string; structure: any[]; ngOnChanges(changes: SimpleChanges): void { if ('binary' in changes) { try { this.structure = flatten( this.parser.readOrdered(new StringBinaryReader(this.binary)).value ).filter(a => a.className.match(this.filterClassName)); } catch (e) { console.log(e); } } } } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-inline/binary-inline.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { BinaryInlineComponent } from './binary-inline.component'; import { BinaryDisplayComponent } from './binary-display/binary-display.component'; @NgModule({ declarations: [BinaryInlineComponent, BinaryDisplayComponent], exports: [BinaryInlineComponent], imports: [CommonModule] }) export class BinaryInlineModule {} ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-parser-demo/binary-parser-demo.component.css ================================================ .error::before { color: #e51400; content: '🙀'; } .error { color: #e51400; border: 1px #e51400 solid; border-radius: 12px; font-size: 3vw; padding: 2vw; display: block; } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-parser-demo/binary-parser-demo.component.html ================================================
{{ i }}
{{ error }}
================================================ FILE: apps/kirjs/src/app/modules/binary/binary-parser-demo/binary-parser-demo.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BinaryParserDemoComponent } from './binary-parser-demo.component'; describe('BinaryParserDemoComponent', () => { let component: BinaryParserDemoComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [BinaryParserDemoComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BinaryParserDemoComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-parser-demo/binary-parser-demo.component.ts ================================================ import { Component, EventEmitter, Input, OnInit } from '@angular/core'; import { Parser } from 'binary-parser'; import { Buffer } from 'buffer'; import { bin2hex } from '../shared'; (window as any).Buffer = Buffer; @Component({ selector: 'kirjs-binary-parser-demo', templateUrl: './binary-parser-demo.component.html', styleUrls: ['./binary-parser-demo.component.css'] }) export class BinaryParserDemoComponent implements OnInit { @Input() helpers; code = ''; @Input() filterClassName = /./; result = ''; @Input() binary = ''; error = ''; onCodeChange = new EventEmitter(); constructor() {} ngOnInit() { this.code = this.helpers[0]; this.generateCode(); } generateCode() { this.onCodeChange.emit(this.code); this.error = ''; try { const parser = new Function( 'Parser', 'const parser = ' + this.code + '; return parser;' )(Parser); this.result = JSON.stringify( parser.parse(Buffer.from(bin2hex(this.binary), 'hex')), null, ' ' ); } catch (e) { this.error = e.message; } } } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-plain/binary-plain.component.css ================================================ .wrapper { display: flex; flex-direction: column; } button { font-size: inherit; } .legend { width: 200px; font-size: 2vw; padding: 3vw 0; display: flex; } .legend .item { margin-right: 10px; background: #dddddd; border-radius: 4px; padding: 0.5vw 1vw; } .enums .enums { background: #ff3302; color: black; } .string .string { background: #ffaf00; color: black; } .const .const { background: #aaaaaa; color: black; } .number .number { background: #00ff51; color: black; } .hex .color, .hex .hex { background: #00a4ff; color: black; } .boolean .boolean { background: #f600ff; color: black; } .binary { white-space: normal; font-size: 3vw; word-break: break-all; padding-right: 3vw; } .spacing .binary { display: flex; flex-wrap: wrap; } .spacing .bin-block { margin-right: 4vw; white-space: nowrap; } .groups .parent-palette { background: #00ff51; } .groups .parent-header { background: #ffaf00; } .groups .parent-extensions { background: #00a4ff; } .spacing { line-height: 50px; } .mini .legend { font-size: 1.2vw; padding: 0; } .mini .legend .item { padding: 0.2vw; height: 2vw; } .mini input[type='checkbox'] { zoom: 2 !important; } .mini .spacing { line-height: 2vw; } .item-header:first-child { margin-top: 0; } .item-header { width: 100%; font-size: 5vw; display: block; margin-bottom: 2vw; margin-top: 4vw; } .selected { width: 100%; border: 1px #ddd solid; padding: 1vw; } .label { display: inline-block; width: 24vw; flex-shrink: 0; } .meta { margin-top: 2vw; margin-bottom: 1vw; } .selected .bin-block { font-size: 4vw; } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-plain/binary-plain.component.html ================================================
{{ item.name }}
{{ item.rawValue }} - {{ hackHack[item.value] }}
{{ item.rawValue }}
value:{{ item.value }}
name:{{ item.name }}
description:{{ item.description }}
================================================ FILE: apps/kirjs/src/app/modules/binary/binary-plain/binary-plain.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BinaryPlainComponent } from './binary-plain.component'; describe('BinaryPlainComponent', () => { let component: BinaryPlainComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [BinaryPlainComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BinaryPlainComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-plain/binary-plain.component.ts ================================================ import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core'; import { BinaryParser } from '../parser/binary-parser'; import { StringBinaryReader } from '../parser/readers/string-reader'; import { flatten } from '../binary-flat/binary-flat.component'; @Component({ selector: 'kirjs-binary-plain', templateUrl: './binary-plain.component.html', styleUrls: ['./binary-plain.component.css'] }) export class BinaryPlainComponent implements OnChanges { @Output() updateChunk = new EventEmitter(); @Input() parser: BinaryParser; @Input() highlightGroups = false; @Input() filterClassName = /./; @Input() mini = false; @Input() showPopups = false; hackHack = { 0x01: 'Types', 0x02: 'Import', 0x03: 'Function', 0x05: 'Table', 0x07: 'Export', 0x08: 'Start', 0x0a: 'Code' }; show = []; types = ['boolean', 'number', 'hex', 'string', 'const', 'enums']; @Input() spacing = false; @Input() highlightedMap = this.types.reduce((r, v) => { r[v] = false; return r; }, {}); structure: any[]; @Input() binary: string; get highlighted() { return Object.keys(this.highlightedMap) .filter(key => this.highlightedMap[key]) .join(' '); } ngOnChanges() { if (this.binary && this.parser) { try { this.structure = flatten( this.parser.readOrdered(new StringBinaryReader(this.binary)).value ).filter(a => a.className.match(this.filterClassName)); } catch (e) { console.log(e); } } } update(chunk: any, value: any) { this.updateChunk.emit({ chunk, value }); } } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/array/array.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/array/array.component.html ================================================ {{ item.name }} ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/array/array.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ArrayComponent } from './array.component'; describe('ArrayComponent', () => { let component: ArrayComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ArrayComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(ArrayComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/array/array.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-array', templateUrl: './array.component.html', styleUrls: ['./array.component.css'] }) export class ArrayComponent implements OnInit { @Input() data; @Input() showMeta: boolean; constructor() {} trackBy(i, data) { return data.index; } ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/binary-parent/binary-parent.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/binary-parent/binary-parent.component.scss ================================================ :host ::ng-deep { .block { display: block; font-size: 30px; margin-left: 40px; } .name { width: 200px; } kirjs-object .block > .name { display: block; } } .block { margin-left: 0; font-size: 40px; } :host ::ng-deep { input { background: transparent; font-size: inherit; border: 0 solid; font-family: Monaco, 'Lucida Console', monospace; } } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/binary-parent/binary-parent.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BinaryParentComponent } from './binary-parent.component'; describe('BinaryParentComponent', () => { let component: BinaryParentComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [BinaryParentComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BinaryParentComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/binary-parent/binary-parent.component.ts ================================================ import { Component, EventEmitter, Input, OnChanges, OnInit, Output } from '@angular/core'; import { BinaryParser } from '../../parser/binary-parser'; import { StringBinaryReader } from '../../parser/readers/string-reader'; @Component({ selector: 'kirjs-binary-parent', templateUrl: './binary-parent.component.html', styleUrls: ['./binary-parent.component.scss'] }) export class BinaryParentComponent implements OnInit, OnChanges { @Input() showMeta = true; @Input() parser: BinaryParser; @Input() binary: string; @Input() type = 'structure'; @Input() spacing = false; @Output() updateBinary = new EventEmitter(); structure: any; constructor() {} ngOnInit() { this.regenerate(); } ngOnChanges() { this.regenerate(); } regenerate() { this.structure = this.parser.readOrdered( new StringBinaryReader(this.binary) ); } update(chunk, value) { const len = chunk.end - chunk.start; value = value.padEnd(len, 0).slice(0, len); this.binary = this.binary.slice(0, chunk.start) + value + this.binary.substr(chunk.end); this.updateBinary.emit(this.binary); this.regenerate(); } updatePart(chunk, value) { const len = chunk.end - chunk.start; value = value.padEnd(len, 0).slice(0, len); this.binary = this.binary.slice(0, chunk.start) + value + this.binary.substr(chunk.end); // } } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/binary-view.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { BlockComponent } from './block/block.component'; import { ObjectComponent } from './object/object.component'; import { BitsComponent } from './bits/bits.component'; import { StringComponent } from './string/string.component'; import { NumberComponent } from './number/number.component'; import { ArrayComponent } from './array/array.component'; import { ColorComponent } from './color/color.component'; import { BinaryParentComponent } from './binary-parent/binary-parent.component'; import { HexComponent } from './hex/hex.component'; import { InlineComponent } from './inline/inline.component'; import { InlineRootComponent } from './inline-root/inline-root.component'; import { BinaryFlatComponent } from '../binary-flat/binary-flat.component'; import { BinaryPlainComponent } from '../binary-plain/binary-plain.component'; import { FormsModule } from '@angular/forms'; @NgModule({ imports: [CommonModule, FormsModule], declarations: [ BlockComponent, ObjectComponent, BitsComponent, StringComponent, NumberComponent, ArrayComponent, ColorComponent, BinaryParentComponent, BinaryFlatComponent, HexComponent, InlineComponent, InlineRootComponent, BinaryPlainComponent ], exports: [ BinaryFlatComponent, BinaryPlainComponent, BlockComponent, ObjectComponent, BitsComponent, StringComponent, NumberComponent, ArrayComponent, ColorComponent, BinaryParentComponent, HexComponent ], entryComponents: [ BinaryFlatComponent, BlockComponent, ObjectComponent, BitsComponent, StringComponent, NumberComponent, ArrayComponent, ColorComponent, HexComponent, BinaryPlainComponent ] }) export class BinaryViewModule {} ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/bits/bits.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/bits/bits.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/bits/bits.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BitsComponent } from './bits.component'; describe('BitsComponent', () => { let component: BitsComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [BitsComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BitsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/bits/bits.component.ts ================================================ import { Component, Input } from '@angular/core'; import { BinaryParentComponent } from '../binary-parent/binary-parent.component'; @Component({ selector: 'kirjs-bits', templateUrl: './bits.component.html', styleUrls: ['./bits.component.css'] }) export class BitsComponent { @Input() data; @Input() showMeta = false; constructor(private readonly root: BinaryParentComponent) {} update(value) { this.root.update(this.data, Number(value).toString()); } } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/block/block.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/block/block.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/block/block.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BlockComponent } from './block.component'; describe('BlockComponent', () => { let component: BlockComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [BlockComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BlockComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/block/block.component.ts ================================================ import { ChangeDetectorRef, Component, ComponentFactoryResolver, Input, OnChanges, OnInit, ViewContainerRef } from '@angular/core'; import { ObjectComponent } from '../object/object.component'; import { BitsComponent } from '../bits/bits.component'; import { StringComponent } from '../string/string.component'; import { NumberComponent } from '../number/number.component'; import { ArrayComponent } from '../array/array.component'; import { ColorComponent } from '../color/color.component'; import { HexComponent } from '../hex/hex.component'; const componentMap = { object: ObjectComponent, bits: BitsComponent, string: StringComponent, number: NumberComponent, array: ArrayComponent, color: ColorComponent, hex: HexComponent }; @Component({ selector: 'kirjs-block', templateUrl: './block.component.html', styleUrls: ['./block.component.css'] }) export class BlockComponent implements OnInit, OnChanges { @Input() data: any; @Input() showMeta: boolean; private componentRef: any; constructor( private vcr: ViewContainerRef, private readonly cdr: ChangeDetectorRef, private componentFactoryResolver: ComponentFactoryResolver ) {} ngOnChanges(changes) { if (this.componentRef && changes.showMeta) { this.componentRef.instance.showMeta = changes.showMeta.currentValue; } if (this.componentRef && changes.data) { this.componentRef.instance.data = changes.data.currentValue; } this.cdr.detectChanges(); } ngOnInit() { if (!componentMap[this.data.type]) { // tslint:disable-next-line:no-debugger debugger; } const cf = this.componentFactoryResolver.resolveComponentFactory( componentMap[this.data.type] ); this.vcr.clear(); this.componentRef = this.vcr.createComponent(cf) as any; this.componentRef.instance.data = this.data; this.componentRef.instance.showMeta = this.showMeta; } } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/color/color.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/color/color.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/color/color.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ColorComponent } from './color.component'; describe('ColorComponent', () => { let component: ColorComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ColorComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(ColorComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/color/color.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; import { BinaryParentComponent } from '../binary-parent/binary-parent.component'; @Component({ selector: 'kirjs-color', templateUrl: './color.component.html', styleUrls: ['./color.component.css'] }) export class ColorComponent implements OnInit { color: string; constructor(private readonly root: BinaryParentComponent) {} private _data: any; get data() { return this._data; } @Input() set data(data) { this._data = data; this.color = data.value.toString(16).padStart(6, 0); } update(value) { const val = (parseInt(value.slice(1), 16).toString(2) as any).padStart( 24, 0 ); this.root.update(this._data, val); } updateBinary(binary) { this.root.update(this.data, binary); } ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/hex/hex.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/hex/hex.component.html ================================================ {{ data.rawValue }} ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/hex/hex.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { HexComponent } from './hex.component'; describe('HexComponent', () => { let component: HexComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [HexComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(HexComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/hex/hex.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-hex', templateUrl: './hex.component.html', styleUrls: ['./hex.component.css'] }) export class HexComponent implements OnInit { @Input() data; constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/inline/inline.component.css ================================================ span { font-size: 50px; word-wrap: break-word; } .header { background: #52ff93; } .palette { background: #ffb71a; } .extensions { background: #61e9ff; } .item:hover { box-shadow: 0px 0px 4px 4px #444; cursor: pointer; } input { background: transparent; border: 0 solid; font-size: inherit; } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/inline/inline.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/inline/inline.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { InlineComponent } from './inline.component'; describe('InlineComponent', () => { let component: InlineComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [InlineComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(InlineComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/inline/inline.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; import { InlineRootComponent } from '../inline-root/inline-root.component'; import { BinaryParentComponent } from '../binary-parent/binary-parent.component'; @Component({ selector: 'kirjs-inline', templateUrl: './inline.component.html', styleUrls: ['./inline.component.css'] }) export class InlineComponent implements OnInit { isArray: boolean; private value: any; constructor( private readonly root: InlineRootComponent, private readonly binaryRoot: BinaryParentComponent ) {} private _data: any; get data() { return this._data; } @Input() set data(data) { this._data = data; this.value = data.value; this.isArray = Array.isArray(data.value); } updateValue(value) { this.binaryRoot.update(this.data, value); } ngOnInit() {} display() { this.root.display(this.data); } } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/inline-root/inline-root.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/inline-root/inline-root.component.html ================================================ {{ displayData | json }}

{{ displayData.name }}

{{ displayData.value }}

================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/inline-root/inline-root.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { InlineRootComponent } from './inline-root.component'; describe('InlineRootComponent', () => { let component: InlineRootComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [InlineRootComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(InlineRootComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/inline-root/inline-root.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-inline-root', templateUrl: './inline-root.component.html', styleUrls: ['./inline-root.component.css'] }) export class InlineRootComponent implements OnInit { @Input() data: any; displayData: any; constructor() {} ngOnInit() {} display(displayData: any) { this.displayData = displayData; } } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/number/number.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/number/number.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/number/number.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { NumberComponent } from './number.component'; describe('NumberComponent', () => { let component: NumberComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [NumberComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(NumberComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/number/number.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; import { BinaryParentComponent } from '../binary-parent/binary-parent.component'; @Component({ selector: 'kirjs-number', templateUrl: './number.component.html', styleUrls: ['./number.component.css'] }) export class NumberComponent implements OnInit { @Input() data: any; @Input() showMeta = false; constructor(private readonly root: BinaryParentComponent) {} update(value) { const val = (Number(value).toString(2) as any).padStart( this.data.length, 0 ); this.root.update(this.data, val.slice(8) + val.slice(0, 8)); } updateBinary(binary) { this.root.update(this.data, binary); } ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/object/object.component.css ================================================ .show-meta { padding: 4px; margin-right: 4px; margin-top: 4px; border: 1px #ddd solid; border-radius: 10px; font-size: 2vw; } .name { display: none; } .show-meta .name { display: inline-block; } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/object/object.component.html ================================================ {{ item.name }} ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/object/object.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ObjectComponent } from './object.component'; describe('ObjectComponent', () => { let component: ObjectComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ObjectComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(ObjectComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/object/object.component.ts ================================================ import { ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-object', templateUrl: './object.component.html', styleUrls: ['./object.component.css'] }) export class ObjectComponent implements OnInit { @Input() data: any; @Input() showMeta = true; constructor(private cdr: ChangeDetectorRef) {} ngOnInit() {} trackBy(i, data) { return data.name; } init() { this.cdr.detectChanges(); } } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/string/string.component.css ================================================ :host { word-break: break-all; } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/string/string.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/string/string.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { StringComponent } from './string.component'; describe('StringComponent', () => { let component: StringComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [StringComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(StringComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/binary-view/string/string.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; import { BinaryParentComponent } from '../binary-parent/binary-parent.component'; @Component({ selector: 'kirjs-string', templateUrl: './string.component.html', styleUrls: ['./string.component.css'] }) export class StringComponent implements OnInit { get data() { return this._data; } @Input() set data(data) { this._data = data; } @Input() showMeta = false; _data: any; constructor(private readonly root: BinaryParentComponent) {} updateBinary(binary) { this.root.update(this.data, binary); } update(value) { const val = value .split('') .map(a => a.charCodeAt(0)) .map(a => a.toString(2)) .map(a => (a as any).padStart(8, 0)) .join(''); this.root.update(this.data, val); } ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary.component.html ================================================

@kirjs

️JavaScript ❤️ Binary

kirjs.com/binary/0

If you can't read the numbers below, move closer or go to kirjs.com/binary/0

{{ binaryLittleGif.substr(0, 200) }}

Binary is hidden behind many layers of abstraction

In this talk:

  • Binary in files (images, video, other media, zip)
  • Binary for data transfer (instead of JSON)
  • Javascript using binary in memory

Let's see what's inside of gif


{{ chikinGif }}
{{ littleGif }}
{{ binaryLittleGif }}

Binary data makes no sense without a schema

Binary data 01001110 01101111 01110110 01101001 00100000 01110011 01100001 01100100
+
Schema Lol, what's this?
=
Numbers? Picture? Archive? Text?

Binary data makes no sense without a schema

Binary data 01001110 01101111 01110110 01101001 00100000 01110011 01100001 01100100
+
Schema This is UTF-8 string!
=
Novi Sad
GRAPHICS INTERCHANGE FORMAT(sm)

Version 89a

(c)1987,1988,1989,1990

Copyright
CompuServe Incorporated
Columbus, Ohio
      

Header

  • Contains size, global pallets specs, transparency.

Palette (Optional, size defined )

  • Contains indexed colors

Extensions (Optional)

  • Actual image data
  • Image Control
  • Animation Control
  • Comments

Convert binary to decimal with JavaScript

Convert decimal to binary

Convert binary to hexadecimal with JavaScript

Convert hexadecimal to binary with JavaScript

ПЕЛЬМЕНЬ

ПЕЛЬМЕНЬ

ПЕЉМЕЊ

┌─┬┐  ╔═╦╗  ╓─╥╖  ╒═╤╕
│ ││  ║ ║║  ║ ║║  │ ││
├─┼┤  ╠═╬╣  ╟─╫╢  ╞═╪╡
└─┴┘  ╚═╩╝  ╙─╨╜  ╘═╧╛
┌───────────────────┐
│  ╔═══╗            │
│  ╚═╦═╝            │
╞═╤══╩══╤═══════════╡
│ ├──┬──┤           │
│ └──┴──┘           │
└───────────────────┘
    

Get charcode from string

Get letter from charcode

Parsing binary(with binary-parser)

Gif facts

  • Image size: 1х1 to 65535х65535
  • Colors: 2 - 256
  • True color gifs are possible
  • Max number of animation frames - unlimited
  • Animation delay 1/100 - 655 seconds
  • There's a plain text extension
  • 24 pages + 12 pages appendix in gif89 standard

File header constants

  • Gif - GIF87a (or GIF89a)
  • Jpeg - begin with ‘FF D8‘ and end with ‘FF D9'
  • Java class - CAFEBABE
  • ZIP files begin with ‘PK‘ (50 4B)
  • PDF files start with ‘%PDF‘ (25 50 44 46)
  • PNG image files begin with “\211 P N G \r \n 32 \n” (89 50 4E 47 0D 0A 1A 0A)
  • HTTP2 - PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n

Big library of binary formats

https://formats.kaitai.io/

Can we use binary instead of JSON?

Serialized Size

JSON

  • Bigger

Binary

  • Smaller

Serialization speed

JSON

  • Slower

Binary

  • Faster

Debugging

JSON

  • Easy to understand
  • Human readable

Binary

  • Requires special tooling

Tooling

JSON

  • For JS works out of the box

Binary

  • Requires special tooling

Schema

JSON

  • No schema

Binary

  • Needs a schema
  • Comes with type checking
  • Comes with validation

Existing solutions

Thought experiment

Memory management in JavaScript

Let's see how memory works in typed arrays

Angular Component Flags (bitmask)

ReactDOM Flags (bitmask)

The end:

  • Binary in files (images, video, other media, zip)
  • Binary for data transfer (instead of JSON)
  • Javascript using binary in memory

@kirjs

Binary ❤️ JavaScript

kirjs.com/binary/0

Thanks @andrey_sitnik, @_bmsdave, @NisamProgramer for review

================================================ FILE: apps/kirjs/src/app/modules/binary/binary.component.scss ================================================ h1 { font-size: 5vw; } ::ng-deep feedback-widget { position: absolute; right: 0; bottom: 140px; display: block; width: 32px; height: 32px; z-index: 100; .feedback-button { width: 24px; height: 24px; } } #font-size b { font-size: 10vw; margin-bottom: 6vw; display: block; } :host { .binary-presentation ::ng-deep { .slide [intro] { background: url(pics/kirjs.webp); background-size: 100vh !important; padding-left: 3vw; h2 { font-size: 3vw; } h1, h2, h3 { text-align: left; margin-left: 100vh; margin-bottom: 2vw; } h3 { font-size: 3vw; } } .slide [json] { background: url(pics/p2.jpg); } .slide [krakoziabry] { background: url(pics/krakoziabry.jpg); background-size: cover; } .slide [endianness] { background: url(pics/endianness.png); background-size: 80vw !important; } .slide [pelmen] { background: url(pics/pelmeni.jpg); background-size: cover; h1 { font-size: 10vw; text-shadow: 0 0 1vw #ffffff; } } .slide [kaitai] { background: url(pics/kaitai.png); background-size: 400px 400px !important; background-color: #fff; h1 { font-size: 3vw; } h2 { text-align: center; font-size: 2vw; } } .slide [gif] { background: url(pics/zoom.gif); } .slide #binary-abstraction { background: url(pics/binary-abstraction.jpg); } .slide [message], [gif] .slide { background: url(pics/gdg-binary.jpg); } .slide [gif-bg], [gif-bg] .slide { background: url(pics/little.gif); } .slide [bg], [bg] .slide { background-repeat: no-repeat; background-size: cover; } .slide [vs] { display: flex; align-items: center; justify-content: center; } .slide h1 { font-size: 5vw; color: #444; text-align: center; } h2 { font-size: 4vw; margin-bottom: 2vw; } [bg] h1 { margin-top: 40px; font-size: 10vw; text-shadow: 0 3px 20px #fff, 0 -3px 20px #fff, -3px 0 20px #fff, 3px 0 20px #fff; } [bg] h2 { font-size: 6vw; text-shadow: 0 3px 20px #fff, 0 -3px 20px #fff, -3px 0 20px #fff, 3px 0 20px #fff; } td { font-size: 2vw; font-weight: 300; padding: 1vw; } } [bg] h1 b, [bg] h2 b { background: transparent; font-weight: bold; } } .compare-json { width: 50%; } .compare-serialization { padding: 0 20vw; } #gif-structure { .text, li { font-size: 3vw; } h2 { margin-bottom: 1vw; } .text { margin-bottom: 2vw; } } [binary-schema] { table { width: 100%; } td { font-size: 4vw; font-weight: 400; } } #gif-structure { .parent-palette { background: #00ff51; } .parent-header { background: #ffaf00; } .parent-extensions { background: #00a4ff; } } #bitwise-to-read .book { background-image: url(./pics/hackers-delight.jpeg); } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary.component.ts ================================================ import { Component } from '@angular/core'; import { strToBin } from './parser/utils'; declare const require; const littleGif = require('!binary-loader!./pics/little.gif'); const chikinGif = require('!binary-loader!./pics/chikin.gif'); @Component({ selector: 'kirjs-binary', templateUrl: './binary.component.html', styleUrls: ['./binary.component.scss'] }) export class BinaryComponent { fontSize = 30; binaryLittleGif = strToBin(littleGif); binaryChikinGif = strToBin(chikinGif); code = { message: `['01001011', '01001111', '01010100', '01001100', '01001001', '01001110', '00100000', '00111110', '00100000', '01001010', '01000001', '01010110', '01000001' ] // .map( // a => String.fromCharCode( // parseInt(a, 2))) // .join(''); `, simpleBinaryOperations: `let i = 7; // 7 = 1 + 2 + 4 !!(i & 1) // true !!(i & 2) // true !!(i & 4) // true i = i & ~2; // 5 = 1 + 4 (Not 2) !!(i & 1) // true !!(i & 2) // false !!(i & 4) // true i = i | 2 // 7 = 1 + 2 + 4 !!(i & 1) // true !!(i & 2) // true !!(i & 4) // true `, jsonBasic: `{ "name": "Sarah", "test": true, "something": 1212 }`, jsonOne: `{ "str": "1", "number": 1, "bool": true }`, inputFile: '', fileHandlerHighlight: { match: /read/, className: 'highlighted-code' }, fileHandler: `const input = document.getElementById('file'); input.addEventListener('change', (e) => { const reader = new FileReader(); reader.onloadend = (e) => { console.log(e.target.result); }; reader.readAsString(input.files[0]); }); `, fileHandlerBinary: `const input = document.getElementById('file'); input.addEventListener('change', (e) => { const reader = new FileReader(); reader.onloadend = (e) => { console.log(e.target.result); }; reader.readAsArrayBuffer(input.files[0]); });` }; littleGif = littleGif; chikinGif = chikinGif; binaryParserHeaderMatch = /parent-header/; binaryParserHeaderHelpers = [ `new Parser();`, `new Parser() .string('gif', {length: 3}) `, `new Parser() .string('gif', {length: 3}) .string('version', {length: 3}) `, `new Parser() .string('gif', {length: 3}) .string('version', {length: 3}) .uint16('width') `, `new Parser() .string('gif', {length: 3}) .string('version', {length: 3}) .uint16le('width') `, `new Parser() .string('gif', {length: 3}) .string('version', {length: 3}) .uint16le('width') .uint16le('height') `, `new Parser() .string('gif', {length: 3}) .string('version', {length: 3}) .uint16le('width') .uint16le('height') .bit1('globalPalette') `, `new Parser() .string('gif', {length: 3}) .string('version', {length: 3}) .uint16le('width') .uint16le('height') .bit1('globalPalette') .bit3('resolution') .bit1('paletteSorted') .bit3('paletteSize') .uint8('background') .uint8('ratio') ` ]; reactBitmask = `// Don't change these two values. They're used by React Dev Tools. var NoEffect = /* */0; var PerformedWork = /* */1; // You can change the rest (and add more). var Placement = /* */2; var Update = /* */4; var PlacementAndUpdate = /* */6; var Deletion = /* */8; var ContentReset = /* */16; var Callback = /* */32; var DidCapture = /* */64; var Ref = /* */128; var Snapshot = /* */256; var Passive = /* */512; // Passive & Update & Callback & Ref & Snapshot var LifecycleEffectMask = /* */932; // Union of all host effects var HostEffectMask = /* */1023; var Incomplete = /* */1024; var ShouldCapture = /* */2048;`; binaryParserPaletteMatch = /parent-palette/; binaryParserPaletteHelpers = [ `new Parser() .string('gif', {length: 3}) .string('version', {length: 3}) .uint16le('width') .uint16le('height') .bit1('globalPalette') .bit3('resolution') .bit1('paletteSorted') .bit3('paletteSize') .uint8('background') .uint8('ratio')`, `new Parser() .string('gif', {length: 3}) .string('version', {length: 3}) .uint16le('width') .uint16le('height') .bit1('globalPalette') .bit3('resolution') .bit1('paletteSorted') .bit3('paletteSize') .uint8('background') .uint8('ratio') .array('palette', { type: new Parser().bit24('color'), length: 4 } )`, `new Parser() .string('gif', {length: 3}) .string('version', {length: 3}) .uint16le('width') .uint16le('height') .bit1('globalPalette') .bit3('resolution') .bit1('paletteSorted') .bit3('paletteSize') .uint8('background') .uint8('ratio') .array('palette', { type: new Parser().bit24('color'), length: (result) => 2 ** (result.paletteSize + 1) } )`, `new Parser() .string('gif', {length: 3}) .string('version', {length: 3}) .uint16le('width') .uint16le('height') .bit1('globalPalette') .bit3('resolution') .bit1('paletteSorted') .bit3('paletteSize') .uint8('background') .uint8('ratio') .array('palette', { type: new Parser() .uint8('r') .uint8('g') .uint8('b'), length: (result) => 2 ** (result.paletteSize + 1) } )` ]; // // `file.byteLength`, // `String.fromCharCode(...new Uint8Array(file))`, // // `// Let's test how many arguments we can apply // // String.fromCharCode(...Array.from(new Array(100)))`, // // `String.fromCharCode(...Array.from(new Array(10000)))`, // // `String.fromCharCode(...Array.from(new Array(100000)))`, // // `String.fromCharCode(...Array.from(new Array(1000000)))`, // // `String.fromCharCode(...Array.from(new Array(125307)))`, // // ` // // // read more: // https://stackoverflow.com/questions/22747068/is-there-a-max-number-of-arguments-javascript-functions-can-accept // // String.fromCharCode(...Array.from(new Array(125306)))`, // `String.fromCharCode(...new Uint8Array(file))`, // `Array.from(new Uint8Array(file)).map(a=>a.toString(2).padStart(8, 0)).join('')`, // // document.getElementById('file').addEventListener('change', (e)=>{ // const reader = new FileReader(); // // reader.onloadend = (e) => { // file = e.target.result; // console.log(file); // }; // // reader.readAsArrayBuffer(e.target.files[0]); // }) // gif = { // width: "4", // height: "4", // image: [ // '#f00', '#f00', '#f00', '#f00', // '#f90', '#f0f', '#f00', '#f00', // '#f90', '#f0f', '#f00', '#f00', // '#f90', '#f0f', '#f00', '#f00', // ] // } commands = [ `\` @kirjs Binary ❤️ JavaScript \``, ``, ` document.getElementById('file').addEventListener('change', (e)=>{ const reader = new FileReader(); reader.onloadend = (e) => { file = e.target.result; console.log(file); }; reader.readAsArrayBuffer(e.target.files[0]); })`, `file.byteLength`, `String.fromCharCode(...new Uint8Array(file))`, // `// Let's test how many arguments we can apply // String.fromCharCode(...Array.from(new Array(100)))`, // `String.fromCharCode(...Array.from(new Array(10000)))`, // `String.fromCharCode(...Array.from(new Array(100000)))`, // `String.fromCharCode(...Array.from(new Array(1000000)))`, // `String.fromCharCode(...Array.from(new Array(125307)))`, // ` // // read more: https://stackoverflow.com/questions/22747068/is-there-a-max-number-of-arguments-javascript-functions-can-accept // String.fromCharCode(...Array.from(new Array(125306)))`, `String.fromCharCode(...new Uint8Array(file))`, `Array.from(new Uint8Array(file)).map(a=>a.toString(2).padStart(8, 0)).join('')`, `explain('message', 'basic')`, `explain('message', 'bytes')`, `explain('bindec', 'uint8')`, `parseInt('01010101', 2)`, `explain('bindec', 'int8')`, `explain('message', 'uint8')`, `explain('ascii')`, `explain('message', 'string')`, `explain('compare')`, `explain('json')`, `explain('gif')`, ` // Let's reinvent gif with JSON: gif = { width: "4", height: "4", image: [ '#f00', '#f00', '#f00', '#f00', '#f90', '#f0f', '#f00', '#f00', '#f90', '#f0f', '#f00', '#f00', '#f90', '#f0f', '#f00', '#f00', ] } `, ` // Let's index the colors gif = { width: "4", height: "4", colors: ['#f00', '#f90', '#f0f'], image: [ 0, 0, 0, 0, 1, 2, 0, 0, 1, 2, 0, 0, 1, 0 ,2 , 0 ] } `, `JSON.stringify(gif)`, `JSON.stringify(gif).length`, `"010010010111010000100000011010010111001100100000011000110110111101101101011011010110111101101110001" `, `explain('gif')`, `// How to read binary data?` ]; private evaledMessage: string; evalMessage() { this.evaledMessage = eval(this.code.message); } setLittleGifBinary(value: string) { this.littleGif = value; this.binaryLittleGif = strToBin(value); } } ================================================ FILE: apps/kirjs/src/app/modules/binary/binary.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { MatSelectModule } from '@angular/material/select'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { FeedbackModule } from '@codelab/feedback'; import { ConsoleModule } from '@codelab/console'; import { CodeDemoModule } from '@codelab/code-demos'; import { SharedPipeModule } from '@codelab/utils/src/lib/pipes/pipes.module'; import { BinaryComponent } from './binary.component'; import { FakeGifComponent } from './fake-gif/fake-gif.component'; import { GifPaletteComponent } from './gif-palette/gif-palette.component'; import { BinaryViewModule } from './binary-view/binary-view.module'; import { MidiComponent } from './midi/midi.component'; import { AsciiComponent } from './ascii/ascii.component'; import { BindecComponent } from './bindec/bindec.component'; import { MessageComponent } from './message/message.component'; import { JsonComponent } from './json/json.component'; import { CompareComponent } from './compare/compare.component'; import { HtmlPostComponent } from './html-post/html-post.component'; import { NewProgressBarModule } from '../ast/new-progress-bar/new-progress-bar.module'; import { BinaryGifComponent } from './binary-gif/binary-gif.component'; import { BitComponent } from './bit/bit.component'; import { MemoryComponent } from './memory/memory.component'; import { BinaryParserDemoComponent } from './binary-parser-demo/binary-parser-demo.component'; import { HexdecComponent } from './hexdec/hexdec.component'; import { AngularFlagsComponent } from './angular-flags/angular-flags.component'; import { ColorIndexingComponent } from './color-indexing/color-indexing.component'; import { BitwiseComponent } from './bitwise/bitwise.component'; import { ToReadComponent } from './to-read/to-read.component'; const routes = RouterModule.forChild(SlidesRoutes.get(BinaryComponent)); @NgModule({ imports: [ routes, FormsModule, CommonModule, BinaryViewModule, CodeDemoModule, MatAutocompleteModule, SharedPipeModule, ConsoleModule, NewProgressBarModule, MatSelectModule, SlidesModule, FeedbackModule ], declarations: [ BinaryComponent, FakeGifComponent, GifPaletteComponent, MidiComponent, AsciiComponent, BindecComponent, MessageComponent, JsonComponent, CompareComponent, HtmlPostComponent, BinaryGifComponent, BitComponent, MemoryComponent, BinaryParserDemoComponent, HexdecComponent, AngularFlagsComponent, ColorIndexingComponent, BitwiseComponent, ToReadComponent ], entryComponents: [ FakeGifComponent, MidiComponent, AsciiComponent, BindecComponent, MessageComponent, JsonComponent, HtmlPostComponent, CompareComponent ], exports: [BinaryComponent] }) export class BinaryModule {} ================================================ FILE: apps/kirjs/src/app/modules/binary/bindec/bindec.component.css ================================================ .powers { display: flex; font-size: 5vw; justify-content: center; } .power { width: 12.5%; border: 1px #ddd dotted; text-align: center; cursor: pointer; } .power:hover { box-shadow: 0 0 12px 6px #999; background: #eeeeee; } .power input { zoom: 4; } .dec { font-size: 3vw; color: #444; } .bin { font-size: 10vw; } .number { width: 100%; text-align: center; font-size: 16vw; padding: 2vw; } .link:hover { color: #90cd79; } .link { border-bottom: 1px #999 dotted; margin-right: 20px; cursor: pointer; } ================================================ FILE: apps/kirjs/src/app/modules/binary/bindec/bindec.component.html ================================================
================================================ FILE: apps/kirjs/src/app/modules/binary/bindec/bindec.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BindecComponent } from './bindec.component'; describe('BindecComponent', () => { let component: BindecComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [BindecComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BindecComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/bindec/bindec.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-bindec', templateUrl: './bindec.component.html', styleUrls: ['./bindec.component.css'] }) export class BindecComponent implements OnInit { digits = [0]; result = [0]; displaySign = false; sign = false; constructor() {} get size() { return this.digits.length; } get convertedValue() { return ( (this.sign ? -1 : 1) * this.result.reduce( (result, value, index) => result + value * this.getBaseValue(index), 0 ) ); } getBaseValue(i: number) { return 2 ** (this.size - i - 1); } update(value) {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/binary/bit/bit.component.css ================================================ .bits { display: flex; margin-bottom: 4vw; } .size-16 .bit { font-size: 5vw; } .size-24 .bit { font-size: 4vw; } .size-32 .bit { font-size: 2.5vw; } .bit { font-size: 10vw; margin-left: 1vw; color: #444; border-bottom: 0.2vw #888 solid; } .label { color: #999; border: none; margin-right: 2vw; } :host ::ng-deep .content.content.content table td { font-size: 4vw; } ================================================ FILE: apps/kirjs/src/app/modules/binary/bit/bit.component.html ================================================
{{ bitValue.length }} bits:
{{ value }}
================================================ FILE: apps/kirjs/src/app/modules/binary/bit/bit.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BitComponent } from './bit.component'; describe('BitComponent', () => { let component: BitComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [BitComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BitComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/bit/bit.component.ts ================================================ import { Component, Input, OnDestroy, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-bit', templateUrl: './bit.component.html', styleUrls: ['./bit.component.css'] }) export class BitComponent implements OnInit, OnDestroy { bits = 7; @Input() param = 1; bitValue: number[] = []; private interval = setInterval(() => { this.generate(); }, 500); generate() { this.bitValue = Array.from({ length: this.param }).map(a => Math.round(Math.random()) ); } ngOnDestroy() { clearInterval(this.interval); } ngOnInit() { this.generate(); } } ================================================ FILE: apps/kirjs/src/app/modules/binary/bitwise/bitwise.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/binary/bitwise/bitwise.component.html ================================================

bitwise works!

================================================ FILE: apps/kirjs/src/app/modules/binary/bitwise/bitwise.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BitwiseComponent } from './bitwise.component'; describe('BitwiseComponent', () => { let component: BitwiseComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [BitwiseComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BitwiseComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/bitwise/bitwise.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-bitwise', templateUrl: './bitwise.component.html', styleUrls: ['./bitwise.component.css'] }) export class BitwiseComponent implements OnInit { constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/binary/color-indexing/color-indexing.component.css ================================================ .cell { width: 8vw; height: 8vw; text-align: center; font-size: 3vw !important; vertical-align: middle; line-height: 8vw; } ================================================ FILE: apps/kirjs/src/app/modules/binary/color-indexing/color-indexing.component.html ================================================

Unindexed image

{{ cell }}

Color table (Palette)

{{ color.index }}

Indexed image

{{ hash[cell] }}
================================================ FILE: apps/kirjs/src/app/modules/binary/color-indexing/color-indexing.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ColorIndexingComponent } from './color-indexing.component'; describe('ColorIndexingComponent', () => { let component: ColorIndexingComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ColorIndexingComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(ColorIndexingComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/color-indexing/color-indexing.component.ts ================================================ import { Component, OnInit } from '@angular/core'; interface TableItem { color: string; index: number; } interface ColorTableHash { [key: string]: number; } @Component({ selector: 'kirjs-color-indexing', templateUrl: './color-indexing.component.html', styleUrls: ['./color-indexing.component.css'] }) export class ColorIndexingComponent implements OnInit { noIndexing = [ ['#ff0000', '#ff0000', '#ff0000'], ['#fff000', '#ff0000', '#fff000'], ['#ff0000', '#ff0000', '#ff0000'] ]; colorTable: TableItem[]; hash: ColorTableHash; constructor() { this.generate(); } index() { const index = this.noIndexing.reduce((colors, row) => { return row.reduce((colors, cell) => { colors[cell] = true; return colors; }, colors); }, {}); return Object.keys(index).map((color, index) => ({ color, index })); } generate() { this.colorTable = this.index(); this.hash = this.colorTable.reduce( (hash: ColorTableHash, value: TableItem): ColorTableHash => { hash[value.color] = value.index; return hash; }, {} ); } ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/binary/compare/compare.component.css ================================================ .slide { flex: 0 0 100vw; } ================================================ FILE: apps/kirjs/src/app/modules/binary/compare/compare.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/binary/compare/compare.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { CompareComponent } from './compare.component'; describe('CompareComponent', () => { let component: CompareComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [CompareComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(CompareComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/compare/compare.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-compare', templateUrl: './compare.component.html', styleUrls: ['./compare.component.css'] }) export class CompareComponent implements OnInit { constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/binary/fake-gif/fake-gif.component.css ================================================ * { font-family: Monaco, 'Lucida Console', monospace; } ================================================ FILE: apps/kirjs/src/app/modules/binary/fake-gif/fake-gif.component.html ================================================
This is always "GIF"
This is always "87a" or "89a"
Width of the image
Height of the image
Whether global palette is present
Number of bits per primary color available
Whether the palette is sorted
Specifies number of colors in the palette proportional a power of two. e.g.
If present specifies index of a color in the global color table that would be transparent
Ratio of the pixel
Reserved bits
Disposal Method - Indicates the way in which the graphic is to be treated after being displayed. Values : 0 - No disposal specified. The decoder is not required to take any action. 1 - Do not dispose. The graphic is to be left in place. 2 - Restore to background color. The area used by the graphic must be restored to the background color. 3 - Restore to previous. The decoder is required to restore the area overwritten by the graphic with what was there prior to rendering the graphic. 4-7 - To be defined.
Not used, the initial intention was to allow user interactions
Whether the frame should have a transparent color
Animation delay for next image
Optional transparent color index
Horizontal shift in pixels
Vertical shift in pixels
Width of the image
Height of the image
Whether the image has local palette
Indicates if the image is interlaced.
Whether local palette is sorted
Bucket of sizes of local palette.
Identifies the Netscape Looping Extension. This field contains the fixed value 0x01
Size of the extension block in bytes
Number of animation loops
This is the actual image encoded with LZW
================================================ FILE: apps/kirjs/src/app/modules/binary/fake-gif/fake-gif.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { FakeGifComponent } from './fake-gif.component'; describe('FakeGifComponent', () => { let component: FakeGifComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [FakeGifComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(FakeGifComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/fake-gif/fake-gif.component.ts ================================================ import { AfterViewInit, Component, EventEmitter, Input, Output, ViewChild } from '@angular/core'; import { BinaryParser } from '../parser/binary-parser'; import { gifParser } from './gif-parser'; import { extractMessages } from '@codelab/utils/src/lib/i18n/i18n-tools'; interface Chunk { name: string; size: number; value: string; start?: number; } @Component({ selector: 'kirjs-fake-gif', templateUrl: './fake-gif.component.html', styleUrls: ['./fake-gif.component.css'] }) export class FakeGifComponent implements AfterViewInit { t: { [key: string]: string }; @Input() spacing = false; showMeta = true; @Input() binary: string; @Input() highlightedMap: Record = {}; @Input() highlightGroups = false; @Input() preview = true; @Input() filterClassName = /./; @Input() mini = false; @Input() showPopups = false; @Output() binaryUpdate = new EventEmitter(); gif: string; parser: BinaryParser; @ViewChild('translations', { static: false }) translation; constructor() {} upload(file) { const reader = new FileReader(); reader.onloadend = (e: any) => { const result = new Uint8Array(e.target.result); const binaries = Array.from(result) .map(a => a.toString(2)) .map(a => (a as any).padStart(8, 0)); this.binary = binaries.join(''); }; reader.readAsArrayBuffer(file.files[0]); } update(chunk, value) { const len = chunk.end - chunk.start; value = value.padEnd(len, 0).slice(0, len); this.binary = this.binary.slice(0, chunk.start) + value + this.binary.substr(chunk.end); this.binaryUpdate.emit(this.binary); } ngAfterViewInit() { requestAnimationFrame(() => { this.t = extractMessages(this.translation); this.parser = new BinaryParser().block('gif', gifParser(this.t)); }); } updateChunk({ chunk, value }) { this.update(chunk, value); } } ================================================ FILE: apps/kirjs/src/app/modules/binary/fake-gif/gif-parser.ts ================================================ import { BinaryParser } from '../parser/binary-parser'; import { lzw } from './gif'; export function gifParser(t: { [key: string]: string }) { const header = new BinaryParser() .string('headerConst', { length: 3, description: t.headerConst }) .string('version', { length: 3, description: t.version }) .uInt16('width', { description: t.width }) .uInt16('height', { description: t.height }) .boolean('globalPalette', { description: t.globalPalette }) .bit3('resolution', { type: 'number', description: t.resolution }) .boolean('isPaletteSorted', { description: t.isPaletteSorted }) .bit3('paletteSize', { type: 'number', description: t.paletteSize }) .uInt8('background', { description: t.background }) .uInt8('Ratio', { description: t.ratio }); const commentParser = new BinaryParser() .string('comment', { readUntil: '00000000' }) .hex('end', { length: 2 }); const palette = new BinaryParser().array('palette', { parser: new BinaryParser().hex('color', { length: 6, type: 'color' }), length(data) { const paletteSize = data .find(d => d.name === '_parent') .value.find(d => d.name === 'header') .value.find(d => d.name === 'paletteSize').value; const size = parseInt(paletteSize, 2); return 2 ** (size + 1); } }); const netscapeParser = new BinaryParser() .uInt8('extensionSize', { description: t.extensionSize }) .constBits('00000001', { description: t.netscapeLoopingExtensionId }) .uInt16('loops', { description: t.loops }) .constBits('00000000', { description: '' }); const xmpParser = new BinaryParser() .string('data', { readUntil: '00000000' }) .hex('end', { length: 4 }); const extensionParser = new BinaryParser() .hex('0b', { length: 2 }) .string('type', { length: 8 }) .string('code', { length: 3 }) .choice('data', { key: 'type', values: { NETSCAPE: netscapeParser, 'XMP Data': xmpParser } }); const graphicControlParser = new BinaryParser() .hex('const', { length: 2 }) .constBits('000', { description: t.reservedBits }) .bit3('disposalMethod', { type: 'enums', description: t.disposalMethod }) .boolean('UI', { description: t.UI }) .boolean('isTransparent', { description: t.isTransparent }) .uInt16('delay', { description: t.delay }) .uInt8('transparentColor', { description: t.transparentColor }) .constBits('00000000'); const exclamationMarkParser = new BinaryParser() .hex('subtype', { length: 2 }) .choice('extension', { key: 'subtype', values: { f9: graphicControlParser, ff: extensionParser, fe: commentParser } }); const imageDescriptorParser = new BinaryParser() .uInt16('left', { description: t.left }) .uInt16('top', { description: t.top }) .uInt16('imageWidth', { description: t.imageWidth }) .uInt16('imageHeight', { description: t.imageHeight }) .boolean('localPalette', { description: t.localPalette }) .boolean('isImageInterlacingEnabld', { description: t.isImageInterlacingEnabld }) .boolean('isLocalPaletteSorted', { description: t.isLocalPaletteSorted }) .constBits('00', { description: t.reservedBits }) .bit3('localPaletteSize', { type: 'enums', description: t.localPaletteSize }) .uInt8('colorDepth') .uInt8('blockSize') .bit('graphicBlock', { description: t.graphicBlock, length: fields => { return ( (Object as any).values(fields).find(a => a.name === 'blockSize') .value * 8 ); }, converter(bits) { return lzw( 2, bits.match(/.{8}/g).map(a => parseInt(a, 2)), 4 ); } }) .constBits('00000000'); const body = new BinaryParser() .string('marker', { length: 1 }) .choice('extension', { key: 'marker', values: { '!': exclamationMarkParser, ';': new BinaryParser(), ',': imageDescriptorParser } }); return new BinaryParser() .block('header', header) .block('palette', palette) .array('extensions', { parser: body, length: 200 }); } ================================================ FILE: apps/kirjs/src/app/modules/binary/fake-gif/gif.ts ================================================ export function lzw(minCodeSize, data, pixelCount) { const MAX_STACK_SIZE = 4096; const nullCode = -1; const npix = pixelCount; let available, clear, code_mask, code_size, end_of_information, in_code, old_code, bits, code, i, datum, data_size, first, top, bi, pi; const dstPixels = new Array(pixelCount); const prefix = new Array(MAX_STACK_SIZE); const suffix = new Array(MAX_STACK_SIZE); const pixelStack = new Array(MAX_STACK_SIZE + 1); // Initialize GIF data stream decoder. data_size = minCodeSize; clear = 1 << data_size; end_of_information = clear + 1; available = clear + 2; old_code = nullCode; code_size = data_size + 1; code_mask = (1 << code_size) - 1; for (code = 0; code < clear; code++) { prefix[code] = 0; suffix[code] = code; } // Decode GIF pixel stream. datum = bits = first = top = pi = bi = 0; for (i = 0; i < npix; ) { if (top === 0) { if (bits < code_size) { // get the next byte datum += data[bi] << bits; bits += 8; bi++; continue; } // Get the next code. code = datum & code_mask; datum >>= code_size; bits -= code_size; // Interpret the code if (code > available || code === end_of_information) { break; } if (code === clear) { // Reset decoder. code_size = data_size + 1; code_mask = (1 << code_size) - 1; available = clear + 2; old_code = nullCode; continue; } if (old_code === nullCode) { pixelStack[top++] = suffix[code]; old_code = code; first = code; continue; } in_code = code; if (code === available) { pixelStack[top++] = first; code = old_code; } while (code > clear) { pixelStack[top++] = suffix[code]; code = prefix[code]; } first = suffix[code] & 0xff; pixelStack[top++] = first; // add a new string to the table, but only if space is available // if not, just continue with current table until a clear code is found // (deferred clear code implementation as per GIF spec) if (available < MAX_STACK_SIZE) { prefix[available] = old_code; suffix[available] = first; available++; if ((available & code_mask) === 0 && available < MAX_STACK_SIZE) { code_size++; code_mask += available; } } old_code = in_code; } // Pop a pixel off the pixel stack. top--; dstPixels[pi++] = pixelStack[top]; i++; } for (i = pi; i < npix; i++) { dstPixels[i] = 0; // clear missing pixels } return dstPixels; } ================================================ FILE: apps/kirjs/src/app/modules/binary/gif-palette/gif-palette.component.css ================================================ * { font-family: Monaco, 'Lucida Console', monospace; } ================================================ FILE: apps/kirjs/src/app/modules/binary/gif-palette/gif-palette.component.html ================================================
================================================ FILE: apps/kirjs/src/app/modules/binary/gif-palette/gif-palette.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { GifPaletteComponent } from './gif-palette.component'; describe('GifPaletteComponent', () => { let component: GifPaletteComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [GifPaletteComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(GifPaletteComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/gif-palette/gif-palette.component.ts ================================================ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; interface Chunk { name: string; size: number; value: string; start?: number; } @Component({ selector: 'kirjs-gif-palette', templateUrl: './gif-palette.component.html', styleUrls: ['./gif-palette.component.css'] }) export class GifPaletteComponent implements OnInit { @Output() change = new EventEmitter(); colors: number[][]; private _value = ''; get value() { return this._value; } @Input() set value(val: string) { this._value = val; this.colors = Array.from(val.match(/.{24}/g)).map(a => Array.from(a.match(/.{8}/g)).map(str => parseInt(str, 2)) ); } serialize() { this._value = this.colors .map(c => c .map(p => ((+p).toString(2) as any).padStart(8, 0).slice(0, 8)) .join('') ) .join(''); } ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/binary/hexdec/hexdec.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/binary/hexdec/hexdec.component.html ================================================

Converting bin to hex

{{ n.hex }}
{{ n.bin }}
================================================ FILE: apps/kirjs/src/app/modules/binary/hexdec/hexdec.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { HexdecComponent } from './hexdec.component'; describe('HexdecComponent', () => { let component: HexdecComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [HexdecComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(HexdecComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/hexdec/hexdec.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-hexdec', templateUrl: './hexdec.component.html', styleUrls: ['./hexdec.component.css'] }) export class HexdecComponent implements OnInit { numbers = new Array(16).fill(0).map((a, i) => ({ bin: i.toString(2).padStart(4, '0'), hex: i.toString(16) })); constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/binary/html-post/html-post.component.css ================================================ :host { padding: 2vw; display: block; zoom: 3; } ================================================ FILE: apps/kirjs/src/app/modules/binary/html-post/html-post.component.html ================================================
================================================ FILE: apps/kirjs/src/app/modules/binary/html-post/html-post.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { HtmlPostComponent } from './html-post.component'; describe('HtmlPostComponent', () => { let component: HtmlPostComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [HtmlPostComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(HtmlPostComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/html-post/html-post.component.ts ================================================ import { Component, Input } from '@angular/core'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; @Component({ selector: 'kirjs-html-post', templateUrl: './html-post.component.html', styleUrls: ['./html-post.component.css'] }) export class HtmlPostComponent { html: SafeHtml; constructor(private sanitizer: DomSanitizer) { this.html = sanitizer.bypassSecurityTrustHtml(''); } @Input() set param(html: string) { this.html = this.sanitizer.bypassSecurityTrustHtml(html); } } ================================================ FILE: apps/kirjs/src/app/modules/binary/json/json.component.html ================================================

Data as JSON ({{ codeLength }} bytes)

Binary data ({{ binariesLength }} bytes)

{{ error }}
{{ item.binary }} {{ item.type }} {{ l.value }}  {{ l.bin }} {{ item.value }} {{ item.binary }} {{ item.value }} {{ item.binary }}

Schema ({{ schemaLength }} bytes)

  {{ item.value }}
Next
================================================ FILE: apps/kirjs/src/app/modules/binary/json/json.component.scss ================================================ .wrapper { font-size: 3vw; word-break: break-all; } .code { white-space: pre-wrap; font-size: 2vw; margin-right: 2vw; margin-left: 1vw; } .binary .selected { display: block; box-shadow: 0 0 4px 1px #444; background: #eee; font-size: 3vw; } .code .selected { color: #444; font-size: 3vw; box-shadow: 0 0 4px 1px #444; background: #eee; } .detail { display: none; } .selected .detail { display: block; } .selected .value { display: none; } :host ::ng-deep { .highlight-0 { background: rgba(255, 124, 0, 0.47); } .highlight-1 { background: rgba(255, 239, 0, 0.47); } .highlight-2 { background: #00ff29; } .highlight-3 { background: #00ffe7; } .highlight-4 { background: rgba(0, 177, 255, 0.46); } .highlight-5 { background: rgba(185, 0, 255, 0.29); } .highlight-6 { background: rgba(255, 0, 89, 0.47); } .highlight-7 { background: rgba(255, 115, 0, 0.4); } } h2 { font-size: 3vw !important; } .error::before { color: #e51400; content: '🙀'; } .error { color: #e51400; border: 1px #e51400 solid; border-radius: 12px; font-size: 3vw; padding: 2vw; display: block; } ================================================ FILE: apps/kirjs/src/app/modules/binary/json/json.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { JsonComponent } from './json.component'; describe('JsonComponent', () => { let component: JsonComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [JsonComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(JsonComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/json/json.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; function strToBin(s: string) { return Array.from(new TextEncoder().encode(s)) .map(a => a.toString(2).padStart(8, '0')) .join(''); } @Component({ selector: 'kirjs-json', templateUrl: './json.component.html', styleUrls: ['./json.component.scss'] }) export class JsonComponent implements OnInit { @Input() code = `{ "name": "Sarah", "test": true, "something": 1212 }`; match = []; index = 0; binaries: any[]; binariesLength: number; codeLength: number; schema: { value: string }[] = []; schemaLength: number; error: string; constructor() {} handleLineChange({ value: code, lineNumber }) { this.binaries = [ { binary: '', comment: `we don't need to encode curly braces :)` } ]; let val; try { val = JSON.parse(code); this.error = ''; } catch (e) { this.error = e.message; return; } this.binaries = this.binaries.concat( Object.keys(val).map(key => { const value = val[key]; const data: any = {}; data.key = key; data.key = key; if (typeof value === 'boolean') { data.binary = Number(value); data.type = 'boolean'; data.comment = 'just one bit!'; } else if (typeof value === 'number') { data.binary = value .toString(2) .padStart(Math.ceil(Math.log2(value + 1) / 8) * 8, '0'); data.type = 'number'; data.comment = 'Number'; } else if (typeof value === 'string') { data.binary = strToBin(value) + '0000000000000000'; data.display = value .split('') .map(value => ({ value, bin: (value.charCodeAt(0).toString(2) as any).padStart(8, 0) })) .concat({ value: 'Separator', bin: '000000000000000000' }); data.type = 'string'; data.comment = 'String!'; } data.value = value; return data; }) ); this.schema = [ { value: 'message {', className: '' } ] .concat( this.binaries.slice(1).map((b, i) => ({ value: ` ${b.type} ${b.key} = ${i};`, className: 'highlight-' + i })) ) .concat({ value: '}', className: '' }); this.schemaLength = this.schema.map(s => s.value).join('').length; this.match = code .split('\n') .map((a, i) => ({ match: a.trim(), className: `highlight-${i}` })); this.index = lineNumber - 1; this.codeLength = code.replace(/\s/g, '').length; this.binariesLength = Math.ceil( this.binaries .map(a => a.binary.toString().length) .reduce((a, b) => a + b, 0) / 8 ); } ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/binary/memory/memory.component.css ================================================ .memory { display: flex; flex-wrap: wrap; width: 100%; flex-shrink: 0; } .cell { font-size: 1vw; color: #999; width: 5.7vw; box-sizing: border-box; margin-right: 0.3vw; height: 6vw; background: #ddd; margin-bottom: 0.5vw; padding: 0.5vw; border: 1px #999 solid; } .value { font-size: 3vw; } .index { font-size: 1.5vw; vertical-align: top; padding: 0.2vw; color: #444; width: 2vw; display: inline-block; } .cell.cell-empty { background: #ffffff; } .cell.cell-boolean { background: #444444; color: #bbb; } .cell.selected2.selected2.selected2, .cell.cell-selected2 { background: #ff9900; color: #444; } .cell.cell-number.selected, .cell.cell-selected.cell-selected.cell-selected { background: #ffff00; color: #444; } .cell.cell-number-end-highlight, .cell.cell-number-highlight { background: #ff0; color: #444; } .cell.cell-cell-number-end, .cell.cell-number { background: #444444; color: #bbb; } .cell.cell-cell-number-end .index, .cell.cell-number .index { color: #999; } .cell.cell-number.cell-selected, .cell.cell-number-highlight, .cell.cell-number { width: 6vw; margin-right: 0; border-right: 0; border-left-style: dotted; } .number.number { background: #9f0; color: black; } .bool.bool { background: #f90; color: black; } .link.link { background: #00c8ff; color: black; } ================================================ FILE: apps/kirjs/src/app/modules/binary/memory/memory.component.html ================================================
{{ i }} {{ cell.value }}
================================================ FILE: apps/kirjs/src/app/modules/binary/memory/memory.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { MemoryComponent } from './memory.component'; describe('MemoryComponent', () => { let component: MemoryComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [MemoryComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(MemoryComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/memory/memory.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-memory', templateUrl: './memory.component.html', styleUrls: ['./memory.component.css'] }) export class MemoryComponent implements OnInit { @Input() param = 0; @Input() code = ''; start = 0; memory = Array.from({ length: 64 }).fill({ value: 0, type: 'empty' }) as any; constructor() {} allocValues(values, classes = []) { let i = 0; for (let x = 0; x < values.length; x++) { const v = values[x]; for (let y = 0; y < v.length; y++) { this.memory[i] = { value: v[y], type: (y === v.length - 1 ? 'cell-number-end' : 'number') + ' ' + (classes[x] || '') }; i++; } } } highlightBoolean(i: number) { this.memory[i].type = (this.memory[i].type || '') + ' selected cell-selected'; } highlightShouldBe(i: number) { this.memory[i].type = (this.memory[i].type || '') + ' selected2'; } highlightNumber(i: number) { for (let y = 0; y < 8; y++) { this.memory[i * 8 + y] = { value: Math.round(Math.random()), type: y === 7 ? 'number-end-highlight' : 'number-highlight' }; } } ngOnInit() { if (this.param === 0) { return; } if (this.param === 1) { this.allocValues('00000'.split('')); } if (this.param === 2) { this.allocValues('00000'.split('')); this.highlightBoolean(3); } if (this.param === 3) { this.allocValues([ '00000000', '00000000', '00000000', '00000000', '00000000' ]); } if (this.param === 4) { this.allocValues([ '00000000', '00000000', '00000000', '00000000', '00000000' ]); this.highlightNumber(3); } if (this.param === 5) { this.allocValues('10101'.split('')); this.highlightBoolean(3); } const bools = '10101'.split(''); const num = '00000011'; bools[1] = num; if (this.param === 6) { this.allocValues(bools); this.highlightBoolean(3); this.highlightShouldBe(10); } if (this.param === 7) { const typedArray = [ '001', '1', '010', num, '001', '0', '001', '0', '001', '1', '000' ]; this.allocValues(typedArray, [ ' bool', '', ' number', '', ' bool', '', ' bool', ' cell-selected ', ' bool' ]); } const typedArray = [ '011110', '010010', '101101', '110001', '110101', '001', '1', '010', num, '001', '0', '001', '0', '001', '1' ]; if (this.param === 8) { this.allocValues(typedArray, [ ' link', ' link', ' link', ' link', ' link', ' bool', '', ' number', '', ' bool', '', ' bool', '', ' bool' ]); } if (this.param === 9) { const typedArrayWidhBool = [...typedArray, '001', '0']; typedArrayWidhBool[1] = '111001'; this.allocValues(typedArrayWidhBool, [ ' link', ' link', ' link', ' link highlight', ' link', ' bool', '', ' bool', '', ' bool', '', ' bool', '', ' bool', '', ' bool' ]); } } } ================================================ FILE: apps/kirjs/src/app/modules/binary/message/message.component.css ================================================ .blocks { display: flex; flex-wrap: wrap; } .block { font-size: 1.5vw; color: #444; } .basic .block { padding: 0; font-size: 3vw; } .bytes .block { font-size: 3vw; } .human { font-size: 4vw; } .block { padding: 1vw; text-align: center; } ================================================ FILE: apps/kirjs/src/app/modules/binary/message/message.component.html ================================================
{{ block.bin }}
{{ block.human }}
================================================ FILE: apps/kirjs/src/app/modules/binary/message/message.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { MessageComponent } from './message.component'; describe('MessageComponent', () => { let component: MessageComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [MessageComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(MessageComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/message/message.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; function toByte(message) { return Array.from(message.match(/.{8}/gi)).map(bin => ({ className: 'basic', bin })); } const transforms = { basic: toByte, bytes: toByte, boolean(message) { return message.split('').map(bin => { return { className: 'basic', bin, human: (!!Number(bin)).toString() }; }); }, uint16(message) { return Array.from(message.match(/.{16}/gi)).map(bin => ({ className: 'basic', bin, human: parseInt(bin as string, 2) })); }, uint17(message) { return Array.from(message.match(/.{17}/gi)).map(bin => ({ className: 'basic', bin, human: parseInt(bin as string, 2) })); }, uint32(message) { return Array.from(message.match(/.{32}/gi)).map(bin => ({ className: 'basic', bin, human: parseInt(bin as string, 2) })); }, hex(message) { return Array.from(message.match(/.{8}/gi)).map((bin: string) => ({ className: 'basic', bin, human: parseInt(bin, 2).toString(16) })); }, uint8(message) { return Array.from(message.match(/.{8}/gi)).map(bin => ({ className: 'basic', bin, human: parseInt(bin as string, 2) })); }, int8(message) { return Array.from(message.match(/.{8}/gi)).map(bin => { const sign = !!(Number(bin) & 128) ? 1 : -1; return { className: 'basic', bin, human: sign * (Number(bin) & 127) }; }); }, string(message) { return Array.from(message.match(/.{8}/gi)).map((bin: string) => ({ className: 'basic', bin, human: String.fromCharCode(parseInt(bin, 2)) })); } }; @Component({ selector: 'kirjs-message', templateUrl: './message.component.html', styleUrls: ['./message.component.css'] }) export class MessageComponent implements OnInit { message = '0100111001100101011101100110010101110010001000000110011101101111011011100110111001100001001000000110011' + '101101001011101100110010100100000011110010110111101110101001000000111010101110000001000000100111001100101011101' + '100110010101110010001000000110011101101111011011100110111001100001001000000110110001100101011101000010000001111' + '00101101111011101010010000001100100011011110111011101101110'; mode = 1; display = 'boolean'; blocks = transforms[this.display](this.message); constructor() {} @Input() set param(value: string) { this.setDisplay(value); } setDisplay(value: string) { this.display = value; this.blocks = transforms[this.display](this.message); } ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/binary/midi/midi.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/binary/midi/midi.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/binary/midi/midi.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { MidiComponent } from './midi.component'; describe('MidiComponent', () => { let component: MidiComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [MidiComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(MidiComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/midi/midi.component.ts ================================================ import { Component, OnInit } from '@angular/core'; import { BinaryParser } from '../parser/binary-parser'; import { BinaryReaderResult } from '../parser/readers/abstract-reader'; import { StringBinaryReader } from '../parser/readers/string-reader'; import { StringParser } from '../parser/parsers/string-parser'; @Component({ selector: 'kirjs-midi', templateUrl: './midi.component.html', styleUrls: ['./midi.component.css'] }) export class MidiComponent implements OnInit { showMeta = true; binary: string; parser: BinaryParser; s: BinaryReaderResult; constructor() {} updateBinary(e: Event) {} ngOnInit() { this.binary = localStorage.getItem('midi'); const header = new BinaryParser() .string('headerConst', { length: 4 }) .uInt32('6') .uInt16('Single multi-channel track') .uInt16('Number of tracs') .uInt16('Time-code-based time'); const timeSignatureParser = new BinaryParser() .uInt8('upper') .uInt8('lower') .uInt8('clocks') .uInt8('something'); const theEnd = new BinaryParser().uInt8('00'); const metaParser = new BinaryParser() .uInt8('subtype') .uInt8('length') .choice('value', { parser(data) { const type = (Object as any) .values(data) .find(l => l.name === 'subtype').rawValue; const length = (Object as any) .values(data) .find(l => l.name === 'length').value; const parsers = { '00000011': new StringParser({ length }), '00000010': new StringParser({ length }), '01011000': timeSignatureParser, // tempo '01010001': new BinaryParser().uInt24('value'), '00101111': theEnd }; if (parsers[type]) { return parsers[type]; } // tslint:disable-next-line:no-debugger debugger; } }); const noteSwitch = new BinaryParser() .uInt8('note number') .uInt8('velocity'); const instrumentChannel = new BinaryParser().uInt8('instrument'); const track = new BinaryParser() .varuint7('delta') .uInt8('type') .choice('typeData', { parser: data => { const type = (Object as any).values(data).find(l => l.name === 'type') .rawValue; const parsers = { '11111111': metaParser, '11000000': instrumentChannel, '10010000': noteSwitch, '10000000': noteSwitch }; if (parsers[type]) { return parsers[type]; } // tslint:disable-next-line:no-debugger debugger; } }); const tracks = new BinaryParser() .string('headerConst', { length: 4 }) .uInt32('tracklen') .array('tracks', { parser: track, length: 12 }); this.parser = new BinaryParser() .block('header', header) .block('block', tracks); this.s = this.parser.readOrdered(new StringBinaryReader(this.binary)); } upload(file) { const reader = new FileReader(); reader.onloadend = (e: ProgressEvent) => { const result = new Uint8Array((e.target as any).result); const binaries = Array.from(result) .map(a => a.toString(2)) .map(a => (a as any).padStart(8, 0)); this.binary = binaries.join(''); localStorage.setItem('midi', this.binary); }; reader.readAsArrayBuffer(file.files[0]); } } ================================================ FILE: apps/kirjs/src/app/modules/binary/parser/binary-parser.spec.ts ================================================ import { StringBinaryReader } from './readers/string-reader'; import { BinaryParser } from './binary-parser'; describe('BinaryParser', () => { beforeEach(() => { const s = 'Universal Serial Bus' .split('') .map(a => a.charCodeAt(0)) .map(a => a.toString(2)) .map(a => (a as any).padStart(8, 0)) .join(''); this.reader = new StringBinaryReader(s); }); describe('BinaryParser', () => { it('allows shortcuts', () => { const parser = new BinaryParser() .string('u', { length: 3 }) .bit1('a') .bit1('b'); expect(parser.read(this.reader, {}).value).toEqual({ a: '0', b: '1', u: 'Uni' }); }); it('allows nesting', () => { const header = new BinaryParser().bit1('a').bit1('b'); const parser = new BinaryParser() .block('header', header) .bit1('c') .bit1('d'); expect(parser.read(this.reader).value).toEqual({ header: { a: '0', b: '1' }, c: '0', d: '1' }); }); it('tracks position', () => { const header = new BinaryParser().bit1('a').bit1('b'); const parser = new BinaryParser() .block('header', header) .bit1('c') .bit1('d'); const result = parser.readOrdered(this.reader).value; expect(result).toEqual([ { start: 0, end: 2, length: 2, name: 'header', value: [ { start: 0, end: 1, length: 1, name: 'a', value: '0', rawValue: '0', type: 'bits' }, { start: 1, end: 2, length: 1, name: 'b', value: '1', rawValue: '1', type: 'bits' } ], rawValue: '01', type: 'object' }, { start: 2, end: 3, length: 1, name: 'c', value: '0', rawValue: '0', type: 'bits' }, { start: 3, end: 4, length: 1, name: 'd', value: '1', rawValue: '1', type: 'bits' } ]); }); it('allows uint16', () => { const parser = new BinaryParser().uInt16('u'); expect(parser.read(this.reader).value).toEqual({ u: 28245 }); }); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/parser/binary-parser.ts ================================================ import { BinaryObjectParser } from './parsers/object-parser'; import { StringParser, StringParserConfig } from './parsers/string-parser'; import { BinaryChoiceParser } from './parsers/choice-parser'; import { BinaryArrayParser } from './parsers/array-parser'; import { BitParser } from './parsers/bit-parser'; import { VarUintParser, VarUintParserConfig } from './parsers/var-uint-parser'; import { BinaryReader } from './readers/abstract-reader'; import { beToLe32 } from './parsers/common'; export interface BaseConfig { description?: string; length?: number; type?: string; converter?: (a: string) => number; enum?: { [key: string]: string }; } export class BinaryParser { type = 'object'; private parser: BinaryObjectParser; constructor() { this.parser = new BinaryObjectParser(); } string(name: string, config: StringParserConfig) { this.parser.addStep(name, new StringParser(config)); return this; } varuint7(name: string, config: Partial = {}) { this.parser.addStep(name, new VarUintParser(config)); return this; } varuint31(name: string, config: Partial = {}) { this.parser.addStep( name, new VarUintParser({ ...config, size: 31 }) ); return this; } choice(name: string, config: any) { this.parser.addStep(name, new BinaryChoiceParser({ ...config })); return this; } array(name: string, config: any) { this.parser.addStep(name, new BinaryArrayParser({ ...config })); return this; } bit(name: string, config: any) { this.parser.addStep(name, new BitParser({ ...config })); return this; } block(name: string, parser) { this.parser.addStep(name, parser); return this; } constBits(value, config?: Partial) { return this.bit('const', { length: value.length, type: 'const', ...config }); } boolean(name: string, config?: Partial) { return this.bit(name, { length: 1, type: 'boolean', ...config }); } bit1(name: string, config?: Partial) { return this.bit(name, { length: 1, ...config }); } bit2(name: string, config?: Partial) { return this.bit(name, { length: 2, ...config }); } bit3(name: string, config?: Partial) { return this.bit(name, { length: 3, ...config }); } bit8(name: string, config?: Partial) { return this.bit(name, { length: 8, ...config }); } bit32(name: string, config?: Partial) { return this.bit(name, { length: 32, ...config }); } bit24(name: string, config?: Partial) { return this.bit(name, { length: 24, ...config }); } object(name: string, config?: Partial) { return this.bit(name, { length: 1, ...config }); } uInt16(name: string, config?: Partial) { return this.bit(name, { type: 'number', length: 16, converter: a => { return parseInt(a.slice(8) + a.slice(0, 8), 2); }, ...config }); } uInt24(name: string, config: any = {}) { return this.bit(name, { type: 'number', length: 24, converter: a => { return parseInt(a, 2); }, ...config }); } uInt32(name: string, config?: Partial) { return this.bit(name, { type: 'number', length: 32, converter: a => { return parseInt(a, 2); }, ...config }); } uInt32le(name: string, config?: Partial) { return this.uInt32(name, { converter: a => { return beToLe32(parseInt(a, 2)); }, ...config }); } uInt8(name: string, config?: Partial) { return this.bit(name, { type: 'number', subtype: 'uint8', length: 8, converter: a => { return parseInt(a, 2); }, ...config }); } hex(name: string, config?: Partial) { if (typeof config.length === 'function') { // tslint:disable-next-line:no-debugger debugger; // TODO } return this.bit(name, { type: 'hex', converter: data => { return Array.from(data.match(/.{4}/g)) .map(a => parseInt(a.toString(), 2)) .map(a => a.toString(16)) .join(''); }, ...config, length: config.length * 4 }); } read(reader, data: any = {}) { return this.parser.read(reader, data); } readOrdered(reader: BinaryReader, data: any = [], start = 0) { const v = this.parser.readOrdered(reader, data, start); return { start: start, ...v }; } } ================================================ FILE: apps/kirjs/src/app/modules/binary/parser/parsers/abstract-parser.ts ================================================ import { BinaryReader, BinaryReaderResult } from '../readers/abstract-reader'; export abstract class AbstractBinaryParser { type: string; abstract read( reader: BinaryReader, data: BinaryReaderResult ): BinaryReaderResult; abstract readOrdered( reader: BinaryReader, data: any[], start: number ): BinaryReaderResult; } ================================================ FILE: apps/kirjs/src/app/modules/binary/parser/parsers/array-parser.spec.ts ================================================ import { StringParser } from './string-parser'; import { BinaryArrayParser } from './array-parser'; import { StringBinaryReader } from '../readers/string-reader'; describe('array parser', () => { beforeEach(() => { const s = 'Universal Serial Bus' .split('') .map(a => a.charCodeAt(0)) .map(a => a.toString(2)) .map(a => (a as any).padStart(8, 0)) .join(''); this.reader = new StringBinaryReader(s); }); it('can read one letter', () => { const parser = new BinaryArrayParser({ length: 2, parser: new StringParser({ length: 2 }) }); expect(parser.read(this.reader).value).toEqual(['Un', 'iv']); }); it('can read one letter', () => { const parser = new BinaryArrayParser({ parser: new StringParser({ length: 2 }) }); expect(parser.read(this.reader).value).toEqual([ 'Un', 'iv', 'er', 'sa', 'l ', 'Se', 'ri', 'al', ' B', 'us' ]); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/parser/parsers/array-parser.ts ================================================ import { AbstractBinaryParser } from './abstract-parser'; import { BinaryReader, BinaryReaderResult } from '../readers/abstract-reader'; import { resolveFunctionOrvalue, resolveLengthOrdered } from '../utils'; export class BinaryArrayParser extends AbstractBinaryParser { type = 'array'; constructor(private config) { super(); } read( reader: BinaryReader, data: BinaryReaderResult = {} ): BinaryReaderResult { let len = resolveFunctionOrvalue(this.config.length, data) || Infinity; let raw = ''; const value = []; while (len > 0 && reader.hasMore()) { const result = this.config.parser.read(reader, data); raw += result.raw; value.push(result.value); len--; } return { value, raw }; } readOrdered( reader: BinaryReader, data: BinaryReaderResult = [], start = 0 ): BinaryReaderResult { let numberOfElements = resolveLengthOrdered(this.config.length, data); if (numberOfElements === undefined) { numberOfElements = Infinity; } let rawValue = ''; const value = []; let len = 0; let index = 0; while (numberOfElements > 0 && reader.hasMore()) { index++; const result = this.config.parser.readOrdered(reader, data, start + len); rawValue += result.rawValue; const length = result.rawValue.length; value.push({ start: start + len, end: start + len + length, length, index, type: this.config.parser.type, value: result.value, rawValue: result.rawValue }); len += length; numberOfElements--; } return { name: this.config.name, start, length: len, end: start + len, value, rawValue, type: this.type }; } } ================================================ FILE: apps/kirjs/src/app/modules/binary/parser/parsers/bit-parser.spec.ts ================================================ import { StringBinaryReader } from '../readers/string-reader'; import { BitParser } from './bit-parser'; describe('BinaryParser', () => { beforeEach(() => { const s = 'Universal Serial Bus' .split('') .map(a => a.charCodeAt(0)) .map(a => a.toString(2)) .map(a => (a as any).padStart(8, 0)) .join(''); this.reader = new StringBinaryReader(s); }); describe('bit parser', () => { it('can read 3 bit bit', () => { const parser = new BitParser({ length: 3 }); const results = parser.read(this.reader); expect(results.value).toBe('010'); expect(results.rawValue).toBe('010'); }); it('takes a length function', () => { const parser = new BitParser({ length: () => 3 }); const result = parser.read(this.reader); expect(result.value).toBe('010'); expect(result.rawValue).toBe('010'); }); it('takes a length function which can use existing data', () => { const parser = new BitParser({ length: data => data.len }); const result = parser.read(this.reader, { len: 3 }); expect(result.value).toBe('010'); expect(result.rawValue).toBe('010'); }); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/parser/parsers/bit-parser.ts ================================================ import { AbstractBinaryParser } from './abstract-parser'; import { BinaryReader, BinaryReaderResult } from '../readers/abstract-reader'; import { resolveByKey, resolveFunctionKeyOrValue, resolveOrderedByKey } from '../utils'; export class BitParser extends AbstractBinaryParser { type = 'bits'; constructor(private config) { super(); this.type = config.type || this.type; } readWithLength( reader: BinaryReader, data: BinaryReaderResult = [], len: number ) { const rawValue = reader.read(len); const converter = this.config.converter || (a => a); return { value: converter(rawValue), rawValue }; } read( reader: BinaryReader, data: BinaryReaderResult = {} ): BinaryReaderResult { const len = resolveFunctionKeyOrValue( this.config.length, data, resolveByKey ); return this.readWithLength(reader, data, len); } readOrdered( reader: BinaryReader, data: BinaryReaderResult = [], start = 0 ): BinaryReaderResult { if (start === 0) { // tslint:disable-next-line:no-debugger debugger; } const len = resolveFunctionKeyOrValue( this.config.length, data, resolveOrderedByKey ); const result = this.readWithLength(reader, data, len); const length = result.rawValue.length; const end = start + length; return { displayValue: (this.config && this.config.enum && this.config.enum[result.value]) || result.value, start, length, end, ...result, type: this.type, description: this.config && this.config.description, unconverter: this.config.unconverter }; } } ================================================ FILE: apps/kirjs/src/app/modules/binary/parser/parsers/choice-parser.spec.ts ================================================ import { StringBinaryReader } from '../readers/string-reader'; import { BitParser } from './bit-parser'; import { BinaryChoiceParser } from './choice-parser'; import { StringParser } from './string-parser'; describe('BinaryParser', () => { beforeEach(() => { const s = 'Universal Serial Bus' .split('') .map(a => a.charCodeAt(0)) .map(a => a.toString(2)) .map(a => (a as any).padStart(8, 0)) .join(''); this.reader = new StringBinaryReader(s); }); describe('choice parser', () => { it('can read one letter', () => { const parser = new BinaryChoiceParser({ key: ({ p }) => p, values: { '1': new BitParser({ length: 8 }), '2': new StringParser({ length: 2 }) } }); expect(parser.read(this.reader, { p: '1' }).value).toEqual('01010101'); expect(parser.read(this.reader, { p: '2' }).value).toEqual('ni'); }); it('can read one letter', () => { const parser = new BinaryChoiceParser({ key: ({ p }) => p, values: { '1': new BitParser({ length: 8 }), '2': new StringParser({ length: 2 }) } }); expect(parser.readOrdered(this.reader, { p: '1' }).value).toEqual( '01010101' ); expect(parser.readOrdered(this.reader, { p: '2' }).value).toEqual('ni'); }); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/parser/parsers/choice-parser.ts ================================================ import { AbstractBinaryParser } from './abstract-parser'; import { BinaryReader, BinaryReaderResult } from '../readers/abstract-reader'; import { resolveByKey, resolveOrderedByKey } from '../utils'; export class BinaryChoiceParser extends AbstractBinaryParser { constructor(private config) { super(); } get type() { return 'bich'; } read( reader: BinaryReader, data: BinaryReaderResult = [] ): BinaryReaderResult { return this.getParser(data, resolveByKey).read(reader, data); } getParser(data, resolver) { let parser: AbstractBinaryParser; if (this.config.key) { const keyValue = resolver(this.config.key, data); parser = this.config.values[keyValue]; } if (this.config.parser) { parser = this.config.parser(data); } if (!parser) { // tslint:disable-next-line:no-debugger debugger; } return parser; } readOrdered( reader: BinaryReader, data: BinaryReaderResult = [], start = 0 ): BinaryReaderResult { const parser = this.getParser(data, resolveOrderedByKey); const { value, rawValue, description } = parser.readOrdered( reader, data, start ); return { start, value, rawValue, type: parser.type, description }; } } ================================================ FILE: apps/kirjs/src/app/modules/binary/parser/parsers/common.ts ================================================ export interface ParserConfig { description?: string; } export enum Endianness { BIG = 1, LITTLE } export interface BinaryParserConfig { endianness: Endianness.BIG; } export const defaultConfig: BinaryParserConfig = { endianness: Endianness.BIG }; export function beToLe32(val) { return ( ((val & 0xff) << 24) | ((val & 0xff00) << 8) | ((val >> 8) & 0xff00) | ((val >> 24) & 0xff) ); } export interface ReadResult { parent: ReadResult | null; results: ReadResult[]; } ================================================ FILE: apps/kirjs/src/app/modules/binary/parser/parsers/debugger-parser.ts ================================================ import { BinaryReader, BinaryReaderResult } from '../readers/abstract-reader'; export abstract class DebuggerParser { type = 'debugger'; read(reader: BinaryReader, data: BinaryReaderResult) { // tslint:disable-next-line:no-debugger debugger; } readOrdered(reader: BinaryReader, data: BinaryReaderResult) { // tslint:disable-next-line:no-debugger debugger; } } ================================================ FILE: apps/kirjs/src/app/modules/binary/parser/parsers/first-bit-parser.spec.ts ================================================ import { StringBinaryReader } from '../readers/string-reader'; import { VarUintParser } from './var-uint-parser'; fdescribe('FirstBitParse', () => { it('can read 1 byte', () => { const reader = new StringBinaryReader('0111111111111111'); const result = new VarUintParser().read(reader); expect(result.rawValue).toEqual('01111111'); }); it('can read 1 byte', () => { const reader = new StringBinaryReader('11111111'); const result = new VarUintParser().read(reader); expect(result.rawValue).toEqual('11111111'); }); it('can read 2 bytes', () => { const reader = new StringBinaryReader('1111111101111111111'); const result = new VarUintParser().read(reader); expect(result.rawValue).toEqual('1111111101111111'); }); it('can read 3 bytes', () => { const reader = new StringBinaryReader('111111111111111101111111111'); const result = new VarUintParser().read(reader); expect(result.rawValue).toEqual('111111111111111101111111'); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/parser/parsers/object-parser.spec.ts ================================================ import { StringBinaryReader } from '../readers/string-reader'; import { BinaryObjectParser } from './object-parser'; import { BitParser } from './bit-parser'; describe('BinaryParser', () => { beforeEach(() => { const s = 'Universal Serial Bus' .split('') .map(a => a.charCodeAt(0)) .map(a => a.toString(2)) .map(a => (a as any).padStart(8, 0)) .join(''); this.reader = new StringBinaryReader(s); }); describe('BinaryObjectParser', () => { it('can read simple bits', () => { const parser = new BinaryObjectParser(); parser.addStep('a', new BitParser({ length: 1 })); parser.addStep('b', new BitParser({ length: 2 })); parser.addStep('c', new BitParser({ length: 1 })); const result = parser.read(this.reader); expect(result.value).toEqual({ a: '0', b: '10', c: '1' }); }); it('can read nested objects', () => { const parser = new BinaryObjectParser(); parser.addStep('a', new BitParser({ length: 1 })); const innerParser = new BinaryObjectParser(); innerParser.addStep('a', new BitParser({ length: 1 })); innerParser.addStep('b', new BitParser({ length: 1 })); parser.addStep('inner', innerParser); parser.addStep('b', new BitParser({ length: 1 })); const result = parser.read(this.reader); expect(result.value).toEqual({ a: '0', inner: { a: '1', b: '0' }, b: '1' }); }); it('can read nested objects', () => { const parser = new BinaryObjectParser(); parser.addStep('a', new BitParser({ length: 1 })); const innerParser = new BinaryObjectParser(); innerParser.addStep('a', new BitParser({ length: 1 })); innerParser.addStep('b', new BitParser({ length: 1 })); parser.addStep('inner', innerParser); parser.addStep('b', new BitParser({ length: 1 })); const result = parser.readOrdered(this.reader); expect(result.value).toEqual([ { name: 'a', value: '0', rawValue: '0', type: 'bits' }, { name: 'inner', value: [ { name: 'a', value: '1', rawValue: '1', type: 'bits' }, { name: 'b', value: '0', rawValue: '0', type: 'bits' } ], rawValue: '10', type: 'object' }, { name: 'b', value: '1', rawValue: '1', type: 'bits' } ]); }); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/parser/parsers/object-parser.ts ================================================ import { AbstractBinaryParser } from './abstract-parser'; import { BinaryReader, BinaryReaderResult } from '../readers/abstract-reader'; export class BinaryObjectParser extends AbstractBinaryParser { type = 'object'; steps: { name: string; description?: string; parser: AbstractBinaryParser; }[] = []; addStep(name: string, parser: AbstractBinaryParser) { this.steps.push({ name, parser }); } read( reader: BinaryReader, data: BinaryReaderResult = {} ): BinaryReaderResult { let raw = ''; const val = this.steps.reduce((result, step) => { const { value, rawValue } = step.parser.read(reader, result); result[step.name] = value; raw += rawValue; return result; }, {}); return { value: val, rawValue: raw }; } readOrdered( reader: BinaryReader, data: BinaryReaderResult = [], start = 0 ): BinaryReaderResult { let raw = ''; let len = 0; const value = this.steps.reduce((result, step) => { const { value, rawValue, type, description, displayValue } = step.parser.readOrdered( reader, [...result, { name: '_parent', value: data }], start + len ); raw += rawValue; if (!type) { // tslint:disable-next-line:no-debugger debugger; } len += rawValue.length; result.push({ start: start + len - rawValue.length, end: start + len, length: rawValue.length, name: step.name, description, value, displayValue, rawValue, type }); return result; }, []); return { start, length: len, end: start + len, value, rawValue: raw, type: 'object' }; } } ================================================ FILE: apps/kirjs/src/app/modules/binary/parser/parsers/string-parser.spec.ts ================================================ import { BinaryParser } from '../binary-parser'; import { StringParser } from './string-parser'; import { StringBinaryReader } from '../readers/string-reader'; describe('BinaryParser', () => { beforeEach(() => { const s = 'Universal Serial Bus' .split('') .map(a => a.charCodeAt(0)) .map(a => a.toString(2)) .map(a => (a as any).padStart(8, 0)) .join(''); this.reader = new StringBinaryReader(s); }); fdescribe('string parser', () => { it('can read one letter', () => { const parser = new StringParser({ length: 1 }); expect(parser.read(this.reader).value).toBe('U'); }); it('can read 3 letttes', () => { const parser = new StringParser({ length: 3 }); expect(parser.read(this.reader).value).toBe('Uni'); }); describe('readuntil', () => { const s = 'lollol' .split('') .map(a => a.charCodeAt(0)) .map(a => a.toString(2)) .map(a => (a as any).padStart(8, 0)) .join(''); const s2 = 'ogogog' .split('') .map(a => a.charCodeAt(0)) .map(a => a.toString(2)) .map(a => (a as any).padStart(8, 0)) .join(''); it('can read until 00', () => { this.reader = new StringBinaryReader(s + '00000000' + s2); const parser = new StringParser({ readUntil: '00000000' }); const binaryReaderResult = parser.readOrdered(this.reader); expect(binaryReaderResult.value).toBe('lollol'); }); it('Reads the whole thing if no 00', () => { this.reader = new StringBinaryReader(s + '01010101' + s2); const parser = new StringParser({ readUntil: '00000000' }); const binaryReaderResult = parser.readOrdered(this.reader); expect(binaryReaderResult.value).toBe('lollolUogogog'); }); }); it('can read ordered 3 letttes', () => { const parser = new StringParser({ length: 3 }); const binaryReaderResult = parser.readOrdered(this.reader); expect(binaryReaderResult.value).toBe('Uni'); expect(binaryReaderResult.start).toBe(0); expect(binaryReaderResult.end).toBe(24); expect(binaryReaderResult.length).toBe(24); }); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/parser/parsers/string-parser.ts ================================================ import { AbstractBinaryParser } from './abstract-parser'; import { BinaryReader, BinaryReaderResult } from '../readers/abstract-reader'; import { resolveFunctionKeyOrValue, resolveFunctionOrvalue, resolveLengthOrdered } from '../utils'; import { ParserConfig } from './common'; function bytesToChar(a) { return String.fromCharCode(parseInt(a, 2)); } export interface StringParserConfig extends ParserConfig { length?: number | Function | string; readUntil?: string; } export class StringParser extends AbstractBinaryParser { type = 'string'; constructor(private config: StringParserConfig) { super(); } read( reader: BinaryReader, data: BinaryReaderResult = {}, start = 0 ): BinaryReaderResult { if (this.config.readUntil) { let value = ''; let rawValue = ''; while ( reader.peak(this.config.readUntil.length) !== this.config.readUntil && reader.peak(this.config.readUntil.length) > 0 ) { const letter = reader.read(8); value += bytesToChar(letter); rawValue += letter; } return { value, rawValue }; } else { const len = resolveLengthOrdered(this.config.length, data) * 8; const rawValue = reader.read(len); const value = rawValue .match(/.{8}/g) .map(bytesToChar) .join(''); return { value, rawValue }; } } readOrdered( reader: BinaryReader, data: BinaryReaderResult = [], start = 0 ): BinaryReaderResult { const result = this.read(reader, data); const length = start + result.rawValue.length; const end = start + length; return { ...result, type: this.type, description: this.config.description, start, end, length }; } } ================================================ FILE: apps/kirjs/src/app/modules/binary/parser/parsers/var-uint-parser.ts ================================================ import { AbstractBinaryParser } from './abstract-parser'; import { BinaryReader, BinaryReaderResult } from '../readers/abstract-reader'; import { BaseConfig } from '../binary-parser'; export interface VarUintParserConfig extends BaseConfig { size?: number; } export const defaultVarUintParserConfig = { size: 7 }; export class VarUintParser extends AbstractBinaryParser { type = 'bits'; constructor( private config: VarUintParserConfig = defaultVarUintParserConfig ) { super(); this.type = config.type || this.type; } static converter(n: string) { return parseInt(n, 2); } read( reader: BinaryReader, data: BinaryReaderResult = {} ): BinaryReaderResult { let hasNext = true; let value = ''; let rawValue = ''; const size = this.config.size || 7; let i = 100; while (hasNext) { const firstBit = reader.read(1); hasNext = !!+firstBit; rawValue += firstBit; const result = reader.read(size); value += result; rawValue += result; if (i-- < 1) { // tslint:disable-next-line:no-debugger debugger; } } const converter = this.config.converter || VarUintParser.converter; return { value: converter(rawValue), rawValue }; } readOrdered( reader: BinaryReader, data: BinaryReaderResult = [], start = 0 ): BinaryReaderResult { return { ...this.read(reader, data), type: this.type }; } } ================================================ FILE: apps/kirjs/src/app/modules/binary/parser/readers/abstract-reader.ts ================================================ export type BinaryReaderResult = any; export abstract class BinaryReader { abstract read(bits: number); abstract peak(bits: number); abstract hasMore(): boolean; } ================================================ FILE: apps/kirjs/src/app/modules/binary/parser/readers/string-reader.ts ================================================ import { BinaryReader, BinaryReaderResult } from './abstract-reader'; export class StringBinaryReader implements BinaryReader { private index = 0; constructor(private s: string) {} hasMore() { return this.index < this.s.length; } peak(bits: number): BinaryReaderResult { return this.s.slice(this.index, this.index + bits); } read(bits: number): BinaryReaderResult { this.index += bits; const result = this.s.slice(this.index - bits, this.index); return result; } } ================================================ FILE: apps/kirjs/src/app/modules/binary/parser/utils.ts ================================================ export function resolveLengthOrdered(functionOrValue, data) { if (typeof functionOrValue === 'string') { return resolveOrderedByKey(functionOrValue, data); } return typeof functionOrValue === 'function' ? functionOrValue(data) : functionOrValue; } export function resolveFunctionOrvalue(functionOrValue, arg) { return typeof functionOrValue === 'function' ? functionOrValue(arg) : functionOrValue; } export function resolveFunctionKeyOrValue(val, data, resolve) { if (typeof val === 'string') { return resolve(val, data); } if (typeof val === 'function') { return val(data, resolve); } return val; } export function resolveOrderedByKey(key: string, data: any[]) { return Object.values(data).find(a => a.name === key).value; } export function resolveByKey(key: string, data: any) { return data[key]; } export function strToBin(str) { return str .split('') .map(a => a.charCodeAt(0)) .map(a => a.toString(2).padStart(8, 0)) .join(''); } ================================================ FILE: apps/kirjs/src/app/modules/binary/shared.ts ================================================ export function bin2hex(bin: string) { return bin .match(/.{8}/gi) .map(a => (parseInt(a, 2).toString(16) as any).padStart(2, 0)) .join(''); } ================================================ FILE: apps/kirjs/src/app/modules/binary/to-read/to-read.component.css ================================================ h2 { font-size: 5vw !important; line-height: 5vw !important; } h3 { font-size: 3vw; color: #444; } :host ::ng-deep .book { width: 40vw; height: 100vw; background-repeat: no-repeat; background-size: 40vw; } ================================================ FILE: apps/kirjs/src/app/modules/binary/to-read/to-read.component.html ================================================

{{ title }}

{{ author }}

================================================ FILE: apps/kirjs/src/app/modules/binary/to-read/to-read.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ToReadComponent } from './to-read.component'; describe('ToReadComponent', () => { let component: ToReadComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ToReadComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(ToReadComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/binary/to-read/to-read.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-to-read', templateUrl: './to-read.component.html', styleUrls: ['./to-read.component.css'] }) export class ToReadComponent implements OnInit { @Input() author: string; @Input() title: string; constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/board/board.component.css ================================================ .cell { border: 1px #888 solid; margin-left: -1px; margin-top: -5px; } .cell.cell-x { border: none; } .cell-1 { background: black; } .board, .cell { display: inline-block; } .line { } ================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/board/board.component.html ================================================
================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/board/board.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BoardComponent } from './board.component'; describe('BoardComponent', () => { let component: BoardComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [BoardComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BoardComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should be created', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/board/board.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-board', templateUrl: './board.component.html', styleUrls: ['./board.component.css'] }) export class BoardComponent implements OnInit { @Input() pattern; @Input() cellHeight = 50; @Input() cellWidth = 50; @Input() transform; @Input() playing = false; @Input() delay = 500; constructor() {} public play() { this.playing = true; } runTransform() { if (this.playing) { this.pattern = this.transform(this.pattern); } setTimeout(() => { this.runTransform(); }, this.delay); } ngOnInit() { if (this.transform) { this.runTransform(); } } } ================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/cellular-automation-routing.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { SlidesRoutes } from '@ng360/slides'; import { CellularAutomationComponent } from './cellular-automation.component'; import { CellularAutomationModule } from './cellular-automation.module'; const routes = RouterModule.forChild( SlidesRoutes.get(CellularAutomationComponent) ); @NgModule({ imports: [routes, CellularAutomationModule], declarations: [], exports: [] }) export class CellularAutomationRoutingModule {} ================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/cellular-automation.component.css ================================================ h2 { font-weight: 800; } pre { font-size: 24px; } .empty-slide { width: 400px; height: 250px; border: 1px solid #000; margin-left: 300px; margin-top: 10px; -webkit-box-shadow: 10px 10px 5px 0px rgba(181, 172, 181, 1); -moz-box-shadow: 10px 10px 5px 0px rgba(181, 172, 181, 1); box-shadow: 10px 10px 5px 0px rgba(181, 172, 181, 1); } .html-elements { font-size: 28px; margin-top: 20px; margin-left: 24px; } .kirjs { width: 800px; height: 300px; background: url(intro/kirjs.png) no-repeat; background-size: 100%; margin: 0 auto; } .rules { display: flex; } .play:hover { background-color: #d6510f; } .play { font-size: 14px; padding: 5px; text-align: center; cursor: pointer; margin-left: 20px; margin-top: 10px; background-color: #000000; color: #ffffee; height: 30px; width: 30px; } .grid-header { margin-top: 20px; display: flex; } .img-wolfram { width: 400px; height: 400px; background: url('intro/wolfram.jpg'); } .img-moore { width: 400px; height: 400px; background: url('intro/moore.jpg') no-repeat; background-size: 100%; margin-right: 50px; } .img-neuman { width: 400px; height: 400px; background: url('intro/JohnvonNeumann.gif') no-repeat; background-size: 100%; margin-right: 50px; } .img-conway { width: 600px; height: 400px; background: url('intro/Conway_.jpg') no-repeat; background-size: 100%; margin-right: 50px; } .neighborhood { display: flex; } .img-gun { width: 800px; height: 600px; background: url('intro/Gosperglidergun.gif') no-repeat; background-size: 100%; margin-right: 50px; } .img-turing { width: 800px; height: 800px; background: url('intro/turing.gif') no-repeat; background-size: 100%; margin-right: 50px; } .img-mp { width: 800px; height: 800px; background: url('intro/metapixel.png') no-repeat; background-size: 100%; margin-right: 50px; } .img-30 { width: 800px; height: 800px; background: url('intro/30.png') no-repeat; background-size: 100%; margin-right: 50px; } .img-30-2 { width: 800px; height: 800px; background: url('intro/30-2.png') no-repeat; background-size: 100%; margin-right: 50px; } .img-gosper { width: 800px; height: 800px; background: url('intro/Bill_Gosper_2006.jpg') no-repeat; background-size: 100%; margin-right: 50px; } .img-cave { width: 800px; height: 800px; background: url('intro/maze.png') no-repeat; background-size: 100%; margin-right: 50px; } .img-nks { width: 800px; height: 800px; background: url('intro/nks.jpg') no-repeat; background-size: 100%; margin-right: 50px; } :host /deep/ .slide { display: block; } ================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/cellular-automation.component.html ================================================

Grid

Rules

Grid
{{ board1.playing ? '❚❚' : '▶' }}

Rules

Grid
{{ board1.playing ? '❚❚' : '▶' }}

Rules

Grid
{{ board1.playing ? '❚❚' : '▶' }}

Rule 90

Grid
{{ board1.playing ? '❚❚' : '▶' }}

Stephen Wolfram (Rule 30)

Grid
{{ board1.playing ? '❚❚' : '▶' }}

Grid
{{ board1.playing ? '❚❚' : '▶' }}

Grid
{{ board1.playing ? '❚❚' : '▶' }}

New kind of science

cambridge

Cellular Automata 2D!

Neighborhoods

Edward F. Moore

John von Neumann

Extended

Random Rules: Survive: {{ examples.life.randomRules.survive }} Born: {{ examples.life.randomRules.born }}
{{ board1.playing ? '❚❚' : '▶' }}

Survive: {{ examples.life.labyrynthRules.survive }} Born: {{ examples.life.labyrynthRules.born }}
{{ board1.playing ? '❚❚' : '▶' }}

Survive: {{ examples.life.labRules4.survive }} Born: {{ examples.life.labRules4.born }}
{{ board1.playing ? '❚❚' : '▶' }}

Survive: {{ examples.life.labRules5.survive }} Born: {{ examples.life.labRules5.born }}
{{ board1.playing ? '❚❚' : '▶' }}

Survive: {{ examples.life.labRules6.survive }} Born: {{ examples.life.labRules6.born }}
{{ board1.playing ? '❚❚' : '▶' }}

Random cave generation

game of life: Survive: {{ examples.life.gameOfLifeRules.survive }} Born: {{ examples.life.gameOfLifeRules.born }}
{{ board1.playing ? '❚❚' : '▶' }}

Still life
{{ board1.playing ? '❚❚' : '▶' }}

What's that?
{{ board1.playing ? '❚❚' : '▶' }}

What's that?
{{ board1.playing ? '❚❚' : '▶' }}

List of oscilators

Space ships - glider (by Richard K. Guy)
{{ board1.playing ? '❚❚' : '▶' }}

Copperhead (2016)
{{ board1.playing ? '❚❚' : '▶' }}

Guns - Gosper Gun ($50)

Synthesis

Metapixel (Guess The Size)

Metapixel (2048x2048)

Life in life

Turing Machine

Bunch of demos

3d

================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/cellular-automation.component.ts ================================================ import { Component } from '@angular/core'; function inPlace(rule) { const map = (256 + rule) .toString(2) .substr(1) .split('') .map(Number) .reduce((a, v, i) => { a[(15 - i).toString(2).substr(1)] = v; return a; }, {}); function transformRow(row) { const result = []; for (let i = 0; i < row.length; i++) { const a1 = row[(row.length + i - 1) % row.length].toString(); const a2 = row[i].toString(); const a3 = row[(i + 1) % row.length].toString(); result[i] = [map[a1 + a2 + a3]]; } return result; } return function(grid) { return grid.map(transformRow); }; } function transform2d(rule) { const survive = rule.survive.reduce((a, v) => { a[v] = true; return a; }, {}); const born = rule.born.reduce((a, v) => { a[v] = true; return a; }, {}); return function(grid) { return grid.map((row, y) => { return row.map((cell, x) => { const px = (row.length + x - 1) % row.length; const nx = (x + 1) % row.length; const py = (grid.length + y - 1) % grid.length; const ny = (y + 1) % grid.length; const score = grid[py][px] + grid[py][x] + grid[py][nx] + grid[y][px] + grid[y][nx] + grid[ny][px] + grid[ny][x] + grid[ny][nx]; if ((cell === 0 && born[score]) || (cell === 1 && survive[score])) { return 1; } return 0; }); }); }; } function appendTransform(rule) { const transform = inPlace(rule); return function(grid) { const lastRow = grid[grid.length - 1]; grid.push(transform([lastRow])[0]); return grid; }; } const rand = Math.floor(Math.random() * 255); const randomRules = new Array(8).fill(0).reduce( (a, v, i) => { if (Math.random() < 0.3) { a.survive.push(i); } if (Math.random() < 0.3) { a.born.push(i); } return a; }, { survive: [], born: [] } ); const gameOfLifeRules = { survive: [2, 3], born: [3] }; const gameOfLife = transform2d(gameOfLifeRules); const randomPattern = new Array(30) .fill(0) .map(() => new Array(60).fill(0).map(() => (Math.random() < 0.3 ? 1 : 0))); const randomPatternSparce = new Array(30) .fill(0) .map(() => new Array(60).fill(0).map(() => (Math.random() < 0.04 ? 1 : 0))); const labyrynthRules = { survive: [0, 1, 2, 3], born: [1] }; const labRules4 = { survive: [0, 1, 2, 3, 4], born: [1] }; const labRules5 = { survive: [0, 1, 2, 3, 4, 5], born: [1] }; const labRules6 = { survive: [0, 1, 2, 3, 4, 5, 6], born: [1] }; @Component({ selector: 'kirjs-cellular-automation', templateUrl: './cellular-automation.component.html', styleUrls: ['./cellular-automation.component.css'] }) export class CellularAutomationComponent { examples = { intro: { inverse(pattern) { return pattern.map(line => line.map(cell => (cell + 1) % 2)); }, rand, inPlace16: inPlace(16), inPlace90: inPlace(90), twoD18: appendTransform(18), twoD30: appendTransform(30), twoD90: appendTransform(90), twoD110: appendTransform(110), twoDRand: appendTransform(rand), pattern: [ [0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0] ] }, life: { randomRules, random: transform2d(randomRules), gameOfLifeRules, gameOfLife, labyrynthRules: labyrynthRules, labyrynth: transform2d(labyrynthRules), labRules4, labyrynth4: transform2d(labRules4), labRules5, labyrynth5: transform2d(labRules5), labRules6, labyrynth6: transform2d(labRules6), pattern: { randomPattern, randomPatternSparce, stillLife: [ [0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0] ], oscilator: [ [0, 0, 0, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 1, 0, 0], [0, 0, 0, 0, 0] ], oscilatorClock: [ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0], [0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0], [0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0], [0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0], [0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ], glider: [ [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ], copperhead: [ [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ] } } }; } ================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/cellular-automation.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SlidesModule } from '@ng360/slides'; import { FeedbackModule } from '@codelab/feedback'; import { CellularAutomationComponent } from './cellular-automation.component'; import { BoardComponent } from './board/board.component'; import { Rule3Component } from './rule3/rule3.component'; import { RuleComponent } from './rule/rule.component'; import { Rule8Component } from './rule8/rule8.component'; import { OscilatorsComponent } from './oscilators/oscilators.component'; import { Rule4Component } from './rule3/rule4/rule4.component'; @NgModule({ imports: [SlidesModule, FeedbackModule, CommonModule], declarations: [ CellularAutomationComponent, BoardComponent, Rule3Component, RuleComponent, Rule8Component, OscilatorsComponent, Rule4Component ], exports: [ CellularAutomationComponent, CellularAutomationComponent, BoardComponent, Rule3Component, RuleComponent, Rule8Component, OscilatorsComponent, BoardComponent, Rule4Component ] }) export class CellularAutomationModule {} ================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/oscilators/oscilators.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/oscilators/oscilators.component.html ================================================
Period First discovered Discoverer Year of discovery                      Smallest known Minimum # of cells
2 blinker John Conway 1970 blinker 3
3 pulsar John Conway 1970 caterer 12
4 pinwheel Simon Norton 1970 mazing, mold 12
5 octagon 2 Sol Goodman, Arthur Taber 1971 pseudo-barberpole 15
6 $rats David Buckingham 1972 unix 16
7 burloaferimeter David Buckingham 1972 28P7.1, 28P7.2, burloaferimeter 28
8 figure eight Simon Norton 1970 figure eight 12
9 worker bee David Buckingham 1972 29P9 29
10 42P10.1 David Buckingham 1976 24P10 24
11 38P11.1 David Buckingham 1977 rattlesnake 33
12 dinner table Robert Wainwright 1972 dinner table 33
13 65P13.1 David Buckingham 1976 34P13 34
14 tumbler George Collins, Jr. 1970 tumbler 16
15 pentadecathlon John Conway 1970 pentadecathlon 12
16 two pre-L hasslers Robert Wainwright 1983 Achim's p16 32
17 54P17.1 Dean Hickerson 1997 Honey thieves 36
18 117P18 David Buckingham 1991 43P18 43
19 None found
20 145P20 Noam Elkies 1995 mold on fumarole 30
21 124P21 Robert Wainwright 1995 caterer on 42P7.1 54
22 168P22.1 Noam Elkies 1997 36P22 36
23 None found
24 186P24 Bill Gosper 1994 caterer on figure eight 24
25 134P25 Noam Elkies 1994 p25 honey farm hassler 88
26 pre-pulsar shuttle 26 David Buckingham 1983 87P26 87
27 123P27.1 Noam Elkies 2002 56P27 56
28 Newshuttle David Buckingham 1973 pre-pulsar shuttle 28, mold on 41P7.2 53
29 pre-pulsar shuttle 29 David Buckingham 1980 pre-pulsar shuttle 29 54
30 queen bee shuttle Bill Gosper 1970 queen bee shuttle 20
31 48P31 Matthias Merzenich 2010 48P31 48
32 gourmet David Buckingham 1978 gourmet 66
33 258P3 on Achim's p11 Noam Elkies, Achim Flammenkamp 1997 caterer on rattlesnake 46
34 No non-trivial examples found
35 P35 beehive hassler Dean Hickerson 1995 p35 honey farm hassler 44
36 P36 toad hassler Robert Wainwright 1984 22P36 22
37 124P37 Nicolay Beluchenko 2009 124P37 124
38 None found
39 134P39.1 David Buckingham, Noam Elkies 2000 caterer on 34P13 46
40 P40 B-heptomino shuttle David Buckingham ≤1991 26P40 26
41 None found
42 P42 glider shuttle Noam Elkies 1994 unix on 41P7.2 57
43 P43 glider loop Mike Playle 2013 P43 glider loop 228
44 P44 pi-heptomino hassler David Buckingham 1992 mold on rattlesnake 45
45 pentadecathlon on snacker  ? ≤1995 pentadecathlon on thumb 1 48
46 twin bees shuttle Bill Gosper 1971 twin bees shuttle 28
47 pre-pulsar shuttle 47 David Buckingham 1982 pre-pulsar shuttle 47 84
48 P48 toad hassler Bill Gosper 1994 65P48 65
49 p49 glider shuttle Noam Elkies 1999 p49 glider loop 228
50 P50 glider shuttle Dean Hickerson 1992 P50 traffic jam 92
51 112P51 Nicolay Beluchenko 2009 92P51 92
52 35P52 David Buckingham 1977 35P52 35
53 P53 glider loop Mike Playle 2013 P53 glider loop 228
54 P54 shuttle David Buckingham 1974 P54 shuttle 48
55 pre-pulsar hassler 55 David Buckingham 1986 pseudo-barberpole on rattlesnake 49
56 P56 B-heptomino shuttle David Buckingham ≤1991 P56 B-heptomino shuttle 45
57 P57 Herschel loop 1 Dietrich Leithner 1997 P57 glider loop 228
58 P58 toadsucker Bill Gosper 1994 pre-pulsar shuttle 58 104
59 P59 Herschel loop 1 David Buckingham 1997 P59 glider loop 228
60 P60 glider shuttle David Buckingham ≤ 1971 mold on pentadecathlon 24
61 P61 Herschel loop 1 David Buckingham 1996 P61 glider loop 228
62  ?  ?  ? 110P62 110
63  ?  ?  ? snacker on 38P7.2 78
64  ?  ?  ? Merzenich's p64 34
65  ?  ?  ? fumarole on 34P13 52
66  ?  ?  ? caterer on 36P22 48
67  ?  ?  ?  ?  ?
68  ?  ?  ?  ?  ?
69  ?  ?  ?  ?  ?
70  ?  ?  ? 78P70 78
71  ?  ?  ?  ?  ?
72 47P72 Robert Wainwright 1990 figure eight on 22P36 36
73  ?  ?  ?  ?  ?
74  ?  ?  ?  ?  ?
75  ?  ?  ? 6 bits 49
76  ?  ?  ?  ?  ?
77  ?  ?  ? 77P77 77
78  ?  ?  ? 126P78.1 126
79  ?  ?  ?  ?  ?
80  ?  ?  ? 122P80.1 122
81  ?  ?  ?  ?  ?
82  ?  ?  ?  ?  ?
83  ?  ?  ?  ?  ?
84  ?  ?  ? 114P84 84
85  ?  ?  ?  ?  ?
86  ?  ?  ?  ?  ?
87  ?  ?  ? 84P87 84
88 P88 pi-heptomino hassler Dean Hickerson 1994 figure eight on rattlesnake 45
89  ?  ?  ?  ?  ?
90  ?  ?  ? fumarole on 43P18 61
91  ?  ?  ?  ?  ?
92  ?  ?  ? twin bees shuttles hassling blinker 61
93  ?  ?  ? caterer on 48P31 60
================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/oscilators/oscilators.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { OscilatorsComponent } from './oscilators.component'; describe('OscilatorsComponent', () => { let component: OscilatorsComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [OscilatorsComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(OscilatorsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should be created', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/oscilators/oscilators.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-oscilators', templateUrl: './oscilators.component.html', styleUrls: ['./oscilators.component.css'] }) export class OscilatorsComponent implements OnInit { constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/rule/rule.component.css ================================================ :host { margin: 10px 0; } .rule { margin-left: 10px; } .arrow { font-size: 18px; text-align: center; padding: 5px; } ================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/rule/rule.component.html ================================================
================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/rule/rule.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RuleComponent } from './rule.component'; import { BoardComponent } from '../board/board.component'; describe('RuleComponent', () => { let component: RuleComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [RuleComponent, BoardComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(RuleComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should be created', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/rule/rule.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-rule', templateUrl: './rule.component.html', styleUrls: ['./rule.component.css'] }) export class RuleComponent implements OnInit { @Input() before = []; @Input() after = []; @Input() arrow = false; constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/rule3/rule3.component.css ================================================ .rule { display: flex; width: 100%; justify-content: space-between; } .index { text-align: center; font-size: 50px; flex: 1; margin-left: -20px; } kirjs-rule { margin-right: 20px; } ================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/rule3/rule3.component.html ================================================
{{ v[1] }}
================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/rule3/rule3.component.ts ================================================ import { Component, Input, OnChanges, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-rule3', templateUrl: './rule3.component.html', styleUrls: ['./rule3.component.css'] }) export class Rule3Component implements OnInit, OnChanges { after: Array>; before: Array>; @Input() rule = 0; @Input() indexes = false; @Input() arrow = false; constructor() {} ngOnChanges() { this.after = (256 + this.rule) .toString(2) .substr(1) .split('') .map(Number) .map(a => ['x', a, 'x']); // tslint:disable this.before = this.after .map((v, i) => (8 + i) .toString(2) .substr(1) .split('') ) .reverse(); } ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/rule3/rule4/rule4.component.css ================================================ .wrapper { display: flex; flex-wrap: wrap; } .rule { margin-left: 10px; margin-top: 10px; } .cell { width: 20px; height: 20px; border: 1px #ddd solid; } .after { margin-left: 20px; } .active { background: lime; } ================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/rule3/rule4/rule4.component.html ================================================
================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/rule3/rule4/rule4.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { Rule4Component } from './rule4.component'; describe('Rule4Component', () => { let component: Rule4Component; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [Rule4Component] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(Rule4Component); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/rule3/rule4/rule4.component.ts ================================================ import { Component, Input, OnChanges } from '@angular/core'; @Component({ selector: 'kirjs-rule4', templateUrl: './rule4.component.html', styleUrls: ['./rule4.component.css'] }) export class Rule4Component implements OnChanges { @Input() test; before: string[][]; after: number[]; ngOnChanges() { this.after = this.test.table.map(a => (a === 'enable' ? 1 : 0)); this.before = new Array(8) .fill(0) .map((v, i) => (8 + i) .toString(2) .substr(1) .split('') ) .reverse(); } } ================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/rule8/rule8.component.css ================================================ :host { width: 100%; } kirjs-rule { margin-right: 20px; display: flex; flex-direction: column; align-items: center; float: left; margin-bottom: 20px; } ================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/rule8/rule8.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/cellular-automation/rule8/rule8.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-rule8', templateUrl: './rule8.component.html', styleUrls: ['./rule8.component.css'] }) export class Rule8Component implements OnInit { after: Array>>; before: Array>>; @Input() rule = 0; @Input() arrow = false; constructor() {} ngOnInit() { const numbers = new Array(1024).fill(0, 0, 1024); this.after = numbers.map(a => [[Math.round(Math.random())]]); this.before = numbers.map((v, i) => { const arr = (1024 + i).toString(2).substr(1); return [ arr.substr(0, 3).split(''), arr.substr(3, 3).split(''), arr.substr(6, 3).split('') ]; }); } } ================================================ FILE: apps/kirjs/src/app/modules/gomoku/board/board.component.html ================================================
================================================ FILE: apps/kirjs/src/app/modules/gomoku/board/board.component.scss ================================================ .column { display: flex; flex-grow: 1; flex-basis: 0; } kirjs-tools { display: none; } .board { width: 100%; height: 100%; border-left: 1px #999 solid; border-top: 1px #999 solid; display: flex; flex-direction: column; } .cell { &.highlight-yellow { background: #ff0; } &.highlight-orange { background: #ffb900; } &.highlight-transparent { opacity: 0.4; } flex-grow: 1; flex-basis: 0; border-right: 1px #999 solid; border-bottom: 1px #999 solid; display: flex; align-items: center; justify-content: center; position: relative; } .theme-print { .cell-1:before { content: 'x'; color: #c53a00; } .cell-2:before { content: 'o'; color: #666; } .cell-1:before, .cell-2:before { font-size: 12px; } } .theme-gomoku { .cell-1:before { content: '⚫'; } .cell-2:before { content: '🔴'; } .cell-3:before { content: '⚫'; opacity: 0.5; } .cell-4:before { content: '🔴'; opacity: 0.5; } .cell-1:before, .cell-2:before { font-size: 28px; } } .theme-xo { .cell-1:before { content: '×'; color: red; } .cell-2:before { content: '⚬'; color: blue; } .cell-1:before, .cell-2:before { font-size: 200px; } } ================================================ FILE: apps/kirjs/src/app/modules/gomoku/board/board.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BoardComponent } from './board.component'; import { ToolsComponent } from '../tools/tools.component'; describe('BoardComponent', () => { let component: BoardComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [BoardComponent, ToolsComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BoardComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/gomoku/board/board.component.ts ================================================ import { Component, HostListener, Input } from '@angular/core'; import { Gomoku } from 'gomoku-tools'; import { Highlights } from '../highlights'; @Component({ selector: 'kirjs-board', templateUrl: './board.component.html', styleUrls: ['./board.component.scss'] }) export class BoardComponent { @Input() theme = 'gomoku'; @Input() game = new Gomoku().moveTo( 'H8', 'I8', 'H9', 'H7', 'J9', 'I9', 'I10', 'H11', 'J11', 'K12', 'J10', 'J12', 'G10', 'H10', 'G8', 'F7', 'G9', 'G7', 'I7', 'J6', 'H12' ); @Input() highlights = new Highlights(); @Input() showTools = true; @HostListener('window:keydown.H', ['$event.target']) @HostListener('window:keydown.J', ['$event.target']) back() { this.game.back(); } @HostListener('window:keydown.T', ['$event.target']) @HostListener('window:keydown.K', ['$event.target']) forward() { this.game.forward(); } constructor() {} log() { console.log(this.game.getHistory()); console.log(this.game.getHistory().length); console.log(this.highlights.highlights); } } ================================================ FILE: apps/kirjs/src/app/modules/gomoku/board/board.module.ts ================================================ import { NgModule } from '@angular/core'; import { BoardComponent } from './board.component'; import { CommonModule } from '@angular/common'; import { ToolsComponent } from '../tools/tools.component'; @NgModule({ imports: [CommonModule], declarations: [BoardComponent, ToolsComponent], exports: [BoardComponent] }) export class GomokuBoardModule {} ================================================ FILE: apps/kirjs/src/app/modules/gomoku/gomoku.component.css ================================================ #intro, #outro { /* background: url(./browser/landscape-with-trees-1.jpg) no-repeat; */ background-size: 100% auto; display: inline-block; height: 100vh; } input[type='checkbox'] { transform: scale(2); } .hb, cake { width: 100vw; height: 200px; background-position: center center; background-color: white; background-image: url('print/hb.jpg'); background-size: 300px; background-repeat: no-repeat; } kirjs-board { width: 500px; height: 500px; } :host ::ng-deep [title][blue] .slide { background: #0079ff; } :host ::ng-deep [title] .slide { align-items: center; background: #ff6e00; color: white; display: flex; height: 100vh; justify-content: center; text-align: center; width: 100%; } h1 { font-size: 100px; } tr:first-child td { background: #ff6e00; } td, th { font-size: 40px; padding: 20px; } b { background: transparent; color: black; text-decoration: underline dotted; } a { color: #444; } ================================================ FILE: apps/kirjs/src/app/modules/gomoku/gomoku.component.html ================================================

Gomoku

by @kirjs

Plan

  1. What's gomoku?
  2. Rules
  3. Why Gomoku?
  4. Gomoku and Computers
  5. Sample game

Gomoku

Abstract strategy board game

coming from ancient asia (China & Japan)

tic-tac-toe

Gomoku

Rules

Rule 1: Black puts a stone first

Rule 2: Players alternate, putting a stone of their color on an empty cell

Rule 3: The first player to get 5 in a row wins

Sample game

Winning plan:

  • Open 3 attack
  • Open 4 attack
  • Victory on continuous 4s
  • A fork
  • ????
  • Profit

8 Reasons to play Gomoku

1. Brain massage

  • Lots of memorization
  • Patterns recognition
  • Problem solving
  • A lot of gomoku playes became successful at poker

2. Transferable life skills

  • Learn to win/lose
  • Opportunities to learn from mistakes
  • The skill is directly proportional to the amount of work put in
  • Strategical/Tacting thinking
  • Understanding consequences / thinking ahead

3. Social Aspect

  • Cross cultures/ages/accessible to everyone
  • Play people from different countries
  • Play in Tournaments or online
  • Play solo or in a team.
  • Hopefully soon play in real life :)

4. Quantifiable

  • All stats are out there
  • Learning curve - plateaus and accelerations.

5. Easy to start

  • Can play with pen and paper
  • Play online
  • A bunch of android apps
  • Very simple rules

6.Not very popular in the US

  • Very few people are playing
  • Not much has been done
  • Not much learning materials/tutorials/videos in english

7. Lots of space for technical solutions

  • No good database of games
  • Existing online playing platforms not perfect

8. It's Fun!

Gomoku and Computers 🤖

Complexity

Game Space/State
as log to base 10
Game Tree
as log to base 10
Branching Factor
Gomoku 105 70 210
Chess 47 123 35
Go 170 360 250
Draughts (8x8) 20 20 2.8
Tic-Tac-Toe 3 5 4
More on wikipedia

In the initial rules ⚫️ always started in the center

In 1994 Victor Ellis proved that this way ⚫️ always win. 📄

Since then more advanced opening rules have been used, which haven't yet been solved.

One of them is swap2

(Using divide and choose)

Black is stronger

A draw?

White surewin

Swap2 opening hasn't been solved by a computer

Gomocup is a yearly gomoku AI tournament.

Now let's look at a real game

Very Advanced - Defend from 4s

How to start playing

How to start playing

================================================ FILE: apps/kirjs/src/app/modules/gomoku/gomoku.component.ts ================================================ import { Component } from '@angular/core'; import { parse } from 'babylon'; import { TicTacToe, Gomoku } from 'gomoku-tools'; import utils from 'gomoku-tools/src/tools/utils'; declare const require; const json = require('./renlib/moves.json'); class Node { p: string; down: boolean; depth = 0; parent: Node; children: Node[] = []; constructor(public position: [number, number]) { this.p = position.join(','); } addChild(node: Node) { this.children.push(node); node.parent = this; node.depth = this.depth + 1; } } let i = 0; function buildTree(moves, index, parentNode) { i++; const move = moves[index]; let node = new Node(move.move); node.down = !!move.down; parentNode.addChild(node); if (index + 1 < moves.length) { if (move.down) { node.down = true; } if (move.right) { while (node && !node.down) { if (!node) { throw new Error('Weird'); } node = node.parent; } node = node.parent; } return buildTree(moves, index + 1, node); } } const parent = new Node([2, 2]); buildTree(json.moves, 0, parent); console.log(i); class RenlibGame { private current: Node; parent; constructor(private start) { this.current = start; } back() { if (this.current.parent) { this.current = this.current.parent; } } moveTo(point) { let child = this.current.children.find( ({ position }) => position[0] === point[0] && position[1] === point[1] ); if (!child) { child = new Node(point); this.current.children.push(child); } this.current = child; } forward() {} getPosition() { let node = this.current; const game = [node.position]; while ((node = node.parent)) { game.push(node.position); } const putStones = (stones, move, index) => { stones[move[0]][move[1]] = (index % 2) + 1; return stones; }; const position = game .reverse() .reduce(putStones, utils.generateEmptyPosition(15, 15)); this.current.children .map(n => n.position) .reduce((stones, move) => { stones[move[0]][move[1]] = (game.length % 2) + 1 + 2; return stones; }, position); console.log(position); return position; } } @Component({ selector: 'kirjs-gomoku', templateUrl: './gomoku.component.html', styleUrls: ['./gomoku.component.css'] }) export class GomokuComponent { fontSize = 18; games = { renlib: new RenlibGame(parent), ticTacToe: new TicTacToe().moveTo('B2', 'A2', 'A1', 'C3', 'B1', 'B3', 'C1'), empty: new Gomoku().moveTo(), start: new Gomoku().moveTo('H8'), start2: new Gomoku().moveTo('H8', 'H7'), swap2: new Gomoku().moveTo('H8', 'E7', 'G8'), // Sure win swap23: new Gomoku().moveTo('H8', 'E7', 'F10'), // Draw swap24: new Gomoku().moveTo('H8', 'H13', 'M10'), // White surewin H11, 10 horizontal swap25: new Gomoku().moveTo('C3', 'L7', 'F8'), // White surewin swap26: new Gomoku().moveTo('H8', 'J8', 'M8'), // Central Draw start5: new Gomoku() .moveTo('H8', 'H7', 'I8', 'I7', 'J8', 'J7', 'K8', 'K7', 'L8') .jumpToMove(2), sample: new Gomoku() .moveTo('H8', 'I7', 'H7', 'H6', 'G8', 'I8', 'H9', 'I6') .jumpToMove(2), start52: new Gomoku().moveTo( 'H8', 'H7', 'I8', 'I7', 'J8', 'K8', 'J9', 'J7', 'G8', 'F8', 'G7', 'K7', 'L7', 'I6', 'L9', 'K5', 'K9', 'H5', 'G4', 'K4', 'K6', 'J5', 'L3', 'I5', 'L5', 'G5' ), fork33: new Gomoku().moveTo('H8', 'H7', 'I8', 'I7', 'J7', 'I6', 'J6', 'H6'), fork43: new Gomoku().moveTo( 'H8', 'H7', 'I8', 'I7', 'J7', 'I6', 'J6', 'H6', 'G8', 'F8' ), fork44: new Gomoku().moveTo( 'H8', 'H7', 'I8', 'I7', 'J7', 'I6', 'J6', 'H6', 'G8', 'F8', 'J5', 'J4' ), wonGame: new Gomoku().moveTo( 'H8', 'I10', 'I11', 'H10', 'J10', 'I9', 'J8', 'J9', 'K9', 'L8', 'G9', 'I7', 'I6', 'I8', 'G8', 'L7', 'K8', 'L9', 'L10', 'J7', 'K7', 'L5', 'L6', 'K6', 'M4', 'H9' ), openThree: new Gomoku().moveTo('H8', 'H7', 'I8', 'I7', 'J8'), old: new Gomoku().moveTo( 'h8', 'i8', 'h7', 'h9', 'j7', 'i7', 'i6', 'j5', 'h5', 'g4', 'h6', 'h4', 'f6', 'g6', 'g7', 'e5', 'f8', 'e9', 'f9', 'f7', 'g8', 'e10', 'j10', 'i9', 'j9', 'i10', 'i11', 'j8', 'e8', 'd8', 'h11', 'g10', 'h10', 'j12', 'k9', 'f11', 'e12', 'l8', 'k11', 'k8', 'm8', 'g11', 'h12', 'g13', 'g12', 'f12', 'e11', 'f4', 'i4', 'c7', 'f10', 'd6', 'g9', 'g3' ), moreFours: new Gomoku().moveTo( 'C14', 'C12', 'D14', 'D12', 'E14', 'F12', 'G14', 'G12', 'C10', 'B10', 'E10', 'O1', 'F10', 'N1', 'G10', 'H10' ), openThreeSpaced: new Gomoku().moveTo('H8', 'H7', 'I8', 'I7', 'K8'), closedFour: new Gomoku().moveTo( 'H8', 'H7', 'I8', 'I7', 'J8', 'K8', 'J9', 'J7', 'G8' ), closedBrokenFour: new Gomoku().moveTo( 'H8', 'H7', 'I8', 'I7', 'J8', 'K8', 'J9', 'J7', 'F8' ), findWin: new Gomoku().moveTo( 'H8', 'H7', 'I8', 'I7', 'J8', 'K8', 'J9', 'J7', 'G8', 'F8', 'G7', 'K7', 'L7', 'I6', 'L9' ), defendFrom4s: new Gomoku().moveTo( 'H8', 'H7', 'I8', 'I7', 'J8', 'K8', 'J9', 'J7', 'G8', 'F8', 'G7', 'K7', 'L7', 'I6', 'L9', 'K5', 'K6', 'H5', 'G4', 'I5', 'J5', 'I4', 'I3' ), defendFrom4sSolved: new Gomoku().moveTo( 'H8', 'H7', 'I8', 'I7', 'J8', 'K8', 'J9', 'J7', 'G8', 'F8', 'G7', 'K7', 'L7', 'I6', 'L9', 'K5', 'K6', 'H5', 'G4', 'I5', 'J5', 'I4', 'I3', 'J6', 'N9', 'M8', 'K9', 'M9', 'L8', 'H4', 'G3', 'H6', 'H3', 'G6', 'F6' ), many4s: new Gomoku() .moveTo( 'h8', 'i9', 'j9', 'j8', 'h11', 'h10', 'g11', 'i11', 'i10', 'g12', 'g9', 'f10', 'g8', 'g10', 'e10', 'f9', 'f7', 'h9', 'f11', 'e8', 'd7', 'e6', 'g5', 'g7', 'e5', 'f4', 'd8', 'd9', 'd5', 'c5', 'h5', 'f5', 'f3', 'g4', 'h4', 'h3', 'i2', 'i3', 'j3', 'j5', 'k4', 'l5', 'e7', 'f6', 'b7', 'c7', 'e4', 'c6', 'g2', 'h1', 'i4', 'g6', 'd6', 'd4', 'h6', 'h7', 'k2', 'l1', 'j2', 'h2', 'l2', 'm2', 'j4', 'l4', 'k3' ) .jumpToMove(39) }; } ================================================ FILE: apps/kirjs/src/app/modules/gomoku/gomoku.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CommonModule } from '@angular/common'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { FeedbackModule } from '@codelab/feedback'; import { BrowserWindowModule } from '@codelab/browser'; import { GomokuComponent } from './gomoku.component'; import { GomokuBoardModule } from './board/board.module'; const routes = RouterModule.forChild(SlidesRoutes.get(GomokuComponent)); @NgModule({ imports: [ routes, BrowserWindowModule, FeedbackModule, CommonModule, GomokuBoardModule, SlidesModule ], declarations: [GomokuComponent], exports: [GomokuComponent] }) export class GomokuModule {} ================================================ FILE: apps/kirjs/src/app/modules/gomoku/highlights.spec.ts ================================================ import { Highlights } from './highlights'; describe('highlights', () => { beforeEach(() => { this.highlights = new Highlights(); }); it('gets empty value if there are no highlightss set', () => { expect(this.highlights.get([0, 0])).toBe(''); }); it('Toggles single value', () => { this.highlights.toggle([1, 2], 'pirojok'); expect(this.highlights.get([1, 2])).toBe('pirojok'); expect(this.highlights.get([5, 5])).toBe(''); this.highlights.toggle([1, 2], 'pirojok'); expect(this.highlights.get([1, 2])).toBe(''); }); it('Toggles multiple values', () => { this.highlights.toggle([1, 2], 'a'); this.highlights.toggle([1, 2], 'b'); expect(this.highlights.get([1, 2])).toBe('a b'); this.highlights.toggle([1, 2], 'c'); expect(this.highlights.get([1, 2])).toBe('a b c'); this.highlights.toggle([1, 2], 'b'); expect(this.highlights.get([1, 2])).toBe('a c'); }); it('Allows chaning', () => { expect(this.highlights.toggle([1, 2], 'a').get([1, 2])).toBe('a'); }); }); ================================================ FILE: apps/kirjs/src/app/modules/gomoku/highlights.ts ================================================ export class Highlights { history = []; historyIndex = 0; constructor(public highlights = []) {} redo() { if (this.historyIndex < this.history.length) { this.historyIndex++; const change = this.history[this.historyIndex]; this.highlights[change.point[0]][change.point[1]] = change.newValue; } } undo() { if (this.historyIndex > 0) { this.historyIndex--; const change = this.history[this.historyIndex]; this.highlights[change.point[0]][change.point[1]] = change.oldValue; } } toggle([x, y], type) { this.highlights[x] = this.highlights[x] || []; this.highlights[x][y] = this.highlights[x][y] || []; const oldValue = [...this.highlights[x][y]]; let newValue; if (this.highlights[x][y].includes(type)) { newValue = this.highlights[x][y] = this.highlights[x][y].filter( a => a !== type ); } else { this.highlights[x][y].push(type); newValue = this.highlights[x][y]; } this.history = this.history.slice(0, this.historyIndex); this.history.push({ point: [x, y], oldValue, newValue }); this.historyIndex++; return this; } get([x, y]) { return ( (this.highlights[x] && this.highlights[x][y] && this.highlights[x][y].join(' ')) || '' ); } clear(point?) { const [x, y] = point; if (point) { this.highlights[x] = this.highlights[x] || []; this.highlights[x][y] = []; } else { this.highlights = []; } } } ================================================ FILE: apps/kirjs/src/app/modules/gomoku/renlib/moves.json ================================================ { "header": { "open": 255, "type": "RenLib", "major": 3, "minor": 0 }, "moves": [ { "move": [7, 7], "down": 0, "right": 0, "hz4": 0, "mark": 0, "hasComment": 0, "hz2": 0, "hz1": 0, "extension": 0, "comment": {} }, { "move": [6, 7], "down": 1, "right": 0, "hz4": 0, "mark": 0, "hasComment": 0, "hz2": 0, "hz1": 0, "extension": 0, "comment": {} }, { "move": [5, 7], "down": 0, "right": 0, "hz4": 0, "mark": 0, "hasComment": 0, "hz2": 0, "hz1": 0, "extension": 0, "comment": {} }, { "move": [4, 7], "down": 0, "right": 0, "hz4": 0, "mark": 0, "hasComment": 0, "hz2": 0, "hz1": 0, "extension": 0, "comment": {} }, { "move": [3, 7], "down": 0, "right": 0, "hz4": 0, "mark": 0, "hasComment": 0, "hz2": 0, "hz1": 0, "extension": 0, "comment": {} }, { "move": [2, 7], "down": 0, "right": 0, "hz4": 0, "mark": 0, "hasComment": 0, "hz2": 0, "hz1": 0, "extension": 0, "comment": {} }, { "move": [1, 7], "down": 0, "right": 0, "hz4": 0, "mark": 0, "hasComment": 0, "hz2": 0, "hz1": 0, "extension": 0, "comment": {} }, { "move": [0, 7], "down": 0, "right": 1, "hz4": 0, "mark": 0, "hasComment": 0, "hz2": 0, "hz1": 0, "extension": 0, "comment": {} }, { "move": [6, 8], "down": 1, "right": 0, "hz4": 0, "mark": 0, "hasComment": 0, "hz2": 0, "hz1": 0, "extension": 0, "comment": {} }, { "move": [5, 9], "down": 0, "right": 0, "hz4": 0, "mark": 0, "hasComment": 0, "hz2": 0, "hz1": 0, "extension": 0, "comment": {} }, { "move": [4, 8], "down": 1, "right": 1, "hz4": 0, "mark": 0, "hasComment": 0, "hz2": 0, "hz1": 0, "extension": 0, "comment": {} }, { "move": [4, 10], "down": 0, "right": 0, "hz4": 0, "mark": 0, "hasComment": 0, "hz2": 0, "hz1": 0, "extension": 0, "comment": {} }, { "move": [3, 11], "down": 0, "right": 1, "hz4": 0, "mark": 0, "hasComment": 0, "hz2": 0, "hz1": 0, "extension": 0, "comment": {} }, { "move": [7, 8], "down": 0, "right": 1, "hz4": 0, "mark": 0, "hasComment": 0, "hz2": 0, "hz1": 0, "extension": 0, "comment": {} } ] } ================================================ FILE: apps/kirjs/src/app/modules/gomoku/renlib/parse.js ================================================ // Module import const fs = require('fs'); const Parser = require('binary-parser').Parser; const readComment = Parser.start() .array('comment', { type: 'uint8', readUntil: function(a, b) { return b.readUInt16LE() === 0 || b.length < 3; }, formatter: function(a) { return a .map(v => String.fromCharCode(parseInt(v, 10).toString(10))) .join(''); } }) .skip(2); const readMove = Parser.start() .uint8('move', { formatter: flag => [(flag % 16) - 1, Math.ceil(flag / 16) - 1] }) .bit1('down') // 128 Has Siblings .bit1('right') // 64 Has a child node, into the subtree .bit1('hz4') .bit1('mark') .bit1('hasComment') .bit1('hz2') .bit1('hz1') .bit1('extension') .choice('comment', { tag: 'hasComment', choices: { 0: Parser.start(), 1: readComment } }); const header = Parser.start() .endianess('little') .uint8('open', { assert: 255 }) .string('type', { length: 6, assert: 'RenLib' }) .uint8('open', { assert: 255 }) .int8('major', { assert: 3 }) .int8('minor') .skip(10); p = new Parser() .nest('header', { type: header }) .array('moves', { type: readMove, readUntil: 'eof' }); require('fs').readFile('ss.lib', function(err, data) { const v = p.parse(data); console.log(JSON.stringify(v)); fs.writeFileSync('moves.json', JSON.stringify(v), 'UTF-8'); }); ================================================ FILE: apps/kirjs/src/app/modules/gomoku/tools/tools.component.css ================================================ .tools { display: flex; } .tool.selected { outline: 1px #888 solid; background: #eeeeee; } .tool { outline: 1px #ddd solid; width: 30px; height: 30px; display: flex; justify-content: center; align-items: center; } .tool-next:before { content: '?'; } .tool-clear:before { content: 'X'; } .tool-undo:before { content: '⎌'; } .tool-redo { content: '⎌'; transform: scale(-1, 1); } .tool-red-maybe:before { content: '🔴'; opacity: 0.5; } .tool-highlight:before { content: '▩'; } .tool-black-maybe:before { content: '⚫'; opacity: 0.5; } ================================================ FILE: apps/kirjs/src/app/modules/gomoku/tools/tools.component.html ================================================
{{ selectedTool | json }}? ================================================ FILE: apps/kirjs/src/app/modules/gomoku/tools/tools.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ToolsComponent } from './tools.component'; describe('ToolsComponent', () => { let component: ToolsComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ToolsComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(ToolsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/gomoku/tools/tools.component.ts ================================================ import { Component, HostListener, Input, OnInit } from '@angular/core'; import { Highlights } from '../highlights'; @Component({ selector: 'kirjs-tools', templateUrl: './tools.component.html', styleUrls: ['./tools.component.css'] }) export class ToolsComponent implements OnInit { @Input() game; @Input() highlights; tools = [ { name: 'next', action: function(point, game) { game.moveTo(point); }, init: function() { return this; } }, { name: 'red-maybe', action: function(point, game, highlights) { highlights.toggle(point, 'highlight-transparent cell-2'); }, init: function() { return this; } }, { name: 'black-maybe', action: function(point, game, highlights) { highlights.toggle(point, 'highlight-transparent cell-1'); }, init: function() { return this; } }, { name: 'highlight', action: function(point, game, highlights) { highlights.toggle(point, 'highlight-yellow'); }, init: function() { return this; } }, { name: 'highlight2', action: function(point, game, highlights: Highlights) { highlights.toggle(point, 'highlight-orange'); }, init: function() { return this; } }, { name: 'clear', action: function(point, game, highlights) { highlights.clear(point); }, init: function() { return this; } }, { name: 'clear-all', init: function(game, highlights: Highlights) { highlights.clear(); } }, { name: 'undo', init: function(game, highlights: Highlights) { highlights.undo(); } }, { name: 'redo', init: function(game, highlights: Highlights) { highlights.redo(); } } ]; selectedTool: any; @HostListener('window:keydown', ['$event.keyCode']) shortcut(a: number) { if (a >= 48 && a <= 57) { this.handle(this.tools[a - 49]); } } handle(tool) { const selected = tool.init(this.game, this.highlights); if (selected) { this.selectedTool = tool; } } constructor() { this.selectedTool = this.tools[0]; } ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/gomoku-print/gomoku-print.component.css ================================================ #intro, #outro { /* background: url(./browser/landscape-with-trees-1.jpg) no-repeat; */ background-size: 100% auto; display: inline-block; } h1 { font-size: 20px; } h2 { font-size: 20px; } .hb { width: 200px; height: 200px; background: url('hb.jpg'); background-size: 200px; background-repeat: no-repeat; } .header p { font-size: 14px; } b { background: #fff; color: #000; font-size: 14px; font-weight: bold; } li { font-size: 12px; } @media print { @page { size: A4 landscape; } } .front { display: flex; width: 1024px; height: 768px; } .main-board { width: 360px; height: 360px; display: block; margin: 0 auto; } .left, .right { width: 512px; box-sizing: border-box; padding: 20px; } .board84 { display: block; width: 120px; height: 60px; } .explanation { display: flex; margin-bottom: 20px; } .explanation .text p { text-align: right; padding-right: 20px; font-size: 12px; margin-bottom: 2px; } .explanation .text { flex: 1; font-size: 12px; } .back { margin: 0 auto; width: 1024px; padding: 0 60px; display: flex; flex-wrap: wrap; } .mini-board { display: block; width: 400px; height: 400px; margin-right: 30px; margin-bottom: 20px; } ================================================ FILE: apps/kirjs/src/app/modules/gomoku-print/gomoku-print.component.html ================================================

Hi, I'm Gomoku!

I'm an awesome board game coming from ancient Asia!

My Rules are super simple:
  1. First player puts in a cell on the board below
  2. Second player puts
  3. Players go in turns until someone puts 5 items in a row on the board (that person wins!)
  4. Putting more than 5 items in a row is not a win

Some gomoku hints:

When playing try to create one of the patterns below:

’s have an “Open 3” attack.

if ’s don’t block it from either side, they lose.

This is also an “Open 3” .

Now there are 3 places where it can (and should) be blocked.

Here ’s have “Open 3”, but X’s went with “Open 4” attack

It’s even stronger and must be blocked asap!

And here has a “3 by 4 fork”. They will win this game!!

’s can’t block both attacks, and will lose.

(despite having an “Open 3” attack)

Enjoy!
================================================ FILE: apps/kirjs/src/app/modules/gomoku-print/gomoku-print.component.ts ================================================ import { Component } from '@angular/core'; import { parse } from 'babylon'; import { TicTacToe, Gomoku } from 'gomoku-tools'; declare const require; @Component({ selector: 'kirjs-gomoku', templateUrl: './gomoku-print.component.html', styleUrls: ['./gomoku-print.component.css'] }) export class GomokuPrintComponent { fontSize = 18; game = new Gomoku().moveTo(); examples = { open3: new Gomoku({ cellsX: 8, cellsY: 4 }).moveTo( 'C3', 'C2', 'D3', 'D2', 'E3' ), open32: new Gomoku({ cellsX: 8, cellsY: 4 }).moveTo( 'C3', 'C2', 'D3', 'D2', 'F3' ), close4: new Gomoku({ cellsX: 8, cellsY: 4 }).moveTo( 'C3', 'C2', 'D3', 'D2', 'F3', 'E2', 'E3', 'B3' ), fork: new Gomoku({ cellsX: 8, cellsY: 5 }).moveTo( 'C3', 'C2', 'D3', 'D2', 'F3', 'E2', 'E3', 'B3', 'F4', 'E4', 'F2' ) }; } ================================================ FILE: apps/kirjs/src/app/modules/gomoku-print/gomoku-print.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { GomokuPrintComponent } from './gomoku-print.component'; import { GomokuBoardModule } from '../gomoku/board/board.module'; import { XComponent } from './x/x.component'; import { OComponent } from './o/o.component'; @NgModule({ imports: [ RouterModule.forChild([{ path: `**`, component: GomokuPrintComponent }]), GomokuBoardModule ], declarations: [GomokuPrintComponent, XComponent, OComponent], exports: [GomokuPrintComponent] }) export class GomokuPrintModule {} ================================================ FILE: apps/kirjs/src/app/modules/gomoku-print/o/o.component.css ================================================ :host { color: #444; font-size: 18px; font-weight: bold; line-height: 14px; } ================================================ FILE: apps/kirjs/src/app/modules/gomoku-print/o/o.component.html ================================================ o ================================================ FILE: apps/kirjs/src/app/modules/gomoku-print/o/o.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { OComponent } from './o.component'; describe('OComponent', () => { let component: OComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [OComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(OComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/gomoku-print/o/o.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ // tslint:disable-next-line selector: 'o', templateUrl: './o.component.html', styleUrls: ['./o.component.css'] }) export class OComponent {} ================================================ FILE: apps/kirjs/src/app/modules/gomoku-print/x/x.component.css ================================================ :host { color: #c53a00; font-size: 18px; font-weight: bold; line-height: 14px; vertical-align: middle; } ================================================ FILE: apps/kirjs/src/app/modules/gomoku-print/x/x.component.html ================================================ × ================================================ FILE: apps/kirjs/src/app/modules/gomoku-print/x/x.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { XComponent } from './x.component'; describe('XComponent', () => { let component: XComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [XComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(XComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/gomoku-print/x/x.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ // tslint:disable-next-line selector: 'x', templateUrl: './x.component.html', styleUrls: ['./x.component.css'] }) export class XComponent {} ================================================ FILE: apps/kirjs/src/app/modules/home/home.component.css ================================================ .wrapper { font-family: 'Helvetica Neue', sans-serif; font-weight: 300; border-top: 10vw black solid; border-bottom: 10vw black solid; box-sizing: border-box; } kirjs-polaroid { display: block; margin-left: 30px; } .picture.fb { background: url(pics/fb.png); background-size: cover; } .picture.lastfm { background: url(pics/lastfm.png); background-size: cover; } .picture.binary { background: url(pics/binary.png); background-size: cover; } .picture.kirjs { background: url(pics/kirjs.jpg); background-size: cover; } a { text-decoration: none; color: #444; } ================================================ FILE: apps/kirjs/src/app/modules/home/home.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/home/home.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { HomeComponent } from './home.component'; describe('HomeComponent', () => { let component: HomeComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [HomeComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(HomeComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/home/home.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-home', templateUrl: './home.component.html', styleUrls: ['./home.component.css'] }) export class HomeComponent implements OnInit { constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/home/home.module.spec.ts ================================================ import { HomeModule } from './home.module'; describe('HomeModule', () => { let homeModule: HomeModule; beforeEach(() => { homeModule = new HomeModule(); }); it('should create an instance', () => { expect(homeModule).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/home/home.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { HomeComponent } from './home.component'; import { RouterModule } from '@angular/router'; import { PolaroidComponent } from './polaroid/polaroid.component'; @NgModule({ imports: [ CommonModule, RouterModule.forChild([ { path: '', component: HomeComponent } ]) ], declarations: [HomeComponent, PolaroidComponent], entryComponents: [HomeComponent] }) export class HomeModule {} ================================================ FILE: apps/kirjs/src/app/modules/home/polaroid/polaroid.component.css ================================================ ::ng-deep .picture { width: 200px; height: 200px; background-size: cover; box-shadow: 0 0 10px #444 inset; border-radius: 50%; } .frame-outer { width: 240px; height: 400px; background: #fff; padding: 20px; box-sizing: border-box; } .text { margin-top: 20px; background: white; padding: 20px; font-size: 1.3vw; } ================================================ FILE: apps/kirjs/src/app/modules/home/polaroid/polaroid.component.html ================================================
================================================ FILE: apps/kirjs/src/app/modules/home/polaroid/polaroid.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { PolaroidComponent } from './polaroid.component'; describe('PolaroidComponent', () => { let component: PolaroidComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [PolaroidComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(PolaroidComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/home/polaroid/polaroid.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-polaroid', templateUrl: './polaroid.component.html', styleUrls: ['./polaroid.component.css'] }) export class PolaroidComponent implements OnInit { constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/msk/msk.component.css ================================================ .pic { width: 100%; height: 100%; background-repeat: no-repeat; background-size: contain; } .awesome { background-image: url(./pics/awesome.png); } .default { background-image: url(./pics/default.png); } .ivy { background-image: url(./pics/ivy.png); } .differential-loading { background-image: url(./pics/differential-loading.png); } ================================================ FILE: apps/kirjs/src/app/modules/msk/msk.component.html ================================================

What's new in the Angular World

by @kirjs

ng make-this-awesome

ng deploy

Now you can deploy your angular-cli app to multiple cloud providers using ng deploy command

ng add @angular/fire@next
ng deploy
    

Now you can deploy

  • Firebase
  • Netlify
  • Azure
  • Github
  • And more...

Differential loading

✨ Juan's tip ✨

https://browserl.ist/

New default app

Angular ivy

cdk-experimental clipboard service + directive

================================================ FILE: apps/kirjs/src/app/modules/msk/msk.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { MskComponent } from './msk.component'; describe('MskComponent', () => { let component: MskComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [MskComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(MskComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/msk/msk.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-msk', templateUrl: './msk.component.html', styleUrls: ['./msk.component.css'] }) export class MskComponent implements OnInit { polls = [ { key: 'favorite', type: 'choice', question: 'Which framework do you use most at work?', options: [ 'Angular', 'AngularJS', 'React', 'Vue', 'Svelte', 'jQuery', 'Something else' ] }, { key: 'skill', type: 'choice', question: 'How well do you know angular?', options: [ 'Not at all', 'Somewhat', 'I can use it', 'Good', 'Really good', "I'm Minko Fluin" ] }, { key: 'build-dev', type: 'choice', question: 'how long does it take to rebuild your app in dev mode (and see the result via local dev server) - the total turnaround time', options: [ '< 1 second', '1 - 5 seconds', '5 - 10 seconds', '10 - 30 seconds', '30 - 60 seconds', '1-10 minutes', 'More than 10 minutes' ] }, { key: 'build-prod', type: 'choice', question: 'how long does it take to create a production build of your app', options: [ '< 1 second', '1 - 5 seconds', '5 - 10 seconds', '10 - 30 seconds', '30 - 60 seconds', '1-10 minutes', 'More than 10 minutes' ] }, { key: 'cli', type: 'choice', question: 'Which feature is NOT in CLI 8.3.0-next.2 ', answer: 'New command ng make-this-awesome', options: [ 'Redesigned default app', 'New command ng make-this-awesome', 'Faster builds with enabled differential loading', 'New command ng deploy' ] }, { key: 'tomorrow', type: 'choice', question: 'What is being released today?', answer: 'CLI 9.0.0-next.0 with Ivy by default', options: [ 'CLI 9.0.0-next.0 with Ivy by default', 'RxJS 8', 'React 16.12', 'Angular XS' ] }, { key: 'material', type: 'choice', question: 'Which feature was added to Angular CDK library 8.1.3 "gelatin-key" (2019-08-14)?', answer: '', options: [ 'Windows 95 theme support', 'Drag and drop', 'New material-fox component', 'New Clipboard service + directive' ] } ]; constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/msk/msk.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { AngularFireDatabaseModule } from '@angular/fire/database'; import { RouterModule } from '@angular/router'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { SyncDataService } from '@codelab/utils/src/lib/sync/services/sync-data.service'; import { SyncSessionService } from '@codelab/utils/src/lib/sync/services/sync-session.service'; import { SyncDbService } from '@codelab/utils/src/lib/sync/services/sync-db.service'; import { SyncPollService } from '@codelab/utils/src/lib/sync/components/poll/common/sync-poll.service'; import { SyncRegistrationService } from '@codelab/utils/src/lib/sync/components/registration/sync-registration.service'; import { SyncButtonModule } from '@codelab/utils/src/lib/sync/sync-button/sync-button.module'; import { QuestionsModule } from '@codelab/utils/src/lib/sync/components/questions/questions.module'; import { SyncModule } from '@codelab/utils/src/lib/sync/sync.module'; import { AngularFireAuthModule } from '@angular/fire/auth'; import { SyncDirectivesModule } from '@codelab/utils/src/lib/sync/directives/sync-directives.module'; import { SyncRegistrationModule } from '@codelab/utils/src/lib/sync/components/registration/sync-registration.module'; import { SyncPollModule } from '@codelab/utils/src/lib/sync/components/poll/sync-poll.module'; import { MskComponent } from './msk.component'; const routes = RouterModule.forChild(SlidesRoutes.get(MskComponent)); @NgModule({ providers: [ SyncDataService, SyncSessionService, SyncDbService, SyncPollService, SyncRegistrationService ], declarations: [MskComponent], imports: [ CommonModule, SlidesModule, routes, QuestionsModule, SyncModule, AngularFireAuthModule, AngularFireDatabaseModule, SyncButtonModule, SyncDirectivesModule, SyncRegistrationModule, SyncPollModule ] }) export class MskModule {} ================================================ FILE: apps/kirjs/src/app/modules/music/music.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/music/music.component.html ================================================

music works!

================================================ FILE: apps/kirjs/src/app/modules/music/music.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { MusicComponent } from './music.component'; describe('MusicComponent', () => { let component: MusicComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [MusicComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(MusicComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/music/music.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-music', templateUrl: './music.component.html', styleUrls: ['./music.component.css'] }) export class MusicComponent implements OnInit { constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/music/music.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MusicComponent } from './music.component'; @NgModule({ imports: [CommonModule], declarations: [MusicComponent] }) export class MusicModule {} ================================================ FILE: apps/kirjs/src/app/modules/qna/qna.component.css ================================================ :host ::ng-deep { display: block; padding: 40px; position: relative; } ================================================ FILE: apps/kirjs/src/app/modules/qna/qna.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/qna/qna.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { QnaComponent } from './qna.component'; describe('QnaComponent', () => { let component: QnaComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [QnaComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(QnaComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/qna/qna.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-qna', templateUrl: './qna.component.html', styleUrls: ['./qna.component.css'] }) export class QnaComponent implements OnInit { constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/qna/qna.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { CommonModule } from '@angular/common'; import { AngularFireAuthModule } from '@angular/fire/auth'; import { AngularFireDatabaseModule } from '@angular/fire/database'; import { SlidesModule } from '@ng360/slides'; import { QuestionsModule } from '@codelab/utils/src/lib/sync/components/questions/questions.module'; import { SyncModule } from '@codelab/utils/src/lib/sync/sync.module'; import { SyncDataService } from '@codelab/utils/src/lib/sync/services/sync-data.service'; import { SyncRegistrationService } from '@codelab/utils/src/lib/sync/components/registration/sync-registration.service'; import { SyncSessionService } from '@codelab/utils/src/lib/sync/services/sync-session.service'; import { SyncDbService } from '@codelab/utils/src/lib/sync/services/sync-db.service'; import { SyncPollService } from '@codelab/utils/src/lib/sync/components/poll/common/sync-poll.service'; import { SyncButtonModule } from '@codelab/utils/src/lib/sync/sync-button/sync-button.module'; import { SyncDirectivesModule } from '@codelab/utils/src/lib/sync/directives/sync-directives.module'; import { SyncRegistrationModule } from '@codelab/utils/src/lib/sync/components/registration/sync-registration.module'; import { QnaComponent } from './qna.component'; const routes = RouterModule.forChild([{ path: '', component: QnaComponent }]); @NgModule({ declarations: [QnaComponent], providers: [ SyncDataService, SyncSessionService, SyncDbService, SyncPollService, SyncRegistrationService ], imports: [ CommonModule, SlidesModule, routes, QuestionsModule, SyncModule, AngularFireAuthModule, AngularFireDatabaseModule, SyncButtonModule, SyncDirectivesModule, SyncRegistrationModule ] }) export class QnaModule {} ================================================ FILE: apps/kirjs/src/app/modules/regex/live/index.ts ================================================ export * from './live.module'; ================================================ FILE: apps/kirjs/src/app/modules/regex/live/live-mock/index.ts ================================================ export * from './live-mock.module'; ================================================ FILE: apps/kirjs/src/app/modules/regex/live/live-mock/live-mock.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/regex/live/live-mock/live-mock.component.html ================================================
{{ data | json }}
================================================ FILE: apps/kirjs/src/app/modules/regex/live/live-mock/live-mock.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { LiveMockComponent } from './live-mock.component'; describe('LiveMockComponent', () => { let component: LiveMockComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [LiveMockComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(LiveMockComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/regex/live/live-mock/live-mock.component.ts ================================================ import { Component, OnDestroy, OnInit } from '@angular/core'; import { LiveService, LiveInfo } from '../live.service'; import { Subject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; import { FormBuilder, FormGroup } from '@angular/forms'; @Component({ selector: 'kirjs-live-mock-component', templateUrl: './live-mock.component.html', styleUrls: ['./live-mock.component.css'] }) export class LiveMockComponent implements OnInit, OnDestroy { data: LiveInfo; form: FormGroup = this.fb.group({ user: this.fb.control(''), status: this.fb.control('') }); private onDestroy: Subject = new Subject(); constructor(private service: LiveService, private fb: FormBuilder) {} ngOnInit() { this.form.valueChanges.subscribe(data => { this.service.storeLiveInfo(data); }); this.service.liveInfo.pipe(takeUntil(this.onDestroy)).subscribe(data => { this.data = data; this.form.patchValue(data, { emitEvent: false }); }); } ngOnDestroy(): void { this.onDestroy.next(null); this.onDestroy.complete(); } } ================================================ FILE: apps/kirjs/src/app/modules/regex/live/live-mock/live-mock.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { LiveMockComponent } from './live-mock.component'; @NgModule({ imports: [CommonModule, ReactiveFormsModule], declarations: [LiveMockComponent], exports: [LiveMockComponent] }) export class LiveMockModule {} ================================================ FILE: apps/kirjs/src/app/modules/regex/live/live.module.ts ================================================ import { NgModule } from '@angular/core'; import { LiveMockModule } from './live-mock'; import { PollModule } from './poll'; @NgModule({ exports: [LiveMockModule, PollModule] }) export class LiveModule {} ================================================ FILE: apps/kirjs/src/app/modules/regex/live/live.service.spec.ts ================================================ import { TestBed } from '@angular/core/testing'; import { LiveService } from './live.service'; describe('LiveService', () => { beforeEach(() => TestBed.configureTestingModule({})); it('should be created', () => { const service: LiveService = TestBed.inject(LiveService); expect(service).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/regex/live/live.service.ts ================================================ import { Injectable, OnDestroy } from '@angular/core'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; export interface LiveInfo { // Login service user: string; // Global sessionId: string; // status: 'presenter' | 'viewer'; // in the future 'admin'; // e.g. regex presentationId: string; // slide id slide: string; } type AllData = Record; @Injectable({ providedIn: 'root' }) export class LiveService implements OnDestroy { private liveInfoSubject: BehaviorSubject = new BehaviorSubject< LiveInfo >({ user: 'code', sessionId: 'test', status: 'presenter', // 'viewer' presentationId: 'regex', slide: 'config' } as LiveInfo); liveInfo: Observable = this.liveInfoSubject.asObservable(); private allDataSubject: BehaviorSubject> = new BehaviorSubject< AllData >({} as AllData); allData = this.allDataSubject.asObservable(); myData = combineLatest([this.allData, this.liveInfo]).pipe( map(([allData, { user }]) => { return allData[user]; }) ); constructor() {} storeLiveInfo(data: LiveInfo): void { const liveInfo = this.liveInfoSubject.getValue(); this.liveInfoSubject.next({ ...liveInfo, ...data }); } // Viewer storeMyData(data: T): void { // firebase.store const liveInfo = this.liveInfoSubject.getValue(); const allData = this.allDataSubject.getValue(); const value = { ...allData, [liveInfo.user]: data }; this.allDataSubject.next(value); } ngOnDestroy() { if (this.allDataSubject) { this.allDataSubject.complete(); this.allDataSubject = null; } if (this.liveInfoSubject) { this.liveInfoSubject.complete(); this.liveInfoSubject = null; } } } ================================================ FILE: apps/kirjs/src/app/modules/regex/live/poll/index.ts ================================================ export * from './poll.module'; ================================================ FILE: apps/kirjs/src/app/modules/regex/live/poll/poll.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/regex/live/poll/poll.component.html ================================================ 2
    {{ service.allData | async | json }} 3
      Results are here!!!
      // results myDAta ================================================ FILE: apps/kirjs/src/app/modules/regex/live/poll/poll.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { PollComponent } from './poll.component'; describe('SyncPollComponent', () => { let component: PollComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [PollComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(PollComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/regex/live/poll/poll.component.ts ================================================ import { Component, Injectable, Input, OnInit } from '@angular/core'; import { LiveService } from '../live.service'; @Component({ selector: 'kirjs-poll', templateUrl: './poll.component.html', styleUrls: ['./poll.component.css'] }) export class PollComponent { @Input() question: string; constructor(readonly service: LiveService) {} } @Component({ selector: 'kirjs-poll-answer', template: `
    • `, styleUrls: ['./poll.component.css'] }) export class SlidesAnswerComponent implements OnInit { @Input() value: string; constructor(readonly service: LiveService) {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/regex/live/poll/poll.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { PollComponent, SlidesAnswerComponent } from './poll.component'; @NgModule({ declarations: [PollComponent, SlidesAnswerComponent], exports: [PollComponent, SlidesAnswerComponent], imports: [CommonModule] }) export class PollModule {} ================================================ FILE: apps/kirjs/src/app/modules/regex/regex.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/regex/regex.component.html ================================================

      Hello

      • a
      • b
      Yes No
      ================================================ FILE: apps/kirjs/src/app/modules/regex/regex.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RegexComponent } from './regex.component'; describe('RegexComponent', () => { let component: RegexComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [RegexComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(RegexComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/regex/regex.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-regex', templateUrl: './regex.component.html', styleUrls: ['./regex.component.css'] }) export class RegexComponent implements OnInit { constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/regex/regex.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { RegexComponent } from './regex.component'; import { LiveModule } from './live'; const routes = RouterModule.forChild(SlidesRoutes.get(RegexComponent)); @NgModule({ declarations: [RegexComponent], imports: [routes, CommonModule, SlidesModule, LiveModule] }) export class RegexModule {} ================================================ FILE: apps/kirjs/src/app/modules/stack/simple-stack/simple-stack.component.css ================================================ :host { white-space: nowrap; border: 3px #666 dotted; border-top-left-radius: 5px; border-bottom-left-radius: 5px; border-right: 0; padding-left: 10px; padding-right: 40px; height: 46px; position: relative; } :host:after { content: ''; position: absolute; left: 0; top: 0; height: 46px; right: 80px; background: white; opacity: 0.7; } ================================================ FILE: apps/kirjs/src/app/modules/stack/simple-stack/simple-stack.component.html ================================================ {{ value }} ================================================ FILE: apps/kirjs/src/app/modules/stack/simple-stack/simple-stack.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleStackComponent } from './simple-stack.component'; describe('SimpleStackComponent', () => { let component: SimpleStackComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SimpleStackComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SimpleStackComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/stack/simple-stack/simple-stack.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'slides-simple-stack', templateUrl: './simple-stack.component.html', styleUrls: ['./simple-stack.component.css'] }) export class SimpleStackComponent implements OnInit { @Input() value: string; constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/stack/stack-game/stack-function/stack-function.component.css ================================================ :host { white-space: nowrap; } .grid { display: grid; grid-template-columns: 1fr 80px 1fr; } .arrow { text-align: center; } :host.disabled { opacity: 0.4; background: #eee; } .inputs { justify-self: end; white-space: nowrap; } ================================================ FILE: apps/kirjs/src/app/modules/stack/stack-game/stack-function/stack-function.component.html ================================================
      {{ func.name }}
      ({{ func.inputs }}) => {{ func.outputs }}
      ================================================ FILE: apps/kirjs/src/app/modules/stack/stack-game/stack-function/stack-function.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { StackFunctionComponent } from './stack-function.component'; describe('StackFunctionComponent', () => { let component: StackFunctionComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [StackFunctionComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(StackFunctionComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/stack/stack-game/stack-function/stack-function.component.ts ================================================ import { Component, HostBinding, Input, OnInit } from '@angular/core'; import { StackFunction } from '../stack-game.component'; @Component({ selector: 'slides-stack-function', templateUrl: './stack-function.component.html', styleUrls: ['./stack-function.component.css'] }) export class StackFunctionComponent implements OnInit { @Input() func: StackFunction; @HostBinding('class.disabled') @Input() disabled = false; constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/stack/stack-game/stack-function-button/stack-function-button.component.css ================================================ button { font-size: inherit; cursor: pointer; background: #ffffff; border: 1px #ddd solid; border-radius: 10px; padding: 10px; margin: 0 10px; } button:hover { background: #eee; } ================================================ FILE: apps/kirjs/src/app/modules/stack/stack-game/stack-function-button/stack-function-button.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/stack/stack-game/stack-function-button/stack-function-button.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { StackFunctionButtonComponent } from './stack-function-button.component'; describe('StackFunctionButtonComponent', () => { let component: StackFunctionButtonComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [StackFunctionButtonComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(StackFunctionButtonComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/stack/stack-game/stack-function-button/stack-function-button.component.ts ================================================ import { Component, HostBinding, Input, OnInit } from '@angular/core'; import { StackFunction } from '../stack-game.component'; @Component({ // tslint:disable-next-line:component-selector selector: 'slides-stack-function-button', templateUrl: './stack-function-button.component.html', styleUrls: ['./stack-function-button.component.css'] }) export class StackFunctionButtonComponent { @Input() func: StackFunction; @Input() disabled = false; } ================================================ FILE: apps/kirjs/src/app/modules/stack/stack-game/stack-game.component.css ================================================ .buttons { margin: 20px 0 40px; text-align: center; } .complete { text-align: center; } .stack { margin-left: 30px; } .item { white-space: nowrap; margin-bottom: 10px; height: 55px; display: block; line-height: 55px; } .initial { color: #666; text-align: right; } .function-usage { text-align: right; } .history { display: grid; grid-template-columns: 1fr 1fr; } .next { background: #dddddd; text-align: center; border-radius: 5px; } .remove-button { margin-right: 10px; } ================================================ FILE: apps/kirjs/src/app/modules/stack/stack-game/stack-game.component.html ================================================
      ✨💖 Success! ✨💖
      Initial stack:

      Expected:
      ================================================ FILE: apps/kirjs/src/app/modules/stack/stack-game/stack-game.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { StackGameComponent } from './stack-game.component'; describe('StackGameComponent', () => { let component: StackGameComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [StackGameComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(StackGameComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/stack/stack-game/stack-game.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; export interface StackFunction { inputs: string; outputs: string; name?: string; } export interface Level { functions: StackFunction[]; inputs: string; outputs: string; } const ANY_CHAR = '*'; @Component({ selector: 'slides-stack-game', templateUrl: './stack-game.component.html', styleUrls: ['./stack-game.component.css'] }) export class StackGameComponent implements OnInit { isComplete = false; @Input() level: Level = { functions: [ { inputs: '', outputs: '🍏', name: 'push 🍏' }, { inputs: '🍏🍏', outputs: '🍋' }, { inputs: '🍋🍋', outputs: '🍒' }, { inputs: '*', outputs: '', name: 'pop' } ], inputs: '🍏', outputs: '🍒' }; functions = []; stack = ''; history: string[]; canAddFunction(stack: string, func) { return stack.match(new RegExp(func.inputs.replace(ANY_CHAR, '.') + '$')); } calcStack() { let stack = this.level.inputs.replace(ANY_CHAR, '🍏'); const history = []; for (const func of this.functions) { stack = stack.slice( 0, stack.length - func.inputs.replace(ANY_CHAR, '🍏').length ) + func.outputs; history.push(stack); } this.history = history; this.stack = stack; if (this.stack === this.level.outputs) { this.isComplete = true; } } addFunction(func: StackFunction) { this.functions.push(func); this.calcStack(); } removeFunction() { this.functions.pop(); this.calcStack(); } ngOnInit() { this.stack = this.level.inputs; } } ================================================ FILE: apps/kirjs/src/app/modules/stack/stack-routing.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { SlidesRoutes } from '@ng360/slides'; import { StackComponent } from './stack.component'; import { StackModule } from './stack.module'; const routes = RouterModule.forChild(SlidesRoutes.get(StackComponent)); @NgModule({ imports: [StackModule, routes] }) export class StackRoutingModule {} ================================================ FILE: apps/kirjs/src/app/modules/stack/stack-test/stack-test.component.html ================================================

      Test time! those two companies have "stack" in their name, but only one of them actually has stack as their logo. which one?

      ================================================ FILE: apps/kirjs/src/app/modules/stack/stack-test/stack-test.component.scss ================================================ .wrapper { display: flex; .company { text-align: center; flex: 1; .name { font-size: 40px; font-weight: 300; margin: 20px 0; } .logo { height: 150px; > div { background-size: cover; margin: 20px auto; } .sb { width: 80px; height: 120px; background-image: url('./assets/sb-icon.svg'); } .so { width: 150px; height: 150px; background-image: url('./assets/so-icon.svg'); } } } .gap { flex: 0.2; } } ================================================ FILE: apps/kirjs/src/app/modules/stack/stack-test/stack-test.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { StackTestComponent } from './stack-test.component'; describe('StackTestComponent', () => { let component: StackTestComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [StackTestComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(StackTestComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/stack/stack-test/stack-test.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ // tslint:disable-next-line:component-selector selector: 'slides-stack-test', templateUrl: './stack-test.component.html', styleUrls: ['./stack-test.component.scss'] }) export class StackTestComponent implements OnInit { constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/stack/stack.component.css ================================================ :host ::ng-deep .slide.slide > div { display: flex; align-items: center; justify-content: center; font-size: 40px; padding: 0 20vw; } ::ng-deep { font-weight: 300; } .button-wrapper { margin: 20px 0; } ================================================ FILE: apps/kirjs/src/app/modules/stack/stack.component.html ================================================

      Stack is a data structure, serving as a collection. Here's a stack of fruit:

      All operations are possible only with the last element of the stack.

      First operation is called pop, it removes the last element.

      The other operation is push. It adds an element on top of the stack.

      Some implementations also have a peek operation. It tells you what top element on the stack is. You can try it below:

      Congrats! It is a 🍋

      Let's practice!

      Use the commands you know already to get a stack of:

      Congrats! Now you are a stack expert!

      But why are stacks important? And how can I use them in a language like asm

      Let's learn stack machine.

      This is one of the fundamental principles used in JVM Java Byte Code, WebAssembly byte code

      Stack machine offers various intructions that can pop and then push multiple element on the stack

      For example (🌲🍏)=>🍍 takes two elements from the stack (Pine and Apple) and pushes a 🍍 back

      Now use the instructions below to prepare a lemonade drink:

      Produce a dog

      ================================================ FILE: apps/kirjs/src/app/modules/stack/stack.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { StackComponent } from './stack.component'; describe('StackComponent', () => { let component: StackComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [StackComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(StackComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/stack/stack.component.ts ================================================ import { Component, OnInit } from '@angular/core'; import { Level } from './stack-game/stack-game.component'; @Component({ selector: 'kirjs-stack', templateUrl: './stack.component.html', styleUrls: ['./stack.component.css'] }) export class StackComponent implements OnInit { itIsALemon = false; levels: Record = { push: { functions: [ { inputs: '', outputs: '🍏', name: 'push 🍏' }, { inputs: '', outputs: '🍋', name: 'push 🍋' } ], inputs: '', outputs: '🍏🍋🍏' }, pop: { functions: [ { inputs: '*', outputs: '', name: 'pop' } ], inputs: '🍏🍏🍏🍏🍏', outputs: '🍏' }, together: { functions: [ { inputs: '*', outputs: '', name: 'pop' }, { inputs: '', outputs: '🍓', name: 'push 🍓' }, { inputs: '', outputs: '🍋', name: 'push 🍋' } ], inputs: '🍏🍏', outputs: '🍓🍋' }, lemonade: { functions: [ { inputs: '', outputs: '💦' }, { inputs: '', outputs: '🍋' }, { inputs: '', outputs: '🍒' }, { inputs: '🍒💦🍋', outputs: '🍹' } ], inputs: '', outputs: '🍹' }, level1: { functions: [ { inputs: '', outputs: '🍏🍏' }, { inputs: '', outputs: '🍋' }, { inputs: '🍋🍋', outputs: '🍒' }, { inputs: '*', outputs: '', name: 'pop' } ], inputs: '🍏', outputs: '🍒' }, level2: { functions: [ { inputs: '', outputs: '🍏', name: 'push 🍏' }, { inputs: '🍏🍏', outputs: '🍋' }, { inputs: '🍋🍋', outputs: '🍒' }, { inputs: '*', outputs: '', name: 'pop' } ], inputs: '🍏', outputs: '🍒' } }; constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/stack/stack.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SlidesModule } from '@ng360/slides'; import { StackComponent } from './stack.component'; import { StackGameComponent } from './stack-game/stack-game.component'; import { SimpleStackComponent } from './simple-stack/simple-stack.component'; import { StackTestComponent } from './stack-test/stack-test.component'; import { StackFunctionComponent } from './stack-game/stack-function/stack-function.component'; import { StackFunctionButtonComponent } from './stack-game/stack-function-button/stack-function-button.component'; import { MatButtonModule } from '@angular/material/button'; @NgModule({ declarations: [ StackComponent, StackGameComponent, SimpleStackComponent, StackTestComponent, StackFunctionComponent, StackFunctionButtonComponent ], exports: [ StackComponent, StackGameComponent, SimpleStackComponent, StackTestComponent, StackFunctionComponent, StackFunctionButtonComponent ], imports: [CommonModule, SlidesModule, MatButtonModule] }) export class StackModule {} ================================================ FILE: apps/kirjs/src/app/modules/streaming/common.ts ================================================ import { InjectionToken } from '@angular/core'; export const FLAME_LINK = new InjectionToken('FLAME_LINK'); interface Guest { name: string; twitter: string; avatar: string; } export interface StreamSession { name: string; guests: Guest[]; } ================================================ FILE: apps/kirjs/src/app/modules/streaming/overlay/overlay.component.html ================================================

      {{ config.header }}

      {{ config.subHeader }}

      {{ guest.name }}
      @{{ guest.twitter }}

      Chat

      @kirjs

      @{{ guest.twitter }}
      ================================================ FILE: apps/kirjs/src/app/modules/streaming/overlay/overlay.component.scss ================================================ :host { border: 1px #ddd solid; width: 1650px; height: 1050px; display: block; font-family: 'Helvetica Neue', sans-serif; position: relative; } .circle { position: absolute; right: 20px; bottom: 20px; height: 240px; width: 240px; border-radius: 50%; } :host ::ng-deep { h2 { margin-bottom: 0; font-family: 'Helvetica Neue', sans-serif; font-weight: 300; } p { margin-top: 8px; } } ================================================ FILE: apps/kirjs/src/app/modules/streaming/overlay/overlay.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { OverlayComponent } from './overlay.component'; describe('OverlayComponent', () => { let component: OverlayComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [OverlayComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(OverlayComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/streaming/overlay/overlay.component.ts ================================================ import { Component, Inject, OnInit } from '@angular/core'; import { FLAME_LINK, StreamSession } from '../common'; import { interval, Observable } from 'rxjs'; import { map, startWith, switchMap } from 'rxjs/operators'; @Component({ selector: 'slides-overlay', templateUrl: './overlay.component.html', styleUrls: ['./overlay.component.scss'] }) export class OverlayComponent implements OnInit { readonly layout = 'horizontal'; data$: Observable = interval(5000).pipe( startWith(0), switchMap(() => { return this.flameLink.content.get({ schemaKey: 'currentSession', populate: true }); }), map((a: any) => a.session) ); constructor(@Inject(FLAME_LINK) private flameLink: any) {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/streaming/streaming.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { OverlayComponent } from './overlay/overlay.component'; import firebase from 'firebase/app'; // Add additional services that you want to use import 'firebase/auth'; import 'firebase/firestore'; import flamelink from 'flamelink/app'; // Add additional modules that you want to use import 'flamelink/content'; import 'flamelink/storage'; import { FLAME_LINK } from './common'; import { MarkdownModule } from 'ngx-markdown'; const firebaseConfig = { apiKey: 'AIzaSyC_Zyq9Ve1SrbenuN0iDlDd4hQvTIlruP8', authDomain: 'kirjs-c884f.firebaseapp.com', databaseURL: 'https://kirjs-c884f.firebaseio.com', projectId: 'kirjs-c884f', storageBucket: 'kirjs-c884f.appspot.com', messagingSenderId: '651206687896', appId: '1:651206687896:web:3df45fa9e636bb5882a4ed', measurementId: 'G-3B7YEC4QG7' }; const firebaseApp = firebase.initializeApp(firebaseConfig); const app = flamelink({ firebaseApp, env: 'production', // optional, defaults to `production` locale: 'en-US', // optional, defaults to `en-US` dbType: 'cf' // optional, defaults to `rtdb` - can be 'rtdb' or 'cf' (Realtime DB vs Cloud Firestore) }); @NgModule({ declarations: [], providers: [ { provide: FLAME_LINK, useValue: app } ], imports: [ RouterModule.forChild([ { path: '', component: OverlayComponent } ]), CommonModule, MarkdownModule ] }) export class StreamingModule {} ================================================ FILE: apps/kirjs/src/app/modules/svg/samples/app.component.ts ================================================ import { Component } from '@angular/core'; // Just an empty component to make everything compile @Component({ selector: 'kirjs-app', template: '' }) export class AppComponent {} ================================================ FILE: apps/kirjs/src/app/modules/svg/samples/app.module.ts ================================================ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; @NgModule({ imports: [BrowserModule], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/kirjs/src/app/modules/svg/samples/attr/app.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'kirjs-app', template: ` ` }) export class AppComponent { y = 200; constructor() { window.setInterval(() => { this.y = Math.random() * 300; }, 200); } } ================================================ FILE: apps/kirjs/src/app/modules/svg/samples/bs.module.ts ================================================ import { AppComponent as A1 } from './sub.component'; import { AppComponent as A2 } from './attr/app.component'; import { AppComponent as A3 } from './chart/app.component'; import { AppComponent as A4 } from './svg/app.component'; import { AppComponent as A5 } from './chart4/app.component.solved'; import { AppComponent as A6 } from './chart2/app.component.solved'; import { Component, Input, NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; @Component({ selector: 'kirjs-ticks', template: '' }) export class FakeTicksComponent { @Input() data: any; } @NgModule({ imports: [CommonModule], declarations: [A1, A2, A3, A4, A5, A6, FakeTicksComponent] }) export class AppModule {} ================================================ FILE: apps/kirjs/src/app/modules/svg/samples/chart/app.component.ts ================================================ import { Component } from '@angular/core'; function generateData() { return Array.from(new Array(10)).map(index => Math.round(Math.random() * 300) ); } @Component({ selector: 'kirjs-app', template: ` {{ item }} ` }) export class AppComponent { barWidth = 30; padding = 10; barSpace = this.padding + this.barWidth; data = generateData(); constructor() { window.setInterval(() => { this.data = generateData(); }, 1000); } getIndex(a, b) { return a; } } ================================================ FILE: apps/kirjs/src/app/modules/svg/samples/chart2/app.component.solved.ts ================================================ import { Component } from '@angular/core'; function generateData() { return Array.from(new Array(10)).map(index => ({ index, value: Math.round(Math.random() * 300) })); } @Component({ selector: 'kirjs-app', template: ` {{ item.value }} ` }) export class AppComponent { barWidth = 30; padding = 10; barSpace = this.padding + this.barWidth; data = generateData(); constructor() { window.setInterval(() => { this.data = generateData(); }, 1000); } getIndex(a, b) { return a; } } ================================================ FILE: apps/kirjs/src/app/modules/svg/samples/chart2/app.component.ts ================================================ import { Component } from '@angular/core'; function generateData() { return Array.from(new Array(10)).map(index => ({ index, value: Math.round(Math.random() * 300) })); } @Component({ selector: 'kirjs-app', template: ` {{ item.value }} ` }) export class AppComponent { barWidth = 30; padding = 10; barSpace = this.padding + this.barWidth; data = generateData(); constructor() { window.setInterval(() => { this.data = generateData(); }, 1000); } getIndex(a, b) { return a; } } ================================================ FILE: apps/kirjs/src/app/modules/svg/samples/chart2/app.module.ts ================================================ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { TicksComponent } from './ticks.component'; @NgModule({ imports: [BrowserModule], declarations: [AppComponent, TicksComponent], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/kirjs/src/app/modules/svg/samples/chart2/ticks.component.ts ================================================ import { Component, Input } from '@angular/core'; @Component({ selector: 'kirjs-ticks', template: ` {{ i }} ` }) export class TicksComponent { @Input() data; @Input() barWidth = 30; padding = 10; barSpace = this.padding + this.barWidth; getIndex(i: number) { return i; } } ================================================ FILE: apps/kirjs/src/app/modules/svg/samples/chart3/app.component.ts ================================================ import { Component } from '@angular/core'; function generateData() { return Array.from(new Array(10)).map(index => ({ index, value: Math.round(Math.random() * 300) })); } @Component({ selector: 'kirjs-app', template: ` {{ item.value }} ` }) export class AppComponent { barWidth = 30; padding = 10; barSpace = this.padding + this.barWidth; data = generateData(); constructor() { window.setInterval(() => { this.data = generateData(); }, 1000); } getIndex(a, b) { return a; } } ================================================ FILE: apps/kirjs/src/app/modules/svg/samples/chart3/app.module.ts ================================================ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { TicksComponent } from './ticks.component'; @NgModule({ imports: [BrowserModule], declarations: [AppComponent, TicksComponent], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/kirjs/src/app/modules/svg/samples/chart3/ticks.component.ts ================================================ import { Component, Input } from '@angular/core'; @Component({ selector: 'kirjs-ticks', template: ` {{ i }} ` }) export class TicksComponent { @Input() data; @Input() barWidth = 30; padding = 10; barSpace = this.padding + this.barWidth; getIndex(i: number) { return i; } } ================================================ FILE: apps/kirjs/src/app/modules/svg/samples/chart4/app.component.solved.ts ================================================ import { Component } from '@angular/core'; function generateData() { return Array.from(new Array(10)).map(index => ({ index, value: Math.round(Math.random() * 300) })); } @Component({ selector: 'kirjs-app', template: ` {{ item.value }} ` }) export class AppComponent { barWidth = 30; padding = 10; barSpace = this.padding + this.barWidth; data = generateData(); constructor() { window.setInterval(() => { this.data = generateData(); }, 1000); } getIndex(a, b) { return a; } } ================================================ FILE: apps/kirjs/src/app/modules/svg/samples/chart4/app.component.ts ================================================ import { Component } from '@angular/core'; function generateData() { return Array.from(new Array(10)).map(index => ({ index, value: Math.round(Math.random() * 300) })); } @Component({ selector: 'kirjs-app', template: ` {{ item.value }} ` }) export class AppComponent { barWidth = 30; padding = 10; barSpace = this.padding + this.barWidth; data = generateData(); constructor() { window.setInterval(() => { this.data = generateData(); }, 1000); } getIndex(a, b) { return a; } } ================================================ FILE: apps/kirjs/src/app/modules/svg/samples/chart4/app.module.ts ================================================ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { TicksComponent } from './ticks.component'; @NgModule({ imports: [BrowserModule], declarations: [AppComponent, TicksComponent], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/kirjs/src/app/modules/svg/samples/chart4/ticks.component.ts ================================================ import { Component, Input } from '@angular/core'; @Component({ selector: 'kirjs-ticks', template: ` {{ i }} ` }) export class TicksComponent { @Input() data; @Input() barWidth = 30; padding = 10; barSpace = this.padding + this.barWidth; getIndex(i: number) { return i; } } ================================================ FILE: apps/kirjs/src/app/modules/svg/samples/index.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/svg/samples/style.css ================================================ svg, body, html { width: 100%; height: 100%; } text { font-family: sans-serif; text-anchor: middle; } rect, text, g { transition: 1s; } ================================================ FILE: apps/kirjs/src/app/modules/svg/samples/sub.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'kirjs-app', template: ` ` }) export class AppComponent { y = 200; constructor() { window.setInterval(() => { this.y = Math.random() * 300; }, 200); } } ================================================ FILE: apps/kirjs/src/app/modules/svg/samples/svg/app.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'kirjs-app', template: ` ` }) export class AppComponent { y = 200; constructor() { window.setInterval(() => { this.y = Math.random() * 300; }, 200); } } ================================================ FILE: apps/kirjs/src/app/modules/svg/svg-demo/svg-demo.component.css ================================================ :host { height: 100%; display: block; } :host ::ng-deep svg { width: 100%; height: 100%; } ================================================ FILE: apps/kirjs/src/app/modules/svg/svg-demo/svg-demo.component.html ================================================
      ================================================ FILE: apps/kirjs/src/app/modules/svg/svg-demo/svg-demo.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SvgDemoComponent } from './svg-demo.component'; describe('SvgDemoComponent', () => { let component: SvgDemoComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SvgDemoComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SvgDemoComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/svg/svg-demo/svg-demo.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-svg-demo', templateUrl: './svg-demo.component.html', styleUrls: ['./svg-demo.component.css'] }) export class SvgDemoComponent implements OnInit { code: string; @Input() fontSize; @Input('code') set codeInput(value) { this.code = '\n' + value + '\n'; } ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/svg/svg-playground/svg-playground.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/svg/svg-playground/svg-playground.component.html ================================================

      svg-playground works!

      ================================================ FILE: apps/kirjs/src/app/modules/svg/svg-playground/svg-playground.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SvgPlaygroundComponent } from './svg-playground.component'; describe('RaceComponent', () => { let component: SvgPlaygroundComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SvgPlaygroundComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SvgPlaygroundComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/svg/svg-playground/svg-playground.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-svg-playground', templateUrl: './svg-playground.component.html', styleUrls: ['./svg-playground.component.css'] }) export class SvgPlaygroundComponent implements OnInit { @Input() code: string; constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/svg/svg-together/svg-together.component.css ================================================ :host { height: 100%; display: block; } :host ::ng-deep svg { width: 100%; height: 100%; } code-demo-editor { height: 500px; width: 100%; display: inline-block; border: 1px solid #000; } ::ng-deep svg { width: 500px; height: 500px; border: 1px solid #999999; min-height: 500px; } ================================================ FILE: apps/kirjs/src/app/modules/svg/svg-together/svg-together.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/svg/svg-together/svg-together.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SvgTogetherComponent } from './svg-together.component'; describe('SvgTogetherComponent', () => { let component: SvgTogetherComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SvgTogetherComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SvgTogetherComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/svg/svg-together/svg-together.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; import { AngularFireDatabase, AngularFireList } from '@angular/fire/database'; @Component({ selector: 'kirjs-svg-together', templateUrl: './svg-together.component.html', styleUrls: ['./svg-together.component.css'] }) export class SvgTogetherComponent implements OnInit { code = ''; angularFireList: AngularFireList; @Input() fontSize; allCode = 'TBD'; helpers = [ { label: '⭕️', code: ` ` }, { label: '⬭', code: ` ` }, { label: '▭', code: ` ` }, { label: '_', code: ` ` }, { label: 't', code: ` LOL❤ ` }, { label: '☆', code: ` ` }, { label: '⌇', code: ` ` } ]; constructor(af: AngularFireDatabase) { this.angularFireList = af.list('/svg-together'); this.angularFireList.snapshotChanges().subscribe(a => { this.allCode = '' + a.map(a => a.payload.val()).join('\n') + ''; }); this.reset(); } @Input('code') set codeInput(value) { this.code = '\n' + value + '\n'; } submit() { const code = this.code.replace(/\s+/, '').replace('', ''); this.angularFireList.push(code); this.reset(); } reset() { this.code = ` `; } ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/svg/svg-together-result/svg-together-result.component.css ================================================ :host { height: 100%; display: block; } :host ::ng-deep svg { width: 100%; height: 100%; } code-demo-editor { height: 500px; width: 100%; display: inline-block; border: 1px solid #000; } ::ng-deep svg { width: 500px; height: 500px; border: 1px solid #999999; min-height: 500px; } ================================================ FILE: apps/kirjs/src/app/modules/svg/svg-together-result/svg-together-result.component.html ================================================
      https://codelab.fun/svg/draw
      ================================================ FILE: apps/kirjs/src/app/modules/svg/svg-together-result/svg-together-result.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SvgTogetherResultComponent } from './svg-together-result.component'; describe('SvgTogetherResultComponent', () => { let component: SvgTogetherResultComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SvgTogetherResultComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SvgTogetherResultComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/svg/svg-together-result/svg-together-result.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; import { AngularFireDatabase, AngularFireList } from '@angular/fire/database'; @Component({ selector: 'kirjs-svg-together-result', templateUrl: './svg-together-result.component.html', styleUrls: ['./svg-together-result.component.css'] }) export class SvgTogetherResultComponent implements OnInit { code = ''; angularFireList: AngularFireList; @Input() fontSize; allCode = 'TBD'; constructor(af: AngularFireDatabase) { this.angularFireList = af.list('/svg-together'); this.angularFireList.snapshotChanges().subscribe(a => { this.allCode = '' + a.map(a => a.payload.val()).join('\n') + ''; }); } @Input('code') set codeInput(value) { this.code = '\n' + value + '\n'; } ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/svg/svg.component.css ================================================ :host ::ng-deep .monaco-editor .view-overlays .current-line { display: none; } :host ::ng-deep .decorationsOverviewRuler { display: none; } :host ::ng-deep .scrollbar.vertical { display: none; } .timer { position: fixed; left: 20px; bottom: 20px; } .bg { width: 100%; height: 100%; background-repeat: no-repeat; background-size: cover; } .intro { background-image: url(pics/1.jpg); } .kirjs { background-image: url(pics/ava.gif); } .svg-60 { background-image: url(pics/2.jpg); } .example { background-image: url(pics/3.jpg); } .the-end { background-image: url(pics/4.jpg); } .dog { background-image: url(pics/5.gif); } .bg.intro h2 { background: #000; color: white; margin-top: 50vh; padding: 50px; font-size: 10vh; opacity: 0.7; } .bg.kirjs h2 { background: #000; color: white; margin-top: 50vh; padding: 10px; font-size: 10vh; opacity: 0.7; } .bg h2 { background: #fff; color: black; margin-top: 50vh; padding: 50px; font-size: 10vh; opacity: 0.7; } .btn-bar { line-height: 3vw; } .btn-bar:hover .font-size { display: block; font-size: 4vw; } .btn-bar .font-size { display: none; } .twitter { color: #444; font-size: 3vw; margin: 2vw; } ================================================ FILE: apps/kirjs/src/app/modules/svg/svg.component.html ================================================

      Angular ❤️ SVG
      @kirjs

      • I read 📖📖📖
      • @kirjs

      What is SVG?

      • Scalable Vector Graphic Format
      • Can be edited with a text editor!
      • Every object is just an XML tag
      • Works with CSS
      • Plays well with angular templates!

      SVG in 5 minutes

      Some cool stuff

      Now you're an SVG expert!

      However SVG has a couple of edge cases

      Sign up for my 80 hour workshop "Wait, SVG, LOL, Really?"

      Let's check out some examples:

      SVG attributes binding

      LOL, but why?

      Let's create a simple barchart

      Custom SVG Elements

      But why?

      ================================================ FILE: apps/kirjs/src/app/modules/svg/svg.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { SlidesRoutes } from '@ng360/slides'; import { FeedbackModule } from '@codelab/feedback'; import { SvgComponent } from './svg.component'; import { FormsModule } from '@angular/forms'; import { SvgDemoComponent } from './svg-demo/svg-demo.component'; import { SvgPlaygroundComponent } from './svg-playground/svg-playground.component'; import { TimerComponent } from './timer/timer.component'; import { CommonModule } from '@angular/common'; import { SvgTogetherComponent } from './svg-together/svg-together.component'; import { MatButtonModule } from '@angular/material/button'; import { SharedPipeModule } from '@codelab/utils/src/lib/pipes/pipes.module'; import { SlidesModule } from '@ng360/slides'; import { CodeDemoModule } from '@codelab/code-demos'; import { SvgTogetherResultComponent } from './svg-together-result/svg-together-result.component'; import { NewProgressBarModule } from '../ast/new-progress-bar/new-progress-bar.module'; const routes = RouterModule.forChild(SlidesRoutes.get(SvgComponent)); @NgModule({ imports: [ routes, CommonModule, FeedbackModule, FormsModule, MatButtonModule, NewProgressBarModule, SharedPipeModule, SlidesModule, CodeDemoModule ], declarations: [ SvgComponent, SvgTogetherComponent, SvgTogetherResultComponent, SvgDemoComponent, SvgPlaygroundComponent, TimerComponent ], exports: [SvgComponent] }) export class SvgModule {} ================================================ FILE: apps/kirjs/src/app/modules/svg/timer/timer.component.css ================================================ .timer-running { color: #444; font-size: 3vw; } ================================================ FILE: apps/kirjs/src/app/modules/svg/timer/timer.component.html ================================================
      ▶️
      {{ time }}️
      ================================================ FILE: apps/kirjs/src/app/modules/svg/timer/timer.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TimerComponent } from './timer.component'; describe('TimerComponent', () => { let component: TimerComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [TimerComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TimerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/svg/timer/timer.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-timer', templateUrl: './timer.component.html', styleUrls: ['./timer.component.css'] }) export class TimerComponent implements OnInit { time = 0; reset() { this.time = 180; } constructor() { window.setInterval(() => { if (this.time > 0) { this.time--; } }, 1000); } ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/svg-race/finish/finish.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/svg-race/finish/finish.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/svg-race/finish/finish.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { FinishComponent } from './finish.component'; describe('FinishComponent', () => { let component: FinishComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [FinishComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(FinishComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/svg-race/finish/finish.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; @Component({ // tslint:disable-next-line:component-selector selector: '[kirjs-finish]', templateUrl: './finish.component.html', styleUrls: ['./finish.component.css'] }) export class FinishComponent implements OnInit { @Input() position = { x: 0, y: 0 }; constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/svg-race/little-car/little-car.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/svg-race/little-car/little-car.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/svg-race/little-car/little-car.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { LittleCarComponent } from './little-car.component'; describe('LittleCarComponent', () => { let component: LittleCarComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [LittleCarComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(LittleCarComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/svg-race/little-car/little-car.component.ts ================================================ import { Component, Input } from '@angular/core'; @Component({ // tslint:disable-next-line:component-selector selector: '[kirjs-little-car]', templateUrl: './little-car.component.html', styleUrls: ['./little-car.component.css'] }) export class LittleCarComponent { @Input() position = { x: 0, y: 0, angle: 0 }; lightColor = '#ffbc05'; darkColor = '#e38100'; @Input() set color(color: string) { this.lightColor = color; this.darkColor = '#444'; } } ================================================ FILE: apps/kirjs/src/app/modules/svg-race/player/player.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/svg-race/player/player.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/svg-race/player/player.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { PlayerComponent } from './player.component'; describe('PlayerComponent', () => { let component: PlayerComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [PlayerComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(PlayerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/svg-race/player/player.component.ts ================================================ import { AfterViewInit, Component, ElementRef, EventEmitter, Input, OnChanges, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core'; @Component({ // tslint:disable-next-line:component-selector selector: '[kirjs-player]', templateUrl: './player.component.html', styleUrls: ['./player.component.css'] }) export class PlayerComponent implements OnInit, AfterViewInit, OnChanges { @ViewChild('guess', { static: true }) guess: ElementRef; @Input() path; @Input() d = ''; @Input() color = '#ffffff'; @Output() scoreChanged = new EventEmitter(); points = []; carPosition = { x: 50, y: 50, angle: 0 }; pathLength = 0; startPosition = { x: 0, y: 0 }; trackWidth = 20; score = 0; ngOnChanges(changes: SimpleChanges) { if (changes.d) { this.calculateScore(); } } calculateScore() { requestAnimationFrame(() => { this.calculateCarPosition(); this.score = 0; const guess = this.guess.nativeElement; for (let i = 0; i < this.points.length; i++) { const point = this.points[i]; const { distance } = closestPoint(guess, point); if (distance < this.trackWidth) { this.score++; } else { this.pathLength = ((i - 3) / 100) * this.path.getTotalLength(); this.scoreChanged.emit(this.score); return; } } this.scoreChanged.emit(this.score); }); } ngAfterViewInit() { requestAnimationFrame(() => { const path = this.path; const l = path.getTotalLength(); for (let i = 0; i < l; i += l / 100) { this.points.push(path.getPointAtLength(i)); } this.startPosition = path.getPointAtLength(0); }); } calculateCarPosition() { const guess = this.guess.nativeElement; const totalLength = guess.getTotalLength(); this.carPosition = guess.getPointAtLength(totalLength); if (totalLength > 1) { const carPosition = guess.getPointAtLength(totalLength - 1); const dx = this.carPosition.x - carPosition.x; const dy = this.carPosition.y - carPosition.y; this.carPosition.angle = (Math.atan2(-dx, dy) * 180) / Math.PI; } } ngOnInit() {} } function closestPoint(pathNode, point) { const pathLength = pathNode.getTotalLength(); let precision = 8, best, bestLength, bestDistance = Infinity; // linear scan for coarse approximation for ( let scan, scanLength = 0, scanDistance; scanLength <= pathLength; scanLength += precision ) { if ( (scanDistance = distance2( (scan = pathNode.getPointAtLength(scanLength)) )) < bestDistance ) { (best = scan), (bestLength = scanLength), (bestDistance = scanDistance); } } // binary search for precise estimate precision /= 2; while (precision > 0.5) { let before, after, beforeLength, afterLength, beforeDistance, afterDistance; if ( (beforeLength = bestLength - precision) >= 0 && (beforeDistance = distance2( (before = pathNode.getPointAtLength(beforeLength)) )) < bestDistance ) { (best = before), (bestLength = beforeLength), (bestDistance = beforeDistance); } else if ( (afterLength = bestLength + precision) <= pathLength && (afterDistance = distance2( (after = pathNode.getPointAtLength(afterLength)) )) < bestDistance ) { (best = after), (bestLength = afterLength), (bestDistance = afterDistance); } else { precision /= 2; } } best = [best.x, best.y]; best.distance = Math.sqrt(bestDistance); return best; function distance2(p) { const dx = p.x - point.x, dy = p.y - point.y; return dx * dx + dy * dy; } } ================================================ FILE: apps/kirjs/src/app/modules/svg-race/race/race.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/svg-race/race/race.component.html ================================================

      Progress

      {{ car.name }}
      ================================================ FILE: apps/kirjs/src/app/modules/svg-race/race/race.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RaceComponent } from './race.component'; describe('RaceComponent', () => { let component: RaceComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [RaceComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(RaceComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/svg-race/race/race.component.ts ================================================ import { AfterViewInit, Component, ElementRef, Input, ViewChild } from '@angular/core'; @Component({ selector: 'kirjs-race', templateUrl: './race.component.html', styleUrls: ['./race.component.css'] }) export class RaceComponent implements AfterViewInit { @Input() code: string; d = 'M50 450 v -5'; cars = [ { name: 'cheburek', color: '#fd6b00', d: this.d, score: 0 }, { name: 'banana', color: '#fde200', d: 'M50 450 v 5 l -10 -300', score: 0 }, { name: 'ololo', color: '#bdfd00', d: 'M50 450 v -5 h 10v-300', score: 0 } ]; @Input() track: string; @ViewChild('path', { static: false }) path: ElementRef; name = 'cheburek'; scores = {}; trackWidth = 20; finishPosition = { x: 0, y: 0 }; updateCurrentPlayer() { const c = this.cars.find(car => car.name === this.name); if (c) { c.d = this.d; } } ngAfterViewInit() { const path = this.path.nativeElement; this.finishPosition = path.getPointAtLength(path.getTotalLength()); } setScore(name: string, score: number) { const c = this.cars.find(car => name === car.name); if (c) { c.score = score; } } } ================================================ FILE: apps/kirjs/src/app/modules/svg-race/svg-race.component.css ================================================ :host ::ng-deep .monaco-editor .view-overlays .current-line { display: none; } :host ::ng-deep .decorationsOverviewRuler { display: none; } :host ::ng-deep .scrollbar.vertical { display: none; } .timer { position: fixed; left: 20px; bottom: 20px; } .btn-bar { line-height: 3vw; } .btn-bar:hover .font-size { display: block; font-size: 4vw; } .btn-bar .font-size { display: none; } .twitter { color: #444; font-size: 3vw; margin: 2vw; } ================================================ FILE: apps/kirjs/src/app/modules/svg-race/svg-race.component.html ================================================

      Angular ❤️ SVG
      @kirjs

      Anybody will see this info!

      Only presenter sees this

      Only viewer sees this

      We can take user input

      {{ item.value }}

      You are a presenter

      You are a viewer

      What is SVG?

      • Scalable Vector Graphic Format
      • Every object is just an XML tag
      • Also works with CSS

      LOL

      Race

      Race

      ================================================ FILE: apps/kirjs/src/app/modules/svg-race/svg-race.component.ts ================================================ import { Component } from '@angular/core'; import { bootstrap, builder, exercise, stylesheet } from '../../../../../codelab/src/app/shared/helpers/helpers'; declare const require; @Component({ selector: 'kirjs-svg-race', templateUrl: './svg-race.component.html', styleUrls: ['./svg-race.component.css'] }) export class SvgRaceComponent { fontSize = 20; tracks = { advanced: `M50 450 Q -50 -60 300 50 Q 380 75 400 150 Q 450 350 300 150 Q 250 50 150 120 Q 50 250 150 320 Q 250 420 450 320 ` }; input = 'hi'; input2: any; constructor() {} } ================================================ FILE: apps/kirjs/src/app/modules/svg-race/svg-race.module.ts ================================================ import { NgModule, Pipe, PipeTransform } from '@angular/core'; import { RouterModule } from '@angular/router'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { DomSanitizer } from '@angular/platform-browser'; import { CommonModule } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { FeedbackModule } from '@codelab/feedback'; import { SyncModule } from '@codelab/utils/src/lib/sync/sync.module'; import { CodeDemoModule } from '@codelab/code-demos'; import { TimerComponent } from './timer/timer.component'; import { RaceComponent } from './race/race.component'; import { LittleCarComponent } from './little-car/little-car.component'; import { FinishComponent } from './finish/finish.component'; import { PlayerComponent } from './player/player.component'; import { SvgRaceComponent } from './svg-race.component'; import { ButtonsNavBarModule } from '../../../../../codelab/src/app/components/buttons-nav-bar/buttons-nav-bar.module'; const routes = RouterModule.forChild(SlidesRoutes.get(SvgRaceComponent)); @Pipe({ name: 'safeHtml' }) export class SafeHtml implements PipeTransform { constructor(private readonly sanitizer: DomSanitizer) {} transform(html) { return this.sanitizer.bypassSecurityTrustHtml(html); } } @NgModule({ imports: [ routes, CommonModule, SlidesModule, ButtonsNavBarModule, FeedbackModule, CodeDemoModule, FormsModule, MatButtonModule, SyncModule, ReactiveFormsModule ], declarations: [ RaceComponent, SafeHtml, SvgRaceComponent, TimerComponent, LittleCarComponent, FinishComponent, PlayerComponent ], exports: [SvgRaceComponent] }) export class SvgRaceModule {} ================================================ FILE: apps/kirjs/src/app/modules/svg-race/timer/timer.component.css ================================================ .timer-running { color: #444; font-size: 3vw; } ================================================ FILE: apps/kirjs/src/app/modules/svg-race/timer/timer.component.html ================================================
      ▶️
      {{ time }}️
      ================================================ FILE: apps/kirjs/src/app/modules/svg-race/timer/timer.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TimerComponent } from './timer.component'; describe('TimerComponent', () => { let component: TimerComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [TimerComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TimerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/svg-race/timer/timer.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-timer', templateUrl: './timer.component.html', styleUrls: ['./timer.component.css'] }) export class TimerComponent implements OnInit { time = 0; reset() { this.time = 180; } constructor() { window.setInterval(() => { if (this.time > 0) { this.time--; } }, 1000); } ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/sync/sync.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/sync/sync.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/sync/sync.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SyncComponent } from './sync.component'; describe('SyncComponent', () => { let component: SyncComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SyncComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SyncComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/sync/sync.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-sync', templateUrl: './sync.component.html', styleUrls: ['./sync.component.css'] }) export class SyncComponent implements OnInit { constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/sync/sync.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { RouterModule } from '@angular/router'; import { SyncModule as SyncLibModule } from '@codelab/utils/src/lib/sync/sync.module'; import { AngularFireAuthModule } from '@angular/fire/auth'; import { SyncSessionsComponent } from '@codelab/utils/src/lib/sync/components/sync-sessions/sync-sessions.component'; import { SyncComponent } from './sync.component'; const routes = RouterModule.forChild([ { path: 'sessions', component: SyncSessionsComponent }, ...SlidesRoutes.get(SyncComponent) ]); @NgModule({ declarations: [SyncComponent], imports: [ CommonModule, SlidesModule, routes, SyncLibModule, AngularFireAuthModule ] }) export class SyncModule {} ================================================ FILE: apps/kirjs/src/app/modules/test/test.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/test/test.component.html ================================================

      test works!

      ================================================ FILE: apps/kirjs/src/app/modules/test/test.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TestComponent } from './test.component'; describe('TestComponent', () => { let component: TestComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [TestComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TestComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/test/test.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'kirjs-test', templateUrl: './test.component.html', styleUrls: ['./test.component.css'] }) export class TestComponent implements OnInit { constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/test/test.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { SlidesRoutes } from '@ng360/slides'; import { TestComponent } from './test.component'; const routes = RouterModule.forChild(SlidesRoutes.get(TestComponent)); @NgModule({ imports: [CommonModule, routes], declarations: [TestComponent], entryComponents: [TestComponent] }) export class TestModule {} ================================================ FILE: apps/kirjs/src/app/modules/webassembly/ca/ca.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SingleCellComponent } from './single-cell/single-cell.component'; import { SingleGridComponent } from './single-grid/single-grid.component'; @NgModule({ declarations: [SingleCellComponent, SingleGridComponent], exports: [SingleCellComponent, SingleGridComponent], imports: [CommonModule] }) export class CaModule {} ================================================ FILE: apps/kirjs/src/app/modules/webassembly/ca/single-cell/single-cell.component.css ================================================ :host { display: flex; height: 100%; width: 100%; align-items: center; justify-content: space-around; } .cell { width: 10vh; height: 10vh; border: 4px #444 solid; animation: toColor 5s infinite linear, size 5s linear; } @keyframes size { 0% { width: 60vh; height: 60vh; } 100% { width: 10vh; height: 10vh; } } @keyframes toColor { 0% { background: #000; } 45% { background: #000; } 50% { background: #fff; } 95% { background: #fff; } 100% { background: #000; } } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/ca/single-cell/single-cell.component.html ================================================
      ================================================ FILE: apps/kirjs/src/app/modules/webassembly/ca/single-cell/single-cell.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SingleCellComponent } from './single-cell.component'; describe('SingleCellComponent', () => { let component: SingleCellComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SingleCellComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SingleCellComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/webassembly/ca/single-cell/single-cell.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'slides-single-cell', templateUrl: './single-cell.component.html', styleUrls: ['./single-cell.component.css'] }) export class SingleCellComponent implements OnInit { @Input() single = true; constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/ca/single-grid/single-grid.component.css ================================================ :host { display: flex; height: 100%; width: 100%; align-items: center; justify-content: center; } .cell { width: 10vh; height: 10vh; animation: toColor 4s linear infinite; /*animation: size 15s linear;*/ } .row { display: flex; } .central { animation: none; background: #000; } @keyframes size { 0% { width: 60vh; height: 60vh; } 100% { width: 10vh; height: 10vh; } } @keyframes toColor { 0% { background: #000; } 45% { background: #000; } 50% { background: #fff; } 95% { background: #fff; } 100% { background: #000; } } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/ca/single-grid/single-grid.component.html ================================================
      ================================================ FILE: apps/kirjs/src/app/modules/webassembly/ca/single-grid/single-grid.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SingleGridComponent } from './single-grid.component'; describe('SingleGridComponent', () => { let component: SingleGridComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SingleGridComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SingleGridComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/webassembly/ca/single-grid/single-grid.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'slides-single-grid', templateUrl: './single-grid.component.html', styleUrls: ['./single-grid.component.css'] }) export class SingleGridComponent implements OnInit { readonly f = [...new Array(9)].map((a, i) => i); readonly randomDelays = this.f.map(a => this.f.map(b => Math.random() * 5)); constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/full-screen-runner/full-screen-runner.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/webassembly/full-screen-runner/full-screen-runner.component.html ================================================ 90, 110, 184, 30 ================================================ FILE: apps/kirjs/src/app/modules/webassembly/full-screen-runner/full-screen-runner.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { FullScreenRunnerComponent } from './full-screen-runner.component'; describe('FullScreenRunnerComponent', () => { let component: FullScreenRunnerComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [FullScreenRunnerComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(FullScreenRunnerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/webassembly/full-screen-runner/full-screen-runner.component.ts ================================================ import { Component, Input, OnChanges, OnInit } from '@angular/core'; import { extractExpressionByMatch } from '../utils'; @Component({ selector: 'slides-full-screen-runner', templateUrl: './full-screen-runner.component.html', styleUrls: ['./full-screen-runner.component.css'] }) export class FullScreenRunnerComponent implements OnInit, OnChanges { @Input() code: any; cellSize = 10; wat: string; js: string; width = 2000; height = 1800; rowSize = 100; rule = 1; steps = 100; constructor() {} ngOnInit() {} ngOnChanges() { this.prepare(); } prepare() { const expected = (256 + this.rule) .toString(2) .substr(1) .split('') .map(Number) .reverse() .map(n => (n ? ' $enable' : ' $disable')) .join('\n'); this.wat = this.code.wat; this.js = this.code.js; this.js = this.js.replace(/size = \d+/, 'size = ' + this.cellSize); this.js = this.js.replace(/steps: \d+/, 'steps: ' + this.steps); this.js = this.js.replace(/rowSize: \d+/, 'rowSize: ' + this.rowSize); const elem = extractExpressionByMatch(/\(elem/, this.wat); const newElem = `(elem (i32.const 0) ${expected} )`; this.wat = this.wat.replace(elem, newElem); } } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/full-screen-runner/full-screen-runner.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FullScreenRunnerComponent } from './full-screen-runner.component'; import { WebassemblyRunnerModule } from '../webassembly-playground/webassembly-runner/webassembly-runner.module'; import { MatInputModule } from '@angular/material/input'; import { FormsModule } from '@angular/forms'; import { CellularAutomationModule } from '../../cellular-automation/cellular-automation.module'; @NgModule({ declarations: [FullScreenRunnerComponent], exports: [FullScreenRunnerComponent], imports: [ CommonModule, WebassemblyRunnerModule, MatInputModule, FormsModule, CellularAutomationModule ] }) export class FullScreenRunnerModule {} ================================================ FILE: apps/kirjs/src/app/modules/webassembly/monaco-wat.ts ================================================ /* Copyright 2018 Mozilla Foundation * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the 'Software'), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ declare const monaco; const languages = monaco.languages; const CompletionItemKind = languages.CompletionItemKind; const CompletionItemInsertTextRule = languages.CompletionItemInsertTextRule; type CompletionItem = typeof languages.CompletionItem; type IModel = any; type IPosition = any; type IRichLanguageConfiguration = any; let completionItems: CompletionItem[] = null; export function getWatCompletionItems() { const keyword = CompletionItemKind.Keyword; if (completionItems) { return completionItems; } return (completionItems = [ { label: 'module', documentation: '', kind: keyword, insertText: 'module' }, { label: 'func', documentation: 'function declaration', kind: keyword, insertText: 'func' }, { label: 'param', documentation: 'parameter', kind: keyword, insertText: { value: 'param ${1:identifier} ${2:type}' } as any }, { label: 'i32', documentation: '32-bit integer', kind: keyword, insertText: 'i32' }, { label: 'i64', documentation: '64-bit integer', kind: keyword, insertText: 'i64' }, { label: 'f32', documentation: '32-bit floating point', kind: keyword, insertText: 'f32' }, { label: 'f64', documentation: '64-bit floating point', kind: keyword, insertText: 'f64' }, { label: 'anyfunc', documentation: 'function reference', kind: keyword, insertText: 'anyfunc' }, { label: 'i32.load8_s', documentation: 'load 1 byte and sign-extend i8 to i32', kind: keyword, insertText: 'i32.load8_s' }, { label: 'i32.load8_u', documentation: 'load 1 byte and zero-extend i8 to i32', kind: keyword, insertText: 'i32.load8_u' }, { label: 'i32.load16_s', documentation: 'load 2 bytes and sign-extend i16 to i32', kind: keyword, insertText: 'i32.load16_s' }, { label: 'i32.load16_u', documentation: 'load 2 bytes and zero-extend i16 to i32', kind: keyword, insertText: 'i32.load16_u' }, { label: 'i32.load', documentation: 'load 4 bytes as i32', kind: keyword, insertText: 'i32.load' }, { label: 'i64.load8_s', documentation: 'load 1 byte and sign-extend i8 to i64', kind: keyword, insertText: 'i64.load8_s' }, { label: 'i64.load8_u', documentation: 'load 1 byte and zero-extend i8 to i64', kind: keyword, insertText: 'i64.load8_u' }, { label: 'i64.load16_s', documentation: 'load 2 bytes and sign-extend i16 to i64', kind: keyword, insertText: 'i64.load16_s' }, { label: 'i64.load16_u', documentation: 'load 2 bytes and zero-extend i16 to i64', kind: keyword, insertText: 'i64.load16_u' }, { label: 'i64.load32_s', documentation: 'load 4 bytes and sign-extend i32 to i64', kind: keyword, insertText: 'i64.load32_s' }, { label: 'i64.load32_u', documentation: 'load 4 bytes and zero-extend i32 to i64', kind: keyword, insertText: 'i64.load32_u' }, { label: 'i64.load', documentation: 'load 8 bytes as i64', kind: keyword, insertText: 'i64.load' }, { label: 'f32.load', documentation: 'load 4 bytes as f32', kind: keyword, insertText: 'f32.load' }, { label: 'f64.load', documentation: 'load 8 bytes as f64', kind: keyword, insertText: 'f64.load' }, { label: 'i32.store8', documentation: 'wrap i32 to i8 and store 1 byte', kind: keyword, insertText: 'i32.store8' }, { label: 'i32.store16', documentation: 'wrap i32 to i16 and store 2 bytes', kind: keyword, insertText: 'i32.store16' }, { label: 'i32.store', documentation: '(no conversion) store 4 bytes', kind: keyword, insertText: 'i32.store' }, { label: 'i64.store8', documentation: 'wrap i64 to i8 and store 1 byte', kind: keyword, insertText: 'i64.store8' }, { label: 'i64.store16', documentation: 'wrap i64 to i16 and store 2 bytes', kind: keyword, insertText: 'i64.store16' }, { label: 'i64.store32', documentation: 'wrap i64 to i32 and store 4 bytes', kind: keyword, insertText: 'i64.store32' }, { label: 'i64.store', documentation: '(no conversion) store 8 bytes', kind: keyword, insertText: 'i64.store' }, { label: 'f32.store', documentation: '(no conversion) store 4 bytes', kind: keyword, insertText: 'f32.store' }, { label: 'f64.store', documentation: '(no conversion) store 8 bytes', kind: keyword, insertText: 'f64.store' }, { label: 'get_local', documentation: 'read the current value of a local variable', kind: keyword, insertText: 'get_local' }, { label: 'set_local', documentation: 'set the current value of a local variable', kind: keyword, insertText: 'set_local' }, { label: 'tee_local', documentation: 'like `set_local`, but also returns the set value', kind: keyword, insertText: 'tee_local' }, { label: 'get_global', documentation: 'get the current value of a global variable', kind: keyword, insertText: 'get_global' }, { label: 'set_global', documentation: 'set the current value of a global variable', kind: keyword, insertText: 'set_global' }, { label: 'nop', documentation: 'no operation, no effect', kind: keyword, insertText: 'nop' }, { label: 'block', documentation: 'the beginning of a block construct, a sequence of instructions with a label at the end', kind: keyword, insertText: 'block' }, { label: 'loop', documentation: 'a block with a label at the beginning which may be used to form loops', kind: keyword, insertText: 'loop' }, { label: 'if', documentation: 'the beginning of an if construct with an implicit *then* block', kind: keyword, insertText: 'if' }, { label: 'else', documentation: 'marks the else block of an if', kind: keyword, insertText: 'else' }, { label: 'br', documentation: 'branch to a given label in an enclosing construct', kind: keyword, insertText: 'br' }, { label: 'br_if', documentation: 'conditionally branch to a given label in an enclosing construct', kind: keyword, insertText: 'br_if' }, { label: 'br_table', documentation: 'a jump table which jumps to a label in an enclosing construct', kind: keyword, insertText: 'br_table' }, { label: 'return', documentation: 'return zero or more values from this function', kind: keyword, insertText: 'return' }, { label: 'end', documentation: 'an instruction that marks the end of a block, loop, if, or function', kind: keyword, insertText: 'end' }, { label: 'call', documentation: 'call function directly', kind: keyword, insertText: 'call' }, { label: 'call_indirect', documentation: 'call function indirectly', kind: keyword, insertText: 'call_indirect' }, { label: 'i64.const', documentation: 'produce the value of an i64 immediate', kind: keyword, insertText: { value: 'i64.const ${1:constant}' } }, { label: 'i32.const', documentation: 'produce the value of an i32 immediate', kind: keyword, insertText: { value: 'i32.const ${1:constant}' } }, { label: 'f32.const', documentation: 'produce the value of an f32 immediate', kind: keyword, insertText: { value: 'f32.const ${1:constant}' } }, { label: 'f64.const', documentation: 'produce the value of an f64 immediate', kind: keyword, insertText: { value: 'f64.const ${1:constant}' } }, { label: 'i32.add', documentation: 'sign-agnostic addition', kind: keyword, insertText: 'i32.add' }, { label: 'i32.sub', documentation: 'sign-agnostic subtraction', kind: keyword, insertText: 'i32.sub' }, { label: 'i32.mul', documentation: 'sign-agnostic multiplication (lower 32-bits)', kind: keyword, insertText: 'i32.mul' }, { label: 'i32.div_s', documentation: 'signed division (result is truncated toward zero)', kind: keyword, insertText: 'i32.div_s' }, { label: 'i32.div_u', documentation: 'unsigned division (result is [floored](https://en.wikipedia.org/wiki/Floor_and_ceiling_functions))', kind: keyword, insertText: 'i32.div_u' }, { label: 'i32.rem_s', documentation: 'signed remainder (result has the sign of the dividend)', kind: keyword, insertText: 'i32.rem_s' }, { label: 'i32.rem_u', documentation: 'unsigned remainder', kind: keyword, insertText: 'i32.rem_u' }, { label: 'i32.and', documentation: 'sign-agnostic bitwise and', kind: keyword, insertText: 'i32.and' }, { label: 'i32.or', documentation: 'sign-agnostic bitwise inclusive or', kind: keyword, insertText: 'i32.or' }, { label: 'i32.xor', documentation: 'sign-agnostic bitwise exclusive or', kind: keyword, insertText: 'i32.xor' }, { label: 'i32.shl', documentation: 'sign-agnostic shift left', kind: keyword, insertText: 'i32.shl' }, { label: 'i32.shr_u', documentation: 'zero-replicating (logical) shift right', kind: keyword, insertText: 'i32.shr_u' }, { label: 'i32.shr_s', documentation: 'sign-replicating (arithmetic) shift right', kind: keyword, insertText: 'i32.shr_s' }, { label: 'i32.rotl', documentation: 'sign-agnostic rotate left', kind: keyword, insertText: 'i32.rotl' }, { label: 'i32.rotr', documentation: 'sign-agnostic rotate right', kind: keyword, insertText: 'i32.rotr' }, { label: 'i32.eq', documentation: 'sign-agnostic compare equal', kind: keyword, insertText: 'i32.eq' }, { label: 'i32.ne', documentation: 'sign-agnostic compare unequal', kind: keyword, insertText: 'i32.ne' }, { label: 'i32.lt_s', documentation: 'signed less than', kind: keyword, insertText: 'i32.lt_s' }, { label: 'i32.le_s', documentation: 'signed less than or equal', kind: keyword, insertText: 'i32.le_s' }, { label: 'i32.lt_u', documentation: 'unsigned less than', kind: keyword, insertText: 'i32.lt_u' }, { label: 'i32.le_u', documentation: 'unsigned less than or equal', kind: keyword, insertText: 'i32.le_u' }, { label: 'i32.gt_s', documentation: 'signed greater than', kind: keyword, insertText: 'i32.gt_s' }, { label: 'i32.ge_s', documentation: 'signed greater than or equal', kind: keyword, insertText: 'i32.ge_s' }, { label: 'i32.gt_u', documentation: 'unsigned greater than', kind: keyword, insertText: 'i32.gt_u' }, { label: 'i32.ge_u', documentation: 'unsigned greater than or equal', kind: keyword, insertText: 'i32.ge_u' }, { label: 'i32.clz', documentation: 'sign-agnostic count leading zero bits (All zero bits are considered leading if the value is zero)', kind: keyword, insertText: 'i32.clz' }, { label: 'i32.ctz', documentation: 'sign-agnostic count trailing zero bits (All zero bits are considered trailing if the value is zero)', kind: keyword, insertText: 'i32.ctz' }, { label: 'i32.popcnt', documentation: 'sign-agnostic count number of one bits', kind: keyword, insertText: 'i32.popcnt' }, { label: 'i32.eqz', documentation: 'compare equal to zero (return 1 if operand is zero, 0 otherwise)', kind: keyword, insertText: 'i32.eqz' }, { label: 'f32.add', documentation: 'addition', kind: keyword, insertText: 'f32.add' }, { label: 'f32.sub', documentation: 'subtraction', kind: keyword, insertText: 'f32.sub' }, { label: 'f32.mul', documentation: 'multiplication', kind: keyword, insertText: 'f32.mul' }, { label: 'f32.div', documentation: 'division', kind: keyword, insertText: 'f32.div' }, { label: 'f32.abs', documentation: 'absolute value', kind: keyword, insertText: 'f32.abs' }, { label: 'f32.neg', documentation: 'negation', kind: keyword, insertText: 'f32.neg' }, { label: 'f32.copysign', documentation: 'copysign', kind: keyword, insertText: 'f32.copysign' }, { label: 'f32.ceil', documentation: 'ceiling operator', kind: keyword, insertText: 'f32.ceil' }, { label: 'f32.floor', documentation: 'floor operator', kind: keyword, insertText: 'f32.floor' }, { label: 'f32.trunc', documentation: 'round to nearest integer towards zero', kind: keyword, insertText: 'f32.trunc' }, { label: 'f32.nearest', documentation: 'round to nearest integer, ties to even', kind: keyword, insertText: 'f32.nearest' }, { label: 'f32.eq', documentation: 'compare ordered and equal', kind: keyword, insertText: 'f32.eq' }, { label: 'f32.ne', documentation: 'compare unordered or unequal', kind: keyword, insertText: 'f32.ne' }, { label: 'f32.lt', documentation: 'compare ordered and less than', kind: keyword, insertText: 'f32.lt' }, { label: 'f32.le', documentation: 'compare ordered and less than or equal', kind: keyword, insertText: 'f32.le' }, { label: 'f32.gt', documentation: 'compare ordered and greater than', kind: keyword, insertText: 'f32.gt' }, { label: 'f32.ge', documentation: 'compare ordered and greater than or equal', kind: keyword, insertText: 'f32.ge' }, { label: 'f32.sqrt', documentation: 'square root', kind: keyword, insertText: 'f32.sqrt' }, { label: 'f32.min', documentation: 'minimum (binary operator); if either operand is NaN, returns NaN', kind: keyword, insertText: 'f32.min' }, { label: 'f32.max', documentation: 'maximum (binary operator); if either operand is NaN, returns NaN', kind: keyword, insertText: 'f32.max' }, { label: 'f64.add', documentation: 'addition', kind: keyword, insertText: 'f64.add' }, { label: 'f64.sub', documentation: 'subtraction', kind: keyword, insertText: 'f64.sub' }, { label: 'f64.mul', documentation: 'multiplication', kind: keyword, insertText: 'f64.mul' }, { label: 'f64.div', documentation: 'division', kind: keyword, insertText: 'f64.div' }, { label: 'f64.abs', documentation: 'absolute value', kind: keyword, insertText: 'f64.abs' }, { label: 'f64.neg', documentation: 'negation', kind: keyword, insertText: 'f64.neg' }, { label: 'f64.copysign', documentation: 'copysign', kind: keyword, insertText: 'f64.copysign' }, { label: 'f64.ceil', documentation: 'ceiling operator', kind: keyword, insertText: 'f64.ceil' }, { label: 'f64.floor', documentation: 'floor operator', kind: keyword, insertText: 'f64.floor' }, { label: 'f64.trunc', documentation: 'round to nearest integer towards zero', kind: keyword, insertText: 'f64.trunc' }, { label: 'f64.nearest', documentation: 'round to nearest integer, ties to even', kind: keyword, insertText: 'f64.nearest' }, { label: 'f64.eq', documentation: 'compare ordered and equal', kind: keyword, insertText: 'f64.eq' }, { label: 'f64.ne', documentation: 'compare unordered or unequal', kind: keyword, insertText: 'f64.ne' }, { label: 'f64.lt', documentation: 'compare ordered and less than', kind: keyword, insertText: 'f64.lt' }, { label: 'f64.le', documentation: 'compare ordered and less than or equal', kind: keyword, insertText: 'f64.le' }, { label: 'f64.gt', documentation: 'compare ordered and greater than', kind: keyword, insertText: 'f64.gt' }, { label: 'f64.ge', documentation: 'compare ordered and greater than or equal', kind: keyword, insertText: 'f64.ge' }, { label: 'f64.sqrt', documentation: 'square root', kind: keyword, insertText: 'f64.sqrt' }, { label: 'f64.min', documentation: 'minimum (binary operator); if either operand is NaN, returns NaN', kind: keyword, insertText: 'f64.min' }, { label: 'f64.max', documentation: 'maximum (binary operator); if either operand is NaN, returns NaN', kind: keyword, insertText: 'f64.max' }, { label: 'i32.wrap/i64', documentation: 'wrap a 64-bit integer to a 32-bit integer', kind: keyword, insertText: 'i32.wrap/i64' }, { label: 'i32.trunc_s/f32', documentation: 'truncate a 32-bit float to a signed 32-bit integer', kind: keyword, insertText: 'i32.trunc_s/f32' }, { label: 'i32.trunc_s/f64', documentation: 'truncate a 64-bit float to a signed 32-bit integer', kind: keyword, insertText: 'i32.trunc_s/f64' }, { label: 'i32.trunc_u/f32', documentation: 'truncate a 32-bit float to an unsigned 32-bit integer', kind: keyword, insertText: 'i32.trunc_u/f32' }, { label: 'i32.trunc_u/f64', documentation: 'truncate a 64-bit float to an unsigned 32-bit integer', kind: keyword, insertText: 'i32.trunc_u/f64' }, { label: 'i32.reinterpret/f32', documentation: 'reinterpret the bits of a 32-bit float as a 32-bit integer', kind: keyword, insertText: 'i32.reinterpret/f32' }, { label: 'i64.extend_s/i32', documentation: 'extend a signed 32-bit integer to a 64-bit integer', kind: keyword, insertText: 'i64.extend_s/i32' }, { label: 'i64.extend_u/i32', documentation: 'extend an unsigned 32-bit integer to a 64-bit integer', kind: keyword, insertText: 'i64.extend_u/i32' }, { label: 'i64.trunc_s/f32', documentation: 'truncate a 32-bit float to a signed 64-bit integer', kind: keyword, insertText: 'i64.trunc_s/f32' }, { label: 'i64.trunc_s/f64', documentation: 'truncate a 64-bit float to a signed 64-bit integer', kind: keyword, insertText: 'i64.trunc_s/f64' }, { label: 'i64.trunc_u/f32', documentation: 'truncate a 32-bit float to an unsigned 64-bit integer', kind: keyword, insertText: 'i64.trunc_u/f32' }, { label: 'i64.trunc_u/f64', documentation: 'truncate a 64-bit float to an unsigned 64-bit integer', kind: keyword, insertText: 'i64.trunc_u/f64' }, { label: 'i64.reinterpret/f64', documentation: 'reinterpret the bits of a 64-bit float as a 64-bit integer', kind: keyword, insertText: 'i64.reinterpret/f64' }, { label: 'f32.demote/f64', documentation: 'demote a 64-bit float to a 32-bit float', kind: keyword, insertText: 'f32.demote/f64' }, { label: 'f32.convert_s/i32', documentation: 'convert a signed 32-bit integer to a 32-bit float', kind: keyword, insertText: 'f32.convert_s/i32' }, { label: 'f32.convert_s/i64', documentation: 'convert a signed 64-bit integer to a 32-bit float', kind: keyword, insertText: 'f32.convert_s/i64' }, { label: 'f32.convert_u/i32', documentation: 'convert an unsigned 32-bit integer to a 32-bit float', kind: keyword, insertText: 'f32.convert_u/i32' }, { label: 'f32.convert_u/i64', documentation: 'convert an unsigned 64-bit integer to a 32-bit float', kind: keyword, insertText: 'f32.convert_u/i64' }, { label: 'f32.reinterpret/i32', documentation: 'reinterpret the bits of a 32-bit integer as a 32-bit float', kind: keyword, insertText: 'f32.reinterpret/i32' }, { label: 'f64.promote/f32', documentation: 'promote a 32-bit float to a 64-bit float', kind: keyword, insertText: 'f64.promote/f32' }, { label: 'f64.convert_s/i32', documentation: 'convert a signed 32-bit integer to a 64-bit float', kind: keyword, insertText: 'f64.convert_s/i32' }, { label: 'f64.convert_s/i64', documentation: 'convert a signed 64-bit integer to a 64-bit float', kind: keyword, insertText: 'f64.convert_s/i64' }, { label: 'f64.convert_u/i32', documentation: 'convert an unsigned 32-bit integer to a 64-bit float', kind: keyword, insertText: 'f64.convert_u/i32' }, { label: 'f64.convert_u/i64', documentation: 'convert an unsigned 64-bit integer to a 64-bit float', kind: keyword, insertText: 'f64.convert_u/i64' }, { label: 'f64.reinterpret/i64', documentation: 'reinterpret the bits of a 64-bit integer as a 64-bit float', kind: keyword, insertText: 'f64.reinterpret/i64' }, { label: 'current_memory', documentation: 'current memory size in 64k pages', kind: keyword, insertText: 'current_memory' }, { label: 'grow_memory', documentation: 'grow memory size by the specified amount of 64k pages', kind: keyword, insertText: 'grow_memory' } ]); } const LanguageConfiguration: IRichLanguageConfiguration = { // the default separators except `@$` wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\'\,\.\<\>\/\?\s]+)/g, comments: { lineComment: ';;' }, brackets: [ ['{', '}'], ['[', ']'], ['(', ')'] ], autoClosingPairs: [ { open: '{', close: '}' }, { open: '[', close: ']' }, { open: '(', close: ')' }, { open: "'", close: "'" }, { open: "'", close: "'" } ], surroundingPairs: [ { open: '{', close: '}' }, { open: '[', close: ']' }, { open: '(', close: ')' }, { open: "'", close: "'" }, { open: "'", close: "'" }, { open: '<', close: '>' } ] }; const MonarchDefinitions = { // Set defaultToken to invalid to see what you do not tokenize yet // defaultToken: 'invalid', keywords: [ 'module', 'table', 'memory', 'export', 'import', 'func', 'result', 'offset', 'anyfunc', 'type', 'data', 'start', 'element', 'global', 'local', 'mut', 'param', 'result', 'i32.load8_s', 'i32.load8_u', 'i32.load16_s', 'i32.load16_u', 'i32.load', 'i64.load8_s', 'i64.load8_u', 'i64.load16_s', 'i64.load16_u', 'i64.load32_s', 'i64.load32_u', 'i64.load', 'f32.load', 'f64.load', 'i32.store8', 'i32.store16', 'i32.store', 'i64.store8', 'i64.store16', 'i64.store32', 'i64.store', 'f32.store', 'f64.store', 'i32.const', 'i64.const', 'f32.const', 'f64.const', 'i32.add', 'i32.sub', 'i32.mul', 'i32.div_s', 'i32.div_u', 'i32.rem_s', 'i32.rem_u', 'i32.and', 'i32.or', 'i32.xor', 'i32.shl', 'i32.shr_u', 'i32.shr_s', 'i32.rotl', 'i32.rotr', 'i32.eq', 'i32.ne', 'i32.lt_s', 'i32.le_s', 'i32.lt_u', 'i32.le_u', 'i32.gt_s', 'i32.ge_s', 'i32.gt_u', 'i32.ge_u', 'i32.clz', 'i32.ctz', 'i32.popcnt', 'i32.eqz', 'f32.add', 'f32.sub', 'f32.mul', 'f32.div', 'f32.abs', 'f32.neg', 'f32.copysign', 'f32.ceil', 'f32.floor', 'f32.trunc', 'f32.nearest', 'f32.eq', 'f32.ne', 'f32.lt', 'f32.le', 'f32.gt', 'f32.ge', 'f32.sqrt', 'f32.min', 'f32.max', 'f64.add', 'f64.sub', 'f64.mul', 'f64.div', 'f64.abs', 'f64.neg', 'f64.copysign', 'f64.ceil', 'f64.floor', 'f64.trunc', 'f64.nearest', 'f64.eq', 'f64.ne', 'f64.lt', 'f64.le', 'f64.gt', 'f64.ge', 'f64.sqrt', 'f64.min', 'f64.max', 'i32.wrap/i64', 'i32.trunc_s/f32', 'i32.trunc_s/f64', 'i32.trunc_u/f32', 'i32.trunc_u/f64', 'i32.reinterpret/f32', 'i64.extend_s/i32', 'i64.extend_u/i32', 'i64.trunc_s/f32', 'i64.trunc_s/f64', 'i64.trunc_u/f32', 'i64.trunc_u/f64', 'i64.reinterpret/f64', 'f32.demote/f64', 'f32.convert_s/i32', 'f32.convert_s/i64', 'f32.convert_u/i32', 'f32.convert_u/i64', 'f32.reinterpret/i32', 'f64.promote/f32', 'f64.convert_s/i32', 'f64.convert_s/i64', 'f64.convert_u/i32', 'f64.convert_u/i64', 'f64.reinterpret/i64', 'local.get', 'local.set', 'local.tee', 'global.get', 'global.set', 'global.tee', 'get_local', 'set_local', 'tee_local', 'get_global', 'set_global', 'current_memory', 'grow_memory' ], typeKeywords: ['i32', 'i64', 'f32', 'f64', 'anyfunc'], operators: [] as any, brackets: [ ['(', ')', 'bracket.parenthesis'], ['{', '}', 'bracket.curly'], ['[', ']', 'bracket.square'] ], // we include these common regular expressions symbols: /[=> x.label === word; const item = watCompletionItems.find(predicate); if (!item) { return; } return { range: new monaco.Range( position.lineNumber, index + 1, position.lineNumber, index + 1 + word.length ), contents: [ '**DETAILS**', { language: 'html', value: item.documentation } ] }; } } }; monaco.languages.onLanguage('wat', () => { monaco.languages.setMonarchTokensProvider( 'wat', Wat.MonarchDefinitions as any ); monaco.languages.setLanguageConfiguration('wat', Wat.LanguageConfiguration); monaco.languages.registerCompletionItemProvider( 'wat', Wat.CompletionItemProvider ); monaco.languages.registerHoverProvider('wat', Wat.HoverProvider as any); }); monaco.languages.register({ id: 'wat' }); monaco.languages.registerCompletionItemProvider( 'wat', { provideCompletionItems: function() { return { suggestions: [ { label: 'const', kind: monaco.languages.CompletionItemKind.Function, documentation: 'i32', insertText: 'i32.const ${1:value}', insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet }, { label: 'function', kind: monaco.languages.CompletionItemKind.Keyword, documentation: 'i32', insertText: '(func $${1:name} ${2:params} (result i32)\n ${3}\n)', insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet }, { label: 'add', kind: monaco.languages.CompletionItemKind.Keyword, documentation: 'i32', insertText: 'i32.add', insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet }, { label: 'param', kind: monaco.languages.CompletionItemKind.Keyword, documentation: 'i32', insertText: '(param $${1:name} i32) ', insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet }, { label: 'local.get', kind: monaco.languages.CompletionItemKind.Keyword, documentation: 'i32', insertText: 'local.get $${1:name}', insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet }, { label: 'global.get', kind: monaco.languages.CompletionItemKind.Keyword, documentation: 'i32', insertText: 'global.get $${1:name}', insertTextRules: CompletionItemInsertTextRule.InsertAsSnippet } ] }; } }, '(' ); ================================================ FILE: apps/kirjs/src/app/modules/webassembly/samples/answer.wat ================================================ (module (import "config" "rowSize" (global $rowSize i32)) (memory 1) (export "memory" (memory 0)) (table 8 anyfunc) (func $enable (result i32) (i32.const 1) ) (func $disable (result i32) (i32.const 0) ) (elem (i32.const 0) $enable ;; 000 $enable ;; 001 $enable ;; 010 $enable ;; 011 $enable ;; 100 $enable ;; 101 $enable ;; 110 $enable ;; 111 ) (type $return_i32 (func (result i32))) (global $step (export "step") (mut i32) (i32.const 1)) (func $rotate (param $x i32) (result i32) local.get $x global.get $rowSize i32.add global.get $rowSize i32.rem_s ) (func $getIndex (param $x i32) (param $y i32) (result i32) local.get $x local.get $y global.get $rowSize i32.mul i32.add ) (func $loadCell (param $x i32) (param $y i32) (result i32) (local.get $x) call $rotate (local.get $y) call $getIndex i32.const 4 i32.mul i32.load ) (func $storeCell (param $x i32) (param $value i32) local.get $x global.get $step call $getIndex i32.const 4 i32.mul local.get $value i32.store ) (func $shift (param $a i32) (param $b i32) (param $c i32) (result i32) local.get $a (i32.const 4) i32.mul local.get $b (i32.const 2) i32.mul local.get $c i32.add i32.add ) (func $loadPreviousCell (param $x i32) (result i32) local.get $x global.get $step (i32.const 1) i32.sub call $loadCell ) (func $getCellScore (param $x i32) (result i32) local.get $x i32.const 1 i32.sub call $loadPreviousCell local.get $x call $loadPreviousCell local.get $x i32.const 1 i32.add call $loadPreviousCell call $shift ) (func $evolveCell (param $x i32) local.get $x local.get $x call $getCellScore call_indirect (type $return_i32) call $storeCell ) (func $evolveRow (local $i i32) block loop local.get $i call $evolveCell local.get $i (i32.const 1) i32.add local.tee $i global.get $rowSize i32.eq br_if 1 br 0 end end ) (func $evolve (export "evolve") (param $steps i32) (local $i i32) block loop call $evolveRow global.get $step i32.const 1 i32.add global.set $step local.get $i (i32.const 1) i32.add tee_local $i local.get $steps i32.eq br_if 1 br 0 end end ) ) ================================================ FILE: apps/kirjs/src/app/modules/webassembly/samples/base.js ================================================ const size = 80; const config = { rowSize: 5, steps: 100, log: console.log }; function drawOnCanvas(canvas, arr) { const context = canvas.getContext('2d'); for (let i = 0; i < arr.length; i++) { const x = i % config.rowSize; const y = Math.floor(i / config.rowSize); context.fillStyle = arr[i] ? 'lime' : '#444'; context.fillRect(x * size, y * size, size - 1, size - 1); } } async function run(code, canvas) { const result = await WebAssembly.instantiate(code, { config }); let memory = new Uint32Array(result.instance.exports.memory.buffer); memory[Math.round(config.rowSize / 2)] = 1; const r = result.instance.exports.evolve(config.steps); drawOnCanvas(canvas, memory); return r; } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/samples/base.wat ================================================ (module ) ================================================ FILE: apps/kirjs/src/app/modules/webassembly/samples/old.wat ================================================ (module (import "config" "rowSize" (global $rowSize i32)) (import "config" "log" (func $log (param i32))) (type $return_i32 (func (result i32))) (memory 1) (export "memory" (memory 0)) (table 8 funcref) (elem (i32.const 0) $remove ;; 000 $remove ;; 001 $create ;; 010 $create ;; 011 $create ;; 100 $create ;; 101 $remove ;; 110 $remove ;; 111 ) (func $create (result i32) i32.const 1) (func $remove (result i32) i32.const 0) (global $step (export "step") (mut i32) (i32.const 1)) (func $getIndex (param $x i32) (param $y i32) (result i32) (i32.mul (i32.const 4) (i32.add (local.get $x) (i32.mul (local.get $y) (global.get $rowSize)))) ) (func $getCurrentIndex (param $x i32) (result i32) (call $getIndex (local.get $x) (global.get $step)) ) (func $getPreviousIndex (param $x i32) (result i32) (call $getIndex (local.get $x) (i32.sub (global.get $step) (i32.const 1))) ) (func $rotate (param $x i32) (result i32) (i32.rem_s (i32.add (global.get $rowSize) (local.get $x)) (global.get $rowSize) ) ) (func $shift (param $a i32) (param $b i32) (param $c i32) (result i32) (local.get $c) (local.get $b) (i32.const 1) i32.shl i32.add (local.get $a) (i32.const 2) i32.shl i32.add ) (func $calcNextState (param $x i32) (result i32) local.get $x call $getCellScore call_indirect (type $return_i32) ) (func $getCellScore (param $x i32) (result i32) (local $a i32) local.get $x i32.const 1 i32.sub call $rotate call $getPreviousIndex i32.load local.get $x call $rotate call $getPreviousIndex i32.load local.get $x i32.const 1 i32.add call $rotate call $getPreviousIndex i32.load call $shift ) (func $evolve (param $steps i32) (local $index i32) block loop (call $evolveSingle) ;; $index++ local.get $index i32.const 1 i32.add local.tee $index (br_if 1 (i32.eq (local.get $steps) (local.get $index))) drop br 0 end end ) (func $evolveSingle (result i32) (local $index i32) block loop ;; color up the cell local.get $index call $getCurrentIndex local.get $index call $calcNextState i32.store ;; $index++ local.get $index i32.const 1 i32.add local.tee $index (br_if 1 (i32.eq (global.get $rowSize) (local.get $index))) drop br 0 end end global.get $step i32.const 1 i32.add global.set $step local.get $index ) (export "evolve" (func $evolve)) ) ================================================ FILE: apps/kirjs/src/app/modules/webassembly/tests/add-tests.ts ================================================ export const addTests = [ { args: [0, 0], output: 0 }, { args: [1, 0], output: 1 }, { args: [1, 1], output: 2 }, { args: [17, 25], output: 42 } ]; ================================================ FILE: apps/kirjs/src/app/modules/webassembly/tests/common.ts ================================================ export const red = '#ff3f00'; export const defaultRowSize = 5; export function colorMatchesExpected(a, i, c, test) { return test.actualMemory[i] === test.expectedMemory[i] ? '#ddd' : red; } export function outputCoordinates(config) { return [ { y: -1, x: config.args[0], color: 'yellow', text: config.args[0] }, { x: -1, y: config.args[1], color: 'yellow', text: config.args[1] }, { x: config.args[0], y: config.args[1], color: 'lime', text: config.output } ]; } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/tests/disable-tests.ts ================================================ export const disableTests = [ { args: [], output: 0 } ]; ================================================ FILE: apps/kirjs/src/app/modules/webassembly/tests/enable-tests.ts ================================================ export const enableTests = [ { args: [], output: 1 } ]; ================================================ FILE: apps/kirjs/src/app/modules/webassembly/tests/evolve-cell.ts ================================================ import { colorMatchesExpected } from './common'; const rowSize = 3; const viz = { type: 'evolve', rowSize, text: a => a, memory: test => test.actualMemory, color: colorMatchesExpected }; export const evolveCellTests = [ { args: [0], memory: [0, 0, 0, 0, 0, 0], table: [ 'enable', 'enable', 'enable', 'enable', 'enable', 'enable', 'enable', 'enable' ], expectedMemory: [0, 0, 0, 1, 0, 0], imports: { config: { step: 1, rowSize: 3 } }, viz: { ...viz, rule: 0 } }, { args: [2], memory: [0, 0, 0, 0, 0, 0], table: [ 'enable', 'enable', 'enable', 'enable', 'enable', 'enable', 'enable', 'enable' ], expectedMemory: [0, 0, 0, 0, 0, 1], imports: { config: { step: 1, rowSize: 3 } }, viz }, { args: [2], memory: [1, 1, 1, 1, 1, 1], table: [ 'disable', 'disable', 'disable', 'disable', 'disable', 'disable', 'disable', 'disable' ], expectedMemory: [1, 1, 1, 1, 1, 0], imports: { config: { step: 1, rowSize: 3 } }, viz } ]; ================================================ FILE: apps/kirjs/src/app/modules/webassembly/tests/evolve-row.ts ================================================ import { colorMatchesExpected } from './common'; const rowSize = 3; const viz = { type: 'evolve', rowSize, text: a => a, memory: test => test.actualMemory, color: colorMatchesExpected }; export const evolveRowTests = [ { args: [], memory: [0, 0, 0, 0, 0, 0], table: [ 'enable', 'enable', 'enable', 'enable', 'enable', 'enable', 'enable', 'enable' ], expectedMemory: [0, 0, 0, 1, 1, 1], imports: { config: { step: 1, rowSize: 3 } }, viz }, { args: [2], memory: [1, 1, 1, 1, 1, 1], table: [ 'disable', 'disable', 'disable', 'disable', 'disable', 'disable', 'disable', 'disable' ], expectedMemory: [1, 1, 1, 0, 0, 0], imports: { config: { step: 1, rowSize: 3 } }, viz } ]; ================================================ FILE: apps/kirjs/src/app/modules/webassembly/tests/evolve.ts ================================================ import { colorMatchesExpected, defaultRowSize } from './common'; const viz = { type: 'evolve', rowSize: defaultRowSize, text: a => a, memory: test => test.actualMemory, color: colorMatchesExpected }; export const evolveTests = [ { args: [1], memory: [0, 0, 0, 1, 0], table: [ 'enable', 'enable', 'enable', 'enable', 'enable', 'enable', 'enable', 'enable' ], expectedMemory: [0, 0, 0, 1, 0, 1, 1, 1, 1, 1], imports: { config: { step: 1, rowSize: defaultRowSize } }, viz } ]; ================================================ FILE: apps/kirjs/src/app/modules/webassembly/tests/get-cell-score.ts ================================================ export const getCellScoreTests = [ { args: [1], memory: [0, 0, 1], output: 0b001, imports: { config: { rowSize: 3, step: 1 } } }, { args: [2], memory: [1, 0, 0], output: 0b001, imports: { config: { rowSize: 3, step: 1 } } }, { args: [1], memory: [1, 1, 0], output: 0b110, imports: { config: { rowSize: 3, step: 1 } } } ]; ================================================ FILE: apps/kirjs/src/app/modules/webassembly/tests/get-index.ts ================================================ import { outputCoordinates } from './common'; const rowSize = 5; const viz = { rowSize, text: (a, i) => i, extras: outputCoordinates }; export const getIndex = [ { args: [0, 0], imports: { config: { rowSize: rowSize } }, output: 0, viz }, { args: [1, 0], imports: { config: { rowSize: rowSize } }, output: 1, viz }, { args: [0, 1], imports: { config: { rowSize: rowSize } }, output: rowSize, viz }, { args: [0, 1], imports: { config: { rowSize: 4 } }, output: 4, viz }, { args: [3, 3], imports: { config: { rowSize: 4 } }, output: 15, viz } ]; ================================================ FILE: apps/kirjs/src/app/modules/webassembly/tests/load-cell.ts ================================================ const rowSize = 2; export const memoryRepViz = { rowSize: rowSize * 4, text: (a, i, c, test) => { return i % 4 === 0 ? test.memory[i / 4] : ''; }, color: (a, i, o, test) => { const c = (test.args[1] * rowSize + test.args[0]) * 4; if (i >= c && i < c + 4) { return 'lime'; } return Math.floor((i / 4) % 2) === 0 ? '#ddd' : '#999'; } }; export const loadCellTests = [ { args: [0, 0], memory: [0, 0, 0], output: 0, imports: { config: { rowSize } }, viz: memoryRepViz }, { args: [0, 0], memory: [1, 0, 0], output: 1, imports: { config: { rowSize } }, viz: memoryRepViz }, { args: [1, 0], memory: [1, 1, 0, 0], output: 1, imports: { config: { rowSize } }, viz: memoryRepViz }, { args: [0, 1], memory: [1, 0, 1, 0], viz: memoryRepViz, output: 1, imports: { config: { rowSize } } }, { args: [1, 1], memory: [1, 0, 0, 0], output: 0, viz: memoryRepViz, imports: { config: { rowSize } } } ]; ================================================ FILE: apps/kirjs/src/app/modules/webassembly/tests/load-previous-cell.ts ================================================ export const loadPreviousCellTests = [ { args: [0], memory: [0, 0, 0], output: 0, imports: { config: { step: 1, rowSize: 3 } } }, { args: [0], memory: [1, 0, 0], output: 1, imports: { config: { step: 1, rowSize: 3 } } }, { args: [2], memory: [1, 0, 1], output: 1, imports: { config: { step: 1, rowSize: 3 } } }, { args: [2], memory: [1, 0, 1, 1, 0, 1], output: 1, imports: { config: { step: 2, rowSize: 3 } } }, { args: [2], memory: [1, 0, 1, 1, 0, 0], output: 0, imports: { config: { step: 2, rowSize: 3 } } } ]; ================================================ FILE: apps/kirjs/src/app/modules/webassembly/tests/rotate.ts ================================================ const rowSize = 5; const longFowSize = 4; const yellow = { color: 'yellow', text: 'Y' }; const lime = { color: 'lime', text: 'G' }; const red = { color: 'red', text: 'R' }; export const rotate = [ { args: [0, rowSize], imports: { config: { rowSize } }, output: 0, viz: { rowSize, extras: [ { ...lime, x: 0, y: 0 }, { ...yellow, x: 0, y: 1 } ] } }, { args: [rowSize, rowSize], imports: { config: { rowSize } }, output: 0, viz: { rowSize, extras: [ { ...red, x: rowSize, y: 0 }, { ...lime, x: 0, y: 0 }, { ...lime, x: rowSize - 2, y: 0 }, { ...lime, x: rowSize - 1, y: 0 }, { ...yellow, x: rowSize - 1, y: 1 } ] } }, { args: [-1, rowSize], imports: { config: { rowSize } }, output: 4, viz: { rowSize, extras: [ { ...red, x: -1, y: 0 }, { ...lime, x: 0, y: 0 }, { ...lime, x: 1, y: 0 }, { ...lime, x: rowSize - 1, y: 0 }, { ...yellow, x: 0, y: 1 } ] } }, { args: [longFowSize, longFowSize], imports: { config: { rowSize: longFowSize } }, output: 0, viz: { rowSize: longFowSize, extras: [ { ...red, x: longFowSize, y: 0 }, { ...lime, x: 0, y: 0 }, { ...lime, x: longFowSize - 2, y: 0 }, { ...lime, x: longFowSize - 1, y: 0 }, { ...yellow, x: longFowSize - 1, y: 1 } ] } } ]; ================================================ FILE: apps/kirjs/src/app/modules/webassembly/tests/shift-tests.ts ================================================ const viz = { type: 'shift' }; export const shiftTests = [ { args: [0, 0, 0], output: 0, viz }, { args: [0, 0, 1], output: 0b1, viz }, { args: [0, 1, 0], output: 0b10, viz }, { args: [1, 0, 0], output: 0b100, viz }, { args: [1, 1, 1], output: 0b111, viz } ]; ================================================ FILE: apps/kirjs/src/app/modules/webassembly/tests/store-cell-tests.ts ================================================ import { memoryRepViz } from './load-cell'; export const storeCellTests = [ { args: [1, 1], imports: { config: { rowSize: 3, step: 0 } }, memory: [0, 1, 0], expectedMemory: [0, 1, 0], viz: memoryRepViz }, { args: [1, 0], imports: { config: { rowSize: 3, step: 0 } }, memory: [0, 1, 0], expectedMemory: [0, 0, 0], viz: memoryRepViz } ]; ================================================ FILE: apps/kirjs/src/app/modules/webassembly/utils.spec.ts ================================================ import { extractFunction } from './utils'; describe('SyncComponent', () => { it('should create', () => { expect( extractFunction( 'getIndex', ` (export "memory" (memory 0)) (global $step (export "step") (mut i32) (i32.const 1)) (func $getIndex (param $x i32) (param $y i32) (result i32) (i32.mul (i32.const 4) (i32.add (local.get $x) (i32.mul (local.get $y) (global.get $rowSize)))) ) ` ) ).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/webassembly/utils.ts ================================================ import { BaseBlock, CodeHelperBlock } from './webassembly-playground/monaco-directives/common'; export function extractFunction(name, code) { return extractExpressionByMatch( new RegExp('\\(func \\$' + name + '\\b'), code ); } export function prepareTableCode(code) { const elements = extractExpressionByMatch(/\(elem/, code); if (!elements) { // tslint:disable-next-line:no-debugger debugger; } const functions = [ ...new Set([...elements.matchAll(/\$(\w+)\b/g)].map(a => a[1])) ] .map(name => extractFunction(name, code)) .join('\n'); const tableDef = extractExpressionByMatch(/\(table/, code); return ` ;; table ${tableDef} ;; elem ${elements} ;; table functions ${functions} `; } export function extractTypeCode(code) { // TODO(kirjs): This would only extract one type, at some point there can be more const type = extractExpressionByMatch(/\(type/, code); return ` ;; types (we only extract one ATM) ${type} `; } const matchTypeRegex = /^\(\s*([\w.]+)\b/; const funcNameRegex = /func\s*\$(\w+)/; function getName(code) { if (getType(code) === 'func') { const match = code.match(funcNameRegex); return match ? match[1] : undefined; } return undefined; } function getType(code) { const t = code.match(matchTypeRegex); return t && t[1]; } export function serializeBlocks(blocks: CodeHelperBlock[]) { return blocks.map(b => b.type + (b.name ? `(${b.name})` : '')).join('/'); } export function populateBlocks(blocks: BaseBlock[]): CodeHelperBlock[] { return blocks.map((b: any) => { const type = getType(b.code) || 'module'; return { name: getName(b.code), type, ...b }; }); } export function extractBlocks( textBefore, textAfter, prependLeft = '', prependRight = '' ) { const before = findPrevNonMatchingClosingBrace(textBefore); const after = findNextNonMatchingClosingBrace(textAfter); if (before && after) { const next = extractBlocks( textBefore.slice(0, -before.length - 1), textAfter.slice(after.length), before + prependLeft, prependRight + after ); return [ { before, after, code: before + prependLeft + prependRight + after }, ...next ]; } return []; } export function findNextNonMatchingClosingBrace(code: string) { return findMatchingBrace(code, 0, 1, 1); } export function findPrevNonMatchingClosingBrace(code: string) { return findMatchingBrace(code, code.length - 1, -1, -1, -1); } function findMatchingBrace( code: string, startIndex = 0, shift = 1, braces = 0, /*This is a hack, need proper fix*/ resultShift = 0 ) { let i = startIndex; while (code[i]) { const c = code[i]; if (c === '(') { braces++; } if (c === ')') { braces--; } i += shift; if (braces === 0) { return code.substring(startIndex, i - resultShift); } } } export function extractExpressionByMatch(regex, code) { const match = regex.exec(code); if (match) { let i = match.index; let braces = 0; while (true) { const c = code[i]; if (!c) { return null; } if (c === '(') { braces++; } if (c === ')') { braces--; } i++; if (braces === 0) { return code.substring(match.index, i); } } } } export function extractGlobalAccess(code) { const match = /(?:get_global|global\.get)\s+\$(\w+)*/g; return [...new Set([...code.matchAll(match)].map(a => a[1]))]; } export function extractGlobals(code, allCode) { return extractGlobalAccess(code); } export function hasMemoryCalls(code) { return /i32\.(store|load)\s+/g.test(code); } export function hasTypeCalls(code) { return /type \$/g.test(code); } export function hasTableCalls(code, config) { return /(call_indirect)\s+/g.test(code); } function unique(arr) { return [...new Set(arr)]; } export function populateTestCode( code: string, test, allCode: string, table: string ) { if (test.table) { const funcs = unique(test.table) .map(name => extractFunction(name, allCode)) .join('\n\n'); const elements = test.table.map(e => ' $' + e).join('\n'); table = ` (table ${test.table.length} funcref) (elem (i32.const 0) ${elements} ) ${funcs} `; } const regExp = /;;{table}/; if (table) { code = code.replace(regExp, table); } return code; } export function extractFunctionWithDependencies( name, code: string, dependencies: string[] ) { return extractFunctionDependencyNames(name, code, dependencies) .map(name => extractFunction(name, code)) .join('\n\n'); } export function hasGlobal(name, code) { return /global\s+/g.test(code); } export function extractFunctionDependencyNames( name, code: string, dependencies: string[] ) { const funcCode = extractFunction(name, code); const match = /(?:\bcall)\s+\$(\w+)*/g; if (!funcCode) { return []; } const functions = [ ...new Set([...funcCode.matchAll(match)].map(a => a[1])) ].filter(d => !dependencies.includes(d)); const nestedDeps = functions.flatMap(f => extractFunctionDependencyNames(f, code, [...dependencies, ...functions]) ); return [...new Set([...dependencies, ...nestedDeps, ...functions])]; } function getMemoryCode(hasMemory: boolean) { return hasMemory ? ` (memory 1) (export "memory" (memory 0)) ` : ''; } export function generateWatTestCode({ code, globals, name, hasMemory, types }: any) { const globalsCode = globals .map( global => `(global \$${global} (export "${global}") (mut i32) (i32.const 0))` ) .join('\n'); const memoryCode = getMemoryCode(hasMemory); const funcExport = code.match(`export "${name}"`) ? '' : `(export "${name}" (func $${name}))`; return `(module ${funcExport} ${types} ${globalsCode} ;;{table} ${memoryCode} ${code} )`; } export function extractAllFunctions(code) { const match = /func\s+\$(\w+)*/g; return [...new Set([...code.matchAll(match)].map(a => a[1]))]; } export function extractAnswers(code) { const functions = extractAllFunctions(code).map(name => [ name, extractFunction(name, code) ]); return new Map(functions as any); } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/wasm-binary/wasm-binary.component.css ================================================ ::ng-deep [name='section-type'] { display: block; flex-basis: 100%; background: #444 !important; border-radius: 4px; margin-top: 8px; margin-bottom: 4px; padding: 8px 16px; color: white !important; } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/wasm-binary/wasm-binary.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/webassembly/wasm-binary/wasm-binary.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { WasmBinaryComponent } from './wasm-binary.component'; describe('WasmBinaryComponent', () => { let component: WasmBinaryComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [WasmBinaryComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(WasmBinaryComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/webassembly/wasm-binary/wasm-binary.component.ts ================================================ import { Component } from '@angular/core'; import { wasmParser } from './wasm-parser'; import { strToBin } from '../../binary/parser/utils'; declare const require; // if the extention is .wasm, webpack is being stupid. const wasm = require('!binary-loader!./test._wasm'); @Component({ selector: 'kirjs-wasm-binary', templateUrl: './wasm-binary.component.html', styleUrls: ['./wasm-binary.component.css'] }) export class WasmBinaryComponent { readonly binary = strToBin(wasm); readonly parser = wasmParser(); } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/wasm-binary/wasm-parser.ts ================================================ import { BinaryParser } from '../../binary/parser/binary-parser'; const funcTypes = { 0x7f: 'i32', 0x7e: 'i64', 0x7d: 'f32', 0x7c: 'f64', 0x70: 'anyfunc', 0x60: 'func', 0x40: 'empty_block' }; const valueTypes = { 0x7f: 'i32', 0x7e: 'i64', 0x7d: 'f32', 0x7c: 'f64' }; export function wasmParser() { const funcParamTypeParser = new BinaryParser().uInt8('type', { enum: valueTypes }); const funcTypeParser = new BinaryParser() .uInt8('type', { enum: funcTypes }) .uInt8('paramCount') .array('params', { parser: funcParamTypeParser, length: 'paramCount' }) .uInt8('returnCount') .array('returns', { parser: funcParamTypeParser, length: 'returnCount' }); const fieldParser = new BinaryParser() .varuint7('len') .string('name', { length: 'len' }); const kindParser = new BinaryParser().uInt8('kind').choice('declaration', { key: 'kind', values: { 0: new BinaryParser().varuint7('function'), 2: new BinaryParser().varuint7('memory-tbd') } }); const exportItemParser = new BinaryParser() .block('field', fieldParser) .block('kind', kindParser); const localEntryParser = new BinaryParser() .varuint7('local_count') .uInt8('value_type'); const codeBodyParser = new BinaryParser() .varuint7('function_body_type_body_size') .block('local', localEntryParser) .block('local', localEntryParser) .block('local', localEntryParser) .block('local', localEntryParser); const sectionParsers = { type: new BinaryParser().uInt8('count').array('params', { length: 'count', parser: funcTypeParser }), import: new BinaryParser() .uInt8('count') .varuint7('moduleLen') .string('module', { length: 'moduleLen' }) .block('field', fieldParser) .block('kind', kindParser), function: new BinaryParser().bit32('TBD'), table: new BinaryParser().bit24('TBD'), export: new BinaryParser().varuint7('count').array('items', { length: 'count', parser: exportItemParser }), start: new BinaryParser().varuint7('index'), code: new BinaryParser() .varuint7('number_of_functions') .block('lol', codeBodyParser) }; const sectionParser = new BinaryParser() .uInt8('section-type', { enum: { 0x01: 'Types', 0x02: 'Import', 0x03: 'Function', 0x05: 'Table', 0x07: 'Export', 0x08: 'Start', 0x0a: 'Code' } }) .varuint7('size') .choice('section', { key: 'section-type', values: { 0x01: sectionParsers.type, 0x02: sectionParsers.import, 0x03: sectionParsers.function, 0x05: sectionParsers.table, 0x07: sectionParsers.export, 0x08: sectionParsers.start, 0x0a: sectionParsers.code } }); const header = new BinaryParser() .string('headerConst', { length: 4, description: `header const` }) .uInt32le('version') .array('sections', { length: 7, parser: sectionParser }); return new BinaryParser().block('header', header); } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/error-message/error-message.component.css ================================================ .error { font-size: 20px; padding: 20px; text-align: center; background: #ff4e3d; color: #fff; opacity: 0.9; width: 100%; margin-left: -20px; } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/error-message/error-message.component.html ================================================
      {{ result.value }}
      ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/error-message/error-message.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ErrorMessageComponent } from './error-message.component'; describe('ErrorMessageComponent', () => { let component: ErrorMessageComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ErrorMessageComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(ErrorMessageComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/error-message/error-message.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; import { Result } from '../web-assembly.service'; @Component({ selector: 'slides-error-message', templateUrl: './error-message.component.html', styleUrls: ['./error-message.component.css'] }) export class ErrorMessageComponent implements OnInit { @Input() result: Result; constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/monaco-directives/common.ts ================================================ import { webAssemblyModuleContentHandler, webAssemblyTestHandler } from '../runners/wasm-test-runner/wasm-test-runner.component'; export interface BaseBlock { code: string; before?: string; after?: string; } export interface CodeHelperBlock extends BaseBlock { type: string; name?: string; meta: any; } export interface CodePath { type: 'ts' | 'wat'; blocks: CodeHelperBlock[]; } export function getCodeBlockHandler(lang, type) { if (!codeBlockHandlers[lang] || !codeBlockHandlers[lang][type]) { return; } return codeBlockHandlers[lang][type]; } const displayPreview = () => { return { mode: 'default' }; }; export const codeBlockHandlers = { ts: { FunctionDeclaration: displayPreview, SourceFile: displayPreview }, wat: { elem: displayPreview, func: webAssemblyTestHandler, module: webAssemblyModuleContentHandler } }; ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/monaco-directives/monaco-js-position.directive.ts ================================================ import { AfterViewInit, Directive, EventEmitter, Optional, Output, Self } from '@angular/core'; import { CodeDemoEditorInjector } from '@codelab/code-demos/src/lib/code-demo-editor/code-demo-editor.injector'; import { getTypeScript } from '@codelab/utils/src/lib/loaders/loaders'; import { serializeBlocks } from '../../utils'; import { CodePath } from './common'; const ts = getTypeScript(); const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed }); const usefulTypes = new Set([ ts.SyntaxKind.SourceFile, ts.SyntaxKind.FunctionDeclaration ]); function getName(token) { if (token.kind === ts.SyntaxKind.FunctionDeclaration) { return token.name.text; } } function resolveToken(token, sourceFile) { const code = printer.printNode(ts.EmitHint.Unspecified, token, sourceFile); const name = getName(token); return { code, type: ts.SyntaxKind[token.kind], ...token, name }; } function processBlocks(blocks: any[], sourceFile) { return blocks .map(b => resolveToken(b, sourceFile)) .filter(b => { return usefulTypes.has(b.kind); }); } @Directive({ selector: '[slidesMonacoJsPosition]' }) export class MonacoJsPositionDirective implements AfterViewInit { @Output() slidesMonacoJsPosition = new EventEmitter(); lastResult: string; constructor( @Self() @Optional() private editorInjector: CodeDemoEditorInjector ) {} ngAfterViewInit() { const editor = this.editorInjector.editor; editor.onDidChangeCursorPosition(({ position }) => { const model = editor.getModel(); const value = model.getValue(); const sourceFile = ts.createSourceFile( 'file.ts', value, ts.ScriptTarget.ES2015, /*setParentNodes */ true ); const offset = model.getOffsetAt(position); let token = (ts as any).getTokenAtPosition(sourceFile, offset); const blocks = [token]; while (token.parent && token.kind) { token = token.parent; blocks.push(token); } const processedBlocks = processBlocks(blocks, sourceFile); const result = serializeBlocks(processedBlocks); // If we're in the same path and value is the same, let's not update const key = result + value; if (this.lastResult !== key) { this.lastResult = key; this.slidesMonacoJsPosition.emit({ type: 'ts', blocks: processedBlocks }); } }); } } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/monaco-directives/monaco-load-answer.directive.ts ================================================ import { Directive, EventEmitter, Optional, Output, Self } from '@angular/core'; import { CodeDemoEditorInjector } from '@codelab/code-demos/src/lib/code-demo-editor/code-demo-editor.injector'; @Directive({ selector: '[slidesMonacoLoadAnswer]', exportAs: 'MonacoWatLoadAnswer' }) export class MonacoWatLoadAnswerDirective { @Output() slidesMonacoLoadAnswer = new EventEmitter(); constructor( @Self() @Optional() private editorInjector: CodeDemoEditorInjector ) {} loadAnswer(config: any) { const model = this.editorInjector.editor.getModel(); const value = model.getValue(); const newValue = value.replace(config.originalCode, config.answer); model.setValue(newValue); this.slidesMonacoLoadAnswer.emit(); const pos = model.getPositionAt(newValue.indexOf(config.answer) + 20); this.editorInjector.editor.setPosition(pos); this.editorInjector.editor.focus(); } } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/monaco-directives/monaco-scrolling.directive.ts ================================================ import { Directive, Input, OnChanges, Optional, Self } from '@angular/core'; import { CodeDemoEditorInjector } from '@codelab/code-demos/src/lib/code-demo-editor/code-demo-editor.injector'; import { findPosition } from '@codelab/code-demos/src/lib/code-demo-editor/utils/utils'; @Directive({ selector: '[slidesMonacoScrolling]' }) export class MonacoScrollingDirective implements OnChanges { @Input() slidesMonacoScrolling = ''; lastValue: string; constructor( @Self() @Optional() private editorInjector: CodeDemoEditorInjector ) {} ngOnChanges(changes) { const editor = this.editorInjector.editor; if ( editor && changes.slidesMonacoScrolling && changes.slidesMonacoScrolling.currentValue !== this.lastValue ) { this.lastValue = this.slidesMonacoScrolling; if (this.slidesMonacoScrolling) { const range = findPosition( editor.getModel().getValue(), this.slidesMonacoScrolling ); // This does not really work editor.revealRangeInCenter({ startColumn: range.indexStart, endColumn: range.indexEnd, startLineNumber: range.lineStart, endLineNumber: range.lineEnd }); } } } } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/monaco-directives/monaco-wat-position.directive.spec.ts ================================================ import { MonacoWatPositionDirective } from './monaco-cursor-position.directive'; describe('MonacoCursorPositionDirective', () => { it('should create an instance', () => { const directive = new MonacoWatPositionDirective(); expect(directive).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/monaco-directives/monaco-wat-position.directive.ts ================================================ import { AfterViewInit, Directive, EventEmitter, Optional, Output, Self } from '@angular/core'; import { CodeDemoEditorInjector } from '@codelab/code-demos/src/lib/code-demo-editor/code-demo-editor.injector'; import { extractBlocks, populateBlocks, serializeBlocks } from '../../utils'; import { CodePath } from './common'; @Directive({ selector: '[slidesMonacoWatPosition]' }) export class MonacoWatPositionDirective implements AfterViewInit { @Output() slidesMonacoWatPosition = new EventEmitter(); lastResult: string; constructor( @Self() @Optional() private editorInjector: CodeDemoEditorInjector ) {} ngAfterViewInit() { const editor = this.editorInjector.editor; editor.onDidChangeCursorPosition(x => { const position = x.position; const model = editor.getModel(); const offset = model.getOffsetAt(position); const value = model.getValue(); const textBefore = value.slice(0, offset + 1); const textAfter = value.slice(offset); const blocks = populateBlocks(extractBlocks(textBefore, textAfter)); const result = serializeBlocks(blocks); // If we're in the same path and value is the same, let's not update const key = result + value; if (this.lastResult !== key) { this.lastResult = key; this.slidesMonacoWatPosition.emit({ type: 'wat', blocks }); } }); } } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/runners/wasm-test-runner/runner.js ================================================ async function run(code, { args, imports, name, memory }) { // imports.config.log = console.log; const program = await WebAssembly.instantiate(code, imports); if (memory) { if (!program.instance.exports.memory) { throw new Error( 'This test expects memory to be exported from WebAssembly, but none was exported.' ); } const mem = new Uint32Array(program.instance.exports.memory.buffer); for (let m = 0; m < memory.length; m++) { mem[m] = memory[m]; } } if (imports) { Object.entries(imports.config).map(([key, value]) => { if (program.instance.exports[key]) { program.instance.exports[key].value = value; } }); } const result = program.instance.exports[name](...args); return { result, exports: program.instance.exports }; } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/runners/wasm-test-runner/wasm-test-runner.component.html ================================================
      {{ config.name }}({{ test.args }}) {{ test.memory }}
      result: {{ test.result.value.result }}
      expected: {{ test.output }}
      result: {{ test.actualMemory }} expected: {{ test.expectedMemory }}
      ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/runners/wasm-test-runner/wasm-test-runner.component.scss ================================================ .indicator { width: 20px; height: 20px; margin-top: 5px; margin-right: 10px; } .test-result { display: flex; margin-bottom: 10px; } slides-viz { display: block; margin-top: 20px; } .call { font-size: 20px; } .result-expected { font-size: 20px; } .focused { .call { font-size: 3vw; } .result-expected { font-size: 2vw; } .indicator { width: 3vw; height: 3vw; margin-top: 5px; margin-right: 10px; } } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/runners/wasm-test-runner/wasm-test-runner.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { WasmTestRunnerComponent } from './wasm-test-runner.component'; describe('WasmTestRunnerComponent', () => { let component: WasmTestRunnerComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [WasmTestRunnerComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(WasmTestRunnerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/runners/wasm-test-runner/wasm-test-runner.component.ts ================================================ import { Component, Input, OnChanges } from '@angular/core'; import { forkJoin, Observable, Subject } from 'rxjs'; import { Result, RunResult, WebAssemblyService } from '../../web-assembly.service'; import { extractAllFunctions, extractFunction, extractFunctionWithDependencies, extractGlobals, extractTypeCode, generateWatTestCode, hasMemoryCalls, hasTableCalls, hasTypeCalls, populateTestCode, prepareTableCode } from '../../../utils'; import { wasmAnswers } from '../../../webassembly.component'; declare const require; interface TestResult { pass: boolean; result: Result; isFirstFailed: boolean; } interface TestConfig { type: string; name: string; tests: any[]; } export interface WebAssemblyTestConfig extends TestConfig { highlights: string[]; mode: string; originalCode: string; globals: string[]; table?: string; answer?: string; code: { wat: string; js: string; }; } function verifyGlobalsInTests(tests, globals) { if (globals.length) { tests.forEach(test => { if (!test.imports) { throw new Error( 'No imports present in the test. Function being tested expect the following: ' + globals.join(',') ); } if (!test.imports.config) { throw new Error('config property is missing on imports'); } for (const g of globals) { if (!(g in test.imports.config)) { throw new Error('input is not present: ' + g); } } }); } } function testsHaveMemory(config: TestConfig) { return config.tests.some(t => t.memory); } function passOrNot(m: any, blockCode, allCode) { if (m.type === 'func') { return new Set(extractAllFunctions(allCode)).has(m.name); } if (m.type === 'module') { return !!allCode.match(/\(module/); } if (m.type === 'table') { return !!allCode.match(/\(table/); } if (m.type === 'elem') { return !!allCode.match(/\(elem/); } if (m.type === 'global') { return !!allCode.match(new RegExp('global\\s*\\$' + m.name)); } if (m.type === 'global.rowSize') { // (import "config" "rowSize" (global $rowSize i32)) return !!allCode.match(/\(global \$rowSize i32\)/); } if (m.type === 'global.step') { // (global $step (export "step") (mut i32) (i32.const 1)) return !!allCode.match(/\(export "step"\)/); } if (m.type === 'memory') { return !!allCode.match(new RegExp('memory')); } } export function webAssemblyModuleContentHandler( config: any, blockCode: string, allCode: string ) { let hasUpassed = false; const milestones = config.milestones.map(m => { const pass = passOrNot(m, blockCode, allCode); let firstFailed = false; if (!pass && !hasUpassed) { hasUpassed = true; firstFailed = true; } return { firstFailed, pass, ...m }; }); return { mode: config.type, ...config, milestones }; } export function webAssemblyTestHandler( config: TestConfig, blockCode: string, allCode: string ): WebAssemblyTestConfig { const originalCode = extractFunction(config.name, allCode); const funcCode = extractFunctionWithDependencies(config.name, allCode, [ config.name ]); const globals = extractGlobals(funcCode, allCode); const hasMemory = testsHaveMemory(config) || hasMemoryCalls(funcCode); const table = hasTableCalls(funcCode, config) ? prepareTableCode(allCode) : ''; const types = hasTypeCalls(funcCode) ? extractTypeCode(allCode) : ''; verifyGlobalsInTests(config.tests, globals); const wat = generateWatTestCode({ globals, code: funcCode, name: config.name, hasMemory, types }); const answer = wasmAnswers.get(config.name) as string; return { code: { wat, js: require('!!raw-loader!./runner.js') }, answer, globals, table, ...config, mode: 'test', highlights: originalCode, originalCode }; } @Component({ selector: 'slides-wasm-test-runner', templateUrl: './wasm-test-runner.component.html', styleUrls: ['./wasm-test-runner.component.scss'] }) export class WasmTestRunnerComponent implements OnChanges { readonly result$ = new Subject>(); @Input() config: any; focused; constructor(private readonly webAssemblyService: WebAssemblyService) {} isFocused(test) { return this.focused ? test === this.focused : test.isFirstFailed; } runTests() { this.focused = undefined; const { tests, code, name, allCode, table } = this.config as any; let hasFailures = false; const sources = (tests as any[]).map(test => { return new Observable(subscriber => { const wat = populateTestCode(code.wat, test, allCode, table); return this.webAssemblyService .run(wat, code.js, { args: test.args, imports: test.imports, memory: test.memory, name }) .subscribe(result => { let pass = false; if (result.type === 'result') { if ('output' in test) { pass = (result.value as RunResult).result === test.output; } if ('expectedMemory' in test) { const mem = new Uint32Array( (result.value as RunResult).exports.memory.buffer ); pass = test.expectedMemory.every((m, i) => mem[i] === m); test.actualMemory = mem.slice(0, test.expectedMemory.length); } } let isFirstFailed = false; if (!pass && !hasFailures) { hasFailures = true; isFirstFailed = true; } subscriber.next({ isFirstFailed, pass: pass, result, ...test }); subscriber.complete(); }); }); }); forkJoin(sources).subscribe(results => { const error = results.find(({ result }) => result.type === 'error'); if (error) { this.result$.next(error.result as any); return; } this.result$.next({ type: 'result', value: results }); }); } ngOnChanges() { this.runTests(); } } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/viz/grid/grid.component.css ================================================ ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/viz/grid/grid.component.html ================================================
      ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/viz/grid/grid.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { GridComponent } from './grid.component'; describe('GridComponent', () => { let component: GridComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [GridComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(GridComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/viz/grid/grid.component.ts ================================================ import { Component, Input, OnChanges, ViewChild } from '@angular/core'; export interface Figure { type?: 'rect' | 'circle'; x: number; y: number; color: string; text?: string; } export interface GridConfig { values: number[]; rowSize: number; showIndex?: boolean; extras?: Figure[] | any; } const fakeMemory = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]; interface DrawCellConfig extends Figure { context: any; size: number; } function drawCell({ context, size, x, y, color, text, type = 'rect' }: DrawCellConfig) { const gridShift = size; context.fillStyle = color; const x1 = gridShift + x * size; const y1 = gridShift + y * size; if (type === 'rect') { context.fillRect(x1, y1, size - 1, size - 1); } if (type === 'circle') { context.arc(x1, y1, size, 0, Math.PI * 2, 0); } if (text !== undefined) { context.fillStyle = 'black'; context.textAlign = 'center'; context.textBaseline = 'middle'; context.font = '20px "Helvetica Neue", sans-serif'; context.fillText(text, x1 + size / 2, y1 + size / 2); } } function resolveFuncOrValueForItem( funcOfValue: any, item, index, vizConfig, test, defaultValue ) { if (!funcOfValue) { return defaultValue; } if (typeof funcOfValue === 'function') { return funcOfValue(item, index, vizConfig, test); } return funcOfValue; } function resolveFuncOrValue(funcOfValue: any, vizConfig, defaultValue) { if (!funcOfValue) { return defaultValue; } if (Array.isArray(funcOfValue)) { return funcOfValue; } if (typeof funcOfValue === 'function') { return funcOfValue(vizConfig); } } @Component({ selector: 'slides-grid', templateUrl: './grid.component.html', styleUrls: ['./grid.component.css'] }) export class GridComponent implements OnChanges { @ViewChild('canvas', { static: true }) canvas; @Input() test: any; async ngOnChanges(changes) { const size = 40; const canvas = this.canvas.nativeElement; const context = canvas.getContext('2d'); context.clearRect(0, 0, canvas.width, canvas.height); const config = this.test.viz; const memory = resolveFuncOrValue(config.memory, this.test, fakeMemory); for (let i = 0; i < memory.length; i++) { const x = i % config.rowSize; const y = Math.floor(i / config.rowSize); const color = resolveFuncOrValueForItem( config.color, memory[i], i, config, this.test, '#ddd' ); const text = resolveFuncOrValueForItem( config.text, memory[i], i, config, this.test, '' ); drawCell({ context, size, x, y, color, text }); } const extras = resolveFuncOrValue(config.extras, this.test, []); for (const s of extras) { drawCell({ context, size, ...s }); } } } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/viz/viz.component.css ================================================ td { font-size: 30px; } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/viz/viz.component.html ================================================
      X << 2 = X00 +
      Y << 1 = Y0 +
      Z Z
      XYZ
      ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/viz/viz.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { VizComponent } from './viz.component'; describe('VizComponent', () => { let component: VizComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [VizComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(VizComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/viz/viz.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; import { WebAssemblyTestConfig } from '../runners/wasm-test-runner/wasm-test-runner.component'; import { GridConfig } from './grid/grid.component'; @Component({ selector: 'slides-viz', templateUrl: './viz.component.html', styleUrls: ['./viz.component.css'] }) export class VizComponent implements OnInit { @Input() config: WebAssemblyTestConfig; @Input() test; ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/viz/viz.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { VizComponent } from './viz.component'; import { GridComponent } from './grid/grid.component'; import { CellularAutomationModule } from '../../../cellular-automation/cellular-automation.module'; @NgModule({ declarations: [VizComponent, GridComponent], exports: [VizComponent], imports: [CommonModule, CellularAutomationModule] }) export class VizModule {} ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/wasm-contents/wasm-contents.component.css ================================================ .indicator { width: 20px; height: 20px; margin-top: 5px; margin-right: 10px; } .test-result { display: flex; } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/wasm-contents/wasm-contents.component.html ================================================
      {{ m.name }}
      {{ m.name }}
      ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/wasm-contents/wasm-contents.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { WasmContentsComponent } from './wasm-contents.component'; describe('WasmContentsComponent', () => { let component: WasmContentsComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [WasmContentsComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(WasmContentsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/wasm-contents/wasm-contents.component.ts ================================================ import { Component, EventEmitter, Input, Output } from '@angular/core'; function genGlobalStep() { return { answer: `global $rowSize i32)) (global $step (export "step") (mut i32) (i32.const 1)) `, originalCode: /global \$rowSize i32\)\)/ }; } function genMemory(name) { return { answer: `global $rowSize i32)) (memory 1) (export "memory" (memory 0)) `, originalCode: /global \$rowSize i32\)\)/ }; } function genTable() { return { answer: `(memory 0)) (table 8 anyfunc) (type $return_i32 (func (result i32))) `, originalCode: /\(memory 0\)\)/ }; } function genElem() { return { answer: `(table 8 anyfunc) (func $enable (result i32) (i32.const 1) ) (func $disable (result i32) (i32.const 0) ) (elem (i32.const 0) $enable ;; 000 $enable ;; 001 $enable ;; 010 $enable ;; 011 $enable ;; 100 $enable ;; 101 $enable ;; 110 $enable ;; 111 ) `, originalCode: /\(table 8 anyfunc\)/ }; } function genRowSize() { return { answer: `(module (import "config" "rowSize" (global $rowSize i32)) `, originalCode: /\(module/ }; } function genModule() { return { answer: `(module ) `, originalCode: /^/ }; } function genFuncName(name) { return { answer: ` (func $${name} (result i32) ) ) `, originalCode: /\)\s*$/ }; } @Component({ selector: 'slides-wasm-contents', templateUrl: './wasm-contents.component.html', styleUrls: ['./wasm-contents.component.css'] }) export class WasmContentsComponent { @Input() config: any; @Output() loadAnswer = new EventEmitter(); loadFunction(m) { if (m.type === 'func') { this.loadAnswer.emit({ ...genFuncName(m.name), ...m }); } if (m.type === 'module') { this.loadAnswer.emit({ ...genModule(), ...m }); } if (m.type === 'global.step') { this.loadAnswer.emit({ ...genGlobalStep(), ...m }); } if (m.type === 'table') { this.loadAnswer.emit({ ...genTable(), ...m }); } if (m.type === 'elem') { this.loadAnswer.emit({ ...genElem(), ...m }); } if (m.type === 'global.rowSize') { this.loadAnswer.emit({ ...genRowSize(), ...m }); } if (m.type === 'memory') { this.loadAnswer.emit({ ...genMemory(m.name), ...m }); } } } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/web-assembly.service.spec.ts ================================================ import { TestBed } from '@angular/core/testing'; import { WebAssemblyService } from './web-assembly.service'; describe('WebAssemblyService', () => { beforeEach(() => TestBed.configureTestingModule({})); it('should be created', () => { const service: WebAssemblyService = TestBed.inject(WebAssemblyService); expect(service).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/web-assembly.service.ts ================================================ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; declare const require; // TODO(kirjs): Find a better way. const wabt = (function() { // Needed to make umd properly export stuff. const exports = {}; const module = {}; const code = require('!raw-loader!wabt'); return eval(code)(); })(); export interface Result { type: 'error' | 'result'; value: T | string; error?: string; } function wat2wasm(wat) { const module = wabt.parseWat('main.wasm', wat); const binary = module.toBinary({ log: false, canonicalize_lebs: false, relocatable: false, write_debug_names: false }); return binary.buffer; } export interface RunConfig { args: any[]; imports: any; name: string; memory?: number[]; } export interface RunResult { result: number; exports: { [key: string]: any }; } @Injectable({ providedIn: 'root' }) export class WebAssemblyService { constructor() {} run(wat: string, js: string, wasmConfig: RunConfig) { return new Observable>(subscriber => { try { const wasm = wat2wasm(wat); const setResult = (result: RunResult) => { subscriber.next({ type: 'result', value: result }); subscriber.complete(); }; const setError = (error: string) => { subscriber.next({ type: 'error', value: error }); subscriber.complete(); }; eval(` (async function(){ try { const code = new Uint8Array([${wasm.toString()}]).buffer; ${js} setResult(await run(code, wasmConfig)); } catch(e){ setError(e.message); } }()) `); } catch (e) { subscriber.next({ type: 'error', value: e.message }); subscriber.complete(); } }); } saveWasmFile(code: string) { function saveByteArray(name, byte) { const blob = new Blob([byte], { type: 'application/wasm' }); const link = document.createElement('a'); link.href = window.URL.createObjectURL(blob); link.download = name; link.click(); } saveByteArray('result.wasm', wat2wasm(code)); } } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/webassembly-code-mode/webassembly-code-mode.component.css ================================================ :host { position: fixed; right: 0; top: 0; background-color: #444; color: #fff; width: 400px; padding: 10px 20px; bottom: 0; overflow: scroll; transition: width 200ms; } .back { margin-bottom: 10px; } .has-meta { text-decoration: underline; } .side { display: flex; flex-direction: column; overflow: hidden; } .content { overflow: visible; margin-bottom: 200px; } h2 { font-size: 20px; } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/webassembly-code-mode/webassembly-code-mode.component.html ================================================

      {{ selectedMode.name }}

      {{ selectedMode.description }}

      ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/webassembly-code-mode/webassembly-code-mode.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { WebassemblyCodeModeComponent } from './webassembly-code-mode.component'; describe('WebassemblyCodeModeComponent', () => { let component: WebassemblyCodeModeComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [WebassemblyCodeModeComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(WebassemblyCodeModeComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/webassembly-code-mode/webassembly-code-mode.component.ts ================================================ import { Component, EventEmitter, HostBinding, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; declare const require; @Component({ selector: 'slides-webassembly-code-mode', templateUrl: './webassembly-code-mode.component.html', styleUrls: ['./webassembly-code-mode.component.css'] }) export class WebassemblyCodeModeComponent implements OnChanges { @HostBinding('style.width.px') width = 0; @Input() code: any; @Input() sideBarBlocks: any[]; @Output() wasmSelectionHighlight = new EventEmitter(); @Output() loadAnswer = new EventEmitter(); wat: string; js: string; mode = 'getIndex'; state: any; selectedMode = {}; private blocks: any[]; constructor() {} updateCode() { if (this.code) { this.wat = this.code.wat; this.js = this.code.js; } } ngOnChanges(changes: SimpleChanges) { if ('sideBarBlocks' in changes) { this.blocks = this.sideBarBlocks || []; const block = this.blocks.find(b => !!b.meta); this.selectedMode = {}; if (block) { this.selectedMode = block.meta; } this.width = Object.keys(this.selectedMode).length > 0 ? 400 : 0; } if (this.code) { this.updateCode(); } } loadAnswerFromConfig(selectedMode: any) { this.loadAnswer.emit(selectedMode); } } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/webassembly-playground.component.html ================================================ ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/webassembly-playground.component.scss ================================================ :host { display: grid; grid-template-columns: 2fr 1fr; grid-template-rows: auto auto; height: 100%; width: 100%; grid-template-areas: 'wa result' 'js result'; } .runner { position: fixed; right: 0; top: 0; } .webassembly-code { grid-area: wa; } .js-code { grid-area: js; } ::ng-deep { .has-highlight { .view-lines span:not(.highlighted-code) { color: #777; } } .highlighted-code { } } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/webassembly-playground.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { WebassemblyPlaygroundComponent } from './webassembly-playground.component'; describe('WebassemblyPlaygroundComponent', () => { let component: WebassemblyPlaygroundComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [WebassemblyPlaygroundComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(WebassemblyPlaygroundComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/webassembly-playground.component.ts ================================================ import { Component, forwardRef, Input } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { CodeHelperBlock, CodePath, getCodeBlockHandler } from './monaco-directives/common'; interface WebassemblyPlaygroundInputs { wat: string; js: string; } @Component({ selector: 'kirjs-webassembly-playground', templateUrl: './webassembly-playground.component.html', styleUrls: ['./webassembly-playground.component.scss'], providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => WebassemblyPlaygroundComponent), multi: true } ] }) export class WebassemblyPlaygroundComponent implements ControlValueAccessor { @Input() modeConfig = {}; @Input() fontSize = 30; code: WebassemblyPlaygroundInputs; wasmSelectionHighlight: string; selectedMode = {}; sideBarBlocks: CodeHelperBlock[]; private onChange: (code: WebassemblyPlaygroundInputs) => void; registerOnChange( onChange: (code: WebassemblyPlaygroundInputs) => void ): void { this.onChange = onChange; } registerOnTouched(fn: any): void {} setDisabledState(isDisabled: boolean): void {} writeValue(code: WebassemblyPlaygroundInputs): void { this.code = code; } selectFunction(path: CodePath) { // Last block contains all the code of the module if (path.blocks.length === 0) { return; } const allCode = path.blocks[path.blocks.length - 1].code; this.sideBarBlocks = path.blocks.map(b => { const langConfig = this.modeConfig[path.type]; if (!langConfig) { return { ...b }; } let meta = langConfig[b.type]; const name = b.name || 'default'; if (meta && meta[name]) { meta = meta[name]; meta.name = b.name; meta.type = b.type; const handler = meta.handler || getCodeBlockHandler(path.type, b.type) || (a => a); meta = handler(meta, b.code, allCode); meta.allCode = allCode; } return { ...b, meta }; }); const block = this.sideBarBlocks.find(b => !!b.meta); this.selectedMode = {}; if (block) { // TODO(kirjs): Uncommit // this.selectedMode = block.meta; } } update() { this.code = { ...this.code }; this.onChange(this.code); } } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/webassembly-runner/webassembly-runner.component.css ================================================ :host { background-color: #444444; height: 100%; padding: 10px; display: block; } .result { font-size: 20px; padding: 20px; width: 20px; text-align: center; position: fixed; right: 0; top: 0; background: #007302; color: #fff; opacity: 0.9; } .error { font-size: 20px; padding: 20px; width: 200px; text-align: center; position: fixed; right: 0; top: 0; background: #ff4e3d; color: #fff; opacity: 0.9; } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/webassembly-runner/webassembly-runner.component.html ================================================
      {{ result.value }}
      ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/webassembly-runner/webassembly-runner.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { WebassemblyRunnerComponent } from './webassembly-runner.component'; describe('WebassemblyRunnerComponent', () => { let component: WebassemblyRunnerComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [WebassemblyRunnerComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(WebassemblyRunnerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/webassembly-runner/webassembly-runner.component.ts ================================================ import { Component, Input, OnChanges, ViewChild } from '@angular/core'; import { Subject } from 'rxjs'; import { Result, RunResult, WebAssemblyService } from '../web-assembly.service'; @Component({ selector: 'kirjs-webassembly-runner', templateUrl: './webassembly-runner.component.html', styleUrls: ['./webassembly-runner.component.css'] }) export class WebassemblyRunnerComponent implements OnChanges { @Input() webAssemblyCode: string; @Input() jsCode: string; @Input() width = 400; @Input() height = 1000; @ViewChild('canvas', { static: true }) canvas; readonly result$ = new Subject>(); constructor(private readonly webAssemblyService: WebAssemblyService) {} async ngOnChanges(changes) { const canvas = this.canvas.nativeElement; canvas.getContext('2d').clearRect(0, 0, canvas.width, canvas.height); this.webAssemblyService .run(this.webAssemblyCode, this.jsCode, canvas) .subscribe(this.result$); } } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly-playground/webassembly-runner/webassembly-runner.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { WebassemblyRunnerComponent } from './webassembly-runner.component'; @NgModule({ declarations: [WebassemblyRunnerComponent], exports: [WebassemblyRunnerComponent], imports: [CommonModule] }) export class WebassemblyRunnerModule {} ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly.component.html ================================================

      Cellular Automata

      Web assembly

      @kirjs

      Previously In @BibyDigital's talk...

      (Or WebAssembly in 15 seconds)

      🔥 Web Assembly 🔥

      Not exactly an assembly

      Also not only for web

      Web Assembly

      Intermediate representation

      Compile from (almost) any typed language

      Run in your browser, from other language, or in a cloud...

      Web Assembly can only operate on 4 types

      • i32
      • i64
      • f32
      • f64

      Web Assembly

      Has a binary representation

      {{ binaryc.binary.slice(0, 400) }}

      Web Assembly

      ...and a text representation. It is stack based

      Cellular Automata

      Grid

      Rules

      Grid
      {{ board1.playing ? '❚❚' : '▶' }}

      Rules

      Grid
      {{ board1.playing ? '❚❚' : '▶' }}

      Rules

      Grid
      {{ board1.playing ? '❚❚' : '▶' }}

      Rule 30:

      Book rec #2:

      Surely You're Joking, Mr. Feynman! by Richard Feynman

      Well, we'd just been crawling around the floor—with help from some other people—trying to use meter rules to measure some feature of a giant printout of it. And Feynman took me aside, rather conspiratorially, and said, "Look, I just want to ask you one thing: how did you know rule 30 would do all this crazy stuff?" "You know me," I said. "I didn't. I just had a computer try all the possible rules. And I found it." "Ah," he said, "now I feel much better. I was worried you had some way to figure it out."

      Rule 30

      Grid
      {{ board1.playing ? '❚❚' : '▶' }}

      Let's build it in WebAssembly!

      3 minute intro

      Into stack-based programming

      Stack is a data structure, serving as a collection. Here's a stack of fruit:

      All operations are possible only with the last element of the stack.

      First operation is called pop, it removes the last element.

      The other operation is push. It adds an element on top of the stack.

      Some implementations also have a peek operation. It tells you what top element on the stack is. You can try it below:

      Congrats! It is a 🍋

      Let's practice!

      Use the commands you know already to get a stack of:

      Stack machine is one of the fundamental principles used in JVM Java Byte Code, WebAssembly byte code

      Stack machine offers various instructions that can pop and then push multiple element on the stack

      For example (🌲🍏)=>🍍 takes two elements from the stack (Pine and Apple) and pushes a 🍍 back

      Now use the instructions below to prepare a lemonade drink:

      Coding time! The plan:

      cambridge

      Thanks

      Stefan Fejes

      ArmadaJS team

      Cellular Automata

      Web assembly

      @kirjs

      ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly.component.scss ================================================ :host ::ng-deep .slide.slide > div.center { display: flex; align-items: center; justify-content: center; font-size: 40px; padding: 0 20vw; p { font-weight: 300; } } ::ng-deep { font-weight: 300; } .button-wrapper { margin: 20px 0; } .rules { display: flex; .rule { .arrow { margin-bottom: 8px; } } } .grid-header { display: flex; margin-top: 20px; .play { margin-left: 8px; } } .img { background-size: cover; background-repeat: no-repeat; background-position: center center; box-shadow: 0 0 10px #444 inset; } .img.milica { background-image: url('./pics/milica.jpg'); margin-top: 30px; margin-bottom: 50px; width: 400px; height: 400px; border-radius: 50%; } .img.nks { background-image: url('./pics/nks.jpg'); margin-top: 30px; margin-bottom: 50px; width: 800px; height: 800px; } .img.feynman { background-image: url('./pics/feynman.png'); margin-top: 30px; margin-bottom: 50px; width: 800px; height: 800px; } .img.joking { background-image: url('./pics/joking.jpg'); margin-top: 30px; margin-bottom: 50px; width: 800px; height: 800px; } .key-items { display: flex; text-align: center; .item { flex: 1; font-size: 40px; } } .img-30 { background-image: url('./pics/30.png'); margin-top: 30px; margin-bottom: 50px; width: 800px; height: 800px; } .img-30-2 { background-image: url('./pics/30-2.png'); margin-top: 30px; margin-bottom: 50px; width: 800px; height: 800px; } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { WebassemblyComponent } from './webassembly.component'; describe('WebassemblyComponent', () => { let component: WebassemblyComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [WebassemblyComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(WebassemblyComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly.component.ts ================================================ import { Component, OnInit } from '@angular/core'; import './monaco-wat'; import { getIndex } from './tests/get-index'; import { rotate } from './tests/rotate'; import { shiftTests } from './tests/shift-tests'; import { getCellScoreTests } from './tests/get-cell-score'; import { addTests } from './tests/add-tests'; import { loadCellTests } from './tests/load-cell'; import { loadPreviousCellTests } from './tests/load-previous-cell'; import { storeCellTests } from './tests/store-cell-tests'; import { evolveCellTests } from './tests/evolve-cell'; import { enableTests } from './tests/enable-tests'; import { disableTests } from './tests/disable-tests'; import { evolveRowTests } from './tests/evolve-row'; import { evolveTests } from './tests/evolve'; import { extractAnswers } from './utils'; import { Level } from '../stack/stack-game/stack-game.component'; declare const require; export const wasmAnswers = extractAnswers( require('!!raw-loader!./samples/answer.wat') ); function generateArray(size = 89, callback) { const result = Array.from(new Array(size)).map((a, i) => 0); return (callback || (a => a))(result); } function inPlace(rule) { const map = (256 + rule) .toString(2) .substr(1) .split('') .map(Number) .reduce((a, v, i) => { a[(15 - i).toString(2).substr(1)] = v; return a; }, {}); function transformRow(row) { const result = []; for (let i = 0; i < row.length; i++) { const a1 = row[(row.length + i - 1) % row.length].toString(); const a2 = row[i].toString(); const a3 = row[(i + 1) % row.length].toString(); result[i] = [map[a1 + a2 + a3]]; } return result; } return function(grid) { return grid.map(transformRow); }; } function transform2d(rule) { const survive = rule.survive.reduce((a, v) => { a[v] = true; return a; }, {}); const born = rule.born.reduce((a, v) => { a[v] = true; return a; }, {}); return function(grid) { return grid.map((row, y) => { return row.map((cell, x) => { const px = (row.length + x - 1) % row.length; const nx = (x + 1) % row.length; const py = (grid.length + y - 1) % grid.length; const ny = (y + 1) % grid.length; const score = grid[py][px] + grid[py][x] + grid[py][nx] + grid[y][px] + grid[y][nx] + grid[ny][px] + grid[ny][x] + grid[ny][nx]; if ((cell === 0 && born[score]) || (cell === 1 && survive[score])) { return 1; } return 0; }); }); }; } function appendTransform(rule) { const transform = inPlace(rule); return function(grid) { const lastRow = grid[grid.length - 1]; grid.push(transform([lastRow])[0]); return grid; }; } const rand = Math.floor(Math.random() * 255); const randomRules = new Array(8).fill(0).reduce( (a, v, i) => { if (Math.random() < 0.3) { a.survive.push(i); } if (Math.random() < 0.3) { a.born.push(i); } return a; }, { survive: [], born: [] } ); const gameOfLifeRules = { survive: [2, 3], born: [3] }; const gameOfLife = transform2d(gameOfLifeRules); const randomPattern = new Array(30) .fill(0) .map(() => new Array(60).fill(0).map(() => (Math.random() < 0.3 ? 1 : 0))); const randomPatternSparce = new Array(30) .fill(0) .map(() => new Array(60).fill(0).map(() => (Math.random() < 0.04 ? 1 : 0))); const labyrynthRules = { survive: [0, 1, 2, 3], born: [1] }; const labRules4 = { survive: [0, 1, 2, 3, 4], born: [1] }; const labRules5 = { survive: [0, 1, 2, 3, 4, 5], born: [1] }; const labRules6 = { survive: [0, 1, 2, 3, 4, 5, 6], born: [1] }; @Component({ selector: 'kirjs-webassembly', templateUrl: './webassembly.component.html', styleUrls: ['./webassembly.component.scss'] }) export class WebassemblyComponent implements OnInit { patterns = { rule90Start: generateArray(89, a => { a[44] = 1; return a; }) }; fontSize = 30; examples = { wat: `(func $add (param $x i32) (param $y i32) (result i32) local.get $x local.get $y i32.add )`, intro: { inverse(pattern) { return pattern.map(line => line.map(cell => (cell + 1) % 2)); }, rand, inPlace16: inPlace(16), inPlace90: inPlace(90), twoD18: appendTransform(18), twoD30: appendTransform(30), twoD90: appendTransform(90), twoD110: appendTransform(110), twoDRand: appendTransform(rand), pattern: [ [0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0], [0, 0, 0, 1, 0, 0, 0, 0] ] }, life: { randomRules, random: transform2d(randomRules), gameOfLifeRules, gameOfLife, labyrynthRules: labyrynthRules, labyrynth: transform2d(labyrynthRules), labRules4, labyrynth4: transform2d(labRules4), labRules5, labyrynth5: transform2d(labRules5), labRules6, labyrynth6: transform2d(labRules6), pattern: { randomPattern, randomPatternSparce, stillLife: [ [0, 0, 0, 0], [0, 1, 1, 0], [0, 1, 1, 0], [0, 0, 0, 0] ] } } }; code = { add: { wat: `(module (func $add (param $x i32) (param $y i32) (result i32) ) ) `, js: require('!!raw-loader!./samples/base.js') }, simple: { wat: require('!!raw-loader!./samples/base.wat'), js: require('!!raw-loader!./samples/base.js') }, brIf: { wa: `(module (func $add (param $lhs i32) (param $rhs i32) (result i32) (local $l1 i32) i32.const 33 set_local $l1 block $lol i32.const 1 i32.const 1 i32.eq br_if $lol i32.const 22 set_local $l1 end get_local $l1 ) (export "add" (func $add)) ) ` } }; itIsALemon = false; addModeConfig = { wat: { func: { add: { description: 'Takes two numbers and adds them together', tests: addTests } } } }; modeConfig = { wat: { elem: { default: {} }, module: { default: { milestones: [ { type: 'func', name: 'rotate' }, { type: 'global.rowSize', name: 'rowSize' }, { type: 'func', name: 'getIndex' }, { type: 'global.step', name: 'step' }, { type: 'memory', name: 'memory' }, { type: 'func', name: 'loadCell' }, { type: 'func', name: 'storeCell' }, { type: 'func', name: 'shift' }, { type: 'func', name: 'loadPreviousCell' }, { type: 'func', name: 'getCellScore' }, { type: 'table', name: 'table' }, { type: 'elem', name: 'elem' }, { type: 'func', name: 'evolveCell' }, { type: 'func', name: 'evolveRow' }, { type: 'func', name: 'evolve' } ] } }, func: { add: { description: 'Takes two numbers and adds them together', tests: addTests }, rotate: { description: 'Takes an index, and rotates it to be within the range of the line', tests: rotate }, getIndex: { description: 'Takes X and Y coordinate and returns index in the memory.', tests: getIndex }, loadCell: { description: 'Takes a position, calculates appropriate index and retrieves the data from memory', tests: loadCellTests }, storeCell: { description: 'Stores single cell value im the memory', tests: storeCellTests }, shift: { description: 'Takes 3 0|1 value and generates 3 bit number merging them together', tests: shiftTests }, loadPreviousCell: { description: 'Loads previous cell', tests: loadPreviousCellTests }, getCellScore: { description: 'Looks for 3 cells before, and gets a number 0-7', tests: getCellScoreTests }, evolveCell: { description: 'Evolves single cell based on values of the previous cells', tests: evolveCellTests }, evolveRow: { description: 'Evolves the whole row based on valueds of the previous row', tests: evolveRowTests }, evolve: { description: 'Evolves the whole thing N generations', tests: evolveTests }, enable: { description: 'This should always return 1', tests: enableTests }, disable: { description: 'This should always return 0', tests: disableTests } } }, ts: { SourceFile: { default: {} }, FunctionDeclaration: { default: {} } } }; levels: Record = { push: { functions: [ { inputs: '', outputs: '🍏', name: 'push 🍏' }, { inputs: '', outputs: '🍋', name: 'push 🍋' } ], inputs: '', outputs: '🍏🍋🍏' }, pop: { functions: [ { inputs: '*', outputs: '', name: 'pop' } ], inputs: '🍏🍏🍏🍏🍏', outputs: '🍏' }, together: { functions: [ { inputs: '*', outputs: '', name: 'pop' }, { inputs: '', outputs: '🍓', name: 'push 🍓' }, { inputs: '', outputs: '🍋', name: 'push 🍋' } ], inputs: '🍏🍏', outputs: '🍓🍋' }, lemonade: { functions: [ { inputs: '', outputs: '💦' }, { inputs: '', outputs: '🍋' }, { inputs: '', outputs: '🍒' }, { inputs: '🍒💦🍋', outputs: '🍹' } ], inputs: '', outputs: '🍹' }, level1: { functions: [ { inputs: '', outputs: '🍏🍏' }, { inputs: '', outputs: '🍋' }, { inputs: '🍋🍋', outputs: '🍒' }, { inputs: '*', outputs: '', name: 'pop' } ], inputs: '🍏', outputs: '🍒' }, level2: { functions: [ { inputs: '', outputs: '🍏', name: 'push 🍏' }, { inputs: '🍏🍏', outputs: '🍋' }, { inputs: '🍋🍋', outputs: '🍒' }, { inputs: '*', outputs: '', name: 'pop' } ], inputs: '🍏', outputs: '🍒' } }; constructor() {} ngOnInit() {} } ================================================ FILE: apps/kirjs/src/app/modules/webassembly/webassembly.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { RouterModule } from '@angular/router'; import { WebassemblyComponent } from './webassembly.component'; import { WebassemblyPlaygroundComponent } from './webassembly-playground/webassembly-playground.component'; import { CodeDemoModule } from '@codelab/code-demos'; import { FormsModule } from '@angular/forms'; import { StackModule } from '../stack/stack.module'; import { WasmBinaryComponent } from './wasm-binary/wasm-binary.component'; import { BinaryViewModule } from '../binary/binary-view/binary-view.module'; import { BinaryInlineModule } from '../binary/binary-inline/binary-inline.module'; import { SyncDirectivesModule } from '@codelab/utils/src/lib/sync/directives/sync-directives.module'; import { WebassemblyCodeModeComponent } from './webassembly-playground/webassembly-code-mode/webassembly-code-mode.component'; import { MatButtonModule } from '@angular/material/button'; import { ErrorMessageComponent } from './webassembly-playground/error-message/error-message.component'; import { WasmTestRunnerComponent } from './webassembly-playground/runners/wasm-test-runner/wasm-test-runner.component'; import { MonacoScrollingDirective } from './webassembly-playground/monaco-directives/monaco-scrolling.directive'; import { MonacoJsPositionDirective } from './webassembly-playground/monaco-directives/monaco-js-position.directive'; import { MonacoWatPositionDirective } from './webassembly-playground/monaco-directives/monaco-wat-position.directive'; import { MonacoWatLoadAnswerDirective } from './webassembly-playground/monaco-directives/monaco-load-answer.directive'; import { VizModule } from './webassembly-playground/viz/viz.module'; import { WasmContentsComponent } from './webassembly-playground/wasm-contents/wasm-contents.component'; import { CaModule } from './ca/ca.module'; import { CellularAutomationModule } from '../cellular-automation/cellular-automation.module'; import { NewProgressBarModule } from '../ast/new-progress-bar/new-progress-bar.module'; import { FullScreenRunnerModule } from './full-screen-runner/full-screen-runner.module'; import { WebassemblyRunnerModule } from './webassembly-playground/webassembly-runner/webassembly-runner.module'; const routes = RouterModule.forChild(SlidesRoutes.get(WebassemblyComponent)); @NgModule({ declarations: [ WebassemblyComponent, WebassemblyPlaygroundComponent, WasmBinaryComponent, WebassemblyCodeModeComponent, ErrorMessageComponent, MonacoWatPositionDirective, WasmTestRunnerComponent, MonacoScrollingDirective, MonacoJsPositionDirective, MonacoWatLoadAnswerDirective, WasmContentsComponent ], exports: [], imports: [ StackModule, MatButtonModule, CommonModule, SlidesModule, CodeDemoModule, FormsModule, routes, BinaryViewModule, BinaryInlineModule, SyncDirectivesModule, VizModule, CaModule, CellularAutomationModule, NewProgressBarModule, FullScreenRunnerModule, WebassemblyRunnerModule ] }) export class WebassemblyModule {} ================================================ FILE: apps/kirjs/src/assets/.gitkeep ================================================ ================================================ FILE: apps/kirjs/src/assets/runner/index.html ================================================ ================================================ FILE: apps/kirjs/src/environments/environment.prod.ts ================================================ export const environment = { production: true, // Firebase firebaseConfig: { apiKey: 'AIzaSyBiY1Lg2RIcKtbgqzfE6Vrg28Zjal6ZWHs', authDomain: 'angular-presentation.firebaseapp.com', databaseURL: 'https://angular-presentation.firebaseio.com', projectId: 'angular-presentation', storageBucket: 'angular-presentation.appspot.com', messagingSenderId: '1087862173437', appId: '1:1087862173437:web:0bb7fe324b62580bb31894' } }; ================================================ FILE: apps/kirjs/src/environments/environment.ts ================================================ // This file can be replaced during build by using the `fileReplacements` array. // `ng build ---prod` replaces `environment.ts` with `environment.prod.ts`. // The list of file replacements can be found in `angular.json`. export const environment = { production: false, // Firebase firebaseConfig: { apiKey: 'AIzaSyBiY1Lg2RIcKtbgqzfE6Vrg28Zjal6ZWHs', authDomain: 'angular-presentation.firebaseapp.com', databaseURL: 'https://angular-presentation.firebaseio.com', projectId: 'angular-presentation', storageBucket: 'angular-presentation.appspot.com', messagingSenderId: '1087862173437', appId: '1:1087862173437:web:0bb7fe324b62580bb31894' } }; /* * In development mode, for easier debugging, you can ignore zone related error * stack frames such as `zone.run`/`zoneDelegate.invokeTask` by importing the * below file. Don't forget to comment it out in production mode * because it will have a performance impact when errors are thrown */ import 'zone.js/dist/zone-error'; // Included with Angular CLI. ================================================ FILE: apps/kirjs/src/index.html ================================================ Kirjs ================================================ FILE: apps/kirjs/src/locale/kirjs.ru.xtb ================================================ ]> Вступление @kirjs JavaScript ❤️ Бинарные Данные Двоичная система спрятана под большим количеством слоев абстракции В этом выступлении Давайте посмотрим что внутри <b><b>gif</b></b> Заголовок Содержит информацию о размерах картинки, наличии и конфигурации глобальной палитры, и прозрачности. Палитра (наличие и размер настраиваются в заголовке) Содержит проиндексированные цвета Расширения (необязательны) Код самой картинки Блок с графическими свйоствами картинки Настройки анимации Комментарии Конвертация из двоичной системы исчисления в десятичную с помощью JavaScript Конвертация из десятичной системы счисления в двоичную с помощью JavaScript Конвертация из двоичной системы счисления в шестнадцатиричную с помощью JavaScript Конвертация из шестнадцатиричной системы счисления в двоичную с помощью JavaScript ПЕЉМЕЊ!!! Получение charCode из строки Получение символа из charCode Парсим бинарный файл (с помощью <b><b>binary-parser</b></b>) Можем ли мы использовать <b><b>бинарные данные </b></b> вместо <b><b>JSON</b></b>? Размер после сериализации JSON Больше Бинарные данные Меньше Скорость сериализации Медленнее Быстрее Отладка Простота понимания Понятно без сторонних инструментов Необходимы специальные инструменты Интсрументы В JS работает из коробки Схема Нету заданной схемы Нужна четкая схема (чаще всего) Бесплатная проверка типов Бесплатная валидация Существующие решения <a><a>Protocol buffers</a></a> (Google) <a><a>Flatbuffers</a></a> (Google) <a><a>Thrift</a></a> (Apache/Facebook) Заголовки бинарных файлов Gif - GIF87a (или GIF89a) Jpeg - начинается с ‘FF D8‘ и заканчивается ‘FF D9' Java класс - CAFEBABE ZIP - начинается с ‘PK‘ (50 4B) PDF файлы начинаются с ‘%PDF‘ (25 50 44 46) PNG файлы начинаются с “\211 P N G \r \n 32 \n” (89 50 4E 47 0D 0A 1A 0A) HTTP2 - PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n Большая коллекция бинарных форматов https://formats.kaitai.io/ Давайте посмотрим как хранятся данные в JavaScript Вот так хранятся типизированные массивы в других языках Факты о формате Gif Размер картинки: 1х1 to 65535х65535 Цвета - 2-256 Можно получить полноцветную картинку если заморочиться (но не нужно) Количество кадров анимации - не ограничено Задержка анимации - 1/100 - 655 seconds Существовало расширение позволяющее накладывать текст на картинку Всего в стандарте 24 страницы (+12 аппендикс) JavaScript ❤️ Бинарные данные kirjs.com/binary/0 Спасибо @andrey_sitnik и @_bmsdave за ревью Это значение - всегда "GIF" Это всегда "87a" или "89a" Ширина картинки Высота картинки Флаг наличия глобальной палитры Глубина цвета в палитре (в битах) Флаг сортировки палитры по важности цвета. Нужен для экранов которые не поддерживают все 256 цветов, позволяет выбрать самые частые. Количество цветов в палитре (результат пропорционален двойке в указанной степени) Указывает индекс цвета в таблице, который будет использоваться в качестве прозрачного Пропорции пикселя Зарезервированные биты Действие после показа кадра: 0) Ничего не делать. 1) Оставить все как есть. 2) Восстановить фон. 3) Вернуть предыдущий кадр 4-7) Возможно будут заданы в будущем. Этот бит не используется, но в теории позволял картинке реагировать на действия пользователя. Флаг, значение которого позволяет включить/выключить прозрачность для последующего кадра Задержка анимации для следующего кадра Индекс прозрачного цвета Горизонтальный сдвиг кадра в пикселях Горизонтальные сдвиг кадра в пикселях Флаг, значение которого задает наличие локальной палитры Флаг включающий/выключающий чересстрочную развёртку Флаг, значение которого задает наличие локальной палитры Размер локальной палитры Обозначает раширение Netscape, значение всегда 0x01 Размер расширения в байтах Количество анимационных циклов Код картинки сжатые с помощью LZW Если вам сложно различить цифры ниже, садитесь ближе или откройте слайды: <b><b>kirjs.com/binary/0</b></b> Что внутри бинарных файлов (картинки, видео, медиа, zip и пр.)? Передача бинарных данных (вместо JSON) Как JavaScript хранит данные в памяти Чтобы понять бинарные данные нужна схема Бинарные данные 01101000 01101111 01101100 01111001 00101110 01101010 01110011 ??? Это строка в кодировке UTF-8 Данные в виде JSON (<b><b>{{codeLength}}{{codeLength}} байт</b></b> Бинарные данные (<b><b>{{binariesLength}}{{binariesLength}} байт</b></b> Схема (<b><b>{{schemaLength}}{{schemaLength}} bytes</b></b>) Непроиндексированная картинка Таблица цветов (Палитра) Проиндексированная картинка ================================================ FILE: apps/kirjs/src/locale/messages.xmb ================================================ ]> src/app/modules/binary/binary.component.html:10src/app/modules/binary/binary.component.html:588@kirjs src/app/modules/binary/binary.component.html:11️JavaScript ❤️ Binary src/app/modules/binary/binary.component.html:16,18 If you can't read the numbers below, move closer or go to <b><b>kirjs.com/binary/0</b></b> src/app/modules/binary/binary.component.html:25Binary is hidden behind many layers of abstraction src/app/modules/binary/binary.component.html:29In this talk: src/app/modules/binary/binary.component.html:31Binary in files (images, video, other media, zip) src/app/modules/binary/binary.component.html:32Binary for data transfer (instead of JSON) src/app/modules/binary/binary.component.html:33Javascript using binary in memory src/app/modules/binary/binary.component.html:41Let's see what's inside of <b><b>gif</b></b> src/app/modules/binary/binary.component.html:87src/app/modules/binary/binary.component.html:111 Binary data makes no sense without a schema src/app/modules/binary/binary.component.html:90src/app/modules/binary/binary.component.html:114Binary data src/app/modules/binary/binary.component.html:91src/app/modules/binary/binary.component.html:11501101000 01101111 01101100 01111001 00101110 01101010 01110011 src/app/modules/binary/binary.component.html:97src/app/modules/binary/binary.component.html:121src/app/modules/binary/binary.component.html:449Schema src/app/modules/binary/binary.component.html:98Lol, what's this? src/app/modules/binary/binary.component.html:122This is UTF-8 string! src/app/modules/binary/binary.component.html:167Header src/app/modules/binary/binary.component.html:169Contains size, global pallets specs, transparency. src/app/modules/binary/binary.component.html:171Palette (Optional, size defined ) src/app/modules/binary/binary.component.html:173Contains indexed colors src/app/modules/binary/binary.component.html:175Extensions (Optional) src/app/modules/binary/binary.component.html:178Actual image data src/app/modules/binary/binary.component.html:179Image Control src/app/modules/binary/binary.component.html:180Animation Control src/app/modules/binary/binary.component.html:181Comments src/app/modules/binary/binary.component.html:214Convert binary to decimal with JavaScript src/app/modules/binary/binary.component.html:219Convert decimal to binary src/app/modules/binary/binary.component.html:241Convert binary to hexadecimal with JavaScript src/app/modules/binary/binary.component.html:246Convert hexadecimal to binary with JavaScript src/app/modules/binary/binary.component.html:278ПЕЉМЕЊ!!! src/app/modules/binary/binary.component.html:293Get charcode from string src/app/modules/binary/binary.component.html:298Get letter from charcode src/app/modules/binary/binary.component.html:334Parsing binary(with <b><b>binary-parser</b></b>) src/app/modules/binary/binary.component.html:355Can we use <b><b>binary</b></b> instead of <b><b>JSON</b></b>? src/app/modules/binary/binary.component.html:368Serialized Size src/app/modules/binary/binary.component.html:372src/app/modules/binary/binary.component.html:391src/app/modules/binary/binary.component.html:410src/app/modules/binary/binary.component.html:432src/app/modules/binary/binary.component.html:452JSON src/app/modules/binary/binary.component.html:374Bigger src/app/modules/binary/binary.component.html:379src/app/modules/binary/binary.component.html:398src/app/modules/binary/binary.component.html:420src/app/modules/binary/binary.component.html:440src/app/modules/binary/binary.component.html:460Binary src/app/modules/binary/binary.component.html:381Smaller src/app/modules/binary/binary.component.html:388Serialization speed src/app/modules/binary/binary.component.html:393Slower src/app/modules/binary/binary.component.html:400Faster src/app/modules/binary/binary.component.html:407Debugging src/app/modules/binary/binary.component.html:413Easy to understand src/app/modules/binary/binary.component.html:414Human readable src/app/modules/binary/binary.component.html:422src/app/modules/binary/binary.component.html:442Requires special tooling src/app/modules/binary/binary.component.html:429Tooling src/app/modules/binary/binary.component.html:434For JS works out of the box src/app/modules/binary/binary.component.html:454No schema src/app/modules/binary/binary.component.html:462Needs a schema src/app/modules/binary/binary.component.html:463Comes with type checking src/app/modules/binary/binary.component.html:464Comes with validation src/app/modules/binary/binary.component.html:471Existing solutions src/app/modules/binary/binary.component.html:473<a><a>Protocol buffers</a></a> (Google) src/app/modules/binary/binary.component.html:474<a><a>Flatbuffers</a></a> (Google) src/app/modules/binary/binary.component.html:475<a><a>Thrift</a></a> (Apache/Facebook) src/app/modules/binary/binary.component.html:480File header constants src/app/modules/binary/binary.component.html:482Gif - GIF87a (or GIF89a) src/app/modules/binary/binary.component.html:483Jpeg - begin with ‘FF D8‘ and end with ‘FF D9' src/app/modules/binary/binary.component.html:484Java class - CAFEBABE src/app/modules/binary/binary.component.html:485ZIP files begin with ‘PK‘ (50 4B) src/app/modules/binary/binary.component.html:486PDF files start with ‘%PDF‘ (25 50 44 46) src/app/modules/binary/binary.component.html:487PNG image files begin with “\211 P N G \r \n 32 \n” (89 50 4E 47 0D 0A 1A 0A) src/app/modules/binary/binary.component.html:488HTTP2 - PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n src/app/modules/binary/binary.component.html:493Big library of binary formats src/app/modules/binary/binary.component.html:494https://formats.kaitai.io/ src/app/modules/binary/binary.component.html:499Let's look at memory management in JS src/app/modules/binary/binary.component.html:503Let's see how memory works in typed arrays src/app/modules/binary/binary.component.html:575Gif facts src/app/modules/binary/binary.component.html:577Image size: 1х1 to 65535х65535 src/app/modules/binary/binary.component.html:578Colors: 2 - 256 src/app/modules/binary/binary.component.html:579True color gifs are possible src/app/modules/binary/binary.component.html:580Max number of animation frames - unlimited src/app/modules/binary/binary.component.html:581Animation delay 1/100 - 655 seconds src/app/modules/binary/binary.component.html:582There's a plain text extension src/app/modules/binary/binary.component.html:58324 pages + 12 pages appendix in gif89 standard src/app/modules/binary/binary.component.html:589Binary ❤️ JavaScript src/app/modules/binary/binary.component.html:590kirjs.com/binary/0 src/app/modules/binary/binary.component.html:591Thanks @andrey_sitnik, @_bmsdave for review src/app/modules/binary/fake-gif/fake-gif.component.html:7This is always "GIF" src/app/modules/binary/fake-gif/fake-gif.component.html:8This is always "87a" or "89a" src/app/modules/binary/fake-gif/fake-gif.component.html:9src/app/modules/binary/fake-gif/fake-gif.component.html:43Width of the image src/app/modules/binary/fake-gif/fake-gif.component.html:10src/app/modules/binary/fake-gif/fake-gif.component.html:44Height of the image src/app/modules/binary/fake-gif/fake-gif.component.html:12Whether global palette is present src/app/modules/binary/fake-gif/fake-gif.component.html:13Number of bits per primary color available src/app/modules/binary/fake-gif/fake-gif.component.html:14Whether the palette is sorted src/app/modules/binary/fake-gif/fake-gif.component.html:15,17Specifies number of colors in the palette proportional a power of two. e.g. src/app/modules/binary/fake-gif/fake-gif.component.html:18,19If present specifies index of a color in the global color table that would be transparent src/app/modules/binary/fake-gif/fake-gif.component.html:20Ratio of the pixel src/app/modules/binary/fake-gif/fake-gif.component.html:22Reserved bits src/app/modules/binary/fake-gif/fake-gif.component.html:23,36Disposal Method - Indicates the way in which the graphic is to be treated after being displayed. Values : 0 - No disposal specified. The decoder is not required to take any action. 1 - Do not dispose. The graphic is to be left in place. 2 - Restore to background color. The area used by the graphic must be restored to the background color. 3 - Restore to previous. The decoder is required to restore the area overwritten by the graphic with what was there prior to rendering the graphic. 4-7 - To be defined. src/app/modules/binary/fake-gif/fake-gif.component.html:37Not used, the initial intention was to allow user interactions src/app/modules/binary/fake-gif/fake-gif.component.html:38Whether the frame should have a transparent color src/app/modules/binary/fake-gif/fake-gif.component.html:39Animation delay for next image src/app/modules/binary/fake-gif/fake-gif.component.html:40Optional transparent color index src/app/modules/binary/fake-gif/fake-gif.component.html:41Horizontal shift in pixels src/app/modules/binary/fake-gif/fake-gif.component.html:42Vertical shift in pixels src/app/modules/binary/fake-gif/fake-gif.component.html:45Whether the image has local palette src/app/modules/binary/fake-gif/fake-gif.component.html:46Indicates if the image is interlaced. src/app/modules/binary/fake-gif/fake-gif.component.html:47Whether local palette is sorted src/app/modules/binary/fake-gif/fake-gif.component.html:48Bucket of sizes of local palette. src/app/modules/binary/fake-gif/fake-gif.component.html:50,52Identifies the Netscape Looping Extension. This field contains the fixed value 0x01 src/app/modules/binary/fake-gif/fake-gif.component.html:53Size of the extension block in bytes src/app/modules/binary/fake-gif/fake-gif.component.html:54Number of animation loops src/app/modules/binary/fake-gif/fake-gif.component.html:55This is the actual image encoded with LZW src/app/modules/binary/json/json.component.html:3Data as JSON (<b><b>{{codeLength}}{{codeLength}} bytes</b></b>) src/app/modules/binary/json/json.component.html:10Binary data (<b><b>{{binariesLength}}{{binariesLength}} bytes</b></b>) src/app/modules/binary/json/json.component.html:34Schema (<b><b>{{schemaLength}}{{schemaLength}} bytes</b></b>) src/app/modules/binary/color-indexing/color-indexing.component.html:3Unindexed image src/app/modules/binary/color-indexing/color-indexing.component.html:13Color table (Palette) src/app/modules/binary/color-indexing/color-indexing.component.html:17Indexed image src/app/modules/react/react.component.html:5Introduction ================================================ FILE: apps/kirjs/src/main.ts ================================================ import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { KirjsModule } from './app/kirjs.module'; import { environment } from './environments/environment'; if (environment.production) { enableProdMode(); } platformBrowserDynamic() .bootstrapModule(KirjsModule) .catch(err => console.log(err)); ================================================ FILE: apps/kirjs/src/polyfills.ts ================================================ import 'zone.js/dist/zone'; // Included with Angular CLI. (window as any).Buffer = {}; (window as any).global = {}; import '@angular/localize/init'; ================================================ FILE: apps/kirjs/src/styles.css ================================================ @import '~@angular/material/prebuilt-themes/indigo-pink.css'; html, body { width: 100%; height: 100%; padding: 0; margin: 0; } ================================================ FILE: apps/kirjs/src/test.ts ================================================ // This file is required by karma.conf.js and loads recursively all the .spec and framework files import 'zone.js/dist/zone-testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; declare const require: any; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); // Then we find all the tests. const context = require.context('./', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context); ================================================ FILE: apps/kirjs/tsconfig.app.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "resolveJsonModule": true, "allowSyntheticDefaultImports": true, "esModuleInterop": true, "outDir": "../../dist/out-tsc", "types": [] }, "exclude": ["test.ts", "**/*.spec.ts"] } ================================================ FILE: apps/kirjs/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "types": ["jasmine", "node"] }, "angularCompilerOptions": { "enableIvy": true } } ================================================ FILE: apps/kirjs/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "types": ["jasmine", "node"] }, "files": ["src/test.ts", "src/polyfills.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: apps/kirjs/tslint.json ================================================ { "extends": "../../tslint.json", "linterOptions": { "exclude": ["src/**/*.json"] } } ================================================ FILE: apps/lis/browserslist ================================================ # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. # For additional information regarding the format and rule options, please see: # https://github.com/browserslist/browserslist#queries # You can see what browsers were selected by your queries by running: # npx browserslist > 0.5% last 2 versions Firefox ESR not dead not IE 9-11 # For IE 9-11 support, remove 'not'. ================================================ FILE: apps/lis/jest.config.js ================================================ module.exports = { name: 'lis', preset: '../../jest.config.js', coverageDirectory: '../../coverage/apps/lis', snapshotSerializers: [ 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 'jest-preset-angular/build/AngularSnapshotSerializer.js', 'jest-preset-angular/build/HTMLCommentSerializer.js' ] }; ================================================ FILE: apps/lis/src/app/app.component.css ================================================ /* * Remove template code below */ ================================================ FILE: apps/lis/src/app/app.component.html ================================================ ================================================ FILE: apps/lis/src/app/app.component.spec.ts ================================================ import { TestBed, async } from '@angular/core/testing'; import { AppComponent } from './app.component'; import { RouterTestingModule } from '@angular/router/testing'; describe('AppComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [RouterTestingModule], declarations: [AppComponent] }).compileComponents(); })); it('should create the app', () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app).toBeTruthy(); }); it(`should have as title 'lis'`, () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app.title).toEqual('lis'); }); it('should render title', () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.debugElement.nativeElement; expect(compiled.querySelector('.content span').textContent).toContain( 'lis app is running!' ); }); }); ================================================ FILE: apps/lis/src/app/app.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'codelab-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent { title = 'lis'; } ================================================ FILE: apps/lis/src/app/app.module.ts ================================================ import { APP_INITIALIZER, NgModule } from '@angular/core'; import { AngularFireModule } from '@angular/fire'; import { AngularFireAuthModule } from '@angular/fire/auth'; import { AngularFireDatabaseModule } from '@angular/fire/database'; import { BrowserModule } from '@angular/platform-browser'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { RouterModule } from '@angular/router'; import { monacoReady } from '@codelab/code-demos'; import { environment } from '../../../codelab/src/environments/environment'; import { AppComponent } from './app.component'; export const AngularFireApp = AngularFireModule.initializeApp( environment.firebaseConfig ); @NgModule({ declarations: [AppComponent], imports: [ BrowserModule, BrowserAnimationsModule, AngularFireDatabaseModule, AngularFireAuthModule, AngularFireApp, RouterModule.forRoot( [ { path: '', loadChildren: () => import('./modules/rxjs/rxjs.module').then(_ => _.RxjsModule) } ], { initialNavigation: 'enabled' } ) ], providers: [ { provide: APP_INITIALIZER, useValue: monacoReady, multi: true } ], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/lis/src/app/modules/rxjs/rxjs.component.css ================================================ ================================================ FILE: apps/lis/src/app/modules/rxjs/rxjs.component.html ================================================

      🥺 There is no active session

      here
      isAdmin
      isPresenting
      isViewing

      Hello

      • a
      • b
      ================================================ FILE: apps/lis/src/app/modules/rxjs/rxjs.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RxjsComponent } from './rxjs.component'; describe('RxjsComponent', () => { let component: RxjsComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [RxjsComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(RxjsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/lis/src/app/modules/rxjs/rxjs.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'codelab-rxjs', templateUrl: './rxjs.component.html', styleUrls: ['./rxjs.component.css'] }) export class RxjsComponent implements OnInit { code: Record = { 'index.html': 'Lol', 'bootstrap.ts': ` import { Injector } from '@angular/core'; console.log(11230); ` }; polls = [ { key: 'favorite', type: 'choice', question: 'Which framework do you use most at work?', options: [ 'Angular', 'AngularJS', 'React', 'Vue', 'Svelte', 'jQuery', 'Something else' ] }, { key: 'skill', type: 'choice', question: 'How well do you know angular?', options: [ 'Not at all', 'Somewhat', 'I can use it', 'Good', 'Really good', "I'm Minko Fluin" ] }, { key: 'build-dev', type: 'choice', question: 'how long does it take to rebuild your app in dev mode (and see the result via local dev server) - the total turnaround time', options: [ '< 1 second', '1 - 5 seconds', '5 - 10 seconds', '10 - 30 seconds', '30 - 60 seconds', '1-10 minutes', 'More than 10 minutes' ] }, { key: 'build-prod', type: 'choice', question: 'how long does it take to create a production build of your app', options: [ '< 1 second', '1 - 5 seconds', '5 - 10 seconds', '10 - 30 seconds', '30 - 60 seconds', '1-10 minutes', 'More than 10 minutes' ] }, { key: 'cli', type: 'choice', question: 'Which feature is NOT in CLI 8.3.0-next.2 ', answer: 'New command ng make-this-awesome', options: [ 'Redesigned default app', 'New command ng make-this-awesome', 'Faster builds with enabled differential loading', 'New command ng deploy' ] }, { key: 'tomorrow', type: 'choice', question: 'What is being released today?', answer: 'CLI 9.0.0-next.0 with Ivy by default', options: [ 'CLI 9.0.0-next.0 with Ivy by default', 'RxJS 8', 'React 16.12', 'Angular XS' ] }, { key: 'material', type: 'choice', question: 'Which feature was added to Angular CDK library 8.1.3 "gelatin-key" (2019-08-14)?', answer: '', options: [ 'Windows 95 theme support', 'Drag and drop', 'New material-fox component', 'New Clipboard service + directive' ] } ]; constructor() {} ngOnInit() {} } ================================================ FILE: apps/lis/src/app/modules/rxjs/rxjs.module.ts ================================================ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { FormsModule } from '@angular/forms'; import { RouterModule } from '@angular/router'; import { CodeDemoModule } from '@codelab/code-demos'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { SyncPollModule } from '@codelab/utils/src/lib/sync/components/poll/sync-poll.module'; import { SyncRegistrationModule } from '@codelab/utils/src/lib/sync/components/registration/sync-registration.module'; import { SyncDirectivesModule } from '@codelab/utils/src/lib/sync/directives/sync-directives.module'; import { SyncButtonModule } from '@codelab/utils/src/lib/sync/sync-button/sync-button.module'; import { RxjsComponent } from './rxjs.component'; const routes = RouterModule.forChild(SlidesRoutes.get(RxjsComponent)); @NgModule({ declarations: [RxjsComponent], imports: [ CommonModule, SlidesModule, routes, CodeDemoModule, FormsModule, SyncButtonModule, SyncRegistrationModule, SyncPollModule, SyncDirectivesModule ] }) export class RxjsModule {} ================================================ FILE: apps/lis/src/assets/.gitkeep ================================================ ================================================ FILE: apps/lis/src/environments/environment.prod.ts ================================================ export const environment = { production: true, // Firebase firebaseConfig: { apiKey: 'AIzaSyBiY1Lg2RIcKtbgqzfE6Vrg28Zjal6ZWHs', authDomain: 'angular-presentation.firebaseapp.com', databaseURL: 'https://angular-presentation.firebaseio.com', projectId: 'angular-presentation', storageBucket: 'angular-presentation.appspot.com', messagingSenderId: '1087862173437', appId: '1:1087862173437:web:0bb7fe324b62580bb31894' } }; ================================================ FILE: apps/lis/src/environments/environment.ts ================================================ // This file can be replaced during build by using the `fileReplacements` array. // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. // The list of file replacements can be found in `angular.json`. export const environment = { production: false, // Firebase firebaseConfig: { apiKey: 'AIzaSyBiY1Lg2RIcKtbgqzfE6Vrg28Zjal6ZWHs', authDomain: 'angular-presentation.firebaseapp.com', databaseURL: 'https://angular-presentation.firebaseio.com', projectId: 'angular-presentation', storageBucket: 'angular-presentation.appspot.com', messagingSenderId: '1087862173437', appId: '1:1087862173437:web:0bb7fe324b62580bb31894' } }; /* * For easier debugging in development mode, you can import the following file * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. * * This import should be commented out in production mode because it will have a negative impact * on performance if an error is thrown. */ // import 'zone.js/dist/zone-error'; // Included with Angular CLI. ================================================ FILE: apps/lis/src/index.html ================================================ Lis ================================================ FILE: apps/lis/src/main.ts ================================================ import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; if (environment.production) { enableProdMode(); } platformBrowserDynamic() .bootstrapModule(AppModule) .catch(err => console.error(err)); ================================================ FILE: apps/lis/src/polyfills.ts ================================================ /** * This file includes polyfills needed by Angular and is loaded before the app. * You can add your own extra polyfills to this file. * * This file is divided into 2 sections: * 1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers. * 2. Application imports. Files imported after ZoneJS that should be loaded before your main * file. * * The current setup is for so-called "evergreen" browsers; the last versions of browsers that * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera), * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile. * * Learn more in https://angular.io/guide/browser-support */ /*************************************************************************************************** * BROWSER POLYFILLS */ /** IE10 and IE11 requires the following for NgClass support on SVG elements */ // import 'classlist.js'; // Run `npm install --save classlist.js`. /** * Web Animations `@angular/platform-browser/animations` * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari. * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0). */ // import 'web-animations-js'; // Run `npm install --save web-animations-js`. /** * By default, zone.js will patch all possible macroTask and DomEvents * user can disable parts of macroTask/DomEvents patch by setting following flags * because those flags need to be set before `zone.js` being loaded, and webpack * will put import in the top of bundle, so user need to create a separate file * in this directory (for example: zone-flags.ts), and put the following flags * into that file, and then add the following code before importing zone.js. * import './zone-flags.ts'; * * The flags allowed in zone-flags.ts are listed here. * * The following flags will work for all browsers. * * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames * * in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js * with the following flag, it will bypass `zone.js` patch for IE/Edge * * (window as any).__Zone_enable_cross_context_check = true; * */ /*************************************************************************************************** * Zone JS is required by default for Angular itself. */ import 'zone.js/dist/zone'; // Included with Angular CLI. /*************************************************************************************************** * APPLICATION IMPORTS */ ================================================ FILE: apps/lis/src/styles.css ================================================ /* You can add global styles to this file, and also import other style files */ @import '~@angular/material/prebuilt-themes/indigo-pink.css'; html, body { width: 100%; height: 100%; padding: 0; margin: 0; } ================================================ FILE: apps/lis/src/test-setup.ts ================================================ import 'jest-preset-angular'; ================================================ FILE: apps/lis/tsconfig.app.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "types": [] }, "files": ["src/main.ts", "src/polyfills.ts"], "include": ["**/*.ts"], "exclude": ["src/test-setup.ts", "**/*.spec.ts"] } ================================================ FILE: apps/lis/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "types": ["node", "jest"] }, "include": ["**/*.ts"] } ================================================ FILE: apps/lis/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "module": "commonjs", "types": ["jest", "node"] }, "files": ["src/test-setup.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: apps/lis/tslint.json ================================================ { "extends": "../../tslint.json", "rules": { "directive-selector": [true, "attribute", "codelab", "camelCase"], "component-selector": [true, "element", "codelab", "kebab-case"] } } ================================================ FILE: apps/playground/browserslist ================================================ # This file is used by the build system to adjust CSS and JS output to support the specified browsers below. # For additional information regarding the format and rule options, please see: # https://github.com/browserslist/browserslist#queries # You can see what browsers were selected by your queries by running: # npx browserslist > 0.5% last 2 versions Firefox ESR not dead not IE 9-11 # For IE 9-11 support, remove 'not'. ================================================ FILE: apps/playground/jest.config.js ================================================ module.exports = { name: 'playground', preset: '../../jest.config.js', coverageDirectory: '../../coverage/apps/playground', snapshotSerializers: [ 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 'jest-preset-angular/build/AngularSnapshotSerializer.js', 'jest-preset-angular/build/HTMLCommentSerializer.js' ] }; ================================================ FILE: apps/playground/src/app/app.component.html ================================================ ================================================ FILE: apps/playground/src/app/app.component.scss ================================================ /* * Remove template code below */ :host { display: block; font-family: sans-serif; min-width: 300px; max-width: 1600px; margin: 50px auto; } .gutter-left { margin-left: 9px; } .col-span-2 { grid-column: span 2; } .flex { display: flex; align-items: center; justify-content: center; } header { background-color: #143055; color: white; padding: 5px; border-radius: 3px; } main { padding: 0 36px; } p { text-align: center; } h1 { text-align: center; margin-left: 18px; font-size: 24px; } h2 { text-align: center; font-size: 20px; margin: 40px 0 10px 0; } .resources { text-align: center; list-style: none; padding: 0; display: grid; grid-gap: 9px; grid-template-columns: 1fr 1fr; } .resource { color: #0094ba; height: 36px; background-color: rgba(0, 0, 0, 0); border: 1px solid rgba(0, 0, 0, 0.12); border-radius: 4px; padding: 3px 9px; text-decoration: none; } .resource:hover { background-color: rgba(68, 138, 255, 0.04); } pre { padding: 9px; border-radius: 4px; background-color: black; color: #eee; } details { border-radius: 4px; color: #333; background-color: rgba(0, 0, 0, 0); border: 1px solid rgba(0, 0, 0, 0.12); padding: 3px 9px; margin-bottom: 9px; } summary { cursor: pointer; outline: none; height: 36px; line-height: 36px; } .github-star-container { margin-top: 12px; line-height: 20px; } .github-star-container a { display: flex; align-items: center; text-decoration: none; color: #333; } .github-star-badge { color: #24292e; display: flex; align-items: center; font-size: 12px; padding: 3px 10px; border: 1px solid rgba(27, 31, 35, 0.2); border-radius: 3px; background-image: linear-gradient(-180deg, #fafbfc, #eff3f6 90%); margin-left: 4px; font-weight: 600; } .github-star-badge:hover { background-image: linear-gradient(-180deg, #f0f3f6, #e6ebf1 90%); border-color: rgba(27, 31, 35, 0.35); background-position: -0.5em; } .github-star-badge .material-icons { height: 16px; width: 16px; margin-right: 4px; } ================================================ FILE: apps/playground/src/app/app.component.spec.ts ================================================ import { async, TestBed } from '@angular/core/testing'; import { AppComponent } from './app.component'; import { RouterTestingModule } from '@angular/router/testing'; import { AppModule } from './app.module'; describe('AppComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [RouterTestingModule, AppModule] }).compileComponents(); })); it('should create the app', () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app).toBeTruthy(); }); it(`should have as title 'playground'`, () => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app.title).toEqual('playground'); }); it('should render title', () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.debugElement.nativeElement; expect(compiled.querySelector('h1').textContent).toContain( 'Welcome to playground!' ); }); }); ================================================ FILE: apps/playground/src/app/app.component.ts ================================================ import { Component } from '@angular/core'; @Component({ selector: 'codelab-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent { title = 'playground'; } ================================================ FILE: apps/playground/src/app/app.module.ts ================================================ import { BrowserModule } from '@angular/platform-browser'; import { APP_INITIALIZER, NgModule } from '@angular/core'; import { AppComponent } from './app.component'; import { RouterModule } from '@angular/router'; import { monacoReady } from '@codelab/code-demos'; import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { environment } from '../environments/environment'; import { AngularFireModule } from '@angular/fire'; import { AngularFireDatabaseModule } from '@angular/fire/database'; import { AngularFireAuthModule } from '@angular/fire/auth'; const routes = [ { path: '', redirectTo: 'code-sync', pathMatch: 'full' }, { path: 'angular', loadChildren: () => import('./playground/playground.module').then(m => m.PlaygroundModule) }, { path: 'code-sync', loadChildren: () => import('./code-sync/code-sync.module').then(m => m.CodeSyncModule) } ]; export const AngularFireApp = AngularFireModule.initializeApp( environment.firebaseConfig ); @NgModule({ declarations: [AppComponent], imports: [ BrowserModule, BrowserAnimationsModule, RouterModule.forRoot(routes, { initialNavigation: 'enabled' }), AngularFireApp, AngularFireDatabaseModule, AngularFireAuthModule ], providers: [ { provide: APP_INITIALIZER, useValue: monacoReady, multi: true } ], bootstrap: [AppComponent] }) export class AppModule {} ================================================ FILE: apps/playground/src/app/code-sync/code-sync.component.css ================================================ ================================================ FILE: apps/playground/src/app/code-sync/code-sync.component.html ================================================
      ================================================ FILE: apps/playground/src/app/code-sync/code-sync.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { CodeSyncComponent } from './code-sync.component'; import { CodeSyncModule } from './code-sync.module'; import { RouterTestingModule } from '@angular/router/testing'; import { getMockAngularFireProviders } from '@codelab/utils/src/lib/testing/mocks/angular-fire'; describe('CodeSyncComponent', () => { let component: CodeSyncComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [CodeSyncModule, RouterTestingModule], providers: [getMockAngularFireProviders()] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(CodeSyncComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/playground/src/app/code-sync/code-sync.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'codelab-code-sync', templateUrl: './code-sync.component.html', styleUrls: ['./code-sync.component.css'] }) export class CodeSyncComponent implements OnInit { constructor() {} ngOnInit() {} } ================================================ FILE: apps/playground/src/app/code-sync/code-sync.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { RouterModule } from '@angular/router'; import { CodeSyncComponent } from './code-sync.component'; import { SyncButtonModule } from '@codelab/utils/src/lib/sync/sync-button/sync-button.module'; import { SyncDirectivesModule } from '@codelab/utils/src/lib/sync/directives/sync-directives.module'; import { AngularFireDatabaseModule } from '@angular/fire/database'; const routes = RouterModule.forChild(SlidesRoutes.get(CodeSyncComponent)); @NgModule({ declarations: [CodeSyncComponent], imports: [ CommonModule, SlidesModule, routes, SyncDirectivesModule, SyncButtonModule, AngularFireDatabaseModule ] }) export class CodeSyncModule {} ================================================ FILE: apps/playground/src/app/playground/angular-sample.ts ================================================ export const angularSampleCode = { 'app.component.ts': `import { Component } from '@angular/core'; @Component({ selector: 'my-app', template: \`

      Edit me

      \` }) export class AppComponent {}`, 'app.module.ts': `import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; @NgModule({ imports: [BrowserModule], declarations: [AppComponent], bootstrap: [AppComponent] }) export class AppModule {}`, 'main.ts': `import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app.module'; platformBrowserDynamic().bootstrapModule(AppModule); `, 'index.html': '' }; ================================================ FILE: apps/playground/src/app/playground/playground.component.css ================================================ ================================================ FILE: apps/playground/src/app/playground/playground.component.html ================================================ ================================================ FILE: apps/playground/src/app/playground/playground.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { PlaygroundComponent } from './playground.component'; import { PlaygroundModule } from './playground.module'; import { ActivatedRoute, Router } from '@angular/router'; describe('PlaygroundComponent', () => { let component: PlaygroundComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [PlaygroundModule], providers: [ { provide: ActivatedRoute, useValue: { snapshot: { queryParams: { code: '' } } } }, { provide: Router, useValue: { navigate: jasmine.createSpy('navigate') } } ] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(PlaygroundComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create!', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: apps/playground/src/app/playground/playground.component.ts ================================================ import { Component } from '@angular/core'; import { angularSampleCode } from './angular-sample'; import { ActivatedRoute, Router } from '@angular/router'; @Component({ selector: 'codelab-playground', templateUrl: './playground.component.html', styleUrls: ['./playground.component.css'] }) export class PlaygroundComponent { code = angularSampleCode; constructor( private readonly activatedRoute: ActivatedRoute, private readonly router: Router ) { const code = activatedRoute.snapshot.queryParams.code; if (code) { try { this.code = { ...angularSampleCode, ...JSON.parse(atob(code)) }; } catch (e) { console.log('can not parse code', code); } } } handleUpdate(code: any) { const encoded = btoa(JSON.stringify(code)); this.router.navigate([], { relativeTo: this.activatedRoute, queryParams: { code: encoded } }); } } ================================================ FILE: apps/playground/src/app/playground/playground.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { PlaygroundComponent } from './playground.component'; import { RouterModule } from '@angular/router'; import { CodeDemoModule } from '@codelab/code-demos'; import { FormsModule } from '@angular/forms'; import { FirebaseModule } from '@codelab/firebase'; @NgModule({ declarations: [PlaygroundComponent], imports: [ RouterModule.forChild([{ path: '', component: PlaygroundComponent }]), CodeDemoModule, CommonModule, FormsModule, FirebaseModule ] }) export class PlaygroundModule {} ================================================ FILE: apps/playground/src/assets/.gitkeep ================================================ ================================================ FILE: apps/playground/src/environments/environment.prod.ts ================================================ export const environment = { production: true, // Firebase firebaseConfig: { apiKey: 'AIzaSyBiY1Lg2RIcKtbgqzfE6Vrg28Zjal6ZWHs', authDomain: 'angular-presentation.firebaseapp.com', databaseURL: 'https://angular-presentation.firebaseio.com', projectId: 'angular-presentation', storageBucket: 'angular-presentation.appspot.com', messagingSenderId: '1087862173437', appId: '1:1087862173437:web:0bb7fe324b62580bb31894' } }; ================================================ FILE: apps/playground/src/environments/environment.ts ================================================ // This file can be replaced during build by using the `fileReplacements` array. // `ng build --prod` replaces `environment.ts` with `environment.prod.ts`. // The list of file replacements can be found in `angular.json`. export const environment = { production: false, // Firebase firebaseConfig: { apiKey: 'AIzaSyBiY1Lg2RIcKtbgqzfE6Vrg28Zjal6ZWHs', authDomain: 'angular-presentation.firebaseapp.com', databaseURL: 'https://angular-presentation.firebaseio.com', projectId: 'angular-presentation', storageBucket: 'angular-presentation.appspot.com', messagingSenderId: '1087862173437', appId: '1:1087862173437:web:0bb7fe324b62580bb31894' } }; /* * For easier debugging in development mode, you can import the following file * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`. * * This import should be commented out in production mode because it will have a negative impact * on performance if an error is thrown. */ // import 'zone.js/dist/zone-error'; // Included with Angular CLI. ================================================ FILE: apps/playground/src/index.html ================================================ Playground ================================================ FILE: apps/playground/src/main.ts ================================================ import { enableProdMode } from '@angular/core'; import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app/app.module'; import { environment } from './environments/environment'; if (environment.production) { enableProdMode(); } platformBrowserDynamic() .bootstrapModule(AppModule) .catch(err => console.error(err)); ================================================ FILE: apps/playground/src/polyfills.ts ================================================ import 'zone.js/dist/zone'; // Included with Angular CLI. import '@angular/localize/init'; // Needed for babel :( (window as any).Buffer = {}; ================================================ FILE: apps/playground/src/styles.scss ================================================ @import '~@angular/material/prebuilt-themes/indigo-pink.css'; ================================================ FILE: apps/playground/src/test-setup.ts ================================================ import 'jest-preset-angular'; ================================================ FILE: apps/playground/tsconfig.app.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "types": [] }, "angularCompilerOptions": { "enableIvy": true }, "files": ["src/main.ts", "src/polyfills.ts"], "include": ["**/*.ts"], "exclude": ["src/test-setup.ts", "**/*.spec.ts"] } ================================================ FILE: apps/playground/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "types": ["node", "jest"] }, "angularCompilerOptions": { "enableIvy": true }, "include": ["**/*.ts"] } ================================================ FILE: apps/playground/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "module": "commonjs", "types": ["jest", "node"] }, "files": ["src/test-setup.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: apps/playground/tslint.json ================================================ { "extends": "../../tslint.json", "rules": { "directive-selector": [true, "attribute", "codelab", "camelCase"], "component-selector": [true, "element", "codelab", "kebab-case"] } } ================================================ FILE: create-issue.js ================================================ /** * Created by kirjs on 4/26/17. */ ================================================ FILE: cypress/fixtures/example.json ================================================ { "name": "Using fixtures to represent data", "email": "hello@cypress.io", "body": "Fixtures are a great way to mock data for responses to routes" } ================================================ FILE: cypress/integration/codelab/home.spec.js ================================================ context('home page', () => { beforeEach(() => { cy.viewport(2400, 1370); cy.visit('http://localhost:4200/'); }); it('didnt click on typescript', () => { cy.matchImageSnapshot('home'); }); it('typescript should be clickable', () => { cy.get('.learn-box.typescript').click(); // cy.matchImageSnapshot("home"); cy.location().should(location => { expect(location.href).to.eq('http://localhost:4200/typescript/intro'); }); }); }); ================================================ FILE: cypress/plugins/cy-ts-preprocessor.js ================================================ const wp = require('@cypress/webpack-preprocessor'); const webpackOptions = { resolve: { extensions: ['.ts', '.js'] }, module: { rules: [ { test: /\.ts$/, exclude: [/node_modules/], use: [ { loader: 'ts-loader' } ] } ] } }; const options = { webpackOptions }; module.exports = wp(options); ================================================ FILE: cypress/plugins/index.js ================================================ const cypressTypeScriptPreprocessor = require('./cy-ts-preprocessor'); const { addMatchImageSnapshotPlugin } = require('cypress-image-snapshot/plugin'); module.exports = (on, config) => { addMatchImageSnapshotPlugin(on, config); // on('file:preprocessor', cypressTypeScriptPreprocessor); }; ================================================ FILE: cypress/support/commands.js ================================================ // *********************************************** // This example commands.js shows you how to // create various custom commands and overwrite // existing commands. // // For more comprehensive examples of custom // commands please read more here: // https://on.cypress.io/custom-commands // *********************************************** //import { addMatchImageSnapshotCommand } from 'cypress-image-snapshot/command'; // // -- This is a parent command -- // Cypress.Commands.add("login", (email, password) => { ... }) // // // -- This is a child command -- // Cypress.Commands.add("drag", { prevSubject: 'element'}, (subject, options) => { ... }) // // // -- This is a dual command -- // Cypress.Commands.add("dismiss", { prevSubject: 'optional'}, (subject, options) => { ... }) // // // -- This is will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }) import { addMatchImageSnapshotCommand } from 'cypress-image-snapshot/command'; addMatchImageSnapshotCommand({ failureThreshold: 0.03, // threshold for entire image failureThresholdType: 'percent', // percent of image or number of pixels customDiffConfig: { threshold: 0.1 }, // threshold for each pixel capture: 'runner' }); ================================================ FILE: cypress/support/index.js ================================================ // *********************************************************** // This example support/index.js is processed and // loaded automatically before your test files. // // This is a great place to put global configuration and // behavior that modifies Cypress. // // You can change the location of this file or turn off // automatically serving support files with the // 'supportFile' configuration option. // // You can read more here: // https://on.cypress.io/configuration // *********************************************************** // Import commands.js using ES2015 syntax: import './commands'; // Alternatively you can use CommonJS syntax: // require('./commands') ================================================ FILE: cypress/tsconfig.json ================================================ { "compilerOptions": { "strict": true, "baseUrl": "../node_modules", "target": "es5", "lib": ["es5", "dom"], "types": ["cypress"] }, "include": ["**/*.ts"] } ================================================ FILE: cypress.json ================================================ {} ================================================ FILE: docs/CONTRIBUTING.md ================================================ # Contributing to Angular Codelab Thank you for deciding to contribute to codelab.fun! We're excited to have you on the team 🙌 Below you'll find our guidelines for contributing to further development of this codelab, which is hosted at [codelab-fun/codelab](https://github.com/codelab-fun/codelab) on GitHub. --- #### Table Of Contents - [Before You Start](#before-you-start) - [Code of Conduct](#code-of-conduct) - [Quick start](#quick-start) - [Repository structure](#repository-structure) - [Contributing to the Codelab](#contributing-to-the-codelab) - [Reporting Bugs](#reporting-bugs) - [Before Sending Feedback](#before-sending-feedback) - [Submitting a Report](#submitting-a-report) - [Opening Issues on GitHub](#opening-issues-on-github) - [Contributing Code](#contributing-code) - [Making a Pull Request](#making-a-pull-request) - [On Style](#on-style) - [Git Commit Messages](#git-commit-messages) - [For example:](#for-example-) - [Code Style](#code-style) - [Autoformatting](#autoformatting) * [Linter](#linter) --- ## Before You Start ### Code of Conduct This project adheres to the [Contributor Covenant Code of Conduct](http://contributor-covenant.org/version/1/4/). So that everyone can feel welcome we ask you to please uphold this code should you decide to contribute to this project. --- ## Quick start - `git clone https://github.com/codelab-fun/codelab.git` (this UTL might be different if you forked) - `npm install` - `npm start` _Note:_ We use NPM as package manager, not Yarn. Thus, please keep `package-lock.json` in sync with `package.json`, and do not commit `yarn.lock` file. ⚠ `Windows` users may need to use `yarn` if `npm` fails ## Repository structure We're using [NX](https://nx.dev/web) - Extensible Dev Tools for Monorepos. This allows us to have multiple projects and libraries in one repository. Most of the work will happen in `apps/codelab`, but here's the overview of the other folders: ``` - apps - codelab - The actual codelab code. Most of the work will be done here. - angular-thirty-seconds - 30.codelab.fun code - kirjs - @kirjs's folder for experiments - lis - @the_kibs's folder for experiments - blog - blog is coming soon - libs - Libraries and helper code shared across projects - ng2ts - Legacy code which shouldn't be touched - tools - Angular schematic for generating new presentations. ``` ## Contributing to the Codelab ### Reporting Bugs If you find a bug while going through the codelab as a student, you can submit feedback through the blue feedback button in the bottom-right corner. --- ### Contributing Code If you don't know where to start, try perusing issues marked by `help-wanted` tags! If you want to work on something there isn't yet an issue for, consider submitting an issue so that multiple contributers aren't _unknowingly_ working on solving the same problem in parallel. If you're new to Git check out this awesome [free Udacity class](https://www.udacity.com/course/how-to-use-git-and-github--ud775) by [Caroline Buckey](https://github.com/cbuckey-uda) and [Sarah Spikes](https://github.com/salogel42) 📚 And if you're new to just GitHub check out [this cool tutorial series](https://egghead.io/courses/how-to-contribute-to-an-open-source-project-on-github) by [Kent C. Dodds](https://github.com/kentcdodds) 📝 ### Making a Pull Request - Don't forget to pull from the master branch and resolve any merge conflicts first! - We run `npm run lint && npm run build:prod && npm run format:check` in CI, so run it locally before committing you code - If you don't have access to the repo, fork the project and make a PR from there - Describe what the PR addresses - Include screenshots and descriptive explanations if necessary ### Code Style ## Autoformatting run `npm run format:write` before submitting your code. # Linter Angular Codelab comes with its own linter settings. If you're not sure your linter is picking up on them, please run `npm lint` and fix any styling errors before submitting. ================================================ FILE: docs/HOSTING.md ================================================ # Host a live Angular Codelab event in your city We have hosted multiple live codelab events in 10+ cities, and YOU can help us host one! People learning angular If you know Angular and want to share your knowledge with other people in your home town, we would be happy to support you! ## How to host an event To host an Angular Codelab 3 things are needed: - A place to host the event (potentially in some local Angular company) - Some way to invite people (local Angular or GDG meetup could help) - At least one mentor who knows Angular and is familiar with Material. The most important thing is to know Angular and to be interested to share your knowledge. We can help with the rest. To start - [Create a new issue](https://github.com/codelab-fun/codelab/issues/new?title=[Hosting]%20I%20would%20like%20to%host%20codelab%20in%20_CITY_&body=) ## Previous events Here are some examples and descriptions from previous events: - [Codelab in NYC](https://www.meetup.com/AngularNYC/events/263172186/) (in English) - [Codelab in Moscow](https://www.meetup.com/AngularMoscow/events/262852935/) (in Russian) ## FAQ ❓What does it look like? People learning angular A group of people gather together and starts going through the codelab with their own pace. Organizes and volunteers are there to provide additional context and answer any outstanding questions. ❓How many people should I invite There's no fixed size. We have hosted successful events of 3-60 people. More is possible, but it might be harded to find space. ❓How long is it? There are two formats: - After work: starts about 6-7PM, and goes for 3 hours. Easier, but everybody's tired after work - Weekend: starts in the morning, and goes for 3-5 hours. More productive, but takes part of your weekend ❓How can codelab.fun team help? Some of us organize events all over the world and we can help you get in touch with local companies and meetup organizers, and also with promoting your events. ❓Can I host private event at work? Go for it! ================================================ FILE: docs/TRANSLATING.md ================================================ # Translating Codelab.fun You can help us translate codelab.fun to other languages! We're using online dashboard for seamless collaborative translation: Online translation dashboard ## How much work would it be? We currently have about 500 translation terms, which amounts for a few days of work by one person. the work can be easily parallelized and it's recommended, (but not required) to find couple more people (we can help spread the word). Wording in the course is periodically updated, and it would be amazing if you could also help us periodically update translations. ## Currently Supported languages Codelab.fun is available in: - English - Russian ## How to start Check is somebody is already translating the codelab into your language in [https://github.com/codelab-fun/codelab/issues](issues). - If someone has already started, ping the issue to see if your help is needed - Otherwise [Create a new issue](https://github.com/codelab-fun/codelab/issues/new?title=[Translation]%20I%20would%20like%20to%20translate%20codelab%20into%20_LANGUAGE_&body=) ================================================ FILE: firebase.json ================================================ { "hosting": [ { "target": "codelab", "public": "dist/apps/codelab", "rewrites": [ { "source": "/ru/**", "destination": "/ru/index.html" }, { "source": "/30/**", "destination": "/30/index.html" }, { "source": "**", "destination": "/index.html" } ] }, { "target": "codelab-next", "public": "dist/apps/codelab", "rewrites": [ { "source": "/ru/**", "destination": "/ru/index.html" }, { "source": "/30/**", "destination": "/30/index.html" }, { "source": "**", "destination": "/index.html" } ] }, { "target": "kirjs", "public": "dist/apps/kirjs", "rewrites": [ { "source": "**", "destination": "/index.html" } ] }, { "target": "lis", "public": "dist/apps/lis", "rewrites": [ { "source": "**", "destination": "/index.html" } ] }, { "target": "angular-ivy", "public": "dist/apps/playground", "rewrites": [ { "source": "**", "destination": "/index.html" } ] } ] } ================================================ FILE: jest.config.js ================================================ module.exports = { testMatch: ['**/+(*.)+(spec|test).+(ts|js)?(x)'], transform: { '^.+\\.(ts|js|html)$': 'ts-jest' }, resolver: '@nrwl/jest/plugins/resolver', moduleFileExtensions: ['ts', 'js', 'html'], collectCoverage: true, coverageReporters: ['html'] }; ================================================ FILE: libs/angular-ast-viz/karma.conf.js ================================================ // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html const { join } = require('path'); const getBaseKarmaConfig = require('../../karma.conf'); module.exports = function(config) { const baseConfig = getBaseKarmaConfig(); config.set({ ...baseConfig, coverageIstanbulReporter: { ...baseConfig.coverageIstanbulReporter, dir: join(__dirname, '../../coverage/libs/angular-ast-viz') } }); }; ================================================ FILE: libs/angular-ast-viz/ng-package.json ================================================ { "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", "dest": "../../dist/libs/angular-ast-viz", "deleteDestPath": false, "lib": { "entryFile": "src/index.ts" } } ================================================ FILE: libs/angular-ast-viz/ng-package.prod.json ================================================ { "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", "dest": "../../dist/libs/angular-ast-viz", "lib": { "entryFile": "src/index.ts" } } ================================================ FILE: libs/angular-ast-viz/package.json ================================================ { "name": "angular-ast-viz", "version": "0.3.3", "peerDependencies": { "@angular/common": "^6.0.0-rc.0 || ^6.0.0", "@angular/core": "^6.0.0-rc.0 || ^6.0.0" } } ================================================ FILE: libs/angular-ast-viz/src/index.ts ================================================ export * from './lib/angular-ast-viz.module'; ================================================ FILE: libs/angular-ast-viz/src/lib/angular-ast-viz.module.spec.ts ================================================ import { async, TestBed } from '@angular/core/testing'; import { AngularAstVizModule } from './angular-ast-viz.module'; describe('AngularAstVizModule', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [AngularAstVizModule] }).compileComponents(); })); it('should create', () => { expect(AngularAstVizModule).toBeDefined(); }); }); ================================================ FILE: libs/angular-ast-viz/src/lib/angular-ast-viz.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { AstTreeComponent } from './ast-tree/ast-tree.component'; @NgModule({ imports: [CommonModule], declarations: [AstTreeComponent], exports: [AstTreeComponent] }) export class AngularAstVizModule {} ================================================ FILE: libs/angular-ast-viz/src/lib/app.component.css ================================================ ================================================ FILE: libs/angular-ast-viz/src/lib/app.component.html ================================================
          {{ ast | json }}
        
      ================================================ FILE: libs/angular-ast-viz/src/lib/app.component.spec.ts ================================================ import { TestBed, async } from '@angular/core/testing'; import { AppComponent } from './app.component'; describe('AppComponent', () => { beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [AppComponent] }).compileComponents(); })); it('should create the app', async(() => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app).toBeTruthy(); })); it(`should have as title 'app'`, async(() => { const fixture = TestBed.createComponent(AppComponent); const app = fixture.debugElement.componentInstance; expect(app.title).toEqual('app'); })); it('should render title in a h1 tag', async(() => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const compiled = fixture.debugElement.nativeElement; expect(compiled.querySelector('h1').textContent).toContain( 'Welcome to app!' ); })); }); ================================================ FILE: libs/angular-ast-viz/src/lib/app.component.ts ================================================ import { Component, OnInit } from '@angular/core'; import * as babylon from 'babylon'; @Component({ selector: 'ast-viz-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements OnInit { ast = []; code = `console.log();`; ngOnInit() { this.generateAst(this.code); } selectNode(node) { console.log(node); } generateAst(value: string) { this.ast = babylon.parse(value).program.body; console.log(this.ast); } } ================================================ FILE: libs/angular-ast-viz/src/lib/ast-tree/ast-tree.component.css ================================================ .box { padding: 10px; background: #eee; margin-top: 4px; display: inline-block; min-width: 120px; } .box.CallExpression { background: #e4ff04; } .box.ExpressionStatement { background: #444; color: white; } .box.Identifier { background: #ffe000; } .box.Array { background: #fafafa; } .box.NumericLiteral, .box.RegExpLiteral, .box.NullLiteral, .box.BooleanLiteral, .box.TemplateLiteral, .box.StringLiteral { background: #00daff; } .props { color: #666; } .box .key { } .box .node { font-weight: 300; } ================================================ FILE: libs/angular-ast-viz/src/lib/ast-tree/ast-tree.component.html ================================================
      {{ key ? key + ': ' : '' }}
      {{ split(node.type) }}
      {{ key }} = '{{ node[key] }}'
      {{ key ? key + ': ' : '' }}
      Array
      {{ key }}: []
      {{ key ? key + ': ' : '' }} Null
      ================================================ FILE: libs/angular-ast-viz/src/lib/ast-tree/ast-tree.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { AstTreeComponent } from './ast-tree.component'; describe('AstTreeComponent', () => { let component: AstTreeComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [AstTreeComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(AstTreeComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/angular-ast-viz/src/lib/ast-tree/ast-tree.component.ts ================================================ import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; const systemKeys = new Set([ 'type', 'start', 'end', 'loc', 'comments', 'tokens', 'sourceType', 'directives', 'extra' ]); @Component({ selector: 'ast-viz', templateUrl: './ast-tree.component.html', changeDetection: ChangeDetectionStrategy.OnPush, styleUrls: ['./ast-tree.component.css'] }) export class AstTreeComponent implements OnInit { @Input() node: any = {}; @Input() key: string; @Input() shortNames = true; @Output() selectNode = new EventEmitter(); split(value) { return value.replace(/([A-Z])/g, ' $1'); } get specialKeys() { return Object.keys(this.node).filter(key => !systemKeys.has(key)); } get stringKeys() { return this.specialKeys.filter( key => typeof this.node[key] === 'string' || typeof this.node[key] === 'number' ); } get objectKeys() { return this.specialKeys .filter(key => typeof this.node[key] === 'object') .reverse(); } get isArray() { return Array.isArray(this.node); } constructor() {} ngOnInit() {} } ================================================ FILE: libs/angular-ast-viz/src/lib/ast-tree/ast-tree.module.ts ================================================ ================================================ FILE: libs/angular-ast-viz/src/lib/ast-tree/short-name-babel.pipe.spec.ts ================================================ import { ShortNameBabelPipe } from './short-name-babel.pipe'; describe('ShortNameBabelPipe', () => { it('create an instance', () => { const pipe = new ShortNameBabelPipe(); expect(pipe).toBeTruthy(); }); }); ================================================ FILE: libs/angular-ast-viz/src/lib/ast-tree/short-name-babel.pipe.ts ================================================ import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'shortNameBabel' }) export class ShortNameBabelPipe implements PipeTransform { transform(value: string): any { return value.replace(/[a-z]+/g, ''); } } ================================================ FILE: libs/angular-ast-viz/src/test.ts ================================================ // This file is required by karma.conf.js and loads recursively all the .spec and framework files import 'zone.js/dist/zone'; import 'zone.js/dist/zone-testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; declare const require: any; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); // Then we find all the tests. const context = require.context('./', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context); ================================================ FILE: libs/angular-ast-viz/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "types": ["jasmine", "node"] } } ================================================ FILE: libs/angular-ast-viz/tsconfig.lib.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "target": "es2015", "module": "es2015", "moduleResolution": "node", "declaration": true, "sourceMap": true, "inlineSources": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "importHelpers": true, "types": [], "lib": ["dom", "es2015"] }, "angularCompilerOptions": { "annotateForClosureCompiler": true, "skipTemplateCodegen": true, "strictMetadataEmit": true, "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "flatModuleId": "AUTOGENERATED", "flatModuleOutFile": "AUTOGENERATED" }, "exclude": ["src/test.ts", "**/*.spec.ts"] } ================================================ FILE: libs/angular-ast-viz/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "types": ["jasmine", "node"] }, "files": ["src/test.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: libs/angular-ast-viz/tslint.json ================================================ { "extends": "../../tslint.json", "rules": { "directive-selector": [true, "attribute", "ast-viz", "camelCase"], "component-selector": [true, "element", "ast-viz", "kebab-case"] } } ================================================ FILE: libs/angular-slides-to-pdf/karma.conf.js ================================================ // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html const { join } = require('path'); const getBaseKarmaConfig = require('../../karma.conf'); module.exports = function(config) { const baseConfig = getBaseKarmaConfig(); config.set({ ...baseConfig, coverageIstanbulReporter: { ...baseConfig.coverageIstanbulReporter, dir: join(__dirname, '../../coverage/libs/angular-slides-to-pdf') } }); }; ================================================ FILE: libs/angular-slides-to-pdf/ng-package.json ================================================ { "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", "dest": "../../dist/libs/angular-slides-to-pdf", "deleteDestPath": false, "lib": { "entryFile": "src/index.ts" } } ================================================ FILE: libs/angular-slides-to-pdf/ng-package.prod.json ================================================ { "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", "dest": "../../dist/libs/angular-slides-to-pdf", "lib": { "entryFile": "src/index.ts" } } ================================================ FILE: libs/angular-slides-to-pdf/package.json ================================================ { "name": "@codelab/angular-slides-to-pdf", "version": "0.0.1", "peerDependencies": { "@angular/common": "^6.0.0-rc.0 || ^6.0.0", "@angular/core": "^6.0.0-rc.0 || ^6.0.0" } } ================================================ FILE: libs/angular-slides-to-pdf/src/index.ts ================================================ export * from './lib/angular-slides-to-pdf.module'; ================================================ FILE: libs/angular-slides-to-pdf/src/lib/angular-slides-to-pdf.component.ts ================================================ import { Component } from '@angular/core'; import { SlidesDeckComponent } from '@ng360/slides'; @Component({ selector: 'slides-codelab-to-pdf', template: '' }) export class AngularSlidesToPdfComponent { constructor(private presentation: SlidesDeckComponent) {} async toPdf() { const width = 1024; const height = 600; this.presentation.goToSlide(0); const JsPDF = function() {} as any; const doc = new JsPDF({ orientation: 'landscape', unit: 'px', format: [width, height] }); for (let i = 0; i < this.presentation.slides.length; i++) { // TODO(kirjs): Uncomment // const el = this.presentation.el.nativeElement.querySelector( // 'slides-slide' // ); // const slide = el.querySelector('.slide'); // // slide.style.width = width + 'px '; // // slide.style.height = width + 'px '; // // if (i > 0) { // doc.addPage(); // } // // const image1 = await domtoimage.toPng(el); // const image2 = await domtoimage.toPng(slide); // const image = image1.length === 6 ? image2 : image1; // // doc.addImage(image, 0, 0, width, height); // // this.presentation.nextSlide(); } doc.save('a4.pdf'); } } ================================================ FILE: libs/angular-slides-to-pdf/src/lib/angular-slides-to-pdf.module.spec.ts ================================================ import { async, TestBed } from '@angular/core/testing'; import { AngularSlidesToPdfModule } from './angular-slides-to-pdf.module'; describe('AngularSlidesToPdfModule', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [AngularSlidesToPdfModule] }).compileComponents(); })); it('should create', () => { expect(AngularSlidesToPdfModule).toBeDefined(); }); }); ================================================ FILE: libs/angular-slides-to-pdf/src/lib/angular-slides-to-pdf.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { AngularSlidesToPdfComponent } from './angular-slides-to-pdf.component'; @NgModule({ declarations: [AngularSlidesToPdfComponent], exports: [AngularSlidesToPdfComponent], imports: [CommonModule] }) export class AngularSlidesToPdfModule {} ================================================ FILE: libs/angular-slides-to-pdf/src/test.ts ================================================ // This file is required by karma.conf.js and loads recursively all the .spec and framework files import 'zone.js/dist/zone'; import 'zone.js/dist/zone-testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; declare const require: any; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); // Then we find all the tests. const context = require.context('./', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context); ================================================ FILE: libs/angular-slides-to-pdf/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "types": ["jasmine", "node"] } } ================================================ FILE: libs/angular-slides-to-pdf/tsconfig.lib.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "target": "es2015", "module": "es2015", "moduleResolution": "node", "declaration": true, "sourceMap": true, "inlineSources": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "importHelpers": true, "types": [], "lib": ["dom", "es2015"] }, "angularCompilerOptions": { "annotateForClosureCompiler": true, "skipTemplateCodegen": true, "strictMetadataEmit": true, "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "flatModuleId": "AUTOGENERATED", "flatModuleOutFile": "AUTOGENERATED" }, "exclude": ["src/test.ts", "**/*.spec.ts"] } ================================================ FILE: libs/angular-slides-to-pdf/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "types": ["jasmine", "node"] }, "files": ["src/test.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: libs/angular-slides-to-pdf/tslint.json ================================================ { "extends": "../../tslint.json", "rules": { "directive-selector": [true, "attribute", "slides", "camelCase"], "component-selector": [true, "element", "slides", "kebab-case"] } } ================================================ FILE: libs/browser/src/index.ts ================================================ export * from './lib/browser.module'; ================================================ FILE: libs/browser/src/lib/browser-window/browser-window.component.css ================================================ :host { height: 100%; display: block; } .frame { box-shadow: 0 0 1px #dddddd; margin-top: 0; width: 100%; background-color: #fff; flex-grow: 1; display: flex; flex-direction: column; } :host ::ng-deep .runner { flex-grow: 1; } ::ng-deep iframe { display: block; width: 100%; height: 100%; } .img-full img { height: 2vw; } .img-full { width: 100%; flex-shrink: 0; height: 2vw; background-image: url(./pics/browsertop-middle.png); background-repeat: repeat; background-size: 2vw 2vw; display: flex; justify-content: space-between; } .browser-frame { position: relative; display: flex; flex-direction: column; height: 100%; } .browser-frame .url { position: absolute; left: 3.2vw; top: 1.2vw; font-size: 0.5vw; background-color: #fff; } .img-left { width: 5vw; height: 2vw; background: url(./pics/browsertop-left.png); background-size: cover; } .img-right { width: 3vw; height: 2vw; background: url(./pics/browsertop-right.png); background-size: cover; background-position-y: -1px; } ================================================ FILE: libs/browser/src/lib/browser-window/browser-window.component.html ================================================
      {{ url }}
      ================================================ FILE: libs/browser/src/lib/browser-window/browser-window.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'preview-browser-window', templateUrl: './browser-window.component.html', styleUrls: ['./browser-window.component.css'] }) export class BrowserWindowComponent implements OnInit { @Input() height = ''; @Input() url = 'http://localhost:4200/'; constructor() {} ngOnInit() {} } ================================================ FILE: libs/browser/src/lib/browser.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { BrowserWindowComponent } from './browser-window/browser-window.component'; import { TerminalWindowComponent } from './terminal-window/terminal-window.component'; import { PreviewWindowComponent } from './preview-window/preview-window.component'; @NgModule({ imports: [CommonModule], declarations: [ BrowserWindowComponent, TerminalWindowComponent, PreviewWindowComponent ], exports: [ BrowserWindowComponent, TerminalWindowComponent, PreviewWindowComponent ] }) export class BrowserWindowModule {} ================================================ FILE: libs/browser/src/lib/preview-window/preview-window.component.html ================================================
      {{ url }}
      ================================================ FILE: libs/browser/src/lib/preview-window/preview-window.component.scss ================================================ $border-color: #c2c2c2; :host { height: 100%; display: block; ::ng-deep iframe { display: block; width: 100%; flex-grow: 1; height: 100%; } } .preview-frame { position: relative; display: flex; flex-direction: column; height: 100%; .frame { box-shadow: 0 0 1px #dddddd; margin-top: 0; width: 100%; background-color: #fff; flex-grow: 1; display: flex; flex-direction: column; & ::ng-deep *:first-child { flex-grow: 1; display: flex; flex-direction: column; } } } .console-frame { font: 16px monospace; .address-bar { padding: 0.2em; font-weight: bold; &::before { display: inline; color: #ff4081; content: '>>'; } .url { display: inline-block; margin-left: 0.5em; } } } .browser-frame { font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif; .address-bar { background: #eaeaea; border: 1px solid #c2c2c2; padding: 2px 2px 2px 20px; -webkit-box-align: center; align-items: center; & > .url { background: #fff; box-shadow: inset 1px 1px 1px rgba(0, 0, 0, 0.1); border: 1px solid $border-color; padding: 0.3em; box-sizing: border-box; } } } ================================================ FILE: libs/browser/src/lib/preview-window/preview-window.component.ts ================================================ import { Component, Input } from '@angular/core'; export type PreviewWindowType = 'console' | 'browser'; @Component({ selector: 'preview-window', templateUrl: './preview-window.component.html', styleUrls: ['./preview-window.component.scss'] }) export class PreviewWindowComponent { @Input() set url(url: string) { this._url = url.replace('about:blank', 'https://localhost:4200'); } @Input() height = ''; @Input() ui: PreviewWindowType = 'console'; get url() { return this.ui === 'console' ? 'console' : this._url; } private _url = 'http://'; } ================================================ FILE: libs/browser/src/lib/terminal-window/terminal-window.component.css ================================================ .terminal-frame { width: 100%; height: 100%; box-shadow: 0 0 1px #dddddd; } .frame { box-shadow: 0 0 1px #dddddd; margin-top: 0; color: #444; width: 100%; background-color: #fff; white-space: pre-wrap; font-family: monospace; } .img-full { width: 100%; height: 2vw; background-image: url(/assets/images/consoletop-middle.png); background-repeat: repeat; display: flex; justify-content: space-between; } .img-full img { height: 2vw; } ::ng-deep .success { color: green; } ::ng-deep .skipped { color: #ddd; } ================================================ FILE: libs/browser/src/lib/terminal-window/terminal-window.component.html ================================================
      ================================================ FILE: libs/browser/src/lib/terminal-window/terminal-window.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'preview-terminal-window', templateUrl: './terminal-window.component.html', styleUrls: ['./terminal-window.component.css'] }) export class TerminalWindowComponent implements OnInit { @Input() height = ''; constructor() {} ngOnInit() {} } ================================================ FILE: libs/browser/src/test.ts ================================================ // This file is required by karma.conf.js and loads recursively all the .spec and framework files import 'zone.js/dist/zone'; import 'zone.js/dist/zone-testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; declare const require: any; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); // Then we find all the tests. const context = require.context('./', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context); ================================================ FILE: libs/browser/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "types": ["jasmine", "node"] } } ================================================ FILE: libs/browser/tsconfig.lib.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "target": "es2015", "module": "es2015", "moduleResolution": "node", "declaration": true, "sourceMap": true, "inlineSources": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "importHelpers": true, "types": [], "lib": ["dom", "es2015"] }, "angularCompilerOptions": { "annotateForClosureCompiler": true, "skipTemplateCodegen": true, "strictMetadataEmit": true, "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "flatModuleId": "AUTOGENERATED", "flatModuleOutFile": "AUTOGENERATED" }, "exclude": ["src/test.ts", "**/*.spec.ts"] } ================================================ FILE: libs/browser/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "types": ["jasmine", "node"] }, "files": ["src/test.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: libs/browser/tslint.json ================================================ { "extends": "../../tslint.json", "rules": { "directive-selector": [true, "attribute", "preview", "camelCase"], "component-selector": [true, "element", "preview", "kebab-case"] } } ================================================ FILE: libs/code-demos/assets/runner/README.md ================================================ # Ng bundler TODO(kirjs): tidy up and add more details. ## Danger Angular fox warns you: There is a lot of magic happening in this folder, only touch it if you know what's going on. ``` _,-=._ /|_/| `-.} `=._,.-=-._., @ @._, `._ _,-. ) _,.-' ` G.m-"^m`m' Dmytro O. Redchuk ``` ## What's going on here? We use the files generated here to run code examples in an iframe. There are two files here that we generate: 1. ng-dts/files.ts - contains all the types for monaco to use 2. ng2/ng-bundle.js - contains all the js files bundled together. ## Regenerating To regenerate the files: - run `npm run build:bundler` - test it in the app really well.

      Fox is very confused

      ================================================ FILE: libs/code-demos/assets/runner/index.html ================================================ ================================================ FILE: libs/code-demos/assets/runner/js/mocha.js ================================================ (function e(t, n, r) { function s(o, u) { if (!n[o]) { if (!t[o]) { var a = typeof require == 'function' && require; if (!u && a) return a(o, !0); if (i) return i(o, !0); var f = new Error("Cannot find module '" + o + "'"); throw ((f.code = 'MODULE_NOT_FOUND'), f); } var l = (n[o] = { exports: {} }); t[o][0].call( l.exports, function(e) { var n = t[o][1][e]; return s(n ? n : e); }, l, l.exports, e, t, n, r ); } return n[o].exports; } var i = typeof require == 'function' && require; for (var o = 0; o < r.length; o++) s(r[o]); return s; })( { 1: [ function(require, module, exports) { (function(process) { module.exports = process.env.COV ? require('./lib-cov/mocha') : require('./lib/mocha'); }.call(this, require('_process'))); }, { './lib-cov/mocha': undefined, './lib/mocha': 14, _process: 51 } ], 2: [ function(require, module, exports) { /* eslint-disable no-unused-vars */ module.exports = function(type) { return function() {}; }; }, {} ], 3: [ function(require, module, exports) { /** * Module exports. */ exports.EventEmitter = EventEmitter; /** * Object#hasOwnProperty reference. */ var objToString = Object.prototype.toString; /** * Check if a value is an array. * * @api private * @param {*} val The value to test. * @return {boolean} true if the value is a boolean, otherwise false. */ function isArray(val) { return objToString.call(val) === '[object Array]'; } /** * Event emitter constructor. * * @api public */ function EventEmitter() {} /** * Add a listener. * * @api public * @param {string} name Event name. * @param {Function} fn Event handler. * @return {EventEmitter} Emitter instance. */ EventEmitter.prototype.on = function(name, fn) { if (!this.$events) { this.$events = {}; } if (!this.$events[name]) { this.$events[name] = fn; } else if (isArray(this.$events[name])) { this.$events[name].push(fn); } else { this.$events[name] = [this.$events[name], fn]; } return this; }; EventEmitter.prototype.addListener = EventEmitter.prototype.on; /** * Adds a volatile listener. * * @api public * @param {string} name Event name. * @param {Function} fn Event handler. * @return {EventEmitter} Emitter instance. */ EventEmitter.prototype.once = function(name, fn) { var self = this; function on() { self.removeListener(name, on); fn.apply(this, arguments); } on.listener = fn; this.on(name, on); return this; }; /** * Remove a listener. * * @api public * @param {string} name Event name. * @param {Function} fn Event handler. * @return {EventEmitter} Emitter instance. */ EventEmitter.prototype.removeListener = function(name, fn) { if (this.$events && this.$events[name]) { var list = this.$events[name]; if (isArray(list)) { var pos = -1; for (var i = 0, l = list.length; i < l; i++) { if ( list[i] === fn || (list[i].listener && list[i].listener === fn) ) { pos = i; break; } } if (pos < 0) { return this; } list.splice(pos, 1); if (!list.length) { delete this.$events[name]; } } else if (list === fn || (list.listener && list.listener === fn)) { delete this.$events[name]; } } return this; }; /** * Remove all listeners for an event. * * @api public * @param {string} name Event name. * @return {EventEmitter} Emitter instance. */ EventEmitter.prototype.removeAllListeners = function(name) { if (name === undefined) { this.$events = {}; return this; } if (this.$events && this.$events[name]) { this.$events[name] = null; } return this; }; /** * Get all listeners for a given event. * * @api public * @param {string} name Event name. * @return {EventEmitter} Emitter instance. */ EventEmitter.prototype.listeners = function(name) { if (!this.$events) { this.$events = {}; } if (!this.$events[name]) { this.$events[name] = []; } if (!isArray(this.$events[name])) { this.$events[name] = [this.$events[name]]; } return this.$events[name]; }; /** * Emit an event. * * @api public * @param {string} name Event name. * @return {boolean} true if at least one handler was invoked, else false. */ EventEmitter.prototype.emit = function(name) { if (!this.$events) { return false; } var handler = this.$events[name]; if (!handler) { return false; } var args = Array.prototype.slice.call(arguments, 1); if (typeof handler === 'function') { handler.apply(this, args); } else if (isArray(handler)) { var listeners = handler.slice(); for (var i = 0, l = listeners.length; i < l; i++) { listeners[i].apply(this, args); } } else { return false; } return true; }; }, {} ], 4: [ function(require, module, exports) { /** * Expose `Progress`. */ module.exports = Progress; /** * Initialize a new `Progress` indicator. */ function Progress() { this.percent = 0; this.size(0); this.fontSize(11); this.font('helvetica, arial, sans-serif'); } /** * Set progress size to `size`. * * @api public * @param {number} size * @return {Progress} Progress instance. */ Progress.prototype.size = function(size) { this._size = size; return this; }; /** * Set text to `text`. * * @api public * @param {string} text * @return {Progress} Progress instance. */ Progress.prototype.text = function(text) { this._text = text; return this; }; /** * Set font size to `size`. * * @api public * @param {number} size * @return {Progress} Progress instance. */ Progress.prototype.fontSize = function(size) { this._fontSize = size; return this; }; /** * Set font to `family`. * * @param {string} family * @return {Progress} Progress instance. */ Progress.prototype.font = function(family) { this._font = family; return this; }; /** * Update percentage to `n`. * * @param {number} n * @return {Progress} Progress instance. */ Progress.prototype.update = function(n) { this.percent = n; return this; }; /** * Draw on `ctx`. * * @param {CanvasRenderingContext2d} ctx * @return {Progress} Progress instance. */ Progress.prototype.draw = function(ctx) { try { var percent = Math.min(this.percent, 100); var size = this._size; var half = size / 2; var x = half; var y = half; var rad = half - 1; var fontSize = this._fontSize; ctx.font = fontSize + 'px ' + this._font; var angle = Math.PI * 2 * (percent / 100); ctx.clearRect(0, 0, size, size); // outer circle ctx.strokeStyle = '#9f9f9f'; ctx.beginPath(); ctx.arc(x, y, rad, 0, angle, false); ctx.stroke(); // inner circle ctx.strokeStyle = '#eee'; ctx.beginPath(); ctx.arc(x, y, rad - 1, 0, angle, true); ctx.stroke(); // text var text = this._text || (percent | 0) + '%'; var w = ctx.measureText(text).width; ctx.fillText(text, x - w / 2 + 1, y + fontSize / 2 - 1); } catch (err) { // don't fail if we can't render progress } return this; }; }, {} ], 5: [ function(require, module, exports) { (function(global) { exports.isatty = function isatty() { return true; }; exports.getWindowSize = function getWindowSize() { if ('innerHeight' in global) { return [global.innerHeight, global.innerWidth]; } // In a Web Worker, the DOM Window is not available. return [640, 480]; }; }.call( this, typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : {} )); }, {} ], 6: [ function(require, module, exports) { /** * Expose `Context`. */ module.exports = Context; /** * Initialize a new `Context`. * * @api private */ function Context() {} /** * Set or get the context `Runnable` to `runnable`. * * @api private * @param {Runnable} runnable * @return {Context} */ Context.prototype.runnable = function(runnable) { if (!arguments.length) { return this._runnable; } this.test = this._runnable = runnable; return this; }; /** * Set test timeout `ms`. * * @api private * @param {number} ms * @return {Context} self */ Context.prototype.timeout = function(ms) { if (!arguments.length) { return this.runnable().timeout(); } this.runnable().timeout(ms); return this; }; /** * Set test timeout `enabled`. * * @api private * @param {boolean} enabled * @return {Context} self */ Context.prototype.enableTimeouts = function(enabled) { this.runnable().enableTimeouts(enabled); return this; }; /** * Set test slowness threshold `ms`. * * @api private * @param {number} ms * @return {Context} self */ Context.prototype.slow = function(ms) { this.runnable().slow(ms); return this; }; /** * Mark a test as skipped. * * @api private * @return {Context} self */ Context.prototype.skip = function() { this.runnable().skip(); return this; }; /** * Inspect the context void of `._runnable`. * * @api private * @return {string} */ Context.prototype.inspect = function() { return JSON.stringify( this, function(key, val) { return key === 'runnable' || key === 'test' ? undefined : val; }, 2 ); }; }, {} ], 7: [ function(require, module, exports) { /** * Module dependencies. */ var Runnable = require('./runnable'); var inherits = require('./utils').inherits; /** * Expose `Hook`. */ module.exports = Hook; /** * Initialize a new `Hook` with the given `title` and callback `fn`. * * @param {String} title * @param {Function} fn * @api private */ function Hook(title, fn) { Runnable.call(this, title, fn); this.type = 'hook'; } /** * Inherit from `Runnable.prototype`. */ inherits(Hook, Runnable); /** * Get or set the test `err`. * * @param {Error} err * @return {Error} * @api public */ Hook.prototype.error = function(err) { if (!arguments.length) { err = this._error; this._error = null; return err; } this._error = err; }; }, { './runnable': 35, './utils': 39 } ], 8: [ function(require, module, exports) { /** * Module dependencies. */ var Suite = require('../suite'); var Test = require('../test'); var escapeRe = require('escape-string-regexp'); /** * BDD-style interface: * * describe('Array', function() { * describe('#indexOf()', function() { * it('should return -1 when not present', function() { * // ... * }); * * it('should return the index when present', function() { * // ... * }); * }); * }); * * @param {Suite} suite Root suite. */ module.exports = function(suite) { var suites = [suite]; suite.on('pre-require', function(context, file, mocha) { var common = require('./common')(suites, context); context.before = common.before; context.after = common.after; context.beforeEach = common.beforeEach; context.afterEach = common.afterEach; context.run = mocha.options.delay && common.runWithSuite(suite); /** * Describe a "suite" with the given `title` * and callback `fn` containing nested suites * and/or tests. */ context.describe = context.context = function(title, fn) { var suite = Suite.create(suites[0], title); suite.file = file; suites.unshift(suite); fn.call(suite); suites.shift(); return suite; }; /** * Pending describe. */ context.xdescribe = context.xcontext = context.describe.skip = function( title, fn ) { var suite = Suite.create(suites[0], title); suite.pending = true; suites.unshift(suite); fn.call(suite); suites.shift(); }; /** * Exclusive suite. */ context.describe.only = function(title, fn) { var suite = context.describe(title, fn); mocha.grep(suite.fullTitle()); return suite; }; /** * Describe a specification or test-case * with the given `title` and callback `fn` * acting as a thunk. */ context.it = context.specify = function(title, fn) { var suite = suites[0]; if (suite.pending) { fn = null; } var test = new Test(title, fn); test.file = file; suite.addTest(test); return test; }; /** * Exclusive test-case. */ context.it.only = function(title, fn) { var test = context.it(title, fn); var reString = '^' + escapeRe(test.fullTitle()) + '$'; mocha.grep(new RegExp(reString)); return test; }; /** * Pending test case. */ context.xit = context.xspecify = context.it.skip = function(title) { context.it(title); }; }); }; }, { '../suite': 37, '../test': 38, './common': 9, 'escape-string-regexp': 68 } ], 9: [ function(require, module, exports) { 'use strict'; /** * Functions common to more than one interface. * * @param {Suite[]} suites * @param {Context} context * @return {Object} An object containing common functions. */ module.exports = function(suites, context) { return { /** * This is only present if flag --delay is passed into Mocha. It triggers * root suite execution. * * @param {Suite} suite The root wuite. * @return {Function} A function which runs the root suite */ runWithSuite: function runWithSuite(suite) { return function run() { suite.run(); }; }, /** * Execute before running tests. * * @param {string} name * @param {Function} fn */ before: function(name, fn) { suites[0].beforeAll(name, fn); }, /** * Execute after running tests. * * @param {string} name * @param {Function} fn */ after: function(name, fn) { suites[0].afterAll(name, fn); }, /** * Execute before each test case. * * @param {string} name * @param {Function} fn */ beforeEach: function(name, fn) { suites[0].beforeEach(name, fn); }, /** * Execute after each test case. * * @param {string} name * @param {Function} fn */ afterEach: function(name, fn) { suites[0].afterEach(name, fn); }, test: { /** * Pending test case. * * @param {string} title */ skip: function(title) { context.test(title); } } }; }; }, {} ], 10: [ function(require, module, exports) { /** * Module dependencies. */ var Suite = require('../suite'); var Test = require('../test'); /** * TDD-style interface: * * exports.Array = { * '#indexOf()': { * 'should return -1 when the value is not present': function() { * * }, * * 'should return the correct index when the value is present': function() { * * } * } * }; * * @param {Suite} suite Root suite. */ module.exports = function(suite) { var suites = [suite]; suite.on('require', visit); function visit(obj, file) { var suite; for (var key in obj) { if (typeof obj[key] === 'function') { var fn = obj[key]; switch (key) { case 'before': suites[0].beforeAll(fn); break; case 'after': suites[0].afterAll(fn); break; case 'beforeEach': suites[0].beforeEach(fn); break; case 'afterEach': suites[0].afterEach(fn); break; default: var test = new Test(key, fn); test.file = file; suites[0].addTest(test); } } else { suite = Suite.create(suites[0], key); suites.unshift(suite); visit(obj[key]); suites.shift(); } } } }; }, { '../suite': 37, '../test': 38 } ], 11: [ function(require, module, exports) { exports.bdd = require('./bdd'); exports.tdd = require('./tdd'); exports.qunit = require('./qunit'); exports.exports = require('./exports'); }, { './bdd': 8, './exports': 10, './qunit': 12, './tdd': 13 } ], 12: [ function(require, module, exports) { /** * Module dependencies. */ var Suite = require('../suite'); var Test = require('../test'); var escapeRe = require('escape-string-regexp'); /** * QUnit-style interface: * * suite('Array'); * * test('#length', function() { * var arr = [1,2,3]; * ok(arr.length == 3); * }); * * test('#indexOf()', function() { * var arr = [1,2,3]; * ok(arr.indexOf(1) == 0); * ok(arr.indexOf(2) == 1); * ok(arr.indexOf(3) == 2); * }); * * suite('String'); * * test('#length', function() { * ok('foo'.length == 3); * }); * * @param {Suite} suite Root suite. */ module.exports = function(suite) { var suites = [suite]; suite.on('pre-require', function(context, file, mocha) { var common = require('./common')(suites, context); context.before = common.before; context.after = common.after; context.beforeEach = common.beforeEach; context.afterEach = common.afterEach; context.run = mocha.options.delay && common.runWithSuite(suite); /** * Describe a "suite" with the given `title`. */ context.suite = function(title) { if (suites.length > 1) { suites.shift(); } var suite = Suite.create(suites[0], title); suite.file = file; suites.unshift(suite); return suite; }; /** * Exclusive test-case. */ context.suite.only = function(title, fn) { var suite = context.suite(title, fn); mocha.grep(suite.fullTitle()); }; /** * Describe a specification or test-case * with the given `title` and callback `fn` * acting as a thunk. */ context.test = function(title, fn) { var test = new Test(title, fn); test.file = file; suites[0].addTest(test); return test; }; /** * Exclusive test-case. */ context.test.only = function(title, fn) { var test = context.test(title, fn); var reString = '^' + escapeRe(test.fullTitle()) + '$'; mocha.grep(new RegExp(reString)); }; context.test.skip = common.test.skip; }); }; }, { '../suite': 37, '../test': 38, './common': 9, 'escape-string-regexp': 68 } ], 13: [ function(require, module, exports) { /** * Module dependencies. */ var Suite = require('../suite'); var Test = require('../test'); var escapeRe = require('escape-string-regexp'); /** * TDD-style interface: * * suite('Array', function() { * suite('#indexOf()', function() { * suiteSetup(function() { * * }); * * test('should return -1 when not present', function() { * * }); * * test('should return the index when present', function() { * * }); * * suiteTeardown(function() { * * }); * }); * }); * * @param {Suite} suite Root suite. */ module.exports = function(suite) { var suites = [suite]; suite.on('pre-require', function(context, file, mocha) { var common = require('./common')(suites, context); context.setup = common.beforeEach; context.teardown = common.afterEach; context.suiteSetup = common.before; context.suiteTeardown = common.after; context.run = mocha.options.delay && common.runWithSuite(suite); /** * Describe a "suite" with the given `title` and callback `fn` containing * nested suites and/or tests. */ context.suite = function(title, fn) { var suite = Suite.create(suites[0], title); suite.file = file; suites.unshift(suite); fn.call(suite); suites.shift(); return suite; }; /** * Pending suite. */ context.suite.skip = function(title, fn) { var suite = Suite.create(suites[0], title); suite.pending = true; suites.unshift(suite); fn.call(suite); suites.shift(); }; /** * Exclusive test-case. */ context.suite.only = function(title, fn) { var suite = context.suite(title, fn); mocha.grep(suite.fullTitle()); }; /** * Describe a specification or test-case with the given `title` and * callback `fn` acting as a thunk. */ context.test = function(title, fn) { var suite = suites[0]; if (suite.pending) { fn = null; } var test = new Test(title, fn); test.file = file; suite.addTest(test); return test; }; /** * Exclusive test-case. */ context.test.only = function(title, fn) { var test = context.test(title, fn); var reString = '^' + escapeRe(test.fullTitle()) + '$'; mocha.grep(new RegExp(reString)); }; context.test.skip = common.test.skip; }); }; }, { '../suite': 37, '../test': 38, './common': 9, 'escape-string-regexp': 68 } ], 14: [ function(require, module, exports) { (function(process, global, __dirname) { /*! * mocha * Copyright(c) 2011 TJ Holowaychuk * MIT Licensed */ /** * Module dependencies. */ var escapeRe = require('escape-string-regexp'); var path = require('path'); var reporters = require('./reporters'); var utils = require('./utils'); /** * Expose `Mocha`. */ exports = module.exports = Mocha; /** * To require local UIs and reporters when running in node. */ if (!process.browser) { var cwd = process.cwd(); module.paths.push(cwd, path.join(cwd, 'node_modules')); } /** * Expose internals. */ exports.utils = utils; exports.interfaces = require('./interfaces'); exports.reporters = reporters; exports.Runnable = require('./runnable'); exports.Context = require('./context'); exports.Runner = require('./runner'); exports.Suite = require('./suite'); exports.Hook = require('./hook'); exports.Test = require('./test'); /** * Return image `name` path. * * @api private * @param {string} name * @return {string} */ function image(name) { return path.join(__dirname, '../images', name + '.png'); } /** * Set up mocha with `options`. * * Options: * * - `ui` name "bdd", "tdd", "exports" etc * - `reporter` reporter instance, defaults to `mocha.reporters.spec` * - `globals` array of accepted globals * - `timeout` timeout in milliseconds * - `bail` bail on the first test failure * - `slow` milliseconds to wait before considering a test slow * - `ignoreLeaks` ignore global leaks * - `fullTrace` display the full stack-trace on failing * - `grep` string or regexp to filter tests with * * @param {Object} options * @api public */ function Mocha(options) { options = options || {}; this.files = []; this.options = options; if (options.grep) { this.grep(new RegExp(options.grep)); } if (options.fgrep) { this.grep(options.fgrep); } this.suite = new exports.Suite('', new exports.Context()); this.ui(options.ui); this.bail(options.bail); this.reporter(options.reporter, options.reporterOptions); if ( typeof options.timeout !== 'undefined' && options.timeout !== null ) { this.timeout(options.timeout); } this.useColors(options.useColors); if (options.enableTimeouts !== null) { this.enableTimeouts(options.enableTimeouts); } if (options.slow) { this.slow(options.slow); } this.suite.on('pre-require', function(context) { exports.afterEach = context.afterEach || context.teardown; exports.after = context.after || context.suiteTeardown; exports.beforeEach = context.beforeEach || context.setup; exports.before = context.before || context.suiteSetup; exports.describe = context.describe || context.suite; exports.it = context.it || context.test; exports.setup = context.setup || context.beforeEach; exports.suiteSetup = context.suiteSetup || context.before; exports.suiteTeardown = context.suiteTeardown || context.after; exports.suite = context.suite || context.describe; exports.teardown = context.teardown || context.afterEach; exports.test = context.test || context.it; exports.run = context.run; }); } /** * Enable or disable bailing on the first failure. * * @api public * @param {boolean} [bail] */ Mocha.prototype.bail = function(bail) { if (!arguments.length) { bail = true; } this.suite.bail(bail); return this; }; /** * Add test `file`. * * @api public * @param {string} file */ Mocha.prototype.addFile = function(file) { this.files.push(file); return this; }; /** * Set reporter to `reporter`, defaults to "spec". * * @param {String|Function} reporter name or constructor * @param {Object} reporterOptions optional options * @api public * @param {string|Function} reporter name or constructor * @param {Object} reporterOptions optional options */ Mocha.prototype.reporter = function(reporter, reporterOptions) { if (typeof reporter === 'function') { this._reporter = reporter; } else { reporter = reporter || 'spec'; var _reporter; // Try to load a built-in reporter. if (reporters[reporter]) { _reporter = reporters[reporter]; } // Try to load reporters from process.cwd() and node_modules if (!_reporter) { try { _reporter = require(reporter); } catch (err) { err.message.indexOf('Cannot find module') !== -1 ? console.warn('"' + reporter + '" reporter not found') : console.warn( '"' + reporter + '" reporter blew up with error:\n' + err.stack ); } } if (!_reporter && reporter === 'teamcity') { console.warn( 'The Teamcity reporter was moved to a package named ' + 'mocha-teamcity-reporter ' + '(https://npmjs.org/package/mocha-teamcity-reporter).' ); } if (!_reporter) { throw new Error('invalid reporter "' + reporter + '"'); } this._reporter = _reporter; } this.options.reporterOptions = reporterOptions; return this; }; /** * Set test UI `name`, defaults to "bdd". * * @api public * @param {string} bdd */ Mocha.prototype.ui = function(name) { name = name || 'bdd'; this._ui = exports.interfaces[name]; if (!this._ui) { try { this._ui = require(name); } catch (err) { throw new Error('invalid interface "' + name + '"'); } } this._ui = this._ui(this.suite); return this; }; /** * Load registered files. * * @api private */ Mocha.prototype.loadFiles = function(fn) { var self = this; var suite = this.suite; var pending = this.files.length; this.files.forEach(function(file) { file = path.resolve(file); suite.emit('pre-require', global, file, self); suite.emit('require', require(file), file, self); suite.emit('post-require', global, file, self); --pending || (fn && fn()); }); }; /** * Enable growl support. * * @api private */ Mocha.prototype._growl = function(runner, reporter) { var notify = require('growl'); runner.on('end', function() { var stats = reporter.stats; if (stats.failures) { var msg = stats.failures + ' of ' + runner.total + ' tests failed'; notify(msg, { name: 'mocha', title: 'Failed', image: image('error') }); } else { notify( stats.passes + ' tests passed in ' + stats.duration + 'ms', { name: 'mocha', title: 'Passed', image: image('ok') } ); } }); }; /** * Add regexp to grep, if `re` is a string it is escaped. * * @param {RegExp|String} re * @return {Mocha} * @api public * @param {RegExp|string} re * @return {Mocha} */ Mocha.prototype.grep = function(re) { this.options.grep = typeof re === 'string' ? new RegExp(escapeRe(re)) : re; return this; }; /** * Invert `.grep()` matches. * * @return {Mocha} * @api public */ Mocha.prototype.invert = function() { this.options.invert = true; return this; }; /** * Ignore global leaks. * * @param {Boolean} ignore * @return {Mocha} * @api public * @param {boolean} ignore * @return {Mocha} */ Mocha.prototype.ignoreLeaks = function(ignore) { this.options.ignoreLeaks = Boolean(ignore); return this; }; /** * Enable global leak checking. * * @return {Mocha} * @api public */ Mocha.prototype.checkLeaks = function() { this.options.ignoreLeaks = false; return this; }; /** * Display long stack-trace on failing * * @return {Mocha} * @api public */ Mocha.prototype.fullTrace = function() { this.options.fullStackTrace = true; return this; }; /** * Enable growl support. * * @return {Mocha} * @api public */ Mocha.prototype.growl = function() { this.options.growl = true; return this; }; /** * Ignore `globals` array or string. * * @param {Array|String} globals * @return {Mocha} * @api public * @param {Array|string} globals * @return {Mocha} */ Mocha.prototype.globals = function(globals) { this.options.globals = (this.options.globals || []).concat(globals); return this; }; /** * Emit color output. * * @param {Boolean} colors * @return {Mocha} * @api public * @param {boolean} colors * @return {Mocha} */ Mocha.prototype.useColors = function(colors) { if (colors !== undefined) { this.options.useColors = colors; } return this; }; /** * Use inline diffs rather than +/-. * * @param {Boolean} inlineDiffs * @return {Mocha} * @api public * @param {boolean} inlineDiffs * @return {Mocha} */ Mocha.prototype.useInlineDiffs = function(inlineDiffs) { this.options.useInlineDiffs = inlineDiffs !== undefined && inlineDiffs; return this; }; /** * Set the timeout in milliseconds. * * @param {Number} timeout * @return {Mocha} * @api public * @param {number} timeout * @return {Mocha} */ Mocha.prototype.timeout = function(timeout) { this.suite.timeout(timeout); return this; }; /** * Set slowness threshold in milliseconds. * * @param {Number} slow * @return {Mocha} * @api public * @param {number} slow * @return {Mocha} */ Mocha.prototype.slow = function(slow) { this.suite.slow(slow); return this; }; /** * Enable timeouts. * * @param {Boolean} enabled * @return {Mocha} * @api public * @param {boolean} enabled * @return {Mocha} */ Mocha.prototype.enableTimeouts = function(enabled) { this.suite.enableTimeouts( arguments.length && enabled !== undefined ? enabled : true ); return this; }; /** * Makes all tests async (accepting a callback) * * @return {Mocha} * @api public */ Mocha.prototype.asyncOnly = function() { this.options.asyncOnly = true; return this; }; /** * Disable syntax highlighting (in browser). * * @api public */ Mocha.prototype.noHighlighting = function() { this.options.noHighlighting = true; return this; }; /** * Enable uncaught errors to propagate (in browser). * * @return {Mocha} * @api public */ Mocha.prototype.allowUncaught = function() { this.options.allowUncaught = true; return this; }; /** * Delay root suite execution. * @returns {Mocha} */ Mocha.prototype.delay = function delay() { this.options.delay = true; return this; }; /** * Run tests and invoke `fn()` when complete. * * @api public * @param {Function} fn * @return {Runner} */ Mocha.prototype.run = function(fn) { if (this.files.length) { this.loadFiles(); } var suite = this.suite; var options = this.options; options.files = this.files; var runner = new exports.Runner(suite, options.delay); var reporter = new this._reporter(runner, options); runner.ignoreLeaks = options.ignoreLeaks !== false; runner.fullStackTrace = options.fullStackTrace; runner.asyncOnly = options.asyncOnly; runner.allowUncaught = options.allowUncaught; if (options.grep) { runner.grep(options.grep, options.invert); } if (options.globals) { runner.globals(options.globals); } if (options.growl) { this._growl(runner, reporter); } if (options.useColors !== undefined) { exports.reporters.Base.useColors = options.useColors; } exports.reporters.Base.inlineDiffs = options.useInlineDiffs; function done(failures) { if (reporter.done) { reporter.done(failures, fn); } else { fn && fn(failures); } } return runner.run(done); }; }.call( this, require('_process'), typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : {}, '/lib' )); }, { './context': 6, './hook': 7, './interfaces': 11, './reporters': 22, './runnable': 35, './runner': 36, './suite': 37, './test': 38, './utils': 39, _process: 51, 'escape-string-regexp': 68, growl: 69, path: 41 } ], 15: [ function(require, module, exports) { /** * Helpers. */ var s = 1000; var m = s * 60; var h = m * 60; var d = h * 24; var y = d * 365.25; /** * Parse or format the given `val`. * * Options: * * - `long` verbose formatting [false] * * @api public * @param {string|number} val * @param {Object} options * @return {string|number} */ module.exports = function(val, options) { options = options || {}; if (typeof val === 'string') { return parse(val); } // https://github.com/mochajs/mocha/pull/1035 return options['long'] ? longFormat(val) : shortFormat(val); }; /** * Parse the given `str` and return milliseconds. * * @api private * @param {string} str * @return {number} */ function parse(str) { var match = /^((?:\d+)?\.?\d+) *(ms|seconds?|s|minutes?|m|hours?|h|days?|d|years?|y)?$/i.exec( str ); if (!match) { return; } var n = parseFloat(match[1]); var type = (match[2] || 'ms').toLowerCase(); switch (type) { case 'years': case 'year': case 'y': return n * y; case 'days': case 'day': case 'd': return n * d; case 'hours': case 'hour': case 'h': return n * h; case 'minutes': case 'minute': case 'm': return n * m; case 'seconds': case 'second': case 's': return n * s; case 'ms': return n; default: // No default case } } /** * Short format for `ms`. * * @api private * @param {number} ms * @return {string} */ function shortFormat(ms) { if (ms >= d) { return Math.round(ms / d) + 'd'; } if (ms >= h) { return Math.round(ms / h) + 'h'; } if (ms >= m) { return Math.round(ms / m) + 'm'; } if (ms >= s) { return Math.round(ms / s) + 's'; } return ms + 'ms'; } /** * Long format for `ms`. * * @api private * @param {number} ms * @return {string} */ function longFormat(ms) { return ( plural(ms, d, 'day') || plural(ms, h, 'hour') || plural(ms, m, 'minute') || plural(ms, s, 'second') || ms + ' ms' ); } /** * Pluralization helper. * * @api private * @param {number} ms * @param {number} n * @param {string} name */ function plural(ms, n, name) { if (ms < n) { return; } if (ms < n * 1.5) { return Math.floor(ms / n) + ' ' + name; } return Math.ceil(ms / n) + ' ' + name + 's'; } }, {} ], 16: [ function(require, module, exports) { /** * Expose `Pending`. */ module.exports = Pending; /** * Initialize a new `Pending` error with the given message. * * @param {string} message */ function Pending(message) { this.message = message; } }, {} ], 17: [ function(require, module, exports) { (function(process, global) { /** * Module dependencies. */ var tty = require('tty'); var diff = require('diff'); var ms = require('../ms'); var utils = require('../utils'); var supportsColor = process.browser ? null : require('supports-color'); /** * Expose `Base`. */ exports = module.exports = Base; /** * Save timer references to avoid Sinon interfering. * See: https://github.com/mochajs/mocha/issues/237 */ /* eslint-disable no-unused-vars, no-native-reassign */ var Date = global.Date; var setTimeout = global.setTimeout; var setInterval = global.setInterval; var clearTimeout = global.clearTimeout; var clearInterval = global.clearInterval; /* eslint-enable no-unused-vars, no-native-reassign */ /** * Check if both stdio streams are associated with a tty. */ var isatty = tty.isatty(1) && tty.isatty(2); /** * Enable coloring by default, except in the browser interface. */ exports.useColors = !process.browser && (supportsColor || process.env.MOCHA_COLORS !== undefined); /** * Inline diffs instead of +/- */ exports.inlineDiffs = false; /** * Default color map. */ exports.colors = { pass: 90, fail: 31, 'bright pass': 92, 'bright fail': 91, 'bright yellow': 93, pending: 36, suite: 0, 'error title': 0, 'error message': 31, 'error stack': 90, checkmark: 32, fast: 90, medium: 33, slow: 31, green: 32, light: 90, 'diff gutter': 90, 'diff added': 32, 'diff removed': 31 }; /** * Default symbol map. */ exports.symbols = { ok: '✓', err: '✖', dot: '․' }; // With node.js on Windows: use symbols available in terminal default fonts if (process.platform === 'win32') { exports.symbols.ok = '\u221A'; exports.symbols.err = '\u00D7'; exports.symbols.dot = '.'; } /** * Color `str` with the given `type`, * allowing colors to be disabled, * as well as user-defined color * schemes. * * @param {string} type * @param {string} str * @return {string} * @api private */ var color = (exports.color = function(type, str) { if (!exports.useColors) { return String(str); } return '\u001b[' + exports.colors[type] + 'm' + str + '\u001b[0m'; }); /** * Expose term window size, with some defaults for when stderr is not a tty. */ exports.window = { width: 75 }; if (isatty) { exports.window.width = process.stdout.getWindowSize ? process.stdout.getWindowSize(1)[0] : tty.getWindowSize()[1]; } /** * Expose some basic cursor interactions that are common among reporters. */ exports.cursor = { hide: function() { isatty && process.stdout.write('\u001b[?25l'); }, show: function() { isatty && process.stdout.write('\u001b[?25h'); }, deleteLine: function() { isatty && process.stdout.write('\u001b[2K'); }, beginningOfLine: function() { isatty && process.stdout.write('\u001b[0G'); }, CR: function() { if (isatty) { exports.cursor.deleteLine(); exports.cursor.beginningOfLine(); } else { process.stdout.write('\r'); } } }; /** * Outut the given `failures` as a list. * * @param {Array} failures * @api public */ exports.list = function(failures) { console.log(); failures.forEach(function(test, i) { // format var fmt = color('error title', ' %s) %s:\n') + color('error message', ' %s') + color('error stack', '\n%s\n'); // msg var msg; var err = test.err; var message; if (err.message) { message = err.message; } else if (typeof err.inspect === 'function') { message = err.inspect() + ''; } else { message = ''; } var stack = err.stack || message; var index = stack.indexOf(message); var actual = err.actual; var expected = err.expected; var escape = true; if (index === -1) { msg = message; } else { index += message.length; msg = stack.slice(0, index); // remove msg from stack stack = stack.slice(index + 1); } // uncaught if (err.uncaught) { msg = 'Uncaught ' + msg; } // explicitly show diff if ( err.showDiff !== false && sameType(actual, expected) && expected !== undefined ) { escape = false; if (!(utils.isString(actual) && utils.isString(expected))) { err.actual = actual = utils.stringify(actual); err.expected = expected = utils.stringify(expected); } fmt = color('error title', ' %s) %s:\n%s') + color('error stack', '\n%s\n'); var match = message.match(/^([^:]+): expected/); msg = '\n ' + color('error message', match ? match[1] : msg); if (exports.inlineDiffs) { msg += inlineDiff(err, escape); } else { msg += unifiedDiff(err, escape); } } // indent stack trace stack = stack.replace(/^/gm, ' '); console.log(fmt, i + 1, test.fullTitle(), msg, stack); }); }; /** * Initialize a new `Base` reporter. * * All other reporters generally * inherit from this reporter, providing * stats such as test duration, number * of tests passed / failed etc. * * @param {Runner} runner * @api public */ function Base(runner) { var stats = (this.stats = { suites: 0, tests: 0, passes: 0, pending: 0, failures: 0 }); var failures = (this.failures = []); if (!runner) { return; } this.runner = runner; runner.stats = stats; runner.on('start', function() { stats.start = new Date(); }); runner.on('suite', function(suite) { stats.suites = stats.suites || 0; suite.root || stats.suites++; }); runner.on('test end', function() { stats.tests = stats.tests || 0; stats.tests++; }); runner.on('pass', function(test) { stats.passes = stats.passes || 0; if (test.duration > test.slow()) { test.speed = 'slow'; } else if (test.duration > test.slow() / 2) { test.speed = 'medium'; } else { test.speed = 'fast'; } stats.passes++; }); runner.on('fail', function(test, err) { stats.failures = stats.failures || 0; stats.failures++; test.err = err; failures.push(test); }); runner.on('end', function() { stats.end = new Date(); stats.duration = new Date() - stats.start; }); runner.on('pending', function() { stats.pending++; }); } /** * Output common epilogue used by many of * the bundled reporters. * * @api public */ Base.prototype.epilogue = function() { var stats = this.stats; var fmt; console.log(); // passes fmt = color('bright pass', ' ') + color('green', ' %d passing') + color('light', ' (%s)'); console.log(fmt, stats.passes || 0, ms(stats.duration)); // pending if (stats.pending) { fmt = color('pending', ' ') + color('pending', ' %d pending'); console.log(fmt, stats.pending); } // failures if (stats.failures) { fmt = color('fail', ' %d failing'); console.log(fmt, stats.failures); Base.list(this.failures); console.log(); } console.log(); }; /** * Pad the given `str` to `len`. * * @api private * @param {string} str * @param {string} len * @return {string} */ function pad(str, len) { str = String(str); return Array(len - str.length + 1).join(' ') + str; } /** * Returns an inline diff between 2 strings with coloured ANSI output * * @api private * @param {Error} err with actual/expected * @param {boolean} escape * @return {string} Diff */ function inlineDiff(err, escape) { var msg = errorDiff(err, 'WordsWithSpace', escape); // linenos var lines = msg.split('\n'); if (lines.length > 4) { var width = String(lines.length).length; msg = lines .map(function(str, i) { return pad(++i, width) + ' |' + ' ' + str; }) .join('\n'); } // legend msg = '\n' + color('diff removed', 'actual') + ' ' + color('diff added', 'expected') + '\n\n' + msg + '\n'; // indent msg = msg.replace(/^/gm, ' '); return msg; } /** * Returns a unified diff between two strings. * * @api private * @param {Error} err with actual/expected * @param {boolean} escape * @return {string} The diff. */ function unifiedDiff(err, escape) { var indent = ' '; function cleanUp(line) { if (escape) { line = escapeInvisibles(line); } if (line[0] === '+') { return indent + colorLines('diff added', line); } if (line[0] === '-') { return indent + colorLines('diff removed', line); } if (line.match(/\@\@/)) { return null; } if (line.match(/\\ No newline/)) { return null; } return indent + line; } function notBlank(line) { return typeof line !== 'undefined' && line !== null; } var msg = diff.createPatch('string', err.actual, err.expected); var lines = msg.split('\n').splice(4); return ( '\n ' + colorLines('diff added', '+ expected') + ' ' + colorLines('diff removed', '- actual') + '\n\n' + lines .map(cleanUp) .filter(notBlank) .join('\n') ); } /** * Return a character diff for `err`. * * @api private * @param {Error} err * @param {string} type * @param {boolean} escape * @return {string} */ function errorDiff(err, type, escape) { var actual = escape ? escapeInvisibles(err.actual) : err.actual; var expected = escape ? escapeInvisibles(err.expected) : err.expected; return diff['diff' + type](actual, expected) .map(function(str) { if (str.added) { return colorLines('diff added', str.value); } if (str.removed) { return colorLines('diff removed', str.value); } return str.value; }) .join(''); } /** * Returns a string with all invisible characters in plain text * * @api private * @param {string} line * @return {string} */ function escapeInvisibles(line) { return line .replace(/\t/g, '') .replace(/\r/g, '') .replace(/\n/g, '\n'); } /** * Color lines for `str`, using the color `name`. * * @api private * @param {string} name * @param {string} str * @return {string} */ function colorLines(name, str) { return str .split('\n') .map(function(str) { return color(name, str); }) .join('\n'); } /** * Object#toString reference. */ var objToString = Object.prototype.toString; /** * Check that a / b have the same type. * * @api private * @param {Object} a * @param {Object} b * @return {boolean} */ function sameType(a, b) { return objToString.call(a) === objToString.call(b); } }.call( this, require('_process'), typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : {} )); }, { '../ms': 15, '../utils': 39, _process: 51, diff: 67, 'supports-color': 41, tty: 5 } ], 18: [ function(require, module, exports) { /** * Module dependencies. */ var Base = require('./base'); var utils = require('../utils'); /** * Expose `Doc`. */ exports = module.exports = Doc; /** * Initialize a new `Doc` reporter. * * @param {Runner} runner * @api public */ function Doc(runner) { Base.call(this, runner); var indents = 2; function indent() { return Array(indents).join(' '); } runner.on('suite', function(suite) { if (suite.root) { return; } ++indents; console.log('%s
      ', indent()); ++indents; console.log('%s

      %s

      ', indent(), utils.escape(suite.title)); console.log('%s
      ', indent()); }); runner.on('suite end', function(suite) { if (suite.root) { return; } console.log('%s
      ', indent()); --indents; console.log('%s
      ', indent()); --indents; }); runner.on('pass', function(test) { console.log('%s
      %s
      ', indent(), utils.escape(test.title)); var code = utils.escape(utils.clean(test.fn.toString())); console.log( '%s
      %s
      ', indent(), code ); }); runner.on('fail', function(test, err) { console.log( '%s
      %s
      ', indent(), utils.escape(test.title) ); var code = utils.escape(utils.clean(test.fn.toString())); console.log( '%s
      %s
      ', indent(), code ); console.log( '%s
      %s
      ', indent(), utils.escape(err) ); }); } }, { '../utils': 39, './base': 17 } ], 19: [ function(require, module, exports) { (function(process) { /** * Module dependencies. */ var Base = require('./base'); var inherits = require('../utils').inherits; var color = Base.color; /** * Expose `Dot`. */ exports = module.exports = Dot; /** * Initialize a new `Dot` matrix test reporter. * * @api public * @param {Runner} runner */ function Dot(runner) { Base.call(this, runner); var self = this; var width = (Base.window.width * 0.75) | 0; var n = -1; runner.on('start', function() { process.stdout.write('\n'); }); runner.on('pending', function() { if (++n % width === 0) { process.stdout.write('\n '); } process.stdout.write(color('pending', Base.symbols.dot)); }); runner.on('pass', function(test) { if (++n % width === 0) { process.stdout.write('\n '); } if (test.speed === 'slow') { process.stdout.write(color('bright yellow', Base.symbols.dot)); } else { process.stdout.write(color(test.speed, Base.symbols.dot)); } }); runner.on('fail', function() { if (++n % width === 0) { process.stdout.write('\n '); } process.stdout.write(color('fail', Base.symbols.dot)); }); runner.on('end', function() { console.log(); self.epilogue(); }); } /** * Inherit from `Base.prototype`. */ inherits(Dot, Base); }.call(this, require('_process'))); }, { '../utils': 39, './base': 17, _process: 51 } ], 20: [ function(require, module, exports) { (function(process, __dirname) { /** * Module dependencies. */ var JSONCov = require('./json-cov'); var readFileSync = require('fs').readFileSync; var join = require('path').join; /** * Expose `HTMLCov`. */ exports = module.exports = HTMLCov; /** * Initialize a new `JsCoverage` reporter. * * @api public * @param {Runner} runner */ function HTMLCov(runner) { var jade = require('jade'); var file = join(__dirname, '/templates/coverage.jade'); var str = readFileSync(file, 'utf8'); var fn = jade.compile(str, { filename: file }); var self = this; JSONCov.call(this, runner, false); runner.on('end', function() { process.stdout.write( fn({ cov: self.cov, coverageClass: coverageClass }) ); }); } /** * Return coverage class for a given coverage percentage. * * @api private * @param {number} coveragePctg * @return {string} */ function coverageClass(coveragePctg) { if (coveragePctg >= 75) { return 'high'; } if (coveragePctg >= 50) { return 'medium'; } if (coveragePctg >= 25) { return 'low'; } return 'terrible'; } }.call(this, require('_process'), '/lib/reporters')); }, { './json-cov': 23, _process: 51, fs: 41, jade: 41, path: 41 } ], 21: [ function(require, module, exports) { (function(global) { /* eslint-env browser */ /** * Module dependencies. */ var Base = require('./base'); var utils = require('../utils'); var Progress = require('../browser/progress'); var escapeRe = require('escape-string-regexp'); var escape = utils.escape; /** * Save timer references to avoid Sinon interfering (see GH-237). */ /* eslint-disable no-unused-vars, no-native-reassign */ var Date = global.Date; var setTimeout = global.setTimeout; var setInterval = global.setInterval; var clearTimeout = global.clearTimeout; var clearInterval = global.clearInterval; /* eslint-enable no-unused-vars, no-native-reassign */ /** * Expose `HTML`. */ exports = module.exports = HTML; /** * Stats template. */ var statsTemplate = ''; /** * Initialize a new `HTML` reporter. * * @api public * @param {Runner} runner */ function HTML(runner) { Base.call(this, runner); var self = this; var stats = this.stats; var stat = fragment(statsTemplate); var items = stat.getElementsByTagName('li'); var passes = items[1].getElementsByTagName('em')[0]; var passesLink = items[1].getElementsByTagName('a')[0]; var failures = items[2].getElementsByTagName('em')[0]; var failuresLink = items[2].getElementsByTagName('a')[0]; var duration = items[3].getElementsByTagName('em')[0]; var canvas = stat.getElementsByTagName('canvas')[0]; var report = fragment('
        '); var stack = [report]; var progress; var ctx; var root = document.getElementById('mocha'); if (canvas.getContext) { var ratio = window.devicePixelRatio || 1; canvas.style.width = canvas.width; canvas.style.height = canvas.height; canvas.width *= ratio; canvas.height *= ratio; ctx = canvas.getContext('2d'); ctx.scale(ratio, ratio); progress = new Progress(); } if (!root) { return error('#mocha div missing, add it to your document'); } // pass toggle on(passesLink, 'click', function() { unhide(); var name = /pass/.test(report.className) ? '' : ' pass'; report.className = report.className.replace(/fail|pass/g, '') + name; if (report.className.trim()) { hideSuitesWithout('test pass'); } }); // failure toggle on(failuresLink, 'click', function() { unhide(); var name = /fail/.test(report.className) ? '' : ' fail'; report.className = report.className.replace(/fail|pass/g, '') + name; if (report.className.trim()) { hideSuitesWithout('test fail'); } }); root.appendChild(stat); root.appendChild(report); if (progress) { progress.size(40); } runner.on('suite', function(suite) { if (suite.root) { return; } // suite var url = self.suiteURL(suite); var el = fragment( '
      • %s

      • ', url, escape(suite.title) ); // container stack[0].appendChild(el); stack.unshift(document.createElement('ul')); el.appendChild(stack[0]); }); runner.on('suite end', function(suite) { if (suite.root) { return; } stack.shift(); }); runner.on('fail', function(test) { if (test.type === 'hook') { runner.emit('test end', test); } }); runner.on('test end', function(test) { // TODO: add to stats var percent = ((stats.tests / this.total) * 100) | 0; if (progress) { progress.update(percent).draw(ctx); } // update stats var ms = new Date() - stats.start; text(passes, stats.passes); text(failures, stats.failures); text(duration, (ms / 1000).toFixed(2)); // test var el; if (test.state === 'passed') { var url = self.testURL(test); el = fragment( '
      • %e%ems

      • ', test.speed, test.title, test.duration, url ); } else if (test.pending) { el = fragment( '
      • %e

      • ', test.title ); } else { el = fragment( '
      • %e

      • ', test.title, self.testURL(test) ); var stackString; // Note: Includes leading newline var message = test.err.toString(); // <=IE7 stringifies to [Object Error]. Since it can be overloaded, we // check for the result of the stringifying. if (message === '[object Error]') { message = test.err.message; } if (test.err.stack) { var indexOfMessage = test.err.stack.indexOf(test.err.message); if (indexOfMessage === -1) { stackString = test.err.stack; } else { stackString = test.err.stack.substr( test.err.message.length + indexOfMessage ); } } else if (test.err.sourceURL && test.err.line !== undefined) { // Safari doesn't give you a stack. Let's at least provide a source line. stackString = '\n(' + test.err.sourceURL + ':' + test.err.line + ')'; } stackString = stackString || ''; if (test.err.htmlMessage && stackString) { el.appendChild( fragment( '
        %s\n
        %e
        ', test.err.htmlMessage, stackString ) ); } else if (test.err.htmlMessage) { el.appendChild( fragment( '
        %s
        ', test.err.htmlMessage ) ); } else { el.appendChild( fragment( '
        %e%e
        ', message, stackString ) ); } } // toggle code // TODO: defer if (!test.pending) { var h2 = el.getElementsByTagName('h2')[0]; on(h2, 'click', function() { pre.style.display = pre.style.display === 'none' ? 'block' : 'none'; }); var pre = fragment( '
        %e
        ', utils.clean(test.fn.toString()) ); el.appendChild(pre); pre.style.display = 'none'; } // Don't call .appendChild if #mocha-report was already .shift()'ed off the stack. if (stack[0]) { stack[0].appendChild(el); } }); } /** * Makes a URL, preserving querystring ("search") parameters. * * @param {string} s * @return {string} A new URL. */ function makeUrl(s) { var search = window.location.search; // Remove previous grep query parameter if present if (search) { search = search .replace(/[?&]grep=[^&\s]*/g, '') .replace(/^&/, '?'); } return ( window.location.pathname + (search ? search + '&' : '?') + 'grep=' + encodeURIComponent(escapeRe(s)) ); } /** * Provide suite URL. * * @param {Object} [suite] */ HTML.prototype.suiteURL = function(suite) { return makeUrl(suite.fullTitle()); }; /** * Provide test URL. * * @param {Object} [test] */ HTML.prototype.testURL = function(test) { return makeUrl(test.fullTitle()); }; /** * Display error `msg`. * * @param {string} msg */ function error(msg) { document.body.appendChild( fragment('
        %s
        ', msg) ); } /** * Return a DOM fragment from `html`. * * @param {string} html */ function fragment(html) { var args = arguments; var div = document.createElement('div'); var i = 1; div.innerHTML = html.replace(/%([se])/g, function(_, type) { switch (type) { case 's': return String(args[i++]); case 'e': return escape(args[i++]); // no default } }); return div.firstChild; } /** * Check for suites that do not have elements * with `classname`, and hide them. * * @param {text} classname */ function hideSuitesWithout(classname) { var suites = document.getElementsByClassName('suite'); for (var i = 0; i < suites.length; i++) { var els = suites[i].getElementsByClassName(classname); if (!els.length) { suites[i].className += ' hidden'; } } } /** * Unhide .hidden suites. */ function unhide() { var els = document.getElementsByClassName('suite hidden'); for (var i = 0; i < els.length; ++i) { els[i].className = els[i].className.replace( 'suite hidden', 'suite' ); } } /** * Set an element's text contents. * * @param {HTMLElement} el * @param {string} contents */ function text(el, contents) { if (el.textContent) { el.textContent = contents; } else { el.innerText = contents; } } /** * Listen on `event` with callback `fn`. */ function on(el, event, fn) { if (el.addEventListener) { el.addEventListener(event, fn, false); } else { el.attachEvent('on' + event, fn); } } }.call( this, typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : {} )); }, { '../browser/progress': 4, '../utils': 39, './base': 17, 'escape-string-regexp': 68 } ], 22: [ function(require, module, exports) { // Alias exports to a their normalized format Mocha#reporter to prevent a need // for dynamic (try/catch) requires, which Browserify doesn't handle. exports.Base = exports.base = require('./base'); exports.Dot = exports.dot = require('./dot'); exports.Doc = exports.doc = require('./doc'); exports.TAP = exports.tap = require('./tap'); exports.JSON = exports.json = require('./json'); exports.HTML = exports.html = require('./html'); exports.List = exports.list = require('./list'); exports.Min = exports.min = require('./min'); exports.Spec = exports.spec = require('./spec'); exports.Nyan = exports.nyan = require('./nyan'); exports.XUnit = exports.xunit = require('./xunit'); exports.Markdown = exports.markdown = require('./markdown'); exports.Progress = exports.progress = require('./progress'); exports.Landing = exports.landing = require('./landing'); exports.JSONCov = exports['json-cov'] = require('./json-cov'); exports.HTMLCov = exports['html-cov'] = require('./html-cov'); exports.JSONStream = exports['json-stream'] = require('./json-stream'); }, { './base': 17, './doc': 18, './dot': 19, './html': 21, './html-cov': 20, './json': 25, './json-cov': 23, './json-stream': 24, './landing': 26, './list': 27, './markdown': 28, './min': 29, './nyan': 30, './progress': 31, './spec': 32, './tap': 33, './xunit': 34 } ], 23: [ function(require, module, exports) { (function(process, global) { /** * Module dependencies. */ var Base = require('./base'); /** * Expose `JSONCov`. */ exports = module.exports = JSONCov; /** * Initialize a new `JsCoverage` reporter. * * @api public * @param {Runner} runner * @param {boolean} output */ function JSONCov(runner, output) { Base.call(this, runner); output = arguments.length === 1 || output; var self = this; var tests = []; var failures = []; var passes = []; runner.on('test end', function(test) { tests.push(test); }); runner.on('pass', function(test) { passes.push(test); }); runner.on('fail', function(test) { failures.push(test); }); runner.on('end', function() { var cov = global._$jscoverage || {}; var result = (self.cov = map(cov)); result.stats = self.stats; result.tests = tests.map(clean); result.failures = failures.map(clean); result.passes = passes.map(clean); if (!output) { return; } process.stdout.write(JSON.stringify(result, null, 2)); }); } /** * Map jscoverage data to a JSON structure * suitable for reporting. * * @api private * @param {Object} cov * @return {Object} */ function map(cov) { var ret = { instrumentation: 'node-jscoverage', sloc: 0, hits: 0, misses: 0, coverage: 0, files: [] }; for (var filename in cov) { if (Object.prototype.hasOwnProperty.call(cov, filename)) { var data = coverage(filename, cov[filename]); ret.files.push(data); ret.hits += data.hits; ret.misses += data.misses; ret.sloc += data.sloc; } } ret.files.sort(function(a, b) { return a.filename.localeCompare(b.filename); }); if (ret.sloc > 0) { ret.coverage = (ret.hits / ret.sloc) * 100; } return ret; } /** * Map jscoverage data for a single source file * to a JSON structure suitable for reporting. * * @api private * @param {string} filename name of the source file * @param {Object} data jscoverage coverage data * @return {Object} */ function coverage(filename, data) { var ret = { filename: filename, coverage: 0, hits: 0, misses: 0, sloc: 0, source: {} }; data.source.forEach(function(line, num) { num++; if (data[num] === 0) { ret.misses++; ret.sloc++; } else if (data[num] !== undefined) { ret.hits++; ret.sloc++; } ret.source[num] = { source: line, coverage: data[num] === undefined ? '' : data[num] }; }); ret.coverage = (ret.hits / ret.sloc) * 100; return ret; } /** * Return a plain-object representation of `test` * free of cyclic properties etc. * * @api private * @param {Object} test * @return {Object} */ function clean(test) { return { duration: test.duration, fullTitle: test.fullTitle(), title: test.title }; } }.call( this, require('_process'), typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : {} )); }, { './base': 17, _process: 51 } ], 24: [ function(require, module, exports) { (function(process) { /** * Module dependencies. */ var Base = require('./base'); /** * Expose `List`. */ exports = module.exports = List; /** * Initialize a new `List` test reporter. * * @api public * @param {Runner} runner */ function List(runner) { Base.call(this, runner); var self = this; var total = runner.total; runner.on('start', function() { console.log(JSON.stringify(['start', { total: total }])); }); runner.on('pass', function(test) { console.log(JSON.stringify(['pass', clean(test)])); }); runner.on('fail', function(test, err) { test = clean(test); test.err = err.message; test.stack = err.stack || null; console.log(JSON.stringify(['fail', test])); }); runner.on('end', function() { process.stdout.write(JSON.stringify(['end', self.stats])); }); } /** * Return a plain-object representation of `test` * free of cyclic properties etc. * * @api private * @param {Object} test * @return {Object} */ function clean(test) { return { title: test.title, fullTitle: test.fullTitle(), duration: test.duration }; } }.call(this, require('_process'))); }, { './base': 17, _process: 51 } ], 25: [ function(require, module, exports) { (function(process) { /** * Module dependencies. */ var Base = require('./base'); /** * Expose `JSON`. */ exports = module.exports = JSONReporter; /** * Initialize a new `JSON` reporter. * * @api public * @param {Runner} runner */ function JSONReporter(runner) { Base.call(this, runner); var self = this; var tests = []; var pending = []; var failures = []; var passes = []; runner.on('test end', function(test) { tests.push(test); }); runner.on('pass', function(test) { passes.push(test); }); runner.on('fail', function(test) { failures.push(test); }); runner.on('pending', function(test) { pending.push(test); }); runner.on('end', function() { var obj = { stats: self.stats, tests: tests.map(clean), pending: pending.map(clean), failures: failures.map(clean), passes: passes.map(clean) }; runner.testResults = obj; process.stdout.write(JSON.stringify(obj, null, 2)); }); } /** * Return a plain-object representation of `test` * free of cyclic properties etc. * * @api private * @param {Object} test * @return {Object} */ function clean(test) { return { title: test.title, fullTitle: test.fullTitle(), duration: test.duration, err: errorJSON(test.err || {}) }; } /** * Transform `error` into a JSON object. * * @api private * @param {Error} err * @return {Object} */ function errorJSON(err) { var res = {}; Object.getOwnPropertyNames(err).forEach(function(key) { res[key] = err[key]; }, err); return res; } }.call(this, require('_process'))); }, { './base': 17, _process: 51 } ], 26: [ function(require, module, exports) { (function(process) { /** * Module dependencies. */ var Base = require('./base'); var inherits = require('../utils').inherits; var cursor = Base.cursor; var color = Base.color; /** * Expose `Landing`. */ exports = module.exports = Landing; /** * Airplane color. */ Base.colors.plane = 0; /** * Airplane crash color. */ Base.colors['plane crash'] = 31; /** * Runway color. */ Base.colors.runway = 90; /** * Initialize a new `Landing` reporter. * * @api public * @param {Runner} runner */ function Landing(runner) { Base.call(this, runner); var self = this; var width = (Base.window.width * 0.75) | 0; var total = runner.total; var stream = process.stdout; var plane = color('plane', '✈'); var crashed = -1; var n = 0; function runway() { var buf = Array(width).join('-'); return ' ' + color('runway', buf); } runner.on('start', function() { stream.write('\n\n\n '); cursor.hide(); }); runner.on('test end', function(test) { // check if the plane crashed var col = crashed === -1 ? ((width * ++n) / total) | 0 : crashed; // show the crash if (test.state === 'failed') { plane = color('plane crash', '✈'); crashed = col; } // render landing strip stream.write('\u001b[' + (width + 1) + 'D\u001b[2A'); stream.write(runway()); stream.write('\n '); stream.write(color('runway', Array(col).join('⋅'))); stream.write(plane); stream.write( color('runway', Array(width - col).join('⋅') + '\n') ); stream.write(runway()); stream.write('\u001b[0m'); }); runner.on('end', function() { cursor.show(); console.log(); self.epilogue(); }); } /** * Inherit from `Base.prototype`. */ inherits(Landing, Base); }.call(this, require('_process'))); }, { '../utils': 39, './base': 17, _process: 51 } ], 27: [ function(require, module, exports) { (function(process) { /** * Module dependencies. */ var Base = require('./base'); var inherits = require('../utils').inherits; var color = Base.color; var cursor = Base.cursor; /** * Expose `List`. */ exports = module.exports = List; /** * Initialize a new `List` test reporter. * * @api public * @param {Runner} runner */ function List(runner) { Base.call(this, runner); var self = this; var n = 0; runner.on('start', function() { console.log(); }); runner.on('test', function(test) { process.stdout.write( color('pass', ' ' + test.fullTitle() + ': ') ); }); runner.on('pending', function(test) { var fmt = color('checkmark', ' -') + color('pending', ' %s'); console.log(fmt, test.fullTitle()); }); runner.on('pass', function(test) { var fmt = color('checkmark', ' ' + Base.symbols.dot) + color('pass', ' %s: ') + color(test.speed, '%dms'); cursor.CR(); console.log(fmt, test.fullTitle(), test.duration); }); runner.on('fail', function(test) { cursor.CR(); console.log(color('fail', ' %d) %s'), ++n, test.fullTitle()); }); runner.on('end', self.epilogue.bind(self)); } /** * Inherit from `Base.prototype`. */ inherits(List, Base); }.call(this, require('_process'))); }, { '../utils': 39, './base': 17, _process: 51 } ], 28: [ function(require, module, exports) { (function(process) { /** * Module dependencies. */ var Base = require('./base'); var utils = require('../utils'); /** * Constants */ var SUITE_PREFIX = '$'; /** * Expose `Markdown`. */ exports = module.exports = Markdown; /** * Initialize a new `Markdown` reporter. * * @api public * @param {Runner} runner */ function Markdown(runner) { Base.call(this, runner); var level = 0; var buf = ''; function title(str) { return Array(level).join('#') + ' ' + str; } function mapTOC(suite, obj) { var ret = obj; var key = SUITE_PREFIX + suite.title; obj = obj[key] = obj[key] || { suite: suite }; suite.suites.forEach(function(suite) { mapTOC(suite, obj); }); return ret; } function stringifyTOC(obj, level) { ++level; var buf = ''; var link; for (var key in obj) { if (key === 'suite') { continue; } if (key !== SUITE_PREFIX) { link = ' - [' + key.substring(1) + ']'; link += '(#' + utils.slug(obj[key].suite.fullTitle()) + ')\n'; buf += Array(level).join(' ') + link; } buf += stringifyTOC(obj[key], level); } return buf; } function generateTOC(suite) { var obj = mapTOC(suite, {}); return stringifyTOC(obj, 0); } generateTOC(runner.suite); runner.on('suite', function(suite) { ++level; var slug = utils.slug(suite.fullTitle()); buf += '' + '\n'; buf += title(suite.title) + '\n'; }); runner.on('suite end', function() { --level; }); runner.on('pass', function(test) { var code = utils.clean(test.fn.toString()); buf += test.title + '.\n'; buf += '\n```js\n'; buf += code + '\n'; buf += '```\n\n'; }); runner.on('end', function() { process.stdout.write('# TOC\n'); process.stdout.write(generateTOC(runner.suite)); process.stdout.write(buf); }); } }.call(this, require('_process'))); }, { '../utils': 39, './base': 17, _process: 51 } ], 29: [ function(require, module, exports) { (function(process) { /** * Module dependencies. */ var Base = require('./base'); var inherits = require('../utils').inherits; /** * Expose `Min`. */ exports = module.exports = Min; /** * Initialize a new `Min` minimal test reporter (best used with --watch). * * @api public * @param {Runner} runner */ function Min(runner) { Base.call(this, runner); runner.on('start', function() { // clear screen process.stdout.write('\u001b[2J'); // set cursor position process.stdout.write('\u001b[1;3H'); }); runner.on('end', this.epilogue.bind(this)); } /** * Inherit from `Base.prototype`. */ inherits(Min, Base); }.call(this, require('_process'))); }, { '../utils': 39, './base': 17, _process: 51 } ], 30: [ function(require, module, exports) { (function(process) { /** * Module dependencies. */ var Base = require('./base'); var inherits = require('../utils').inherits; /** * Expose `Dot`. */ exports = module.exports = NyanCat; /** * Initialize a new `Dot` matrix test reporter. * * @param {Runner} runner * @api public */ function NyanCat(runner) { Base.call(this, runner); var self = this; var width = (Base.window.width * 0.75) | 0; var nyanCatWidth = (this.nyanCatWidth = 11); this.colorIndex = 0; this.numberOfLines = 4; this.rainbowColors = self.generateColors(); this.scoreboardWidth = 5; this.tick = 0; this.trajectories = [[], [], [], []]; this.trajectoryWidthMax = width - nyanCatWidth; runner.on('start', function() { Base.cursor.hide(); self.draw(); }); runner.on('pending', function() { self.draw(); }); runner.on('pass', function() { self.draw(); }); runner.on('fail', function() { self.draw(); }); runner.on('end', function() { Base.cursor.show(); for (var i = 0; i < self.numberOfLines; i++) { write('\n'); } self.epilogue(); }); } /** * Inherit from `Base.prototype`. */ inherits(NyanCat, Base); /** * Draw the nyan cat * * @api private */ NyanCat.prototype.draw = function() { this.appendRainbow(); this.drawScoreboard(); this.drawRainbow(); this.drawNyanCat(); this.tick = !this.tick; }; /** * Draw the "scoreboard" showing the number * of passes, failures and pending tests. * * @api private */ NyanCat.prototype.drawScoreboard = function() { var stats = this.stats; function draw(type, n) { write(' '); write(Base.color(type, n)); write('\n'); } draw('green', stats.passes); draw('fail', stats.failures); draw('pending', stats.pending); write('\n'); this.cursorUp(this.numberOfLines); }; /** * Append the rainbow. * * @api private */ NyanCat.prototype.appendRainbow = function() { var segment = this.tick ? '_' : '-'; var rainbowified = this.rainbowify(segment); for (var index = 0; index < this.numberOfLines; index++) { var trajectory = this.trajectories[index]; if (trajectory.length >= this.trajectoryWidthMax) { trajectory.shift(); } trajectory.push(rainbowified); } }; /** * Draw the rainbow. * * @api private */ NyanCat.prototype.drawRainbow = function() { var self = this; this.trajectories.forEach(function(line) { write('\u001b[' + self.scoreboardWidth + 'C'); write(line.join('')); write('\n'); }); this.cursorUp(this.numberOfLines); }; /** * Draw the nyan cat * * @api private */ NyanCat.prototype.drawNyanCat = function() { var self = this; var startWidth = this.scoreboardWidth + this.trajectories[0].length; var dist = '\u001b[' + startWidth + 'C'; var padding = ''; write(dist); write('_,------,'); write('\n'); write(dist); padding = self.tick ? ' ' : ' '; write('_|' + padding + '/\\_/\\ '); write('\n'); write(dist); padding = self.tick ? '_' : '__'; var tail = self.tick ? '~' : '^'; write(tail + '|' + padding + this.face() + ' '); write('\n'); write(dist); padding = self.tick ? ' ' : ' '; write(padding + '"" "" '); write('\n'); this.cursorUp(this.numberOfLines); }; /** * Draw nyan cat face. * * @api private * @return {string} */ NyanCat.prototype.face = function() { var stats = this.stats; if (stats.failures) { return '( x .x)'; } else if (stats.pending) { return '( o .o)'; } else if (stats.passes) { return '( ^ .^)'; } return '( - .-)'; }; /** * Move cursor up `n`. * * @api private * @param {number} n */ NyanCat.prototype.cursorUp = function(n) { write('\u001b[' + n + 'A'); }; /** * Move cursor down `n`. * * @api private * @param {number} n */ NyanCat.prototype.cursorDown = function(n) { write('\u001b[' + n + 'B'); }; /** * Generate rainbow colors. * * @api private * @return {Array} */ NyanCat.prototype.generateColors = function() { var colors = []; for (var i = 0; i < 6 * 7; i++) { var pi3 = Math.floor(Math.PI / 3); var n = i * (1.0 / 6); var r = Math.floor(3 * Math.sin(n) + 3); var g = Math.floor(3 * Math.sin(n + 2 * pi3) + 3); var b = Math.floor(3 * Math.sin(n + 4 * pi3) + 3); colors.push(36 * r + 6 * g + b + 16); } return colors; }; /** * Apply rainbow to the given `str`. * * @api private * @param {string} str * @return {string} */ NyanCat.prototype.rainbowify = function(str) { if (!Base.useColors) { return str; } var color = this.rainbowColors[ this.colorIndex % this.rainbowColors.length ]; this.colorIndex += 1; return '\u001b[38;5;' + color + 'm' + str + '\u001b[0m'; }; /** * Stdout helper. * * @param {string} string A message to write to stdout. */ function write(string) { process.stdout.write(string); } }.call(this, require('_process'))); }, { '../utils': 39, './base': 17, _process: 51 } ], 31: [ function(require, module, exports) { (function(process) { /** * Module dependencies. */ var Base = require('./base'); var inherits = require('../utils').inherits; var color = Base.color; var cursor = Base.cursor; /** * Expose `Progress`. */ exports = module.exports = Progress; /** * General progress bar color. */ Base.colors.progress = 90; /** * Initialize a new `Progress` bar test reporter. * * @api public * @param {Runner} runner * @param {Object} options */ function Progress(runner, options) { Base.call(this, runner); var self = this; var width = (Base.window.width * 0.5) | 0; var total = runner.total; var complete = 0; var lastN = -1; // default chars options = options || {}; options.open = options.open || '['; options.complete = options.complete || '▬'; options.incomplete = options.incomplete || Base.symbols.dot; options.close = options.close || ']'; options.verbose = false; // tests started runner.on('start', function() { console.log(); cursor.hide(); }); // tests complete runner.on('test end', function() { complete++; var percent = complete / total; var n = (width * percent) | 0; var i = width - n; if (n === lastN && !options.verbose) { // Don't re-render the line if it hasn't changed return; } lastN = n; cursor.CR(); process.stdout.write('\u001b[J'); process.stdout.write(color('progress', ' ' + options.open)); process.stdout.write(Array(n).join(options.complete)); process.stdout.write(Array(i).join(options.incomplete)); process.stdout.write(color('progress', options.close)); if (options.verbose) { process.stdout.write( color('progress', ' ' + complete + ' of ' + total) ); } }); // tests are complete, output some stats // and the failures if any runner.on('end', function() { cursor.show(); console.log(); self.epilogue(); }); } /** * Inherit from `Base.prototype`. */ inherits(Progress, Base); }.call(this, require('_process'))); }, { '../utils': 39, './base': 17, _process: 51 } ], 32: [ function(require, module, exports) { /** * Module dependencies. */ var Base = require('./base'); var inherits = require('../utils').inherits; var color = Base.color; var cursor = Base.cursor; /** * Expose `Spec`. */ exports = module.exports = Spec; /** * Initialize a new `Spec` test reporter. * * @api public * @param {Runner} runner */ function Spec(runner) { Base.call(this, runner); var self = this; var indents = 0; var n = 0; function indent() { return Array(indents).join(' '); } runner.on('start', function() { console.log(); }); runner.on('suite', function(suite) { ++indents; console.log(color('suite', '%s%s'), indent(), suite.title); }); runner.on('suite end', function() { --indents; if (indents === 1) { console.log(); } }); runner.on('pending', function(test) { var fmt = indent() + color('pending', ' - %s'); console.log(fmt, test.title); }); runner.on('pass', function(test) { var fmt; if (test.speed === 'fast') { fmt = indent() + color('checkmark', ' ' + Base.symbols.ok) + color('pass', ' %s'); cursor.CR(); console.log(fmt, test.title); } else { fmt = indent() + color('checkmark', ' ' + Base.symbols.ok) + color('pass', ' %s') + color(test.speed, ' (%dms)'); cursor.CR(); console.log(fmt, test.title, test.duration); } }); runner.on('fail', function(test) { cursor.CR(); console.log(indent() + color('fail', ' %d) %s'), ++n, test.title); }); runner.on('end', self.epilogue.bind(self)); } /** * Inherit from `Base.prototype`. */ inherits(Spec, Base); }, { '../utils': 39, './base': 17 } ], 33: [ function(require, module, exports) { /** * Module dependencies. */ var Base = require('./base'); /** * Expose `TAP`. */ exports = module.exports = TAP; /** * Initialize a new `TAP` reporter. * * @api public * @param {Runner} runner */ function TAP(runner) { Base.call(this, runner); var n = 1; var passes = 0; var failures = 0; runner.on('start', function() { var total = runner.grepTotal(runner.suite); console.log('%d..%d', 1, total); }); runner.on('test end', function() { ++n; }); runner.on('pending', function(test) { console.log('ok %d %s # SKIP -', n, title(test)); }); runner.on('pass', function(test) { passes++; console.log('ok %d %s', n, title(test)); }); runner.on('fail', function(test, err) { failures++; console.log('not ok %d %s', n, title(test)); if (err.stack) { console.log(err.stack.replace(/^/gm, ' ')); } }); runner.on('end', function() { console.log('# tests ' + (passes + failures)); console.log('# pass ' + passes); console.log('# fail ' + failures); }); } /** * Return a TAP-safe title of `test` * * @api private * @param {Object} test * @return {String} */ function title(test) { return test.fullTitle().replace(/#/g, ''); } }, { './base': 17 } ], 34: [ function(require, module, exports) { (function(global) { /** * Module dependencies. */ var Base = require('./base'); var utils = require('../utils'); var inherits = utils.inherits; var fs = require('fs'); var escape = utils.escape; /** * Save timer references to avoid Sinon interfering (see GH-237). */ /* eslint-disable no-unused-vars, no-native-reassign */ var Date = global.Date; var setTimeout = global.setTimeout; var setInterval = global.setInterval; var clearTimeout = global.clearTimeout; var clearInterval = global.clearInterval; /* eslint-enable no-unused-vars, no-native-reassign */ /** * Expose `XUnit`. */ exports = module.exports = XUnit; /** * Initialize a new `XUnit` reporter. * * @api public * @param {Runner} runner */ function XUnit(runner, options) { Base.call(this, runner); var stats = this.stats; var tests = []; var self = this; if (options.reporterOptions && options.reporterOptions.output) { if (!fs.createWriteStream) { throw new Error('file output not supported in browser'); } self.fileStream = fs.createWriteStream( options.reporterOptions.output ); } runner.on('pending', function(test) { tests.push(test); }); runner.on('pass', function(test) { tests.push(test); }); runner.on('fail', function(test) { tests.push(test); }); runner.on('end', function() { self.write( tag( 'testsuite', { name: 'Mocha Tests', tests: stats.tests, failures: stats.failures, errors: stats.failures, skipped: stats.tests - stats.failures - stats.passes, timestamp: new Date().toUTCString(), time: stats.duration / 1000 || 0 }, false ) ); tests.forEach(function(t) { self.test(t); }); self.write(''); }); } /** * Inherit from `Base.prototype`. */ inherits(XUnit, Base); /** * Override done to close the stream (if it's a file). * * @param failures * @param {Function} fn */ XUnit.prototype.done = function(failures, fn) { if (this.fileStream) { this.fileStream.end(function() { fn(failures); }); } else { fn(failures); } }; /** * Write out the given line. * * @param {string} line */ XUnit.prototype.write = function(line) { if (this.fileStream) { this.fileStream.write(line + '\n'); } else { console.log(line); } }; /** * Output tag for the given `test.` * * @param {Test} test */ XUnit.prototype.test = function(test) { var attrs = { classname: test.parent.fullTitle(), name: test.title, time: test.duration / 1000 || 0 }; if (test.state === 'failed') { var err = test.err; this.write( tag( 'testcase', attrs, false, tag( 'failure', {}, false, cdata(escape(err.message) + '\n' + err.stack) ) ) ); } else if (test.pending) { this.write( tag('testcase', attrs, false, tag('skipped', {}, true)) ); } else { this.write(tag('testcase', attrs, true)); } }; /** * HTML tag helper. * * @param name * @param attrs * @param close * @param content * @return {string} */ function tag(name, attrs, close, content) { var end = close ? '/>' : '>'; var pairs = []; var tag; for (var key in attrs) { if (Object.prototype.hasOwnProperty.call(attrs, key)) { pairs.push(key + '="' + escape(attrs[key]) + '"'); } } tag = '<' + name + (pairs.length ? ' ' + pairs.join(' ') : '') + end; if (content) { tag += content + ''; } }.call( this, typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : {} )); }, { '../utils': 39, './base': 17, fs: 41 } ], 35: [ function(require, module, exports) { (function(global) { /** * Module dependencies. */ var EventEmitter = require('events').EventEmitter; var Pending = require('./pending'); var debug = require('debug')('mocha:runnable'); var milliseconds = require('./ms'); var utils = require('./utils'); var inherits = utils.inherits; /** * Save timer references to avoid Sinon interfering (see GH-237). */ /* eslint-disable no-unused-vars, no-native-reassign */ var Date = global.Date; var setTimeout = global.setTimeout; var setInterval = global.setInterval; var clearTimeout = global.clearTimeout; var clearInterval = global.clearInterval; /* eslint-enable no-unused-vars, no-native-reassign */ /** * Object#toString(). */ var toString = Object.prototype.toString; /** * Expose `Runnable`. */ module.exports = Runnable; /** * Initialize a new `Runnable` with the given `title` and callback `fn`. * * @param {String} title * @param {Function} fn * @api private * @param {string} title * @param {Function} fn */ function Runnable(title, fn) { this.title = title; this.fn = fn; this.async = fn && fn.length; this.sync = !this.async; this._timeout = 2000; this._slow = 75; this._enableTimeouts = true; this.timedOut = false; this._trace = new Error('done() called multiple times'); } /** * Inherit from `EventEmitter.prototype`. */ inherits(Runnable, EventEmitter); /** * Set & get timeout `ms`. * * @api private * @param {number|string} ms * @return {Runnable|number} ms or Runnable instance. */ Runnable.prototype.timeout = function(ms) { if (!arguments.length) { return this._timeout; } if (ms === 0) { this._enableTimeouts = false; } if (typeof ms === 'string') { ms = milliseconds(ms); } debug('timeout %d', ms); this._timeout = ms; if (this.timer) { this.resetTimeout(); } return this; }; /** * Set & get slow `ms`. * * @api private * @param {number|string} ms * @return {Runnable|number} ms or Runnable instance. */ Runnable.prototype.slow = function(ms) { if (!arguments.length) { return this._slow; } if (typeof ms === 'string') { ms = milliseconds(ms); } debug('timeout %d', ms); this._slow = ms; return this; }; /** * Set and get whether timeout is `enabled`. * * @api private * @param {boolean} enabled * @return {Runnable|boolean} enabled or Runnable instance. */ Runnable.prototype.enableTimeouts = function(enabled) { if (!arguments.length) { return this._enableTimeouts; } debug('enableTimeouts %s', enabled); this._enableTimeouts = enabled; return this; }; /** * Halt and mark as pending. * * @api private */ Runnable.prototype.skip = function() { throw new Pending(); }; /** * Return the full title generated by recursively concatenating the parent's * full title. * * @api public * @return {string} */ Runnable.prototype.fullTitle = function() { return this.parent.fullTitle() + ' ' + this.title; }; /** * Clear the timeout. * * @api private */ Runnable.prototype.clearTimeout = function() { clearTimeout(this.timer); }; /** * Inspect the runnable void of private properties. * * @api private * @return {string} */ Runnable.prototype.inspect = function() { return JSON.stringify( this, function(key, val) { if (key[0] === '_') { return; } if (key === 'parent') { return '#'; } if (key === 'ctx') { return '#'; } return val; }, 2 ); }; /** * Reset the timeout. * * @api private */ Runnable.prototype.resetTimeout = function() { var self = this; var ms = this.timeout() || 1e9; if (!this._enableTimeouts) { return; } this.clearTimeout(); this.timer = setTimeout(function() { if (!self._enableTimeouts) { return; } self.callback( new Error( 'timeout of ' + ms + 'ms exceeded. Ensure the done() callback is being called in this test.' ) ); self.timedOut = true; }, ms); }; /** * Whitelist a list of globals for this test run. * * @api private * @param {string[]} globals */ Runnable.prototype.globals = function(globals) { this._allowedGlobals = globals; }; /** * Run the test and invoke `fn(err)`. * * @param {Function} fn * @api private */ Runnable.prototype.run = function(fn) { var self = this; var start = new Date(); var ctx = this.ctx; var finished; var emitted; // Sometimes the ctx exists, but it is not runnable if (ctx && ctx.runnable) { ctx.runnable(this); } // called multiple times function multiple(err) { if (emitted) { return; } emitted = true; self.emit( 'error', err || new Error( 'done() called multiple times; stacktrace may be inaccurate' ) ); } // finished function done(err) { var ms = self.timeout(); if (self.timedOut) { return; } if (finished) { return multiple(err || self._trace); } self.clearTimeout(); self.duration = new Date() - start; finished = true; if (!err && self.duration > ms && self._enableTimeouts) { err = new Error( 'timeout of ' + ms + 'ms exceeded. Ensure the done() callback is being called in this test.' ); } fn(err); } // for .resetTimeout() this.callback = done; // explicit async with `done` argument if (this.async) { this.resetTimeout(); if (this.allowUncaught) { return callFnAsync(this.fn); } try { callFnAsync(this.fn); } catch (err) { done(utils.getError(err)); } return; } if (this.allowUncaught) { callFn(this.fn); done(); return; } // sync or promise-returning try { if (this.pending) { done(); } else { callFn(this.fn); } } catch (err) { done(utils.getError(err)); } function callFn(fn) { var result = fn.call(ctx); if (result && typeof result.then === 'function') { self.resetTimeout(); result.then( function() { done(); }, function(reason) { done( reason || new Error('Promise rejected with no or falsy reason') ); } ); } else { if (self.asyncOnly) { return done( new Error( '--async-only option in use without declaring `done()` or returning a promise' ) ); } done(); } } function callFnAsync(fn) { fn.call(ctx, function(err) { if ( err instanceof Error || toString.call(err) === '[object Error]' ) { return done(err); } if (err) { if ( Object.prototype.toString.call(err) === '[object Object]' ) { return done( new Error( 'done() invoked with non-Error: ' + JSON.stringify(err) ) ); } return done( new Error('done() invoked with non-Error: ' + err) ); } done(); }); } }; }.call( this, typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : {} )); }, { './ms': 15, './pending': 16, './utils': 39, debug: 2, events: 3 } ], 36: [ function(require, module, exports) { (function(process, global) { /** * Module dependencies. */ var EventEmitter = require('events').EventEmitter; var Pending = require('./pending'); var utils = require('./utils'); var inherits = utils.inherits; var debug = require('debug')('mocha:runner'); var Runnable = require('./runnable'); var filter = utils.filter; var indexOf = utils.indexOf; var keys = utils.keys; var stackFilter = utils.stackTraceFilter(); var stringify = utils.stringify; var type = utils.type; var undefinedError = utils.undefinedError; /** * Non-enumerable globals. */ var globals = [ 'setTimeout', 'clearTimeout', 'setInterval', 'clearInterval', 'XMLHttpRequest', 'Date', 'setImmediate', 'clearImmediate' ]; /** * Expose `Runner`. */ module.exports = Runner; /** * Initialize a `Runner` for the given `suite`. * * Events: * * - `start` execution started * - `end` execution complete * - `suite` (suite) test suite execution started * - `suite end` (suite) all tests (and sub-suites) have finished * - `test` (test) test execution started * - `test end` (test) test completed * - `hook` (hook) hook execution started * - `hook end` (hook) hook complete * - `pass` (test) test passed * - `fail` (test, err) test failed * - `pending` (test) test pending * * @api public * @param {Suite} suite Root suite * @param {boolean} [delay] Whether or not to delay execution of root suite * until ready. */ function Runner(suite, delay) { var self = this; this._globals = []; this._abort = false; this._delay = delay; this.suite = suite; this.started = false; this.total = suite.total(); this.failures = 0; this.on('test end', function(test) { self.checkGlobals(test); }); this.on('hook end', function(hook) { self.checkGlobals(hook); }); this._defaultGrep = /.*/; this.grep(this._defaultGrep); this.globals(this.globalProps().concat(extraGlobals())); } /** * Wrapper for setImmediate, process.nextTick, or browser polyfill. * * @param {Function} fn * @api private */ Runner.immediately = global.setImmediate || process.nextTick; /** * Inherit from `EventEmitter.prototype`. */ inherits(Runner, EventEmitter); /** * Run tests with full titles matching `re`. Updates runner.total * with number of tests matched. * * @param {RegExp} re * @param {Boolean} invert * @return {Runner} for chaining * @api public * @param {RegExp} re * @param {boolean} invert * @return {Runner} Runner instance. */ Runner.prototype.grep = function(re, invert) { debug('grep %s', re); this._grep = re; this._invert = invert; this.total = this.grepTotal(this.suite); return this; }; /** * Returns the number of tests matching the grep search for the * given suite. * * @param {Suite} suite * @return {Number} * @api public * @param {Suite} suite * @return {number} */ Runner.prototype.grepTotal = function(suite) { var self = this; var total = 0; suite.eachTest(function(test) { var match = self._grep.test(test.fullTitle()); if (self._invert) { match = !match; } if (match) { total++; } }); return total; }; /** * Return a list of global properties. * * @return {Array} * @api private */ Runner.prototype.globalProps = function() { var props = keys(global); // non-enumerables for (var i = 0; i < globals.length; ++i) { if (~indexOf(props, globals[i])) { continue; } props.push(globals[i]); } return props; }; /** * Allow the given `arr` of globals. * * @param {Array} arr * @return {Runner} for chaining * @api public * @param {Array} arr * @return {Runner} Runner instance. */ Runner.prototype.globals = function(arr) { if (!arguments.length) { return this._globals; } debug('globals %j', arr); this._globals = this._globals.concat(arr); return this; }; /** * Check for global variable leaks. * * @api private */ Runner.prototype.checkGlobals = function(test) { if (this.ignoreLeaks) { return; } var ok = this._globals; var globals = this.globalProps(); var leaks; if (test) { ok = ok.concat(test._allowedGlobals || []); } if (this.prevGlobalsLength === globals.length) { return; } this.prevGlobalsLength = globals.length; leaks = filterLeaks(ok, globals); this._globals = this._globals.concat(leaks); if (leaks.length > 1) { this.fail( test, new Error('global leaks detected: ' + leaks.join(', ') + '') ); } else if (leaks.length) { this.fail(test, new Error('global leak detected: ' + leaks[0])); } }; /** * Fail the given `test`. * * @api private * @param {Test} test * @param {Error} err */ Runner.prototype.fail = function(test, err) { ++this.failures; test.state = 'failed'; if ( !( err instanceof Error || (err && typeof err.message === 'string') ) ) { err = new Error( 'the ' + type(err) + ' ' + stringify(err) + ' was thrown, throw an Error :)' ); } err.stack = this.fullStackTrace || !err.stack ? err.stack : stackFilter(err.stack); this.emit('fail', test, err); }; /** * Fail the given `hook` with `err`. * * Hook failures work in the following pattern: * - If bail, then exit * - Failed `before` hook skips all tests in a suite and subsuites, * but jumps to corresponding `after` hook * - Failed `before each` hook skips remaining tests in a * suite and jumps to corresponding `after each` hook, * which is run only once * - Failed `after` hook does not alter * execution order * - Failed `after each` hook skips remaining tests in a * suite and subsuites, but executes other `after each` * hooks * * @api private * @param {Hook} hook * @param {Error} err */ Runner.prototype.failHook = function(hook, err) { if (hook.ctx && hook.ctx.currentTest) { hook.originalTitle = hook.originalTitle || hook.title; hook.title = hook.originalTitle + ' for "' + hook.ctx.currentTest.title + '"'; } this.fail(hook, err); if (this.suite.bail()) { this.emit('end'); } }; /** * Run hook `name` callbacks and then invoke `fn()`. * * @api private * @param {string} name * @param {Function} fn */ Runner.prototype.hook = function(name, fn) { var suite = this.suite; var hooks = suite['_' + name]; var self = this; function next(i) { var hook = hooks[i]; if (!hook) { return fn(); } self.currentRunnable = hook; hook.ctx.currentTest = self.test; self.emit('hook', hook); if (!hook.listeners('error').length) { hook.on('error', function(err) { self.failHook(hook, err); }); } hook.run(function(err) { var testError = hook.error(); if (testError) { self.fail(self.test, testError); } if (err) { if (err instanceof Pending) { suite.pending = true; } else { self.failHook(hook, err); // stop executing hooks, notify callee of hook err return fn(err); } } self.emit('hook end', hook); delete hook.ctx.currentTest; next(++i); }); } Runner.immediately(function() { next(0); }); }; /** * Run hook `name` for the given array of `suites` * in order, and callback `fn(err, errSuite)`. * * @api private * @param {string} name * @param {Array} suites * @param {Function} fn */ Runner.prototype.hooks = function(name, suites, fn) { var self = this; var orig = this.suite; function next(suite) { self.suite = suite; if (!suite) { self.suite = orig; return fn(); } self.hook(name, function(err) { if (err) { var errSuite = self.suite; self.suite = orig; return fn(err, errSuite); } next(suites.pop()); }); } next(suites.pop()); }; /** * Run hooks from the top level down. * * @param {String} name * @param {Function} fn * @api private */ Runner.prototype.hookUp = function(name, fn) { var suites = [this.suite].concat(this.parents()).reverse(); this.hooks(name, suites, fn); }; /** * Run hooks from the bottom up. * * @param {String} name * @param {Function} fn * @api private */ Runner.prototype.hookDown = function(name, fn) { var suites = [this.suite].concat(this.parents()); this.hooks(name, suites, fn); }; /** * Return an array of parent Suites from * closest to furthest. * * @return {Array} * @api private */ Runner.prototype.parents = function() { var suite = this.suite; var suites = []; while (suite.parent) { suite = suite.parent; suites.push(suite); } return suites; }; /** * Run the current test and callback `fn(err)`. * * @param {Function} fn * @api private */ Runner.prototype.runTest = function(fn) { var self = this; var test = this.test; if (this.asyncOnly) { test.asyncOnly = true; } if (this.allowUncaught) { test.allowUncaught = true; return test.run(fn); } try { test.on('error', function(err) { self.fail(test, err); }); test.run(fn); } catch (err) { fn(err); } }; /** * Run tests in the given `suite` and invoke the callback `fn()` when complete. * * @api private * @param {Suite} suite * @param {Function} fn */ Runner.prototype.runTests = function(suite, fn) { var self = this; var tests = suite.tests.slice(); var test; function hookErr(_, errSuite, after) { // before/after Each hook for errSuite failed: var orig = self.suite; // for failed 'after each' hook start from errSuite parent, // otherwise start from errSuite itself self.suite = after ? errSuite.parent : errSuite; if (self.suite) { // call hookUp afterEach self.hookUp('afterEach', function(err2, errSuite2) { self.suite = orig; // some hooks may fail even now if (err2) { return hookErr(err2, errSuite2, true); } // report error suite fn(errSuite); }); } else { // there is no need calling other 'after each' hooks self.suite = orig; fn(errSuite); } } function next(err, errSuite) { // if we bail after first err if (self.failures && suite._bail) { return fn(); } if (self._abort) { return fn(); } if (err) { return hookErr(err, errSuite, true); } // next test test = tests.shift(); // all done if (!test) { return fn(); } // grep var match = self._grep.test(test.fullTitle()); if (self._invert) { match = !match; } if (!match) { // Run immediately only if we have defined a grep. When we // define a grep — It can cause maximum callstack error if // the grep is doing a large recursive loop by neglecting // all tests. The run immediately function also comes with // a performance cost. So we don't want to run immediately // if we run the whole test suite, because running the whole // test suite don't do any immediate recursive loops. Thus, // allowing a JS runtime to breathe. if (self._grep !== self._defaultGrep) { Runner.immediately(next); } else { next(); } return; } // pending if (test.pending) { self.emit('pending', test); self.emit('test end', test); return next(); } // execute test and hook(s) self.emit('test', (self.test = test)); self.hookDown('beforeEach', function(err, errSuite) { if (suite.pending) { self.emit('pending', test); self.emit('test end', test); return next(); } if (err) { return hookErr(err, errSuite, false); } self.currentRunnable = self.test; self.runTest(function(err) { test = self.test; if (err) { if (err instanceof Pending) { self.emit('pending', test); } else { self.fail(test, err); } self.emit('test end', test); if (err instanceof Pending) { return next(); } return self.hookUp('afterEach', next); } test.state = 'passed'; self.emit('pass', test); self.emit('test end', test); self.hookUp('afterEach', next); }); }); } this.next = next; this.hookErr = hookErr; next(); }; /** * Run the given `suite` and invoke the callback `fn()` when complete. * * @api private * @param {Suite} suite * @param {Function} fn */ Runner.prototype.runSuite = function(suite, fn) { var i = 0; var self = this; var total = this.grepTotal(suite); var afterAllHookCalled = false; debug('run suite %s', suite.fullTitle()); if (!total || (self.failures && suite._bail)) { return fn(); } this.emit('suite', (this.suite = suite)); function next(errSuite) { if (errSuite) { // current suite failed on a hook from errSuite if (errSuite === suite) { // if errSuite is current suite // continue to the next sibling suite return done(); } // errSuite is among the parents of current suite // stop execution of errSuite and all sub-suites return done(errSuite); } if (self._abort) { return done(); } var curr = suite.suites[i++]; if (!curr) { return done(); } // Avoid grep neglecting large number of tests causing a // huge recursive loop and thus a maximum call stack error. // See comment in `this.runTests()` for more information. if (self._grep !== self._defaultGrep) { Runner.immediately(function() { self.runSuite(curr, next); }); } else { self.runSuite(curr, next); } } function done(errSuite) { self.suite = suite; self.nextSuite = next; if (afterAllHookCalled) { fn(errSuite); } else { // mark that the afterAll block has been called once // and so can be skipped if there is an error in it. afterAllHookCalled = true; self.hook('afterAll', function() { self.emit('suite end', suite); fn(errSuite); }); } } this.nextSuite = next; this.hook('beforeAll', function(err) { if (err) { return done(); } self.runTests(suite, next); }); }; /** * Handle uncaught exceptions. * * @param {Error} err * @api private */ Runner.prototype.uncaught = function(err) { if (err) { debug( 'uncaught exception %s', err !== function() { return this; }.call(err) ? err : err.message || err ); } else { debug('uncaught undefined exception'); err = undefinedError(); } err.uncaught = true; var runnable = this.currentRunnable; if (!runnable) { runnable = new Runnable('Uncaught error outside test suite'); runnable.parent = this.suite; if (this.started) { this.fail(runnable, err); } else { // Can't recover from this failure this.emit('start'); this.fail(runnable, err); this.emit('end'); } return; } runnable.clearTimeout(); // Ignore errors if complete if (runnable.state) { return; } this.fail(runnable, err); // recover from test if (runnable.type === 'test') { this.emit('test end', runnable); this.hookUp('afterEach', this.next); return; } // recover from hooks if (runnable.type === 'hook') { var errSuite = this.suite; // if hook failure is in afterEach block if (runnable.fullTitle().indexOf('after each') > -1) { return this.hookErr(err, errSuite, true); } // if hook failure is in beforeEach block if (runnable.fullTitle().indexOf('before each') > -1) { return this.hookErr(err, errSuite, false); } // if hook failure is in after or before blocks return this.nextSuite(errSuite); } // bail this.emit('end'); }; /** * Run the root suite and invoke `fn(failures)` * on completion. * * @param {Function} fn * @return {Runner} for chaining * @api public * @param {Function} fn * @return {Runner} Runner instance. */ Runner.prototype.run = function(fn) { var self = this; var rootSuite = this.suite; fn = fn || function() {}; function uncaught(err) { self.uncaught(err); } function start() { self.started = true; self.emit('start'); self.runSuite(rootSuite, function() { debug('finished running'); self.emit('end'); }); } debug('start'); // callback this.on('end', function() { debug('end'); process.removeListener('uncaughtException', uncaught); fn(self.failures); }); // uncaught exception process.on('uncaughtException', uncaught); if (this._delay) { // for reporters, I guess. // might be nice to debounce some dots while we wait. this.emit('waiting', rootSuite); rootSuite.once('run', start); } else { start(); } return this; }; /** * Cleanly abort execution. * * @api public * @return {Runner} Runner instance. */ Runner.prototype.abort = function() { debug('aborting'); this._abort = true; return this; }; /** * Filter leaks with the given globals flagged as `ok`. * * @api private * @param {Array} ok * @param {Array} globals * @return {Array} */ function filterLeaks(ok, globals) { return filter(globals, function(key) { // Firefox and Chrome exposes iframes as index inside the window object if (/^d+/.test(key)) { return false; } // in firefox // if runner runs in an iframe, this iframe's window.getInterface method not init at first // it is assigned in some seconds if (global.navigator && /^getInterface/.test(key)) { return false; } // an iframe could be approached by window[iframeIndex] // in ie6,7,8 and opera, iframeIndex is enumerable, this could cause leak if (global.navigator && /^\d+/.test(key)) { return false; } // Opera and IE expose global variables for HTML element IDs (issue #243) if (/^mocha-/.test(key)) { return false; } var matched = filter(ok, function(ok) { if (~ok.indexOf('*')) { return key.indexOf(ok.split('*')[0]) === 0; } return key === ok; }); return ( !matched.length && (!global.navigator || key !== 'onerror') ); }); } /** * Array of globals dependent on the environment. * * @return {Array} * @api private */ function extraGlobals() { if ( typeof process === 'object' && typeof process.version === 'string' ) { var parts = process.version.split('.'); var nodeVersion = utils.reduce(parts, function(a, v) { return (a << 8) | v; }); // 'errno' was renamed to process._errno in v0.9.11. if (nodeVersion < 0x00090b) { return ['errno']; } } return []; } }.call( this, require('_process'), typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : {} )); }, { './pending': 16, './runnable': 35, './utils': 39, _process: 51, debug: 2, events: 3 } ], 37: [ function(require, module, exports) { /** * Module dependencies. */ var EventEmitter = require('events').EventEmitter; var Hook = require('./hook'); var utils = require('./utils'); var inherits = utils.inherits; var debug = require('debug')('mocha:suite'); var milliseconds = require('./ms'); /** * Expose `Suite`. */ exports = module.exports = Suite; /** * Create a new `Suite` with the given `title` and parent `Suite`. When a suite * with the same title is already present, that suite is returned to provide * nicer reporter and more flexible meta-testing. * * @api public * @param {Suite} parent * @param {string} title * @return {Suite} */ exports.create = function(parent, title) { var suite = new Suite(title, parent.ctx); suite.parent = parent; if (parent.pending) { suite.pending = true; } title = suite.fullTitle(); parent.addSuite(suite); return suite; }; /** * Initialize a new `Suite` with the given `title` and `ctx`. * * @api private * @param {string} title * @param {Context} parentContext */ function Suite(title, parentContext) { this.title = title; function Context() {} Context.prototype = parentContext; this.ctx = new Context(); this.suites = []; this.tests = []; this.pending = false; this._beforeEach = []; this._beforeAll = []; this._afterEach = []; this._afterAll = []; this.root = !title; this._timeout = 2000; this._enableTimeouts = true; this._slow = 75; this._bail = false; this.delayed = false; } /** * Inherit from `EventEmitter.prototype`. */ inherits(Suite, EventEmitter); /** * Return a clone of this `Suite`. * * @api private * @return {Suite} */ Suite.prototype.clone = function() { var suite = new Suite(this.title); debug('clone'); suite.ctx = this.ctx; suite.timeout(this.timeout()); suite.enableTimeouts(this.enableTimeouts()); suite.slow(this.slow()); suite.bail(this.bail()); return suite; }; /** * Set timeout `ms` or short-hand such as "2s". * * @api private * @param {number|string} ms * @return {Suite|number} for chaining */ Suite.prototype.timeout = function(ms) { if (!arguments.length) { return this._timeout; } if (ms.toString() === '0') { this._enableTimeouts = false; } if (typeof ms === 'string') { ms = milliseconds(ms); } debug('timeout %d', ms); this._timeout = parseInt(ms, 10); return this; }; /** * Set timeout to `enabled`. * * @api private * @param {boolean} enabled * @return {Suite|boolean} self or enabled */ Suite.prototype.enableTimeouts = function(enabled) { if (!arguments.length) { return this._enableTimeouts; } debug('enableTimeouts %s', enabled); this._enableTimeouts = enabled; return this; }; /** * Set slow `ms` or short-hand such as "2s". * * @api private * @param {number|string} ms * @return {Suite|number} for chaining */ Suite.prototype.slow = function(ms) { if (!arguments.length) { return this._slow; } if (typeof ms === 'string') { ms = milliseconds(ms); } debug('slow %d', ms); this._slow = ms; return this; }; /** * Sets whether to bail after first error. * * @api private * @param {boolean} bail * @return {Suite|number} for chaining */ Suite.prototype.bail = function(bail) { if (!arguments.length) { return this._bail; } debug('bail %s', bail); this._bail = bail; return this; }; /** * Run `fn(test[, done])` before running tests. * * @api private * @param {string} title * @param {Function} fn * @return {Suite} for chaining */ Suite.prototype.beforeAll = function(title, fn) { if (this.pending) { return this; } if (typeof title === 'function') { fn = title; title = fn.name; } title = '"before all" hook' + (title ? ': ' + title : ''); var hook = new Hook(title, fn); hook.parent = this; hook.timeout(this.timeout()); hook.enableTimeouts(this.enableTimeouts()); hook.slow(this.slow()); hook.ctx = this.ctx; this._beforeAll.push(hook); this.emit('beforeAll', hook); return this; }; /** * Run `fn(test[, done])` after running tests. * * @api private * @param {string} title * @param {Function} fn * @return {Suite} for chaining */ Suite.prototype.afterAll = function(title, fn) { if (this.pending) { return this; } if (typeof title === 'function') { fn = title; title = fn.name; } title = '"after all" hook' + (title ? ': ' + title : ''); var hook = new Hook(title, fn); hook.parent = this; hook.timeout(this.timeout()); hook.enableTimeouts(this.enableTimeouts()); hook.slow(this.slow()); hook.ctx = this.ctx; this._afterAll.push(hook); this.emit('afterAll', hook); return this; }; /** * Run `fn(test[, done])` before each test case. * * @api private * @param {string} title * @param {Function} fn * @return {Suite} for chaining */ Suite.prototype.beforeEach = function(title, fn) { if (this.pending) { return this; } if (typeof title === 'function') { fn = title; title = fn.name; } title = '"before each" hook' + (title ? ': ' + title : ''); var hook = new Hook(title, fn); hook.parent = this; hook.timeout(this.timeout()); hook.enableTimeouts(this.enableTimeouts()); hook.slow(this.slow()); hook.ctx = this.ctx; this._beforeEach.push(hook); this.emit('beforeEach', hook); return this; }; /** * Run `fn(test[, done])` after each test case. * * @api private * @param {string} title * @param {Function} fn * @return {Suite} for chaining */ Suite.prototype.afterEach = function(title, fn) { if (this.pending) { return this; } if (typeof title === 'function') { fn = title; title = fn.name; } title = '"after each" hook' + (title ? ': ' + title : ''); var hook = new Hook(title, fn); hook.parent = this; hook.timeout(this.timeout()); hook.enableTimeouts(this.enableTimeouts()); hook.slow(this.slow()); hook.ctx = this.ctx; this._afterEach.push(hook); this.emit('afterEach', hook); return this; }; /** * Add a test `suite`. * * @api private * @param {Suite} suite * @return {Suite} for chaining */ Suite.prototype.addSuite = function(suite) { suite.parent = this; suite.timeout(this.timeout()); suite.enableTimeouts(this.enableTimeouts()); suite.slow(this.slow()); suite.bail(this.bail()); this.suites.push(suite); this.emit('suite', suite); return this; }; /** * Add a `test` to this suite. * * @api private * @param {Test} test * @return {Suite} for chaining */ Suite.prototype.addTest = function(test) { test.parent = this; test.timeout(this.timeout()); test.enableTimeouts(this.enableTimeouts()); test.slow(this.slow()); test.ctx = this.ctx; this.tests.push(test); this.emit('test', test); return this; }; /** * Return the full title generated by recursively concatenating the parent's * full title. * * @api public * @return {string} */ Suite.prototype.fullTitle = function() { if (this.parent) { var full = this.parent.fullTitle(); if (full) { return full + ' ' + this.title; } } return this.title; }; /** * Return the total number of tests. * * @api public * @return {number} */ Suite.prototype.total = function() { return ( utils.reduce( this.suites, function(sum, suite) { return sum + suite.total(); }, 0 ) + this.tests.length ); }; /** * Iterates through each suite recursively to find all tests. Applies a * function in the format `fn(test)`. * * @api private * @param {Function} fn * @return {Suite} */ Suite.prototype.eachTest = function(fn) { utils.forEach(this.tests, fn); utils.forEach(this.suites, function(suite) { suite.eachTest(fn); }); return this; }; /** * This will run the root suite if we happen to be running in delayed mode. */ Suite.prototype.run = function run() { if (this.root) { this.emit('run'); } }; }, { './hook': 7, './ms': 15, './utils': 39, debug: 2, events: 3 } ], 38: [ function(require, module, exports) { /** * Module dependencies. */ var Runnable = require('./runnable'); var inherits = require('./utils').inherits; /** * Expose `Test`. */ module.exports = Test; /** * Initialize a new `Test` with the given `title` and callback `fn`. * * @api private * @param {String} title * @param {Function} fn */ function Test(title, fn) { Runnable.call(this, title, fn); this.pending = !fn; this.type = 'test'; } /** * Inherit from `Runnable.prototype`. */ inherits(Test, Runnable); }, { './runnable': 35, './utils': 39 } ], 39: [ function(require, module, exports) { (function(process, Buffer) { /* eslint-env browser */ /** * Module dependencies. */ var basename = require('path').basename; var debug = require('debug')('mocha:watch'); var exists = require('fs').existsSync || require('path').existsSync; var glob = require('glob'); var join = require('path').join; var readdirSync = require('fs').readdirSync; var statSync = require('fs').statSync; var watchFile = require('fs').watchFile; /** * Ignored directories. */ var ignore = ['node_modules', '.git']; exports.inherits = require('util').inherits; /** * Escape special characters in the given string of html. * * @api private * @param {string} html * @return {string} */ exports.escape = function(html) { return String(html) .replace(/&/g, '&') .replace(/"/g, '"') .replace(//g, '>'); }; /** * Array#forEach (<=IE8) * * @api private * @param {Array} arr * @param {Function} fn * @param {Object} scope */ exports.forEach = function(arr, fn, scope) { for (var i = 0, l = arr.length; i < l; i++) { fn.call(scope, arr[i], i); } }; /** * Test if the given obj is type of string. * * @api private * @param {Object} obj * @return {boolean} */ exports.isString = function(obj) { return typeof obj === 'string'; }; /** * Array#map (<=IE8) * * @api private * @param {Array} arr * @param {Function} fn * @param {Object} scope * @return {Array} */ exports.map = function(arr, fn, scope) { var result = []; for (var i = 0, l = arr.length; i < l; i++) { result.push(fn.call(scope, arr[i], i, arr)); } return result; }; /** * Array#indexOf (<=IE8) * * @api private * @param {Array} arr * @param {Object} obj to find index of * @param {number} start * @return {number} */ exports.indexOf = function(arr, obj, start) { for (var i = start || 0, l = arr.length; i < l; i++) { if (arr[i] === obj) { return i; } } return -1; }; /** * Array#reduce (<=IE8) * * @api private * @param {Array} arr * @param {Function} fn * @param {Object} val Initial value. * @return {*} */ exports.reduce = function(arr, fn, val) { var rval = val; for (var i = 0, l = arr.length; i < l; i++) { rval = fn(rval, arr[i], i, arr); } return rval; }; /** * Array#filter (<=IE8) * * @api private * @param {Array} arr * @param {Function} fn * @return {Array} */ exports.filter = function(arr, fn) { var ret = []; for (var i = 0, l = arr.length; i < l; i++) { var val = arr[i]; if (fn(val, i, arr)) { ret.push(val); } } return ret; }; /** * Object.keys (<=IE8) * * @api private * @param {Object} obj * @return {Array} keys */ exports.keys = typeof Object.keys === 'function' ? Object.keys : function(obj) { var keys = []; var has = Object.prototype.hasOwnProperty; // for `window` on <=IE8 for (var key in obj) { if (has.call(obj, key)) { keys.push(key); } } return keys; }; /** * Watch the given `files` for changes * and invoke `fn(file)` on modification. * * @api private * @param {Array} files * @param {Function} fn */ exports.watch = function(files, fn) { var options = { interval: 100 }; files.forEach(function(file) { debug('file %s', file); watchFile(file, options, function(curr, prev) { if (prev.mtime < curr.mtime) { fn(file); } }); }); }; /** * Array.isArray (<=IE8) * * @api private * @param {Object} obj * @return {Boolean} */ var isArray = typeof Array.isArray === 'function' ? Array.isArray : function(obj) { return ( Object.prototype.toString.call(obj) === '[object Array]' ); }; /** * Buffer.prototype.toJSON polyfill. * * @type {Function} */ if (typeof Buffer !== 'undefined' && Buffer.prototype) { Buffer.prototype.toJSON = Buffer.prototype.toJSON || function() { return Array.prototype.slice.call(this, 0); }; } /** * Ignored files. * * @api private * @param {string} path * @return {boolean} */ function ignored(path) { return !~ignore.indexOf(path); } /** * Lookup files in the given `dir`. * * @api private * @param {string} dir * @param {string[]} [ext=['.js']] * @param {Array} [ret=[]] * @return {Array} */ exports.files = function(dir, ext, ret) { ret = ret || []; ext = ext || ['js']; var re = new RegExp('\\.(' + ext.join('|') + ')$'); readdirSync(dir) .filter(ignored) .forEach(function(path) { path = join(dir, path); if (statSync(path).isDirectory()) { exports.files(path, ext, ret); } else if (path.match(re)) { ret.push(path); } }); return ret; }; /** * Compute a slug from the given `str`. * * @api private * @param {string} str * @return {string} */ exports.slug = function(str) { return str .toLowerCase() .replace(/ +/g, '-') .replace(/[^-\w]/g, ''); }; /** * Strip the function definition from `str`, and re-indent for pre whitespace. * * @param {string} str * @return {string} */ exports.clean = function(str) { str = str .replace(/\r\n?|[\n\u2028\u2029]/g, '\n') .replace(/^\uFEFF/, '') .replace(/^function *\(.*\)\s*{|\(.*\) *=> *{?/, '') .replace(/\s+\}$/, ''); var spaces = str.match(/^\n?( *)/)[1].length; var tabs = str.match(/^\n?(\t*)/)[1].length; var re = new RegExp( '^\n?' + (tabs ? '\t' : ' ') + '{' + (tabs ? tabs : spaces) + '}', 'gm' ); str = str.replace(re, ''); return exports.trim(str); }; /** * Trim the given `str`. * * @api private * @param {string} str * @return {string} */ exports.trim = function(str) { return str.replace(/^\s+|\s+$/g, ''); }; /** * Parse the given `qs`. * * @api private * @param {string} qs * @return {Object} */ exports.parseQuery = function(qs) { return exports.reduce( qs.replace('?', '').split('&'), function(obj, pair) { var i = pair.indexOf('='); var key = pair.slice(0, i); var val = pair.slice(++i); obj[key] = decodeURIComponent(val); return obj; }, {} ); }; /** * Highlight the given string of `js`. * * @api private * @param {string} js * @return {string} */ function highlight(js) { return js .replace(//g, '>') .replace(/\/\/(.*)/gm, '//$1') .replace(/('.*?')/gm, '$1') .replace(/(\d+\.\d+)/gm, '$1') .replace(/(\d+)/gm, '$1') .replace( /\bnew[ \t]+(\w+)/gm, 'new $1' ) .replace( /\b(function|new|throw|return|var|if|else)\b/gm, '$1' ); } /** * Highlight the contents of tag `name`. * * @api private * @param {string} name */ exports.highlightTags = function(name) { var code = document .getElementById('mocha') .getElementsByTagName(name); for (var i = 0, len = code.length; i < len; ++i) { code[i].innerHTML = highlight(code[i].innerHTML); } }; /** * If a value could have properties, and has none, this function is called, * which returns a string representation of the empty value. * * Functions w/ no properties return `'[Function]'` * Arrays w/ length === 0 return `'[]'` * Objects w/ no properties return `'{}'` * All else: return result of `value.toString()` * * @api private * @param {*} value The value to inspect. * @param {string} [type] The type of the value, if known. * @returns {string} */ function emptyRepresentation(value, type) { type = type || exports.type(value); switch (type) { case 'function': return '[Function]'; case 'object': return '{}'; case 'array': return '[]'; default: return value.toString(); } } /** * Takes some variable and asks `Object.prototype.toString()` what it thinks it * is. * * @api private * @see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString * @param {*} value The value to test. * @returns {string} * @example * type({}) // 'object' * type([]) // 'array' * type(1) // 'number' * type(false) // 'boolean' * type(Infinity) // 'number' * type(null) // 'null' * type(new Date()) // 'date' * type(/foo/) // 'regexp' * type('type') // 'string' * type(global) // 'global' */ exports.type = function type(value) { if (value === undefined) { return 'undefined'; } else if (value === null) { return 'null'; } else if ( typeof Buffer !== 'undefined' && Buffer.isBuffer(value) ) { return 'buffer'; } return Object.prototype.toString .call(value) .replace(/^\[.+\s(.+?)\]$/, '$1') .toLowerCase(); }; /** * Stringify `value`. Different behavior depending on type of value: * * - If `value` is undefined or null, return `'[undefined]'` or `'[null]'`, respectively. * - If `value` is not an object, function or array, return result of `value.toString()` wrapped in double-quotes. * - If `value` is an *empty* object, function, or array, return result of function * {@link emptyRepresentation}. * - If `value` has properties, call {@link exports.canonicalize} on it, then return result of * JSON.stringify(). * * @api private * @see exports.type * @param {*} value * @return {string} */ exports.stringify = function(value) { var type = exports.type(value); if (!~exports.indexOf(['object', 'array', 'function'], type)) { if (type !== 'buffer') { return jsonStringify(value); } var json = value.toJSON(); // Based on the toJSON result return jsonStringify( json.data && json.type ? json.data : json, 2 ).replace(/,(\n|$)/g, '$1'); } for (var prop in value) { if (Object.prototype.hasOwnProperty.call(value, prop)) { return jsonStringify(exports.canonicalize(value), 2).replace( /,(\n|$)/g, '$1' ); } } return emptyRepresentation(value, type); }; /** * like JSON.stringify but more sense. * * @api private * @param {Object} object * @param {number=} spaces * @param {number=} depth * @returns {*} */ function jsonStringify(object, spaces, depth) { if (typeof spaces === 'undefined') { // primitive types return _stringify(object); } depth = depth || 1; var space = spaces * depth; var str = isArray(object) ? '[' : '{'; var end = isArray(object) ? ']' : '}'; var length = object.length || exports.keys(object).length; // `.repeat()` polyfill function repeat(s, n) { return new Array(n).join(s); } function _stringify(val) { switch (exports.type(val)) { case 'null': case 'undefined': val = '[' + val + ']'; break; case 'array': case 'object': val = jsonStringify(val, spaces, depth + 1); break; case 'boolean': case 'regexp': case 'number': val = val === 0 && 1 / val === -Infinity // `-0` ? '-0' : val.toString(); break; case 'date': var sDate = isNaN(val.getTime()) // Invalid date ? val.toString() : val.toISOString(); val = '[Date: ' + sDate + ']'; break; case 'buffer': var json = val.toJSON(); // Based on the toJSON result json = json.data && json.type ? json.data : json; val = '[Buffer: ' + jsonStringify(json, 2, depth + 1) + ']'; break; default: val = val === '[Function]' || val === '[Circular]' ? val : JSON.stringify(val); // string } return val; } for (var i in object) { if (!object.hasOwnProperty(i)) { continue; // not my business } --length; str += '\n ' + repeat(' ', space) + (isArray(object) ? '' : '"' + i + '": ') + // key _stringify(object[i]) + // value (length ? ',' : ''); // comma } return ( str + // [], {} (str.length !== 1 ? '\n' + repeat(' ', --space) + end : end) ); } /** * Test if a value is a buffer. * * @api private * @param {*} value The value to test. * @return {boolean} True if `value` is a buffer, otherwise false */ exports.isBuffer = function(value) { return typeof Buffer !== 'undefined' && Buffer.isBuffer(value); }; /** * Return a new Thing that has the keys in sorted order. Recursive. * * If the Thing... * - has already been seen, return string `'[Circular]'` * - is `undefined`, return string `'[undefined]'` * - is `null`, return value `null` * - is some other primitive, return the value * - is not a primitive or an `Array`, `Object`, or `Function`, return the value of the Thing's `toString()` method * - is a non-empty `Array`, `Object`, or `Function`, return the result of calling this function again. * - is an empty `Array`, `Object`, or `Function`, return the result of calling `emptyRepresentation()` * * @api private * @see {@link exports.stringify} * @param {*} value Thing to inspect. May or may not have properties. * @param {Array} [stack=[]] Stack of seen values * @return {(Object|Array|Function|string|undefined)} */ exports.canonicalize = function(value, stack) { var canonicalizedObj; /* eslint-disable no-unused-vars */ var prop; /* eslint-enable no-unused-vars */ var type = exports.type(value); function withStack(value, fn) { stack.push(value); fn(); stack.pop(); } stack = stack || []; if (exports.indexOf(stack, value) !== -1) { return '[Circular]'; } switch (type) { case 'undefined': case 'buffer': case 'null': canonicalizedObj = value; break; case 'array': withStack(value, function() { canonicalizedObj = exports.map(value, function(item) { return exports.canonicalize(item, stack); }); }); break; case 'function': /* eslint-disable guard-for-in */ for (prop in value) { canonicalizedObj = {}; break; } /* eslint-enable guard-for-in */ if (!canonicalizedObj) { canonicalizedObj = emptyRepresentation(value, type); break; } /* falls through */ case 'object': canonicalizedObj = canonicalizedObj || {}; withStack(value, function() { exports.forEach(exports.keys(value).sort(), function(key) { canonicalizedObj[key] = exports.canonicalize( value[key], stack ); }); }); break; case 'date': case 'number': case 'regexp': case 'boolean': canonicalizedObj = value; break; default: canonicalizedObj = value.toString(); } return canonicalizedObj; }; /** * Lookup file names at the given `path`. * * @api public * @param {string} path Base path to start searching from. * @param {string[]} extensions File extensions to look for. * @param {boolean} recursive Whether or not to recurse into subdirectories. * @return {string[]} An array of paths. */ exports.lookupFiles = function lookupFiles( path, extensions, recursive ) { var files = []; var re = new RegExp('\\.(' + extensions.join('|') + ')$'); if (!exists(path)) { if (exists(path + '.js')) { path += '.js'; } else { files = glob.sync(path); if (!files.length) { throw new Error( "cannot resolve path (or pattern) '" + path + "'" ); } return files; } } try { var stat = statSync(path); if (stat.isFile()) { return path; } } catch (err) { // ignore error return; } readdirSync(path).forEach(function(file) { file = join(path, file); try { var stat = statSync(file); if (stat.isDirectory()) { if (recursive) { files = files.concat( lookupFiles(file, extensions, recursive) ); } return; } } catch (err) { // ignore error return; } if ( !stat.isFile() || !re.test(file) || basename(file)[0] === '.' ) { return; } files.push(file); }); return files; }; /** * Generate an undefined error with a message warning the user. * * @return {Error} */ exports.undefinedError = function() { return new Error( 'Caught undefined error, did you throw without specifying what?' ); }; /** * Generate an undefined error if `err` is not defined. * * @param {Error} err * @return {Error} */ exports.getError = function(err) { return err || exports.undefinedError(); }; /** * @summary * This Filter based on `mocha-clean` module.(see: `github.com/rstacruz/mocha-clean`) * @description * When invoking this function you get a filter function that get the Error.stack as an input, * and return a prettify output. * (i.e: strip Mocha and internal node functions from stack trace). * @returns {Function} */ exports.stackTraceFilter = function() { // TODO: Replace with `process.browser` var slash = '/'; var is = typeof document === 'undefined' ? { node: true } : { browser: true }; var cwd = is.node ? process.cwd() + slash : (typeof location === 'undefined' ? window.location : location ).href.replace(/\/[^\/]*$/, '/'); function isMochaInternal(line) { return ( ~line.indexOf('node_modules' + slash + 'mocha' + slash) || ~line.indexOf('components' + slash + 'mochajs' + slash) || ~line.indexOf('components' + slash + 'mocha' + slash) || ~line.indexOf(slash + 'mocha.js') ); } function isNodeInternal(line) { return ( ~line.indexOf('(timers.js:') || ~line.indexOf('(events.js:') || ~line.indexOf('(node.js:') || ~line.indexOf('(module.js:') || ~line.indexOf('GeneratorFunctionPrototype.next (native)') || false ); } return function(stack) { stack = stack.split('\n'); stack = exports.reduce( stack, function(list, line) { if (isMochaInternal(line)) { return list; } if (is.node && isNodeInternal(line)) { return list; } // Clean up cwd(absolute) list.push(line.replace(cwd, '')); return list; }, [] ); return stack.join('\n'); }; }; }.call(this, require('_process'), require('buffer').Buffer)); }, { _process: 51, buffer: 43, debug: 2, fs: 41, glob: 41, path: 41, util: 66 } ], 40: [ function(require, module, exports) { (function(process) { var WritableStream = require('stream').Writable; var inherits = require('util').inherits; module.exports = BrowserStdout; inherits(BrowserStdout, WritableStream); function BrowserStdout(opts) { if (!(this instanceof BrowserStdout)) return new BrowserStdout(opts); opts = opts || {}; WritableStream.call(this, opts); this.label = opts.label !== undefined ? opts.label : 'stdout'; } BrowserStdout.prototype._write = function(chunks, encoding, cb) { var output = chunks.toString ? chunks.toString() : chunks; if (this.label === false) { console.log(output); } else { console.log(this.label + ':', output); } process.nextTick(cb); }; }.call(this, require('_process'))); }, { _process: 51, stream: 63, util: 66 } ], 41: [function(require, module, exports) {}, {}], 42: [ function(require, module, exports) { arguments[4][41][0].apply(exports, arguments); }, { dup: 41 } ], 43: [ function(require, module, exports) { /*! * The buffer module from node.js, for the browser. * * @author Feross Aboukhadijeh * @license MIT */ var base64 = require('base64-js'); var ieee754 = require('ieee754'); var isArray = require('is-array'); exports.Buffer = Buffer; exports.SlowBuffer = SlowBuffer; exports.INSPECT_MAX_BYTES = 50; Buffer.poolSize = 8192; // not used by this implementation var rootParent = {}; /** * If `Buffer.TYPED_ARRAY_SUPPORT`: * === true Use Uint8Array implementation (fastest) * === false Use Object implementation (most compatible, even IE6) * * Browsers that support typed arrays are IE 10+, Firefox 4+, Chrome 7+, Safari 5.1+, * Opera 11.6+, iOS 4.2+. * * Due to various browser bugs, sometimes the Object implementation will be used even * when the browser supports typed arrays. * * Note: * * - Firefox 4-29 lacks support for adding new properties to `Uint8Array` instances, * See: https://bugzilla.mozilla.org/show_bug.cgi?id=695438. * * - Safari 5-7 lacks support for changing the `Object.prototype.constructor` property * on objects. * * - Chrome 9-10 is missing the `TypedArray.prototype.subarray` function. * * - IE10 has a broken `TypedArray.prototype.subarray` function which returns arrays of * incorrect length in some situations. * We detect these buggy browsers and set `Buffer.TYPED_ARRAY_SUPPORT` to `false` so they * get the Object implementation, which is slower but behaves correctly. */ Buffer.TYPED_ARRAY_SUPPORT = (function() { function Bar() {} try { var arr = new Uint8Array(1); arr.foo = function() { return 42; }; arr.constructor = Bar; return ( arr.foo() === 42 && // typed array instances can be augmented arr.constructor === Bar && // constructor can be set typeof arr.subarray === 'function' && // chrome 9-10 lack `subarray` arr.subarray(1, 1).byteLength === 0 ); // ie10 has broken `subarray` } catch (e) { return false; } })(); function kMaxLength() { return Buffer.TYPED_ARRAY_SUPPORT ? 0x7fffffff : 0x3fffffff; } /** * Class: Buffer * ============= * * The Buffer constructor returns instances of `Uint8Array` that are augmented * with function properties for all the node `Buffer` API functions. We use * `Uint8Array` so that square bracket notation works as expected -- it returns * a single octet. * * By augmenting the instances, we can avoid modifying the `Uint8Array` * prototype. */ function Buffer(arg) { if (!(this instanceof Buffer)) { // Avoid going through an ArgumentsAdaptorTrampoline in the common case. if (arguments.length > 1) return new Buffer(arg, arguments[1]); return new Buffer(arg); } this.length = 0; this.parent = undefined; // Common case. if (typeof arg === 'number') { return fromNumber(this, arg); } // Slightly less common case. if (typeof arg === 'string') { return fromString( this, arg, arguments.length > 1 ? arguments[1] : 'utf8' ); } // Unusual. return fromObject(this, arg); } function fromNumber(that, length) { that = allocate(that, length < 0 ? 0 : checked(length) | 0); if (!Buffer.TYPED_ARRAY_SUPPORT) { for (var i = 0; i < length; i++) { that[i] = 0; } } return that; } function fromString(that, string, encoding) { if (typeof encoding !== 'string' || encoding === '') encoding = 'utf8'; // Assumption: byteLength() return value is always < kMaxLength. var length = byteLength(string, encoding) | 0; that = allocate(that, length); that.write(string, encoding); return that; } function fromObject(that, object) { if (Buffer.isBuffer(object)) return fromBuffer(that, object); if (isArray(object)) return fromArray(that, object); if (object == null) { throw new TypeError( 'must start with number, buffer, array or string' ); } if (typeof ArrayBuffer !== 'undefined') { if (object.buffer instanceof ArrayBuffer) { return fromTypedArray(that, object); } if (object instanceof ArrayBuffer) { return fromArrayBuffer(that, object); } } if (object.length) return fromArrayLike(that, object); return fromJsonObject(that, object); } function fromBuffer(that, buffer) { var length = checked(buffer.length) | 0; that = allocate(that, length); buffer.copy(that, 0, 0, length); return that; } function fromArray(that, array) { var length = checked(array.length) | 0; that = allocate(that, length); for (var i = 0; i < length; i += 1) { that[i] = array[i] & 255; } return that; } // Duplicate of fromArray() to keep fromArray() monomorphic. function fromTypedArray(that, array) { var length = checked(array.length) | 0; that = allocate(that, length); // Truncating the elements is probably not what people expect from typed // arrays with BYTES_PER_ELEMENT > 1 but it's compatible with the behavior // of the old Buffer constructor. for (var i = 0; i < length; i += 1) { that[i] = array[i] & 255; } return that; } function fromArrayBuffer(that, array) { if (Buffer.TYPED_ARRAY_SUPPORT) { // Return an augmented `Uint8Array` instance, for best performance array.byteLength; that = Buffer._augment(new Uint8Array(array)); } else { // Fallback: Return an object instance of the Buffer class that = fromTypedArray(that, new Uint8Array(array)); } return that; } function fromArrayLike(that, array) { var length = checked(array.length) | 0; that = allocate(that, length); for (var i = 0; i < length; i += 1) { that[i] = array[i] & 255; } return that; } // Deserialize { type: 'Buffer', data: [1,2,3,...] } into a Buffer object. // Returns a zero-length buffer for inputs that don't conform to the spec. function fromJsonObject(that, object) { var array; var length = 0; if (object.type === 'Buffer' && isArray(object.data)) { array = object.data; length = checked(array.length) | 0; } that = allocate(that, length); for (var i = 0; i < length; i += 1) { that[i] = array[i] & 255; } return that; } function allocate(that, length) { if (Buffer.TYPED_ARRAY_SUPPORT) { // Return an augmented `Uint8Array` instance, for best performance that = Buffer._augment(new Uint8Array(length)); } else { // Fallback: Return an object instance of the Buffer class that.length = length; that._isBuffer = true; } var fromPool = length !== 0 && length <= Buffer.poolSize >>> 1; if (fromPool) that.parent = rootParent; return that; } function checked(length) { // Note: cannot use `length < kMaxLength` here because that fails when // length is NaN (which is otherwise coerced to zero.) if (length >= kMaxLength()) { throw new RangeError( 'Attempt to allocate Buffer larger than maximum ' + 'size: 0x' + kMaxLength().toString(16) + ' bytes' ); } return length | 0; } function SlowBuffer(subject, encoding) { if (!(this instanceof SlowBuffer)) return new SlowBuffer(subject, encoding); var buf = new Buffer(subject, encoding); delete buf.parent; return buf; } Buffer.isBuffer = function isBuffer(b) { return !!(b != null && b._isBuffer); }; Buffer.compare = function compare(a, b) { if (!Buffer.isBuffer(a) || !Buffer.isBuffer(b)) { throw new TypeError('Arguments must be Buffers'); } if (a === b) return 0; var x = a.length; var y = b.length; var i = 0; var len = Math.min(x, y); while (i < len) { if (a[i] !== b[i]) break; ++i; } if (i !== len) { x = a[i]; y = b[i]; } if (x < y) return -1; if (y < x) return 1; return 0; }; Buffer.isEncoding = function isEncoding(encoding) { switch (String(encoding).toLowerCase()) { case 'hex': case 'utf8': case 'utf-8': case 'ascii': case 'binary': case 'base64': case 'raw': case 'ucs2': case 'ucs-2': case 'utf16le': case 'utf-16le': return true; default: return false; } }; Buffer.concat = function concat(list, length) { if (!isArray(list)) throw new TypeError('list argument must be an Array of Buffers.'); if (list.length === 0) { return new Buffer(0); } var i; if (length === undefined) { length = 0; for (i = 0; i < list.length; i++) { length += list[i].length; } } var buf = new Buffer(length); var pos = 0; for (i = 0; i < list.length; i++) { var item = list[i]; item.copy(buf, pos); pos += item.length; } return buf; }; function byteLength(string, encoding) { if (typeof string !== 'string') string = '' + string; var len = string.length; if (len === 0) return 0; // Use a for loop to avoid recursion var loweredCase = false; for (;;) { switch (encoding) { case 'ascii': case 'binary': // Deprecated case 'raw': case 'raws': return len; case 'utf8': case 'utf-8': return utf8ToBytes(string).length; case 'ucs2': case 'ucs-2': case 'utf16le': case 'utf-16le': return len * 2; case 'hex': return len >>> 1; case 'base64': return base64ToBytes(string).length; default: if (loweredCase) return utf8ToBytes(string).length; // assume utf8 encoding = ('' + encoding).toLowerCase(); loweredCase = true; } } } Buffer.byteLength = byteLength; // pre-set for values that may exist in the future Buffer.prototype.length = undefined; Buffer.prototype.parent = undefined; function slowToString(encoding, start, end) { var loweredCase = false; start = start | 0; end = end === undefined || end === Infinity ? this.length : end | 0; if (!encoding) encoding = 'utf8'; if (start < 0) start = 0; if (end > this.length) end = this.length; if (end <= start) return ''; while (true) { switch (encoding) { case 'hex': return hexSlice(this, start, end); case 'utf8': case 'utf-8': return utf8Slice(this, start, end); case 'ascii': return asciiSlice(this, start, end); case 'binary': return binarySlice(this, start, end); case 'base64': return base64Slice(this, start, end); case 'ucs2': case 'ucs-2': case 'utf16le': case 'utf-16le': return utf16leSlice(this, start, end); default: if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding); encoding = (encoding + '').toLowerCase(); loweredCase = true; } } } Buffer.prototype.toString = function toString() { var length = this.length | 0; if (length === 0) return ''; if (arguments.length === 0) return utf8Slice(this, 0, length); return slowToString.apply(this, arguments); }; Buffer.prototype.equals = function equals(b) { if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer'); if (this === b) return true; return Buffer.compare(this, b) === 0; }; Buffer.prototype.inspect = function inspect() { var str = ''; var max = exports.INSPECT_MAX_BYTES; if (this.length > 0) { str = this.toString('hex', 0, max) .match(/.{2}/g) .join(' '); if (this.length > max) str += ' ... '; } return ''; }; Buffer.prototype.compare = function compare(b) { if (!Buffer.isBuffer(b)) throw new TypeError('Argument must be a Buffer'); if (this === b) return 0; return Buffer.compare(this, b); }; Buffer.prototype.indexOf = function indexOf(val, byteOffset) { if (byteOffset > 0x7fffffff) byteOffset = 0x7fffffff; else if (byteOffset < -0x80000000) byteOffset = -0x80000000; byteOffset >>= 0; if (this.length === 0) return -1; if (byteOffset >= this.length) return -1; // Negative offsets start from the end of the buffer if (byteOffset < 0) byteOffset = Math.max(this.length + byteOffset, 0); if (typeof val === 'string') { if (val.length === 0) return -1; // special case: looking for empty string always fails return String.prototype.indexOf.call(this, val, byteOffset); } if (Buffer.isBuffer(val)) { return arrayIndexOf(this, val, byteOffset); } if (typeof val === 'number') { if ( Buffer.TYPED_ARRAY_SUPPORT && Uint8Array.prototype.indexOf === 'function' ) { return Uint8Array.prototype.indexOf.call(this, val, byteOffset); } return arrayIndexOf(this, [val], byteOffset); } function arrayIndexOf(arr, val, byteOffset) { var foundIndex = -1; for (var i = 0; byteOffset + i < arr.length; i++) { if ( arr[byteOffset + i] === val[foundIndex === -1 ? 0 : i - foundIndex] ) { if (foundIndex === -1) foundIndex = i; if (i - foundIndex + 1 === val.length) return byteOffset + foundIndex; } else { foundIndex = -1; } } return -1; } throw new TypeError('val must be string, number or Buffer'); }; // `get` is deprecated Buffer.prototype.get = function get(offset) { console.log( '.get() is deprecated. Permissions using array indexes instead.' ); return this.readUInt8(offset); }; // `set` is deprecated Buffer.prototype.set = function set(v, offset) { console.log( '.set() is deprecated. Permissions using array indexes instead.' ); return this.writeUInt8(v, offset); }; function hexWrite(buf, string, offset, length) { offset = Number(offset) || 0; var remaining = buf.length - offset; if (!length) { length = remaining; } else { length = Number(length); if (length > remaining) { length = remaining; } } // must be an even number of digits var strLen = string.length; if (strLen % 2 !== 0) throw new Error('Invalid hex string'); if (length > strLen / 2) { length = strLen / 2; } for (var i = 0; i < length; i++) { var parsed = parseInt(string.substr(i * 2, 2), 16); if (isNaN(parsed)) throw new Error('Invalid hex string'); buf[offset + i] = parsed; } return i; } function utf8Write(buf, string, offset, length) { return blitBuffer( utf8ToBytes(string, buf.length - offset), buf, offset, length ); } function asciiWrite(buf, string, offset, length) { return blitBuffer(asciiToBytes(string), buf, offset, length); } function binaryWrite(buf, string, offset, length) { return asciiWrite(buf, string, offset, length); } function base64Write(buf, string, offset, length) { return blitBuffer(base64ToBytes(string), buf, offset, length); } function ucs2Write(buf, string, offset, length) { return blitBuffer( utf16leToBytes(string, buf.length - offset), buf, offset, length ); } Buffer.prototype.write = function write( string, offset, length, encoding ) { // Buffer#write(string) if (offset === undefined) { encoding = 'utf8'; length = this.length; offset = 0; // Buffer#write(string, encoding) } else if (length === undefined && typeof offset === 'string') { encoding = offset; length = this.length; offset = 0; // Buffer#write(string, offset[, length][, encoding]) } else if (isFinite(offset)) { offset = offset | 0; if (isFinite(length)) { length = length | 0; if (encoding === undefined) encoding = 'utf8'; } else { encoding = length; length = undefined; } // legacy write(string, encoding, offset, length) - remove in v0.13 } else { var swap = encoding; encoding = offset; offset = length | 0; length = swap; } var remaining = this.length - offset; if (length === undefined || length > remaining) length = remaining; if ( (string.length > 0 && (length < 0 || offset < 0)) || offset > this.length ) { throw new RangeError('attempt to write outside buffer bounds'); } if (!encoding) encoding = 'utf8'; var loweredCase = false; for (;;) { switch (encoding) { case 'hex': return hexWrite(this, string, offset, length); case 'utf8': case 'utf-8': return utf8Write(this, string, offset, length); case 'ascii': return asciiWrite(this, string, offset, length); case 'binary': return binaryWrite(this, string, offset, length); case 'base64': // Warning: maxLength not taken into account in base64Write return base64Write(this, string, offset, length); case 'ucs2': case 'ucs-2': case 'utf16le': case 'utf-16le': return ucs2Write(this, string, offset, length); default: if (loweredCase) throw new TypeError('Unknown encoding: ' + encoding); encoding = ('' + encoding).toLowerCase(); loweredCase = true; } } }; Buffer.prototype.toJSON = function toJSON() { return { type: 'Buffer', data: Array.prototype.slice.call(this._arr || this, 0) }; }; function base64Slice(buf, start, end) { if (start === 0 && end === buf.length) { return base64.fromByteArray(buf); } else { return base64.fromByteArray(buf.slice(start, end)); } } function utf8Slice(buf, start, end) { end = Math.min(buf.length, end); var res = []; var i = start; while (i < end) { var firstByte = buf[i]; var codePoint = null; var bytesPerSequence = firstByte > 0xef ? 4 : firstByte > 0xdf ? 3 : firstByte > 0xbf ? 2 : 1; if (i + bytesPerSequence <= end) { var secondByte, thirdByte, fourthByte, tempCodePoint; switch (bytesPerSequence) { case 1: if (firstByte < 0x80) { codePoint = firstByte; } break; case 2: secondByte = buf[i + 1]; if ((secondByte & 0xc0) === 0x80) { tempCodePoint = ((firstByte & 0x1f) << 0x6) | (secondByte & 0x3f); if (tempCodePoint > 0x7f) { codePoint = tempCodePoint; } } break; case 3: secondByte = buf[i + 1]; thirdByte = buf[i + 2]; if ( (secondByte & 0xc0) === 0x80 && (thirdByte & 0xc0) === 0x80 ) { tempCodePoint = ((firstByte & 0xf) << 0xc) | ((secondByte & 0x3f) << 0x6) | (thirdByte & 0x3f); if ( tempCodePoint > 0x7ff && (tempCodePoint < 0xd800 || tempCodePoint > 0xdfff) ) { codePoint = tempCodePoint; } } break; case 4: secondByte = buf[i + 1]; thirdByte = buf[i + 2]; fourthByte = buf[i + 3]; if ( (secondByte & 0xc0) === 0x80 && (thirdByte & 0xc0) === 0x80 && (fourthByte & 0xc0) === 0x80 ) { tempCodePoint = ((firstByte & 0xf) << 0x12) | ((secondByte & 0x3f) << 0xc) | ((thirdByte & 0x3f) << 0x6) | (fourthByte & 0x3f); if (tempCodePoint > 0xffff && tempCodePoint < 0x110000) { codePoint = tempCodePoint; } } } } if (codePoint === null) { // we did not generate a valid codePoint so insert a // replacement char (U+FFFD) and advance only 1 byte codePoint = 0xfffd; bytesPerSequence = 1; } else if (codePoint > 0xffff) { // encode to utf16 (surrogate pair dance) codePoint -= 0x10000; res.push(((codePoint >>> 10) & 0x3ff) | 0xd800); codePoint = 0xdc00 | (codePoint & 0x3ff); } res.push(codePoint); i += bytesPerSequence; } return decodeCodePointsArray(res); } // Based on http://stackoverflow.com/a/22747272/680742, the browser with // the lowest limit is Chrome, with 0x10000 args. // We go 1 magnitude less, for safety var MAX_ARGUMENTS_LENGTH = 0x1000; function decodeCodePointsArray(codePoints) { var len = codePoints.length; if (len <= MAX_ARGUMENTS_LENGTH) { return String.fromCharCode.apply(String, codePoints); // avoid extra slice() } // Decode in chunks to avoid "call stack size exceeded". var res = ''; var i = 0; while (i < len) { res += String.fromCharCode.apply( String, codePoints.slice(i, (i += MAX_ARGUMENTS_LENGTH)) ); } return res; } function asciiSlice(buf, start, end) { var ret = ''; end = Math.min(buf.length, end); for (var i = start; i < end; i++) { ret += String.fromCharCode(buf[i] & 0x7f); } return ret; } function binarySlice(buf, start, end) { var ret = ''; end = Math.min(buf.length, end); for (var i = start; i < end; i++) { ret += String.fromCharCode(buf[i]); } return ret; } function hexSlice(buf, start, end) { var len = buf.length; if (!start || start < 0) start = 0; if (!end || end < 0 || end > len) end = len; var out = ''; for (var i = start; i < end; i++) { out += toHex(buf[i]); } return out; } function utf16leSlice(buf, start, end) { var bytes = buf.slice(start, end); var res = ''; for (var i = 0; i < bytes.length; i += 2) { res += String.fromCharCode(bytes[i] + bytes[i + 1] * 256); } return res; } Buffer.prototype.slice = function slice(start, end) { var len = this.length; start = ~~start; end = end === undefined ? len : ~~end; if (start < 0) { start += len; if (start < 0) start = 0; } else if (start > len) { start = len; } if (end < 0) { end += len; if (end < 0) end = 0; } else if (end > len) { end = len; } if (end < start) end = start; var newBuf; if (Buffer.TYPED_ARRAY_SUPPORT) { newBuf = Buffer._augment(this.subarray(start, end)); } else { var sliceLen = end - start; newBuf = new Buffer(sliceLen, undefined); for (var i = 0; i < sliceLen; i++) { newBuf[i] = this[i + start]; } } if (newBuf.length) newBuf.parent = this.parent || this; return newBuf; }; /* * Need to make sure that buffer isn't trying to write out of bounds. */ function checkOffset(offset, ext, length) { if (offset % 1 !== 0 || offset < 0) throw new RangeError('offset is not uint'); if (offset + ext > length) throw new RangeError('Trying to access beyond buffer length'); } Buffer.prototype.readUIntLE = function readUIntLE( offset, byteLength, noAssert ) { offset = offset | 0; byteLength = byteLength | 0; if (!noAssert) checkOffset(offset, byteLength, this.length); var val = this[offset]; var mul = 1; var i = 0; while (++i < byteLength && (mul *= 0x100)) { val += this[offset + i] * mul; } return val; }; Buffer.prototype.readUIntBE = function readUIntBE( offset, byteLength, noAssert ) { offset = offset | 0; byteLength = byteLength | 0; if (!noAssert) { checkOffset(offset, byteLength, this.length); } var val = this[offset + --byteLength]; var mul = 1; while (byteLength > 0 && (mul *= 0x100)) { val += this[offset + --byteLength] * mul; } return val; }; Buffer.prototype.readUInt8 = function readUInt8(offset, noAssert) { if (!noAssert) checkOffset(offset, 1, this.length); return this[offset]; }; Buffer.prototype.readUInt16LE = function readUInt16LE( offset, noAssert ) { if (!noAssert) checkOffset(offset, 2, this.length); return this[offset] | (this[offset + 1] << 8); }; Buffer.prototype.readUInt16BE = function readUInt16BE( offset, noAssert ) { if (!noAssert) checkOffset(offset, 2, this.length); return (this[offset] << 8) | this[offset + 1]; }; Buffer.prototype.readUInt32LE = function readUInt32LE( offset, noAssert ) { if (!noAssert) checkOffset(offset, 4, this.length); return ( (this[offset] | (this[offset + 1] << 8) | (this[offset + 2] << 16)) + this[offset + 3] * 0x1000000 ); }; Buffer.prototype.readUInt32BE = function readUInt32BE( offset, noAssert ) { if (!noAssert) checkOffset(offset, 4, this.length); return ( this[offset] * 0x1000000 + ((this[offset + 1] << 16) | (this[offset + 2] << 8) | this[offset + 3]) ); }; Buffer.prototype.readIntLE = function readIntLE( offset, byteLength, noAssert ) { offset = offset | 0; byteLength = byteLength | 0; if (!noAssert) checkOffset(offset, byteLength, this.length); var val = this[offset]; var mul = 1; var i = 0; while (++i < byteLength && (mul *= 0x100)) { val += this[offset + i] * mul; } mul *= 0x80; if (val >= mul) val -= Math.pow(2, 8 * byteLength); return val; }; Buffer.prototype.readIntBE = function readIntBE( offset, byteLength, noAssert ) { offset = offset | 0; byteLength = byteLength | 0; if (!noAssert) checkOffset(offset, byteLength, this.length); var i = byteLength; var mul = 1; var val = this[offset + --i]; while (i > 0 && (mul *= 0x100)) { val += this[offset + --i] * mul; } mul *= 0x80; if (val >= mul) val -= Math.pow(2, 8 * byteLength); return val; }; Buffer.prototype.readInt8 = function readInt8(offset, noAssert) { if (!noAssert) checkOffset(offset, 1, this.length); if (!(this[offset] & 0x80)) return this[offset]; return (0xff - this[offset] + 1) * -1; }; Buffer.prototype.readInt16LE = function readInt16LE(offset, noAssert) { if (!noAssert) checkOffset(offset, 2, this.length); var val = this[offset] | (this[offset + 1] << 8); return val & 0x8000 ? val | 0xffff0000 : val; }; Buffer.prototype.readInt16BE = function readInt16BE(offset, noAssert) { if (!noAssert) checkOffset(offset, 2, this.length); var val = this[offset + 1] | (this[offset] << 8); return val & 0x8000 ? val | 0xffff0000 : val; }; Buffer.prototype.readInt32LE = function readInt32LE(offset, noAssert) { if (!noAssert) checkOffset(offset, 4, this.length); return ( this[offset] | (this[offset + 1] << 8) | (this[offset + 2] << 16) | (this[offset + 3] << 24) ); }; Buffer.prototype.readInt32BE = function readInt32BE(offset, noAssert) { if (!noAssert) checkOffset(offset, 4, this.length); return ( (this[offset] << 24) | (this[offset + 1] << 16) | (this[offset + 2] << 8) | this[offset + 3] ); }; Buffer.prototype.readFloatLE = function readFloatLE(offset, noAssert) { if (!noAssert) checkOffset(offset, 4, this.length); return ieee754.read(this, offset, true, 23, 4); }; Buffer.prototype.readFloatBE = function readFloatBE(offset, noAssert) { if (!noAssert) checkOffset(offset, 4, this.length); return ieee754.read(this, offset, false, 23, 4); }; Buffer.prototype.readDoubleLE = function readDoubleLE( offset, noAssert ) { if (!noAssert) checkOffset(offset, 8, this.length); return ieee754.read(this, offset, true, 52, 8); }; Buffer.prototype.readDoubleBE = function readDoubleBE( offset, noAssert ) { if (!noAssert) checkOffset(offset, 8, this.length); return ieee754.read(this, offset, false, 52, 8); }; function checkInt(buf, value, offset, ext, max, min) { if (!Buffer.isBuffer(buf)) throw new TypeError('buffer must be a Buffer instance'); if (value > max || value < min) throw new RangeError('value is out of bounds'); if (offset + ext > buf.length) throw new RangeError('index out of range'); } Buffer.prototype.writeUIntLE = function writeUIntLE( value, offset, byteLength, noAssert ) { value = +value; offset = offset | 0; byteLength = byteLength | 0; if (!noAssert) checkInt( this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0 ); var mul = 1; var i = 0; this[offset] = value & 0xff; while (++i < byteLength && (mul *= 0x100)) { this[offset + i] = (value / mul) & 0xff; } return offset + byteLength; }; Buffer.prototype.writeUIntBE = function writeUIntBE( value, offset, byteLength, noAssert ) { value = +value; offset = offset | 0; byteLength = byteLength | 0; if (!noAssert) checkInt( this, value, offset, byteLength, Math.pow(2, 8 * byteLength), 0 ); var i = byteLength - 1; var mul = 1; this[offset + i] = value & 0xff; while (--i >= 0 && (mul *= 0x100)) { this[offset + i] = (value / mul) & 0xff; } return offset + byteLength; }; Buffer.prototype.writeUInt8 = function writeUInt8( value, offset, noAssert ) { value = +value; offset = offset | 0; if (!noAssert) checkInt(this, value, offset, 1, 0xff, 0); if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value); this[offset] = value; return offset + 1; }; function objectWriteUInt16(buf, value, offset, littleEndian) { if (value < 0) value = 0xffff + value + 1; for (var i = 0, j = Math.min(buf.length - offset, 2); i < j; i++) { buf[offset + i] = (value & (0xff << (8 * (littleEndian ? i : 1 - i)))) >>> ((littleEndian ? i : 1 - i) * 8); } } Buffer.prototype.writeUInt16LE = function writeUInt16LE( value, offset, noAssert ) { value = +value; offset = offset | 0; if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0); if (Buffer.TYPED_ARRAY_SUPPORT) { this[offset] = value; this[offset + 1] = value >>> 8; } else { objectWriteUInt16(this, value, offset, true); } return offset + 2; }; Buffer.prototype.writeUInt16BE = function writeUInt16BE( value, offset, noAssert ) { value = +value; offset = offset | 0; if (!noAssert) checkInt(this, value, offset, 2, 0xffff, 0); if (Buffer.TYPED_ARRAY_SUPPORT) { this[offset] = value >>> 8; this[offset + 1] = value; } else { objectWriteUInt16(this, value, offset, false); } return offset + 2; }; function objectWriteUInt32(buf, value, offset, littleEndian) { if (value < 0) value = 0xffffffff + value + 1; for (var i = 0, j = Math.min(buf.length - offset, 4); i < j; i++) { buf[offset + i] = (value >>> ((littleEndian ? i : 3 - i) * 8)) & 0xff; } } Buffer.prototype.writeUInt32LE = function writeUInt32LE( value, offset, noAssert ) { value = +value; offset = offset | 0; if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0); if (Buffer.TYPED_ARRAY_SUPPORT) { this[offset + 3] = value >>> 24; this[offset + 2] = value >>> 16; this[offset + 1] = value >>> 8; this[offset] = value; } else { objectWriteUInt32(this, value, offset, true); } return offset + 4; }; Buffer.prototype.writeUInt32BE = function writeUInt32BE( value, offset, noAssert ) { value = +value; offset = offset | 0; if (!noAssert) checkInt(this, value, offset, 4, 0xffffffff, 0); if (Buffer.TYPED_ARRAY_SUPPORT) { this[offset] = value >>> 24; this[offset + 1] = value >>> 16; this[offset + 2] = value >>> 8; this[offset + 3] = value; } else { objectWriteUInt32(this, value, offset, false); } return offset + 4; }; Buffer.prototype.writeIntLE = function writeIntLE( value, offset, byteLength, noAssert ) { value = +value; offset = offset | 0; if (!noAssert) { var limit = Math.pow(2, 8 * byteLength - 1); checkInt(this, value, offset, byteLength, limit - 1, -limit); } var i = 0; var mul = 1; var sub = value < 0 ? 1 : 0; this[offset] = value & 0xff; while (++i < byteLength && (mul *= 0x100)) { this[offset + i] = (((value / mul) >> 0) - sub) & 0xff; } return offset + byteLength; }; Buffer.prototype.writeIntBE = function writeIntBE( value, offset, byteLength, noAssert ) { value = +value; offset = offset | 0; if (!noAssert) { var limit = Math.pow(2, 8 * byteLength - 1); checkInt(this, value, offset, byteLength, limit - 1, -limit); } var i = byteLength - 1; var mul = 1; var sub = value < 0 ? 1 : 0; this[offset + i] = value & 0xff; while (--i >= 0 && (mul *= 0x100)) { this[offset + i] = (((value / mul) >> 0) - sub) & 0xff; } return offset + byteLength; }; Buffer.prototype.writeInt8 = function writeInt8( value, offset, noAssert ) { value = +value; offset = offset | 0; if (!noAssert) checkInt(this, value, offset, 1, 0x7f, -0x80); if (!Buffer.TYPED_ARRAY_SUPPORT) value = Math.floor(value); if (value < 0) value = 0xff + value + 1; this[offset] = value; return offset + 1; }; Buffer.prototype.writeInt16LE = function writeInt16LE( value, offset, noAssert ) { value = +value; offset = offset | 0; if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000); if (Buffer.TYPED_ARRAY_SUPPORT) { this[offset] = value; this[offset + 1] = value >>> 8; } else { objectWriteUInt16(this, value, offset, true); } return offset + 2; }; Buffer.prototype.writeInt16BE = function writeInt16BE( value, offset, noAssert ) { value = +value; offset = offset | 0; if (!noAssert) checkInt(this, value, offset, 2, 0x7fff, -0x8000); if (Buffer.TYPED_ARRAY_SUPPORT) { this[offset] = value >>> 8; this[offset + 1] = value; } else { objectWriteUInt16(this, value, offset, false); } return offset + 2; }; Buffer.prototype.writeInt32LE = function writeInt32LE( value, offset, noAssert ) { value = +value; offset = offset | 0; if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000); if (Buffer.TYPED_ARRAY_SUPPORT) { this[offset] = value; this[offset + 1] = value >>> 8; this[offset + 2] = value >>> 16; this[offset + 3] = value >>> 24; } else { objectWriteUInt32(this, value, offset, true); } return offset + 4; }; Buffer.prototype.writeInt32BE = function writeInt32BE( value, offset, noAssert ) { value = +value; offset = offset | 0; if (!noAssert) checkInt(this, value, offset, 4, 0x7fffffff, -0x80000000); if (value < 0) value = 0xffffffff + value + 1; if (Buffer.TYPED_ARRAY_SUPPORT) { this[offset] = value >>> 24; this[offset + 1] = value >>> 16; this[offset + 2] = value >>> 8; this[offset + 3] = value; } else { objectWriteUInt32(this, value, offset, false); } return offset + 4; }; function checkIEEE754(buf, value, offset, ext, max, min) { if (value > max || value < min) throw new RangeError('value is out of bounds'); if (offset + ext > buf.length) throw new RangeError('index out of range'); if (offset < 0) throw new RangeError('index out of range'); } function writeFloat(buf, value, offset, littleEndian, noAssert) { if (!noAssert) { checkIEEE754( buf, value, offset, 4, 3.4028234663852886e38, -3.4028234663852886e38 ); } ieee754.write(buf, value, offset, littleEndian, 23, 4); return offset + 4; } Buffer.prototype.writeFloatLE = function writeFloatLE( value, offset, noAssert ) { return writeFloat(this, value, offset, true, noAssert); }; Buffer.prototype.writeFloatBE = function writeFloatBE( value, offset, noAssert ) { return writeFloat(this, value, offset, false, noAssert); }; function writeDouble(buf, value, offset, littleEndian, noAssert) { if (!noAssert) { checkIEEE754( buf, value, offset, 8, 1.7976931348623157e308, -1.7976931348623157e308 ); } ieee754.write(buf, value, offset, littleEndian, 52, 8); return offset + 8; } Buffer.prototype.writeDoubleLE = function writeDoubleLE( value, offset, noAssert ) { return writeDouble(this, value, offset, true, noAssert); }; Buffer.prototype.writeDoubleBE = function writeDoubleBE( value, offset, noAssert ) { return writeDouble(this, value, offset, false, noAssert); }; // copy(targetBuffer, targetStart=0, sourceStart=0, sourceEnd=buffer.length) Buffer.prototype.copy = function copy(target, targetStart, start, end) { if (!start) start = 0; if (!end && end !== 0) end = this.length; if (targetStart >= target.length) targetStart = target.length; if (!targetStart) targetStart = 0; if (end > 0 && end < start) end = start; // Copy 0 bytes; we're done if (end === start) return 0; if (target.length === 0 || this.length === 0) return 0; // Fatal error conditions if (targetStart < 0) { throw new RangeError('targetStart out of bounds'); } if (start < 0 || start >= this.length) throw new RangeError('sourceStart out of bounds'); if (end < 0) throw new RangeError('sourceEnd out of bounds'); // Are we oob? if (end > this.length) end = this.length; if (target.length - targetStart < end - start) { end = target.length - targetStart + start; } var len = end - start; var i; if (this === target && start < targetStart && targetStart < end) { // descending copy from end for (i = len - 1; i >= 0; i--) { target[i + targetStart] = this[i + start]; } } else if (len < 1000 || !Buffer.TYPED_ARRAY_SUPPORT) { // ascending copy from start for (i = 0; i < len; i++) { target[i + targetStart] = this[i + start]; } } else { target._set(this.subarray(start, start + len), targetStart); } return len; }; // fill(value, start=0, end=buffer.length) Buffer.prototype.fill = function fill(value, start, end) { if (!value) value = 0; if (!start) start = 0; if (!end) end = this.length; if (end < start) throw new RangeError('end < start'); // Fill 0 bytes; we're done if (end === start) return; if (this.length === 0) return; if (start < 0 || start >= this.length) throw new RangeError('start out of bounds'); if (end < 0 || end > this.length) throw new RangeError('end out of bounds'); var i; if (typeof value === 'number') { for (i = start; i < end; i++) { this[i] = value; } } else { var bytes = utf8ToBytes(value.toString()); var len = bytes.length; for (i = start; i < end; i++) { this[i] = bytes[i % len]; } } return this; }; /** * Creates a new `ArrayBuffer` with the *copied* memory of the buffer instance. * Added in Node 0.12. Only available in browsers that support ArrayBuffer. */ Buffer.prototype.toArrayBuffer = function toArrayBuffer() { if (typeof Uint8Array !== 'undefined') { if (Buffer.TYPED_ARRAY_SUPPORT) { return new Buffer(this).buffer; } else { var buf = new Uint8Array(this.length); for (var i = 0, len = buf.length; i < len; i += 1) { buf[i] = this[i]; } return buf.buffer; } } else { throw new TypeError( 'Buffer.toArrayBuffer not supported in this browser' ); } }; // HELPER FUNCTIONS // ================ var BP = Buffer.prototype; /** * Augment a Uint8Array *instance* (not the Uint8Array class!) with Buffer methods */ Buffer._augment = function _augment(arr) { arr.constructor = Buffer; arr._isBuffer = true; // save reference to original Uint8Array set method before overwriting arr._set = arr.set; // deprecated arr.get = BP.get; arr.set = BP.set; arr.write = BP.write; arr.toString = BP.toString; arr.toLocaleString = BP.toString; arr.toJSON = BP.toJSON; arr.equals = BP.equals; arr.compare = BP.compare; arr.indexOf = BP.indexOf; arr.copy = BP.copy; arr.slice = BP.slice; arr.readUIntLE = BP.readUIntLE; arr.readUIntBE = BP.readUIntBE; arr.readUInt8 = BP.readUInt8; arr.readUInt16LE = BP.readUInt16LE; arr.readUInt16BE = BP.readUInt16BE; arr.readUInt32LE = BP.readUInt32LE; arr.readUInt32BE = BP.readUInt32BE; arr.readIntLE = BP.readIntLE; arr.readIntBE = BP.readIntBE; arr.readInt8 = BP.readInt8; arr.readInt16LE = BP.readInt16LE; arr.readInt16BE = BP.readInt16BE; arr.readInt32LE = BP.readInt32LE; arr.readInt32BE = BP.readInt32BE; arr.readFloatLE = BP.readFloatLE; arr.readFloatBE = BP.readFloatBE; arr.readDoubleLE = BP.readDoubleLE; arr.readDoubleBE = BP.readDoubleBE; arr.writeUInt8 = BP.writeUInt8; arr.writeUIntLE = BP.writeUIntLE; arr.writeUIntBE = BP.writeUIntBE; arr.writeUInt16LE = BP.writeUInt16LE; arr.writeUInt16BE = BP.writeUInt16BE; arr.writeUInt32LE = BP.writeUInt32LE; arr.writeUInt32BE = BP.writeUInt32BE; arr.writeIntLE = BP.writeIntLE; arr.writeIntBE = BP.writeIntBE; arr.writeInt8 = BP.writeInt8; arr.writeInt16LE = BP.writeInt16LE; arr.writeInt16BE = BP.writeInt16BE; arr.writeInt32LE = BP.writeInt32LE; arr.writeInt32BE = BP.writeInt32BE; arr.writeFloatLE = BP.writeFloatLE; arr.writeFloatBE = BP.writeFloatBE; arr.writeDoubleLE = BP.writeDoubleLE; arr.writeDoubleBE = BP.writeDoubleBE; arr.fill = BP.fill; arr.inspect = BP.inspect; arr.toArrayBuffer = BP.toArrayBuffer; return arr; }; var INVALID_BASE64_RE = /[^+\/0-9A-Za-z-_]/g; function base64clean(str) { // Node strips out invalid characters like \n and \t from the string, base64-js does not str = stringtrim(str).replace(INVALID_BASE64_RE, ''); // Node converts strings with length < 2 to '' if (str.length < 2) return ''; // Node allows for non-padded base64 strings (missing trailing ===), base64-js does not while (str.length % 4 !== 0) { str = str + '='; } return str; } function stringtrim(str) { if (str.trim) return str.trim(); return str.replace(/^\s+|\s+$/g, ''); } function toHex(n) { if (n < 16) return '0' + n.toString(16); return n.toString(16); } function utf8ToBytes(string, units) { units = units || Infinity; var codePoint; var length = string.length; var leadSurrogate = null; var bytes = []; for (var i = 0; i < length; i++) { codePoint = string.charCodeAt(i); // is surrogate component if (codePoint > 0xd7ff && codePoint < 0xe000) { // last char was a lead if (!leadSurrogate) { // no lead yet if (codePoint > 0xdbff) { // unexpected trail if ((units -= 3) > -1) bytes.push(0xef, 0xbf, 0xbd); continue; } else if (i + 1 === length) { // unpaired lead if ((units -= 3) > -1) bytes.push(0xef, 0xbf, 0xbd); continue; } // valid lead leadSurrogate = codePoint; continue; } // 2 leads in a row if (codePoint < 0xdc00) { if ((units -= 3) > -1) bytes.push(0xef, 0xbf, 0xbd); leadSurrogate = codePoint; continue; } // valid surrogate pair codePoint = ((leadSurrogate - 0xd800) << 10) | (codePoint - 0xdc00) | 0x10000; } else if (leadSurrogate) { // valid bmp char, but last char was a lead if ((units -= 3) > -1) bytes.push(0xef, 0xbf, 0xbd); } leadSurrogate = null; // encode utf8 if (codePoint < 0x80) { if ((units -= 1) < 0) break; bytes.push(codePoint); } else if (codePoint < 0x800) { if ((units -= 2) < 0) break; bytes.push((codePoint >> 0x6) | 0xc0, (codePoint & 0x3f) | 0x80); } else if (codePoint < 0x10000) { if ((units -= 3) < 0) break; bytes.push( (codePoint >> 0xc) | 0xe0, ((codePoint >> 0x6) & 0x3f) | 0x80, (codePoint & 0x3f) | 0x80 ); } else if (codePoint < 0x110000) { if ((units -= 4) < 0) break; bytes.push( (codePoint >> 0x12) | 0xf0, ((codePoint >> 0xc) & 0x3f) | 0x80, ((codePoint >> 0x6) & 0x3f) | 0x80, (codePoint & 0x3f) | 0x80 ); } else { throw new Error('Invalid code point'); } } return bytes; } function asciiToBytes(str) { var byteArray = []; for (var i = 0; i < str.length; i++) { // Node's code seems to be doing this and not & 0x7F.. byteArray.push(str.charCodeAt(i) & 0xff); } return byteArray; } function utf16leToBytes(str, units) { var c, hi, lo; var byteArray = []; for (var i = 0; i < str.length; i++) { if ((units -= 2) < 0) break; c = str.charCodeAt(i); hi = c >> 8; lo = c % 256; byteArray.push(lo); byteArray.push(hi); } return byteArray; } function base64ToBytes(str) { return base64.toByteArray(base64clean(str)); } function blitBuffer(src, dst, offset, length) { for (var i = 0; i < length; i++) { if (i + offset >= dst.length || i >= src.length) break; dst[i + offset] = src[i]; } return i; } }, { 'base64-js': 44, ieee754: 45, 'is-array': 46 } ], 44: [ function(require, module, exports) { var lookup = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; (function(exports) { 'use strict'; var Arr = typeof Uint8Array !== 'undefined' ? Uint8Array : Array; var PLUS = '+'.charCodeAt(0); var SLASH = '/'.charCodeAt(0); var NUMBER = '0'.charCodeAt(0); var LOWER = 'a'.charCodeAt(0); var UPPER = 'A'.charCodeAt(0); var PLUS_URL_SAFE = '-'.charCodeAt(0); var SLASH_URL_SAFE = '_'.charCodeAt(0); function decode(elt) { var code = elt.charCodeAt(0); if (code === PLUS || code === PLUS_URL_SAFE) return 62; // '+' if (code === SLASH || code === SLASH_URL_SAFE) return 63; // '/' if (code < NUMBER) return -1; //no match if (code < NUMBER + 10) return code - NUMBER + 26 + 26; if (code < UPPER + 26) return code - UPPER; if (code < LOWER + 26) return code - LOWER + 26; } function b64ToByteArray(b64) { var i, j, l, tmp, placeHolders, arr; if (b64.length % 4 > 0) { throw new Error('Invalid string. Length must be a multiple of 4'); } // the number of equal signs (place holders) // if there are two placeholders, than the two characters before it // represent one byte // if there is only one, then the three characters before it represent 2 bytes // this is just a cheap hack to not do indexOf twice var len = b64.length; placeHolders = '=' === b64.charAt(len - 2) ? 2 : '=' === b64.charAt(len - 1) ? 1 : 0; // base64 is 4/3 + up to two characters of the original data arr = new Arr((b64.length * 3) / 4 - placeHolders); // if there are placeholders, only get up to the last complete 4 chars l = placeHolders > 0 ? b64.length - 4 : b64.length; var L = 0; function push(v) { arr[L++] = v; } for (i = 0, j = 0; i < l; i += 4, j += 3) { tmp = (decode(b64.charAt(i)) << 18) | (decode(b64.charAt(i + 1)) << 12) | (decode(b64.charAt(i + 2)) << 6) | decode(b64.charAt(i + 3)); push((tmp & 0xff0000) >> 16); push((tmp & 0xff00) >> 8); push(tmp & 0xff); } if (placeHolders === 2) { tmp = (decode(b64.charAt(i)) << 2) | (decode(b64.charAt(i + 1)) >> 4); push(tmp & 0xff); } else if (placeHolders === 1) { tmp = (decode(b64.charAt(i)) << 10) | (decode(b64.charAt(i + 1)) << 4) | (decode(b64.charAt(i + 2)) >> 2); push((tmp >> 8) & 0xff); push(tmp & 0xff); } return arr; } function uint8ToBase64(uint8) { var i, extraBytes = uint8.length % 3, // if we have 1 byte left, pad 2 bytes output = '', temp, length; function encode(num) { return lookup.charAt(num); } function tripletToBase64(num) { return ( encode((num >> 18) & 0x3f) + encode((num >> 12) & 0x3f) + encode((num >> 6) & 0x3f) + encode(num & 0x3f) ); } // go through the array every three bytes, we'll deal with trailing stuff later for ( i = 0, length = uint8.length - extraBytes; i < length; i += 3 ) { temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + uint8[i + 2]; output += tripletToBase64(temp); } // pad the end with zeros, but make sure to not forget the extra bytes switch (extraBytes) { case 1: temp = uint8[uint8.length - 1]; output += encode(temp >> 2); output += encode((temp << 4) & 0x3f); output += '=='; break; case 2: temp = (uint8[uint8.length - 2] << 8) + uint8[uint8.length - 1]; output += encode(temp >> 10); output += encode((temp >> 4) & 0x3f); output += encode((temp << 2) & 0x3f); output += '='; break; } return output; } exports.toByteArray = b64ToByteArray; exports.fromByteArray = uint8ToBase64; })(typeof exports === 'undefined' ? (this.base64js = {}) : exports); }, {} ], 45: [ function(require, module, exports) { exports.read = function(buffer, offset, isLE, mLen, nBytes) { var e, m; var eLen = nBytes * 8 - mLen - 1; var eMax = (1 << eLen) - 1; var eBias = eMax >> 1; var nBits = -7; var i = isLE ? nBytes - 1 : 0; var d = isLE ? -1 : 1; var s = buffer[offset + i]; i += d; e = s & ((1 << -nBits) - 1); s >>= -nBits; nBits += eLen; for ( ; nBits > 0; e = e * 256 + buffer[offset + i], i += d, nBits -= 8 ) {} m = e & ((1 << -nBits) - 1); e >>= -nBits; nBits += mLen; for ( ; nBits > 0; m = m * 256 + buffer[offset + i], i += d, nBits -= 8 ) {} if (e === 0) { e = 1 - eBias; } else if (e === eMax) { return m ? NaN : (s ? -1 : 1) * Infinity; } else { m = m + Math.pow(2, mLen); e = e - eBias; } return (s ? -1 : 1) * m * Math.pow(2, e - mLen); }; exports.write = function(buffer, value, offset, isLE, mLen, nBytes) { var e, m, c; var eLen = nBytes * 8 - mLen - 1; var eMax = (1 << eLen) - 1; var eBias = eMax >> 1; var rt = mLen === 23 ? Math.pow(2, -24) - Math.pow(2, -77) : 0; var i = isLE ? 0 : nBytes - 1; var d = isLE ? 1 : -1; var s = value < 0 || (value === 0 && 1 / value < 0) ? 1 : 0; value = Math.abs(value); if (isNaN(value) || value === Infinity) { m = isNaN(value) ? 1 : 0; e = eMax; } else { e = Math.floor(Math.log(value) / Math.LN2); if (value * (c = Math.pow(2, -e)) < 1) { e--; c *= 2; } if (e + eBias >= 1) { value += rt / c; } else { value += rt * Math.pow(2, 1 - eBias); } if (value * c >= 2) { e++; c /= 2; } if (e + eBias >= eMax) { m = 0; e = eMax; } else if (e + eBias >= 1) { m = (value * c - 1) * Math.pow(2, mLen); e = e + eBias; } else { m = value * Math.pow(2, eBias - 1) * Math.pow(2, mLen); e = 0; } } for ( ; mLen >= 8; buffer[offset + i] = m & 0xff, i += d, m /= 256, mLen -= 8 ) {} e = (e << mLen) | m; eLen += mLen; for ( ; eLen > 0; buffer[offset + i] = e & 0xff, i += d, e /= 256, eLen -= 8 ) {} buffer[offset + i - d] |= s * 128; }; }, {} ], 46: [ function(require, module, exports) { /** * isArray */ var isArray = Array.isArray; /** * toString */ var str = Object.prototype.toString; /** * Whether or not the given `val` * is an array. * * example: * * isArray([]); * // > true * isArray(arguments); * // > false * isArray(''); * // > false * * @param {mixed} val * @return {bool} */ module.exports = isArray || function(val) { return !!val && '[object Array]' == str.call(val); }; }, {} ], 47: [ function(require, module, exports) { // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. function EventEmitter() { this._events = this._events || {}; this._maxListeners = this._maxListeners || undefined; } module.exports = EventEmitter; // Backwards-compat with node 0.10.x EventEmitter.EventEmitter = EventEmitter; EventEmitter.prototype._events = undefined; EventEmitter.prototype._maxListeners = undefined; // By default EventEmitters will print a warning if more than 10 listeners are // added to it. This is a useful default which helps finding memory leaks. EventEmitter.defaultMaxListeners = 10; // Obviously not all Emitters should be limited to 10. This function allows // that to be increased. Set to zero for unlimited. EventEmitter.prototype.setMaxListeners = function(n) { if (!isNumber(n) || n < 0 || isNaN(n)) throw TypeError('n must be a positive number'); this._maxListeners = n; return this; }; EventEmitter.prototype.emit = function(type) { var er, handler, len, args, i, listeners; if (!this._events) this._events = {}; // If there is no 'error' event listener then throw. if (type === 'error') { if ( !this._events.error || (isObject(this._events.error) && !this._events.error.length) ) { er = arguments[1]; if (er instanceof Error) { throw er; // Unhandled 'error' event } throw TypeError('Uncaught, unspecified "error" event.'); } } handler = this._events[type]; if (isUndefined(handler)) return false; if (isFunction(handler)) { switch (arguments.length) { // fast cases case 1: handler.call(this); break; case 2: handler.call(this, arguments[1]); break; case 3: handler.call(this, arguments[1], arguments[2]); break; // slower default: len = arguments.length; args = new Array(len - 1); for (i = 1; i < len; i++) args[i - 1] = arguments[i]; handler.apply(this, args); } } else if (isObject(handler)) { len = arguments.length; args = new Array(len - 1); for (i = 1; i < len; i++) args[i - 1] = arguments[i]; listeners = handler.slice(); len = listeners.length; for (i = 0; i < len; i++) listeners[i].apply(this, args); } return true; }; EventEmitter.prototype.addListener = function(type, listener) { var m; if (!isFunction(listener)) throw TypeError('listener must be a function'); if (!this._events) this._events = {}; // To avoid recursion in the case that type === "newListener"! Before // adding it to the listeners, first emit "newListener". if (this._events.newListener) this.emit( 'newListener', type, isFunction(listener.listener) ? listener.listener : listener ); if (!this._events[type]) // Optimize the case of one listener. Don't need the extra array object. this._events[type] = listener; else if (isObject(this._events[type])) // If we've already got an array, just append. this._events[type].push(listener); // Adding the second element, need to change to array. else this._events[type] = [this._events[type], listener]; // Check for listener leak if (isObject(this._events[type]) && !this._events[type].warned) { var m; if (!isUndefined(this._maxListeners)) { m = this._maxListeners; } else { m = EventEmitter.defaultMaxListeners; } if (m && m > 0 && this._events[type].length > m) { this._events[type].warned = true; console.error( '(node) warning: possible EventEmitter memory ' + 'leak detected. %d listeners added. ' + 'Use emitter.setMaxListeners() to increase limit.', this._events[type].length ); if (typeof console.trace === 'function') { // not supported in IE 10 console.trace(); } } } return this; }; EventEmitter.prototype.on = EventEmitter.prototype.addListener; EventEmitter.prototype.once = function(type, listener) { if (!isFunction(listener)) throw TypeError('listener must be a function'); var fired = false; function g() { this.removeListener(type, g); if (!fired) { fired = true; listener.apply(this, arguments); } } g.listener = listener; this.on(type, g); return this; }; // emits a 'removeListener' event iff the listener was removed EventEmitter.prototype.removeListener = function(type, listener) { var list, position, length, i; if (!isFunction(listener)) throw TypeError('listener must be a function'); if (!this._events || !this._events[type]) return this; list = this._events[type]; length = list.length; position = -1; if ( list === listener || (isFunction(list.listener) && list.listener === listener) ) { delete this._events[type]; if (this._events.removeListener) this.emit('removeListener', type, listener); } else if (isObject(list)) { for (i = length; i-- > 0; ) { if ( list[i] === listener || (list[i].listener && list[i].listener === listener) ) { position = i; break; } } if (position < 0) return this; if (list.length === 1) { list.length = 0; delete this._events[type]; } else { list.splice(position, 1); } if (this._events.removeListener) this.emit('removeListener', type, listener); } return this; }; EventEmitter.prototype.removeAllListeners = function(type) { var key, listeners; if (!this._events) return this; // not listening for removeListener, no need to emit if (!this._events.removeListener) { if (arguments.length === 0) this._events = {}; else if (this._events[type]) delete this._events[type]; return this; } // emit removeListener for all listeners on all events if (arguments.length === 0) { for (key in this._events) { if (key === 'removeListener') continue; this.removeAllListeners(key); } this.removeAllListeners('removeListener'); this._events = {}; return this; } listeners = this._events[type]; if (isFunction(listeners)) { this.removeListener(type, listeners); } else { // LIFO order while (listeners.length) this.removeListener(type, listeners[listeners.length - 1]); } delete this._events[type]; return this; }; EventEmitter.prototype.listeners = function(type) { var ret; if (!this._events || !this._events[type]) ret = []; else if (isFunction(this._events[type])) ret = [this._events[type]]; else ret = this._events[type].slice(); return ret; }; EventEmitter.listenerCount = function(emitter, type) { var ret; if (!emitter._events || !emitter._events[type]) ret = 0; else if (isFunction(emitter._events[type])) ret = 1; else ret = emitter._events[type].length; return ret; }; function isFunction(arg) { return typeof arg === 'function'; } function isNumber(arg) { return typeof arg === 'number'; } function isObject(arg) { return typeof arg === 'object' && arg !== null; } function isUndefined(arg) { return arg === void 0; } }, {} ], 48: [ function(require, module, exports) { if (typeof Object.create === 'function') { // implementation from standard node.js 'util' module module.exports = function inherits(ctor, superCtor) { ctor.super_ = superCtor; ctor.prototype = Object.create(superCtor.prototype, { constructor: { value: ctor, enumerable: false, writable: true, configurable: true } }); }; } else { // old school shim for old browsers module.exports = function inherits(ctor, superCtor) { ctor.super_ = superCtor; var TempCtor = function() {}; TempCtor.prototype = superCtor.prototype; ctor.prototype = new TempCtor(); ctor.prototype.constructor = ctor; }; } }, {} ], 49: [ function(require, module, exports) { module.exports = Array.isArray || function(arr) { return Object.prototype.toString.call(arr) == '[object Array]'; }; }, {} ], 50: [ function(require, module, exports) { exports.endianness = function() { return 'LE'; }; exports.hostname = function() { if (typeof location !== 'undefined') { return location.hostname; } else return ''; }; exports.loadavg = function() { return []; }; exports.uptime = function() { return 0; }; exports.freemem = function() { return Number.MAX_VALUE; }; exports.totalmem = function() { return Number.MAX_VALUE; }; exports.cpus = function() { return []; }; exports.type = function() { return 'Browser'; }; exports.release = function() { if (typeof navigator !== 'undefined') { return navigator.appVersion; } return ''; }; exports.networkInterfaces = exports.getNetworkInterfaces = function() { return {}; }; exports.arch = function() { return 'javascript'; }; exports.platform = function() { return 'browser'; }; exports.tmpdir = exports.tmpDir = function() { return '/tmp'; }; exports.EOL = '\n'; }, {} ], 51: [ function(require, module, exports) { // shim for using process in browser var process = (module.exports = {}); var queue = []; var draining = false; var currentQueue; var queueIndex = -1; function cleanUpNextTick() { draining = false; if (currentQueue.length) { queue = currentQueue.concat(queue); } else { queueIndex = -1; } if (queue.length) { drainQueue(); } } function drainQueue() { if (draining) { return; } var timeout = setTimeout(cleanUpNextTick); draining = true; var len = queue.length; while (len) { currentQueue = queue; queue = []; while (++queueIndex < len) { if (currentQueue) { currentQueue[queueIndex].run(); } } queueIndex = -1; len = queue.length; } currentQueue = null; draining = false; clearTimeout(timeout); } process.nextTick = function(fun) { var args = new Array(arguments.length - 1); if (arguments.length > 1) { for (var i = 1; i < arguments.length; i++) { args[i - 1] = arguments[i]; } } queue.push(new Item(fun, args)); if (queue.length === 1 && !draining) { setTimeout(drainQueue, 0); } }; // v8 likes predictible objects function Item(fun, array) { this.fun = fun; this.array = array; } Item.prototype.run = function() { this.fun.apply(null, this.array); }; process.title = 'browser'; process.browser = true; process.env = {}; process.argv = []; process.version = ''; // empty string to avoid regexp issues process.versions = {}; function noop() {} process.on = noop; process.addListener = noop; process.once = noop; process.off = noop; process.removeListener = noop; process.removeAllListeners = noop; process.emit = noop; process.binding = function(name) { throw new Error('process.binding is not supported'); }; process.cwd = function() { return '/'; }; process.chdir = function(dir) { throw new Error('process.chdir is not supported'); }; process.umask = function() { return 0; }; }, {} ], 52: [ function(require, module, exports) { module.exports = require('./lib/_stream_duplex.js'); }, { './lib/_stream_duplex.js': 53 } ], 53: [ function(require, module, exports) { (function(process) { // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. // a duplex stream is just a stream that is both readable and writable. // Since JS doesn't have multiple prototypal inheritance, this class // prototypally inherits from Readable, and then parasitically from // Writable. module.exports = Duplex; /**/ var objectKeys = Object.keys || function(obj) { var keys = []; for (var key in obj) keys.push(key); return keys; }; /**/ /**/ var util = require('core-util-is'); util.inherits = require('inherits'); /**/ var Readable = require('./_stream_readable'); var Writable = require('./_stream_writable'); util.inherits(Duplex, Readable); forEach(objectKeys(Writable.prototype), function(method) { if (!Duplex.prototype[method]) Duplex.prototype[method] = Writable.prototype[method]; }); function Duplex(options) { if (!(this instanceof Duplex)) return new Duplex(options); Readable.call(this, options); Writable.call(this, options); if (options && options.readable === false) this.readable = false; if (options && options.writable === false) this.writable = false; this.allowHalfOpen = true; if (options && options.allowHalfOpen === false) this.allowHalfOpen = false; this.once('end', onend); } // the no-half-open enforcer function onend() { // if we allow half-open state, or if the writable side ended, // then we're ok. if (this.allowHalfOpen || this._writableState.ended) return; // no more data can be written. // But allow more writes to happen in this tick. process.nextTick(this.end.bind(this)); } function forEach(xs, f) { for (var i = 0, l = xs.length; i < l; i++) { f(xs[i], i); } } }.call(this, require('_process'))); }, { './_stream_readable': 55, './_stream_writable': 57, _process: 51, 'core-util-is': 58, inherits: 48 } ], 54: [ function(require, module, exports) { // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. // a passthrough stream. // basically just the most minimal sort of Transform stream. // Every written chunk gets output as-is. module.exports = PassThrough; var Transform = require('./_stream_transform'); /**/ var util = require('core-util-is'); util.inherits = require('inherits'); /**/ util.inherits(PassThrough, Transform); function PassThrough(options) { if (!(this instanceof PassThrough)) return new PassThrough(options); Transform.call(this, options); } PassThrough.prototype._transform = function(chunk, encoding, cb) { cb(null, chunk); }; }, { './_stream_transform': 56, 'core-util-is': 58, inherits: 48 } ], 55: [ function(require, module, exports) { (function(process) { // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. module.exports = Readable; /**/ var isArray = require('isarray'); /**/ /**/ var Buffer = require('buffer').Buffer; /**/ Readable.ReadableState = ReadableState; var EE = require('events').EventEmitter; /**/ if (!EE.listenerCount) EE.listenerCount = function(emitter, type) { return emitter.listeners(type).length; }; /**/ var Stream = require('stream'); /**/ var util = require('core-util-is'); util.inherits = require('inherits'); /**/ var StringDecoder; /**/ var debug = require('util'); if (debug && debug.debuglog) { debug = debug.debuglog('stream'); } else { debug = function() {}; } /**/ util.inherits(Readable, Stream); function ReadableState(options, stream) { var Duplex = require('./_stream_duplex'); options = options || {}; // the point at which it stops calling _read() to fill the buffer // Note: 0 is a valid value, means "don't call _read preemptively ever" var hwm = options.highWaterMark; var defaultHwm = options.objectMode ? 16 : 16 * 1024; this.highWaterMark = hwm || hwm === 0 ? hwm : defaultHwm; // cast to ints. this.highWaterMark = ~~this.highWaterMark; this.buffer = []; this.length = 0; this.pipes = null; this.pipesCount = 0; this.flowing = null; this.ended = false; this.endEmitted = false; this.reading = false; // a flag to be able to tell if the onwrite cb is called immediately, // or on a later tick. We set this to true at first, because any // actions that shouldn't happen until "later" should generally also // not happen before the first write call. this.sync = true; // whenever we return null, then we set a flag to say // that we're awaiting a 'readable' event emission. this.needReadable = false; this.emittedReadable = false; this.readableListening = false; // object stream flag. Used to make read(n) ignore n and to // make all the buffer merging and length checks go away this.objectMode = !!options.objectMode; if (stream instanceof Duplex) this.objectMode = this.objectMode || !!options.readableObjectMode; // Crypto is kind of old and crusty. Historically, its default string // encoding is 'binary' so we have to make this configurable. // Everything else in the universe uses 'utf8', though. this.defaultEncoding = options.defaultEncoding || 'utf8'; // when piping, we only care about 'readable' events that happen // after read()ing all the bytes and not getting any pushback. this.ranOut = false; // the number of writers that are awaiting a drain event in .pipe()s this.awaitDrain = 0; // if true, a maybeReadMore has been scheduled this.readingMore = false; this.decoder = null; this.encoding = null; if (options.encoding) { if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; this.decoder = new StringDecoder(options.encoding); this.encoding = options.encoding; } } function Readable(options) { var Duplex = require('./_stream_duplex'); if (!(this instanceof Readable)) return new Readable(options); this._readableState = new ReadableState(options, this); // legacy this.readable = true; Stream.call(this); } // Manually shove something into the read() buffer. // This returns true if the highWaterMark has not been hit yet, // similar to how Writable.write() returns true if you should // write() some more. Readable.prototype.push = function(chunk, encoding) { var state = this._readableState; if (util.isString(chunk) && !state.objectMode) { encoding = encoding || state.defaultEncoding; if (encoding !== state.encoding) { chunk = new Buffer(chunk, encoding); encoding = ''; } } return readableAddChunk(this, state, chunk, encoding, false); }; // Unshift should *always* be something directly out of read() Readable.prototype.unshift = function(chunk) { var state = this._readableState; return readableAddChunk(this, state, chunk, '', true); }; function readableAddChunk( stream, state, chunk, encoding, addToFront ) { var er = chunkInvalid(state, chunk); if (er) { stream.emit('error', er); } else if (util.isNullOrUndefined(chunk)) { state.reading = false; if (!state.ended) onEofChunk(stream, state); } else if (state.objectMode || (chunk && chunk.length > 0)) { if (state.ended && !addToFront) { var e = new Error('stream.push() after EOF'); stream.emit('error', e); } else if (state.endEmitted && addToFront) { var e = new Error('stream.unshift() after end event'); stream.emit('error', e); } else { if (state.decoder && !addToFront && !encoding) chunk = state.decoder.write(chunk); if (!addToFront) state.reading = false; // if we want the data now, just emit it. if (state.flowing && state.length === 0 && !state.sync) { stream.emit('data', chunk); stream.read(0); } else { // update the buffer info. state.length += state.objectMode ? 1 : chunk.length; if (addToFront) state.buffer.unshift(chunk); else state.buffer.push(chunk); if (state.needReadable) emitReadable(stream); } maybeReadMore(stream, state); } } else if (!addToFront) { state.reading = false; } return needMoreData(state); } // if it's past the high water mark, we can push in some more. // Also, if we have no data yet, we can stand some // more bytes. This is to work around cases where hwm=0, // such as the repl. Also, if the push() triggered a // readable event, and the user called read(largeNumber) such that // needReadable was set, then we ought to push more, so that another // 'readable' event will be triggered. function needMoreData(state) { return ( !state.ended && (state.needReadable || state.length < state.highWaterMark || state.length === 0) ); } // backwards compatibility. Readable.prototype.setEncoding = function(enc) { if (!StringDecoder) StringDecoder = require('string_decoder/').StringDecoder; this._readableState.decoder = new StringDecoder(enc); this._readableState.encoding = enc; return this; }; // Don't raise the hwm > 128MB var MAX_HWM = 0x800000; function roundUpToNextPowerOf2(n) { if (n >= MAX_HWM) { n = MAX_HWM; } else { // Get the next highest power of 2 n--; for (var p = 1; p < 32; p <<= 1) n |= n >> p; n++; } return n; } function howMuchToRead(n, state) { if (state.length === 0 && state.ended) return 0; if (state.objectMode) return n === 0 ? 0 : 1; if (isNaN(n) || util.isNull(n)) { // only flow one buffer at a time if (state.flowing && state.buffer.length) return state.buffer[0].length; else return state.length; } if (n <= 0) return 0; // If we're asking for more than the target buffer level, // then raise the water mark. Bump up to the next highest // power of 2, to prevent increasing it excessively in tiny // amounts. if (n > state.highWaterMark) state.highWaterMark = roundUpToNextPowerOf2(n); // don't have that much. return null, unless we've ended. if (n > state.length) { if (!state.ended) { state.needReadable = true; return 0; } else return state.length; } return n; } // you can override either this method, or the async _read(n) below. Readable.prototype.read = function(n) { debug('read', n); var state = this._readableState; var nOrig = n; if (!util.isNumber(n) || n > 0) state.emittedReadable = false; // if we're doing read(0) to trigger a readable event, but we // already have a bunch of data in the buffer, then just trigger // the 'readable' event and move on. if ( n === 0 && state.needReadable && (state.length >= state.highWaterMark || state.ended) ) { debug('read: emitReadable', state.length, state.ended); if (state.length === 0 && state.ended) endReadable(this); else emitReadable(this); return null; } n = howMuchToRead(n, state); // if we've ended, and we're now clear, then finish it up. if (n === 0 && state.ended) { if (state.length === 0) endReadable(this); return null; } // All the actual chunk generation logic needs to be // *below* the call to _read. The reason is that in certain // synthetic stream cases, such as passthrough streams, _read // may be a completely synchronous operation which may change // the state of the read buffer, providing enough data when // before there was *not* enough. // // So, the steps are: // 1. Figure out what the state of things will be after we do // a read from the buffer. // // 2. If that resulting state will trigger a _read, then call _read. // Note that this may be asynchronous, or synchronous. Yes, it is // deeply ugly to write APIs this way, but that still doesn't mean // that the Readable class should behave improperly, as streams are // designed to be sync/async agnostic. // Take note if the _read call is sync or async (ie, if the read call // has returned yet), so that we know whether or not it's safe to emit // 'readable' etc. // // 3. Actually pull the requested chunks out of the buffer and return. // if we need a readable event, then we need to do some reading. var doRead = state.needReadable; debug('need readable', doRead); // if we currently have less than the highWaterMark, then also read some if (state.length === 0 || state.length - n < state.highWaterMark) { doRead = true; debug('length less than watermark', doRead); } // however, if we've ended, then there's no point, and if we're already // reading, then it's unnecessary. if (state.ended || state.reading) { doRead = false; debug('reading or ended', doRead); } if (doRead) { debug('do read'); state.reading = true; state.sync = true; // if the length is currently zero, then we *need* a readable event. if (state.length === 0) state.needReadable = true; // call internal read method this._read(state.highWaterMark); state.sync = false; } // If _read pushed data synchronously, then `reading` will be false, // and we need to re-evaluate how much data we can return to the user. if (doRead && !state.reading) n = howMuchToRead(nOrig, state); var ret; if (n > 0) ret = fromList(n, state); else ret = null; if (util.isNull(ret)) { state.needReadable = true; n = 0; } state.length -= n; // If we have nothing in the buffer, then we want to know // as soon as we *do* get something into the buffer. if (state.length === 0 && !state.ended) state.needReadable = true; // If we tried to read() past the EOF, then emit end on the next tick. if (nOrig !== n && state.ended && state.length === 0) endReadable(this); if (!util.isNull(ret)) this.emit('data', ret); return ret; }; function chunkInvalid(state, chunk) { var er = null; if ( !util.isBuffer(chunk) && !util.isString(chunk) && !util.isNullOrUndefined(chunk) && !state.objectMode ) { er = new TypeError('Invalid non-string/buffer chunk'); } return er; } function onEofChunk(stream, state) { if (state.decoder && !state.ended) { var chunk = state.decoder.end(); if (chunk && chunk.length) { state.buffer.push(chunk); state.length += state.objectMode ? 1 : chunk.length; } } state.ended = true; // emit 'readable' now to make sure it gets picked up. emitReadable(stream); } // Don't emit readable right away in sync mode, because this can trigger // another read() call => stack overflow. This way, it might trigger // a nextTick recursion warning, but that's not so bad. function emitReadable(stream) { var state = stream._readableState; state.needReadable = false; if (!state.emittedReadable) { debug('emitReadable', state.flowing); state.emittedReadable = true; if (state.sync) process.nextTick(function() { emitReadable_(stream); }); else emitReadable_(stream); } } function emitReadable_(stream) { debug('emit readable'); stream.emit('readable'); flow(stream); } // at this point, the user has presumably seen the 'readable' event, // and called read() to consume some data. that may have triggered // in turn another _read(n) call, in which case reading = true if // it's in progress. // However, if we're not ended, or reading, and the length < hwm, // then go ahead and try to read some more preemptively. function maybeReadMore(stream, state) { if (!state.readingMore) { state.readingMore = true; process.nextTick(function() { maybeReadMore_(stream, state); }); } } function maybeReadMore_(stream, state) { var len = state.length; while ( !state.reading && !state.flowing && !state.ended && state.length < state.highWaterMark ) { debug('maybeReadMore read 0'); stream.read(0); if (len === state.length) // didn't get any data, stop spinning. break; else len = state.length; } state.readingMore = false; } // abstract method. to be overridden in specific implementation classes. // call cb(er, data) where data is <= n in length. // for virtual (non-string, non-buffer) streams, "length" is somewhat // arbitrary, and perhaps not very meaningful. Readable.prototype._read = function(n) { this.emit('error', new Error('not implemented')); }; Readable.prototype.pipe = function(dest, pipeOpts) { var src = this; var state = this._readableState; switch (state.pipesCount) { case 0: state.pipes = dest; break; case 1: state.pipes = [state.pipes, dest]; break; default: state.pipes.push(dest); break; } state.pipesCount += 1; debug('pipe count=%d opts=%j', state.pipesCount, pipeOpts); var doEnd = (!pipeOpts || pipeOpts.end !== false) && dest !== process.stdout && dest !== process.stderr; var endFn = doEnd ? onend : cleanup; if (state.endEmitted) process.nextTick(endFn); else src.once('end', endFn); dest.on('unpipe', onunpipe); function onunpipe(readable) { debug('onunpipe'); if (readable === src) { cleanup(); } } function onend() { debug('onend'); dest.end(); } // when the dest drains, it reduces the awaitDrain counter // on the source. This would be more elegant with a .once() // handler in flow(), but adding and removing repeatedly is // too slow. var ondrain = pipeOnDrain(src); dest.on('drain', ondrain); function cleanup() { debug('cleanup'); // cleanup event handlers once the pipe is broken dest.removeListener('close', onclose); dest.removeListener('finish', onfinish); dest.removeListener('drain', ondrain); dest.removeListener('error', onerror); dest.removeListener('unpipe', onunpipe); src.removeListener('end', onend); src.removeListener('end', cleanup); src.removeListener('data', ondata); // if the reader is waiting for a drain event from this // specific writer, then it would cause it to never start // flowing again. // So, if this is awaiting a drain, then we just call it now. // If we don't know, then assume that we are waiting for one. if ( state.awaitDrain && (!dest._writableState || dest._writableState.needDrain) ) ondrain(); } src.on('data', ondata); function ondata(chunk) { debug('ondata'); var ret = dest.write(chunk); if (false === ret) { debug( 'false write response, pause', src._readableState.awaitDrain ); src._readableState.awaitDrain++; src.pause(); } } // if the dest has an error, then stop piping into it. // however, don't suppress the throwing behavior for this. function onerror(er) { debug('onerror', er); unpipe(); dest.removeListener('error', onerror); if (EE.listenerCount(dest, 'error') === 0) dest.emit('error', er); } // This is a brutally ugly hack to make sure that our error handler // is attached before any userland ones. NEVER DO THIS. if (!dest._events || !dest._events.error) dest.on('error', onerror); else if (isArray(dest._events.error)) dest._events.error.unshift(onerror); else dest._events.error = [onerror, dest._events.error]; // Both close and finish should trigger unpipe, but only once. function onclose() { dest.removeListener('finish', onfinish); unpipe(); } dest.once('close', onclose); function onfinish() { debug('onfinish'); dest.removeListener('close', onclose); unpipe(); } dest.once('finish', onfinish); function unpipe() { debug('unpipe'); src.unpipe(dest); } // tell the dest that it's being piped to dest.emit('pipe', src); // start the flow if it hasn't been started already. if (!state.flowing) { debug('pipe resume'); src.resume(); } return dest; }; function pipeOnDrain(src) { return function() { var state = src._readableState; debug('pipeOnDrain', state.awaitDrain); if (state.awaitDrain) state.awaitDrain--; if (state.awaitDrain === 0 && EE.listenerCount(src, 'data')) { state.flowing = true; flow(src); } }; } Readable.prototype.unpipe = function(dest) { var state = this._readableState; // if we're not piping anywhere, then do nothing. if (state.pipesCount === 0) return this; // just one destination. most common case. if (state.pipesCount === 1) { // passed in one, but it's not the right one. if (dest && dest !== state.pipes) return this; if (!dest) dest = state.pipes; // got a match. state.pipes = null; state.pipesCount = 0; state.flowing = false; if (dest) dest.emit('unpipe', this); return this; } // slow case. multiple pipe destinations. if (!dest) { // remove all. var dests = state.pipes; var len = state.pipesCount; state.pipes = null; state.pipesCount = 0; state.flowing = false; for (var i = 0; i < len; i++) dests[i].emit('unpipe', this); return this; } // try to find the right one. var i = indexOf(state.pipes, dest); if (i === -1) return this; state.pipes.splice(i, 1); state.pipesCount -= 1; if (state.pipesCount === 1) state.pipes = state.pipes[0]; dest.emit('unpipe', this); return this; }; // set up data events if they are asked for // Ensure readable listeners eventually get something Readable.prototype.on = function(ev, fn) { var res = Stream.prototype.on.call(this, ev, fn); // If listening to data, and it has not explicitly been paused, // then call resume to start the flow of data on the next tick. if (ev === 'data' && false !== this._readableState.flowing) { this.resume(); } if (ev === 'readable' && this.readable) { var state = this._readableState; if (!state.readableListening) { state.readableListening = true; state.emittedReadable = false; state.needReadable = true; if (!state.reading) { var self = this; process.nextTick(function() { debug('readable nexttick read 0'); self.read(0); }); } else if (state.length) { emitReadable(this, state); } } } return res; }; Readable.prototype.addListener = Readable.prototype.on; // pause() and resume() are remnants of the legacy readable stream API // If the user uses them, then switch into old mode. Readable.prototype.resume = function() { var state = this._readableState; if (!state.flowing) { debug('resume'); state.flowing = true; if (!state.reading) { debug('resume read 0'); this.read(0); } resume(this, state); } return this; }; function resume(stream, state) { if (!state.resumeScheduled) { state.resumeScheduled = true; process.nextTick(function() { resume_(stream, state); }); } } function resume_(stream, state) { state.resumeScheduled = false; stream.emit('resume'); flow(stream); if (state.flowing && !state.reading) stream.read(0); } Readable.prototype.pause = function() { debug('call pause flowing=%j', this._readableState.flowing); if (false !== this._readableState.flowing) { debug('pause'); this._readableState.flowing = false; this.emit('pause'); } return this; }; function flow(stream) { var state = stream._readableState; debug('flow', state.flowing); if (state.flowing) { do { var chunk = stream.read(); } while (null !== chunk && state.flowing); } } // wrap an old-style stream as the async data source. // This is *not* part of the readable stream interface. // It is an ugly unfortunate mess of history. Readable.prototype.wrap = function(stream) { var state = this._readableState; var paused = false; var self = this; stream.on('end', function() { debug('wrapped end'); if (state.decoder && !state.ended) { var chunk = state.decoder.end(); if (chunk && chunk.length) self.push(chunk); } self.push(null); }); stream.on('data', function(chunk) { debug('wrapped data'); if (state.decoder) chunk = state.decoder.write(chunk); if (!chunk || (!state.objectMode && !chunk.length)) return; var ret = self.push(chunk); if (!ret) { paused = true; stream.pause(); } }); // proxy all the other methods. // important when wrapping filters and duplexes. for (var i in stream) { if (util.isFunction(stream[i]) && util.isUndefined(this[i])) { this[i] = (function(method) { return function() { return stream[method].apply(stream, arguments); }; })(i); } } // proxy certain important events. var events = ['error', 'close', 'destroy', 'pause', 'resume']; forEach(events, function(ev) { stream.on(ev, self.emit.bind(self, ev)); }); // when we try to consume some more bytes, simply unpause the // underlying stream. self._read = function(n) { debug('wrapped _read', n); if (paused) { paused = false; stream.resume(); } }; return self; }; // exposed for testing purposes only. Readable._fromList = fromList; // Pluck off n bytes from an array of buffers. // Length is the combined lengths of all the buffers in the list. function fromList(n, state) { var list = state.buffer; var length = state.length; var stringMode = !!state.decoder; var objectMode = !!state.objectMode; var ret; // nothing in the list, definitely empty. if (list.length === 0) return null; if (length === 0) ret = null; else if (objectMode) ret = list.shift(); else if (!n || n >= length) { // read it all, truncate the array. if (stringMode) ret = list.join(''); else ret = Buffer.concat(list, length); list.length = 0; } else { // read just some of it. if (n < list[0].length) { // just take a part of the first list item. // slice is the same for buffers and strings. var buf = list[0]; ret = buf.slice(0, n); list[0] = buf.slice(n); } else if (n === list[0].length) { // first list is a perfect match ret = list.shift(); } else { // complex case. // we have enough to cover it, but it spans past the first buffer. if (stringMode) ret = ''; else ret = new Buffer(n); var c = 0; for (var i = 0, l = list.length; i < l && c < n; i++) { var buf = list[0]; var cpy = Math.min(n - c, buf.length); if (stringMode) ret += buf.slice(0, cpy); else buf.copy(ret, c, 0, cpy); if (cpy < buf.length) list[0] = buf.slice(cpy); else list.shift(); c += cpy; } } } return ret; } function endReadable(stream) { var state = stream._readableState; // If we get here before consuming all the bytes, then that is a // bug in node. Should never happen. if (state.length > 0) throw new Error('endReadable called on non-empty stream'); if (!state.endEmitted) { state.ended = true; process.nextTick(function() { // Check that we didn't get one last unshift. if (!state.endEmitted && state.length === 0) { state.endEmitted = true; stream.readable = false; stream.emit('end'); } }); } } function forEach(xs, f) { for (var i = 0, l = xs.length; i < l; i++) { f(xs[i], i); } } function indexOf(xs, x) { for (var i = 0, l = xs.length; i < l; i++) { if (xs[i] === x) return i; } return -1; } }.call(this, require('_process'))); }, { './_stream_duplex': 53, _process: 51, buffer: 43, 'core-util-is': 58, events: 47, inherits: 48, isarray: 49, stream: 63, 'string_decoder/': 64, util: 42 } ], 56: [ function(require, module, exports) { // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. // a transform stream is a readable/writable stream where you do // something with the data. Sometimes it's called a "filter", // but that's not a great name for it, since that implies a thing where // some bits pass through, and others are simply ignored. (That would // be a valid example of a transform, of course.) // // While the output is causally related to the input, it's not a // necessarily symmetric or synchronous transformation. For example, // a zlib stream might take multiple plain-text writes(), and then // emit a single compressed chunk some time in the future. // // Here's how this works: // // The Transform stream has all the aspects of the readable and writable // stream classes. When you write(chunk), that calls _write(chunk,cb) // internally, and returns false if there's a lot of pending writes // buffered up. When you call read(), that calls _read(n) until // there's enough pending readable data buffered up. // // In a transform stream, the written data is placed in a buffer. When // _read(n) is called, it transforms the queued up data, calling the // buffered _write cb's as it consumes chunks. If consuming a single // written chunk would result in multiple output chunks, then the first // outputted bit calls the readcb, and subsequent chunks just go into // the read buffer, and will cause it to emit 'readable' if necessary. // // This way, back-pressure is actually determined by the reading side, // since _read has to be called to start processing a new chunk. However, // a pathological inflate type of transform can cause excessive buffering // here. For example, imagine a stream where every byte of input is // interpreted as an integer from 0-255, and then results in that many // bytes of output. Writing the 4 bytes {ff,ff,ff,ff} would result in // 1kb of data being output. In this case, you could write a very small // amount of input, and end up with a very large amount of output. In // such a pathological inflating mechanism, there'd be no way to tell // the system to stop doing the transform. A single 4MB write could // cause the system to run out of memory. // // However, even in such a pathological case, only a single written chunk // would be consumed, and then the rest would wait (un-transformed) until // the results of the previous transformed chunk were consumed. module.exports = Transform; var Duplex = require('./_stream_duplex'); /**/ var util = require('core-util-is'); util.inherits = require('inherits'); /**/ util.inherits(Transform, Duplex); function TransformState(options, stream) { this.afterTransform = function(er, data) { return afterTransform(stream, er, data); }; this.needTransform = false; this.transforming = false; this.writecb = null; this.writechunk = null; } function afterTransform(stream, er, data) { var ts = stream._transformState; ts.transforming = false; var cb = ts.writecb; if (!cb) return stream.emit( 'error', new Error('no writecb in Transform class') ); ts.writechunk = null; ts.writecb = null; if (!util.isNullOrUndefined(data)) stream.push(data); if (cb) cb(er); var rs = stream._readableState; rs.reading = false; if (rs.needReadable || rs.length < rs.highWaterMark) { stream._read(rs.highWaterMark); } } function Transform(options) { if (!(this instanceof Transform)) return new Transform(options); Duplex.call(this, options); this._transformState = new TransformState(options, this); // when the writable side finishes, then flush out anything remaining. var stream = this; // start out asking for a readable event once data is transformed. this._readableState.needReadable = true; // we have implemented the _read method, and done the other things // that Readable wants before the first _read call, so unset the // sync guard flag. this._readableState.sync = false; this.once('prefinish', function() { if (util.isFunction(this._flush)) this._flush(function(er) { done(stream, er); }); else done(stream); }); } Transform.prototype.push = function(chunk, encoding) { this._transformState.needTransform = false; return Duplex.prototype.push.call(this, chunk, encoding); }; // This is the part where you do stuff! // override this function in implementation classes. // 'chunk' is an input chunk. // // Call `push(newChunk)` to pass along transformed output // to the readable side. You may call 'push' zero or more times. // // Call `cb(err)` when you are done with this chunk. If you pass // an error, then that'll put the hurt on the whole operation. If you // never call cb(), then you'll never get another chunk. Transform.prototype._transform = function(chunk, encoding, cb) { throw new Error('not implemented'); }; Transform.prototype._write = function(chunk, encoding, cb) { var ts = this._transformState; ts.writecb = cb; ts.writechunk = chunk; ts.writeencoding = encoding; if (!ts.transforming) { var rs = this._readableState; if ( ts.needTransform || rs.needReadable || rs.length < rs.highWaterMark ) this._read(rs.highWaterMark); } }; // Doesn't matter what the args are here. // _transform does all the work. // That we got here means that the readable side wants more data. Transform.prototype._read = function(n) { var ts = this._transformState; if (!util.isNull(ts.writechunk) && ts.writecb && !ts.transforming) { ts.transforming = true; this._transform(ts.writechunk, ts.writeencoding, ts.afterTransform); } else { // mark that we need a transform, so that any data that comes in // will get processed, now that we've asked for it. ts.needTransform = true; } }; function done(stream, er) { if (er) return stream.emit('error', er); // if there's nothing in the write buffer, then that means // that nothing more will ever be provided var ws = stream._writableState; var ts = stream._transformState; if (ws.length) throw new Error('calling transform done when ws.length != 0'); if (ts.transforming) throw new Error('calling transform done when still transforming'); return stream.push(null); } }, { './_stream_duplex': 53, 'core-util-is': 58, inherits: 48 } ], 57: [ function(require, module, exports) { (function(process) { // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. // A bit simpler than readable streams. // Implement an async ._write(chunk, cb), and it'll handle all // the drain event emission and buffering. module.exports = Writable; /**/ var Buffer = require('buffer').Buffer; /**/ Writable.WritableState = WritableState; /**/ var util = require('core-util-is'); util.inherits = require('inherits'); /**/ var Stream = require('stream'); util.inherits(Writable, Stream); function WriteReq(chunk, encoding, cb) { this.chunk = chunk; this.encoding = encoding; this.callback = cb; } function WritableState(options, stream) { var Duplex = require('./_stream_duplex'); options = options || {}; // the point at which write() starts returning false // Note: 0 is a valid value, means that we always return false if // the entire buffer is not flushed immediately on write() var hwm = options.highWaterMark; var defaultHwm = options.objectMode ? 16 : 16 * 1024; this.highWaterMark = hwm || hwm === 0 ? hwm : defaultHwm; // object stream flag to indicate whether or not this stream // contains buffers or objects. this.objectMode = !!options.objectMode; if (stream instanceof Duplex) this.objectMode = this.objectMode || !!options.writableObjectMode; // cast to ints. this.highWaterMark = ~~this.highWaterMark; this.needDrain = false; // at the start of calling end() this.ending = false; // when end() has been called, and returned this.ended = false; // when 'finish' is emitted this.finished = false; // should we decode strings into buffers before passing to _write? // this is here so that some node-core streams can optimize string // handling at a lower level. var noDecode = options.decodeStrings === false; this.decodeStrings = !noDecode; // Crypto is kind of old and crusty. Historically, its default string // encoding is 'binary' so we have to make this configurable. // Everything else in the universe uses 'utf8', though. this.defaultEncoding = options.defaultEncoding || 'utf8'; // not an actual buffer we keep track of, but a measurement // of how much we're waiting to get pushed to some underlying // socket or file. this.length = 0; // a flag to see when we're in the middle of a write. this.writing = false; // when true all writes will be buffered until .uncork() call this.corked = 0; // a flag to be able to tell if the onwrite cb is called immediately, // or on a later tick. We set this to true at first, because any // actions that shouldn't happen until "later" should generally also // not happen before the first write call. this.sync = true; // a flag to know if we're processing previously buffered items, which // may call the _write() callback in the same tick, so that we don't // end up in an overlapped onwrite situation. this.bufferProcessing = false; // the callback that's passed to _write(chunk,cb) this.onwrite = function(er) { onwrite(stream, er); }; // the callback that the user supplies to write(chunk,encoding,cb) this.writecb = null; // the amount that is being written when _write is called. this.writelen = 0; this.buffer = []; // number of pending user-supplied write callbacks // this must be 0 before 'finish' can be emitted this.pendingcb = 0; // emit prefinish if the only thing we're waiting for is _write cbs // This is relevant for synchronous Transform streams this.prefinished = false; // True if the error was already emitted and should not be thrown again this.errorEmitted = false; } function Writable(options) { var Duplex = require('./_stream_duplex'); // Writable ctor is applied to Duplexes, though they're not // instanceof Writable, they're instanceof Readable. if (!(this instanceof Writable) && !(this instanceof Duplex)) return new Writable(options); this._writableState = new WritableState(options, this); // legacy. this.writable = true; Stream.call(this); } // Otherwise people can pipe Writable streams, which is just wrong. Writable.prototype.pipe = function() { this.emit('error', new Error('Cannot pipe. Not readable.')); }; function writeAfterEnd(stream, state, cb) { var er = new Error('write after end'); // TODO: defer error events consistently everywhere, not just the cb stream.emit('error', er); process.nextTick(function() { cb(er); }); } // If we get something that is not a buffer, string, null, or undefined, // and we're not in objectMode, then that's an error. // Otherwise stream chunks are all considered to be of length=1, and the // watermarks determine how many objects to keep in the buffer, rather than // how many bytes or characters. function validChunk(stream, state, chunk, cb) { var valid = true; if ( !util.isBuffer(chunk) && !util.isString(chunk) && !util.isNullOrUndefined(chunk) && !state.objectMode ) { var er = new TypeError('Invalid non-string/buffer chunk'); stream.emit('error', er); process.nextTick(function() { cb(er); }); valid = false; } return valid; } Writable.prototype.write = function(chunk, encoding, cb) { var state = this._writableState; var ret = false; if (util.isFunction(encoding)) { cb = encoding; encoding = null; } if (util.isBuffer(chunk)) encoding = 'buffer'; else if (!encoding) encoding = state.defaultEncoding; if (!util.isFunction(cb)) cb = function() {}; if (state.ended) writeAfterEnd(this, state, cb); else if (validChunk(this, state, chunk, cb)) { state.pendingcb++; ret = writeOrBuffer(this, state, chunk, encoding, cb); } return ret; }; Writable.prototype.cork = function() { var state = this._writableState; state.corked++; }; Writable.prototype.uncork = function() { var state = this._writableState; if (state.corked) { state.corked--; if ( !state.writing && !state.corked && !state.finished && !state.bufferProcessing && state.buffer.length ) clearBuffer(this, state); } }; function decodeChunk(state, chunk, encoding) { if ( !state.objectMode && state.decodeStrings !== false && util.isString(chunk) ) { chunk = new Buffer(chunk, encoding); } return chunk; } // if we're already writing something, then just put this // in the queue, and wait our turn. Otherwise, call _write // If we return false, then we need a drain event, so set that flag. function writeOrBuffer(stream, state, chunk, encoding, cb) { chunk = decodeChunk(state, chunk, encoding); if (util.isBuffer(chunk)) encoding = 'buffer'; var len = state.objectMode ? 1 : chunk.length; state.length += len; var ret = state.length < state.highWaterMark; // we must ensure that previous needDrain will not be reset to false. if (!ret) state.needDrain = true; if (state.writing || state.corked) state.buffer.push(new WriteReq(chunk, encoding, cb)); else doWrite(stream, state, false, len, chunk, encoding, cb); return ret; } function doWrite(stream, state, writev, len, chunk, encoding, cb) { state.writelen = len; state.writecb = cb; state.writing = true; state.sync = true; if (writev) stream._writev(chunk, state.onwrite); else stream._write(chunk, encoding, state.onwrite); state.sync = false; } function onwriteError(stream, state, sync, er, cb) { if (sync) process.nextTick(function() { state.pendingcb--; cb(er); }); else { state.pendingcb--; cb(er); } stream._writableState.errorEmitted = true; stream.emit('error', er); } function onwriteStateUpdate(state) { state.writing = false; state.writecb = null; state.length -= state.writelen; state.writelen = 0; } function onwrite(stream, er) { var state = stream._writableState; var sync = state.sync; var cb = state.writecb; onwriteStateUpdate(state); if (er) onwriteError(stream, state, sync, er, cb); else { // Check if we're actually ready to finish, but don't emit yet var finished = needFinish(stream, state); if ( !finished && !state.corked && !state.bufferProcessing && state.buffer.length ) { clearBuffer(stream, state); } if (sync) { process.nextTick(function() { afterWrite(stream, state, finished, cb); }); } else { afterWrite(stream, state, finished, cb); } } } function afterWrite(stream, state, finished, cb) { if (!finished) onwriteDrain(stream, state); state.pendingcb--; cb(); finishMaybe(stream, state); } // Must force callback to be called on nextTick, so that we don't // emit 'drain' before the write() consumer gets the 'false' return // value, and has a chance to attach a 'drain' listener. function onwriteDrain(stream, state) { if (state.length === 0 && state.needDrain) { state.needDrain = false; stream.emit('drain'); } } // if there's something in the buffer waiting, then process it function clearBuffer(stream, state) { state.bufferProcessing = true; if (stream._writev && state.buffer.length > 1) { // Fast case, write everything using _writev() var cbs = []; for (var c = 0; c < state.buffer.length; c++) cbs.push(state.buffer[c].callback); // count the one we are adding, as well. // TODO(isaacs) clean this up state.pendingcb++; doWrite( stream, state, true, state.length, state.buffer, '', function(err) { for (var i = 0; i < cbs.length; i++) { state.pendingcb--; cbs[i](err); } } ); // Clear buffer state.buffer = []; } else { // Slow case, write chunks one-by-one for (var c = 0; c < state.buffer.length; c++) { var entry = state.buffer[c]; var chunk = entry.chunk; var encoding = entry.encoding; var cb = entry.callback; var len = state.objectMode ? 1 : chunk.length; doWrite(stream, state, false, len, chunk, encoding, cb); // if we didn't call the onwrite immediately, then // it means that we need to wait until it does. // also, that means that the chunk and cb are currently // being processed, so move the buffer counter past them. if (state.writing) { c++; break; } } if (c < state.buffer.length) state.buffer = state.buffer.slice(c); else state.buffer.length = 0; } state.bufferProcessing = false; } Writable.prototype._write = function(chunk, encoding, cb) { cb(new Error('not implemented')); }; Writable.prototype._writev = null; Writable.prototype.end = function(chunk, encoding, cb) { var state = this._writableState; if (util.isFunction(chunk)) { cb = chunk; chunk = null; encoding = null; } else if (util.isFunction(encoding)) { cb = encoding; encoding = null; } if (!util.isNullOrUndefined(chunk)) this.write(chunk, encoding); // .end() fully uncorks if (state.corked) { state.corked = 1; this.uncork(); } // ignore unnecessary end() calls. if (!state.ending && !state.finished) endWritable(this, state, cb); }; function needFinish(stream, state) { return ( state.ending && state.length === 0 && !state.finished && !state.writing ); } function prefinish(stream, state) { if (!state.prefinished) { state.prefinished = true; stream.emit('prefinish'); } } function finishMaybe(stream, state) { var need = needFinish(stream, state); if (need) { if (state.pendingcb === 0) { prefinish(stream, state); state.finished = true; stream.emit('finish'); } else prefinish(stream, state); } return need; } function endWritable(stream, state, cb) { state.ending = true; finishMaybe(stream, state); if (cb) { if (state.finished) process.nextTick(cb); else stream.once('finish', cb); } state.ended = true; } }.call(this, require('_process'))); }, { './_stream_duplex': 53, _process: 51, buffer: 43, 'core-util-is': 58, inherits: 48, stream: 63 } ], 58: [ function(require, module, exports) { (function(Buffer) { // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. // NOTE: These type checking functions intentionally don't use `instanceof` // because it is fragile and can be easily faked with `Object.create()`. function isArray(ar) { return Array.isArray(ar); } exports.isArray = isArray; function isBoolean(arg) { return typeof arg === 'boolean'; } exports.isBoolean = isBoolean; function isNull(arg) { return arg === null; } exports.isNull = isNull; function isNullOrUndefined(arg) { return arg == null; } exports.isNullOrUndefined = isNullOrUndefined; function isNumber(arg) { return typeof arg === 'number'; } exports.isNumber = isNumber; function isString(arg) { return typeof arg === 'string'; } exports.isString = isString; function isSymbol(arg) { return typeof arg === 'symbol'; } exports.isSymbol = isSymbol; function isUndefined(arg) { return arg === void 0; } exports.isUndefined = isUndefined; function isRegExp(re) { return isObject(re) && objectToString(re) === '[object RegExp]'; } exports.isRegExp = isRegExp; function isObject(arg) { return typeof arg === 'object' && arg !== null; } exports.isObject = isObject; function isDate(d) { return isObject(d) && objectToString(d) === '[object Date]'; } exports.isDate = isDate; function isError(e) { return ( isObject(e) && (objectToString(e) === '[object Error]' || e instanceof Error) ); } exports.isError = isError; function isFunction(arg) { return typeof arg === 'function'; } exports.isFunction = isFunction; function isPrimitive(arg) { return ( arg === null || typeof arg === 'boolean' || typeof arg === 'number' || typeof arg === 'string' || typeof arg === 'symbol' || // ES6 symbol typeof arg === 'undefined' ); } exports.isPrimitive = isPrimitive; function isBuffer(arg) { return Buffer.isBuffer(arg); } exports.isBuffer = isBuffer; function objectToString(o) { return Object.prototype.toString.call(o); } }.call(this, require('buffer').Buffer)); }, { buffer: 43 } ], 59: [ function(require, module, exports) { module.exports = require('./lib/_stream_passthrough.js'); }, { './lib/_stream_passthrough.js': 54 } ], 60: [ function(require, module, exports) { exports = module.exports = require('./lib/_stream_readable.js'); exports.Stream = require('stream'); exports.Readable = exports; exports.Writable = require('./lib/_stream_writable.js'); exports.Duplex = require('./lib/_stream_duplex.js'); exports.Transform = require('./lib/_stream_transform.js'); exports.PassThrough = require('./lib/_stream_passthrough.js'); }, { './lib/_stream_duplex.js': 53, './lib/_stream_passthrough.js': 54, './lib/_stream_readable.js': 55, './lib/_stream_transform.js': 56, './lib/_stream_writable.js': 57, stream: 63 } ], 61: [ function(require, module, exports) { module.exports = require('./lib/_stream_transform.js'); }, { './lib/_stream_transform.js': 56 } ], 62: [ function(require, module, exports) { module.exports = require('./lib/_stream_writable.js'); }, { './lib/_stream_writable.js': 57 } ], 63: [ function(require, module, exports) { // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. module.exports = Stream; var EE = require('events').EventEmitter; var inherits = require('inherits'); inherits(Stream, EE); Stream.Readable = require('readable-stream/readable.js'); Stream.Writable = require('readable-stream/writable.js'); Stream.Duplex = require('readable-stream/duplex.js'); Stream.Transform = require('readable-stream/transform.js'); Stream.PassThrough = require('readable-stream/passthrough.js'); // Backwards-compat with node 0.4.x Stream.Stream = Stream; // old-style streams. Note that the pipe method (the only relevant // part of this class) is overridden in the Readable class. function Stream() { EE.call(this); } Stream.prototype.pipe = function(dest, options) { var source = this; function ondata(chunk) { if (dest.writable) { if (false === dest.write(chunk) && source.pause) { source.pause(); } } } source.on('data', ondata); function ondrain() { if (source.readable && source.resume) { source.resume(); } } dest.on('drain', ondrain); // If the 'end' option is not supplied, dest.end() will be called when // source gets the 'end' or 'close' events. Only dest.end() once. if (!dest._isStdio && (!options || options.end !== false)) { source.on('end', onend); source.on('close', onclose); } var didOnEnd = false; function onend() { if (didOnEnd) return; didOnEnd = true; dest.end(); } function onclose() { if (didOnEnd) return; didOnEnd = true; if (typeof dest.destroy === 'function') dest.destroy(); } // don't leave dangling pipes when there are errors. function onerror(er) { cleanup(); if (EE.listenerCount(this, 'error') === 0) { throw er; // Unhandled stream error in pipe. } } source.on('error', onerror); dest.on('error', onerror); // remove all the event listeners that were added. function cleanup() { source.removeListener('data', ondata); dest.removeListener('drain', ondrain); source.removeListener('end', onend); source.removeListener('close', onclose); source.removeListener('error', onerror); dest.removeListener('error', onerror); source.removeListener('end', cleanup); source.removeListener('close', cleanup); dest.removeListener('close', cleanup); } source.on('end', cleanup); source.on('close', cleanup); dest.on('close', cleanup); dest.emit('pipe', source); // Allow for unix-like usage: A.pipe(B).pipe(C) return dest; }; }, { events: 47, inherits: 48, 'readable-stream/duplex.js': 52, 'readable-stream/passthrough.js': 59, 'readable-stream/readable.js': 60, 'readable-stream/transform.js': 61, 'readable-stream/writable.js': 62 } ], 64: [ function(require, module, exports) { // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. var Buffer = require('buffer').Buffer; var isBufferEncoding = Buffer.isEncoding || function(encoding) { switch (encoding && encoding.toLowerCase()) { case 'hex': case 'utf8': case 'utf-8': case 'ascii': case 'binary': case 'base64': case 'ucs2': case 'ucs-2': case 'utf16le': case 'utf-16le': case 'raw': return true; default: return false; } }; function assertEncoding(encoding) { if (encoding && !isBufferEncoding(encoding)) { throw new Error('Unknown encoding: ' + encoding); } } // StringDecoder provides an interface for efficiently splitting a series of // buffers into a series of JS strings without breaking apart multi-byte // characters. CESU-8 is handled as part of the UTF-8 encoding. // // @TODO Handling all encodings inside a single object makes it very difficult // to reason about this code, so it should be split up in the future. // @TODO There should be a utf8-strict encoding that rejects invalid UTF-8 code // points as used by CESU-8. var StringDecoder = (exports.StringDecoder = function(encoding) { this.encoding = (encoding || 'utf8') .toLowerCase() .replace(/[-_]/, ''); assertEncoding(encoding); switch (this.encoding) { case 'utf8': // CESU-8 represents each of Surrogate Pair by 3-bytes this.surrogateSize = 3; break; case 'ucs2': case 'utf16le': // UTF-16 represents each of Surrogate Pair by 2-bytes this.surrogateSize = 2; this.detectIncompleteChar = utf16DetectIncompleteChar; break; case 'base64': // Base-64 stores 3 bytes in 4 chars, and pads the remainder. this.surrogateSize = 3; this.detectIncompleteChar = base64DetectIncompleteChar; break; default: this.write = passThroughWrite; return; } // Enough space to store all bytes of a single character. UTF-8 needs 4 // bytes, but CESU-8 may require up to 6 (3 bytes per surrogate). this.charBuffer = new Buffer(6); // Number of bytes received for the current incomplete multi-byte character. this.charReceived = 0; // Number of bytes expected for the current incomplete multi-byte character. this.charLength = 0; }); // write decodes the given buffer and returns it as JS string that is // guaranteed to not contain any partial multi-byte characters. Any partial // character found at the end of the buffer is buffered up, and will be // returned when calling write again with the remaining bytes. // // Note: Converting a Buffer containing an orphan surrogate to a String // currently works, but converting a String to a Buffer (via `new Buffer`, or // Buffer#write) will replace incomplete surrogates with the unicode // replacement character. See https://codereview.chromium.org/121173009/ . StringDecoder.prototype.write = function(buffer) { var charStr = ''; // if our last write ended with an incomplete multibyte character while (this.charLength) { // determine how many remaining bytes this buffer has to offer for this char var available = buffer.length >= this.charLength - this.charReceived ? this.charLength - this.charReceived : buffer.length; // add the new bytes to the char buffer buffer.copy(this.charBuffer, this.charReceived, 0, available); this.charReceived += available; if (this.charReceived < this.charLength) { // still not enough chars in this buffer? wait for more ... return ''; } // remove bytes belonging to the current character from the buffer buffer = buffer.slice(available, buffer.length); // get the character that was split charStr = this.charBuffer .slice(0, this.charLength) .toString(this.encoding); // CESU-8: lead surrogate (D800-DBFF) is also the incomplete character var charCode = charStr.charCodeAt(charStr.length - 1); if (charCode >= 0xd800 && charCode <= 0xdbff) { this.charLength += this.surrogateSize; charStr = ''; continue; } this.charReceived = this.charLength = 0; // if there are no more bytes in this buffer, just emit our char if (buffer.length === 0) { return charStr; } break; } // determine and set charLength / charReceived this.detectIncompleteChar(buffer); var end = buffer.length; if (this.charLength) { // buffer the incomplete character bytes we got buffer.copy( this.charBuffer, 0, buffer.length - this.charReceived, end ); end -= this.charReceived; } charStr += buffer.toString(this.encoding, 0, end); var end = charStr.length - 1; var charCode = charStr.charCodeAt(end); // CESU-8: lead surrogate (D800-DBFF) is also the incomplete character if (charCode >= 0xd800 && charCode <= 0xdbff) { var size = this.surrogateSize; this.charLength += size; this.charReceived += size; this.charBuffer.copy(this.charBuffer, size, 0, size); buffer.copy(this.charBuffer, 0, 0, size); return charStr.substring(0, end); } // or just emit the charStr return charStr; }; // detectIncompleteChar determines if there is an incomplete UTF-8 character at // the end of the given buffer. If so, it sets this.charLength to the byte // length that character, and sets this.charReceived to the number of bytes // that are available for this character. StringDecoder.prototype.detectIncompleteChar = function(buffer) { // determine how many bytes we have to check at the end of this buffer var i = buffer.length >= 3 ? 3 : buffer.length; // Figure out if one of the last i bytes of our buffer announces an // incomplete char. for (; i > 0; i--) { var c = buffer[buffer.length - i]; // See http://en.wikipedia.org/wiki/UTF-8#Description // 110XXXXX if (i == 1 && c >> 5 == 0x06) { this.charLength = 2; break; } // 1110XXXX if (i <= 2 && c >> 4 == 0x0e) { this.charLength = 3; break; } // 11110XXX if (i <= 3 && c >> 3 == 0x1e) { this.charLength = 4; break; } } this.charReceived = i; }; StringDecoder.prototype.end = function(buffer) { var res = ''; if (buffer && buffer.length) res = this.write(buffer); if (this.charReceived) { var cr = this.charReceived; var buf = this.charBuffer; var enc = this.encoding; res += buf.slice(0, cr).toString(enc); } return res; }; function passThroughWrite(buffer) { return buffer.toString(this.encoding); } function utf16DetectIncompleteChar(buffer) { this.charReceived = buffer.length % 2; this.charLength = this.charReceived ? 2 : 0; } function base64DetectIncompleteChar(buffer) { this.charReceived = buffer.length % 3; this.charLength = this.charReceived ? 3 : 0; } }, { buffer: 43 } ], 65: [ function(require, module, exports) { module.exports = function isBuffer(arg) { return ( arg && typeof arg === 'object' && typeof arg.copy === 'function' && typeof arg.fill === 'function' && typeof arg.readUInt8 === 'function' ); }; }, {} ], 66: [ function(require, module, exports) { (function(process, global) { // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to permit // persons to whom the Software is furnished to do so, subject to the // following conditions: // // The above copyright notice and this permission notice shall be included // in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE // USE OR OTHER DEALINGS IN THE SOFTWARE. var formatRegExp = /%[sdj%]/g; exports.format = function(f) { if (!isString(f)) { var objects = []; for (var i = 0; i < arguments.length; i++) { objects.push(inspect(arguments[i])); } return objects.join(' '); } var i = 1; var args = arguments; var len = args.length; var str = String(f).replace(formatRegExp, function(x) { if (x === '%%') return '%'; if (i >= len) return x; switch (x) { case '%s': return String(args[i++]); case '%d': return Number(args[i++]); case '%j': try { return JSON.stringify(args[i++]); } catch (_) { return '[Circular]'; } default: return x; } }); for (var x = args[i]; i < len; x = args[++i]) { if (isNull(x) || !isObject(x)) { str += ' ' + x; } else { str += ' ' + inspect(x); } } return str; }; // Mark that a method should not be used. // Returns a modified function which warns once by default. // If --no-deprecation is set, then it is a no-op. exports.deprecate = function(fn, msg) { // Allow for deprecating things in the process of starting up. if (isUndefined(global.process)) { return function() { return exports.deprecate(fn, msg).apply(this, arguments); }; } if (process.noDeprecation === true) { return fn; } var warned = false; function deprecated() { if (!warned) { if (process.throwDeprecation) { throw new Error(msg); } else if (process.traceDeprecation) { console.trace(msg); } else { console.error(msg); } warned = true; } return fn.apply(this, arguments); } return deprecated; }; var debugs = {}; var debugEnviron; exports.debuglog = function(set) { if (isUndefined(debugEnviron)) debugEnviron = process.env.NODE_DEBUG || ''; set = set.toUpperCase(); if (!debugs[set]) { if (new RegExp('\\b' + set + '\\b', 'i').test(debugEnviron)) { var pid = process.pid; debugs[set] = function() { var msg = exports.format.apply(exports, arguments); console.error('%s %d: %s', set, pid, msg); }; } else { debugs[set] = function() {}; } } return debugs[set]; }; /** * Echos the value of a value. Trys to print the value out * in the best way possible given the different types. * * @param {Object} obj The object to print out. * @param {Object} opts Optional options object that alters the output. */ /* legacy: obj, showHidden, depth, colors*/ function inspect(obj, opts) { // default options var ctx = { seen: [], stylize: stylizeNoColor }; // legacy... if (arguments.length >= 3) ctx.depth = arguments[2]; if (arguments.length >= 4) ctx.colors = arguments[3]; if (isBoolean(opts)) { // legacy... ctx.showHidden = opts; } else if (opts) { // got an "options" object exports._extend(ctx, opts); } // set default options if (isUndefined(ctx.showHidden)) ctx.showHidden = false; if (isUndefined(ctx.depth)) ctx.depth = 2; if (isUndefined(ctx.colors)) ctx.colors = false; if (isUndefined(ctx.customInspect)) ctx.customInspect = true; if (ctx.colors) ctx.stylize = stylizeWithColor; return formatValue(ctx, obj, ctx.depth); } exports.inspect = inspect; // http://en.wikipedia.org/wiki/ANSI_escape_code#graphics inspect.colors = { bold: [1, 22], italic: [3, 23], underline: [4, 24], inverse: [7, 27], white: [37, 39], grey: [90, 39], black: [30, 39], blue: [34, 39], cyan: [36, 39], green: [32, 39], magenta: [35, 39], red: [31, 39], yellow: [33, 39] }; // Don't use 'blue' not visible on cmd.exe inspect.styles = { special: 'cyan', number: 'yellow', boolean: 'yellow', undefined: 'grey', null: 'bold', string: 'green', date: 'magenta', // "name": intentionally not styling regexp: 'red' }; function stylizeWithColor(str, styleType) { var style = inspect.styles[styleType]; if (style) { return ( '\u001b[' + inspect.colors[style][0] + 'm' + str + '\u001b[' + inspect.colors[style][1] + 'm' ); } else { return str; } } function stylizeNoColor(str, styleType) { return str; } function arrayToHash(array) { var hash = {}; array.forEach(function(val, idx) { hash[val] = true; }); return hash; } function formatValue(ctx, value, recurseTimes) { // Provide a hook for user-specified inspect functions. // Check that value is an object with an inspect function on it if ( ctx.customInspect && value && isFunction(value.inspect) && // Filter out the util module, it's inspect function is special value.inspect !== exports.inspect && // Also filter out any prototype objects using the circular check. !(value.constructor && value.constructor.prototype === value) ) { var ret = value.inspect(recurseTimes, ctx); if (!isString(ret)) { ret = formatValue(ctx, ret, recurseTimes); } return ret; } // Primitive types cannot have properties var primitive = formatPrimitive(ctx, value); if (primitive) { return primitive; } // Look up the keys of the object. var keys = Object.keys(value); var visibleKeys = arrayToHash(keys); if (ctx.showHidden) { keys = Object.getOwnPropertyNames(value); } // IE doesn't make error fields non-enumerable // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx if ( isError(value) && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0) ) { return formatError(value); } // Some type of object without properties can be shortcutted. if (keys.length === 0) { if (isFunction(value)) { var name = value.name ? ': ' + value.name : ''; return ctx.stylize('[Function' + name + ']', 'special'); } if (isRegExp(value)) { return ctx.stylize( RegExp.prototype.toString.call(value), 'regexp' ); } if (isDate(value)) { return ctx.stylize(Date.prototype.toString.call(value), 'date'); } if (isError(value)) { return formatError(value); } } var base = '', array = false, braces = ['{', '}']; // Make Array say that they are Array if (isArray(value)) { array = true; braces = ['[', ']']; } // Make functions say that they are functions if (isFunction(value)) { var n = value.name ? ': ' + value.name : ''; base = ' [Function' + n + ']'; } // Make RegExps say that they are RegExps if (isRegExp(value)) { base = ' ' + RegExp.prototype.toString.call(value); } // Make dates with properties first say the date if (isDate(value)) { base = ' ' + Date.prototype.toUTCString.call(value); } // Make error with message first say the error if (isError(value)) { base = ' ' + formatError(value); } if (keys.length === 0 && (!array || value.length == 0)) { return braces[0] + base + braces[1]; } if (recurseTimes < 0) { if (isRegExp(value)) { return ctx.stylize( RegExp.prototype.toString.call(value), 'regexp' ); } else { return ctx.stylize('[Object]', 'special'); } } ctx.seen.push(value); var output; if (array) { output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); } else { output = keys.map(function(key) { return formatProperty( ctx, value, recurseTimes, visibleKeys, key, array ); }); } ctx.seen.pop(); return reduceToSingleString(output, base, braces); } function formatPrimitive(ctx, value) { if (isUndefined(value)) return ctx.stylize('undefined', 'undefined'); if (isString(value)) { var simple = "'" + JSON.stringify(value) .replace(/^"|"$/g, '') .replace(/'/g, "\\'") .replace(/\\"/g, '"') + "'"; return ctx.stylize(simple, 'string'); } if (isNumber(value)) return ctx.stylize('' + value, 'number'); if (isBoolean(value)) return ctx.stylize('' + value, 'boolean'); // For some reason typeof null is "object", so special case here. if (isNull(value)) return ctx.stylize('null', 'null'); } function formatError(value) { return '[' + Error.prototype.toString.call(value) + ']'; } function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { var output = []; for (var i = 0, l = value.length; i < l; ++i) { if (hasOwnProperty(value, String(i))) { output.push( formatProperty( ctx, value, recurseTimes, visibleKeys, String(i), true ) ); } else { output.push(''); } } keys.forEach(function(key) { if (!key.match(/^\d+$/)) { output.push( formatProperty( ctx, value, recurseTimes, visibleKeys, key, true ) ); } }); return output; } function formatProperty( ctx, value, recurseTimes, visibleKeys, key, array ) { var name, str, desc; desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; if (desc.get) { if (desc.set) { str = ctx.stylize('[Getter/Setter]', 'special'); } else { str = ctx.stylize('[Getter]', 'special'); } } else { if (desc.set) { str = ctx.stylize('[Setter]', 'special'); } } if (!hasOwnProperty(visibleKeys, key)) { name = '[' + key + ']'; } if (!str) { if (ctx.seen.indexOf(desc.value) < 0) { if (isNull(recurseTimes)) { str = formatValue(ctx, desc.value, null); } else { str = formatValue(ctx, desc.value, recurseTimes - 1); } if (str.indexOf('\n') > -1) { if (array) { str = str .split('\n') .map(function(line) { return ' ' + line; }) .join('\n') .substr(2); } else { str = '\n' + str .split('\n') .map(function(line) { return ' ' + line; }) .join('\n'); } } } else { str = ctx.stylize('[Circular]', 'special'); } } if (isUndefined(name)) { if (array && key.match(/^\d+$/)) { return str; } name = JSON.stringify('' + key); if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { name = name.substr(1, name.length - 2); name = ctx.stylize(name, 'name'); } else { name = name .replace(/'/g, "\\'") .replace(/\\"/g, '"') .replace(/(^"|"$)/g, "'"); name = ctx.stylize(name, 'string'); } } return name + ': ' + str; } function reduceToSingleString(output, base, braces) { var numLinesEst = 0; var length = output.reduce(function(prev, cur) { numLinesEst++; if (cur.indexOf('\n') >= 0) numLinesEst++; return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; }, 0); if (length > 60) { return ( braces[0] + (base === '' ? '' : base + '\n ') + ' ' + output.join(',\n ') + ' ' + braces[1] ); } return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; } // NOTE: These type checking functions intentionally don't use `instanceof` // because it is fragile and can be easily faked with `Object.create()`. function isArray(ar) { return Array.isArray(ar); } exports.isArray = isArray; function isBoolean(arg) { return typeof arg === 'boolean'; } exports.isBoolean = isBoolean; function isNull(arg) { return arg === null; } exports.isNull = isNull; function isNullOrUndefined(arg) { return arg == null; } exports.isNullOrUndefined = isNullOrUndefined; function isNumber(arg) { return typeof arg === 'number'; } exports.isNumber = isNumber; function isString(arg) { return typeof arg === 'string'; } exports.isString = isString; function isSymbol(arg) { return typeof arg === 'symbol'; } exports.isSymbol = isSymbol; function isUndefined(arg) { return arg === void 0; } exports.isUndefined = isUndefined; function isRegExp(re) { return isObject(re) && objectToString(re) === '[object RegExp]'; } exports.isRegExp = isRegExp; function isObject(arg) { return typeof arg === 'object' && arg !== null; } exports.isObject = isObject; function isDate(d) { return isObject(d) && objectToString(d) === '[object Date]'; } exports.isDate = isDate; function isError(e) { return ( isObject(e) && (objectToString(e) === '[object Error]' || e instanceof Error) ); } exports.isError = isError; function isFunction(arg) { return typeof arg === 'function'; } exports.isFunction = isFunction; function isPrimitive(arg) { return ( arg === null || typeof arg === 'boolean' || typeof arg === 'number' || typeof arg === 'string' || typeof arg === 'symbol' || // ES6 symbol typeof arg === 'undefined' ); } exports.isPrimitive = isPrimitive; exports.isBuffer = require('./support/isBuffer'); function objectToString(o) { return Object.prototype.toString.call(o); } function pad(n) { return n < 10 ? '0' + n.toString(10) : n.toString(10); } var months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]; // 26 Feb 16:19:34 function timestamp() { var d = new Date(); var time = [ pad(d.getHours()), pad(d.getMinutes()), pad(d.getSeconds()) ].join(':'); return [d.getDate(), months[d.getMonth()], time].join(' '); } // log is just a thin wrapper to console.log that prepends a timestamp exports.log = function() { console.log( '%s - %s', timestamp(), exports.format.apply(exports, arguments) ); }; /** * Inherit the prototype methods from one constructor into another. * * The Function.prototype.inherits from lang.js rewritten as a standalone * function (not on Function.prototype). NOTE: If this file is to be loaded * during bootstrapping this function needs to be rewritten using some native * functions as prototype setup using normal JavaScript does not work as * expected during bootstrapping (see mirror.js in r114903). * * @param {function} ctor Constructor function which needs to inherit the * prototype. * @param {function} superCtor Constructor function to inherit prototype from. */ exports.inherits = require('inherits'); exports._extend = function(origin, add) { // Don't do anything if add isn't an object if (!add || !isObject(add)) return origin; var keys = Object.keys(add); var i = keys.length; while (i--) { origin[keys[i]] = add[keys[i]]; } return origin; }; function hasOwnProperty(obj, prop) { return Object.prototype.hasOwnProperty.call(obj, prop); } }.call( this, require('_process'), typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : {} )); }, { './support/isBuffer': 65, _process: 51, inherits: 48 } ], 67: [ function(require, module, exports) { /* See LICENSE file for terms of use */ /* * Text diff implementation. * * This library supports the following APIS: * JsDiff.diffChars: Character by character diff * JsDiff.diffWords: Word (as defined by \b regex) diff which ignores whitespace * JsDiff.diffLines: Line based diff * * JsDiff.diffCss: Diff targeted at CSS content * * These methods are based on the implementation proposed in * "An O(ND) Difference Algorithm and its Variations" (Myers, 1986). * http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.4.6927 */ (function(global, undefined) { var objectPrototypeToString = Object.prototype.toString; /*istanbul ignore next*/ function map(arr, mapper, that) { if (Array.prototype.map) { return Array.prototype.map.call(arr, mapper, that); } var other = new Array(arr.length); for (var i = 0, n = arr.length; i < n; i++) { other[i] = mapper.call(that, arr[i], i, arr); } return other; } function clonePath(path) { return { newPos: path.newPos, components: path.components.slice(0) }; } function removeEmpty(array) { var ret = []; for (var i = 0; i < array.length; i++) { if (array[i]) { ret.push(array[i]); } } return ret; } function escapeHTML(s) { var n = s; n = n.replace(/&/g, '&'); n = n.replace(//g, '>'); n = n.replace(/"/g, '"'); return n; } // This function handles the presence of circular references by bailing out when encountering an // object that is already on the "stack" of items being processed. function canonicalize(obj, stack, replacementStack) { stack = stack || []; replacementStack = replacementStack || []; var i; for (i = 0; i < stack.length; i += 1) { if (stack[i] === obj) { return replacementStack[i]; } } var canonicalizedObj; if ('[object Array]' === objectPrototypeToString.call(obj)) { stack.push(obj); canonicalizedObj = new Array(obj.length); replacementStack.push(canonicalizedObj); for (i = 0; i < obj.length; i += 1) { canonicalizedObj[i] = canonicalize( obj[i], stack, replacementStack ); } stack.pop(); replacementStack.pop(); } else if (typeof obj === 'object' && obj !== null) { stack.push(obj); canonicalizedObj = {}; replacementStack.push(canonicalizedObj); var sortedKeys = [], key; for (key in obj) { sortedKeys.push(key); } sortedKeys.sort(); for (i = 0; i < sortedKeys.length; i += 1) { key = sortedKeys[i]; canonicalizedObj[key] = canonicalize( obj[key], stack, replacementStack ); } stack.pop(); replacementStack.pop(); } else { canonicalizedObj = obj; } return canonicalizedObj; } function buildValues( components, newString, oldString, useLongestToken ) { var componentPos = 0, componentLen = components.length, newPos = 0, oldPos = 0; for (; componentPos < componentLen; componentPos++) { var component = components[componentPos]; if (!component.removed) { if (!component.added && useLongestToken) { var value = newString.slice(newPos, newPos + component.count); value = map(value, function(value, i) { var oldValue = oldString[oldPos + i]; return oldValue.length > value.length ? oldValue : value; }); component.value = value.join(''); } else { component.value = newString .slice(newPos, newPos + component.count) .join(''); } newPos += component.count; // Common case if (!component.added) { oldPos += component.count; } } else { component.value = oldString .slice(oldPos, oldPos + component.count) .join(''); oldPos += component.count; // Reverse add and remove so removes are output first to match common convention // The diffing algorithm is tied to add then remove output and this is the simplest // route to get the desired output with minimal overhead. if (componentPos && components[componentPos - 1].added) { var tmp = components[componentPos - 1]; components[componentPos - 1] = components[componentPos]; components[componentPos] = tmp; } } } return components; } function Diff(ignoreWhitespace) { this.ignoreWhitespace = ignoreWhitespace; } Diff.prototype = { diff: function(oldString, newString, callback) { var self = this; function done(value) { if (callback) { setTimeout(function() { callback(undefined, value); }, 0); return true; } else { return value; } } // Handle the identity case (this is due to unrolling editLength == 0 if (newString === oldString) { return done([{ value: newString }]); } if (!newString) { return done([{ value: oldString, removed: true }]); } if (!oldString) { return done([{ value: newString, added: true }]); } newString = this.tokenize(newString); oldString = this.tokenize(oldString); var newLen = newString.length, oldLen = oldString.length; var editLength = 1; var maxEditLength = newLen + oldLen; var bestPath = [{ newPos: -1, components: [] }]; // Seed editLength = 0, i.e. the content starts with the same values var oldPos = this.extractCommon( bestPath[0], newString, oldString, 0 ); if (bestPath[0].newPos + 1 >= newLen && oldPos + 1 >= oldLen) { // Identity per the equality and tokenizer return done([{ value: newString.join('') }]); } // Main worker method. checks all permutations of a given edit length for acceptance. function execEditLength() { for ( var diagonalPath = -1 * editLength; diagonalPath <= editLength; diagonalPath += 2 ) { var basePath; var addPath = bestPath[diagonalPath - 1], removePath = bestPath[diagonalPath + 1], oldPos = (removePath ? removePath.newPos : 0) - diagonalPath; if (addPath) { // No one else is going to attempt to use this value, clear it bestPath[diagonalPath - 1] = undefined; } var canAdd = addPath && addPath.newPos + 1 < newLen, canRemove = removePath && 0 <= oldPos && oldPos < oldLen; if (!canAdd && !canRemove) { // If this path is a terminal then prune bestPath[diagonalPath] = undefined; continue; } // Select the diagonal that we want to branch from. We select the prior // path whose position in the new string is the farthest from the origin // and does not pass the bounds of the diff graph if ( !canAdd || (canRemove && addPath.newPos < removePath.newPos) ) { basePath = clonePath(removePath); self.pushComponent(basePath.components, undefined, true); } else { basePath = addPath; // No need to clone, we've pulled it from the list basePath.newPos++; self.pushComponent(basePath.components, true, undefined); } oldPos = self.extractCommon( basePath, newString, oldString, diagonalPath ); // If we have hit the end of both strings, then we are done if (basePath.newPos + 1 >= newLen && oldPos + 1 >= oldLen) { return done( buildValues( basePath.components, newString, oldString, self.useLongestToken ) ); } else { // Otherwise track this path as a potential candidate and continue. bestPath[diagonalPath] = basePath; } } editLength++; } // Performs the length of edit iteration. Is a bit fugly as this has to support the // sync and async mode which is never fun. Loops over execEditLength until a value // is produced. if (callback) { (function exec() { setTimeout(function() { // This should not happen, but we want to be safe. /*istanbul ignore next */ if (editLength > maxEditLength) { return callback(); } if (!execEditLength()) { exec(); } }, 0); })(); } else { while (editLength <= maxEditLength) { var ret = execEditLength(); if (ret) { return ret; } } } }, pushComponent: function(components, added, removed) { var last = components[components.length - 1]; if (last && last.added === added && last.removed === removed) { // We need to clone here as the component clone operation is just // as shallow array clone components[components.length - 1] = { count: last.count + 1, added: added, removed: removed }; } else { components.push({ count: 1, added: added, removed: removed }); } }, extractCommon: function( basePath, newString, oldString, diagonalPath ) { var newLen = newString.length, oldLen = oldString.length, newPos = basePath.newPos, oldPos = newPos - diagonalPath, commonCount = 0; while ( newPos + 1 < newLen && oldPos + 1 < oldLen && this.equals(newString[newPos + 1], oldString[oldPos + 1]) ) { newPos++; oldPos++; commonCount++; } if (commonCount) { basePath.components.push({ count: commonCount }); } basePath.newPos = newPos; return oldPos; }, equals: function(left, right) { var reWhitespace = /\S/; return ( left === right || (this.ignoreWhitespace && !reWhitespace.test(left) && !reWhitespace.test(right)) ); }, tokenize: function(value) { return value.split(''); } }; var CharDiff = new Diff(); var WordDiff = new Diff(true); var WordWithSpaceDiff = new Diff(); WordDiff.tokenize = WordWithSpaceDiff.tokenize = function(value) { return removeEmpty(value.split(/(\s+|\b)/)); }; var CssDiff = new Diff(true); CssDiff.tokenize = function(value) { return removeEmpty(value.split(/([{}:;,]|\s+)/)); }; var LineDiff = new Diff(); var TrimmedLineDiff = new Diff(); TrimmedLineDiff.ignoreTrim = true; LineDiff.tokenize = TrimmedLineDiff.tokenize = function(value) { var retLines = [], lines = value.split(/^/m); for (var i = 0; i < lines.length; i++) { var line = lines[i], lastLine = lines[i - 1], lastLineLastChar = lastLine && lastLine[lastLine.length - 1]; // Merge lines that may contain windows new lines if (line === '\n' && lastLineLastChar === '\r') { retLines[retLines.length - 1] = retLines[retLines.length - 1].slice(0, -1) + '\r\n'; } else { if (this.ignoreTrim) { line = line.trim(); // add a newline unless this is the last line. if (i < lines.length - 1) { line += '\n'; } } retLines.push(line); } } return retLines; }; var PatchDiff = new Diff(); PatchDiff.tokenize = function(value) { var ret = [], linesAndNewlines = value.split(/(\n|\r\n)/); // Ignore the final empty token that occurs if the string ends with a new line if (!linesAndNewlines[linesAndNewlines.length - 1]) { linesAndNewlines.pop(); } // Merge the content and line separators into single tokens for (var i = 0; i < linesAndNewlines.length; i++) { var line = linesAndNewlines[i]; if (i % 2) { ret[ret.length - 1] += line; } else { ret.push(line); } } return ret; }; var SentenceDiff = new Diff(); SentenceDiff.tokenize = function(value) { return removeEmpty(value.split(/(\S.+?[.!?])(?=\s+|$)/)); }; var JsonDiff = new Diff(); // Discriminate between two lines of pretty-printed, serialized JSON where one of them has a // dangling comma and the other doesn't. Turns out including the dangling comma yields the nicest output: JsonDiff.useLongestToken = true; JsonDiff.tokenize = LineDiff.tokenize; JsonDiff.equals = function(left, right) { return LineDiff.equals( left.replace(/,([\r\n])/g, '$1'), right.replace(/,([\r\n])/g, '$1') ); }; var JsDiff = { Diff: Diff, diffChars: function(oldStr, newStr, callback) { return CharDiff.diff(oldStr, newStr, callback); }, diffWords: function(oldStr, newStr, callback) { return WordDiff.diff(oldStr, newStr, callback); }, diffWordsWithSpace: function(oldStr, newStr, callback) { return WordWithSpaceDiff.diff(oldStr, newStr, callback); }, diffLines: function(oldStr, newStr, callback) { return LineDiff.diff(oldStr, newStr, callback); }, diffTrimmedLines: function(oldStr, newStr, callback) { return TrimmedLineDiff.diff(oldStr, newStr, callback); }, diffSentences: function(oldStr, newStr, callback) { return SentenceDiff.diff(oldStr, newStr, callback); }, diffCss: function(oldStr, newStr, callback) { return CssDiff.diff(oldStr, newStr, callback); }, diffJson: function(oldObj, newObj, callback) { return JsonDiff.diff( typeof oldObj === 'string' ? oldObj : JSON.stringify(canonicalize(oldObj), undefined, ' '), typeof newObj === 'string' ? newObj : JSON.stringify(canonicalize(newObj), undefined, ' '), callback ); }, createTwoFilesPatch: function( oldFileName, newFileName, oldStr, newStr, oldHeader, newHeader ) { var ret = []; if (oldFileName == newFileName) { ret.push('Index: ' + oldFileName); } ret.push( '===================================================================' ); ret.push( '--- ' + oldFileName + (typeof oldHeader === 'undefined' ? '' : '\t' + oldHeader) ); ret.push( '+++ ' + newFileName + (typeof newHeader === 'undefined' ? '' : '\t' + newHeader) ); var diff = PatchDiff.diff(oldStr, newStr); diff.push({ value: '', lines: [] }); // Append an empty value to make cleanup easier // Formats a given set of lines for printing as context lines in a patch function contextLines(lines) { return map(lines, function(entry) { return ' ' + entry; }); } // Outputs the no newline at end of file warning if needed function eofNL(curRange, i, current) { var last = diff[diff.length - 2], isLast = i === diff.length - 2, isLastOfType = i === diff.length - 3 && current.added !== last.added; // Figure out if this is the last line for the given file and missing NL if (!/\n$/.test(current.value) && (isLast || isLastOfType)) { curRange.push('\\ No newline at end of file'); } } var oldRangeStart = 0, newRangeStart = 0, curRange = [], oldLine = 1, newLine = 1; for (var i = 0; i < diff.length; i++) { var current = diff[i], lines = current.lines || current.value.replace(/\n$/, '').split('\n'); current.lines = lines; if (current.added || current.removed) { // If we have previous context, start with that if (!oldRangeStart) { var prev = diff[i - 1]; oldRangeStart = oldLine; newRangeStart = newLine; if (prev) { curRange = contextLines(prev.lines.slice(-4)); oldRangeStart -= curRange.length; newRangeStart -= curRange.length; } } // Output our changes curRange.push.apply( curRange, map(lines, function(entry) { return (current.added ? '+' : '-') + entry; }) ); eofNL(curRange, i, current); // Track the updated file position if (current.added) { newLine += lines.length; } else { oldLine += lines.length; } } else { // Identical context lines. Track line changes if (oldRangeStart) { // Close out any changes that have been output (or join overlapping) if (lines.length <= 8 && i < diff.length - 2) { // Overlapping curRange.push.apply(curRange, contextLines(lines)); } else { // end the range and output var contextSize = Math.min(lines.length, 4); ret.push( '@@ -' + oldRangeStart + ',' + (oldLine - oldRangeStart + contextSize) + ' +' + newRangeStart + ',' + (newLine - newRangeStart + contextSize) + ' @@' ); ret.push.apply(ret, curRange); ret.push.apply( ret, contextLines(lines.slice(0, contextSize)) ); if (lines.length <= 4) { eofNL(ret, i, current); } oldRangeStart = 0; newRangeStart = 0; curRange = []; } } oldLine += lines.length; newLine += lines.length; } } return ret.join('\n') + '\n'; }, createPatch: function( fileName, oldStr, newStr, oldHeader, newHeader ) { return JsDiff.createTwoFilesPatch( fileName, fileName, oldStr, newStr, oldHeader, newHeader ); }, applyPatch: function(oldStr, uniDiff) { var diffstr = uniDiff.split('\n'), hunks = [], i = 0, remEOFNL = false, addEOFNL = false; // Skip to the first change hunk while (i < diffstr.length && !/^@@/.test(diffstr[i])) { i++; } // Parse the unified diff for (; i < diffstr.length; i++) { if (diffstr[i][0] === '@') { var chnukHeader = diffstr[i].split( /@@ -(\d+),(\d+) \+(\d+),(\d+) @@/ ); hunks.unshift({ start: chnukHeader[3], oldlength: +chnukHeader[2], removed: [], newlength: chnukHeader[4], added: [] }); } else if (diffstr[i][0] === '+') { hunks[0].added.push(diffstr[i].substr(1)); } else if (diffstr[i][0] === '-') { hunks[0].removed.push(diffstr[i].substr(1)); } else if (diffstr[i][0] === ' ') { hunks[0].added.push(diffstr[i].substr(1)); hunks[0].removed.push(diffstr[i].substr(1)); } else if (diffstr[i][0] === '\\') { if (diffstr[i - 1][0] === '+') { remEOFNL = true; } else if (diffstr[i - 1][0] === '-') { addEOFNL = true; } } } // Apply the diff to the input var lines = oldStr.split('\n'); for (i = hunks.length - 1; i >= 0; i--) { var hunk = hunks[i]; // Sanity check the input string. Bail if we don't match. for (var j = 0; j < hunk.oldlength; j++) { if (lines[hunk.start - 1 + j] !== hunk.removed[j]) { return false; } } Array.prototype.splice.apply( lines, [hunk.start - 1, hunk.oldlength].concat(hunk.added) ); } // Handle EOFNL insertion/removal if (remEOFNL) { while (!lines[lines.length - 1]) { lines.pop(); } } else if (addEOFNL) { lines.push(''); } return lines.join('\n'); }, convertChangesToXML: function(changes) { var ret = []; for (var i = 0; i < changes.length; i++) { var change = changes[i]; if (change.added) { ret.push(''); } else if (change.removed) { ret.push(''); } ret.push(escapeHTML(change.value)); if (change.added) { ret.push(''); } else if (change.removed) { ret.push(''); } } return ret.join(''); }, // See: http://code.google.com/p/google-diff-match-patch/wiki/API convertChangesToDMP: function(changes) { var ret = [], change, operation; for (var i = 0; i < changes.length; i++) { change = changes[i]; if (change.added) { operation = 1; } else if (change.removed) { operation = -1; } else { operation = 0; } ret.push([operation, change.value]); } return ret; }, canonicalize: canonicalize }; /*istanbul ignore next */ /*global module */ if (typeof module !== 'undefined' && module.exports) { module.exports = JsDiff; } else if (typeof define === 'function' && define.amd) { /*global define */ define([], function() { return JsDiff; }); } else if (typeof global.JsDiff === 'undefined') { global.JsDiff = JsDiff; } })(this); }, {} ], 68: [ function(require, module, exports) { 'use strict'; var matchOperatorsRe = /[|\\{}()[\]^$+*?.]/g; module.exports = function(str) { if (typeof str !== 'string') { throw new TypeError('Expected a string'); } return str.replace(matchOperatorsRe, '\\$&'); }; }, {} ], 69: [ function(require, module, exports) { (function(process) { // Growl - Copyright TJ Holowaychuk (MIT Licensed) /** * Module dependencies. */ var exec = require('child_process').exec, fs = require('fs'), path = require('path'), exists = fs.existsSync || path.existsSync, os = require('os'), quote = JSON.stringify, cmd; function which(name) { var paths = process.env.PATH.split(':'); var loc; for (var i = 0, len = paths.length; i < len; ++i) { loc = path.join(paths[i], name); if (exists(loc)) return loc; } } switch (os.type()) { case 'Darwin': if (which('terminal-notifier')) { cmd = { type: 'Darwin-NotificationCenter', pkg: 'terminal-notifier', msg: '-message', title: '-title', subtitle: '-subtitle', priority: { cmd: '-execute', range: [] } }; } else { cmd = { type: 'Darwin-Growl', pkg: 'growlnotify', msg: '-m', sticky: '--sticky', priority: { cmd: '--priority', range: [ -2, -1, 0, 1, 2, 'Very Low', 'Moderate', 'Normal', 'High', 'Emergency' ] } }; } break; case 'Linux': cmd = { type: 'Linux', pkg: 'notify-send', msg: '', sticky: '-t 0', icon: '-i', priority: { cmd: '-u', range: ['low', 'normal', 'critical'] } }; break; case 'Windows_NT': cmd = { type: 'Windows', pkg: 'growlnotify', msg: '', sticky: '/s:true', title: '/t:', icon: '/i:', priority: { cmd: '/p:', range: [-2, -1, 0, 1, 2] } }; break; } /** * Expose `growl`. */ exports = module.exports = growl; /** * Node-growl version. */ exports.version = '1.4.1'; /** * Send growl notification _msg_ with _options_. * * Options: * * - title Notification title * - sticky Make the notification stick (defaults to false) * - priority Specify an int or named key (default is 0) * - name Application name (defaults to growlnotify) * - image * - path to an icon sets --iconpath * - path to an image sets --image * - capitalized word sets --appIcon * - filename uses extname as --icon * - otherwise treated as --icon * * Examples: * * growl('New email') * growl('5 new emails', { title: 'Thunderbird' }) * growl('Email sent', function(){ * // ... notification sent * }) * * @param {string} msg * @param {object} options * @param {function} fn * @api public */ function growl(msg, options, fn) { var image, args, options = options || {}, fn = fn || function() {}; // noop if (!cmd) return fn(new Error('growl not supported on this platform')); args = [cmd.pkg]; // image if ((image = options.image)) { switch (cmd.type) { case 'Darwin-Growl': var flag, ext = path.extname(image).substr(1); flag = flag || (ext == 'icns' && 'iconpath'); flag = flag || (/^[A-Z]/.test(image) && 'appIcon'); flag = flag || (/^png|gif|jpe?g$/.test(ext) && 'image'); flag = flag || (ext && (image = ext) && 'icon'); flag = flag || 'icon'; args.push('--' + flag, quote(image)); break; case 'Linux': args.push(cmd.icon, quote(image)); // libnotify defaults to sticky, set a hint for transient notifications if (!options.sticky) args.push('--hint=int:transient:1'); break; case 'Windows': args.push(cmd.icon + quote(image)); break; } } // sticky if (options.sticky) args.push(cmd.sticky); // priority if (options.priority) { var priority = options.priority + ''; var checkindexOf = cmd.priority.range.indexOf(priority); if (~cmd.priority.range.indexOf(priority)) { args.push(cmd.priority, options.priority); } } // name if (options.name && cmd.type === 'Darwin-Growl') { args.push('--name', options.name); } switch (cmd.type) { case 'Darwin-Growl': args.push(cmd.msg); args.push(quote(msg)); if (options.title) args.push(quote(options.title)); break; case 'Darwin-NotificationCenter': args.push(cmd.msg); args.push(quote(msg)); if (options.title) { args.push(cmd.title); args.push(quote(options.title)); } if (options.subtitle) { args.push(cmd.subtitle); args.push(quote(options.subtitle)); } break; case 'Darwin-Growl': args.push(cmd.msg); args.push(quote(msg)); if (options.title) args.push(quote(options.title)); break; case 'Linux': if (options.title) { args.push(quote(options.title)); args.push(cmd.msg); args.push(quote(msg)); } else { args.push(quote(msg)); } break; case 'Windows': args.push(quote(msg)); if (options.title) args.push(cmd.title + quote(options.title)); break; } // execute exec(args.join(' '), fn); } }.call(this, require('_process'))); }, { _process: 51, child_process: 41, fs: 41, os: 50, path: 41 } ], 70: [ function(require, module, exports) { (function(process, global) { /** * Shim process.stdout. */ process.stdout = require('browser-stdout')(); var Mocha = require('../'); /** * Create a Mocha instance. * * @return {undefined} */ var mocha = new Mocha({ reporter: 'html' }); /** * Save timer references to avoid Sinon interfering (see GH-237). */ var Date = global.Date; var setTimeout = global.setTimeout; var setInterval = global.setInterval; var clearTimeout = global.clearTimeout; var clearInterval = global.clearInterval; var uncaughtExceptionHandlers = []; var originalOnerrorHandler = global.onerror; /** * Remove uncaughtException listener. * Revert to original onerror handler if previously defined. */ process.removeListener = function(e, fn) { if ('uncaughtException' == e) { if (originalOnerrorHandler) { global.onerror = originalOnerrorHandler; } else { global.onerror = function() {}; } var i = Mocha.utils.indexOf(uncaughtExceptionHandlers, fn); if (i != -1) { uncaughtExceptionHandlers.splice(i, 1); } } }; /** * Implements uncaughtException listener. */ process.on = function(e, fn) { if ('uncaughtException' == e) { global.onerror = function(err, url, line) { fn(new Error(err + ' (' + url + ':' + line + ')')); return !mocha.allowUncaught; }; uncaughtExceptionHandlers.push(fn); } }; // The BDD UI is registered by default, but no UI will be functional in the // browser without an explicit call to the overridden `mocha.ui` (see below). // Ensure that this default UI does not expose its methods to the global scope. mocha.suite.removeAllListeners('pre-require'); var immediateQueue = [], immediateTimeout; function timeslice() { var immediateStart = new Date().getTime(); while ( immediateQueue.length && new Date().getTime() - immediateStart < 100 ) { immediateQueue.shift()(); } if (immediateQueue.length) { immediateTimeout = setTimeout(timeslice, 0); } else { immediateTimeout = null; } } /** * High-performance override of Runner.immediately. */ Mocha.Runner.immediately = function(callback) { immediateQueue.push(callback); if (!immediateTimeout) { immediateTimeout = setTimeout(timeslice, 0); } }; /** * Function to allow assertion libraries to throw errors directly into mocha. * This is useful when running tests in a browser because window.onerror will * only receive the 'message' attribute of the Error. */ mocha.throwError = function(err) { Mocha.utils.forEach(uncaughtExceptionHandlers, function(fn) { fn(err); }); throw err; }; /** * Override ui to ensure that the ui functions are initialized. * Normally this would happen in Mocha.prototype.loadFiles. */ mocha.ui = function(ui) { Mocha.prototype.ui.call(this, ui); this.suite.emit('pre-require', global, null, this); return this; }; /** * Setup mocha with the given setting options. */ mocha.setup = function(opts) { if ('string' == typeof opts) opts = { ui: opts }; for (var opt in opts) this[opt](opts[opt]); return this; }; /** * Run mocha, returning the Runner. */ mocha.run = function(fn) { var options = mocha.options; mocha.globals('location'); var query = Mocha.utils.parseQuery(global.location.search || ''); if (query.grep) mocha.grep(new RegExp(query.grep)); if (query.fgrep) mocha.grep(query.fgrep); if (query.invert) mocha.invert(); return Mocha.prototype.run.call(mocha, function(err) { // The DOM Document is not available in Web Workers. var document = global.document; if ( document && document.getElementById('mocha') && options.noHighlighting !== true ) { Mocha.utils.highlightTags('code'); } if (fn) fn(err); }); }; /** * Expose the process shim. * https://github.com/mochajs/mocha/pull/916 */ Mocha.process = process; /** * Expose mocha. */ window.Mocha = Mocha; window.mocha = mocha; }.call( this, require('_process'), typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : typeof window !== 'undefined' ? window : {} )); }, { '../': 1, _process: 51, 'browser-stdout': 40 } ] }, {}, [70] ); ================================================ FILE: libs/code-demos/assets/runner/js/system-config.js ================================================ function loadSystemModule(name, code) { window.define = function(deps, callback) { console.log(window.System.x); window.System.amdDefine(name, deps, callback); }; window.define.amd = true; eval(code); } global['module'] = {}; System.config({ map: { '@angular/core': 'npm:@angular/core/bundles/core.umd.js', '@angular/core/testing': 'npm:@angular/core/bundles/core-testing.umd.js', '@angular/common': 'npm:@angular/common/bundles/common.umd.js', '@angular/common/http': 'npm:@angular/common/bundles/common-http.umd.js', '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js', '@angular/compiler/testing': 'npm:@angular/compiler/bundles/compiler-testing.umd.js', '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js', '@angular/cdk': 'npm:@angular/cdk/bundles/cdk.umd.js', '@angular/material/autocomplete': 'npm:@angular/material/bundles/material-autocomplete.umd.js', '@angular/material/autocomplete-testing': 'npm:@angular/material/bundles/material-autocomplete-testing.umd.js', '@angular/material/badge': 'npm:@angular/material/bundles/material-badge.umd.js', '@angular/material/bottom-sheet': 'npm:@angular/material/bundles/material-bottom-sheet.umd.js', '@angular/material/button': 'npm:@angular/material/bundles/material-button.umd.js', '@angular/material/button-testing': 'npm:@angular/material/bundles/material-button-testing.umd.js', '@angular/material/button-toggle': 'npm:@angular/material/bundles/material-button-toggle.umd.js', '@angular/material/card': 'npm:@angular/material/bundles/material-card.umd.js', '@angular/material/checkbox': 'npm:@angular/material/bundles/material-checkbox.umd.js', '@angular/material/checkbox-testing': 'npm:@angular/material/bundles/material-checkbox-testing.umd.js', '@angular/material/chips': 'npm:@angular/material/bundles/material-chips.umd.js', '@angular/material/core': 'npm:@angular/material/bundles/material-core.umd.js', '@angular/material/datepicker': 'npm:@angular/material/bundles/material-datepicker.umd.js', '@angular/material/dialog': 'npm:@angular/material/bundles/material-dialog.umd.js', '@angular/material/dialog-testing': 'npm:@angular/material/bundles/material-dialog-testing.umd.js', '@angular/material/divider': 'npm:@angular/material/bundles/material-divider.umd.js', '@angular/material/expansion': 'npm:@angular/material/bundles/material-expansion.umd.js', '@angular/material/form-field': 'npm:@angular/material/bundles/material-form-field.umd.js', '@angular/material/grid-list': 'npm:@angular/material/bundles/material-grid-list.umd.js', '@angular/material/icon': 'npm:@angular/material/bundles/material-icon.umd.js', '@angular/material/input': 'npm:@angular/material/bundles/material-input.umd.js', '@angular/material/list': 'npm:@angular/material/bundles/material-list.umd.js', '@angular/material/menu': 'npm:@angular/material/bundles/material-menu.umd.js', '@angular/material/menu-testing': 'npm:@angular/material/bundles/material-menu-testing.umd.js', '@angular/material/paginator': 'npm:@angular/material/bundles/material-paginator.umd.js', '@angular/material/progress-bar': 'npm:@angular/material/bundles/material-progress-bar.umd.js', '@angular/material/progress-bar-testing': 'npm:@angular/material/bundles/material-progress-bar-testing.umd.js', '@angular/material/progress-spinner': 'npm:@angular/material/bundles/material-progress-spinner.umd.js', '@angular/material/progress-spinner-testing': 'npm:@angular/material/bundles/material-progress-spinner-testing.umd.js', '@angular/material/radio': 'npm:@angular/material/bundles/material-radio.umd.js', '@angular/material/radio-testing': 'npm:@angular/material/bundles/material-radio-testing.umd.js', '@angular/material/select': 'npm:@angular/material/bundles/material-select.umd.js', '@angular/material/sidenav': 'npm:@angular/material/bundles/material-sidenav.umd.js', '@angular/material/sidenav-testing': 'npm:@angular/material/bundles/material-sidenav-testing.umd.js', '@angular/material/slide-toggle': 'npm:@angular/material/bundles/material-slide-toggle.umd.js', '@angular/material/slide-toggle-testing': 'npm:@angular/material/bundles/material-slide-toggle-testing.umd.js', '@angular/material/slider': 'npm:@angular/material/bundles/material-slider.umd.js', '@angular/material/slider-testing': 'npm:@angular/material/bundles/material-slider-testing.umd.js', '@angular/material/snack-bar': 'npm:@angular/material/bundles/material-snack-bar.umd.js', '@angular/material/snack-bar-testing': 'npm:@angular/material/bundles/material-snack-bar-testing.umd.js', '@angular/material/sort': 'npm:@angular/material/bundles/material-sort.umd.js', '@angular/material/stepper': 'npm:@angular/material/bundles/material-stepper.umd.js', '@angular/material/table': 'npm:@angular/material/bundles/material-table.umd.js', '@angular/material/tabs': 'npm:@angular/material/bundles/material-tabs.umd.js', '@angular/material/tabs-testing': 'npm:@angular/material/bundles/material-tabs-testing.umd.js', '@angular/material/toolbar': 'npm:@angular/material/bundles/material-toolbar.umd.js', '@angular/material/tooltip': 'npm:@angular/material/bundles/material-tooltip.umd.js', '@angular/material/tree': 'npm:@angular/material/bundles/material-tree.umd.js', '@angular/cdk/platform': 'npm:@angular/cdk/bundles/cdk-platform.umd.js', '@angular/cdk/bidi': 'npm:@angular/cdk/bundles/cdk-bidi.umd.js', '@angular/cdk/coercion': 'npm:@angular/cdk/bundles/cdk-coercion.umd.js', '@angular/cdk/a11y': 'npm:@angular/cdk/bundles/cdk-a11y.umd.js', '@angular/cdk/keycodes': 'npm:@angular/cdk/bundles/cdk-keycodes.umd.js', '@angular/cdk/portal': 'npm:@angular/cdk/bundles/cdk-portal.umd.js', '@angular/cdk/rxjs': 'npm:@angular/cdk/bundles/cdk-rxjs.umd.js', '@angular/cdk/table': 'npm:@angular/cdk/bundles/cdk-table.umd.js', '@angular/cdk/tree': 'npm:@angular/cdk/bundles/cdk-tree.umd.js', '@angular/cdk/stepper': 'npm:@angular/cdk/bundles/cdk-stepper.umd.js', '@angular/cdk/layout': 'npm:@angular/cdk/bundles/cdk-layout.umd.js', '@angular/cdk/text-field': 'npm:@angular/cdk/bundles/cdk-text-field.umd.js', '@angular/cdk/accordion': 'npm:@angular/cdk/bundles/cdk-accordion.umd.js', '@angular/cdk/scrolling': 'npm:@angular/cdk/bundles/cdk-scrolling.umd.js', '@angular/cdk/observers': 'npm:@angular/cdk/bundles/cdk-observers.umd.js', '@angular/cdk/overlay': 'npm:@angular/cdk/bundles/cdk-overlay.umd.js', '@angular/cdk/collections': 'npm:@angular/cdk/bundles/cdk-collections.umd.js', '@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js', '@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js', '@angular/platform-browser/animations': 'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js', '@angular/platform-browser/testing': 'npm:@angular/platform-browser/bundles/platform-browser-testing.umd.js', '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js', '@angular/platform-browser-dynamic/testing': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js', '@angular/http': 'npm:@angular/http/bundles/http.umd.js', '@angular/router': 'npm:@angular/router/bundles/router.umd.js', '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js', // other libraries tslib: 'npm:tslib/tslib.js', 'rxjs/operators': 'npm:rxjs/operators', rxjs: 'npm:rxjs' }, warnings: true, packages: { 'rxjs/operators': { main: 'index' }, rxjs: { main: 'index' } } }); ================================================ FILE: libs/code-demos/assets/runner/js/test-bootstrap.js ================================================ function mochaBefore() { mocha.suite.suites = []; mocha.suite._afterAll = []; mocha.suite._afterEach = []; mocha.suite._beforeAll = []; mocha.suite._beforeEach = []; mocha.setup('bdd').reporter(function() {}); } System.register( 'initTestBed', ['@angular/core/testing', '@angular/platform-browser-dynamic/testing'], function() { 'use strict'; var testing_1, testing_2; return { setters: [ function(testing_1_1) { testing_1 = testing_1_1; }, function(testing_2_1) { testing_2 = testing_2_1; } ], execute: function() { testing_1.TestBed.initTestEnvironment( testing_2.BrowserDynamicTestingModule, testing_2.platformBrowserDynamicTesting() ); } }; } ); function flattenTests(suite) { const result = []; function extractSuite(suite) { suite.suites.forEach(function(suite) { extractSuite(suite); }); suite.tests.forEach(function(test) { result.push(test.title); }); } extractSuite(suite); return result; } function mochaAfter(runId) { var parentFrame = window.parent; parentFrame.postMessage( { type: 'testList', tests: flattenTests(mocha.suite) }, '*' ); const runner = mocha.run(); runner .on('pass', function(test, result) { parentFrame.postMessage( { type: 'testResult', test: { title: test.title }, result: result, pass: true, runId: runId }, '*' ); }) .on('fail', function(test, error) { parentFrame.postMessage( { type: 'testResult', test: { title: test.title }, result: error.message, pass: false, runId: runId }, '*' ); }) .on('end', function() { parentFrame.postMessage( { type: 'testEnd' }, '*' ); runner.removeAllListeners('pass'); runner.removeAllListeners('fail'); runner.removeAllListeners('end'); }); } ================================================ FILE: libs/code-demos/assets/runner/ng-dts/bundler.ts ================================================ import { readFileSync, writeFileSync } from 'fs'; import { join, basename } from 'path'; const glob = require('glob'); const folders = [ '@angular/core', '@angular/platform-browser', '@angular/platform-browser-dynamic', '@angular/router', '@angular/cdk', '@angular/material/autocomplete', '@angular/material/badge', '@angular/material/bottom-sheet', '@angular/material/button', '@angular/material/button-toggle', '@angular/material/card', '@angular/material/checkbox', '@angular/material/chips', '@angular/material/core', '@angular/material/datepicker', '@angular/material/dialog', '@angular/material/divider', '@angular/material/expansion', '@angular/material/form-field', '@angular/material/grid-list', '@angular/material/icon', '@angular/material/input', '@angular/material/list', '@angular/material/menu', '@angular/material/paginator', '@angular/material/progress-bar', '@angular/material/progress-spinner', '@angular/material/radio', '@angular/material/select', '@angular/material/sidenav', '@angular/material/slide-toggle', '@angular/material/slider', '@angular/material/snack-bar', '@angular/material/sort', '@angular/material/stepper', '@angular/material/table', '@angular/material/tabs', '@angular/material/toolbar', '@angular/material/tooltip', '@angular/material/tree', '@angular/forms', 'rxjs' ]; interface FileModule { typings: string; dtsPaths: string[]; } const fileModules: FileModule[] = folders.map( (folder): FileModule => ({ typings: ( JSON.parse( readFileSync(`node_modules/${folder}/package.json`, { encoding: 'utf-8' }) ) || {} ).typings, dtsPaths: glob.sync(`node_modules/${folder}/**/*.d.ts`) }) ); const vendors = [].concat( ...fileModules.map(({ typings, dtsPaths }) => { return dtsPaths.map(path => { const paths = [path]; if (typings) { const dtsFileName = basename(path); const typingsName = basename(typings); if (typingsName === dtsFileName) { paths.push(path.replace(dtsFileName, 'index.d.ts')); } } const content = readFileSync(path, 'UTF-8'); return { paths, content }; }); }) ); const content = JSON.stringify(vendors, null, 2); writeFileSync(join(__dirname, './files.txt'), content, 'utf-8'); console.log('Done: '); console.log('number of types', vendors.length); console.log('File size (kb): ', content.length / 1000); ================================================ FILE: libs/code-demos/assets/runner/ng-dts/files.txt ================================================ [ { "paths": [ "node_modules/@angular/core/core.d.ts", "node_modules/@angular/core/index.d.ts" ], "content": "/**\n * @license Angular v9.0.0-next.12\n * (c) 2010-2019 Google LLC. https://angular.io/\n * License: MIT\n */\n\nimport { Observable } from 'rxjs';\r\nimport { Subject } from 'rxjs';\r\nimport { Subscription } from 'rxjs';\r\n\r\n/**\r\n * @description\r\n *\r\n * Represents an abstract class `T`, if applied to a concrete class it would stop being\r\n * instantiatable.\r\n *\r\n * @publicApi\r\n */\r\nimport * as ɵngcc0 from './src/r3_symbols';\nexport declare interface AbstractType extends Function {\r\n prototype: T;\r\n}\r\n\r\n/**\r\n * Below are constants for LContainer indices to help us look up LContainer members\r\n * without having to remember the specific indices.\r\n * Uglify will inline these when minifying so there shouldn't be a cost.\r\n */\r\ndeclare const ACTIVE_INDEX = 2;\r\n\r\n/**\r\n * @description\r\n * A lifecycle hook that is called after the default change detector has\r\n * completed checking all content of a directive.\r\n *\r\n * @see `AfterViewChecked`\r\n * @see [Lifecycle Hooks](guide/lifecycle-hooks#onchanges) guide\r\n *\r\n * @usageNotes\r\n * The following snippet shows how a component can implement this interface to\r\n * define its own after-check functionality.\r\n *\r\n * {@example core/ts/metadata/lifecycle_hooks_spec.ts region='AfterContentChecked'}\r\n *\r\n * @publicApi\r\n */\r\nexport declare interface AfterContentChecked {\r\n /**\r\n * A callback method that is invoked immediately after the\r\n * default change detector has completed checking all of the directive's\r\n * content.\r\n */\r\n ngAfterContentChecked(): void;\r\n}\r\n\r\n/**\r\n * @description\r\n * A lifecycle hook that is called after Angular has fully initialized\r\n * all content of a directive.\r\n * Define an `ngAfterContentInit()` method to handle any additional initialization tasks.\r\n *\r\n * @see `OnInit`\r\n * @see `AfterViewInit`\r\n * @see [Lifecycle Hooks](guide/lifecycle-hooks#onchanges) guide\r\n *\r\n * @usageNotes\r\n * The following snippet shows how a component can implement this interface to\r\n * define its own content initialization method.\r\n *\r\n * {@example core/ts/metadata/lifecycle_hooks_spec.ts region='AfterContentInit'}\r\n *\r\n * @publicApi\r\n */\r\nexport declare interface AfterContentInit {\r\n /**\r\n * A callback method that is invoked immediately after\r\n * Angular has completed initialization of all of the directive's\r\n * content.\r\n * It is invoked only once when the directive is instantiated.\r\n */\r\n ngAfterContentInit(): void;\r\n}\r\n\r\n/**\r\n * @description\r\n * A lifecycle hook that is called after the default change detector has\r\n * completed checking a component's view for changes.\r\n *\r\n * @see `AfterContentChecked`\r\n * @see [Lifecycle Hooks](guide/lifecycle-hooks#onchanges) guide\r\n *\r\n * @usageNotes\r\n * The following snippet shows how a component can implement this interface to\r\n * define its own after-check functionality.\r\n *\r\n * {@example core/ts/metadata/lifecycle_hooks_spec.ts region='AfterViewChecked'}\r\n *\r\n * @publicApi\r\n */\r\nexport declare interface AfterViewChecked {\r\n /**\r\n * A callback method that is invoked immediately after the\r\n * default change detector has completed one change-check cycle\r\n * for a component's view.\r\n */\r\n ngAfterViewChecked(): void;\r\n}\r\n\r\n/**\r\n * @description\r\n * A lifecycle hook that is called after Angular has fully initialized\r\n * a component's view.\r\n * Define an `ngAfterViewInit()` method to handle any additional initialization tasks.\r\n *\r\n * @see `OnInit`\r\n * @see `AfterContentInit`\r\n * @see [Lifecycle Hooks](guide/lifecycle-hooks#onchanges) guide\r\n *\r\n * @usageNotes\r\n * The following snippet shows how a component can implement this interface to\r\n * define its own view initialization method.\r\n *\r\n * {@example core/ts/metadata/lifecycle_hooks_spec.ts region='AfterViewInit'}\r\n *\r\n * @publicApi\r\n */\r\nexport declare interface AfterViewInit {\r\n /**\r\n * A callback method that is invoked immediately after\r\n * Angular has completed initialization of a component's view.\r\n * It is invoked only once when the view is instantiated.\r\n *\r\n */\r\n ngAfterViewInit(): void;\r\n}\r\n\r\n/**\r\n * A DI token that you can use to create a virtual [provider](guide/glossary#provider)\r\n * that will populate the `entryComponents` field of components and NgModules\r\n * based on its `useValue` property value.\r\n * All components that are referenced in the `useValue` value (either directly\r\n * or in a nested array or map) are added to the `entryComponents` property.\r\n *\r\n * @usageNotes\r\n *\r\n * The following example shows how the router can populate the `entryComponents`\r\n * field of an NgModule based on a router configuration that refers\r\n * to components.\r\n *\r\n * ```typescript\r\n * // helper function inside the router\r\n * function provideRoutes(routes) {\r\n * return [\r\n * {provide: ROUTES, useValue: routes},\r\n * {provide: ANALYZE_FOR_ENTRY_COMPONENTS, useValue: routes, multi: true}\r\n * ];\r\n * }\r\n *\r\n * // user code\r\n * let routes = [\r\n * {path: '/root', component: RootComp},\r\n * {path: '/teams', component: TeamsComp}\r\n * ];\r\n *\r\n * @NgModule({\r\n * providers: [provideRoutes(routes)]\r\n * })\r\n * class ModuleWithRoutes {}\r\n * ```\r\n *\r\n * @publicApi\r\n */\r\nexport declare const ANALYZE_FOR_ENTRY_COMPONENTS: InjectionToken;\r\n\r\n/**\r\n * All callbacks provided via this token will be called for every component that is bootstrapped.\r\n * Signature of the callback:\r\n *\r\n * `(componentRef: ComponentRef) => void`.\r\n *\r\n * @publicApi\r\n */\r\nexport declare const APP_BOOTSTRAP_LISTENER: InjectionToken<((compRef: ComponentRef) => void)[]>;\r\n\r\n/**\r\n * A DI Token representing a unique string id assigned to the application by Angular and used\r\n * primarily for prefixing application attributes and CSS styles when\r\n * {@link ViewEncapsulation#Emulated ViewEncapsulation.Emulated} is being used.\r\n *\r\n * If you need to avoid randomly generated value to be used as an application id, you can provide\r\n * a custom value via a DI provider configuring the root {@link Injector}\r\n * using this token.\r\n * @publicApi\r\n */\r\nexport declare const APP_ID: InjectionToken;\r\n\r\n/**\r\n * A function that will be executed when an application is initialized.\r\n *\r\n * @publicApi\r\n */\r\nexport declare const APP_INITIALIZER: InjectionToken<(() => void)[]>;\r\n\r\n/**\r\n * A class that reflects the state of running {@link APP_INITIALIZER}s.\r\n *\r\n * @publicApi\r\n */\r\nexport declare class ApplicationInitStatus {\r\n private appInits;\r\n private resolve;\r\n private reject;\r\n private initialized;\r\n readonly donePromise: Promise;\r\n readonly done = false;\r\n constructor(appInits: (() => any)[]);\r\n static ɵfac: ɵngcc0.ɵɵFactoryDef;\n static ɵprov: ɵngcc0.ɵɵInjectableDef;\n}\r\n\r\n/**\r\n * Configures the root injector for an app with\r\n * providers of `@angular/core` dependencies that `ApplicationRef` needs\r\n * to bootstrap components.\r\n *\r\n * Re-exported by `BrowserModule`, which is included automatically in the root\r\n * `AppModule` when you create a new app with the CLI `new` command.\r\n *\r\n * @publicApi\r\n */\r\nexport declare class ApplicationModule {\r\n constructor(appRef: ApplicationRef);\r\n static ɵmod: ɵngcc0.ɵɵNgModuleDefWithMeta;\n static ɵinj: ɵngcc0.ɵɵInjectorDef;\n}\r\n\r\n/**\r\n * A reference to an Angular application running on a page.\r\n *\r\n * @usageNotes\r\n *\r\n * {@a is-stable-examples}\r\n * ### isStable examples and caveats\r\n *\r\n * Note two important points about `isStable`, demonstrated in the examples below:\r\n * - the application will never be stable if you start any kind\r\n * of recurrent asynchronous task when the application starts\r\n * (for example for a polling process, started with a `setInterval`, a `setTimeout`\r\n * or using RxJS operators like `interval`);\r\n * - the `isStable` Observable runs outside of the Angular zone.\r\n *\r\n * Let's imagine that you start a recurrent task\r\n * (here incrementing a counter, using RxJS `interval`),\r\n * and at the same time subscribe to `isStable`.\r\n *\r\n * ```\r\n * constructor(appRef: ApplicationRef) {\r\n * appRef.isStable.pipe(\r\n * filter(stable => stable)\r\n * ).subscribe(() => console.log('App is stable now');\r\n * interval(1000).subscribe(counter => console.log(counter));\r\n * }\r\n * ```\r\n * In this example, `isStable` will never emit `true`,\r\n * and the trace \"App is stable now\" will never get logged.\r\n *\r\n * If you want to execute something when the app is stable,\r\n * you have to wait for the application to be stable\r\n * before starting your polling process.\r\n *\r\n * ```\r\n * constructor(appRef: ApplicationRef) {\r\n * appRef.isStable.pipe(\r\n * first(stable => stable),\r\n * tap(stable => console.log('App is stable now')),\r\n * switchMap(() => interval(1000))\r\n * ).subscribe(counter => console.log(counter));\r\n * }\r\n * ```\r\n * In this example, the trace \"App is stable now\" will be logged\r\n * and then the counter starts incrementing every second.\r\n *\r\n * Note also that this Observable runs outside of the Angular zone,\r\n * which means that the code in the subscription\r\n * to this Observable will not trigger the change detection.\r\n *\r\n * Let's imagine that instead of logging the counter value,\r\n * you update a field of your component\r\n * and display it in its template.\r\n *\r\n * ```\r\n * constructor(appRef: ApplicationRef) {\r\n * appRef.isStable.pipe(\r\n * first(stable => stable),\r\n * switchMap(() => interval(1000))\r\n * ).subscribe(counter => this.value = counter);\r\n * }\r\n * ```\r\n * As the `isStable` Observable runs outside the zone,\r\n * the `value` field will be updated properly,\r\n * but the template will not be refreshed!\r\n *\r\n * You'll have to manually trigger the change detection to update the template.\r\n *\r\n * ```\r\n * constructor(appRef: ApplicationRef, cd: ChangeDetectorRef) {\r\n * appRef.isStable.pipe(\r\n * first(stable => stable),\r\n * switchMap(() => interval(1000))\r\n * ).subscribe(counter => {\r\n * this.value = counter;\r\n * cd.detectChanges();\r\n * });\r\n * }\r\n * ```\r\n *\r\n * Or make the subscription callback run inside the zone.\r\n *\r\n * ```\r\n * constructor(appRef: ApplicationRef, zone: NgZone) {\r\n * appRef.isStable.pipe(\r\n * first(stable => stable),\r\n * switchMap(() => interval(1000))\r\n * ).subscribe(counter => zone.run(() => this.value = counter));\r\n * }\r\n * ```\r\n *\r\n * @publicApi\r\n */\r\nexport declare class ApplicationRef {\r\n private _zone;\r\n private _console;\r\n private _injector;\r\n private _exceptionHandler;\r\n private _componentFactoryResolver;\r\n private _initStatus;\r\n private _bootstrapListeners;\r\n private _views;\r\n private _runningTick;\r\n private _enforceNoNewChanges;\r\n private _stable;\r\n /**\r\n * Get a list of component types registered to this application.\r\n * This list is populated even before the component is created.\r\n */\r\n readonly componentTypes: Type[];\r\n /**\r\n * Get a list of components registered to this application.\r\n */\r\n readonly components: ComponentRef[];\r\n /**\r\n * Returns an Observable that indicates when the application is stable or unstable.\r\n *\r\n * @see [Usage notes](#is-stable-examples) for examples and caveats when using this API.\r\n */\r\n readonly isStable: Observable;\r\n /**\r\n * Bootstrap a new component at the root level of the application.\r\n *\r\n * @usageNotes\r\n * ### Bootstrap process\r\n *\r\n * When bootstrapping a new root component into an application, Angular mounts the\r\n * specified application component onto DOM elements identified by the componentType's\r\n * selector and kicks off automatic change detection to finish initializing the component.\r\n *\r\n * Optionally, a component can be mounted onto a DOM element that does not match the\r\n * componentType's selector.\r\n *\r\n * ### Example\r\n * {@example core/ts/platform/platform.ts region='longform'}\r\n */\r\n bootstrap(componentOrFactory: ComponentFactory | Type, rootSelectorOrNode?: string | any): ComponentRef;\r\n /**\r\n * Invoke this method to explicitly process change detection and its side-effects.\r\n *\r\n * In development mode, `tick()` also performs a second change detection cycle to ensure that no\r\n * further changes are detected. If additional changes are picked up during this second cycle,\r\n * bindings in the app have side-effects that cannot be resolved in a single change detection\r\n * pass.\r\n * In this case, Angular throws an error, since an Angular application can only have one change\r\n * detection pass during which all change detection must complete.\r\n */\r\n tick(): void;\r\n /**\r\n * Attaches a view so that it will be dirty checked.\r\n * The view will be automatically detached when it is destroyed.\r\n * This will throw if the view is already attached to a ViewContainer.\r\n */\r\n attachView(viewRef: ViewRef): void;\r\n /**\r\n * Detaches a view from dirty checking again.\r\n */\r\n detachView(viewRef: ViewRef): void;\r\n private _loadComponent;\r\n private _unloadComponent;\r\n /**\r\n * Returns the number of attached views.\r\n */\r\n readonly viewCount: number;\r\n static ɵfac: ɵngcc0.ɵɵFactoryDef;\n static ɵprov: ɵngcc0.ɵɵInjectableDef;\n}\r\n\r\n/**\r\n * @publicApi\r\n */\r\nexport declare function asNativeElements(debugEls: DebugElement[]): any;\r\n\r\n/**\r\n * Checks that there currently is a platform which contains the given token as a provider.\r\n *\r\n * @publicApi\r\n */\r\nexport declare function assertPlatform(requiredToken: any): PlatformRef;\r\n\r\n/**\r\n * Type of the Attribute metadata.\r\n *\r\n * @publicApi\r\n */\r\nexport declare interface Attribute {\r\n /**\r\n * The name of the attribute whose value can be injected.\r\n */\r\n attributeName?: string;\r\n}\r\n\r\n/**\r\n * Attribute decorator and metadata.\r\n *\r\n * @Annotation\r\n * @publicApi\r\n */\r\nexport declare const Attribute: AttributeDecorator;\r\n\r\n/**\r\n * Type of the Attribute decorator / constructor function.\r\n *\r\n * @publicApi\r\n */\r\nexport declare interface AttributeDecorator {\r\n /**\r\n * Parameter decorator for a directive constructor that designates\r\n * a host-element attribute whose value is injected as a constant string literal.\r\n *\r\n * @usageNotes\r\n *\r\n * Suppose we have an `` element and want to know its `type`.\r\n *\r\n * ```html\r\n * \r\n * ```\r\n *\r\n * The following example uses the decorator to inject the string literal `text`.\r\n *\r\n * {@example core/ts/metadata/metadata.ts region='attributeMetadata'}\r\n *\r\n * ### Example as TypeScript Decorator\r\n *\r\n * {@example core/ts/metadata/metadata.ts region='attributeFactory'}\r\n *\r\n */\r\n (name: string): any;\r\n new (name: string): Attribute;\r\n}\r\n\r\ndeclare const BINDING_INDEX = 7;\r\n\r\ndeclare interface BindingDef {\r\n flags: ɵBindingFlags;\r\n ns: string | null;\r\n name: string | null;\r\n nonMinifiedName: string | null;\r\n securityContext: SecurityContext | null;\r\n suffix: string | null;\r\n}\r\n\r\n/**\r\n * Provides additional options to the bootstraping process.\r\n *\r\n *\r\n */\r\ndeclare interface BootstrapOptions {\r\n /**\r\n * Optionally specify which `NgZone` should be used.\r\n *\r\n * - Provide your own `NgZone` instance.\r\n * - `zone.js` - Use default `NgZone` which requires `Zone.js`.\r\n * - `noop` - Use `NoopNgZone` which does nothing.\r\n */\r\n ngZone?: NgZone | 'zone.js' | 'noop';\r\n}\r\n\r\n\r\n/**\r\n * The strategy that the default change detector uses to detect changes.\r\n * When set, takes effect the next time change detection is triggered.\r\n *\r\n * @publicApi\r\n */\r\nexport declare enum ChangeDetectionStrategy {\r\n /**\r\n * Use the `CheckOnce` strategy, meaning that automatic change detection is deactivated\r\n * until reactivated by setting the strategy to `Default` (`CheckAlways`).\r\n * Change detection can still be explicitly invoked.\r\n * This strategy applies to all child directives and cannot be overridden.\r\n */\r\n OnPush = 0,\r\n /**\r\n * Use the default `CheckAlways` strategy, in which change detection is automatic until\r\n * explicitly deactivated.\r\n */\r\n Default = 1\r\n}\r\n\r\n/**\r\n * Base class for Angular Views, provides change detection functionality.\r\n * A change-detection tree collects all views that are to be checked for changes.\r\n * Use the methods to add and remove views from the tree, initiate change-detection,\r\n * and explicitly mark views as _dirty_, meaning that they have changed and need to be rerendered.\r\n *\r\n * @usageNotes\r\n *\r\n * The following examples demonstrate how to modify default change-detection behavior\r\n * to perform explicit detection when needed.\r\n *\r\n * ### Use `markForCheck()` with `CheckOnce` strategy\r\n *\r\n * The following example sets the `OnPush` change-detection strategy for a component\r\n * (`CheckOnce`, rather than the default `CheckAlways`), then forces a second check\r\n * after an interval. See [live demo](http://plnkr.co/edit/GC512b?p=preview).\r\n *\r\n * \r\n *\r\n * ### Detach change detector to limit how often check occurs\r\n *\r\n * The following example defines a component with a large list of read-only data\r\n * that is expected to change constantly, many times per second.\r\n * To improve performance, we want to check and update the list\r\n * less often than the changes actually occur. To do that, we detach\r\n * the component's change detector and perform an explicit local check every five seconds.\r\n *\r\n * \r\n *\r\n *\r\n * ### Reattaching a detached component\r\n *\r\n * The following example creates a component displaying live data.\r\n * The component detaches its change detector from the main change detector tree\r\n * when the `live` property is set to false, and reattaches it when the property\r\n * becomes true.\r\n *\r\n * \r\n *\r\n * @publicApi\r\n */\r\nexport declare abstract class ChangeDetectorRef {\r\n /**\r\n * When a view uses the {@link ChangeDetectionStrategy#OnPush OnPush} (checkOnce)\r\n * change detection strategy, explicitly marks the view as changed so that\r\n * it can be checked again.\r\n *\r\n * Components are normally marked as dirty (in need of rerendering) when inputs\r\n * have changed or events have fired in the view. Call this method to ensure that\r\n * a component is checked even if these triggers have not occured.\r\n *\r\n * \r\n *\r\n */\r\n abstract markForCheck(): void;\r\n /**\r\n * Detaches this view from the change-detection tree.\r\n * A detached view is not checked until it is reattached.\r\n * Use in combination with `detectChanges()` to implement local change detection checks.\r\n *\r\n * Detached views are not checked during change detection runs until they are\r\n * re-attached, even if they are marked as dirty.\r\n *\r\n * \r\n * \r\n *\r\n */\r\n abstract detach(): void;\r\n /**\r\n * Checks this view and its children. Use in combination with {@link ChangeDetectorRef#detach\r\n * detach}\r\n * to implement local change detection checks.\r\n *\r\n * \r\n * \r\n *\r\n */\r\n abstract detectChanges(): void;\r\n /**\r\n * Checks the change detector and its children, and throws if any changes are detected.\r\n *\r\n * Use in development mode to verify that running change detection doesn't introduce\r\n * other changes.\r\n */\r\n abstract checkNoChanges(): void;\r\n /**\r\n * Re-attaches the previously detached view to the change detection tree.\r\n * Views are attached to the tree by default.\r\n *\r\n * \r\n *\r\n */\r\n abstract reattach(): void;\r\n}\r\n\r\ndeclare const CHILD_HEAD = 14;\r\n\r\ndeclare const CHILD_TAIL = 15;\r\n\r\n/**\r\n * Configures the `Injector` to return an instance of `useClass` for a token.\r\n * @see [\"Dependency Injection Guide\"](guide/dependency-injection).\r\n *\r\n * @usageNotes\r\n *\r\n * {@example core/di/ts/provider_spec.ts region='ClassProvider'}\r\n *\r\n * Note that following two providers are not equal:\r\n *\r\n * {@example core/di/ts/provider_spec.ts region='ClassProviderDifference'}\r\n *\r\n * ### Multi-value example\r\n *\r\n * {@example core/di/ts/provider_spec.ts region='MultiProviderAspect'}\r\n *\r\n * @publicApi\r\n */\r\nexport declare interface ClassProvider extends ClassSansProvider {\r\n /**\r\n * An injection token. (Typically an instance of `Type` or `InjectionToken`, but can be `any`).\r\n */\r\n provide: any;\r\n /**\r\n * When true, injector returns an array of instances. This is useful to allow multiple\r\n * providers spread across many files to provide configuration information to a common token.\r\n */\r\n multi?: boolean;\r\n}\r\n\r\n/**\r\n * Configures the `Injector` to return a value by invoking a `useClass` function.\r\n * Base for `ClassProvider` decorator.\r\n *\r\n * @see [\"Dependency Injection Guide\"](guide/dependency-injection).\r\n *\r\n * @publicApi\r\n */\r\nexport declare interface ClassSansProvider {\r\n /**\r\n * Class to instantiate for the `token`.\r\n */\r\n useClass: Type;\r\n}\r\n\r\ndeclare const CLEANUP = 8;\r\n\r\n/**\r\n * @deprecated v4.0.0 - Use IterableChangeRecord instead.\r\n * @publicApi\r\n */\r\nexport declare interface CollectionChangeRecord extends IterableChangeRecord {\r\n}\r\n\r\n/**\r\n * Marks that the next string is for comment.\r\n *\r\n * See `I18nMutateOpCodes` documentation.\r\n */\r\ndeclare const COMMENT_MARKER: COMMENT_MARKER;\r\n\r\ndeclare interface COMMENT_MARKER {\r\n marker: 'comment';\r\n}\r\n\r\n/**\r\n * Compile an Angular injectable according to its `Injectable` metadata, and patch the resulting\r\n * injectable def (`ɵprov`) onto the injectable type.\r\n */\r\ndeclare function compileInjectable(type: Type, srcMeta?: Injectable): void;\r\n\r\n/**\r\n * Low-level service for running the angular compiler during runtime\r\n * to create {@link ComponentFactory}s, which\r\n * can later be used to create and render a Component instance.\r\n *\r\n * Each `@NgModule` provides an own `Compiler` to its injector,\r\n * that will use the directives/pipes of the ng module for compilation\r\n * of components.\r\n *\r\n * @publicApi\r\n */\r\nexport declare class Compiler {\r\n /**\r\n * Compiles the given NgModule and all of its components. All templates of the components listed\r\n * in `entryComponents` have to be inlined.\r\n */\r\n compileModuleSync: (moduleType: Type) => NgModuleFactory;\r\n /**\r\n * Compiles the given NgModule and all of its components\r\n */\r\n compileModuleAsync: (moduleType: Type) => Promise>;\r\n /**\r\n * Same as {@link #compileModuleSync} but also creates ComponentFactories for all components.\r\n */\r\n compileModuleAndAllComponentsSync: (moduleType: Type) => ModuleWithComponentFactories;\r\n /**\r\n * Same as {@link #compileModuleAsync} but also creates ComponentFactories for all components.\r\n */\r\n compileModuleAndAllComponentsAsync: (moduleType: Type) => Promise>;\r\n /**\r\n * Clears all caches.\r\n */\r\n clearCache(): void;\r\n /**\r\n * Clears the cache for the given component/ngModule.\r\n */\r\n clearCacheFor(type: Type): void;\r\n /**\r\n * Returns the id for a given NgModule, if one is defined and known to the compiler.\r\n */\r\n getModuleId(moduleType: Type): string | undefined;\r\n static ɵfac: ɵngcc0.ɵɵFactoryDef;\n static ɵprov: ɵngcc0.ɵɵInjectableDef;\n}\r\n\r\n/**\r\n * Token to provide CompilerOptions in the platform injector.\r\n *\r\n * @publicApi\r\n */\r\nexport declare const COMPILER_OPTIONS: InjectionToken;\r\n\r\n/**\r\n * A factory for creating a Compiler\r\n *\r\n * @publicApi\r\n */\r\nexport declare abstract class CompilerFactory {\r\n abstract createCompiler(options?: CompilerOptions[]): Compiler;\r\n}\r\n\r\n/**\r\n * Options for creating a compiler\r\n *\r\n * @publicApi\r\n */\r\nexport declare type CompilerOptions = {\r\n useJit?: boolean;\r\n defaultEncapsulation?: ViewEncapsulation;\r\n providers?: StaticProvider[];\r\n missingTranslation?: MissingTranslationStrategy;\r\n preserveWhitespaces?: boolean;\r\n};\r\n\r\n/**\r\n * Supplies configuration metadata for an Angular component.\r\n *\r\n * @publicApi\r\n */\r\nexport declare interface Component extends Directive {\r\n /**\r\n * The change-detection strategy to use for this component.\r\n *\r\n * When a component is instantiated, Angular creates a change detector,\r\n * which is responsible for propagating the component's bindings.\r\n * The strategy is one of:\r\n * - `ChangeDetectionStrategy#OnPush` sets the strategy to `CheckOnce` (on demand).\r\n * - `ChangeDetectionStrategy#Default` sets the strategy to `CheckAlways`.\r\n */\r\n changeDetection?: ChangeDetectionStrategy;\r\n /**\r\n * Defines the set of injectable objects that are visible to its view DOM children.\r\n * See [example](#injecting-a-class-with-a-view-provider).\r\n *\r\n */\r\n viewProviders?: Provider[];\r\n /**\r\n * The module ID of the module that contains the component.\r\n * The component must be able to resolve relative URLs for templates and styles.\r\n * SystemJS exposes the `__moduleName` variable within each module.\r\n * In CommonJS, this can be set to `module.id`.\r\n *\r\n */\r\n moduleId?: string;\r\n /**\r\n * The relative path or absolute URL of a template file for an Angular component.\r\n * If provided, do not supply an inline template using `template`.\r\n *\r\n */\r\n templateUrl?: string;\r\n /**\r\n * An inline template for an Angular component. If provided,\r\n * do not supply a template file using `templateUrl`.\r\n *\r\n */\r\n template?: string;\r\n /**\r\n * One or more relative paths or absolute URLs for files containing CSS stylesheets to use\r\n * in this component.\r\n */\r\n styleUrls?: string[];\r\n /**\r\n * One or more inline CSS stylesheets to use\r\n * in this component.\r\n */\r\n styles?: string[];\r\n /**\r\n * One or more animation `trigger()` calls, containing\r\n * `state()` and `transition()` definitions.\r\n * See the [Animations guide](/guide/animations) and animations API documentation.\r\n *\r\n */\r\n animations?: any[];\r\n /**\r\n * An encapsulation policy for the template and CSS styles. One of:\r\n * - `ViewEncapsulation.Native`: Deprecated. Use `ViewEncapsulation.ShadowDom` instead.\r\n * - `ViewEncapsulation.Emulated`: Use shimmed CSS that\r\n * emulates the native behavior.\r\n * - `ViewEncapsulation.None`: Use global CSS without any\r\n * encapsulation.\r\n * - `ViewEncapsulation.ShadowDom`: Use Shadow DOM v1 to encapsulate styles.\r\n *\r\n * If not supplied, the value is taken from `CompilerOptions`. The default compiler option is\r\n * `ViewEncapsulation.Emulated`.\r\n *\r\n * If the policy is set to `ViewEncapsulation.Emulated` and the component has no `styles`\r\n * or `styleUrls` specified, the policy is automatically switched to `ViewEncapsulation.None`.\r\n */\r\n encapsulation?: ViewEncapsulation;\r\n /**\r\n * Overrides the default encapsulation start and end delimiters (`{{` and `}}`)\r\n */\r\n interpolation?: [string, string];\r\n /**\r\n * A set of components that should be compiled along with\r\n * this component. For each component listed here,\r\n * Angular creates a {@link ComponentFactory} and stores it in the\r\n * {@link ComponentFactoryResolver}.\r\n */\r\n entryComponents?: Array | any[]>;\r\n /**\r\n * True to preserve or false to remove potentially superfluous whitespace characters\r\n * from the compiled template. Whitespace characters are those matching the `\\s`\r\n * character class in JavaScript regular expressions. Default is false, unless\r\n * overridden in compiler options.\r\n */\r\n preserveWhitespaces?: boolean;\r\n}\r\n\r\n/**\r\n * Component decorator and metadata.\r\n *\r\n * @Annotation\r\n * @publicApi\r\n */\r\nexport declare const Component: ComponentDecorator;\r\n\r\n/**\r\n * Component decorator interface\r\n *\r\n * @publicApi\r\n */\r\nexport declare interface ComponentDecorator {\r\n /**\r\n * Decorator that marks a class as an Angular component and provides configuration\r\n * metadata that determines how the component should be processed,\r\n * instantiated, and used at runtime.\r\n *\r\n * Components are the most basic UI building block of an Angular app.\r\n * An Angular app contains a tree of Angular components.\r\n *\r\n * Angular components are a subset of directives, always associated with a template.\r\n * Unlike other directives, only one component can be instantiated per an element in a template.\r\n *\r\n * A component must belong to an NgModule in order for it to be available\r\n * to another component or application. To make it a member of an NgModule,\r\n * list it in the `declarations` field of the `NgModule` metadata.\r\n *\r\n * Note that, in addition to these options for configuring a directive,\r\n * you can control a component's runtime behavior by implementing\r\n * life-cycle hooks. For more information, see the\r\n * [Lifecycle Hooks](guide/lifecycle-hooks) guide.\r\n *\r\n * @usageNotes\r\n *\r\n * ### Setting component inputs\r\n *\r\n * The following example creates a component with two data-bound properties,\r\n * specified by the `inputs` value.\r\n *\r\n * \r\n *\r\n *\r\n * ### Setting component outputs\r\n *\r\n * The following example shows two event emitters that emit on an interval. One\r\n * emits an output every second, while the other emits every five seconds.\r\n *\r\n * {@example core/ts/metadata/directives.ts region='component-output-interval'}\r\n *\r\n * ### Injecting a class with a view provider\r\n *\r\n * The following simple example injects a class into a component\r\n * using the view provider specified in component metadata:\r\n *\r\n * ```ts\r\n * class Greeter {\r\n * greet(name:string) {\r\n * return 'Hello ' + name + '!';\r\n * }\r\n * }\r\n *\r\n * @Directive({\r\n * selector: 'needs-greeter'\r\n * })\r\n * class NeedsGreeter {\r\n * greeter:Greeter;\r\n *\r\n * constructor(greeter:Greeter) {\r\n * this.greeter = greeter;\r\n * }\r\n * }\r\n *\r\n * @Component({\r\n * selector: 'greet',\r\n * viewProviders: [\r\n * Greeter\r\n * ],\r\n * template: ``\r\n * })\r\n * class HelloWorld {\r\n * }\r\n *\r\n * ```\r\n *\r\n * ### Preserving whitespace\r\n *\r\n * Removing whitespace can greatly reduce AOT-generated code size and speed up view creation.\r\n * As of Angular 6, the default for `preserveWhitespaces` is false (whitespace is removed).\r\n * To change the default setting for all components in your application, set\r\n * the `preserveWhitespaces` option of the AOT compiler.\r\n *\r\n * By default, the AOT compiler removes whitespace characters as follows:\r\n * * Trims all whitespaces at the beginning and the end of a template.\r\n * * Removes whitespace-only text nodes. For example,\r\n *\r\n * ```html\r\n * \r\n * ```\r\n *\r\n * becomes:\r\n *\r\n * ```html\r\n * \r\n * ```\r\n *\r\n * * Replaces a series of whitespace characters in text nodes with a single space.\r\n * For example, `\\n some text\\n` becomes ` some text `.\r\n * * Does NOT alter text nodes inside HTML tags such as `
        ` or `
        
            
        >
        ================================================ FILE: libs/console/src/lib/console.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ConsoleComponent } from './console.component'; describe('ConsoleComponent', () => { let component: ConsoleComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ConsoleComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(ConsoleComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/console/src/lib/console.component.ts ================================================ import { AfterViewInit, Component, HostListener, Input, OnInit, Type, ViewChild, ViewChildren } from '@angular/core'; function escapeHtml(str) { const div = document.createElement('div'); div.appendChild(document.createTextNode(str)); return div.innerHTML; } @Component({ selector: 'console-console', templateUrl: './console.component.html', styleUrls: ['./console.component.css'] }) export class ConsoleComponent implements OnInit, AfterViewInit { fontSize = 40; output = []; input = ''; inputHeight = 50; autocomplete: string[] = []; @Input() componentMap: { [key: string]: Type }; @Input() commands: string[]; @ViewChild('inp', { static: false }) inputEl; @ViewChildren('outputBlock') blocks; typeInQueue = []; currentCommand = 0; @HostListener('document:keydown.esc', ['$event']) handleKeyboardEvent() { this.inputEl.nativeElement.focus(); } trackByFn(a, i) { return i; } post(code: any, type: string = 'output') { if (code && code.dynamicComponent) { this.output.push({ code: code.dynamicComponent, param: code.param, type: 'dynamic' }); } else { this.output.push({ code: code, type }); } } evalCode(code: string) { return new Function('return (' + code + ')')(); } ngOnInit() { this.updateAutocomplete(); } calcInputHeight() { this.inputHeight = Math.max(50, this.inputEl.nativeElement.scrollHeight); } next() { this.typeIn(this.commands[this.currentCommand]); this.currentCommand++; } ngAfterViewInit() { const that = this; (window as any).explain = (component: string, param: string) => this.explain(component, param); console.log = function() { Array.from(arguments).map(a => that.post(a, 'log')); }; const addChar = () => { if (this.typeInQueue.length > 0) { const next = this.typeInQueue.shift(); if (next === 'execute') { this.execute(this.input); } else { this.input += next; } } window.setTimeout(() => addChar(), Math.random() * 1); }; requestAnimationFrame(() => { this.next(); }); addChar(); } execute(code: string) { if (code.trim() === '') { return; } if (!/['"`<]/.test(code[0])) { this.post(code); } if (code.trim().substr(0, 1) === '<') { this.post(escapeHtml(code)); code = `explain('html', \`${code}\`)`; } try { this.post(this.evalCode(code), 'result'); } catch (e) { this.post(e.message, 'error'); } requestAnimationFrame(() => { this.calcInputHeight(); this.blocks.last.nativeElement.scrollIntoView(); this.inputEl.nativeElement.focus(); }); this.input = ''; } typeIn(code) { this.typeInQueue = this.typeInQueue.concat(code.split('')); this.typeInQueue.push('execute'); } updateAutocomplete() { if (this.input.length < 3) { this.autocomplete = []; } else { this.autocomplete = this.commands.filter(a => a.includes(this.input)); } } private explain(s: string, param: string) { if (!this.componentMap[s]) { throw new Error(`Unknown component type: ${s}`); } return { dynamicComponent: this.componentMap[s], param }; } } ================================================ FILE: libs/console/src/lib/console.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { ConsoleComponent } from './console.component'; import { SharedPipeModule } from '../../../utils/src/lib/pipes/pipes.module'; import { DisplayDynamicComponent } from './display-dynamic.component/display-dynamic-component.component'; import { FormsModule } from '@angular/forms'; import { MatAutocompleteModule } from '@angular/material/autocomplete'; @NgModule({ imports: [SharedPipeModule, CommonModule, FormsModule, MatAutocompleteModule], declarations: [ConsoleComponent, DisplayDynamicComponent], exports: [ConsoleComponent] }) export class ConsoleModule {} ================================================ FILE: libs/console/src/lib/display-dynamic.component/display-dynamic-component.component.css ================================================ ================================================ FILE: libs/console/src/lib/display-dynamic.component/display-dynamic-component.component.html ================================================ ================================================ FILE: libs/console/src/lib/display-dynamic.component/display-dynamic-component.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { DisplayDynamicComponent } from './display-dynamic-component.component'; describe('DisplayDynamicComponent', () => { let component: DisplayDynamicComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [DisplayDynamicComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(DisplayDynamicComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/console/src/lib/display-dynamic.component/display-dynamic-component.component.ts ================================================ import { Component, ComponentFactoryResolver, Input, OnInit, Type, ViewContainerRef } from '@angular/core'; @Component({ selector: 'console-display-dynamic-component', templateUrl: './display-dynamic-component.component.html', styleUrls: ['./display-dynamic-component.component.css'] }) export class DisplayDynamicComponent implements OnInit { @Input() component: Type; @Input() param: string; constructor( private vcr: ViewContainerRef, private componentFactoryResolver: ComponentFactoryResolver ) {} ngOnInit() { const cf = this.componentFactoryResolver.resolveComponentFactory( this.component ); this.vcr.clear(); const componentRef = this.vcr.createComponent(cf); componentRef.instance.param = this.param; } } ================================================ FILE: libs/console/src/test.ts ================================================ // This file is required by karma.conf.js and loads recursively all the .spec and framework files import 'zone.js/dist/zone'; import 'zone.js/dist/zone-testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; declare const require: any; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); // Then we find all the tests. const context = require.context('./', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context); ================================================ FILE: libs/console/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "types": ["jasmine", "node"] } } ================================================ FILE: libs/console/tsconfig.lib.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "target": "es2015", "module": "es2015", "moduleResolution": "node", "declaration": true, "sourceMap": true, "inlineSources": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "importHelpers": true, "types": [], "lib": ["dom", "es2015"] }, "angularCompilerOptions": { "annotateForClosureCompiler": true, "skipTemplateCodegen": true, "strictMetadataEmit": true, "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "flatModuleId": "AUTOGENERATED", "flatModuleOutFile": "AUTOGENERATED" }, "exclude": ["src/test.ts", "**/*.spec.ts"] } ================================================ FILE: libs/console/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "types": ["jasmine", "node"] }, "files": ["src/test.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: libs/console/tslint.json ================================================ { "extends": "../../tslint.json", "rules": { "directive-selector": [true, "attribute", "console", "camelCase"], "component-selector": [true, "element", "console", "kebab-case"] } } ================================================ FILE: libs/feedback/karma.conf.js ================================================ // Karma configuration file, see link for more information // https://karma-runner.github.io/1.0/config/configuration-file.html const { join } = require('path'); const getBaseKarmaConfig = require('../../karma.conf'); module.exports = function(config) { const baseConfig = getBaseKarmaConfig(); config.set({ ...baseConfig, coverageIstanbulReporter: { ...baseConfig.coverageIstanbulReporter, dir: join(__dirname, '../../coverage/libs/feedback') } }); }; ================================================ FILE: libs/feedback/ng-package.json ================================================ { "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", "dest": "../../dist/libs/feedback", "lib": { "entryFile": "src/index.ts" } } ================================================ FILE: libs/feedback/ng-package.prod.json ================================================ { "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", "dest": "../../dist/libs/feedback", "lib": { "entryFile": "src/index.ts" } } ================================================ FILE: libs/feedback/package.json ================================================ { "name": "@codelab/feedback", "version": "0.0.1", "peerDependencies": { "@angular/common": "^7.0.0", "@angular/core": "^7.0.0" } } ================================================ FILE: libs/feedback/src/index.ts ================================================ export * from './lib/feedback.module'; ================================================ FILE: libs/feedback/src/lib/feedback-issue-dropdown/feedback-issue-dropdown.component.html ================================================ Take Close [Duplicate] Close [No fix] Close [Done] Close [Nice message] Close [Can't reproduce] ================================================ FILE: libs/feedback/src/lib/feedback-issue-dropdown/feedback-issue-dropdown.component.scss ================================================ :host { .dropdown { float: right; button { cursor: pointer; border: none; background: #f0f3f5; } img { width: 15px; } } } ================================================ FILE: libs/feedback/src/lib/feedback-issue-dropdown/feedback-issue-dropdown.component.ts ================================================ import { ChangeDetectionStrategy, Component, Input } from '@angular/core'; import { GithubService } from '@codelab/utils'; @Component({ selector: 'feedback-issue-dropdown', changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: './feedback-issue-dropdown.component.html', styleUrls: ['./feedback-issue-dropdown.component.scss'] }) export class FeedbackIssueDropdownComponent { @Input() message: object; constructor(private ghService: GithubService) {} createIssue() { this.ghService.createIssue(this.message); } createClosedIssue(reason) { this.ghService.createClosedIssue(this.message, reason); } } ================================================ FILE: libs/feedback/src/lib/feedback-rating/feedback-rating.component.css ================================================ .rate { border-radius: 50%; cursor: pointer; margin-left: auto; margin-right: auto; width: 120px; } .rate:hover { background-image: -ms-linear-gradient( left, #b8e1fc 0%, #90bae4 25%, #90bcea 37%, #bdf3fd 100% ); background-image: -webkit-linear-gradient( left, #b8e1fc 0%, #90bae4 25%, #90bcea 37%, #bdf3fd 100% ); background-image: -webkit-gradient( left, #b8e1fc 0%, #90bae4 25%, #90bcea 37%, #bdf3fd 100% ); background-image: -o-linear-gradient( left, #b8e1fc 0%, #90bae4 25%, #90bcea 37%, #bdf3fd 100% ); background-image: -moz-linear-gradient( left, #b8e1fc 0%, #90bae4 25%, #90bcea 37%, #bdf3fd 100% ); background-image: linear-gradient( to right, #b8e1fc 0%, #90bae4 25%, #90bcea 37%, #bdf3fd 100% ); } .rateheading { width: 140px; text-align: center; } .rateoption { width: 200px; text-align: center; margin-left: 10px; margin-right: 10px; } .rateselected { border: 3px solid slategray; } .ratings { margin-top: 25px; } .ratingshidden { visibility: hidden; } .slideup { animation-name: slideup; -webkit-animation-name: slideup; -moz-animation-name: slideup; animation-duration: 1s; -webkit-animation-duration: 1s; -moz-animation-duration: 1s; animation-timing-function: ease; -webkit-animation-timing-function: ease; -moz-animation-timing-function: ease; } @keyframes slideup { 0% { transform: translateY(100%); } 50% { transform: translateY(8%); } 65% { transform: translateY(-4%); } 80% { transform: translateY(4%); } 95% { transform: translateY(-2%); } 100% { transform: translateY(0%); } } @-webkit-keyframes slideup { 0% { -webkit-transform: translateY(100%); transform: translateY(100%); } 50% { -webkit-transform: translateY(8%); transform: translateY(8%); } 65% { -webkit-transform: translateY(-4%); transform: translateY(-4%); } 80% { -webkit-transform: translateY(4%); transform: translateY(4%); } 95% { -webkit-transform: translateY(-2%); transform: translateY(-2%); } 100% { -webkit-transform: translateY(0%); transform: translateY(0%); } } .slidedown { animation-name: slidedown; -webkit-animation-name: slidedown; -moz-animation-name: slidedown; animation-duration: 2s; -webkit-animation-duration: 2s; -moz-animation-duration: 2s; animation-timing-function: ease; -webkit-animation-timing-function: ease; -moz-animation-timing-function: ease; animation-fill-mode: forwards; } @keyframes slidedown { 0% { transform: translateY(0%); } 50% { transform: translateY(0%); } 99% { transform: translateY(200%); } 100% { transform: translateY(200%); visibility: hidden; } } @-webkit-keyframes slidedown { 0% { -webkit-transform: translateY(0%); transform: translateY(0%); } 50% { -webkit-transform: translateY(0%); transform: translateY(0%); } 99% { -webkit-transform: translateY(200%); transform: translateY(200%); } 100% { -webkit-transform: translateY(200%); transform: translateY(200%); visibility: hidden; } } .summarytable { padding: 10px; border-collapse: collapse; } .summarytable tr:nth-child(even) { background-color: lightgoldenrodyellow; } .summarytable tr td { padding: 10px; border-top: 1px solid black; } ================================================ FILE: libs/feedback/src/lib/feedback-rating/feedback-rating.component.html ================================================
        Perfect
        good
        ok
        Hoped for more


        Rate this lesson ...

        {{ rate.text }}

        Lesson

        {{ rate.text }}

        {{ rating.lesson }}

        {{ rating[rate.value] }}

        ================================================ FILE: libs/feedback/src/lib/feedback-rating/feedback-rating.component.ts ================================================ import { AfterViewInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnInit } from '@angular/core'; import { FeedbackService } from '../feedback.service'; import { Observable } from 'rxjs'; @Component({ selector: 'feedback-rating', changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: './feedback-rating.component.html', styleUrls: ['./feedback-rating.component.css'] }) export class FeedbackRatingComponent implements OnInit, AfterViewInit { @Input() showSummary = false; @Input() lesson = ''; ratings$: Observable; ratingsClass = 'ratings ratingshidden'; rateSelected = -1; rates = [ { src: 'ng-smile.svg', value: '"perfect', text: 'Perfect!' }, { src: 'ng-ok.svg', value: 'good', text: 'Good' }, { src: 'ng-soso.svg', value: 'soso', text: 'Ok' }, { src: 'ng-sleepy.svg', value: 'hopedformore', text: 'Hoped for more!' } ]; constructor( private ref: ChangeDetectorRef, private feedbackService: FeedbackService ) {} ngOnInit() { this.ratings$ = this.feedbackService.getRatings(); } ngAfterViewInit() { setTimeout(() => { this.ratingsClass = 'ratings slideup'; // TODO: work on animation process this.ref.markForCheck(); }, 1000); } rateClass(option: number) { let className = ''; if (option === this.rateSelected) { className = 'rateselected'; } return 'rate ' + className; } selectRate(option: number) { this.rateSelected = option; this.ratingsClass = 'ratings slidedown'; this.feedbackService.addRating(this.lesson, this.rates[option].value); } } ================================================ FILE: libs/feedback/src/lib/feedback-widget/feedback-widget.component.html ================================================
        Email is optional and, will not displayed
        {{ statusMessage }}
        ================================================ FILE: libs/feedback/src/lib/feedback-widget/feedback-widget.component.scss ================================================ .modal-title { margin: 0 0 10px 0; } .menu-bar-btn { background-color: #3498db !important; } .messages-count { position: absolute; width: 15px; height: 15px; top: 0; text-align: center; font-size: 13px; } .messages-count { float: right; background: #ff594b; color: white; border-radius: 50%; } .form-container { padding: 10px 10px 10px 0px; max-width: 300px; margin-right: 10px; } textarea { margin: 10px 0; height: 100px; } input, textarea { width: 100%; border: 1px #ddd solid; padding: 4px; margin-top: 10px; } .feedback-items { max-height: 300px; overflow-y: auto; } .feedback-item { background: #f0f3f5; border-radius: 4px; padding: 8px; margin: 5px 0; } .feedback-item .header { display: flex; justify-content: space-between; font-size: 12px; margin-bottom: 4px; } .header .date { float: right; } .feedback-item .name { color: #ff594b; } .messages-count { background: #ff594b; color: white; border-radius: 50%; } #message { margin-top: 15px; } .error { color: red; } .email label { color: #999; font-size: 12px; } .comment { white-space: pre-line; } .text-muted { color: #6c757d !important; } .mt-0 { margin-top: 0; } ================================================ FILE: libs/feedback/src/lib/feedback-widget/feedback-widget.component.ts ================================================ import { ActivatedRoute, Router } from '@angular/router'; import { ChangeDetectionStrategy, Component, OnDestroy, OnInit } from '@angular/core'; import { FeedbackService } from '../feedback.service'; import { Message } from '../message'; import { Observable, Subject } from 'rxjs'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { debounceTime, takeUntil } from 'rxjs/operators'; import { AccessService } from '../../../../../apps/codelab/src/app/shared/services/access.service'; @Component({ selector: 'feedback-widget', changeDetection: ChangeDetectionStrategy.OnPush, templateUrl: './feedback-widget.component.html', styleUrls: ['./feedback-widget.component.scss'] }) export class FeedbackWidgetComponent implements OnInit, OnDestroy { messages$: Observable; formGroup: FormGroup; statusMessage = ''; error = false; private readonly destroy = new Subject(); constructor( private readonly fb: FormBuilder, private readonly activatedRoute: ActivatedRoute, private readonly accessService: AccessService, private readonly feedbackService: FeedbackService, private readonly router: Router ) { this.messages$ = this.feedbackService.getMessagesForCurrentPage(); } ngOnInit() { // TODO: Consider the possibility to transfer logic to it's service. let value = localStorage[`feedback-${this.router.url}-comment`] || ''; value = value === 'null' ? '' : value; this.formGroup = this.fb.group({ comment: [value, Validators.required], name: [localStorage.getItem('userName') || '', Validators.required], email: [localStorage.getItem('userEmail') || '', []] }); this.formGroup.valueChanges .pipe(debounceTime(500), takeUntil(this.destroy)) .subscribe(data => { localStorage[`feedback-${this.router.url}-comment`] = data.comment; }); } submit() { const formValues: any = this.formGroup.getRawValue(); localStorage.setItem('userName', formValues.name); localStorage.setItem('userEmail', formValues.email); this.feedbackService .addMessage( formValues.name, formValues.email, formValues.comment, this.getHeaderText() ) .then(() => { this.formGroup.get('comment').reset(); }) .catch(() => { this.statusMessage = 'Error while sending feedback'; this.error = true; }); } ngOnDestroy() { this.destroy.next(null); this.destroy.complete(); } private getHeaderText(): string { const el = document.body.querySelector('h1'); return el ? el.innerHTML : ''; } } ================================================ FILE: libs/feedback/src/lib/feedback.module.spec.ts ================================================ import { async, TestBed } from '@angular/core/testing'; import { FeedbackModule } from './feedback.module'; describe('FeedbackModule', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [FeedbackModule] }).compileComponents(); })); it('should create', () => { expect(FeedbackModule).toBeDefined(); }); }); ================================================ FILE: libs/feedback/src/lib/feedback.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { AngularFireDatabaseModule } from '@angular/fire/database'; import { AngularFireModule } from '@angular/fire'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; import { MatCardModule } from '@angular/material/card'; import { FeedbackService } from './feedback.service'; import { FeedbackWidgetComponent } from './feedback-widget/feedback-widget.component'; import { FeedbackRatingComponent } from './feedback-rating/feedback-rating.component'; import { FeedbackIssueDropdownComponent } from '@codelab/feedback/src/lib/feedback-issue-dropdown/feedback-issue-dropdown.component'; import { HttpClientModule } from '@angular/common/http'; import { GithubModule, GithubService } from '@codelab/utils'; import { environment } from '../../../../apps/codelab/src/environments/environment'; import { MatButtonModule } from '@angular/material/button'; import { MatMenuModule } from '@angular/material/menu'; export const angularFire = AngularFireModule.initializeApp( environment.firebaseConfig ); @NgModule({ imports: [ CommonModule, ReactiveFormsModule, AngularFireDatabaseModule, angularFire, FormsModule, AngularFireDatabaseModule, HttpClientModule, GithubModule, MatMenuModule, MatButtonModule, MatCardModule ], providers: [FeedbackService, GithubService], declarations: [ FeedbackWidgetComponent, FeedbackRatingComponent, FeedbackIssueDropdownComponent ], exports: [ FeedbackWidgetComponent, FeedbackRatingComponent, FeedbackIssueDropdownComponent ] }) export class FeedbackModule {} ================================================ FILE: libs/feedback/src/lib/feedback.service.spec.ts ================================================ import { ActivatedRoute, Router } from '@angular/router'; import { AngularFireDatabase } from '@angular/fire/database'; import { async, inject, TestBed } from '@angular/core/testing'; import { BehaviorSubject } from 'rxjs'; import { Component } from '@angular/core'; import { FeedbackService } from './feedback.service'; import { RouterTestingModule } from '@angular/router/testing'; @Component({ template: ` ` }) class RoutingComponent {} @Component({ template: '' }) class DummyComponent {} const mockActivatedRoute = { url: new BehaviorSubject('') }; const mockMessages = [ { name: 'a', href: '/typescript/1', comment: 'blah', email: 'dan@dan.com' }, { name: 'b', href: '/typescript/1', comment: 'blah', email: 'dan@dan.com' }, { name: 'c', href: '/typescript/2', comment: 'blah', email: 'dan@dan.com' } ]; let mockMessageStream: any = new BehaviorSubject(mockMessages); mockMessageStream = Object.assign(mockMessageStream, { push: jasmine.createSpy('push') }); const mockDb = { list: jasmine.createSpy('list').and.returnValue(mockMessageStream) }; let fixture; xdescribe('FeedbackService', () => { beforeEach(() => { TestBed.configureTestingModule({ providers: [ FeedbackService, { provide: AngularFireDatabase, useValue: mockDb }, { provide: ActivatedRoute, useValue: mockActivatedRoute } ], imports: [ RouterTestingModule.withRoutes([ { path: 'typescript/1', component: DummyComponent }, { path: 'typescript/2', component: DummyComponent } ]) ], declarations: [RoutingComponent, DummyComponent] }); }); beforeEach(() => { fixture = TestBed.createComponent(RoutingComponent); fixture.detectChanges(); }); it('should initialize data source when created', inject( [FeedbackService], (service: FeedbackService) => { expect(mockDb.list).toHaveBeenCalledWith('/feedback'); } )); it('should list messages filtered by the router url', async( inject( [FeedbackService, ActivatedRoute, Router], ( service: FeedbackService, _activatedRoute: ActivatedRoute, router: Router ) => { router.navigateByUrl('typescript/1').then(() => { const stream = service .getMessages(_activatedRoute) .subscribe(values => { expect(values.length).toEqual(2); }); }); } ) )); it('should list messages filtered by the router url when the activated route changes', async( inject( [FeedbackService, ActivatedRoute, Router], ( service: FeedbackService, _activatedRoute: ActivatedRoute, router: any ) => { let calls = 0; router.navigateByUrl('typescript/1').then(() => { service.getMessages(_activatedRoute).subscribe(values => { if (calls === 0) { expect(values.length).toEqual(2); } if (calls === 1) { expect(values.length).toEqual(1); } calls++; }); router.navigateByUrl('typescript/2').then(() => { mockActivatedRoute.url.next(''); }); }); } ) )); it('should add a message from the current url', async( inject( [FeedbackService, Router], (service: FeedbackService, router: Router) => { router.navigateByUrl('typescript/1').then(() => { service.addMessage('a', 'b', 'c', 'header'); expect(mockMessageStream.push).toHaveBeenCalled(); const call: jasmine.Spy = mockMessageStream.push; const obj = call.calls.mostRecent().args[0]; expect(obj.href).toEqual('/typescript/1'); }); } ) )); }); ================================================ FILE: libs/feedback/src/lib/feedback.service.ts ================================================ import { Event, NavigationEnd, Router } from '@angular/router'; import { AngularFireDatabase, AngularFireList } from '@angular/fire/database'; import { Injectable } from '@angular/core'; import { getRef } from '@angular/fire/database/utils'; import { Message } from './message'; import { defer, Observable, of } from 'rxjs'; import { filter, map, publishReplay, refCount, repeatWhen, switchMap } from 'rxjs/operators'; function normalize(feedback: Array) { return feedback.map(item => ({ ...(item.payload && item.payload.val()), key: item.key })); } @Injectable() export class FeedbackService { private repo$: AngularFireList; private ratings$: AngularFireList; constructor(private database: AngularFireDatabase, private router: Router) { this.repo$ = this.database.list('/feedback'); this.ratings$ = this.database.list('/ratings'); } // Get a stream of messages filtered by href (of a message) getMessages(url: string): Observable { return this.database .list('/feedback', ref => ref.orderByChild('href').equalTo(url)) .snapshotChanges() .pipe( map(normalize), map((items: Message[]) => items.filter(item => !item.isDone)) ); } getMessagesForCurrentPage(): Observable { const onNavigationEnd: Observable = this.router.events.pipe( filter(val => val instanceof NavigationEnd) ); const url: Observable = defer(() => of(this.router.url)).pipe( repeatWhen(() => onNavigationEnd) ); return url.pipe( switchMap(url => this.getMessages(url)), publishReplay(1), refCount() ); } addMessage( name: string, email: string, comment: string, header?: string ): any { const message = { name, email, comment, header, timestamp: new Date().toUTCString(), href: this.router.url }; return this.repo$.push(message); } getRatings(): Observable { return this.ratings$.valueChanges(); } addRating(lesson: string, rating: string) { const path = 'ratings/' + lesson; getRef(this.database.database, path).transaction(ratings => { if (ratings == null) { ratings = { lesson: lesson }; } const count = ratings[rating] || 0; ratings[rating] = count + 1; return ratings; }); } } ================================================ FILE: libs/feedback/src/lib/message.ts ================================================ export interface Message { comment: string; name: string; email: string; timestamp?: string; href?: string; header?: string; isDone?: boolean; } ================================================ FILE: libs/feedback/src/test.ts ================================================ // This file is required by karma.conf.js and loads recursively all the .spec and framework files import 'zone.js/dist/zone'; import 'zone.js/dist/zone-testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; declare const require: any; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); // Then we find all the tests. const context = require.context('./', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context); ================================================ FILE: libs/feedback/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "types": ["jasmine", "node"] } } ================================================ FILE: libs/feedback/tsconfig.lib.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "target": "es2015", "module": "es2015", "moduleResolution": "node", "declaration": true, "sourceMap": true, "inlineSources": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "importHelpers": true, "types": [], "lib": ["dom", "es2018"] }, "angularCompilerOptions": { "annotateForClosureCompiler": true, "skipTemplateCodegen": true, "strictMetadataEmit": true, "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "enableResourceInlining": true }, "exclude": ["src/test.ts", "**/*.spec.ts"] } ================================================ FILE: libs/feedback/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "types": ["jasmine", "node"] }, "files": ["src/test.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: libs/feedback/tslint.json ================================================ { "extends": "../../tslint.json", "rules": { "directive-selector": [true, "attribute", "feedback", "camelCase"], "component-selector": [true, "element", "feedback", "kebab-case"] } } ================================================ FILE: libs/firebase/README.md ================================================ # firebase This library was generated with [Nx](https://nx.dev). ## Running unit tests Run `nx test firebase` to execute the unit tests. ================================================ FILE: libs/firebase/jest.config.js ================================================ module.exports = { name: 'firebase', preset: '../../jest.config.js', coverageDirectory: '../../coverage/libs/firebase', snapshotSerializers: [ 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 'jest-preset-angular/build/AngularSnapshotSerializer.js', 'jest-preset-angular/build/HTMLCommentSerializer.js' ] }; ================================================ FILE: libs/firebase/src/index.ts ================================================ export * from './lib/firebase.module'; ================================================ FILE: libs/firebase/src/lib/firebase.module.spec.ts ================================================ import { async, TestBed } from '@angular/core/testing'; import { FirebaseModule } from './firebase.module'; describe('FirebaseModule', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [FirebaseModule] }).compileComponents(); })); it('should create', () => { expect(FirebaseModule).toBeDefined(); }); }); ================================================ FILE: libs/firebase/src/lib/firebase.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SyncFireStoreDirective } from './sync-fire-store.directive'; import { AngularFireModule } from '@angular/fire'; import { AngularFirestoreModule } from '@angular/fire/firestore'; @NgModule({ imports: [CommonModule, AngularFirestoreModule], exports: [SyncFireStoreDirective], declarations: [SyncFireStoreDirective] }) export class FirebaseModule {} ================================================ FILE: libs/firebase/src/lib/sync-fire-store.directive.spec.ts ================================================ import { SyncFireStoreDirective } from './sync-fire-store.directive'; import { ComponentFixture, TestBed } from '@angular/core/testing'; import { FormsModule } from '@angular/forms'; import { Component } from '@angular/core'; @Component({ selector: 'codelab-any', template: ` ` }) export class TestComponent { lol = 'hello'; } describe('SyncFireStoreDirective', () => { let fixture: ComponentFixture; let component: TestComponent; beforeEach(() => { TestBed.configureTestingModule({ imports: [FormsModule], declarations: [TestComponent, SyncFireStoreDirective] }); fixture = TestBed.createComponent(TestComponent); component = fixture.componentInstance; }); it('should create an instance', () => { const directive = new SyncFireStoreDirective(); expect(directive).toBeTruthy(); }); }); ================================================ FILE: libs/firebase/src/lib/sync-fire-store.directive.ts ================================================ import { Directive, forwardRef, HostBinding, HostListener, Input, OnChanges, SimpleChanges } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR, NgControl } from '@angular/forms'; import { AngularFirestore, AngularFirestoreDocument } from '@angular/fire/firestore'; import { Subscription } from 'rxjs'; export const SYNC_FIRESTORE_VALUE_ACCESSOR = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => SyncFireStoreDirective), multi: true }; // TODO(kirjs): This is not yet working @Directive({ // tslint:disable-next-line:directive-selector selector: '[syncFireStore]' }) export class SyncFireStoreDirective implements OnChanges { @Input() syncFireStore!: T; private doc: AngularFirestoreDocument; private subscription?: Subscription; constructor(private afs: AngularFirestore, control: NgControl) {} ngOnChanges(changes: SimpleChanges) { if (changes.key) { if (this.subscription) { this.subscription.unsubscribe(); this.subscription = undefined; } this.doc = this.afs.doc('items/1'); this.subscription = this.doc.valueChanges().subscribe(); } } } ================================================ FILE: libs/firebase/src/test-setup.ts ================================================ import 'jest-preset-angular'; ================================================ FILE: libs/firebase/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "types": ["node", "jest"] }, "include": ["**/*.ts"] } ================================================ FILE: libs/firebase/tsconfig.lib.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "target": "es2015", "declaration": true, "inlineSources": true, "types": [], "lib": ["dom", "es2018"] }, "angularCompilerOptions": { "annotateForClosureCompiler": true, "skipTemplateCodegen": true, "strictMetadataEmit": true, "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "enableResourceInlining": true }, "exclude": ["src/test-setup.ts", "**/*.spec.ts"] } ================================================ FILE: libs/firebase/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "module": "commonjs", "types": ["jest", "node"] }, "files": ["src/test-setup.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: libs/firebase/tslint.json ================================================ { "extends": "../../tslint.json", "rules": { "directive-selector": [true, "attribute", "codelab", "camelCase"], "component-selector": [true, "element", "codelab", "kebab-case"] } } ================================================ FILE: libs/firebase-login/index.ts ================================================ export * from './src'; ================================================ FILE: libs/firebase-login/karma.conf.js ================================================ module.exports = function(config) { config.set({ basePath: '../', frameworks: ['jasmine', '@angular-devkit/build-angular'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), require('@angular-devkit/build-angular/plugins/karma') ], client: { clearContext: false // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { dir: require('path').join(__dirname, '../coverage'), reports: ['html', 'lcovonly'], fixWebpackSourcePaths: true }, reporters: ['progress', 'kjhtml'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], singleRun: false }); }; ================================================ FILE: libs/firebase-login/ng-package.json ================================================ { "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", "dest": "../../dist/libs/firebase-login", "lib": { "entryFile": "src/index.ts" } } ================================================ FILE: libs/firebase-login/package.json ================================================ { "name": "@codelab/firebase-login", "version": "0.0.1", "peerDependencies": { "@angular/common": "^7.1.0", "@angular/core": "^7.1.0" } } ================================================ FILE: libs/firebase-login/src/index.ts ================================================ export * from './lib'; ================================================ FILE: libs/firebase-login/src/lib/firebase-login.module.spec.ts ================================================ import { async, TestBed } from '@angular/core/testing'; import { FirebaseLoginModule } from './firebase-login.module'; describe('FirebaseLoginModule', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [FirebaseLoginModule] }).compileComponents(); })); it('should create', () => { expect(FirebaseLoginModule).toBeDefined(); }); }); ================================================ FILE: libs/firebase-login/src/lib/firebase-login.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { AngularFireAuthModule } from '@angular/fire/auth'; import { LoginWidgetComponent } from './login-widget/login-widget.component'; import { MatMenuModule } from '@angular/material/menu'; @NgModule({ imports: [CommonModule, AngularFireAuthModule, MatMenuModule], declarations: [LoginWidgetComponent], exports: [LoginWidgetComponent] }) export class FirebaseLoginModule {} ================================================ FILE: libs/firebase-login/src/lib/index.ts ================================================ export * from './firebase-login.module'; export * from './login.service'; ================================================ FILE: libs/firebase-login/src/lib/login-widget/login-widget.component.css ================================================ .link { text-decoration: underline; } .cursor-pointer { cursor: pointer; } .mb-0 { margin-bottom: 0; } .menu-bar-btn { font-size: 20px; } ================================================ FILE: libs/firebase-login/src/lib/login-widget/login-widget.component.html ================================================ 🗝
        {{ user.email }}!

        More features coming soon!

        To open an issue in feedback widget,

        please log in

        Login with Google
        ================================================ FILE: libs/firebase-login/src/lib/login-widget/login-widget.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { LoginWidgetComponent } from './login-widget.component'; import { FirebaseLoginModule, LoginService } from '@codelab/firebase-login'; describe('LoginWidgetComponent', () => { let component: LoginWidgetComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [FirebaseLoginModule], providers: [ { provide: LoginService, useValue: {} } ] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(LoginWidgetComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/firebase-login/src/lib/login-widget/login-widget.component.ts ================================================ import { Component } from '@angular/core'; import { LoginService } from '@codelab/firebase-login/src/lib/login.service'; @Component({ selector: 'codelab-login-widget', templateUrl: './login-widget.component.html', styleUrls: ['./login-widget.component.css'] }) export class LoginWidgetComponent { constructor(readonly loginService: LoginService) {} login() { this.loginService.loginWithGithub(); } logout() { this.loginService.logout(); } } ================================================ FILE: libs/firebase-login/src/lib/login.service.spec.ts ================================================ import { TestBed } from '@angular/core/testing'; import { LoginService } from './login.service'; import { AngularFireAuth } from '@angular/fire/auth'; import { of } from 'rxjs'; describe('LoginService', () => { beforeEach(() => TestBed.configureTestingModule({ providers: [ { provide: AngularFireAuth, useValue: { user: of({}) } } ] }) ); it('should be created', () => { const service: LoginService = TestBed.inject(LoginService); expect(service).toBeTruthy(); }); }); ================================================ FILE: libs/firebase-login/src/lib/login.service.ts ================================================ import { Injectable } from '@angular/core'; import { AngularFireAuth } from '@angular/fire/auth'; import { first, map } from 'rxjs/operators'; import { BehaviorSubject, Observable } from 'rxjs'; import { SyncStatus } from '@codelab/utils/src/lib/sync/common'; import { auth } from 'firebase/app'; @Injectable({ providedIn: 'root' }) export class LoginService { readonly user$ = this.auth.user; readonly isAnonymous$ = this.auth.user.pipe( map(u => (u ? u.isAnonymous : true)) ); readonly uid$: Observable = this.user$.pipe( map(user => user && user.uid) ); readonly preferredStatus$ = new BehaviorSubject(SyncStatus.ADMIN); constructor(private auth: AngularFireAuth) { this.user$.pipe(first()).subscribe(a => { if (!a) { this.anonymousLogin(); } }); } anonymousLogin() { return this.auth.auth .signInAnonymously() .then(() => console.log('successful login')) .catch(error => console.log(error)); } async logout() { await this.auth.auth.signOut(); this.anonymousLogin(); } loginWithGithub() { this.auth.auth.signInWithPopup(new auth.GithubAuthProvider()); } } ================================================ FILE: libs/firebase-login/src/test.ts ================================================ // This file is required by karma.conf.js and loads recursively all the .spec and framework files import 'zone.js/dist/zone'; import 'zone.js/dist/zone-testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; declare const require: any; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); // Then we find all the tests. const context = require.context('./', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context); ================================================ FILE: libs/firebase-login/tsconfig.lib.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "target": "es2015", "module": "es2015", "moduleResolution": "node", "declaration": true, "sourceMap": true, "inlineSources": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "importHelpers": true, "types": [], "lib": ["dom", "es2018"] }, "angularCompilerOptions": { "annotateForClosureCompiler": true, "skipTemplateCodegen": true, "strictMetadataEmit": true, "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "enableResourceInlining": true }, "exclude": ["src/test.ts", "**/*.spec.ts"] } ================================================ FILE: libs/firebase-login/tsconfig.spec.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "types": ["jasmine", "node"] }, "files": ["src/test.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: libs/firebase-login/tslint.json ================================================ { "extends": "../../tslint.json", "rules": { "directive-selector": [true, "attribute", "codelab", "camelCase"], "component-selector": [true, "element", "codelab", "kebab-case"] } } ================================================ FILE: libs/intro/README.md ================================================ # Angular Intro Presentation This module contains scripts that convert existing Google Slides [presentation](https://docs.google.com/presentation/d/1ecaXVe5qRS3YcphrTK9JVAaD1qNhLDkLhwgZIZTfNzw/edit) into an Angular based presentation. The primary purpose is to conditionally display subset of slides based on the requirements. The result is available at https:// TDO ## How it works 1. Using Google Slides API fetches list of slides from a presentation with ID 1ecaXVe5qRS3YcphrTK9JVAaD1qNhLDkLhwgZIZTfNzw 1. Authorization done as described in https://developers.google.com/drive/api/v3/quickstart/nodejs, tokens stored in TODO 1. Generates JSON slides metadata and thumbnails in /assets/slides ## Running the script npm run build:intro ================================================ FILE: libs/intro/generate/generate-thumbnails.ts ================================================ import { createWriteStream, existsSync, mkdirSync, outputJSON } from 'fs-extra'; import { resolve } from 'path'; import fetch from 'node-fetch'; import { ASSETS_SLIDES_PATH, SLIDES_METADATA_PATH } from './helpers/const'; import { getSlides$ } from './helpers/slides'; getSlides$.subscribe( async (contentUrls: string[]): Promise => { if (!existsSync(ASSETS_SLIDES_PATH)) { mkdirSync(ASSETS_SLIDES_PATH); } await Promise.all( contentUrls.map(async (url, i) => { const dest = resolve(ASSETS_SLIDES_PATH, `slide-${i}.png`); const writeStream = createWriteStream(dest); const response = await fetch(url); response.body.pipe(writeStream); }) ); const count = contentUrls.length; await outputJSON( SLIDES_METADATA_PATH, { count, contentUrls }, { spaces: 2 } ); console.log(`🚀 Generated ${count} thumbnails`); } ); ================================================ FILE: libs/intro/generate/helpers/const.ts ================================================ import { resolve } from 'path'; export const CODELAB_PRESENTATION_ID = '1ecaXVe5qRS3YcphrTK9JVAaD1qNhLDkLhwgZIZTfNzw'; export const SCOPE = ['https://www.googleapis.com/auth/presentations.readonly']; export const CREDENTIALS_PATH = resolve(__dirname, '../credentials.json'); export const TOKEN_PATH = resolve(__dirname, '../token.json'); export const SLIDES_METADATA_PATH = resolve(__dirname, '../slides.json'); export const ASSETS_SLIDES_PATH = resolve(__dirname, '../../assets/slides'); ================================================ FILE: libs/intro/generate/helpers/slides.ts ================================================ import { forkJoin, from, ObservableInput, of } from 'rxjs'; import { map, switchMap } from 'rxjs/operators'; import { CODELAB_PRESENTATION_ID } from './const'; import { gSlides$ } from './utils'; import { GSlides } from './types'; const getSlides = (gSlides: GSlides): ObservableInput => of(gSlides).pipe( switchMap(() => gSlides.presentations.get({ presentationId: CODELAB_PRESENTATION_ID }) ), map(response => response.data.slides.map(slide => slide.objectId)), switchMap(objectIds => { const thumbnailRequests: ObservableInput[] = objectIds.map( (pageObjectId: string): ObservableInput => from( gSlides.presentations.pages.getThumbnail({ presentationId: CODELAB_PRESENTATION_ID, pageObjectId }) ).pipe(map(response => response.data.contentUrl)) ); return forkJoin(thumbnailRequests); }) ); export const getSlides$ = gSlides$.pipe(switchMap(getSlides)); ================================================ FILE: libs/intro/generate/helpers/types.ts ================================================ import { slides_v1 } from 'googleapis/build/src/apis/slides/v1'; export type GSlides = slides_v1.Slides; export interface Credentials { installed: { client_id: string; project_id: string; auth_uri: string; token_uri: string; auth_provider_x509_cert_url: string; client_secret: string; redirect_uris: string[]; }; } export interface Token { access_token: string; refresh_token: string; scope: string; token_type: string; expiry_date: number; } ================================================ FILE: libs/intro/generate/helpers/utils.ts ================================================ import { outputJSON, readJSONSync, existsSync } from 'fs-extra'; import { google } from 'googleapis'; import { OAuth2Client } from 'googleapis-common'; import { createInterface, Interface } from 'readline'; import { bindCallback, bindNodeCallback, defer, from, iif, ObservableInput, of } from 'rxjs'; import { mapTo, shareReplay, switchMap, tap } from 'rxjs/operators'; import { Credentials, Token } from './types'; import { CREDENTIALS_PATH, SCOPE, TOKEN_PATH } from './const'; if (!existsSync(CREDENTIALS_PATH)) { throw new Error("credentials.json doesn't exist"); } const CREDENTIALS: Credentials = readJSONSync(CREDENTIALS_PATH); const { client_id, client_secret, redirect_uris } = CREDENTIALS.installed; const oAuth2Client: OAuth2Client = new google.auth.OAuth2( client_id, client_secret, redirect_uris[0] ); const isTokenJsonExists = existsSync(TOKEN_PATH); const TOKEN: Token | null = isTokenJsonExists ? readJSONSync(TOKEN_PATH) : null; const readline$ = defer(() => of( createInterface({ input: process.stdin, output: process.stdout }) ) ).pipe(shareReplay(1)); const getNewToken = (readline: Interface) => readline$.pipe( tap(() => { const authUrl = oAuth2Client.generateAuthUrl({ access_type: 'offline', scope: SCOPE }); console.log('Authorize this app by visiting this url:', authUrl); }), switchMap(() => bindCallback( readline.question.bind(readline, 'Enter the code from that page here: ') )() ), switchMap( (code): ObservableInput<[Token]> => bindNodeCallback(oAuth2Client.getToken.bind(oAuth2Client))(code) ), switchMap( ([token]): ObservableInput => from(outputJSON(TOKEN_PATH, token, { spaces: 2 })).pipe(mapTo(token)) ), tap(() => readline.close()) ); const getNewToken$ = readline$.pipe(switchMap(getNewToken)); const validateToken$ = iif( () => isTokenJsonExists, of(TOKEN), getNewToken$ ).pipe( tap(token => { oAuth2Client.setCredentials(token); }) ); export const gSlides$ = of( google.slides({ version: 'v1', auth: oAuth2Client }) ).pipe(switchMap(gSlides => validateToken$.pipe(mapTo(gSlides)))); ================================================ FILE: libs/intro/generate/slides.json ================================================ { "count": 30, "contentUrls": [ "https://lh3.googleusercontent.com/dZxroMUtUoXIyzzQfzLARGEMiPv7q3zxjE9zcmlXyU4suGll1h5ohTOoP-lunCUaxR8gLPP-oNVrvyIkrrMKs6s1loquKAnrHIU9J4Kl_KQavQMLwDGQ980KMWhQoEtxu9sAux_yW1G9_NkWplM7_kuG8dG7tGB2wqbajra0G9kfmrqIQRRbEZgc8fndACXzFE1Gr0uCp1Er2xRaegaEg8v2URYmlg=s1600", "https://lh4.googleusercontent.com/bnHw-_pX69a21SKEKrKMeOAdkFLKqMFVM-OQ48K8qQm5Gxv9QQZSy_LYJrnFchgq15FurKGGxVDrVyS6Nz3t_P7ITU9eenQXeGhI9GQfrIh_TU-F0nTs1eyzD6p3mBSkkIB-plR0qKptFcpYYw_Pg86hQhSDuDEidN771DlwqLk_DZpaXdc_Myx1Og9R7arAe_Bmpv4Y42zpjgiBvZ2h8bBnUZgsBQ=s1600", "https://lh6.googleusercontent.com/mRv_DVKOh5OI8MGInWu4IRp7CB8wt30PseXw3KX--R_6AqLmK5yxvFb4i77IHrfmVgE_OoHOb2_cI0fTEP4tEOdtLfeNlm_Ngc4ug220ks6n0s0s2vyrCHeCx6yie_O7L7vwm2YKL8YoGNlXPOZVxYGNAtgDavULhZMBcMP2QXsJyB4Z5RkorM5jZqZ1BPmeq_8TwD5_wq6q9QaZPUbSDhTKa0_X9A=s1600", "https://lh3.googleusercontent.com/--eYt2rtt5vjRJyXjdWNu044x9W91AeyqHL2ijcxARSIdDXYB2mIv4deNjQ4b_RtpF3ZCA0-3qUDhaE-Qb1-Ke6gcFLlRJOnkxgRDPOhPC6fANjIEcs5DCV53uXwRuPEIjCq5BcjodogyA2nZOo7NKnhSLOsqkqu78ouJjahMfdm0Y8KvwEwmGQ0EsxxwVn5wm3q0i5GcKV5be-RJhMEXfCAJ2NcCg=s1600", "https://lh5.googleusercontent.com/7XwtvU02urDFB7NtzpMwiXSHDSvhtQKNgIAEPvhM5jHUGv_3se71HenTs6SUg6ZFOZmOod5pgPC0-MMNgvu51wO11Y6OwA024eILNRQ20fNhyiDO4g6AyMVzrSFUsLua3XUvd9i-H74uVZ7PwoXbIfgWsFkC6So4hEE3lTcRBqncxVt6D4U9VtxHKDKAYXdIiovtYWoX3y82eUxWyb6nLieiw8a5Zw=s1600", "https://lh5.googleusercontent.com/oAhv_nXbLOu2sGh50kfLJ1e8AZuOtHspIiw3yz_Dq_iUDZqWF_Yf-ENHcGST07v5sRiH5II-8GdB83NUJdHSvC5CCoenn1G_zl4-Df2FrEq8jSkbIHjKvjaDmB6AfbLdS6zSUG8PrVKjZzsm47rKLFOS0t7J2NojWQ8Xzv4cFC6pZQtJP4I_Lfcq0iVHT-kk09K40QN1Fo5gsjegoMY7WOLt1BDFmg=s1600", "https://lh4.googleusercontent.com/HIs_tTel0jZcMwMB1VUz-1f9OUp7QHUFjLhEr6j6J-9-UPrpz-EUytAFu00FDT-iuXHSIDDpU6iVETsyQa1mfAvY-L_zTuwWVXpLYzM_Q-GTSQE926kF33cClxn1R5B3hPKhEL1QDApNjbB7cNkYbF9WvlLVEasuc7sa1GN2A0HMHfo0x97G3-NStGyBkDLp9Bbv6aSCBnMG_PXnyqmNuxUAl0wD2g=s1600", "https://lh4.googleusercontent.com/NReESqvo8GhNOudzBCQy4_-pBHhATsLnm_sVYXA1zQansVvadrXQ6cUUGKKEh8hgOOlV149J9vgjpqjO1y7yiTJ9pNh1KX7s0lrEdidcepjNV0MUy00OoVPx0_EB6ML2iqCnhdkYtShP5SWFftKvkL72A7HYqXmgi4_bzBy2fRKJqwD9xfezU5gVy3qCT8dlPhiMnskLQkjMgXRIFkbyiQ-yuNgdZw=s1600", "https://lh3.googleusercontent.com/ZnoJa-b8SLmRTCKkKitQxf5lOnVFdSiBEFxzUZENMpehbkpnKoONHdGJyGIRuLMrkdeB0xUOaWtuQOxcE-rjYHFyC4sJlxnjdOYeZt8mMAk8kxDZKIdD6vTqrTlDn6E3nHkT-GqfVa7MBRcgaTJ-ipot6o-h7m2zVvUuxzkWSuXXGc8C_Wye4ykTYTJuYfrzeaazNy40kA2QfbFrRhaup8i-aegSWQ=s1600", "https://lh3.googleusercontent.com/AhFIsujJel-IDJ42pIV9wBZseeMMPOXXBWkqXlvPrl0A8qJkMcqJ37ttTqiu3fyiRGLyGvJxTBVihM8AkiyD20wISl23G6JUoSkxOQR5cv2hfg57fkYq2gj2xtHsaedV4X1cPoig4n7xic5duSOQ0hrTlBoeDrokaNd7DABnftTUzOdEklgtJW7WcJoPMj2hl_pt2tXuDsq7ftB4R0qjzo7jjjFR3A=s1600", "https://lh6.googleusercontent.com/ctRjv_yU4l61oxFkND9qW7wybjY_ZkH0XAlaOsmK50VRAAjHTXJ3nsD_SqB9Ih6DYWMwBnZnVnUXI-lMkOsZZvL0wypU0_uCEPJX_Cn4pbuJ3xxOdo3YGWN4vf9-7zztYe6KVxxXAJ3OYWdNUc-NdlLSWHg8sduhpko6J0lW3DQEzt1vQ-nMrUwBCZ8LB8KLJqccb02HC2552q6FrhXtIFDZAaKfMg=s1600", "https://lh4.googleusercontent.com/6k9w0TxpR1v_OhZ8Ulgup7m0vb7jH6AmUhBeWuvGagyqNPpjElMv3XnLhwqn1Qv0CpdFNirpNS5XSHQKw25dhpcacHTxWDocq9mNVTvk03-eKka_bt1fns9RlJ1iy0XU0KSMMIXkavo3MM6bFmmM5NES-3nhSzzEtVvbxEHoMWfzrgZiUk5Jn8V67iHhooAgVXDAZlB4EV9qiZ7_bPEkf7EKYuDgcg=s1600", "https://lh4.googleusercontent.com/YVkDmp9JIfOtMpwzYy0Tm9Ae1l0apW8p0zOhlhF9MZc_vP8s38zC-Fu_2PICycKX4N27CQcTAgAEUkN8PNraYC6pk6ECdoByG3akiztQ1ZC0DLH-V0nQRK88qNtLK_kL-siVIAOvUkbLM3WkXQEaJ_V_uM3TUlke4av6gYAVtv0jOnrfKrz7MCfMqeldpZNX4T_uLx8pJC1h5SFazRQGEIDlixIkiQ=s1600", "https://lh5.googleusercontent.com/O6ZBdu12JixR_ntUgxzZxj7Ql72laYnQFIoaGLk-uvw8ff6yiFGIvoa2xvLy3BBIfugWkCnXowUZzryB4P6VnFZbLswBhEZvF2GS5yN36hIMiZIl8wHQF-O8KEpVtvVCk7vhzhYh3xpzqr7hljA1cOKVvpIFHLSZsjWrELNrCsTRE8hmfIiI-BKThaaFd_M8NA8jFSx6AIf9raSgX_rJuiBy-m0fmA=s1600", "https://lh3.googleusercontent.com/afWNZoZlC4Vgiu1m1IEMm69aIo8zTu9uq8bB2Q2mvmFW6yV_AfbOuzRE3F82Wd0NWfmYfIaBvJYNm53GWZXh7CekWp56-5tMzqZsSmnUo0dzSFytlSvop4_4brXD05yF6-C3SpEIoFgTbll6W9IvGfYBs5K1A_0eR-FNQSU0tpTyJhvBBEiSOJ0_qaEaz0IaYCGFsS9y6rq77d1LM5fdPCbhdbVn-w=s1600", "https://lh4.googleusercontent.com/jPPknOyMAbqY7smSVB9mCIJZMjNn29MhrZuKZ5RRQm3VxCmWt8TbXXhq8X5Q6hePpG_703GMzIopB6sGht1WykLwAmmE8rxVLlqA5JZPfVGxAtHUJR2f2MjK3O1XNO9_fFrfttdqo_LCYO4Dne_2shLTlwHMOTwrc0eauEdgKwcB5GzBJaU4PPG2IMpjI8Btatn3AHMDx_4yishVq6P8MHfn0aL_LA=s1600", "https://lh6.googleusercontent.com/aa6dOC9j0TyGTJjPCMIeov6VL2Bf544__rCf33Opmmip2ix-fjA5UZdxUbQVu9DU__thd84BGuKpdG6nS9yCaTgG5_CulcVj5WA9GahhaUXvD_NUUmvMNx4XPoEevYfDaNElCDz1rVryA2UkHYNe_bgf8h3J_P6eeaaCggQB4IORchXfLp1F2chJ_J7sG9M04fLnEm0QSkPqdD8Is0SlJ8ssmQvUcw=s1600", "https://lh3.googleusercontent.com/V8_AkBbUqS8MgLgP0ic98Yap_7umSPGUDZU-I1AN5kA-_ZD3yMAiH5bKuEi2nNefwtGRw6vMEHFw_GcS8VouCl2rjQURLVyCUvhaarKaFOG7D-oHulDJTlvPIpL_QxTIMAiG4ORV1ZmoFC6Dyh2jrChGvkqUqPDWFi0waNs-ORiS4aClh92Q4KLClNPTk4XE5hTqK7twvRYQByvzmQTl9ijJQzOS4Q=s1600", "https://lh3.googleusercontent.com/AoPAoID21J1vb1cMlTIEkM712jH3o_wbzFMqQ6IkYdCMz1uZoVjwST3fuqj8KUdSJ2_ip343XSxPpuWm6qwRm2lWrzjwLVaU6uyHGDzbbdf3RA3ROpjPaQD24v98-656Asn-vZnBUnDKtHAcrpzx6dW-29bCYs_WXbVWP7epzPl9fT9v-XkG1wcS91nFvkE8K5HUUv89Z0foQGGMjhjx5vXh6y3QKQ=s1600", "https://lh6.googleusercontent.com/vhvXAZQVXBMhSGagweeHpnkLdJcIJZ53oEfgiMyJkVq_PI93CBuaw6dkmV-yM6bFCTFoIdAkPJ37SUJMku8gotSJdgJQc9qK5Y15GOZ2qVgiQrx0kMH6y31nK3JXRbBM5wwJYnkPvu93PALBxGU0VMTbktaRoAYtsKiWXSeA3GyKMlUqrCRFgsMMUwx6LXCCAOzGaVpT3XuAE-jzMZ8tei5veVFAYA=s1600", "https://lh6.googleusercontent.com/Tbb3JkURK5Dm0Sqqf5u8C2R_gNBQUN5bsgquLyOB87dveVYqQjDuxjdIFha3G6wIk7XtwuzjqxdWKJeZ9wWi0exStOABTeYAxElt9PZOV6JjpIf-8QHZEgMiAGggEsm-DM2YZ2jIuD91r_G5rW9hcep_EDnYBnUx5k-nmqmodDRstz7LgziOMrK_PLH197Nh3D73lsqbf3E87QLNJdHX594qeTkQ8g=s1600", "https://lh4.googleusercontent.com/h5kSCoSShR4KLU6NOoHKInb2nozG1q8hHC2WA7DueDi_V814UCXK-AgGme367nngMRo5bA1rFbWnPOEuoAYv0kYRLgfFVoafaxjsOzAgeD-O8ohzh7DA0s6-fl9-zaoOXuC27UKBKnoQctUfXcDGKId8RLynxdy67LUhD3FfJ2Fx3eIQEk1nuC3YtSYeWUO0vK2SmABVtOi3LdzUlf-rYSGOjWPObA=s1600", "https://lh6.googleusercontent.com/H7QJqVte_rVfIFVJjQrawaX3i7CNhPCdn1MySL2yBEMlM6AWONsf1xR6Bzo3nFm5RpnVN8AsvK7dihwnPdrkRruljvGG5CGhiXmdqK62MPFGu16X1OLYPEvG-Mu4O2Q_ewLuKWLGo9fpZy8kPo11PFBp8ZoWLQ8Ix5YIImSKxUQWcTbJnlV40Hy98wG9oRQCyUpRIvte-0X8FUXpPxzdBDEPWt2xog=s1600", "https://lh5.googleusercontent.com/pUy3TLs6-liKbbfodUKsBfwYmbjODrRmd6fDtK9dFJqs_gPZRnUdrX7lbeuGF1Ziy5dg9l8B0B4QSkQ2DSBd_DB5Za_EFx17eeeyzwzAKkF3932KBltU_RecCT98SaIXltVnKMmvI6o2ARS7zPgiXlH-5NOJ3Za5WyK3jvlCCwv_B9EM6Td82M5YjkvbNtpVvS2280h9SsojFqFu8IXFF0WaVvUZGg=s1600", "https://lh6.googleusercontent.com/h8hzVc_OhKnJfqM0i1cm98oCygCDSQBwhQkiseTPvD9caWJkYCHlFHs8q2C5rACiGuuZt1jv2tD5kgWtX62HNxlcN9iZ4HEdXhMuD-OM0rEBEAu7DTgAtcoodgQMweGcvdQ63E_wjbbgSoT4jENrIBpiF3SinuImpfFTSys55WL2XKSA59KUFGegcSAOh69FUSToAwFfV1YVHQiJEpKtqL4v735DXA=s1600", "https://lh5.googleusercontent.com/4kg7ZQPi1a3XvDq2N_-4nsZGtPe5iU79qwTzBdQ1PyyMK6PcMVQ1zxBk-EHPlj2bcW9g6tQ2I_WE-KTh4cRn9xvYHccTndxif2stLcq-PYyKrmWa25wzBAoXHp_4EweyAeMZqffOcEGhJnhFw_Up1Xho-JdohhlJ3k9H_dsiQeCiurPiPXlJQMUhwToaLj7yoOIEjr60tn6R2TXOfsqNmXWTw62v6Q=s1600", "https://lh6.googleusercontent.com/FV388iZIxGLHOo8g5Eyx-eKWOnv_E7swNDERMJOw4GeOuU2RO51aQ4uvAvgof1kklTEB2y2M_SMgGYv_EG7uv64dUdmz_LjnkTPELY3vHeVwmch9G8u90g2jpg9RTAuhoAFSSWczwd1TpjDK-y7he_bW0CINfVYu8S8rKaQtVXNO0_i2n5TZaf9u4sY1B7n6hIc7yMn9sTPmaRIFz_ldYOLWRN5kvQ=s1600", "https://lh5.googleusercontent.com/ev4yR29jzJp4ltwDqRdVH8Fv22mFygdL2xoti8I3HAoRZSrYUyia_tKajgjK25LI-wQOZdPcMm2u0vNxOtq4x574kHDBKjtgvcuPEAZNOurQR06C5rSNumg6Qn_7TqXWxmbwxoCFv_tAOa1MNbYLZ3Kh8IqhgNj-mAkjEbGlyZAEfygynILG66Vot7EoGQvUJPQpZjH7AYqozuWvvlcssSw73tNbMw=s1600", "https://lh5.googleusercontent.com/Kv_gRVwP8cRRBXl5-X_oKPD2q99hAmZo5T5pK8HAj1uIzAGv8K0kWhFddweYdCwbU3XRGC6RnB9ESRE2EFRklshaj2Fl3AZczu1X_zx3rsparIdvYH3IQxWjuysbvXKGmf_k_8clR_7H1okuJGLZhupTC71e3Ukssb8BR-9qM7wC2hviFR7l3CNLfuEg9hD63G7-4syac72C_XL8AORnjJkteGl6sg=s1600", "https://lh6.googleusercontent.com/04n0oi2J281sD61sM4SR_y0S-e1MEhMdWwMMoO1x1rWUuGHd9G1qr1WdEb8l_lqUxUdhWMTzL-r8U2EAJaoE9EVUHdQDIGj_tb_KVob95NCY9URIi1bnrlEy7K33t4n79sifps5JXoXcROdH8A0SL1TMyNv0JbXOgt9hMff_H5sFeSNFzqN521SaNMO9M_iiVVy1uAHnE_JLMx_20JnAfMx5yr1zAQ=s1600" ] } ================================================ FILE: libs/intro/generate/tsconfig.json ================================================ { "compilerOptions": { "outDir": "./dist/out-tsc", "baseUrl": ".", "sourceMap": true, "declaration": false, "module": "commonjs", "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, "target": "es2017", "inlineSources": true, "allowJs": true, "importHelpers": true, "resolveJsonModule": true, "types": ["node"], "lib": ["dom", "esnext.array"] } } ================================================ FILE: libs/intro/jest.config.js ================================================ module.exports = { name: 'intro', preset: '../../jest.config.js', coverageDirectory: '../../coverage/libs/intro', snapshotSerializers: [ 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 'jest-preset-angular/build/AngularSnapshotSerializer.js', 'jest-preset-angular/build/HTMLCommentSerializer.js' ] }; ================================================ FILE: libs/intro/package.json ================================================ { "name": "@codelab/intro", "version": "0.1.0", "peerDependencies": { "@angular/common": "^9.0.0", "@angular/core": "^9.0.0" } } ================================================ FILE: libs/intro/src/index.ts ================================================ export { IntroModule } from './lib/intro.module'; ================================================ FILE: libs/intro/src/lib/intro.component.css ================================================ .image { margin: auto; height: 100%; } ================================================ FILE: libs/intro/src/lib/intro.component.html ================================================
        slide number {{ slide }}
        ================================================ FILE: libs/intro/src/lib/intro.component.ts ================================================ import { Component, OnInit } from '@angular/core'; import { count } from '../../generate/slides.json'; @Component({ selector: 'codelab-slides-intro', templateUrl: './intro.component.html', styleUrls: ['./intro.component.css'] }) export class IntroComponent implements OnInit { public presentation: number[]; public ngOnInit(): void { this.presentation = Array.from({ length: count }, (_, i) => i); } } ================================================ FILE: libs/intro/src/lib/intro.module.ts ================================================ import { NgModule } from '@angular/core'; import { RouterModule } from '@angular/router'; import { SlidesModule, SlidesRoutes } from '@ng360/slides'; import { FeedbackModule } from '@codelab/feedback'; import { CommonModule } from '@angular/common'; import { BrowserWindowModule } from '@codelab/browser'; import { CodeDemoModule } from '@codelab/code-demos'; import { FormsModule } from '@angular/forms'; import { IntroComponent } from './intro.component'; @NgModule({ imports: [ RouterModule.forChild([...SlidesRoutes.get(IntroComponent)]), FeedbackModule, CommonModule, CodeDemoModule, BrowserWindowModule, CodeDemoModule, SlidesModule, FormsModule ], declarations: [IntroComponent], exports: [IntroComponent] }) export class IntroModule {} ================================================ FILE: libs/intro/src/test-setup.ts ================================================ import 'jest-preset-angular'; ================================================ FILE: libs/intro/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "types": ["node", "jest"] }, "include": ["**/*.ts"] } ================================================ FILE: libs/intro/tsconfig.lib.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "target": "es2015", "declaration": true, "inlineSources": true, "types": [], "lib": ["dom", "es2018"] }, "angularCompilerOptions": { "skipTemplateCodegen": true, "strictMetadataEmit": true, "enableResourceInlining": true }, "exclude": ["src/test-setup.ts", "**/*.spec.ts"] } ================================================ FILE: libs/intro/tsconfig.lib.prod.json ================================================ { "extends": "./tsconfig.lib.json", "angularCompilerOptions": { "enableIvy": true } } ================================================ FILE: libs/intro/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "module": "commonjs", "types": ["jest", "node"] }, "files": ["src/test-setup.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: libs/intro/tslint.json ================================================ { "extends": "../../tslint.json", "rules": { "directive-selector": [true, "attribute", "codelab", "camelCase"], "component-selector": [true, "element", "codelab", "kebab-case"] } } ================================================ FILE: libs/live/README.md ================================================ # Live This library was generated with [Angular CLI](https://github.com/angular/angular-cli) version 7.2.0. ## Code scaffolding Run `ng generate component component-name --project live` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module --project live`. > Note: Don't forget to add `--project live` or else it will be added to the default project in your `angular.json` file. ## Build Run `ng build live` to build the project. The build artifacts will be stored in the `dist/` directory. ## Publishing After building your library with `ng build live`, go to the dist folder `cd dist/live` and run `npm publish`. ## Running unit tests Run `ng test live` to execute the unit tests via [Karma](https://karma-runner.github.io). ## Further help 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). ================================================ FILE: libs/live/jest.config.js ================================================ module.exports = { name: 'live', preset: '../../jest.config.js', coverageDirectory: '../../coverage/libs/live' }; ================================================ FILE: libs/live/ng-package.json ================================================ { "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", "dest": "../../dist/libs/live", "lib": { "entryFile": "src/index.ts" } } ================================================ FILE: libs/live/package.json ================================================ { "name": "@angular-presentation/live", "version": "0.0.1", "peerDependencies": { "@angular/common": "^7.2.0", "@angular/core": "^7.2.0" } } ================================================ FILE: libs/live/src/index.ts ================================================ export * from './lib/live-service.service'; ================================================ FILE: libs/live/src/lib/live-service.service.spec.ts ================================================ import { TestBed } from '@angular/core/testing'; import { LiveServiceService } from './live-service.service'; describe('LiveServiceService', () => { beforeEach(() => TestBed.configureTestingModule({})); it('should be created', () => { const service: LiveServiceService = TestBed.inject(LiveServiceService); expect(service).toBeTruthy(); }); }); ================================================ FILE: libs/live/src/lib/live-service.service.ts ================================================ import { Injectable, Input } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class LiveServiceService { @Input() user = 'pikachu'; @Input() host = 'pirojok'; @Input() session = 'live-share'; @Input() bucket = 'config'; constructor() {} } ================================================ FILE: libs/live/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "types": ["node", "jest"] }, "include": ["**/*.ts"] } ================================================ FILE: libs/live/tsconfig.lib.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "target": "es2015", "module": "es2015", "moduleResolution": "node", "declaration": true, "sourceMap": true, "inlineSources": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "importHelpers": true, "types": [], "lib": ["dom", "es2018"] }, "angularCompilerOptions": { "annotateForClosureCompiler": true, "skipTemplateCodegen": true, "strictMetadataEmit": true, "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "enableResourceInlining": true }, "exclude": ["src/test.ts", "**/*.spec.ts"] } ================================================ FILE: libs/live/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "module": "commonjs", "types": ["jest", "node"] }, "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: libs/live/tslint.json ================================================ { "extends": "../../tslint.json", "rules": { "directive-selector": [ true, "attribute", "angular-presentation", "camelCase" ], "component-selector": [ true, "element", "angular-presentation", "kebab-case" ] } } ================================================ FILE: libs/slides/README.md ================================================ # Angular Slides TODO ## How it works TODO ================================================ FILE: libs/slides/jest.config.js ================================================ module.exports = { name: 'slides', preset: '../../jest.config.js', coverageDirectory: '../../coverage/libs/slides', snapshotSerializers: [ 'jest-preset-angular/build/AngularNoNgAttributesSnapshotSerializer.js', 'jest-preset-angular/build/AngularSnapshotSerializer.js', 'jest-preset-angular/build/HTMLCommentSerializer.js' ] }; ================================================ FILE: libs/slides/ng-package.json ================================================ { "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", "dest": "../../dist/libs/slides", "lib": { "entryFile": "src/index.ts" } } ================================================ FILE: libs/slides/package.json ================================================ { "name": "@ng360/slides", "description": "Presentation creator for Angular", "version": "0.2.0", "repository": { "type": "git", "url": "https://github.com/codelab-fun/codelab.git" }, "homepage": "https://github.com/codelab-fun/codelab/tree/master/libs/slides#readme", "bugs": "https://github.com/codelab-fun/codelab/issues", "license": "MIT", "peerDependencies": { "@angular/common": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", "@angular/core": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", "@angular/router": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0", "tslib": "^1.10.0" }, "keywords": [ "angular", "slides", "presentation", "ng360", "codelab" ], "publishConfig": { "access": "public" } } ================================================ FILE: libs/slides/src/index.ts ================================================ export { FullScreenModeService } from './lib/full-screen-mode'; export { SlidesDeckComponent } from './lib/deck/deck.component'; export { SlidesRoutes } from './lib/routing/slide-routes'; export { SlidesModule } from './lib/slides.module'; ================================================ FILE: libs/slides/src/lib/arrows/slides-arrows.component.css ================================================ .arrow-left { border-top-right-radius: 40%; border-bottom-right-radius: 40%; left: 0; text-align: right; } .arrow-right { border-top-left-radius: 40%; border-bottom-left-radius: 40%; right: 0; text-align: left; } .arrow:hover, .arrow:focus { opacity: 0.5; } .arrow-left, .arrow-right { top: calc(44.7% - 6vh / 2); position: fixed; font-size: 6vh; z-index: 100; padding: 20px 5px; background: #ddd; cursor: pointer; line-height: 12vh; opacity: 0.1; } ================================================ FILE: libs/slides/src/lib/arrows/slides-arrows.component.html ================================================ ================================================ FILE: libs/slides/src/lib/arrows/slides-arrows.component.ts ================================================ import { Component, HostBinding } from '@angular/core'; import { SlidesDeckComponent } from '../deck/deck.component'; /** * Slide arrows are used by slides deck for navigation. */ @Component({ selector: 'slide-arrows', templateUrl: './slides-arrows.component.html', styleUrls: ['./slides-arrows.component.css'] }) export class SlidesArrowsComponent { /** * '.shortcuts-context' class is used by shortcuts * directive to distinguish slide navigation keyboard events * from editable field keyboard navigation. */ @HostBinding('class.shortcuts-context') context = true; constructor(private presentation: SlidesDeckComponent) {} goToPreviousSlide() { this.presentation.previousSlide(); } goToNextSlide() { this.presentation.nextSlide(); } canGoNext(): boolean { return this.presentation.canGoNext(); } canGoPrevious(): boolean { return this.presentation.canGoPrevious(); } } ================================================ FILE: libs/slides/src/lib/deck/deck.component.html ================================================
        ================================================ FILE: libs/slides/src/lib/deck/deck.component.scss ================================================ ::ng-deep { .has-milestone .btn-bar { display: none; } } :host { display: inline-block; height: 100%; width: 100%; position: relative; } :host ::ng-deep { .slide.slide [vertical-split], [vertical-split] .slide.slide { display: flex; flex-direction: row; > *:first-child { width: 49vw; margin-right: 1vw; } > *:nth-child(2) { width: 49vw; margin-left: 1vw; } } .theme-basic { font-family: 'Helvetica Neue', sans-serif; h1, h2, h3, h4, h5, h6, li:not(.action-item) { margin: 0; padding: 0; font-weight: 300; } b { color: #c04e22; font-weight: 300; background-color: #ffe; } h2, li:not(.action-item) { font-size: 3vw; margin-bottom: 1vw; } h1 { margin-bottom: 20px; } .info, .exercise { font-size: 2vw; } .header { font-size: 7vw; } .description { font-size: 3vw; } .instructions { font-size: 2vw; } } [vertical-flex-layout] { display: flex; flex-direction: column; [stretch] { } } .slides-layout { height: 100%; //TODO(kirjs): find a better way to assign a class name & > div { height: 100%; padding: 2vw; box-sizing: border-box; display: flex; flex-direction: column; position: relative; &[no-padding] { padding: 0; } } } } ================================================ FILE: libs/slides/src/lib/deck/deck.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SlidesModule } from '../slides.module'; import { SlidesDeckComponent } from '../deck/deck.component'; describe('Deck', () => { let component: SlidesDeckComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [SlidesModule] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SlidesDeckComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/slides/src/lib/deck/deck.component.ts ================================================ import { ChangeDetectorRef, Component, ContentChildren, EventEmitter, HostBinding, Input, Optional, Output, QueryList, TemplateRef } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; @Component({ selector: 'slide-deck', templateUrl: './deck.component.html', styleUrls: ['./deck.component.scss'] }) export class SlidesDeckComponent { slides: any[] = []; @Input() theme = 'basic'; @ContentChildren(TemplateRef) templates: QueryList>; activeSlideIndex = 0; @Output() slideChange = new EventEmitter(); @Output() slideAdded = new EventEmitter<{ index: number; id: string }>(); @HostBinding('class.has-milestone') hasMilestone = false; private milestone = ''; constructor( private readonly cdr: ChangeDetectorRef, @Optional() private readonly route: ActivatedRoute ) { if (route) { this.milestone = route.snapshot.queryParams.milestone; this.hasMilestone = !!this.milestone; } } addSlide(slide) { if (!this.milestone || this.milestone === slide.milestone) { this.slides.push(slide); } } goToSlide(index: number) { this.activeSlideIndex = index; this.slideChange.emit(index); this.cdr.markForCheck(); } nextSlide() { this.goToSlide(this.activeSlideIndex + 1); } previousSlide() { this.goToSlide(this.activeSlideIndex - 1); } canGoNext(): boolean { return this.activeSlideIndex + 1 < this.slides.length; } canGoPrevious(): boolean { return this.activeSlideIndex > 0; } } ================================================ FILE: libs/slides/src/lib/full-screen-mode/full-screen-mode.service.spec.ts ================================================ import { TestBed } from '@angular/core/testing'; import { FullScreenModeService } from './full-screen-mode.service'; describe('FullScreenModeService', () => { beforeEach(() => TestBed.configureTestingModule({})); it('should be created', () => { const service: FullScreenModeService = TestBed.inject( FullScreenModeService ); expect(service).toBeTruthy(); }); }); ================================================ FILE: libs/slides/src/lib/full-screen-mode/full-screen-mode.service.ts ================================================ import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class FullScreenModeService { constructor() {} toggleFullScreen() { // check if page is in fullscreen if (document['fullscreenElement']) { document.exitFullscreen(); } else { document.documentElement.requestFullscreen(); } } } ================================================ FILE: libs/slides/src/lib/full-screen-mode/index.ts ================================================ export * from './full-screen-mode.service'; ================================================ FILE: libs/slides/src/lib/routing/slide-routes.ts ================================================ export class SlidesRoutes { static get(Component: any) { return [ { path: '', redirectTo: '0', pathMatch: 'full' }, { path: ':id', component: Component }, { path: '**', component: Component } ]; } } ================================================ FILE: libs/slides/src/lib/routing/slides-routing.directive.spec.ts ================================================ import { TestBed } from '@angular/core/testing'; import { ActivatedRoute, Router } from '@angular/router'; import { SlidesDeckComponent } from '../deck/deck.component'; import { SlidesRoutingDirective } from './slides-routing.directive'; import { EventEmitter } from '@angular/core'; describe('SlidesRoutingDirective', () => { let directive: SlidesRoutingDirective; beforeEach(() => { const activatedRouteStub = { snapshot: { params: {} } }; const routerStub = { navigate: () => ({}), events: { pipe: () => ({ subscribe: () => ({}) }) } }; const slidesDeckComponentStub = { slides: [{ id: 1 }, { id: 2 }], goToSlide(n: number) { this.activeSlideIndex = n; this.slideChange.emit(n); }, slideChange: new EventEmitter(), activeSlideIndex: 0 }; TestBed.configureTestingModule({ providers: [ SlidesRoutingDirective, { provide: ActivatedRoute, useValue: activatedRouteStub }, { provide: Router, useValue: routerStub }, { provide: SlidesDeckComponent, useValue: slidesDeckComponentStub } ] }); directive = TestBed.inject(SlidesRoutingDirective); }); it('can load instance', () => { expect(directive).toBeTruthy(); }); xit('should call navigate on slideChange event', () => { const slidesDeckComponentStub: SlidesDeckComponent = TestBed.inject( SlidesDeckComponent ); spyOn(directive, 'getId'); slidesDeckComponentStub.slideChange.emit(10); // window.dispatchEvent(new Event('slideChange')); expect(directive.getId).toHaveBeenCalledTimes(1); expect(directive.getId).toHaveBeenCalledWith(10); }); describe('ngOnInit', () => { it('makes expected calls', () => { const slidesDeckComponentStub: SlidesDeckComponent = TestBed.inject( SlidesDeckComponent ); spyOn(slidesDeckComponentStub, 'goToSlide'); directive.ngOnInit(); expect(slidesDeckComponentStub.goToSlide).toHaveBeenCalled(); }); }); describe('getId', () => { it('should return the id prop of the slide at the given index', () => { const slidesDeckComponentStub: SlidesDeckComponent = TestBed.inject( SlidesDeckComponent ); slidesDeckComponentStub.slides = [ { id: 'typescript' }, { id: 'javascript' } ]; slidesDeckComponentStub.slides.forEach((slide, index, arr) => { const retrievedId = directive.getId(index); expect(retrievedId).toBe(slide.id); }); }); }); }); ================================================ FILE: libs/slides/src/lib/routing/slides-routing.directive.ts ================================================ import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; import { Directive, EventEmitter, HostListener, OnDestroy, OnInit, Output } from '@angular/core'; import { distinctUntilChanged, filter, map, takeUntil } from 'rxjs/operators'; import { Subject } from 'rxjs'; import { SlidesDeckComponent } from '../deck/deck.component'; @Directive({ // tslint:disable-next-line:all TODO: Fix linter warnings on the selector and delete this comment. selector: '[slidesRouting]' }) export class SlidesRoutingDirective implements OnInit, OnDestroy { activeSlideId: string; @Output() change = new EventEmitter(); private ids: { [index: number]: string } = {}; private ngUnsubscribe$ = new Subject(); constructor( private router: Router, private route: ActivatedRoute, private deck: SlidesDeckComponent ) { this.handleBackButtonSlideIndexSync(); } @HostListener('slideChange', ['$event']) slideChange(index) { this.navigate(this.getId(index)); } ngOnInit() { const index2 = this.getIndexFromRouteParam(); this.deck.goToSlide(isNaN(index2) ? 0 : index2); } ngOnDestroy(): void { this.ngUnsubscribe$.next(); this.ngUnsubscribe$.complete(); } getId(index: number) { return this.deck.slides && this.deck.slides[index] && this.deck.slides[index].id ? this.deck.slides[index].id : index; } navigate(url: string) { this.router.navigate(['../' + url], { relativeTo: this.route, queryParamsHandling: 'merge' }); } getIndexFromRouteParam() { const id: string = this.route.snapshot.params['id']; const index = this.deck.slides.findIndex(s => s.id === id); // TODO(kirjs): Clean this up return Number(index === -1 ? id : index); } /** * This is to ensure that the activeSlideIndex of the deck * is always reflective of the router's path for the slide. * * The {@link SlidesDeckComponent} component changes slides only * when the methods to change slide are called. However, this is * only true when {@link SlidesArrowsComponent} invokes it. We * therefore have an issue when the user uses the browser's back * button behavior, which the deck and the arrow does not handle. */ private handleBackButtonSlideIndexSync() { this.router.events .pipe( filter(event => event instanceof NavigationEnd), map(() => this.getIndexFromRouteParam()), distinctUntilChanged(), takeUntil(this.ngUnsubscribe$) ) .subscribe(slide => { this.deck.goToSlide(slide); }); } } ================================================ FILE: libs/slides/src/lib/shortcuts/shortcuts.directive.spec.ts ================================================ import { ShortcutsDirective } from './shortcuts.directive'; describe('ShortcutsDirective', () => { it('should create an instance', () => { // TODO: Write this test. // const directive = new ShortcutsDirective(); // expect(directive).toBeTruthy(); }); }); ================================================ FILE: libs/slides/src/lib/shortcuts/shortcuts.directive.ts ================================================ import { Directive, HostListener, Optional } from '@angular/core'; import { FullScreenModeService } from '../full-screen-mode'; import { SlidesDeckComponent } from '../deck/deck.component'; @Directive({ selector: '[slideShortcuts]' }) export class ShortcutsDirective { constructor( @Optional() private deck: SlidesDeckComponent, private fullScreenService: FullScreenModeService ) {} @HostListener('window:keydown.ArrowRight', ['$event.target']) @HostListener('window:keydown.PageDown', ['$event.target']) next(target) { if (this.canNavigate(target) && this.deck.canGoNext()) { this.deck.nextSlide(); } } @HostListener('window:keydown.ArrowLeft', ['$event.target']) @HostListener('window:keydown.PageUp', ['$event.target']) previous(target) { if (this.canNavigate(target) && this.deck.canGoPrevious()) { this.deck.previousSlide(); } } @HostListener('window:keydown.alt.enter', ['$event']) fullScreenModeToggle(e) { // prevent page reload e.preventDefault(); this.fullScreenService.toggleFullScreen(); } private canNavigate(target: HTMLElement) { return target === document.body || this.isFromContext(target); } /** * Limit keyboard shortcut events to scoped containers * to prevent slide navigation when using keyboard arrows to * navigate an editable field like code editor. * Add '.shortcuts-context' to any component or container containig * focusable elements used for slide navigation. */ private isFromContext(target: HTMLElement) { let parent = target; while (parent) { if (parent.classList.contains('shortcuts-context')) { return true; } parent = parent.parentElement; } return false; } } ================================================ FILE: libs/slides/src/lib/slide/slide.directive.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SlidesModule } from '../slides.module'; import { SlidesDeckComponent } from '../deck/deck.component'; import { SlideDirective } from './slide.directive'; describe('SlideDirective', () => { let fixture: ComponentFixture; let directive: SlidesDeckComponent; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [SlidesModule] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SlidesDeckComponent); directive = fixture.componentInstance; fixture.detectChanges(); }); it('should create an instance', () => { expect(directive).toBeTruthy(); }); }); ================================================ FILE: libs/slides/src/lib/slide/slide.directive.ts ================================================ import { Directive, Input, TemplateRef } from '@angular/core'; import { SlidesDeckComponent } from '../deck/deck.component'; const ID_ATTR_NAME = 'id'; const MILESTONE_ATTR_NAME = 'milestone'; @Directive({ selector: '[slide]' }) export class SlideDirective { @Input() class; constructor( private presentation: SlidesDeckComponent, private template: TemplateRef ) { let slide; // TODO: Move milestone extraction to the codelab. // Ivy if ((template as any)._declarationTContainer) { const attrs = (template as any)._declarationTContainer.attrs || []; const indexPredicate = n => n === ID_ATTR_NAME; const idIndex = attrs.findIndex(indexPredicate); const milestoneIndex = attrs.findIndex(indexPredicate); slide = { id: idIndex !== -1 ? attrs[idIndex + 1] : undefined, milestone: milestoneIndex !== -1 ? attrs[milestoneIndex + 1] : undefined, template }; // Old renderer } else { const attrs = (template as any)._def.element.template.nodes[0].element .attrs; const attrPredicate = ([_, name]: string[]): boolean => name === ID_ATTR_NAME; const idAttr = attrs.find(attrPredicate); const milestoneAttr = attrs.find(attrPredicate); slide = { id: idAttr && idAttr[2], milestone: milestoneAttr && milestoneAttr[2], template }; } presentation.addSlide(slide); } } ================================================ FILE: libs/slides/src/lib/slides.module.spec.ts ================================================ import { async, TestBed } from '@angular/core/testing'; import { SlidesModule } from './slides.module'; describe('SlidesModule', () => { beforeEach(async(() => { TestBed.configureTestingModule({ imports: [SlidesModule] }).compileComponents(); })); it('should create', () => { expect(SlidesModule).toBeDefined(); }); }); ================================================ FILE: libs/slides/src/lib/slides.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { RouterModule } from '@angular/router'; import { SlidesDeckComponent } from './deck/deck.component'; import { SlideDirective } from './slide/slide.directive'; import { ShortcutsDirective } from './shortcuts/shortcuts.directive'; import { SlidesRoutingDirective } from './routing/slides-routing.directive'; import { SlidesArrowsComponent } from './arrows/slides-arrows.component'; @NgModule({ declarations: [ SlidesDeckComponent, SlideDirective, ShortcutsDirective, SlidesArrowsComponent, SlidesRoutingDirective ], exports: [ SlidesDeckComponent, SlideDirective, ShortcutsDirective, SlidesArrowsComponent, SlidesRoutingDirective ], imports: [CommonModule, RouterModule] }) export class SlidesModule {} ================================================ FILE: libs/slides/src/test-setup.ts ================================================ import 'jest-preset-angular'; ================================================ FILE: libs/slides/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "types": ["node", "jest"] }, "include": ["**/*.ts"] } ================================================ FILE: libs/slides/tsconfig.lib.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "target": "es2015", "declaration": true, "inlineSources": true, "types": [], "lib": ["dom", "es2018"] }, "angularCompilerOptions": { "skipTemplateCodegen": true, "strictMetadataEmit": true, "enableResourceInlining": true }, "exclude": ["src/test-setup.ts", "**/*.spec.ts"] } ================================================ FILE: libs/slides/tsconfig.lib.prod.json ================================================ { "extends": "./tsconfig.lib.json", "angularCompilerOptions": { "enableIvy": false } } ================================================ FILE: libs/slides/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "module": "commonjs", "types": ["jest", "node"] }, "files": ["src/test-setup.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: libs/slides/tslint.json ================================================ { "extends": "../../tslint.json", "rules": { "directive-selector": [true, "attribute", "slide", "camelCase"], "component-selector": [true, "element", "slide", "kebab-case"] } } ================================================ FILE: libs/utils/karma.conf.js ================================================ module.exports = function(config) { config.set({ basePath: '../', frameworks: ['jasmine', '@angular-devkit/build-angular'], plugins: [ require('karma-jasmine'), require('karma-chrome-launcher'), require('karma-jasmine-html-reporter'), require('karma-coverage-istanbul-reporter'), require('@angular-devkit/build-angular/plugins/karma') ], client: { clearContext: false // leave Jasmine Spec Runner output visible in browser }, coverageIstanbulReporter: { dir: require('path').join(__dirname, '../coverage'), reports: ['html', 'lcovonly'], fixWebpackSourcePaths: true }, reporters: ['progress', 'kjhtml'], port: 9876, colors: true, logLevel: config.LOG_INFO, autoWatch: true, browsers: ['Chrome'], singleRun: false }); }; ================================================ FILE: libs/utils/src/index.ts ================================================ export * from './lib'; ================================================ FILE: libs/utils/src/lib/analytics/analytics.service.spec.ts ================================================ import { inject, TestBed } from '@angular/core/testing'; import { AnalyticsService } from './analytics.service'; // TODO(kirjs): Uncomment xdescribe('AnalyticsService', () => { beforeEach(() => { TestBed.configureTestingModule({ providers: [AnalyticsService] }); }); it('should ...', inject([AnalyticsService], (service: AnalyticsService) => { expect(service).toBeTruthy(); })); }); ================================================ FILE: libs/utils/src/lib/analytics/analytics.service.ts ================================================ import { Injectable, Optional } from '@angular/core'; import { Event as RouterEvent, NavigationEnd, NavigationError, Router } from '@angular/router'; import { distinctUntilChanged } from 'rxjs/operators'; export declare const ga; @Injectable() export class AnalyticsService { ga: ( action: string, type: string, eventCategory?: string, eventAction?: string, timing?: number | string, extra?: string ) => void; constructor(@Optional() router: Router) { this.ga = window['ga'] || (() => 0); if (router) { router.events.subscribe(a => { if (a instanceof NavigationError) { console.log(a); throw new Error('Navigation error'); } }); router.events .pipe( distinctUntilChanged((previous: any, current: RouterEvent) => { if (current instanceof NavigationEnd) { return previous.url === current.url; } return true; }) ) .subscribe((x: NavigationEnd) => { this.ga('set', 'page', x.url); this.ga('send', 'pageview'); }); } } public sendEvent( eventCategory: string, eventAction: string, eventLabel: string ) { this.ga('send', 'event', eventCategory, eventAction, eventLabel); } public sendTiming( eventCategory: string, eventAction: string, timing: number, path: string ) { this.ga('send', 'timing', eventCategory, eventAction, timing, path); } } ================================================ FILE: libs/utils/src/lib/differ/diffFilesResolver.ts ================================================ import { differ } from './differ'; import { evaled, hidden, justForReference, test } from './fileHelpers'; import { FileConfig } from '../../../../../apps/codelab/src/app/shared/interfaces/file-config'; interface Override { [key: string]: { [key: string]: string; }; } interface Overrides { file: Override; stage: Override; } export class DiffFilesResolver { constructor( private files: { [key: string]: string }, private stages: Array, private overrides: Overrides ) {} resolve(stage: string, files) { const result = []; const bootstrap = (files.bootstrap || []) as Array; if (files.exercise) { files.exercise.forEach(file => { result.push( evaled( this.getFileCodeForStage(file, stage, bootstrap.indexOf(file) >= 0) ) ); }); } if (files.reference) { files.reference.forEach(file => { result.push( ...justForReference( this.getFileCodeForStage(file, stage, bootstrap.indexOf(file) >= 0) ) ); }); } if (files.hidden) { files.hidden.forEach(file => { result.push( ...hidden( this.getFileCodeForStage(file, stage, bootstrap.indexOf(file) >= 0) ) ); }); } if (files.test) { files.test.forEach(file => { result.push( ...test( this.getFileCodeForStage(file, stage, bootstrap.indexOf(file) >= 0) ) ); }); } return result; } getFileByPath(path: string) { if (!this.files[path]) { // tslint:disable-next-line debugger; throw new Error('Incorrect path:' + path); } return this.files[path]; } getFileCodeForStage( path: string, stage: string, bootstrap: boolean ): FileConfig { const type = path.substr(path.lastIndexOf('.') + 1); stage = (this.overrides.stage[path] && this.overrides.stage[path][stage]) || stage; // Using overrides path, but keeping the original path for display purposes. // TODO: This get broken if files are ind different folders const diffs = differ( this.getFileByPath( (this.overrides.file[path] && this.overrides.file[path][stage]) || path ), this.stages ); if (type === 'ts') { return { bootstrap: bootstrap, excludeFromTesting: bootstrap, type: 'typescript', path, template: diffs[stage], moduleName: path.replace('.ts', ''), code: diffs[stage], solution: diffs[stage + 'Solved'] }; } else { return { type, path, code: diffs[stage], template: diffs[stage], solution: diffs[stage + 'Solved'] }; } } } ================================================ FILE: libs/utils/src/lib/differ/differ.spec.ts ================================================ import { differ } from './differ'; describe('differ', () => { it('returns the text the way it is, if there are no special tags.', () => { const commits = differ('hi', ['first', 'second']); expect(commits['initial']).toEqual('hi'); expect(commits['first']).toEqual('hi'); expect(commits['second']).toEqual('hi'); }); it('Progressively adds the text for a single commit.', () => { const commits = differ('hi/*Sd:first*/, world!/*/d*/', ['first']); expect(commits['initial']).toEqual('hi'); expect(commits['first']).toEqual('hi, world!'); }); it('Works for multiline strings', () => { const commits = differ( `hi/*d:first*/, wor ld!/*/d*/`, ['first'] ); expect(commits['initial']).toEqual('hi'); expect(commits['first']).toEqual(`hi, wor ld!`); }); it('Progressively adds the text for multiple commits.', () => { const commits = differ( `/*d:first*/bu/*/d*//*d:second*/ra/*/d*//*d:third*/ti/*/d*//*d:forth*/no/*/d*/`, ['first', 'second', 'third', 'forth'] ); expect(commits['initial']).toEqual(''); expect(commits['first']).toEqual('bu'); expect(commits['second']).toEqual('bura'); expect(commits['third']).toEqual('burati'); expect(commits['forth']).toEqual('buratino'); }); // TODO(kirjs): Nested diffs sound like a fun idea, maybe I'll implement them later. xit('Disallows nested diffs, and throws if they are present', () => { const commits = differ(`/*d:first*/AA/*d:second*/OO/*/d*/AA/*/d*/`, [ 'first', 'second' ]); expect(commits['initial']).toEqual(''); expect(commits['first']).toEqual('AAAA'); expect(commits['second']).toEqual('AAOOAA'); }); it('Throws if not all commits have been used.', () => { expect(() => differ(`/*d:first*/AAOO/*/d*/OO/*d:second*/AA/*/d*/`, ['first']) ).toThrow(); }); it('Allows multi-line tags for nicer formatting', () => { const commits = differ( `hi/* d:first */, world!/*/d*/`, ['first'] ); expect(commits['initial']).toEqual('hi'); expect(commits['first']).toEqual('hi, world!'); }); it('Allows to specify a range of commits.', () => { const commits = differ( `/*d:first*/bu/*/d*//*d:second:second*/ra/*/d*//*d:third*/ti/*/d*//*d:forth*/no/*/d*/`, ['first', 'second', 'third', 'forth'] ); expect(commits['initial']).toEqual(''); expect(commits['first']).toEqual('bu'); expect(commits['second']).toEqual('bura'); expect(commits['third']).toEqual('buti'); expect(commits['forth']).toEqual('butino'); }); it('Allows to specify a range of commits for initial', () => { const commits = differ(`/*d:initial:initial*/hi/*/d*//*d:last*/bye/*/d*/`, [ 'first', 'second', 'third', 'last' ]); expect(commits['initial']).toEqual('hi'); expect(commits['second']).toEqual(''); expect(commits['third']).toEqual(''); expect(commits['last']).toEqual('bye'); }); it('Allows to specify a wider range of commits.', () => { const commits = differ( `/*d:first*/bu/*/d*//*d:second:third*/ra/*/d*//*d:third*/ti/*/d*//*d:forth*/no/*/d*/`, ['first', 'second', 'third', 'forth'] ); expect(commits['initial']).toEqual(''); expect(commits['first']).toEqual('bu'); expect(commits['second']).toEqual('bura'); expect(commits['third']).toEqual('burati'); expect(commits['forth']).toEqual('butino'); }); it('Keeps existing version for the unknown commits.', () => { const commits = differ( `/*d:first*/bu/*/d*//*d:second:third*/ra/*/d*//*d:third*/ti/*/d*//*d:forth*/no/*/d*/`, ['first', 'second', 'secondandahalf', 'third', 'forth'] ); expect(commits['initial']).toEqual(''); expect(commits['first']).toEqual('bu'); expect(commits['second']).toEqual('bura'); expect(commits['secondandahalf']).toEqual('bura'); expect(commits['third']).toEqual('burati'); expect(commits['forth']).toEqual('butino'); }); }); ================================================ FILE: libs/utils/src/lib/differ/differ.ts ================================================ function assertPositive(number, error) { if (number < 0) { throw new Error(error); } return number; } export function differ(file, commits) { return ['initial'] .concat(commits) .concat('neverShow') .reduce((result, commit) => { result.push(commit + 'Pre'); result.push(commit); result.push(commit + 'Solved'); return result; }, []) .reduce((result, commit, index, arr) => { // tslint:disable-next-line:max-line-length TODO: Can this regex be shortened and this comment removed? result[commit] = file.replace( /\/\*[\r\n\s]*d:([a-z]+)(:[a-z]+)?(?:\/(trimBoth|trimLeading|trimTrailing))?[\r\n\s]*\*\/([\n\s]*)((?:.|\n|\r)*?)([\r\n\s]*)\/\*\/d\*\//gi, function( match, from, to, trim: 'trimBoth' | 'trimLeading' | 'trimTrailing', spaceLeading, value, spaceTrailing ) { if (trim === 'trimBoth' || trim === 'trimLeading') { spaceLeading = ''; } if (trim === 'trimBoth' || trim === 'trimTrailing') { spaceTrailing = ''; } const fromIndex = assertPositive( arr.indexOf(from), `[Differ] Invalid commit: ${from}` ); const toIndex = to ? assertPositive( arr.indexOf(to.substr(1)), `[Differ] Invalid commit: ${to.substr(1)}` ) : arr.length; return index >= fromIndex && index <= toIndex ? spaceLeading + value + spaceTrailing : ''; } ); return result; }, {}); } ================================================ FILE: libs/utils/src/lib/differ/fileHelpers.ts ================================================ import { FileConfig } from '../../../../../apps/codelab/src/app/shared/interfaces/file-config'; export function hidden(...files: FileConfig[]): FileConfig[] { return files.map(file => Object.assign({}, file, { hidden: true })); } export function readOnly(...files: FileConfig[]): FileConfig[] { return files.map(file => Object.assign({}, file, { readonly: true })); } export function justForReference(...files: FileConfig[]): FileConfig[] { return collapsed(...readOnly(...files)); } export function collapsed(...files: FileConfig[]): FileConfig[] { return files.map(file => Object.assign({}, file, { collapsed: true })); } export function test(...files: FileConfig[]): FileConfig[] { return files.map(file => Object.assign({}, file, { excludeFromTesting: false, test: true, bootstrap: true, before: 'mochaBefore();', after: 'mochaAfter();', hidden: true }) ); } export function evaled(file) { return Object.assign(file, { after: `export function evalJs( js ){ return eval(js);}` }); } ================================================ FILE: libs/utils/src/lib/github-PR-service/github.module.ts ================================================ import { NgModule } from '@angular/core'; import { HttpClientModule } from '@angular/common/http'; @NgModule({ imports: [HttpClientModule] }) export class GithubModule {} ================================================ FILE: libs/utils/src/lib/github-PR-service/github.service.ts ================================================ import { Injectable } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { AngularFireAuth } from '@angular/fire/auth'; import * as firebase from 'firebase/app'; import { AngularFireDatabase } from '@angular/fire/database'; @Injectable({ providedIn: 'root' }) export class GithubService { repo = 'codelab-fun/codelab'; githubAuth; constructor( private http: HttpClient, private afAuth: AngularFireAuth, private database: AngularFireDatabase ) { afAuth.authState.subscribe(authData => { if (authData === null) { this.login(); } else { this.githubAuth = authData; } }); } async login() { const provider = new firebase.auth.GithubAuthProvider().addScope('repo'); this.githubAuth = await this.afAuth.auth.signInWithPopup(provider); } // TODO clean up 'createIssue' and 'createClosedIssue' methods as 60% of code is the same async createIssue(message) { if (!this.githubAuth.credential) { await this.login(); } const issueData = { title: message.comment, body: this.generateIssueBody(message) }; const accessToken = this.githubAuth.credential.accessToken; const headers = { Authorization: 'token ' + accessToken }; const options = { headers }; this.http .post( `https://api.github.com/repos/${this.repo}/issues`, issueData, options ) .subscribe((responseData: any) => { this.markAsDone(message); this.database .object(`feedback/${message.key}`) .update({ url: responseData.html_url }); window.open(responseData.html_url); }); } async createClosedIssue(message, reason) { if (!this.githubAuth.credential) { await this.login(); } const issueData = { title: reason + ' ' + message.comment, body: this.generateIssueBody(message) }; const accessToken = this.githubAuth.credential.accessToken; const headers = { Authorization: 'token ' + accessToken }; const options = { headers }; this.http .post( `https://api.github.com/repos/${this.repo}/issues`, issueData, options ) .subscribe((responseData: any) => { console.log(responseData.html_url); this.database .object(`feedback/${message.key}`) .update({ url: responseData.html_url }); const changes = { state: 'closed' }; const issueId = responseData.number; const headers = { Authorization: 'token ' + accessToken }; const options = { headers }; this.http .patch( `https://api.github.com/repos/${this.repo}/issues/${issueId}`, changes, options ) .subscribe(res => { if (res) { this.markAsDone(message); } }); }); } generateIssueBody(message) { return `# What the issue is about ${message.comment} # Where to start? Start with reproducing [Locally](http://localhost:4200${message.href}) # Metadata: This was filed through the feedback form. Author: ${message.name} Date: ${message.timestamp} Slide: [Local](http://localhost:4200${message.href}),[Prod](https://codelab.fun${message.href})`; } markAsDone(message) { this.database .object(`feedback/${message.key}`) .update({ isDone: !message.isDone }); } } ================================================ FILE: libs/utils/src/lib/github-PR-service/index.ts ================================================ export * from './github.module'; export * from './github.service'; ================================================ FILE: libs/utils/src/lib/i18n/i18n-tools.ts ================================================ export function extractMessages({ nativeElement }): { [key: string]: string } { return Array.from(nativeElement.children as Array).reduce( (result, el) => { result[el.getAttribute('id')] = el.innerHTML; return result; }, {} ); } ================================================ FILE: libs/utils/src/lib/index.ts ================================================ export * from './github-PR-service/index'; ================================================ FILE: libs/utils/src/lib/loaders/loaders.ts ================================================ declare const require; import * as ts from 'typescript'; /** * Unfortunately when doing: * 'require * as ts from 'typescript'; for the actual code, * webpack tries to ull source-maps and other node deps whichare not needed. * * It requires extra configuration and pollutes the terminal. * * To avoid it we just require and eval raw code. * This is ugly, but as TS is used in very few places, better than polluting the console. * */ export function getTypeScript(): typeof ts { const w = window as any; if (w.ts) { return w.ts; } // Used inside of eval. Typescript will think it's a real module and populate exports. const module = { exports: {} }; eval(require('!!raw-loader!typescript')); w.ts = module.exports; return w.ts; } ================================================ FILE: libs/utils/src/lib/loading-indicator/loading-indicator/loading-indicator.component.css ================================================ mat-icon.running { animation: rotation 1s infinite linear; } @keyframes rotation { from { transform: rotate(0deg); } to { transform: rotate(359deg); } } ================================================ FILE: libs/utils/src/lib/loading-indicator/loading-indicator/loading-indicator.component.html ================================================ refresh ================================================ FILE: libs/utils/src/lib/loading-indicator/loading-indicator/loading-indicator.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { LoadingIndicatorComponent } from './loading-indicator.component'; describe('LoadingIndicatorComponent', () => { let component: LoadingIndicatorComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [LoadingIndicatorComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(LoadingIndicatorComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/loading-indicator/loading-indicator/loading-indicator.component.ts ================================================ import { Component } from '@angular/core'; @Component({ // tslint:disable-next-line:component-selector selector: 'slides-loading-indicator', templateUrl: './loading-indicator.component.html', styleUrls: ['./loading-indicator.component.css'] }) export class LoadingIndicatorComponent {} ================================================ FILE: libs/utils/src/lib/loading-indicator/loading-indicator.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { LoadingIndicatorComponent } from './loading-indicator/loading-indicator.component'; import { MatIconModule } from '@angular/material/icon'; @NgModule({ declarations: [LoadingIndicatorComponent], exports: [LoadingIndicatorComponent], imports: [CommonModule, MatIconModule] }) export class LoadingIndicatorModule {} ================================================ FILE: libs/utils/src/lib/pipes/pipes.module.ts ================================================ import { NgModule } from '@angular/core'; import { SafeHtml } from './safeHtml.pipe'; @NgModule({ declarations: [SafeHtml], exports: [SafeHtml] }) export class SharedPipeModule {} ================================================ FILE: libs/utils/src/lib/pipes/safeHtml.pipe.ts ================================================ import { DomSanitizer } from '@angular/platform-browser'; import { Pipe, PipeTransform } from '@angular/core'; @Pipe({ name: 'safeHtml' }) export class SafeHtml implements PipeTransform { constructor(private readonly sanitizer: DomSanitizer) {} transform(html) { return this.sanitizer.bypassSecurityTrustHtml(html); } } ================================================ FILE: libs/utils/src/lib/sandbox-runner/common.spec.ts ================================================ import { testReducer } from '../sandbox-runner/common'; fdescribe('Test reducer', () => { const data = { tests: [ { name: 'hi', pass: undefined }, { name: 'bye', pass: undefined } ] }; it('sets initial state', () => { const result = testReducer( { tests: [] }, { type: 'reset', data: ['hi', 'bye'] } ); expect(result).toEqual(data); }); it('sets passed state', () => { const result = testReducer(data, { type: 'result', data: { name: 'hi', pass: true } }); expect(result.tests[0].pass).toEqual(true); expect(result.tests[1].pass).toEqual(undefined); }); it('sets an error', () => { const result = testReducer(data, { type: 'error', data: 'lol' }); expect(result.tests).toEqual(data.tests); expect(result.error).toEqual('lol'); }); }); ================================================ FILE: libs/utils/src/lib/sandbox-runner/common.ts ================================================ import produce from 'immer'; import { TestRunResult } from '../test-results/common'; interface TestAction { type: 'terminate' | 'reset' | 'result' | 'error'; data: any; } export const testReducer = produce( (data: TestRunResult = { tests: [] }, action: TestAction): TestRunResult => { if (action.type === 'reset') { return { tests: action.data.map(name => ({ name, pass: undefined })) }; } if (action.type === 'result') { const index = data.tests.findIndex( test => action.data.name === test.name ); if (index === -1) { throw new Error('Test missing'); } data.tests[index] = { ...data.tests[index], ...action.data }; return data; } if (action.type === 'error') { data.error = action.data; return data; } return data; } ); ================================================ FILE: libs/utils/src/lib/sandbox-runner/runners/runner.ts ================================================ import { Observable } from 'rxjs'; export interface Runner { run: (code: string) => void; destroy: () => void; result$: Observable; } ================================================ FILE: libs/utils/src/lib/sandbox-runner/runners/webworker.spec.ts ================================================ import { WebworkerRunner } from '@codelab/utils/src/lib/sandbox-runner/runners/webworker'; import { Observable } from 'rxjs'; function listenToObservable(obs: Observable) { const value: Array = []; let error: T | undefined; let isComplete = false; const sub = obs.subscribe( a => value.push(a), e => (error = e), () => (isComplete = true) ); return { unsubscribe: () => sub.unsubscribe(), value, error, isComplete }; } describe('Test runner', () => { it('sets initial state', () => { const runner = new WebworkerRunner(); const listener = listenToObservable(runner.result$); runner.run('console.log("hey");'); expect(listener.value).toEqual([]); listener.unsubscribe(); }); it('handles errors', () => { const runner = new WebworkerRunner(); const listener = listenToObservable(runner.result$); runner.run('debugger; throw "lol";'); expect(listener.value).toEqual([]); expect(listener.error).toEqual(undefined); listener.unsubscribe(); }); }); ================================================ FILE: libs/utils/src/lib/sandbox-runner/runners/webworker.ts ================================================ import { Observable, Subject } from 'rxjs'; import { shareReplay, switchMap } from 'rxjs/operators'; import { Runner } from './runner'; function run(code) { return new Observable(function(subscriber) { const file = new Blob([code], { type: 'text/javascript' }); const worker = new Worker(window.URL.createObjectURL(file), { name: Math.random().toString(36) }); worker.onerror = e => { subscriber.next({ type: 'error', data: e }); subscriber.complete(); worker.terminate(); }; worker.onmessage = function(a) { const data = a.data; if (data.type === 'complete') { worker.terminate(); } else { subscriber.next(data); } }; return () => { worker.terminate(); }; }); } export class WebworkerRunner implements Runner { private readonly executeSubject = new Subject(); result$ = this.executeSubject.pipe(switchMap(run), shareReplay(1)); destroy() { this.executeSubject.complete(); } run(code: string) { this.executeSubject.next(code); } } ================================================ FILE: libs/utils/src/lib/sandbox-runner/sandbox-runner.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { TestRunnerComponent } from '@codelab/utils/src/lib/sandbox-runner/test-runner.component'; import { TypescriptTestRunnerComponent } from '@codelab/utils/src/lib/sandbox-runner/typescript-test-runner/typescript-test-runner.component'; import { TestResultsModule } from '@codelab/utils/src/lib/test-results/test-results.module'; @NgModule({ declarations: [TestRunnerComponent, TypescriptTestRunnerComponent], exports: [TestRunnerComponent, TypescriptTestRunnerComponent], imports: [CommonModule, TestResultsModule] }) export class SandboxRunnerModule {} ================================================ FILE: libs/utils/src/lib/sandbox-runner/test-runner-service.service.spec.ts ================================================ import { TestBed } from '@angular/core/testing'; import { TestRunnerService } from './test-runner.service'; describe('TestRunnerServiceService', () => { beforeEach(() => TestBed.configureTestingModule({})); it('should be created', () => { const service: TestRunnerService = TestBed.inject(TestRunnerService); expect(service).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sandbox-runner/test-runner.component.html ================================================ ================================================ FILE: libs/utils/src/lib/sandbox-runner/test-runner.component.scss ================================================ :host { --test-succcess: #058b00; display: block; } :host ::ng-deep codelab-simple-tests-progress { margin-bottom: 20px; display: block; .text-box.pass { background: var(--test-succcess); } } mat-checkbox { margin-left: 8px; margin-top: 16px; } ================================================ FILE: libs/utils/src/lib/sandbox-runner/test-runner.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TestRunnerComponent } from './test-runner.component'; describe('TestRunnerComponent', () => { let component: TestRunnerComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [TestRunnerComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TestRunnerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sandbox-runner/test-runner.component.ts ================================================ import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output, SimpleChanges } from '@angular/core'; import { TestRunnerService } from '@codelab/utils/src/lib/sandbox-runner/test-runner.service'; import { filter, takeUntil } from 'rxjs/operators'; import { ReplaySubject } from 'rxjs'; @Component({ // tslint:disable-next-line:component-selector selector: 'slides-test-runner', templateUrl: './test-runner.component.html', styleUrls: ['./test-runner.component.scss'] }) export class TestRunnerComponent implements OnChanges, OnDestroy { @Input() code; @Input() tests; @Output() result = new EventEmitter(); readonly destroy$ = new ReplaySubject(1); readonly result$ = this.testRunner.result$; ngOnChanges(changes: SimpleChanges) { if (changes.code || changes.tests) { this.testRunner.run(this.code, this.tests); } } constructor(private testRunner: TestRunnerService) { this.result$ .pipe( filter(result => { return ( !!result.error || result.tests.every(t => t.pass !== undefined) ); }), takeUntil(this.destroy$) ) .subscribe(this.result); } ngOnDestroy(): void { this.destroy$.next(); } } ================================================ FILE: libs/utils/src/lib/sandbox-runner/test-runner.service.ts ================================================ import { Injectable } from '@angular/core'; import { WebworkerRunner } from '@codelab/utils/src/lib/sandbox-runner/runners/webworker'; import { ScriptLoaderService } from '@codelab/code-demos/src/lib/shared/script-loader.service'; import { TestRunner } from '@codelab/utils/src/lib/sandbox-runner/test-runner'; import { Observable } from 'rxjs'; import { TestRunResult } from '@codelab/utils/src/lib/test-results/common'; @Injectable({ providedIn: 'root' }) export class TestRunnerService { readonly testRunner = new TestRunner( this.scriptLoaderService, new WebworkerRunner() ); readonly result$: Observable = this.testRunner.result$; constructor(private scriptLoaderService: ScriptLoaderService) {} run(code: string, tests?: string) { this.testRunner.run(code, tests); } } ================================================ FILE: libs/utils/src/lib/sandbox-runner/test-runner.spec.ts ================================================ import { TestRunner } from '@codelab/utils/src/lib/sandbox-runner/test-runner'; import { ScriptLoaderService } from '@codelab/code-demos/src/lib/shared/script-loader.service'; import { Runner } from '@codelab/utils/src/lib/sandbox-runner/runners/runner'; describe('Test runner', () => { it('sets initial state', () => { const scriptLoaderService = jasmine.createSpyObj( 'ScriptLoaderService', ['getScript'] ); const runner = jasmine.createSpyObj('ScriptLoaderService', ['run']); const testRunner = new TestRunner(scriptLoaderService, runner); testRunner.run( 'console.log(lol)', ` describe('tests', () => { it('no tests have been setup!', () => { chai.expect(true).to.be.ok; }); it('I will fail', () => { chai.expect(false).to.be.ok; }); }); ` ); }); }); ================================================ FILE: libs/utils/src/lib/sandbox-runner/test-runner.ts ================================================ import { ScriptLoaderService } from '@codelab/code-demos/src/lib/shared/script-loader.service'; import { Runner } from '@codelab/utils/src/lib/sandbox-runner/runners/runner'; import { scan } from 'rxjs/operators'; import { testReducer } from '@codelab/utils/src/lib/sandbox-runner/common'; import { Observable } from 'rxjs'; import { TestRunResult } from '@codelab/utils/src/lib/test-results/common'; import { wrapSystemJs } from '@codelab/code-demos/src/lib/shared/sandbox'; const mochaRun = ` function flattenTests(suite) { const result = []; function extractSuite(suite) { suite.suites.forEach(function(suite) { extractSuite(suite); }); suite.tests.forEach(function(test) { result.push(test.title); }); } extractSuite(suite); return result; } const result = mocha.run(); postMessage({type: 'reset', data: flattenTests(mocha.suite)}); result.on('end', () => { postMessage({type: 'complete'}); }); result.on('pass', (t, e) => { postMessage({type: 'result', data: {pass: true, name: t.title}}); }); result.on('fail', (t, e) => { postMessage({type: 'result', data: {pass: false, name: t.title, error: e.message}}); }); `; export class TestRunner { private readonly code = { mocha: this.scriptLoaderService.getScript('mocha'), chai: this.scriptLoaderService.getScript('chai'), SystemJS: this.scriptLoaderService.getScript('SystemJS'), polyfill: `const window = globalThis;`, mochaSetup: `mocha.setup('bdd').reporter(function(a) {});`, mochaRun: mochaRun, systemMochaRun: ` System.register("runner", ["test"], () => { return { execute: () => { ${mochaRun} } }; }); System.import("runner"); `, defaultTests: ` describe('tests', () => { it('no tests have been setup', () => { chai.expect(true).to.be.ok; }); });` }; readonly result$: Observable = this.runner.result$.pipe( scan(testReducer, { tests: [] }) ); constructor( private readonly scriptLoaderService: ScriptLoaderService, private readonly runner: Runner ) {} run(code, tests = this.code.defaultTests, system = true) { this.runner.run( [ this.code.polyfill, this.code.chai, this.code.mocha, this.code.mochaSetup, system ? wrapSystemJs(this.code.SystemJS) : '', code, tests, system ? this.code.systemMochaRun : this.code.mochaRun ].join('\n') ); } } ================================================ FILE: libs/utils/src/lib/sandbox-runner/typescript-checker-runner/typescript-checker-runner.component.css ================================================ ================================================ FILE: libs/utils/src/lib/sandbox-runner/typescript-checker-runner/typescript-checker-runner.component.html ================================================ ================================================ FILE: libs/utils/src/lib/sandbox-runner/typescript-checker-runner/typescript-checker-runner.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TypescriptCheckerRunnerComponent } from './typescript-checker-runner.component'; describe('TypescriptCheckerRunnerComponent', () => { let component: TypescriptCheckerRunnerComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [TypescriptCheckerRunnerComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TypescriptCheckerRunnerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sandbox-runner/typescript-checker-runner/typescript-checker-runner.component.ts ================================================ import { Component, Input, OnChanges } from '@angular/core'; import { Observable, Subject } from 'rxjs'; import { filter, map } from 'rxjs/operators'; import { compileTsFilesWatch } from '@codelab/code-demos/src/lib/runner/compile-ts-files'; import { TestRunResult } from '../../test-results/common'; import { getTypeScript } from '../../loaders/loaders'; const ts = getTypeScript(); @Component({ // tslint:disable-next-line:component-selector selector: 'slides-typescript-checker-runner', templateUrl: './typescript-checker-runner.component.html', styleUrls: ['./typescript-checker-runner.component.css'] }) export class TypescriptCheckerRunnerComponent implements OnChanges { private readonly codeSubject = new Subject(); readonly result$: Observable = this.codeSubject.pipe( compileTsFilesWatch({ module: ts.ModuleKind.None, target: ts.ScriptTarget.ES2017, experimentalDecorators: true, emitDecoratorMetadata: true, noImplicitAny: true, declaration: true }), filter(a => Object.values(a.files).length > 0), map(a => { return { tests: a.diagnostics.map(d => ({ pass: false, name: d.messageText.toString() })) }; }) ); @Input() code; ngOnChanges() { this.codeSubject.next({ 'main.ts': this.code }); } } ================================================ FILE: libs/utils/src/lib/sandbox-runner/typescript-checker-runner/typescript-checker-runner.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { TypescriptCheckerRunnerComponent } from './typescript-checker-runner.component'; import { TestResultsModule } from '../../test-results/test-results.module'; @NgModule({ declarations: [TypescriptCheckerRunnerComponent], exports: [TypescriptCheckerRunnerComponent], imports: [CommonModule, TestResultsModule] }) export class TypescriptCheckerRunnerModule {} ================================================ FILE: libs/utils/src/lib/sandbox-runner/typescript-test-runner/typescript-test-runner.component.css ================================================ ================================================ FILE: libs/utils/src/lib/sandbox-runner/typescript-test-runner/typescript-test-runner.component.html ================================================ ================================================ FILE: libs/utils/src/lib/sandbox-runner/typescript-test-runner/typescript-test-runner.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TypescriptTestRunnerComponent } from './typescript-test-runner.component'; describe('TypescriptTestRunnerComponent', () => { let component: TypescriptTestRunnerComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [TypescriptTestRunnerComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TypescriptTestRunnerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sandbox-runner/typescript-test-runner/typescript-test-runner.component.ts ================================================ import { Component, EventEmitter, Input, OnChanges, OnDestroy, Output } from '@angular/core'; import { Subject } from 'rxjs'; import { filter, map } from 'rxjs/operators'; import { compileTsFilesWatch, Files } from '@codelab/code-demos/src/lib/runner/compile-ts-files'; @Component({ // tslint:disable-next-line:component-selector selector: 'slides-typescript-test-runner', templateUrl: './typescript-test-runner.component.html', styleUrls: ['./typescript-test-runner.component.css'] }) export class TypescriptTestRunnerComponent implements OnChanges, OnDestroy { @Input() code; @Input() tests; @Output() result = new EventEmitter(); private readonly codeSubject = new Subject(); readonly code$ = this.codeSubject.pipe( compileTsFilesWatch(), filter(a => Object.values(a.files).length > 0), map(a => { return { code: a.files['main.js'], tests: a.files['test.js'] }; }) ); ngOnChanges() { this.codeSubject.next({ 'main.ts': this.code, 'test.ts': this.tests }); } ngOnDestroy(): void { this.codeSubject.complete(); } } ================================================ FILE: libs/utils/src/lib/sync/common.ts ================================================ import { AngularFireAction, DatabaseSnapshot } from '@angular/fire/database'; export enum SyncStatus { OFF = 'off', VIEWING = 'viewing', PRESENTING = 'presenting', ADMIN = 'admin' } export const canWritePresenterData = status => status === SyncStatus.PRESENTING || status === SyncStatus.ADMIN; export function firebaseToValuesWithKey( list: AngularFireAction>[] ) { return list.map(action => ({ key: action.key, ...action.payload.val() })); } export function toValuesAndKeys(object: { [k: string]: T; }): Array<{ key: string; value: T }> { return Object.entries(object).map(([key, value]) => ({ key, value })); } export function sum(array) { return array.reduce((a, b) => a + b, 0); } ================================================ FILE: libs/utils/src/lib/sync/components/configure-sync/configure-sync.component.css ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/configure-sync/configure-sync.component.html ================================================ Enable the survey ================================================ FILE: libs/utils/src/lib/sync/components/configure-sync/configure-sync.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ConfigureSyncComponent } from './configure-sync.component'; describe('ConfigureSyncComponent', () => { let component: ConfigureSyncComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ConfigureSyncComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(ConfigureSyncComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/configure-sync/configure-sync.component.ts ================================================ import { Component, OnInit } from '@angular/core'; @Component({ selector: 'codelab-configure-sync', templateUrl: './configure-sync.component.html', styleUrls: ['./configure-sync.component.css'] }) export class ConfigureSyncComponent implements OnInit { enabled: boolean; constructor() {} ngOnInit() {} } ================================================ FILE: libs/utils/src/lib/sync/components/configure-sync/configure-sync.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatCardModule } from '@angular/material/card'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatFormFieldModule } from '@angular/material/form-field'; import { ConfigureSyncComponent } from './configure-sync.component'; import { SyncDirectivesModule } from '@codelab/utils/src/lib/sync/directives/sync-directives.module'; import { FormsModule } from '@angular/forms'; @NgModule({ declarations: [ConfigureSyncComponent], exports: [ConfigureSyncComponent], imports: [ CommonModule, MatCardModule, MatFormFieldModule, MatCheckboxModule, SyncDirectivesModule, FormsModule ] }) export class ConfigureSyncModule {} ================================================ FILE: libs/utils/src/lib/sync/components/online-indicator/online-indicator.component.css ================================================ .indicator { display: flex; align-items: center; padding: 8px; font-family: sans-serif; } mat-icon { margin-right: 10px; animation: rotation 10s infinite linear; color: #ff5207; } @keyframes rotation { from { transform: rotate(0deg); } to { transform: rotate(359deg); } } ================================================ FILE: libs/utils/src/lib/sync/components/online-indicator/online-indicator.component.html ================================================
        offline_bolt Connecting...
        ================================================ FILE: libs/utils/src/lib/sync/components/online-indicator/online-indicator.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { OnlineIndicatorComponent } from './online-indicator.component'; import { OnlineIndicatorModule } from '@codelab/utils/src/lib/sync/components/online-indicator/online-indicator.module'; describe('OnlineIndicatorComponent', () => { let component: OnlineIndicatorComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [OnlineIndicatorModule] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(OnlineIndicatorComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/online-indicator/online-indicator.component.ts ================================================ import { Component, OnInit } from '@angular/core'; import { FirebaseInfoService } from '@codelab/utils/src/lib/sync/services/firebase-info.service'; @Component({ selector: 'codelab-online-indicator', templateUrl: './online-indicator.component.html', styleUrls: ['./online-indicator.component.css'] }) export class OnlineIndicatorComponent implements OnInit { constructor(readonly firebaseInfoService: FirebaseInfoService) {} ngOnInit() {} } ================================================ FILE: libs/utils/src/lib/sync/components/online-indicator/online-indicator.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { OnlineIndicatorComponent } from './online-indicator.component'; import { MatIconModule } from '@angular/material/icon'; @NgModule({ declarations: [OnlineIndicatorComponent], exports: [OnlineIndicatorComponent], imports: [CommonModule, MatIconModule] }) export class OnlineIndicatorModule {} ================================================ FILE: libs/utils/src/lib/sync/components/poll/common/bar-chart/bar-chart.component.html ================================================
        {{ item.value | number: '1.2-2' }}
        {{ item.key }}
        ================================================ FILE: libs/utils/src/lib/sync/components/poll/common/bar-chart/bar-chart.component.scss ================================================ ::ng-deep :root { --bar-width: 50px; } .filler { width: var(--bar-width); background: #fff; position: relative; transition: height 1s ease-out; } .bar { width: var(--bar-width); background: #ff4e3d; } .highlight-index { .bar { background: #444; } .bar.highlighted { background: #ff4e3d; } .label.highlighted { background: #ff4e3d; color: #ffffff; } } .star { position: absolute; text-align: center; } codelab-stars { display: block; margin: 0 auto; width: calc(var(--bar-width) * 5); margin-bottom: 60px; } ::ng-deep codelab-stars .mat-icon { font-size: var(--bar-width); width: var(--bar-width); height: var(--bar-width); } .label { font-size: 20px; } .horizontal { &.wrapper { margin: 40px auto; display: flex; text-align: center; width: calc(var(--bar-width) * 6); .item { .bar { height: 200px; margin-right: 10px; } .label { margin-top: 20px; width: var(--bar-width); height: var(--bar-width); line-height: var(--bar-width); border-radius: 50%; } .star { width: var(--bar-width); bottom: 10px; } } } } .vertical { &.wrapper { display: flex; flex-direction: column; .item { display: flex; flex-direction: row-reverse; align-items: center; justify-content: space-between; .bar { height: var(--bar-width); width: 100%; margin-right: 100px; margin-bottom: 10px; display: flex; justify-content: flex-end; } .label { margin-right: 20px; width: 200px; word-break: break-word; max-height: var(--bar-width); overflow: hidden; text-overflow: ellipsis; } .star { height: var(--bar-width); line-height: var(--bar-width); left: 10px; } } } } ================================================ FILE: libs/utils/src/lib/sync/components/poll/common/bar-chart/bar-chart.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { BarChartComponent } from './bar-chart.component'; describe('BarChartComponent', () => { let component: BarChartComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [BarChartComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(BarChartComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/poll/common/bar-chart/bar-chart.component.ts ================================================ import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; @Component({ selector: 'codelab-bar-chart', templateUrl: './bar-chart.component.html', styleUrls: ['./bar-chart.component.scss'] }) export class BarChartComponent implements OnChanges { @Input() highlightedIndex: number; @Input() vertical = false; @Input() data: { [k: string]: number }; max: number; breakdown: { value: number; key: string }[]; constructor() {} trackBy(i: number) { return i; } ngOnChanges(changes: SimpleChanges): void { if ('data' in changes) { this.breakdown = Object.entries(this.data || {}).map(([key, value]) => ({ key, value })); this.max = Math.max(...Object.values(this.data || {}), 0); } } } ================================================ FILE: libs/utils/src/lib/sync/components/poll/common/bar-chart/bar-chart.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { BarChartComponent } from './bar-chart.component'; @NgModule({ declarations: [BarChartComponent], exports: [BarChartComponent], imports: [CommonModule] }) export class BarChartModule {} ================================================ FILE: libs/utils/src/lib/sync/components/poll/common/common.ts ================================================ export interface SyncPollConfig { type: string; key: string; options?: string[]; question: string; time?: number; answer?: string; } export const LETTERS = String.fromCharCode( ...Array.from(new Array(25), (a, i) => i + 65) ).split(''); ================================================ FILE: libs/utils/src/lib/sync/components/poll/common/stars/stars.component.css ================================================ :host.enabled .star { cursor: pointer; } :host.enabled .star:hover { color: #ff4e3d; } ================================================ FILE: libs/utils/src/lib/sync/components/poll/common/stars/stars.component.html ================================================ {{ getStarIcon(star) }} ================================================ FILE: libs/utils/src/lib/sync/components/poll/common/stars/stars.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { StarsComponent } from './stars.component'; describe('StarsComponent', () => { let component: StarsComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [StarsComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(StarsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/poll/common/stars/stars.component.ts ================================================ import { Component, forwardRef, HostBinding, Input, OnInit } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; @Component({ selector: 'codelab-stars', templateUrl: './stars.component.html', styleUrls: ['./stars.component.css'], providers: [ { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => StarsComponent), multi: true } ] }) export class StarsComponent implements OnInit, ControlValueAccessor { @HostBinding('class.enabled') @Input() enabled = true; readonly stars = Array.from(Array(5), (a, i) => i + 1); rating = 0; hover = 0; private onChange!: (n: number) => void; constructor() {} getStarIcon(star: number) { const rating = (this.hover || this.rating || 0) - star + 1; if (rating <= 0) { return 'star_border'; } if (rating <= 0.5) { return 'star_half'; } return 'star'; } ngOnInit() {} registerOnChange(fn: (n: number) => void): void { this.onChange = fn; } registerOnTouched(fn: any): void {} writeValue(number: any): void { this.rating = number; } setValue(star: number) { if (this.enabled) { this.onChange(star); } } setHover(index: number) { if (this.enabled) { this.hover = index; } } } ================================================ FILE: libs/utils/src/lib/sync/components/poll/common/stars/stars.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { StarsComponent } from '@codelab/utils/src/lib/sync/components/poll/common/stars/stars.component'; import { MatIconModule } from '@angular/material/icon'; @NgModule({ declarations: [StarsComponent], exports: [StarsComponent], imports: [CommonModule, MatIconModule] }) export class StarsModule {} ================================================ FILE: libs/utils/src/lib/sync/components/poll/common/sync-poll.service.spec.ts ================================================ import { TestBed } from '@angular/core/testing'; import { calculateUserScore } from '@codelab/utils/src/lib/sync/components/poll/common/sync-poll.service'; import { SyncPollConfig } from '@codelab/utils/src/lib/sync/components/poll/common/common'; describe('SyncPoll', () => { const userData = { // Dog is the only one right. dogIsRight: { dog: { answer: 0, time: 102 }, cat: { answer: 1, time: 100 }, pikachu: { answer: 1, time: 100 }, mouse: { answer: 1, time: 100 } }, catIsRightImmediately: { dog: { answer: 0, time: 104 }, cat: { answer: 1, time: 100 }, pikachu: { answer: 2, time: 100 } }, catAndDogAreRightImmediately: { dog: { answer: 1, time: 100 }, cat: { answer: 1, time: 100 }, pikachu: { answer: 2, time: 100 } }, catAndDogAreRightAtTheSameTime: { dog: { answer: 1, time: 120 }, cat: { answer: 1, time: 120 }, pikachu: { answer: 2, time: 100 } }, catAndDogAreRightAtCatIsFaster: { dog: { answer: 1, time: 120 }, cat: { answer: 1, time: 110 }, pikachu: { answer: 2, time: 100 } }, everyoneIsRight: { dog: { answer: 1, time: 101 }, cat: { answer: 1, time: 103 }, pikachu: { answer: 1, time: 105 } }, outsideOfRange: { dog: { answer: 1, time: 30 }, cat: { answer: 1, time: 25000 }, pikachu: { answer: 1, time: 99 }, mouse: { answer: 1, time: 121 } } }; const testConfig: SyncPollConfig[] = [ { key: 'a', type: 'choice', question: 'A???', answer: 'correct', options: ['correct', 'b', 'c', 'd'] }, { key: 'b', type: 'choice', question: 'B??', answer: 'correct', options: ['a', 'correct', 'c', 'd'] }, { key: 'c', type: 'choice', question: 'C???', answer: 'correct', options: ['a', 'b', 'correct', 'd'] }, { key: 'c', type: 'choice', question: 'I do not have an answer', options: ['a', 'b', 'correct', 'd'] } ]; const presenterData = { a: { startTime: 100 }, b: { startTime: 100 }, c: { startTime: 100 }, d: { startTime: 100 } }; beforeEach(() => TestBed.configureTestingModule({})); describe('calculateUserScore', () => { it('gracefully handles the case where presented data has a key that presenters data does not have', () => { const v = calculateUserScore(testConfig, presenterData, { goodKey: {} }); expect(v).toEqual({}); }); it('handles simple case when the first user is right', () => { const v = calculateUserScore(testConfig, presenterData, { a: userData.dogIsRight }); expect(v).toEqual({ dog: 100, cat: 0, pikachu: 0, mouse: 0 }); }); it('handles simple case when the second user is right immideately', () => { const v = calculateUserScore(testConfig, presenterData, { b: userData.catIsRightImmediately }); expect(v).toEqual({ dog: 0, cat: 100, pikachu: 0 }); }); it('two users gave correct answer immediately', () => { const v = calculateUserScore(testConfig, presenterData, { b: userData.catAndDogAreRightImmediately }); expect(v).toEqual({ dog: 100, cat: 100, pikachu: 0 }); }); it('second user gave correct answer at the same time', () => { const v = calculateUserScore(testConfig, presenterData, { b: userData.catAndDogAreRightAtTheSameTime }); expect(v).toEqual({ dog: 100, cat: 100, pikachu: 0 }); }); it('2 users gave correct answer, but cat was faster', () => { const v = calculateUserScore(testConfig, presenterData, { b: userData.everyoneIsRight }); expect(v).toEqual({ dog: 100, cat: 75, pikachu: 50 }); }); it('Multiple questions', () => { const v = calculateUserScore(testConfig, presenterData, { a: userData.dogIsRight, b: userData.everyoneIsRight }); expect(v).toEqual({ mouse: 0, dog: 200, cat: 75, pikachu: 50 }); }); it('Ignores answers that are outside of the range', () => { const v = calculateUserScore(testConfig, presenterData, { b: userData.outsideOfRange }); expect(v).toEqual({ dog: 0, cat: 0, pikachu: 0, mouse: 100 }); }); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/poll/common/sync-poll.service.ts ================================================ import { Injectable } from '@angular/core'; import { SyncDataService } from '@codelab/utils/src/lib/sync/services/sync-data.service'; import { SyncPollConfig } from '@codelab/utils/src/lib/sync/components/poll/common/common'; import { distinctUntilChanged, filter, map, switchMap, withLatestFrom } from 'rxjs/operators'; import { combineLatest, interval, Observable, of } from 'rxjs'; import produce from 'immer'; import { database } from 'firebase/app'; import { SyncSessionService } from '@codelab/utils/src/lib/sync/services/sync-session.service'; import { toValuesAndKeys } from '@codelab/utils/src/lib/sync/common'; import { SyncRegistrationService } from '@codelab/utils/src/lib/sync/components/registration/sync-registration.service'; import { FirebaseInfoService } from '@codelab/utils/src/lib/sync/services/firebase-info.service'; const DEFAULT_TEST_TIME_SECONDS = 20; export interface UserVote { answer: number; time: number; } function getScore( isCorrect: boolean, range: number, speed: number, fastest: number ) { if (!isCorrect) { return 0; } if (range === 0) { return 1; } return 0.5 + ((range + fastest - speed) / range) * 0.5; } export function calculateUserScore(configs, presenterData, userData) { return configs .filter(config => presenterData[config.key]) .map(config => { return { ...config, answerIndex: config.options.indexOf(config.answer), startTime: presenterData[config.key].startTime }; }) .flatMap(config => { if (!userData[config.key]) { return []; } const responses = Object.entries(userData[config.key]).map( ([user, answer]) => { const speed = answer.time - config.startTime; return { user, isCorrect: speed >= 0 && answer.answer === config.answerIndex && speed <= DEFAULT_TEST_TIME_SECONDS * 1000, speed: speed }; } ); const correctResponses = responses.filter(r => r.isCorrect); const slowest = Math.max(...correctResponses.map(a => a.speed), 0); const fastest = Math.min(...correctResponses.map(a => a.speed), slowest); const range = slowest - fastest; return responses.map(r => { return { ...r, score: 100 * getScore(r.isCorrect, range, r.speed, fastest) }; }); }) .reduce((result, record) => { result[record.user] = (result[record.user] || 0) + record.score; return result; }, {}); } export class SyncPoll { readonly key = this.config.key; readonly presenterSettings = this.syncDataService .getPresenterObject('poll') .object(this.config.key) .withDefault({ enabled: true, startTime: 0 }); readonly isPollEnabled$ = this.presenterSettings .valueChanges() .pipe(map(a => a.enabled)); readonly timestamp$: Observable< number > = this.presenterSettings.valueChanges().pipe(map(a => a.startTime)); readonly timeLeft$ = this.timestamp$.pipe( switchMap(time => interval(500).pipe(map(() => time))), withLatestFrom( this.firebaseInfoService.offset$.pipe(distinctUntilChanged()) ), map(([time, offset]) => Math.round( Math.max( 0, time + 1000 * (this.config.time || DEFAULT_TEST_TIME_SECONDS) - Date.now() + offset ) / 1000 ) ), distinctUntilChanged() ); readonly $isPollRunning = this.timeLeft$.pipe(map(time => time > 0)); readonly hasVotes$: Observable; readonly votesCount$: Observable; private readonly viewerData = this.syncDataService .getCurrentViewerObject('poll') .object(this.key) .withDefault({ answer: null, time: 0 }); readonly myVote$ = this.viewerData.valueChanges().pipe(map(a => a.answer)); private readonly votesData = this.syncDataService .getAdminAllUserData('poll') .object(this.key) .withDefault({}); votes$ = this.votesData.valueChanges(); constructor( private readonly syncDataService: SyncDataService, readonly config: SyncPollConfig, private readonly firebaseInfoService: FirebaseInfoService ) { // Reformatting breaks this if it's out of the constructor. this.hasVotes$ = this.votes$.pipe(map(v => Object.keys(v).length > 0)); this.votesCount$ = this.votes$.pipe( map(votes => Object.values(votes).length) ); } vote(answer: number) { this.viewerData.set({ answer, time: database.ServerValue.TIMESTAMP as number }); } start() { this.presenterSettings.updateWithCallback( produce(settings => { settings.startTime = database.ServerValue.TIMESTAMP; }) ); } } @Injectable({ providedIn: 'root' }) export class SyncPollService { constructor( private readonly syncDataService: SyncDataService, private readonly syncSessionService: SyncSessionService, private readonly firebaseInfoService: FirebaseInfoService, private readonly registrationService: SyncRegistrationService ) {} getPoll(config: SyncPollConfig) { return new SyncPoll(this.syncDataService, config, this.firebaseInfoService); } calculateScores(syncPollConfigs: SyncPollConfig[]) { const presenterData$ = this.syncDataService .getPresenterObject('poll') .valueChanges() .pipe(filter(a => a !== null)); const userData$ = this.syncDataService .getAdminAllUserData('poll') .valueChanges() .pipe(filter(a => a !== null)); const result$ = combineLatest([ of(syncPollConfigs), presenterData$, userData$ ]).pipe( map(([configs, presenterData, userData]) => calculateUserScore(configs, presenterData, userData) ) ); return combineLatest([result$, this.registrationService.usersMap$]).pipe( map(([results, users]) => { return Object.entries(users).reduce((result, [uid, name]) => { result[uid] = { name, score: results[uid] || 0 }; return result; }, {}); }) ); } calculateMyScore(syncPollConfigs: SyncPollConfig[]) { return combineLatest([ this.calculateScores(syncPollConfigs), this.syncSessionService.viewerId$ ]).pipe( map(([scoresObj, uid]) => { const scores = toValuesAndKeys<{ score: number; name: string }>( scoresObj ).sort((a, b) => b.value.score - a.value.score); const index = scores.findIndex(score => score.key === uid); if (index === -1) { return { place: 0, score: 0 }; } return { place: index + 1, ...scores[index].value }; }) ); } } ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-admin/sync-poll-admin.component.css ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-admin/sync-poll-admin.component.html ================================================

        {{ config.question }}

        Enable the poll
        ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-admin/sync-poll-admin.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SyncPollAdminComponent } from './sync-poll-admin.component'; describe('SyncPollAdminComponent', () => { let component: SyncPollAdminComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SyncPollAdminComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SyncPollAdminComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-admin/sync-poll-admin.component.ts ================================================ import { Component, Input, OnChanges, OnInit } from '@angular/core'; import { SyncPollConfig } from '@codelab/utils/src/lib/sync/components/poll/common/common'; import { SyncDataService } from '@codelab/utils/src/lib/sync/services/sync-data.service'; import { database } from 'firebase/app'; import { SyncPoll, SyncPollService } from '@codelab/utils/src/lib/sync/components/poll/common/sync-poll.service'; @Component({ selector: 'codelab-sync-poll-admin', templateUrl: './sync-poll-admin.component.html', styleUrls: ['./sync-poll-admin.component.css'] }) export class SyncPollAdminComponent implements OnInit, OnChanges { @Input() config: SyncPollConfig; pollEnabled: boolean; poll!: SyncPoll; private readonly timing = this.syncDataService.getPresenterObject( 'poll-timing' ); constructor( private readonly syncDataService: SyncDataService, private readonly pollService: SyncPollService ) {} ngOnChanges(changes) { if (changes.config && changes.config.currentValue) { this.timing .object(changes.config.currentValue.key) .set(database.ServerValue.TIMESTAMP); } } ngOnInit() { this.poll = this.pollService.getPoll(this.config); } start() { this.poll.start(); } } ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/choice-presenter/choice-presenter.component.css ================================================ .results { display: grid; grid-template-columns: 1fr 1fr; font-size: 60px; text-align: center; } ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/choice-presenter/choice-presenter.component.html ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/choice-presenter/choice-presenter.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { ChoicePresenterComponent } from './choice-presenter.component'; describe('ChoicePresenterComponent', () => { let component: ChoicePresenterComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [ChoicePresenterComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(ChoicePresenterComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/choice-presenter/choice-presenter.component.ts ================================================ import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; import { LETTERS } from '@codelab/utils/src/lib/sync/components/poll/common/common'; import { UserVote } from '@codelab/utils/src/lib/sync/components/poll/common/sync-poll.service'; @Component({ selector: 'codelab-choice-presenter', templateUrl: './choice-presenter.component.html', styleUrls: ['./choice-presenter.component.css'] }) export class ChoicePresenterComponent implements OnChanges { @Input() options: string[]; @Input() answer: string; @Input() answerIndex: number; @Input() votes: { [key: string]: UserVote }; breakdown: { [key: string]: number }; constructor() {} ngOnChanges(changes: SimpleChanges): void { if ('votes' in changes) { const startMap = this.options .map((a, i) => LETTERS[i]) .reduce((r, k) => { r[k] = 0; return r; }, {}); this.breakdown = Object.values(this.votes || {}).reduce( (result, response) => { const value = response.answer; result[LETTERS[value]] = (result[LETTERS[value]] || 0) + 1; return result; }, startMap ); } } } ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/leaderboard/leaderboard-presenter/leaderboard-presenter.component.css ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/leaderboard/leaderboard-presenter/leaderboard-presenter.component.html ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/leaderboard/leaderboard-presenter/leaderboard-presenter.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { LeaderboardPresenterComponent } from './leaderboard-presenter.component'; describe('LeaderboardPresenterComponent', () => { let component: LeaderboardPresenterComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [LeaderboardPresenterComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(LeaderboardPresenterComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/leaderboard/leaderboard-presenter/leaderboard-presenter.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { SyncPollService } from '@codelab/utils/src/lib/sync/components/poll/common/sync-poll.service'; import { map } from 'rxjs/operators'; @Component({ selector: 'codelab-leaderboard-presenter', templateUrl: './leaderboard-presenter.component.html', styleUrls: ['./leaderboard-presenter.component.css'] }) export class LeaderboardPresenterComponent implements OnInit { @Input() config; private leaderboard$: Observable; constructor(private readonly syncPollService: SyncPollService) {} ngOnInit() { this.leaderboard$ = this.syncPollService .calculateScores(this.config.filter(a => a.answer)) .pipe( map(a => { return Object.values<{ name: string; score: number }>(a).reduce( (result, value) => { result[value.name] = value.score; return result; }, {} ); }) ); } } ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/leaderboard/leaderboard-viewer/leaderboard-viewer.component.css ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/leaderboard/leaderboard-viewer/leaderboard-viewer.component.html ================================================

        Place: {{ result.place }}

        Score: {{ result.score | number: '1.2-2' }}

        ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/leaderboard/leaderboard-viewer/leaderboard-viewer.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { LeaderboardViewerComponent } from './leaderboard-viewer.component'; describe('LeaderboardViewerComponent', () => { let component: LeaderboardViewerComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [LeaderboardViewerComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(LeaderboardViewerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/leaderboard/leaderboard-viewer/leaderboard-viewer.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { SyncPollService } from '@codelab/utils/src/lib/sync/components/poll/common/sync-poll.service'; @Component({ selector: 'codelab-leaderboard-viewer', templateUrl: './leaderboard-viewer.component.html', styleUrls: ['./leaderboard-viewer.component.css'] }) export class LeaderboardViewerComponent implements OnInit { @Input() config; myScore$: Observable; constructor(private readonly syncPollService: SyncPollService) {} ngOnInit() { this.myScore$ = this.syncPollService.calculateMyScore( this.config.filter(a => a.answer) ); } } ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/leaderboard/leaderboard.component.css ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/leaderboard/leaderboard.component.html ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/leaderboard/leaderboard.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { LeaderboardComponent } from './leaderboard.component'; describe('LeaderboardComponent', () => { let component: LeaderboardComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [LeaderboardComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(LeaderboardComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/leaderboard/leaderboard.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; import { SyncPollService } from '@codelab/utils/src/lib/sync/components/poll/common/sync-poll.service'; import { Observable } from 'rxjs'; interface Response { correct: boolean; key: string; speed: number; score: number; } @Component({ selector: 'codelab-leaderboard', templateUrl: './leaderboard.component.html', styleUrls: ['./leaderboard.component.css'] }) export class LeaderboardComponent implements OnInit { @Input() config; private leaderboard$: Observable; constructor(private readonly syncPollService: SyncPollService) {} ngOnInit() { this.leaderboard$ = this.syncPollService.calculateScores( this.config.filter(a => a.answer) ); } } ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/leaderboard/leaderboard.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { LeaderboardComponent } from './leaderboard.component'; import { BarChartModule } from '@codelab/utils/src/lib/sync/components/poll/common/bar-chart/bar-chart.module'; import { SyncDirectivesModule } from '@codelab/utils/src/lib/sync/directives/sync-directives.module'; import { LeaderboardPresenterComponent } from './leaderboard-presenter/leaderboard-presenter.component'; import { LeaderboardViewerComponent } from './leaderboard-viewer/leaderboard-viewer.component'; @NgModule({ declarations: [ LeaderboardComponent, LeaderboardPresenterComponent, LeaderboardViewerComponent ], exports: [LeaderboardComponent], imports: [CommonModule, BarChartModule, SyncDirectivesModule] }) export class LeaderboardModule {} ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/stars-presenter/stars-presenter.component.css ================================================ ::ng-deep :root { --bar-width: 50px; } .wrapper { margin: 40px auto; display: flex; text-align: center; width: calc(var(--bar-width) * 6); } .filler { width: var(--bar-width); background: #fff; position: relative; transition: height 1s ease-out; } .bar { width: var(--bar-width); height: 200px; background: #ff4e3d; margin-right: 10px; } .star { width: var(--bar-width); position: absolute; bottom: 10px; text-align: center; } codelab-stars { display: block; margin: 0 auto; width: calc(var(--bar-width) * 5); margin-bottom: 60px; } ::ng-deep codelab-stars .mat-icon { font-size: var(--bar-width); width: var(--bar-width); height: var(--bar-width); } .label { width: var(--bar-width); font-size: 20px; margin-top: 20px; } ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/stars-presenter/stars-presenter.component.html ================================================
        {{ star }}
        {{ i + 1 }}
        ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/stars-presenter/stars-presenter.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { StarsPresenterComponent } from './stars-presenter.component'; describe('StarsPresenterComponent', () => { let component: StarsPresenterComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [StarsPresenterComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(StarsPresenterComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/stars-presenter/stars-presenter.component.ts ================================================ import { Component, Input, OnChanges, SimpleChanges } from '@angular/core'; import { sum } from '@codelab/utils/src/lib/sync/common'; @Component({ selector: 'codelab-stars-presenter', templateUrl: './stars-presenter.component.html', styleUrls: ['./stars-presenter.component.css'] }) export class StarsPresenterComponent implements OnChanges { @Input() votes: { [k: string]: number }; average: number; breakdown: number[]; max: number; constructor() {} trackBy(i: number) { return i; } ngOnChanges(changes: SimpleChanges): void { if ('votes' in changes) { const values = Object.values(this.votes); this.average = sum(values) / values.length; this.breakdown = values .reduce( (r, a) => { r[a]++; return r; }, Array.from(Array(6), () => 0) ) .slice(1); this.max = Math.max(...this.breakdown); } } } ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/sync-poll-presenter.component.css ================================================ .bunny { background-image: url('../../../images/bunny.png'); width: 406px; height: 442px; margin: 0 auto; } .question, .timer { text-align: center; } ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/sync-poll-presenter.component.html ================================================

        ⏱ {{ poll.timeLeft$ | async }} seconds left

        ⏱ Time is over

        {{ config.question }}

        Collecting answers

        {{ poll.votesCount$ | async }} votes

        Waiting for votes...

        Poll has not been started yet.
        ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/sync-poll-presenter.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SyncPollPresenterComponent } from './sync-poll-presenter.component'; describe('SyncPollPresenterComponent', () => { let component: SyncPollPresenterComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SyncPollPresenterComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SyncPollPresenterComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-presenter/sync-poll-presenter.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; import { SyncPollConfig } from '@codelab/utils/src/lib/sync/components/poll/common/common'; import { SyncPoll, SyncPollService } from '@codelab/utils/src/lib/sync/components/poll/common/sync-poll.service'; @Component({ selector: 'codelab-sync-poll-presenter', templateUrl: './sync-poll-presenter.component.html', styleUrls: ['./sync-poll-presenter.component.css'] }) export class SyncPollPresenterComponent implements OnInit { @Input() config: SyncPollConfig; poll: SyncPoll; constructor(private readonly pollService: SyncPollService) {} getAnswerIndex() { return this.config.options.indexOf(this.config.answer); } ngOnInit() { this.poll = this.pollService.getPoll(this.config); } } ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-viewer/sync-poll-viewer-choice/sync-poll-viewer-choice.component.css ================================================ .button-wrapper { margin-bottom: 10px; } .button-wrapper button { width: 100%; position: relative; } .letter { position: absolute; left: -10px; top: 0; width: 32px; border: 1px #ddd solid; border-radius: 10px; background: #fff; transform: rotate(-5deg); color: rgba(0, 0, 0, 0.87); } button[disabled].selected { background: #ff4e3d; } ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-viewer/sync-poll-viewer-choice/sync-poll-viewer-choice.component.html ================================================
        ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-viewer/sync-poll-viewer-choice/sync-poll-viewer-choice.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SyncPollViewerChoiceComponent } from './sync-poll-viewer-choice.component'; describe('SyncPollViewerChoiceComponent', () => { let component: SyncPollViewerChoiceComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SyncPollViewerChoiceComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SyncPollViewerChoiceComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-viewer/sync-poll-viewer-choice/sync-poll-viewer-choice.component.ts ================================================ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { LETTERS } from '../../common/common'; @Component({ selector: 'codelab-sync-poll-viewer-choice', templateUrl: './sync-poll-viewer-choice.component.html', styleUrls: ['./sync-poll-viewer-choice.component.css'] }) export class SyncPollViewerChoiceComponent { readonly LETTERS = LETTERS; @Input() myVote: number; @Input() options: string[]; @Input() enabled = true; @Output() vote = new EventEmitter(); } ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-viewer/sync-poll-viewer.component.css ================================================ .answer { margin-right: 10px; } codelab-stars { width: 300px; margin: 0 auto; display: block; margin-top: 60px; } ::ng-deep codelab-stars .mat-icon { font-size: 50px; width: 50px; height: 50px; margin-right: 10px; } .time { text-align: center; } .disabled { opacity: 0.3; } ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-viewer/sync-poll-viewer.component.html ================================================

        ⏱ {{ poll.timeLeft$ | async }} seconds left

        ⏱ Time is over

        {{ config.question }}

        not started

        ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-viewer/sync-poll-viewer.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SyncPollViewerComponent } from './sync-poll-viewer.component'; describe('SyncPollViewerComponent', () => { let component: SyncPollViewerComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SyncPollViewerComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SyncPollViewerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll-viewer/sync-poll-viewer.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; import { SyncPollConfig } from '@codelab/utils/src/lib/sync/components/poll/common/common'; import { SyncPoll, SyncPollService } from '@codelab/utils/src/lib/sync/components/poll/common/sync-poll.service'; @Component({ selector: 'codelab-sync-poll-viewer', templateUrl: './sync-poll-viewer.component.html', styleUrls: ['./sync-poll-viewer.component.css'] }) export class SyncPollViewerComponent implements OnInit { @Input() config: SyncPollConfig; stars: string; poll: SyncPoll; constructor(private readonly pollService: SyncPollService) {} ngOnInit() { this.poll = this.pollService.getPoll(this.config); } } ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll.component.css ================================================ h1 { text-align: center; } ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll.component.html ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SyncPollComponent } from './sync-poll.component'; import { SyncPollModule } from '@codelab/utils/src/lib/sync/components/poll/sync-poll.module'; describe('SyncPollComponent', () => { let component: SyncPollComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [SyncPollModule] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SyncPollComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll.component.ts ================================================ import { Component, Input } from '@angular/core'; import { SyncPollService } from '@codelab/utils/src/lib/sync/components/poll/common/sync-poll.service'; import { SyncPollConfig } from '@codelab/utils/src/lib/sync/components/poll/common/common'; /** * Coming soon. */ @Component({ selector: 'codelab-poll', templateUrl: './sync-poll.component.html', styleUrls: ['./sync-poll.component.css'], providers: [SyncPollService] }) export class SyncPollComponent { @Input() poll: SyncPollConfig; } ================================================ FILE: libs/utils/src/lib/sync/components/poll/sync-poll.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatIconModule } from '@angular/material/icon'; import { MatTabsModule } from '@angular/material/tabs'; import { SyncPollAdminComponent } from '@codelab/utils/src/lib/sync/components/poll/sync-poll-admin/sync-poll-admin.component'; import { SyncPollPresenterComponent } from '@codelab/utils/src/lib/sync/components/poll/sync-poll-presenter/sync-poll-presenter.component'; import { SyncPollViewerComponent } from '@codelab/utils/src/lib/sync/components/poll/sync-poll-viewer/sync-poll-viewer.component'; import { SyncPollComponent } from '@codelab/utils/src/lib/sync/components/poll/sync-poll.component'; import { SyncDirectivesModule } from '@codelab/utils/src/lib/sync/directives/sync-directives.module'; import { FormsModule } from '@angular/forms'; import { AngularFireDatabaseModule } from '@angular/fire/database'; import { StarsModule } from '@codelab/utils/src/lib/sync/components/poll/common/stars/stars.module'; import { StarsPresenterComponent } from './sync-poll-presenter/stars-presenter/stars-presenter.component'; import { ChoicePresenterComponent } from './sync-poll-presenter/choice-presenter/choice-presenter.component'; import { SyncPollViewerChoiceComponent } from './sync-poll-viewer/sync-poll-viewer-choice/sync-poll-viewer-choice.component'; import { BarChartModule } from '@codelab/utils/src/lib/sync/components/poll/common/bar-chart/bar-chart.module'; import { LeaderboardModule } from '@codelab/utils/src/lib/sync/components/poll/sync-poll-presenter/leaderboard/leaderboard.module'; @NgModule({ declarations: [ SyncPollAdminComponent, SyncPollPresenterComponent, SyncPollViewerComponent, SyncPollComponent, StarsPresenterComponent, ChoicePresenterComponent, SyncPollViewerChoiceComponent ], exports: [SyncPollComponent], imports: [ CommonModule, MatCardModule, MatCheckboxModule, SyncDirectivesModule, AngularFireDatabaseModule, FormsModule, MatButtonModule, MatIconModule, StarsModule, BarChartModule, MatTabsModule, LeaderboardModule ] }) export class SyncPollModule {} ================================================ FILE: libs/utils/src/lib/sync/components/questions/common/common.ts ================================================ export enum QuestionStatus { APPROVED = 'approved', NEW = 'new', ARCHIVED = 'archived' } export const statuses = Object.values(QuestionStatus); export interface QuestionDb { question: string; score: number; time: number; status: QuestionStatus; } export interface Question extends QuestionDb { key: string; starred: boolean; public: boolean; myVote: 1 | 0 | -1; author: string; displayName: string; } export interface QuestionConfig { starredQuestionKey: string; requireApproval: boolean; } ================================================ FILE: libs/utils/src/lib/sync/components/questions/common/question/question.component.html ================================================
        {{ question.score }}
        {{ question.displayName }}
        {{ question.question }}
        {{ question.score }}
        ================================================ FILE: libs/utils/src/lib/sync/components/questions/common/question/question.component.scss ================================================ mat-card { margin-bottom: 10px; } .questions { display: flex; } .upvote, .downvote { color: #999; } .voted { color: #000; } h2 { margin-top: 30px; } .question-body { white-space: pre-line; } :host.starred > mat-card { background: #e7ff00; .question-body { font-size: 20px; } } .question-body-wrapper { display: flex; max-height: 200px; overflow: hidden; } .questions-score { font-size: 16px; width: 30px; max-height: 30px; background: #811b0a; color: #fff; margin-right: 10px; border-radius: 50%; text-align: center; line-height: 32px; min-width: 30px; } .author { font-weight: bold; margin-bottom: 4px; } ================================================ FILE: libs/utils/src/lib/sync/components/questions/common/question/question.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { QuestionComponent } from './question.component'; describe('QuestionComponent', () => { let component: QuestionComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [QuestionComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(QuestionComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/questions/common/question/question.component.ts ================================================ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Question, QuestionStatus, statuses } from '@codelab/utils/src/lib/sync/components/questions/common/common'; import { QuestionsService } from '@codelab/utils/src/lib/sync/components/questions/common/questions.service'; @Component({ selector: 'codelab-question', templateUrl: './question.component.html', styleUrls: ['./question.component.scss'] }) export class QuestionComponent { @Input() question: Question; @Input() showControls = true; @Output() vote = new EventEmitter(); readonly statuses = statuses; readonly QuestionStatus = QuestionStatus; constructor(public readonly questionsService: QuestionsService) {} } ================================================ FILE: libs/utils/src/lib/sync/components/questions/common/question-list/question-list.component.css ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/questions/common/question-list/question-list.component.html ================================================
        ================================================ FILE: libs/utils/src/lib/sync/components/questions/common/question-list/question-list.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { QuestionsComponent } from '@codelab/utils/src/lib/sync/components/questions/questions.component'; describe('QuestionsComponent', () => { let component: QuestionsComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [QuestionsComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(QuestionsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/questions/common/question-list/question-list.component.ts ================================================ import { Component, EventEmitter, Input, Output } from '@angular/core'; import { Question } from '@codelab/utils/src/lib/sync/components/questions/common/common'; import { animate, style, transition, trigger } from '@angular/animations'; @Component({ selector: 'codelab-question-list', templateUrl: './question-list.component.html', styleUrls: ['./question-list.component.css'], animations: [ trigger('questionList', [ transition(':enter', [ style({ transform: 'scale(0.5)', opacity: 0 }), // initial animate( '0.5s cubic-bezier(.8, -0.6, 0.2, 1.5)', style({ transform: 'scale(1)', opacity: 1 }) ) // final ]), transition(':leave', [ style({ transform: 'scale(1)', opacity: 1, height: '*' }), animate( '1s cubic-bezier(.8, -0.6, 0.2, 1.5)', style({ transform: 'scale(0.5)', opacity: 0, height: '0px', margin: '0px' }) ) ]) ]) ] }) export class QuestionListComponent { @Input() questions: Question[]; @Output() vote = new EventEmitter<{ vote: number; question: Question }>(); constructor() {} trackBy(i, question) { console.log(question.key, '$$'); return question.key; } } ================================================ FILE: libs/utils/src/lib/sync/components/questions/common/questions.service.spec.ts ================================================ import { TestBed } from '@angular/core/testing'; import { QuestionsService } from './questions.service'; describe('QuestionsService', () => { beforeEach(() => TestBed.configureTestingModule({})); it('should be created', () => { const service: QuestionsService = TestBed.inject(QuestionsService); expect(service).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/questions/common/questions.service.ts ================================================ import { Injectable } from '@angular/core'; import { map } from 'rxjs/operators'; import { combineLatest, Observable } from 'rxjs'; import produce from 'immer'; import { Question, QuestionDb, QuestionStatus } from '@codelab/utils/src/lib/sync/components/questions/common/common'; import { SyncDataService } from '@codelab/utils/src/lib/sync/services/sync-data.service'; import { SyncRegistrationService } from '@codelab/utils/src/lib/sync/components/registration/sync-registration.service'; const groupVotesByQuestionId = a => { return Object.values(a).reduce( (a, r) => Object.entries(r).reduce((a, [key, value]) => { a[key] = (a[key] || 0) + value; return a; }, a), {} ); }; @Injectable({ providedIn: 'root' }) export class QuestionsService { readonly key = 'qna7'; readonly presenterObject = this.syncDataService .getPresenterObject(this.key) .withDefault({ requireApproval: true, starredQuestionKey: null }); readonly requireApproval$ = this.presenterObject .valueChanges() .pipe(map(a => a.requireApproval)); private readonly questionsObject = this.syncDataService .getAdminAllUserData(this.key) .withDefault({}); public readonly allQuestions$ = this.questionsObject.valueChanges().pipe( map(values => { return Object.entries(values || {}) .map(([author, value]) => { return Object.entries(value.questions).map(([key, question]) => ({ ...question, author, key })); }) .flat(); }) ); private readonly starredQuestionKeyData = this.presenterObject.object( 'starredQuestionKey' ); private readonly votesKey = 'votes'; private readonly votes$ = this.syncDataService .getAdminAllUserData(this.votesKey) .withDefault({}) .valueChanges() .pipe(map(groupVotesByQuestionId)); private readonly myQuestionsList = this.syncDataService .getCurrentViewerObject(this.key) .list('questions'); readonly myUnapprovedQuestions$ = this.myQuestionsList .valueChanges() .pipe( map((questions: QuestionDb) => Object.values(questions).filter( a => a.status !== QuestionStatus.APPROVED ) ) ); private readonly myVotesObject = this.syncDataService .getCurrentViewerObject(this.votesKey) .withDefault({}); readonly questions$: Observable = combineLatest([ this.allQuestions$, this.votes$, this.myVotesObject.valueChanges(), this.starredQuestionKeyData.valueChanges(), this.presenterObject.valueChanges(), this.registrationService.usersMap$ ]).pipe( map( ([ questions, votes, myVotes, starredQuestionKey, presenterData, usersMap ]) => { return questions.map(q => { const status = q.status; return { ...q, displayName: usersMap[q.author], myVote: myVotes[q.key], score: votes[q.key] || 0, starred: starredQuestionKey === q.key, public: presenterData.requireApproval ? status === QuestionStatus.APPROVED : status === QuestionStatus.APPROVED || status === QuestionStatus.NEW } as Question; }); } ), map(questions => { return questions.sort((a, b) => b.score - a.score || a.time - b.time); }) ); publicQuestions$ = this.questions$.pipe( map(questions => questions.filter(q => q.public)) ); starredQuestion$ = combineLatest([ this.starredQuestionKeyData.valueChanges(), this.questions$ ]).pipe( map(([starredQuestionKey, questions]) => { return starredQuestionKey ? questions.find(q => q.key === starredQuestionKey) : null; }) ); constructor( private readonly syncDataService: SyncDataService, private readonly registrationService: SyncRegistrationService ) {} addQuestion(question: string) { this.myQuestionsList.push({ question, score: 0, time: Date.now(), status: QuestionStatus.NEW }); } setVote(key: string, score: number) { this.myVotesObject.updateWithCallback( produce(upvotes => { upvotes[key] = upvotes[key] === score ? 0 : score; }) ); } updateQuestionStatus(question: Question, status: QuestionStatus) { this.syncDataService .getViewerObject(this.key, question.author) .withDefault({}) .updateWithCallback(a => { a.questions[question.key].status = status; return a; }); } starQuestion(starredQuestionKey: string) { this.starredQuestionKeyData.set(starredQuestionKey); } } ================================================ FILE: libs/utils/src/lib/sync/components/questions/questions-admin/questions-admin.component.css ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/questions/questions-admin/questions-admin.component.html ================================================

        Admin

        settings Require approval ================================================ FILE: libs/utils/src/lib/sync/components/questions/questions-admin/questions-admin.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { QuestionsAdminComponent } from './questions-admin.component'; describe('QuestionsAdminComponent', () => { let component: QuestionsAdminComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [QuestionsAdminComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(QuestionsAdminComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/questions/questions-admin/questions-admin.component.ts ================================================ import { Component } from '@angular/core'; import { QuestionsService } from '@codelab/utils/src/lib/sync/components/questions/common/questions.service'; import { map } from 'rxjs/operators'; import { statuses } from '@codelab/utils/src/lib/sync/components/questions/common/common'; @Component({ selector: 'codelab-questions-admin', templateUrl: './questions-admin.component.html', styleUrls: ['./questions-admin.component.css'], providers: [QuestionsService] }) export class QuestionsAdminComponent { readonly statuses = statuses; requireApproval: boolean; readonly questionsByStatus$ = this.questionsService.questions$.pipe( map(questions => { return questions.reduce((result, question) => { result[question.status] = result[question.status] || []; result[question.status].push(question); return result; }, {}); }) ); constructor(readonly questionsService: QuestionsService) {} } ================================================ FILE: libs/utils/src/lib/sync/components/questions/questions-presenter/questions-presenter.component.css ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/questions/questions-presenter/questions-presenter.component.html ================================================

        Questions

        Waiting for questions...
        ================================================ FILE: libs/utils/src/lib/sync/components/questions/questions-presenter/questions-presenter.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { QuestionsPresenterComponent } from './questions-presenter.component'; describe('QuestionsPresenterComponent', () => { let component: QuestionsPresenterComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [QuestionsPresenterComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(QuestionsPresenterComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/questions/questions-presenter/questions-presenter.component.ts ================================================ import { Component } from '@angular/core'; import { QuestionsService } from '@codelab/utils/src/lib/sync/components/questions/common/questions.service'; @Component({ selector: 'codelab-questions-presenter', templateUrl: './questions-presenter.component.html', styleUrls: ['./questions-presenter.component.css'] }) export class QuestionsPresenterComponent { constructor(public readonly questionsService: QuestionsService) {} } ================================================ FILE: libs/utils/src/lib/sync/components/questions/questions-viewer/questions-viewer.component.css ================================================ textarea, mat-form-field { width: 100%; } .question-body { white-space: pre-line; } .question-form { margin-bottom: 20px; } mat-expansion-panel { margin-bottom: 20px; } ================================================ FILE: libs/utils/src/lib/sync/components/questions/questions-viewer/questions-viewer.component.html ================================================

        Ask a question:

        Your question:
        Your questions pending approval: {{ unapproved.length }}

        All questions:

        ================================================ FILE: libs/utils/src/lib/sync/components/questions/questions-viewer/questions-viewer.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { QuestionsViewerComponent } from './questions-viewer.component'; describe('QuestionsViewerComponent', () => { let component: QuestionsViewerComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [QuestionsViewerComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(QuestionsViewerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/questions/questions-viewer/questions-viewer.component.ts ================================================ import { Component } from '@angular/core'; import { QuestionsService } from '@codelab/utils/src/lib/sync/components/questions/common/questions.service'; @Component({ selector: 'codelab-questions-viewer', templateUrl: './questions-viewer.component.html', styleUrls: ['./questions-viewer.component.css'] }) export class QuestionsViewerComponent { constructor(public readonly questionsService: QuestionsService) {} addQuestion(input: HTMLTextAreaElement) { const question = input.value.trim(); if (question !== '') { this.questionsService.addQuestion(question); input.value = ''; } } } ================================================ FILE: libs/utils/src/lib/sync/components/questions/questions.component.css ================================================ .question { margin-bottom: 4px; } .upvote.voted { background: #a6e0c1; } .downvote.voted { background: #ffaf9e; } ================================================ FILE: libs/utils/src/lib/sync/components/questions/questions.component.html ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/questions/questions.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { QuestionsComponent } from './questions.component'; describe('QuestionsComponent', () => { let component: QuestionsComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [QuestionsComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(QuestionsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/questions/questions.component.ts ================================================ import { Component } from '@angular/core'; import { QuestionsService } from '@codelab/utils/src/lib/sync/components/questions/common/questions.service'; import { SyncDataService } from '@codelab/utils/src/lib/sync/services/sync-data.service'; @Component({ selector: 'codelab-questions', templateUrl: './questions.component.html', styleUrls: ['./questions.component.css'], providers: [QuestionsService, SyncDataService] }) export class QuestionsComponent { constructor(public readonly questionsService: QuestionsService) {} } ================================================ FILE: libs/utils/src/lib/sync/components/questions/questions.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatExpansionModule } from '@angular/material/expansion'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatTabsModule } from '@angular/material/tabs'; import { QuestionsComponent } from '@codelab/utils/src/lib/sync/components/questions/questions.component'; import { QuestionsAdminComponent } from '@codelab/utils/src/lib/sync/components/questions/questions-admin/questions-admin.component'; import { SyncDirectivesModule } from '@codelab/utils/src/lib/sync/directives/sync-directives.module'; import { QuestionsViewerComponent } from './questions-viewer/questions-viewer.component'; import { QuestionsPresenterComponent } from './questions-presenter/questions-presenter.component'; import { QuestionComponent } from './common/question/question.component'; import { QuestionListComponent } from '@codelab/utils/src/lib/sync/components/questions/common/question-list/question-list.component'; import { FormsModule } from '@angular/forms'; @NgModule({ imports: [ CommonModule, MatButtonModule, MatFormFieldModule, MatCardModule, MatTabsModule, MatInputModule, MatCheckboxModule, SyncDirectivesModule, MatIconModule, FormsModule, MatExpansionModule ], declarations: [ QuestionsComponent, QuestionsAdminComponent, QuestionsViewerComponent, QuestionsPresenterComponent, QuestionComponent, QuestionListComponent ], exports: [QuestionsComponent] }) export class QuestionsModule {} ================================================ FILE: libs/utils/src/lib/sync/components/registration/registration-admin/registration-admin.component.css ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/registration/registration-admin/registration-admin.component.html ================================================
        Display names
        Enable registration
        • {{ user.name }}
        ================================================ FILE: libs/utils/src/lib/sync/components/registration/registration-admin/registration-admin.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RegistrationAdminComponent } from './registration-admin.component'; describe('RegistrationAdminComponent', () => { let component: RegistrationAdminComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [RegistrationAdminComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(RegistrationAdminComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/registration/registration-admin/registration-admin.component.ts ================================================ import { Component } from '@angular/core'; import { SyncRegistrationService } from '@codelab/utils/src/lib/sync/components/registration/sync-registration.service'; @Component({ selector: 'codelab-registration-admin', templateUrl: './registration-admin.component.html', styleUrls: ['./registration-admin.component.css'] }) export class RegistrationAdminComponent { url: string; displayNames: boolean; isRegistrationEnabled: boolean; constructor(readonly registrationService: SyncRegistrationService) {} drop(userId: string) { this.registrationService.drop(userId); } } ================================================ FILE: libs/utils/src/lib/sync/components/registration/registration-presenter/registration-presenter.component.css ================================================ .bunny { background-image: url('../../../images/bunny.png'); width: 406px; height: 442px; margin: 0 auto; } ================================================ FILE: libs/utils/src/lib/sync/components/registration/registration-presenter/registration-presenter.component.html ================================================

        Users connected: {{ users.length }}

        • {{ user.name }}

        Waiting for users to connect...

        Registration is over

        ================================================ FILE: libs/utils/src/lib/sync/components/registration/registration-presenter/registration-presenter.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RegistrationPresenterComponent } from './registration-presenter.component'; describe('RegistrationPresenterComponent', () => { let component: RegistrationPresenterComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [RegistrationPresenterComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(RegistrationPresenterComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/registration/registration-presenter/registration-presenter.component.ts ================================================ import { Component } from '@angular/core'; import { SyncRegistrationService } from '@codelab/utils/src/lib/sync/components/registration/sync-registration.service'; @Component({ selector: 'codelab-registration-presenter', templateUrl: './registration-presenter.component.html', styleUrls: ['./registration-presenter.component.css'] }) export class RegistrationPresenterComponent { constructor(readonly registrationService: SyncRegistrationService) {} } ================================================ FILE: libs/utils/src/lib/sync/components/registration/registration-viewer/registration-viewer.component.css ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/registration/registration-viewer/registration-viewer.component.html ================================================

        What is your name?

        Your name is: {{ user }}

        Registration is over

        ================================================ FILE: libs/utils/src/lib/sync/components/registration/registration-viewer/registration-viewer.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RegistrationViewerComponent } from './registration-viewer.component'; describe('RegistrationViewerComponent', () => { let component: RegistrationViewerComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [RegistrationViewerComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(RegistrationViewerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/registration/registration-viewer/registration-viewer.component.ts ================================================ import { Component } from '@angular/core'; import { SyncRegistrationService } from '@codelab/utils/src/lib/sync/components/registration/sync-registration.service'; @Component({ selector: 'codelab-registration-viewer', templateUrl: './registration-viewer.component.html', styleUrls: ['./registration-viewer.component.css'] }) export class RegistrationViewerComponent { constructor(readonly registrationService: SyncRegistrationService) {} } ================================================ FILE: libs/utils/src/lib/sync/components/registration/registration.component.css ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/registration/registration.component.html ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/registration/registration.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { RegistrationComponent } from './registration.component'; describe('RegistrationComponent', () => { let component: RegistrationComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [RegistrationComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(RegistrationComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/registration/registration.component.ts ================================================ import { Component } from '@angular/core'; import { SyncRegistrationService } from '@codelab/utils/src/lib/sync/components/registration/sync-registration.service'; @Component({ selector: 'codelab-registration', templateUrl: './registration.component.html', styleUrls: ['./registration.component.css'], providers: [SyncRegistrationService] }) export class RegistrationComponent {} ================================================ FILE: libs/utils/src/lib/sync/components/registration/sync-registration.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { RegistrationComponent } from '@codelab/utils/src/lib/sync/components/registration/registration.component'; import { RegistrationViewerComponent } from '@codelab/utils/src/lib/sync/components/registration/registration-viewer/registration-viewer.component'; import { RegistrationPresenterComponent } from '@codelab/utils/src/lib/sync/components/registration/registration-presenter/registration-presenter.component'; import { RegistrationAdminComponent } from '@codelab/utils/src/lib/sync/components/registration/registration-admin/registration-admin.component'; import { FormsModule } from '@angular/forms'; import { SyncDirectivesModule } from '@codelab/utils/src/lib/sync/directives/sync-directives.module'; import { SyncJoinInstructionsModule } from '@codelab/utils/src/lib/sync/components/sync-join-instructions/sync-join-instructions.module'; import { MatButtonModule } from '@angular/material/button'; @NgModule({ declarations: [ RegistrationComponent, RegistrationViewerComponent, RegistrationAdminComponent, RegistrationPresenterComponent ], exports: [RegistrationComponent], imports: [ SyncJoinInstructionsModule, CommonModule, FormsModule, MatFormFieldModule, MatInputModule, SyncDirectivesModule, MatCheckboxModule, MatButtonModule ] }) export class SyncRegistrationModule {} ================================================ FILE: libs/utils/src/lib/sync/components/registration/sync-registration.service.spec.ts ================================================ import { TestBed } from '@angular/core/testing'; import { SyncRegistrationService } from './sync-registration.service'; describe('SyncRegistrationService', () => { beforeEach(() => TestBed.configureTestingModule({})); it('should be created', () => { const service: SyncRegistrationService = TestBed.inject( SyncRegistrationService ); expect(service).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/registration/sync-registration.service.ts ================================================ import { Injectable } from '@angular/core'; import { map } from 'rxjs/operators'; import { SyncDataService } from '@codelab/utils/src/lib/sync/services/sync-data.service'; @Injectable({ providedIn: 'root' }) export class SyncRegistrationService { readonly key = 'name'; name = ''; readonly userData = this.syncDataService.getAdminAllUserData(this.key); readonly usersMap$ = this.userData.valueChanges(); readonly users$ = this.usersMap$.pipe( map(a => { return a ? Object.entries(a).map(([userId, name]) => ({ userId, name })) : []; }) ); readonly nameObject = this.syncDataService.getCurrentViewerObject(this.key); readonly currentUser$ = this.nameObject.valueChanges(); readonly registrationConfig$ = this.syncDataService .getPresenterObject('registration') .withDefault({ shouldDisplayNames: true, isRegistrationEnabled: true }) .valueChanges(); readonly shouldDisplayNames$ = this.registrationConfig$.pipe( map(a => a.shouldDisplayNames) ); readonly isRegistrationEnabled$ = this.registrationConfig$.pipe( map(a => a.isRegistrationEnabled) ); constructor(private readonly syncDataService: SyncDataService) {} save() { const name = this.name.trim(); if (name !== '') { this.nameObject.set(name); } } clear() { this.nameObject.set(null); } drop(userId: string) { this.syncDataService.getViewerObject(this.key, userId).set(null); } } ================================================ FILE: libs/utils/src/lib/sync/components/sync-code-game/sync-code-game-admin/sync-code-game-admin.component.css ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/sync-code-game/sync-code-game-admin/sync-code-game-admin.component.html ================================================ {{ this.registrationService.users$ | async | json }} ================================================ FILE: libs/utils/src/lib/sync/components/sync-code-game/sync-code-game-admin/sync-code-game-admin.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SyncCodeGameAdminComponent } from './sync-code-game-admin.component'; describe('SyncCodeGameAdminComponent', () => { let component: SyncCodeGameAdminComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SyncCodeGameAdminComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SyncCodeGameAdminComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/sync-code-game/sync-code-game-admin/sync-code-game-admin.component.ts ================================================ import { Component } from '@angular/core'; import { SyncRegistrationService } from '@codelab/utils/src/lib/sync/components/registration/sync-registration.service'; @Component({ // tslint:disable-next-line:component-selector selector: 'slides-sync-code-game-admin', templateUrl: './sync-code-game-admin.component.html', styleUrls: ['./sync-code-game-admin.component.css'] }) export class SyncCodeGameAdminComponent { constructor(readonly registrationService: SyncRegistrationService) {} } ================================================ FILE: libs/utils/src/lib/sync/components/sync-code-game/sync-code-game-presenter/sync-code-game-presenter.component.css ================================================ .selected { background: #ddd; } ================================================ FILE: libs/utils/src/lib/sync/components/sync-code-game/sync-code-game-presenter/sync-code-game-presenter.component.html ================================================
        ================================================ FILE: libs/utils/src/lib/sync/components/sync-code-game/sync-code-game-presenter/sync-code-game-presenter.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SyncCodeGamePresenterComponent } from './sync-code-game-presenter.component'; describe('SyncCodeGamePresenterComponent', () => { let component: SyncCodeGamePresenterComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SyncCodeGamePresenterComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SyncCodeGamePresenterComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/sync-code-game/sync-code-game-presenter/sync-code-game-presenter.component.ts ================================================ import { Component } from '@angular/core'; import { SyncRegistrationService } from '@codelab/utils/src/lib/sync/components/registration/sync-registration.service'; import { SyncCodeGameService } from '@codelab/utils/src/lib/sync/components/sync-code-game/sync-code-game.service'; import { combineLatest, Subject } from 'rxjs'; import { map } from 'rxjs/operators'; @Component({ // tslint:disable-next-line:component-selector selector: 'slides-sync-code-game-presenter', templateUrl: './sync-code-game-presenter.component.html', styleUrls: ['./sync-code-game-presenter.component.css'] }) export class SyncCodeGamePresenterComponent { readonly selectedUserSubject = new Subject(); constructor( readonly codeGameService: SyncCodeGameService, readonly registrationService: SyncRegistrationService ) {} selectedUser$ = combineLatest([ this.codeGameService.allStatuses$, this.selectedUserSubject ]).pipe( map(([users, user]) => { return users.find(u => u.name === user); }) ); } ================================================ FILE: libs/utils/src/lib/sync/components/sync-code-game/sync-code-game-viewer/sync-code-game-viewer.component.css ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/sync-code-game/sync-code-game-viewer/sync-code-game-viewer.component.html ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/sync-code-game/sync-code-game-viewer/sync-code-game-viewer.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SyncCodeGameViewerComponent } from './sync-code-game-viewer.component'; describe('SyncCodeGameViewerComponent', () => { let component: SyncCodeGameViewerComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SyncCodeGameViewerComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SyncCodeGameViewerComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/sync-code-game/sync-code-game-viewer/sync-code-game-viewer.component.ts ================================================ import { Component, OnInit } from '@angular/core'; import { SyncCodeGameService } from '@codelab/utils/src/lib/sync/components/sync-code-game/sync-code-game.service'; import { TestRunResult } from '@codelab/utils/src/lib/test-results/common'; declare const require; @Component({ // tslint:disable-next-line:component-selector selector: 'slides-sync-code-game-viewer', templateUrl: './sync-code-game-viewer.component.html', styleUrls: ['./sync-code-game-viewer.component.css'] }) export class SyncCodeGameViewerComponent implements OnInit { questions = [ { title: 'Write an add function', code: `export function result(): number { return 0; } `, tests: ` import {result} from './main'; describe('result', () => { it('Function "result" exists', () => { chai.expect(typeof result).to.equal('function'); }); it('Is greater than 0', () => { chai.expect(result()).to.be.gte(0);}); it('Is greater than 1', () => { chai.expect(result()).to.be.gte(1); }); it('Is greater than 2', () => { chai.expect(result()).to.be.gte(2); }); it('Is greater than 3', () => { chai.expect(result()).to.be.gte(3); }); it('Is greater than 4', () => { chai.expect(result()).to.be.gte(4); }); it('Is greater than 5', () => { chai.expect(result()).to.be.gte(5); }); it('Is greater than 6', () => { chai.expect(result()).to.be.gte(6); }); it('Is greater than 7', () => { chai.expect(result()).to.be.gte(7); }); it('Is greater than 8', () => { chai.expect(result()).to.be.gte(8); }); it('Is greater than 9', () => { chai.expect(result()).to.be.gte(9); }); });` }, { title: 'Write an subtract function', code: `function subtract(a,b){ }`, tests: ` describe('tests', () => { it('Function subtract exists', () => { chai.expect(typeof subtract).to.equal('function'); }); it('Subtracts two numbers', () => { chai.expect(subtract(2,2)).to.equal(0); }); it('subtracts two other numbers', () => { chai.expect(subtract(24,66)).to.equal(-42); }); });` } ]; code = `function add(a,b){ return a+b; }`; tests = require('!!raw-loader!../tests.ts'); maxScore = 0; update(result: TestRunResult) { const score = result.error ? 0 : result.tests.filter(t => t.pass).length; const maxScore = Math.max(this.maxScore, score); this.maxScore = maxScore; this.codeGameService.viewerStatus.set({ score, maxScore, code: this.questions[0].code }); } constructor(private readonly codeGameService: SyncCodeGameService) {} ngOnInit() {} } ================================================ FILE: libs/utils/src/lib/sync/components/sync-code-game/sync-code-game.component.css ================================================ :host { display: block; height: 100%; } ================================================ FILE: libs/utils/src/lib/sync/components/sync-code-game/sync-code-game.component.html ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/sync-code-game/sync-code-game.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SyncCodeGameComponent } from './sync-code-game.component'; describe('CodeInTheDarkComponent', () => { let component: SyncCodeGameComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SyncCodeGameComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SyncCodeGameComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/sync-code-game/sync-code-game.component.ts ================================================ import { Component } from '@angular/core'; @Component({ // tslint:disable-next-line:component-selector selector: 'sync-code-editor', templateUrl: './sync-code-game.component.html', styleUrls: ['./sync-code-game.component.css'] }) export class SyncCodeGameComponent {} ================================================ FILE: libs/utils/src/lib/sync/components/sync-code-game/sync-code-game.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SyncCodeGameComponent } from './sync-code-game.component'; import { CodeDemoModule } from '@codelab/code-demos'; import { FormsModule } from '@angular/forms'; import { MatIconModule } from '@angular/material/icon'; import { SimpleTestsProgressModule } from '@codelab/utils/src/lib/test-results/simple-tests-progress/simple-tests-progress.module'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { MatTabsModule } from '@angular/material/tabs'; import { TestResultsModule } from '@codelab/utils/src/lib/test-results/test-results.module'; import { TypescriptCheckerRunnerModule } from '@codelab/utils/src/lib/sandbox-runner/typescript-checker-runner/typescript-checker-runner.module'; import { SandboxRunnerModule } from '@codelab/utils/src/lib/sandbox-runner/sandbox-runner.module'; import { SyncDirectivesModule } from '@codelab/utils/src/lib/sync/directives/sync-directives.module'; import { SyncCodeGamePresenterComponent } from './sync-code-game-presenter/sync-code-game-presenter.component'; import { SyncCodeGameAdminComponent } from './sync-code-game-admin/sync-code-game-admin.component'; import { SyncCodeGameViewerComponent } from './sync-code-game-viewer/sync-code-game-viewer.component'; import { MatButtonModule } from '@angular/material/button'; @NgModule({ declarations: [ SyncCodeGameComponent, SyncCodeGamePresenterComponent, SyncCodeGameAdminComponent, SyncCodeGameViewerComponent ], exports: [SyncCodeGameComponent], imports: [ CommonModule, CodeDemoModule, FormsModule, MatIconModule, SimpleTestsProgressModule, MatCheckboxModule, MatTabsModule, TestResultsModule, TypescriptCheckerRunnerModule, SandboxRunnerModule, SyncDirectivesModule, MatButtonModule ] }) export class SyncCodeGameModule {} ================================================ FILE: libs/utils/src/lib/sync/components/sync-code-game/sync-code-game.service.spec.ts ================================================ import { TestBed } from '@angular/core/testing'; import { SyncCodeGameService } from './sync-code-game.service'; describe('SyncCodeGameService', () => { let service: SyncCodeGameService; beforeEach(() => { TestBed.configureTestingModule({}); service = TestBed.inject(SyncCodeGameService); }); it('should be created', () => { expect(service).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/sync-code-game/sync-code-game.service.ts ================================================ import { Injectable } from '@angular/core'; import { SyncDataService } from '@codelab/utils/src/lib/sync/services/sync-data.service'; import { map } from 'rxjs/operators'; @Injectable({ providedIn: 'root' }) export class SyncCodeGameService { private readonly key = 'coding'; readonly viewerStatus = this.syncDataService .getCurrentViewerObject(this.key) .object('session'); private readonly allStatus = this.syncDataService.getAdminAllUserData( this.key ); readonly allStatuses$ = this.allStatus.valueChanges().pipe( map(a => { return Object.entries(a || {}).map(([name, value]) => ({ name, status: value.session })); }) ); constructor(private readonly syncDataService: SyncDataService) {} } ================================================ FILE: libs/utils/src/lib/sync/components/sync-code-game/tests.ts ================================================ describe('a', () => { it('b', () => {}); it('c', () => {}); }); ================================================ FILE: libs/utils/src/lib/sync/components/sync-join-instructions/sync-join-instructions.component.css ================================================ ================================================ FILE: libs/utils/src/lib/sync/components/sync-join-instructions/sync-join-instructions.component.html ================================================

        {{ joinUrl$ | async }}

        ================================================ FILE: libs/utils/src/lib/sync/components/sync-join-instructions/sync-join-instructions.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SyncJoinInstructionsComponent } from './sync-join-instructions.component'; import { SyncJoinInstructionsModule } from '@codelab/utils/src/lib/sync/components/sync-join-instructions/sync-join-instructions.module'; import { getSyncDbService } from '@codelab/utils/src/lib/testing/mocks/sync-db-service'; import { getMockAngularFireProviders } from '@codelab/utils/src/lib/testing/mocks/angular-fire'; describe('SyncJoinInstructionsComponent', () => { let component: SyncJoinInstructionsComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [SyncJoinInstructionsModule], providers: [...getSyncDbService(), ...getMockAngularFireProviders()] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SyncJoinInstructionsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/sync-join-instructions/sync-join-instructions.component.ts ================================================ import { Component } from '@angular/core'; import { SyncDataService } from '@codelab/utils/src/lib/sync/services/sync-data.service'; @Component({ selector: 'codelab-sync-join-instructions', templateUrl: './sync-join-instructions.component.html', styleUrls: ['./sync-join-instructions.component.css'] }) export class SyncJoinInstructionsComponent { readonly key = 'joinUrl'; readonly defaultValue = 'kirjs.com/start'; url: string; readonly joinUrl$ = this.syncDataService .getPresenterObject('registration') .object(this.key) .withDefault(this.defaultValue) .valueChanges(); constructor(private readonly syncDataService: SyncDataService) {} } ================================================ FILE: libs/utils/src/lib/sync/components/sync-join-instructions/sync-join-instructions.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatInputModule } from '@angular/material/input'; import { SyncJoinInstructionsComponent } from './sync-join-instructions.component'; import { FormsModule } from '@angular/forms'; import { SyncDirectivesModule } from '@codelab/utils/src/lib/sync/directives/sync-directives.module'; @NgModule({ declarations: [SyncJoinInstructionsComponent], exports: [SyncJoinInstructionsComponent], imports: [ CommonModule, SyncDirectivesModule, MatFormFieldModule, MatInputModule, FormsModule ] }) export class SyncJoinInstructionsModule {} ================================================ FILE: libs/utils/src/lib/sync/components/sync-sessions/sync-sessions.component.css ================================================ table { width: 100%; } ================================================ FILE: libs/utils/src/lib/sync/components/sync-sessions/sync-sessions.component.html ================================================
        Name {{ session.config.name }} Key {{ session.key }} Owner {{ session.config.owner }} active
        ================================================ FILE: libs/utils/src/lib/sync/components/sync-sessions/sync-sessions.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SyncSessionsComponent } from './sync-sessions.component'; describe('SyncSessionsComponent', () => { let component: SyncSessionsComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SyncSessionsComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SyncSessionsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/components/sync-sessions/sync-sessions.component.ts ================================================ import { Component } from '@angular/core'; import { SyncDbService } from '@codelab/utils/src/lib/sync/services/sync-db.service'; import { SyncDataService } from '@codelab/utils/src/lib/sync/services/sync-data.service'; import { SyncSessionService } from '@codelab/utils/src/lib/sync/services/sync-session.service'; @Component({ selector: 'codelab-sync-sessions', templateUrl: './sync-sessions.component.html', styleUrls: ['./sync-sessions.component.css'], providers: [SyncDataService, SyncSessionService, SyncDbService] }) export class SyncSessionsComponent { readonly displayedColumns = ['name', 'owner', 'key', 'active', 'actions']; constructor(readonly sessionsService: SyncSessionService) {} remove(key: string) { this.sessionsService.remove(key); } } ================================================ FILE: libs/utils/src/lib/sync/components/sync-sessions/sync-sessions.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatInputModule } from '@angular/material/input'; import { MatTableModule } from '@angular/material/table'; import { SyncSessionsComponent } from './sync-sessions.component'; @NgModule({ declarations: [SyncSessionsComponent], entryComponents: [SyncSessionsComponent], imports: [ CommonModule, MatCardModule, MatTableModule, MatButtonModule, MatInputModule ] }) export class SyncSessionsModule {} ================================================ FILE: libs/utils/src/lib/sync/directives/all-viewer-values.directive.ts ================================================ import { AfterViewInit, Directive, Input, OnDestroy, Optional } from '@angular/core'; import { NgControl } from '@angular/forms'; import { Subject } from 'rxjs'; @Directive({ // tslint:disable-next-line:directive-selector selector: '[syncAllUserValues]', exportAs: 'allViewerValues' }) export class AllViewerValuesDirective implements AfterViewInit, OnDestroy { @Input() syncAllUserValues: string; values: { key: string; value: T }[]; private onDestroy = new Subject(); constructor(@Optional() private readonly control: NgControl) {} ngOnDestroy(): void { this.onDestroy.next(); this.onDestroy.complete(); } ngAfterViewInit() { // this.sync.statusChange$.pipe( // status => status === SyncStatus.PRESENTING // ) // this.sync.whenPresenting$.subscribe(() => { // this.sync.getAllViewersValues(this.syncAllUserValues).subscribe(values => { // this.values = Object.entries(values || {}).map(([key, value]) => ({key, value})); // }); // }); } } ================================================ FILE: libs/utils/src/lib/sync/directives/is-status.directive.ts ================================================ import { Directive, OnInit, TemplateRef, ViewContainerRef } from '@angular/core'; import { SyncStatus } from '@codelab/utils/src/lib/sync/common'; import { SyncSessionService } from '@codelab/utils/src/lib/sync/services/sync-session.service'; import { ReplaySubject } from 'rxjs'; import { takeUntil } from 'rxjs/operators'; export class SyncIsStatusDirective implements OnInit { protected readonly status: SyncStatus = SyncStatus.OFF; private readonly destroy = new ReplaySubject(1); constructor( private readonly viewContainer: ViewContainerRef, private readonly templateRef: TemplateRef, private readonly syncSession: SyncSessionService ) {} ngOnInit() { this.syncSession.status$ .pipe(takeUntil(this.destroy)) .subscribe((status: SyncStatus) => { this.toggleContentDisplay(status === this.status); }); } toggleContentDisplay(isDisplayed: boolean) { this.viewContainer.clear(); if (isDisplayed) { this.viewContainer.createEmbeddedView(this.templateRef).markForCheck(); } } } @Directive({ // tslint:disable-next-line:directive-selector selector: '[isViewing]' }) export class SyncIsViewingDirective extends SyncIsStatusDirective { protected readonly status = SyncStatus.VIEWING; constructor( viewContainer: ViewContainerRef, templateRef: TemplateRef, syncSession: SyncSessionService ) { super(viewContainer, templateRef, syncSession); } } @Directive({ // tslint:disable-next-line:directive-selector selector: '[isPresenting]' }) export class SyncIsPresentingDirective extends SyncIsStatusDirective { protected readonly status = SyncStatus.PRESENTING; constructor( viewContainer: ViewContainerRef, templateRef: TemplateRef, syncSession: SyncSessionService ) { super(viewContainer, templateRef, syncSession); } } @Directive({ // tslint:disable-next-line:directive-selector selector: '[isAdmin]' }) export class SyncIsAdminDirective extends SyncIsStatusDirective { protected readonly status = SyncStatus.ADMIN; constructor( viewContainer: ViewContainerRef, templateRef: TemplateRef, syncSession: SyncSessionService ) { super(viewContainer, templateRef, syncSession); } } @Directive({ // tslint:disable-next-line:directive-selector selector: '[isOff]' }) export class SyncIsOffDirective extends SyncIsStatusDirective { protected readonly status = SyncStatus.OFF; constructor( viewContainer: ViewContainerRef, templateRef: TemplateRef, syncSession: SyncSessionService ) { super(viewContainer, templateRef, syncSession); } } ================================================ FILE: libs/utils/src/lib/sync/directives/sync-directives.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SyncIsAdminDirective, SyncIsPresentingDirective, SyncIsViewingDirective, SyncIsOffDirective } from '@codelab/utils/src/lib/sync/directives/is-status.directive'; import { SyncPresenterValueDirective } from '@codelab/utils/src/lib/sync/directives/sync-presenter-value.directive'; import { SyncViewerValueDirective } from '@codelab/utils/src/lib/sync/directives/sync-viewer-value.directive'; import { SyncUserValueDirective } from '@codelab/utils/src/lib/sync/directives/sync-user-value.directive'; @NgModule({ declarations: [ SyncIsViewingDirective, SyncIsPresentingDirective, SyncIsAdminDirective, SyncPresenterValueDirective, SyncViewerValueDirective, SyncIsOffDirective, SyncUserValueDirective ], exports: [ SyncIsViewingDirective, SyncIsPresentingDirective, SyncIsAdminDirective, SyncPresenterValueDirective, SyncViewerValueDirective, SyncIsOffDirective, SyncUserValueDirective ], imports: [CommonModule] }) export class SyncDirectivesModule {} ================================================ FILE: libs/utils/src/lib/sync/directives/sync-is-presenting.directive.spec.ts ================================================ import { SyncIsPresentingDirective } from '@codelab/utils/src/lib/sync/directives/is-status.directive'; describe('SyncIsPresentingDirective', () => { it('should create an instance', () => { // const directive = new SyncIsPresentingDirective(); // expect(directive).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/directives/sync-presenter-value.directive.ts ================================================ import { Directive, Input, OnDestroy, OnInit, Optional } from '@angular/core'; import { NgControl } from '@angular/forms'; import { Subject } from 'rxjs'; import { PresenterConfig, SyncDataService } from '@codelab/utils/src/lib/sync/services/sync-data.service'; import { filter, map, takeUntil } from 'rxjs/operators'; @Directive({ // tslint:disable-next-line:directive-selector selector: '[syncPresenterValue]', exportAs: 'presenterValue' }) export class SyncPresenterValueDirective< K extends keyof PresenterConfig, T extends PresenterConfig[K] > implements OnInit, OnDestroy { @Input() syncPresenterValue: K; @Input() syncPresenterValueDefault: T; private onDestroy$ = new Subject(); constructor( private readonly syncDataService: SyncDataService, @Optional() private readonly control: NgControl ) {} ngOnDestroy(): void { this.onDestroy$.next(); this.onDestroy$.complete(); } ngOnInit() { if (!this.control) { throw new Error( 'syncPresenterValue directive must be attached to a formControl' ); } const data = this.syncDataService.getPresenterObject( this.syncPresenterValue ); data .valueChanges() .pipe( map(a => (a === undefined ? this.syncPresenterValueDefault : a)), takeUntil(this.onDestroy$) ) .subscribe(value => { this.control.valueAccessor.writeValue(value); }); this.control.valueChanges .pipe( filter(a => a !== undefined), takeUntil(this.onDestroy$) ) .subscribe(newValue => { data.set(newValue); }); } } ================================================ FILE: libs/utils/src/lib/sync/directives/sync-user-value.directive.ts ================================================ import { Directive, Input, OnDestroy, OnInit, Optional } from '@angular/core'; import { NgControl } from '@angular/forms'; import { Subject } from 'rxjs'; import { distinctUntilChanged, filter, first, mergeMapTo, takeUntil, tap } from 'rxjs/operators'; import { SyncDbService } from '@codelab/utils/src/lib/sync/services/sync-db.service'; import { LoginService } from '@codelab/firebase-login'; import { SyncDb } from '@codelab/utils/src/lib/sync/services/sync-data.service'; @Directive({ // tslint:disable-next-line:directive-selector selector: '[syncUserValue]', exportAs: 'presenterValue' }) export class SyncUserValueDirective implements OnInit, OnDestroy { @Input() syncUserValue: string; @Input() syncUserValueDefault: T; private onDestroy$ = new Subject(); constructor( private readonly dbService: SyncDbService, private readonly loginService: LoginService, @Optional() private readonly control: NgControl ) {} ngOnDestroy(): void { this.onDestroy$.next(); this.onDestroy$.complete(); } ngOnInit() { if (!this.control) { throw new Error( 'syncPresenterValue directive must be attached to a formControl' ); } const syncDataObject = this.dbService .object('user-data') .object(this.loginService.uid$) .object(this.syncUserValue); const dataValue$ = syncDataObject.valueChanges(); dataValue$.pipe(takeUntil(this.onDestroy$)).subscribe(value => { console.log('FROM STORE', value); this.control.valueAccessor.writeValue(value); }); dataValue$ .pipe( tap(a => { console.log('data', a); }), first(), mergeMapTo(this.control.valueChanges), filter(a => a !== undefined), distinctUntilChanged(), takeUntil(this.onDestroy$) ) .subscribe(newValue => { console.log('FROM CONTROL', newValue); syncDataObject.set(newValue); }); } } ================================================ FILE: libs/utils/src/lib/sync/directives/sync-viewer-value.directive.ts ================================================ import { Directive, Input, OnDestroy, OnInit, Optional } from '@angular/core'; import { NgControl } from '@angular/forms'; import { Subject } from 'rxjs'; import { SyncDataService, ViewerConfig } from '@codelab/utils/src/lib/sync/services/sync-data.service'; import { filter, map, takeUntil } from 'rxjs/operators'; @Directive({ // tslint:disable-next-line:directive-selector selector: '[syncViewerValue]', exportAs: 'viewerValue' }) export class SyncViewerValueDirective implements OnDestroy, OnInit { @Input() syncViewerValue: keyof ViewerConfig; @Input() syncViewerValueDefault: T; private onDestroy$ = new Subject(); constructor( private readonly syncDataService: SyncDataService, @Optional() private readonly control: NgControl ) {} ngOnDestroy(): void { this.onDestroy$.next(); this.onDestroy$.complete(); } ngOnInit() { if (!this.control) { throw new Error( 'syncPresenterValue directive must be attached to a formControl' ); } const data = this.syncDataService.getCurrentViewerObject( this.syncViewerValue ); data .valueChanges() .pipe( takeUntil(this.onDestroy$), map(a => (a === undefined ? this.syncViewerValueDefault : a)) ) .subscribe(value => { this.control.valueAccessor.writeValue(value); }); this.control.valueChanges .pipe( filter(a => a !== undefined), takeUntil(this.onDestroy$) ) .subscribe(newValue => { data.set(newValue); }); } } ================================================ FILE: libs/utils/src/lib/sync/services/common.ts ================================================ export interface FirebaseInfo { connected: string; serverTimeOffset: string; } export interface FirebaseDb { '.info': FirebaseInfo; } export type ArrayElement = A extends readonly (infer T)[] ? T : never; export function mergeValues(value, defaultValue) { if (value === null) { return defaultValue; } if (typeof value === 'object' && typeof defaultValue === 'object') { return { ...defaultValue, ...value }; } return typeof value === 'undefined' ? defaultValue : value; } ================================================ FILE: libs/utils/src/lib/sync/services/firebase-info.service.ts ================================================ import { Injectable } from '@angular/core'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { FirebaseDb } from '@codelab/utils/src/lib/sync/services/common'; import { SyncDbService } from '@codelab/utils/src/lib/sync/services/sync-db.service'; @Injectable({ providedIn: 'root' }) export class FirebaseInfoService { readonly online$ = this.syncDb .object('.info') .object('connected') .valueChanges(); readonly offset$: Observable = this.syncDb .object('.info') .object('serverTimeOffset') .valueChanges() .pipe(map(a => Number(a))); constructor(private readonly syncDb: SyncDbService) {} } ================================================ FILE: libs/utils/src/lib/sync/services/sync-data.service.ts ================================================ import { Injectable } from '@angular/core'; import { filter } from 'rxjs/operators'; import { Observable } from 'rxjs'; import { SyncSessionService } from '@codelab/utils/src/lib/sync/services/sync-session.service'; import { SyncDataObject, SyncDbService } from '@codelab/utils/src/lib/sync/services/sync-db.service'; import { FirebaseDb } from '@codelab/utils/src/lib/sync/services/common'; import { QuestionDb } from '@codelab/utils/src/lib/sync/components/questions/common/common'; interface PresenterPollConfig { enabled: boolean; startTime: number; } export interface UserVotes { [key: string]: number; } interface ViewerPollConfig { answer: number; time: number; } export interface PresenterConfig { qna7: { requireApproval: boolean; starredQuestionKey: string; }; poll: { [pollId: string]: PresenterPollConfig }; registration: { shouldDisplayNames: boolean; joinUrl: string; isRegistrationEnabled: boolean; }; enabled: boolean; currentSlide: number; 'poll-timing': number; } interface PollConfig { [pollId: string]: ViewerPollConfig; } export interface CodingSession { code: string; score: number; maxScore: number; } export interface CodingSessions { [key: string]: CodingSession; } export interface ViewerConfig { poll: { [viewer: string]: PollConfig }; name: { [viewer: string]: string }; qna7: { [viewer: string]: { questions: QuestionDb[] } }; votes: { [viewer: string]: UserVotes }; coding: { [viewer: string]: CodingSessions }; } export interface SyncSessionConfig { autojoin: boolean; active: boolean; admins: string[]; owner: string; name: string; } export interface SyncSession { presenter: PresenterConfig; viewer: ViewerConfig; config: SyncSessionConfig; } export interface SyncDb extends FirebaseDb { 'sync-sessions': { [key: string]: SyncSession }; authorized_users: { [uid: string]: boolean }; admin: { [uid: string]: { permissions: { [permission: string]: boolean } } }; 'user-data': { [userId: string]: { [key: string]: { valueKey: any; }; }; }; } @Injectable({ providedIn: 'root' }) export class SyncDataService { private readonly syncId$: Observable< string > = this.syncSessionService.sessionId$.pipe(filter(a => !!a)); private readonly currentSession$ = this.dbService .object('sync-sessions') .object(this.syncId$); constructor( private readonly syncSessionService: SyncSessionService, private readonly dbService: SyncDbService ) {} getPresenterObject(key: K) { return this.currentSession$.object('presenter').object(key); } getCurrentViewerObject(key: K) { return this.getViewerObject( key, this.syncSessionService.viewerId$.pipe(filter(a => !!a)) ); } getViewerObject( key: K, viewerId: Observable | string ) { return this.currentSession$ .object('viewer') .object(key) .object(viewerId); } getAdminAllUserData( key: K ): SyncDataObject { return this.currentSession$.object('viewer').object(key); } } ================================================ FILE: libs/utils/src/lib/sync/services/sync-db-wrapper.service.spec.ts ================================================ import { TestBed } from '@angular/core/testing'; import { SyncDbService } from './sync-db.service'; describe('SyncDbService', () => { beforeEach(() => TestBed.configureTestingModule({})); it('should be created', () => { const service: SyncDbService = TestBed.inject(SyncDbService); expect(service).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/services/sync-db.service.ts ================================================ import { Injectable } from '@angular/core'; import { AngularFireDatabase, AngularFireList, AngularFireObject } from '@angular/fire/database'; import { combineLatest, isObservable, Observable, of, Subject } from 'rxjs'; import { first, map, shareReplay, switchMap, tap } from 'rxjs/operators'; import { ArrayElement, FirebaseDb, mergeValues } from '@codelab/utils/src/lib/sync/services/common'; @Injectable({ providedIn: 'root' }) export class SyncDbService { constructor(private db: AngularFireDatabase) {} object(key$: Observable | K): SyncDataObject { if (!isObservable(key$)) { key$ = of(key$); } const keyString$ = key$.pipe(map(a => a.toString())); const db$ = keyString$.pipe(map(key => this.db.object(key))); // TODO(kirjs): Drop any return new SyncDataObject(db$, keyString$, this as any); } objectList( key$: Observable | K ): SyncDataList { // TODO(kirjs): This whole function is only needed for typings. // firebase has no real arrays, and we kinda pretend it does return (this.list(key$) as unknown) as SyncDataList; } list( key$: Observable | K ): SyncDataList> { if (!isObservable(key$)) { key$ = of(key$); } const keyString$ = key$.pipe(map(a => a.toString())); const db$ = keyString$.pipe( map(key => this.db.list>(key)) ); // TODO(kirjs): Drop any return new SyncDataList>(db$, keyString$, this as any); } } export class SyncDataObject { private values$?: Observable; private readonly valueChanges$: Observable = this.db$.pipe( switchMap(db => { return db.valueChanges(); }), map(value => { return mergeValues(value, this.defaultValue); }) ); private onDestroy: Subject = new Subject(); constructor( protected readonly db$: Observable>, protected readonly key$: Observable, protected readonly syncDbService: SyncDbService, protected readonly defaultValue?: Partial ) {} valueChanges(): Observable { if (!this.values$) { this.values$ = this.valueChanges$.pipe(shareReplay(1)); } return this.values$; } updateWithCallback(callback: (value: T, index: number) => T) { this.valueChanges$ .pipe( map(callback), tap(a => { console.log({ a }); }), first() ) .subscribe(value => this.set(value)); } withDefault(defaultValue: Partial): SyncDataObject { return new SyncDataObject( this.db$, this.key$, this.syncDbService, defaultValue ); } set(value: T) { return this.db$.pipe(first()).subscribe(db => { db.set(value); }); } remove() { this.db$.pipe(first()).subscribe(db => { db.remove(); }); } destroy() { this.onDestroy.next(null); this.onDestroy.complete(); } object(key$: Observable | K): SyncDataObject { if (!isObservable(key$)) { key$ = of(key$); } // TODO(kirjs): There should be a better way than casting to any const newKey$ = this.key$.pipe( switchMap(k => (key$ as Observable).pipe(map(key => `${k}/${key}`))) ) as any; return this.syncDbService.object(newKey$); } list( key$: Observable | K ): SyncDataList> { if (!isObservable(key$)) { key$ = of(key$); } const newKey$ = this.key$.pipe( switchMap(k => (key$ as Observable).pipe(map(key => `${k}/${key}`))) ) as any; return this.syncDbService.list(newKey$); } } export class SyncDataList { values$ = this.db$.pipe(switchMap(db => db.valueChanges())); snapshots$ = this.db$.pipe( switchMap(db => { return db.snapshotChanges(); }) ); constructor( protected readonly db$: Observable>, protected readonly key$: Observable, protected readonly syncDbService: SyncDbService, protected readonly defaultValue?: T[] ) {} valueChanges(): Observable { return this.values$.pipe( map(value => mergeValues(value, this.defaultValue)) ); } push(value: T) { return this.db$.pipe(first()).subscribe(db => db.push(value as any)); } object(key: string): SyncDataObject { const key$ = this.key$.pipe(map(k => `${k}/${key}`)) as any; return this.syncDbService.object(key$); } } ================================================ FILE: libs/utils/src/lib/sync/services/sync-session.service.spec.ts ================================================ import { TestBed } from '@angular/core/testing'; import { SyncSessionService } from './sync-session.service'; describe('SyncSessionService', () => { beforeEach(() => TestBed.configureTestingModule({})); it('should be created', () => { const service: SyncSessionService = TestBed.inject(SyncSessionService); expect(service).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/services/sync-session.service.ts ================================================ import { Injectable } from '@angular/core'; import { LoginService } from '@codelab/firebase-login'; import { firebaseToValuesWithKey, SyncStatus } from '@codelab/utils/src/lib/sync/common'; import { SyncDbService } from '@codelab/utils/src/lib/sync/services/sync-db.service'; import produce from 'immer'; import { BehaviorSubject, combineLatest, Observable } from 'rxjs'; import { filter, first, map, shareReplay, switchMap, takeUntil, tap } from 'rxjs/operators'; import { SyncDb, SyncSession } from '@codelab/utils/src/lib/sync/services/sync-data.service'; @Injectable({ providedIn: 'root' }) export class SyncSessionService { readonly status$: Observable; readonly viewerId$: Observable = this.loginService.uid$; readonly canStartSession$ = this.viewerId$.pipe( switchMap((uid: string) => { return this.dbService .object('authorized_users') .object(uid) .withDefault(false) .valueChanges(); }), shareReplay(1) ); private readonly sessionId = new BehaviorSubject(null); readonly sessionConfig = this.dbService .object('sync-sessions') .object(this.sessionId) .object('config'); readonly sessionId$ = this.sessionId.asObservable(); readonly hasActiveSession$ = this.sessionId.pipe( map(sessionId => !!sessionId), shareReplay(1) ); private readonly preferredAdminStatusSubject = new BehaviorSubject(null); private readonly preferredAdminStatus$ = combineLatest([ this.loginService.preferredStatus$, this.preferredAdminStatusSubject.asObservable() ]).pipe(map(([a, b]) => b || a)); private readonly sessions = this.dbService.objectList('sync-sessions'); readonly sessions$ = this.sessions.snapshots$.pipe( map(firebaseToValuesWithKey) ); constructor( private readonly dbService: SyncDbService, private loginService: LoginService ) { this.status$ = combineLatest([ this.viewerId$, this.sessionConfig.valueChanges(), this.preferredAdminStatus$ ]).pipe( map(([uid, config, preferredAdminStatus]) => { if (!(config && config.active)) { return SyncStatus.OFF; } if (config.owner === uid || config.admins.includes(uid)) { return preferredAdminStatus || SyncStatus.ADMIN; } return SyncStatus.VIEWING; }), shareReplay(1) ); } create(name: string) { const uid = this.loginService.uid$.pipe(first()); uid .pipe( map(uid => { // TODO(kirjs): Figure this out const session: Partial = { config: { owner: uid, active: true, admins: ['admin'], autojoin: true, name: name } }; return session; }) ) .subscribe(session => this.sessions.push(session as SyncSession)); this.autoJoin(name); } autoJoin(name: string) { const sessions = this.sessions.snapshots$.pipe( filter(s => !!s), takeUntil(this.hasActiveSession$.pipe(filter(a => a))) ); const availableSessions = sessions.pipe( map(sessions => sessions.filter(snapshot => { const config = snapshot.payload.val().config; return config.autojoin && config.active && config.name === name; }) ) ); availableSessions.subscribe(a => { if (a.length > 1) { console.log( 'cannot autojoin as there are more than one active sessions' ); return; } if (a.length === 1) { this.sessionId.next(a[0].key); } }); } dropCurrentSession() { this.sessionConfig.object('active').set(false); } remove(key: string) { this.sessions.object(key).remove(); } flipActive(key: string) { this.sessions.object(key).updateWithCallback( produce(s => { s.config.active = !s.config.active; }) ); } present() { this.preferredAdminStatusSubject.next(SyncStatus.PRESENTING); } administer() { this.preferredAdminStatusSubject.next(SyncStatus.ADMIN); } } ================================================ FILE: libs/utils/src/lib/sync/sync-button/sync-button.component.css ================================================ .registration { width: 100%; height: 100%; position: absolute; left: 0; top: 0; background: white; z-index: 100; padding: 20px; box-sizing: border-box; } .menu-button { position: absolute; right: 0; top: 0; z-index: 1000; } ================================================ FILE: libs/utils/src/lib/sync/sync-button/sync-button.component.html ================================================
        {{ syncSessionService.hasActiveSession$ | async | json }}FOF
        ================================================ FILE: libs/utils/src/lib/sync/sync-button/sync-button.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SyncButtonComponent } from './sync-button.component'; describe('SyncButtonComponent', () => { let component: SyncButtonComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SyncButtonComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SyncButtonComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/sync-button/sync-button.component.ts ================================================ import { Component, Input, OnDestroy, OnInit, Optional } from '@angular/core'; import { SlidesDeckComponent } from '@ng360/slides'; import { SyncRegistrationService } from '@codelab/utils/src/lib/sync/components/registration/sync-registration.service'; import { SyncDataService } from '@codelab/utils/src/lib/sync/services/sync-data.service'; import { SyncSessionService } from '@codelab/utils/src/lib/sync/services/sync-session.service'; import { SyncStatus } from '@codelab/utils/src/lib/sync/common'; import { debounceTime, distinctUntilChanged, filter, mergeMapTo, switchMapTo, take, takeUntil } from 'rxjs/operators'; import { Subject } from 'rxjs'; @Component({ selector: 'codelab-sync-button', templateUrl: './sync-button.component.html', styleUrls: ['./sync-button.component.css'], providers: [SyncRegistrationService] }) export class SyncButtonComponent implements OnInit, OnDestroy { @Input() name = 'default'; sync = {}; private readonly onDestroy = new Subject(); private readonly currentSlide = this.syncDataService.getPresenterObject( 'currentSlide' ); constructor( private readonly syncDataService: SyncDataService, readonly syncSessionService: SyncSessionService, readonly registrationService: SyncRegistrationService, @Optional() private readonly presentation: SlidesDeckComponent ) {} ngOnInit(): void { this.syncSessionService.autoJoin(this.name); if (this.presentation) { this.syncSessionService.status$ .pipe( filter(s => s === SyncStatus.PRESENTING), mergeMapTo(this.presentation.slideChange), distinctUntilChanged(), takeUntil(this.onDestroy), debounceTime(200) ) .subscribe((slide: number) => { this.currentSlide.set(slide); }); this.syncSessionService.status$ .pipe( filter(s => s !== SyncStatus.PRESENTING), switchMapTo(this.currentSlide.valueChanges()), distinctUntilChanged(), filter(s => s !== null && s !== undefined), takeUntil(this.onDestroy) ) .subscribe((slide: number) => { this.presentation.goToSlide(slide); }); } } ngOnDestroy(): void { this.onDestroy.next(); this.onDestroy.complete(); } start(): void { this.syncSessionService.create(this.name); } stop(): void { this.syncSessionService.dropCurrentSession(); } present(): void { this.syncSessionService.present(); } administer(): void { this.syncSessionService.administer(); } copyViewerId(): void { this.syncSessionService.viewerId$ .pipe(take(1)) .subscribe(viewerId => copyToClipboard(viewerId)); } } function copyToClipboard(text: string): void { const inputElement = document.createElement('input'); inputElement.value = text; inputElement.select(); document.execCommand('copy'); inputElement.remove(); } ================================================ FILE: libs/utils/src/lib/sync/sync-button/sync-button.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SyncButtonComponent } from '@codelab/utils/src/lib/sync/sync-button/sync-button.component'; import { SyncDirectivesModule } from '@codelab/utils/src/lib/sync/directives/sync-directives.module'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatIconModule } from '@angular/material/icon'; import { MatMenuModule } from '@angular/material/menu'; import { OnlineIndicatorModule } from '@codelab/utils/src/lib/sync/components/online-indicator/online-indicator.module'; import { SyncRegistrationModule } from '@codelab/utils/src/lib/sync/components/registration/sync-registration.module'; @NgModule({ declarations: [SyncButtonComponent], exports: [SyncButtonComponent], imports: [ CommonModule, OnlineIndicatorModule, MatButtonModule, SyncDirectivesModule, MatIconModule, MatCardModule, MatMenuModule, SyncRegistrationModule ] }) export class SyncButtonModule {} ================================================ FILE: libs/utils/src/lib/sync/sync-playground/sync-playground-presenter/sync-playground-presenter.component.css ================================================ ================================================ FILE: libs/utils/src/lib/sync/sync-playground/sync-playground-presenter/sync-playground-presenter.component.html ================================================ ================================================ FILE: libs/utils/src/lib/sync/sync-playground/sync-playground-presenter/sync-playground-presenter.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SyncPlaygroundPresenterComponent } from './sync-playground-presenter.component'; describe('SyncPlaygroundPresenterComponent', () => { let component: SyncPlaygroundPresenterComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SyncPlaygroundPresenterComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SyncPlaygroundPresenterComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/sync-playground/sync-playground-presenter/sync-playground-presenter.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; import { LoginService } from '@codelab/firebase-login'; import { ReplaySubject, Subject } from 'rxjs'; import { User } from 'firebase/app'; import { SyncDataService } from '@codelab/utils/src/lib/sync/services/sync-data.service'; import { SyncSessionService } from '@codelab/utils/src/lib/sync/services/sync-session.service'; import { SyncDbService } from '@codelab/utils/src/lib/sync/services/sync-db.service'; import { SyncStatus } from '@codelab/utils/src/lib/sync/common'; import { SyncPollService } from '@codelab/utils/src/lib/sync/components/poll/common/sync-poll.service'; import { SyncRegistrationService } from '@codelab/utils/src/lib/sync/components/registration/sync-registration.service'; import { TestRunnerService } from '@codelab/utils/src/lib/sandbox-runner/test-runner.service'; import { SyncCodeGameService } from '@codelab/utils/src/lib/sync/components/sync-code-game/sync-code-game.service'; @Component({ selector: 'codelab-sync-playground-presenter', templateUrl: './sync-playground-presenter.component.html', styleUrls: ['./sync-playground-presenter.component.css'], providers: [ TestRunnerService, SyncDataService, SyncSessionService, SyncDbService, SyncPollService, SyncRegistrationService, SyncCodeGameService, { provide: LoginService, useFactory: () => ({ uid$: new ReplaySubject(1), user$: new ReplaySubject(1), preferredStatus$: new ReplaySubject(1) }) } ] }) export class SyncPlaygroundPresenterComponent implements OnInit { @Input() userId: string; @Input() preferredStatus: SyncStatus; constructor(private readonly loginService: LoginService) {} ngOnInit() { (this.loginService.user$ as Subject).next({ uid: this.userId, displayName: 'lol' } as User); (this.loginService.uid$ as Subject).next(this.userId); (this.loginService.preferredStatus$ as Subject).next( this.preferredStatus ); } } ================================================ FILE: libs/utils/src/lib/sync/sync-playground/sync-playground-test/sync-playground-test.component.css ================================================ ================================================ FILE: libs/utils/src/lib/sync/sync-playground/sync-playground-test/sync-playground-test.component.html ================================================

        Only presenter sees this

        Only viewer sees this

        Only admin sees this

        ================================================ FILE: libs/utils/src/lib/sync/sync-playground/sync-playground-test/sync-playground-test.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SyncPlaygroundTestComponent } from './sync-playground-test.component'; import { SyncModule } from '@codelab/utils/src/lib/sync/sync.module'; import { RouterTestingModule } from '@angular/router/testing'; import { getMockAngularFireProviders } from '@codelab/utils/src/lib/testing/mocks/angular-fire'; describe('SyncPlaygroundTestComponent', () => { let component: SyncPlaygroundTestComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ imports: [SyncModule, RouterTestingModule], providers: [...getMockAngularFireProviders()] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SyncPlaygroundTestComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/sync-playground/sync-playground-test/sync-playground-test.component.ts ================================================ import { Component } from '@angular/core'; import { SyncPollConfig } from '@codelab/utils/src/lib/sync/components/poll/common/common'; @Component({ selector: 'codelab-sync-playground-test', templateUrl: './sync-playground-test.component.html', styleUrls: ['./sync-playground-test.component.css'] }) export class SyncPlaygroundTestComponent { readonly polls: SyncPollConfig[] = [ { key: 'favorite', type: 'choice', question: 'What is your favorite framework?', answer: 'Angular', options: ['react', 'Angular', 'Vue', 'Other'] }, { key: 'other', type: 'choice', question: 'Another question', answer: '3', options: ['1', '2', '3', '4'] } /* { key: 'js', type: 'stars', question: 'How well do you know JavaScript', }, { key: 'angularjs', type: 'stars', question: 'How well do you know AngularJS (Old version)', }, { key: 'angular', type: 'stars', question: 'How well do you know Angular', }, { key: 'fruit', type: 'choice', question: 'What is your favorite fruit?', answer: '🍏', options: [ '🍏', '🍋', '🍓', '🍍' ] }, { key: 'angular question', type: 'choice', question: 'What is your favorite framework?', options: [ 'react', 'Angular', 'Vue', 'Other', ] }, { key: 'longer question', type: 'choice', question: 'Who created angular', options: [ 'Мега корпорация "Крошка-Картошка"', 'Facebook сделал лично Цукерберг', 'Николай Васильевич Гогол', 'Google Angular Brad Green Misko', ] }*/ ]; } ================================================ FILE: libs/utils/src/lib/sync/sync-playground/sync-playground.component.html ================================================ ================================================ FILE: libs/utils/src/lib/sync/sync-playground/sync-playground.component.scss ================================================ :host { display: grid; grid-template-columns: 1fr 1fr; grid-template-rows: 1fr 1fr; height: 100%; > * { border: 1px #444 dotted; } } ================================================ FILE: libs/utils/src/lib/sync/sync-playground/sync-playground.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SyncPlaygroundComponent } from './sync-playground.component'; describe('SyncPlaygroundComponent', () => { let component: SyncPlaygroundComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SyncPlaygroundComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SyncPlaygroundComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/sync/sync-playground/sync-playground.component.ts ================================================ import { Component } from '@angular/core'; import { SyncStatus } from '@codelab/utils/src/lib/sync/common'; @Component({ selector: 'codelab-sync-playground', templateUrl: './sync-playground.component.html', styleUrls: ['./sync-playground.component.scss'] }) export class SyncPlaygroundComponent { SyncStatus = SyncStatus; } ================================================ FILE: libs/utils/src/lib/sync/sync.module.ts ================================================ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; import { AngularFireAuthModule } from '@angular/fire/auth'; import { AngularFireDatabaseModule } from '@angular/fire/database'; import { FormsModule } from '@angular/forms'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatInputModule } from '@angular/material/input'; import { MatMenuModule } from '@angular/material/menu'; import { MatSelectModule } from '@angular/material/select'; import { MatTabsModule } from '@angular/material/tabs'; import { LoginService } from '@codelab/firebase-login'; import { SlidesModule } from '@ng360/slides'; import { LeaderboardModule } from '@codelab/utils/src/lib/sync/components/poll/sync-poll-presenter/leaderboard/leaderboard.module'; import { SyncPollModule } from '@codelab/utils/src/lib/sync/components/poll/sync-poll.module'; import { QuestionsModule } from '@codelab/utils/src/lib/sync/components/questions/questions.module'; import { SyncRegistrationModule } from '@codelab/utils/src/lib/sync/components/registration/sync-registration.module'; import { SyncSessionsModule } from '@codelab/utils/src/lib/sync/components/sync-sessions/sync-sessions.module'; import { AllViewerValuesDirective } from '@codelab/utils/src/lib/sync/directives/all-viewer-values.directive'; import { SyncDirectivesModule } from '@codelab/utils/src/lib/sync/directives/sync-directives.module'; import { SyncButtonModule } from '@codelab/utils/src/lib/sync/sync-button/sync-button.module'; import { SyncPlaygroundPresenterComponent } from './sync-playground/sync-playground-presenter/sync-playground-presenter.component'; import { SyncPlaygroundTestComponent } from './sync-playground/sync-playground-test/sync-playground-test.component'; import { SyncPlaygroundComponent } from './sync-playground/sync-playground.component'; import { SyncCodeGameModule } from '@codelab/utils/src/lib/sync/components/sync-code-game/sync-code-game.module'; @NgModule({ imports: [ SyncDirectivesModule, SyncSessionsModule, CommonModule, MatButtonModule, MatSelectModule, AngularFireDatabaseModule, AngularFireAuthModule, MatMenuModule, MatInputModule, SlidesModule, FormsModule, MatCardModule, MatTabsModule, SyncRegistrationModule, QuestionsModule, SyncPollModule, SyncButtonModule, LeaderboardModule, SyncCodeGameModule ], providers: [LoginService], declarations: [ AllViewerValuesDirective, SyncPlaygroundComponent, SyncPlaygroundPresenterComponent, SyncPlaygroundTestComponent ], exports: [AllViewerValuesDirective, SyncPlaygroundComponent] }) export class SyncModule {} ================================================ FILE: libs/utils/src/lib/test-results/common.ts ================================================ export interface TestResult { pass: boolean; name: string; error?: string; featured?: boolean; } export interface TestRunResult { tests: TestResult[]; error?: Error; } ================================================ FILE: libs/utils/src/lib/test-results/file-aware-description/file-aware-description.component.css ================================================ .filename { text-decoration: underline; cursor: pointer; color: #444; margin-right: -4px; } ================================================ FILE: libs/utils/src/lib/test-results/file-aware-description/file-aware-description.component.html ================================================
        {{ file }} {{ title }} ================================================ FILE: libs/utils/src/lib/test-results/file-aware-description/file-aware-description.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { FileAwareDescriptionComponent } from './file-aware-description.component'; describe('FileAwareDescriptionComponent', () => { let component: FileAwareDescriptionComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [FileAwareDescriptionComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(FileAwareDescriptionComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/test-results/file-aware-description/file-aware-description.component.ts ================================================ import { Component, EventEmitter, Input, Output } from '@angular/core'; function getTestFile(test: string): string | null { const match = test.trim().match(/^([\w.\/]+):/); return match && match[1]; } function getTitle(test: string) { const file = getTestFile(test); return file ? test.replace(file + ':', '') : test; } @Component({ // tslint:disable-next-line:component-selector selector: 'slides-file-aware-description', templateUrl: './file-aware-description.component.html', styleUrls: ['./file-aware-description.component.css'] }) export class FileAwareDescriptionComponent { file: string; title: string; @Output() selectFile = new EventEmitter(); @Input() set test(test: string) { this.file = getTestFile(test); this.title = getTitle(test); } } ================================================ FILE: libs/utils/src/lib/test-results/simple-tests-progress/simple-tests-progress.component.css ================================================ .progress { height: 1vw; font-size: 0.7vw; line-height: 1.1vw; display: flex; } .text-box { border: 1px solid #fff; background: #ff6144; flex: 1; } .text-box.pass { background: #5ccb5d; } .counts { margin: 0 10px; } .preparing-tests { display: flex; align-items: center; } ================================================ FILE: libs/utils/src/lib/test-results/simple-tests-progress/simple-tests-progress.component.html ================================================
        Preparing tests...
        {{ countPassing() }}/{{ (tests || []).length }}
        ================================================ FILE: libs/utils/src/lib/test-results/simple-tests-progress/simple-tests-progress.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { SimpleTestsProgressComponent } from './simple-tests-progress.component'; describe('SimpleTestsProgressComponent', () => { let component: SimpleTestsProgressComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [SimpleTestsProgressComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(SimpleTestsProgressComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should be created', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/test-results/simple-tests-progress/simple-tests-progress.component.ts ================================================ import { Component, Input, OnInit } from '@angular/core'; @Component({ selector: 'codelab-simple-tests-progress', templateUrl: './simple-tests-progress.component.html', styleUrls: ['./simple-tests-progress.component.css'] }) export class SimpleTestsProgressComponent implements OnInit { @Input() tests = []; countPassing() { return (this.tests || []).filter(test => test.pass).length; } constructor() {} ngOnInit() {} } ================================================ FILE: libs/utils/src/lib/test-results/simple-tests-progress/simple-tests-progress.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { SimpleTestsProgressComponent } from '@codelab/utils/src/lib/test-results/simple-tests-progress/simple-tests-progress.component'; import { LoadingIndicatorModule } from '@codelab/utils/src/lib/loading-indicator/loading-indicator.module'; @NgModule({ declarations: [SimpleTestsProgressComponent], exports: [SimpleTestsProgressComponent], imports: [CommonModule, LoadingIndicatorModule] }) export class SimpleTestsProgressModule {} ================================================ FILE: libs/utils/src/lib/test-results/test-results/test-results.component.html ================================================
        Error: {{ result.error?.message }}
        ================================================ FILE: libs/utils/src/lib/test-results/test-results/test-results.component.scss ================================================ codelab-simple-tests-progress { display: block; margin-bottom: 8px; } .container { position: relative; .error-message { position: absolute; background: rgb(187, 2, 0); color: white; opacity: 0.9; font-size: 20px; left: 0; right: 0; top: 0; bottom: 0; padding: 20px; } } ================================================ FILE: libs/utils/src/lib/test-results/test-results/test-results.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TestResultsComponent } from './test-results.component'; describe('TestResultsComponent', () => { let component: TestResultsComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [TestResultsComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TestResultsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/test-results/test-results/test-results.component.ts ================================================ import { ChangeDetectionStrategy, Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { TestResult, TestRunResult } from '@codelab/utils/src/lib/test-results/common'; @Component({ // tslint:disable-next-line:component-selector selector: 'slides-test-results', templateUrl: './test-results.component.html', styleUrls: ['./test-results.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) export class TestResultsComponent implements OnChanges { @Input() result: TestRunResult; @Output() selectFile = new EventEmitter(); @Input() focused = true; @Input() showProgress = true; tests: TestResult[]; ngOnChanges(changes: SimpleChanges) { if (changes.result && this.result && Array.isArray(this.result.tests)) { let hasFailures = false; this.tests = this.result.tests.map(t => { const result = { ...t, featured: !t.pass && !hasFailures }; if (!t.pass) { hasFailures = true; } return result; }); } } } ================================================ FILE: libs/utils/src/lib/test-results/test-results.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { TestRunResultsComponent } from './test-run-results/test-run-results.component'; import { MatCheckboxModule } from '@angular/material/checkbox'; import { FormsModule } from '@angular/forms'; import { MatIconModule } from '@angular/material/icon'; import { TestResultsComponent } from './test-results/test-results.component'; import { SimpleTestsProgressModule } from '@codelab/utils/src/lib/test-results/simple-tests-progress/simple-tests-progress.module'; import { FileAwareDescriptionComponent } from './file-aware-description/file-aware-description.component'; @NgModule({ declarations: [ TestRunResultsComponent, TestResultsComponent, FileAwareDescriptionComponent ], exports: [TestResultsComponent, TestRunResultsComponent], imports: [ CommonModule, MatCheckboxModule, FormsModule, MatIconModule, SimpleTestsProgressModule ] }) export class TestResultsModule {} ================================================ FILE: libs/utils/src/lib/test-results/test-run-results/test-run-results.component.html ================================================
        check_circle error_outline refresh
        {{ t.error }}
        Display all tests ================================================ FILE: libs/utils/src/lib/test-results/test-run-results/test-run-results.component.scss ================================================ :host { display: block; min-height: 80px; } .test { display: flex; align-items: start; margin-bottom: 16px; color: #888; padding-left: 8px; &.featured { background: rgba(255, 88, 75, 0.21); border-radius: 5px; padding: 16px 8px; .name { font-size: 20px; color: #444; margin-bottom: 8px; } .error { color: #888; } } .state { margin-right: 10px; } &:not(.featured) .name { flex: 0; max-width: 400px; line-height: 22px; height: 22px; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; } .pass { color: var(--test-succcess); } .fail { color: #d61f22; } } mat-icon.running { animation: rotation 1s infinite linear; } @keyframes rotation { from { transform: rotate(0deg); } to { transform: rotate(359deg); } } ================================================ FILE: libs/utils/src/lib/test-results/test-run-results/test-run-results.component.spec.ts ================================================ import { async, ComponentFixture, TestBed } from '@angular/core/testing'; import { TestRunResultsComponent } from './test-run-results.component'; describe('TestRunResultsComponent', () => { let component: TestRunResultsComponent; let fixture: ComponentFixture; beforeEach(async(() => { TestBed.configureTestingModule({ declarations: [TestRunResultsComponent] }).compileComponents(); })); beforeEach(() => { fixture = TestBed.createComponent(TestRunResultsComponent); component = fixture.componentInstance; fixture.detectChanges(); }); it('should create', () => { expect(component).toBeTruthy(); }); }); ================================================ FILE: libs/utils/src/lib/test-results/test-run-results/test-run-results.component.ts ================================================ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { TestResult } from '@codelab/utils/src/lib/test-results/common'; @Component({ // tslint:disable-next-line:component-selector selector: 'slides-test-run-results', templateUrl: './test-run-results.component.html', styleUrls: ['./test-run-results.component.scss'] }) export class TestRunResultsComponent implements OnInit { @Output() selectFile = new EventEmitter(); @Input() tests: TestResult[]; @Input() seeAll = false; @Input() focused = true; getName(test) { return test.name; } constructor() {} ngOnInit() {} } ================================================ FILE: libs/utils/src/lib/testing/mocks/angular-fire.ts ================================================ import { of } from 'rxjs'; import { AngularFireDatabase } from '@angular/fire/database'; import { AngularFireAuth } from '@angular/fire/auth'; export const MockAngularFireDatabase = { list: jasmine.createSpy('list').and.returnValue({ snapshotChanges: () => of([]), valueChanges: () => of([]) }), object: jasmine.createSpy('object').and.returnValue( of({ snapshotChanges: () => of({}), valueChanges: () => of({}) }) ) }; export const MockAngularFireAuth = { user: of({ isAnonymous: true, uid: 'lol' }), authState: of({}) }; export function getMockAngularFireProviders() { return [ { provide: AngularFireDatabase, useValue: MockAngularFireDatabase }, { provide: AngularFireAuth, useValue: MockAngularFireAuth } ]; } ================================================ FILE: libs/utils/src/lib/testing/mocks/sync-db-service.ts ================================================ import { SyncDataService } from '@codelab/utils/src/lib/sync/services/sync-data.service'; import { of } from 'rxjs'; export function getSyncDbService() { return [ { provide: SyncDataService, useValue: { getPresenterObject() { return { valueChanges() { return of({}); } }; } } } ]; } ================================================ FILE: libs/utils/src/lib/tracking/tracking.directive.ts ================================================ import { Directive, HostListener } from '@angular/core'; import { AngularFireDatabase } from '@angular/fire/database'; import { AngularFireAuth } from '@angular/fire/auth'; import { Router } from '@angular/router'; import { SlidesDeckComponent } from '@ng360/slides'; @Directive({ // tslint:disable-next-line:all TODO: Fix linter warnings on the selector and delete this comment. selector: '[slides-tracking]' }) export class TrackingDirective { auth; lastSlideChange; history: Array = []; constructor( private afDb: AngularFireDatabase, private afAuth: AngularFireAuth, private router: Router, private presentation: SlidesDeckComponent ) { afAuth.auth.signInAnonymously(); afAuth.authState.subscribe(authData => { this.auth = authData; }); this.lastSlideChange = Date.now(); } @HostListener('slideChange', ['$event']) slideChange(index) { if (this.auth) { const dateNow = Date.now(); const diffMinutes = dateNow - this.lastSlideChange; this.lastSlideChange = dateNow; const userHistory = this.afDb.list('/user_progress/' + this.auth.uid); userHistory.push({ slideId: index, timeStamp: dateNow, msDiff: diffMinutes, // time user spent on the prev. slide route: this.router.url, totalSlides: this.presentation.slides.length, milestone: this.router.url.split('/')[1] }); } // TODO: use observables to preserve all data } } ================================================ FILE: libs/utils/src/lib/tracking/tracking.module.ts ================================================ import { NgModule } from '@angular/core'; import { CommonModule } from '@angular/common'; import { TrackingDirective } from './tracking.directive'; import { AngularFireModule } from '@angular/fire'; import { AngularFireDatabaseModule } from '@angular/fire/database'; import { AngularFireAuthModule } from '@angular/fire/auth'; import { environment } from '../../../../../apps/codelab/src/environments/environment'; export const angularFire = AngularFireModule.initializeApp( environment.firebaseConfig ); @NgModule({ imports: [ CommonModule, AngularFireDatabaseModule, AngularFireAuthModule, angularFire ], declarations: [TrackingDirective], exports: [TrackingDirective] }) export class TrackingModule {} ================================================ FILE: libs/utils/src/test.ts ================================================ // This file is required by karma.conf.js and loads recursively all the .spec and framework files import 'zone.js/dist/zone'; import 'zone.js/dist/zone-testing'; import { getTestBed } from '@angular/core/testing'; import { BrowserDynamicTestingModule, platformBrowserDynamicTesting } from '@angular/platform-browser-dynamic/testing'; declare const require: any; // First, initialize the Angular testing environment. getTestBed().initTestEnvironment( BrowserDynamicTestingModule, platformBrowserDynamicTesting() ); // Then we find all the tests. const context = require.context('./', true, /\.spec\.ts$/); // And load the modules. context.keys().map(context); ================================================ FILE: libs/utils/tsconfig.json ================================================ { "extends": "../../tsconfig.json", "compilerOptions": { "types": ["jasmine", "node"] } } ================================================ FILE: libs/utils/tsconfig.lib.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "target": "es2015", "module": "es2015", "moduleResolution": "node", "declaration": true, "sourceMap": true, "inlineSources": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "importHelpers": true, "types": [], "lib": ["dom", "es2015"] }, "angularCompilerOptions": { "annotateForClosureCompiler": true, "skipTemplateCodegen": true, "strictMetadataEmit": true, "fullTemplateTypeCheck": true, "strictInjectionParameters": true, "flatModuleId": "AUTOGENERATED", "flatModuleOutFile": "AUTOGENERATED" }, "exclude": ["src/test.ts", "**/*.spec.ts"] } ================================================ FILE: libs/utils/tsconfig.spec.json ================================================ { "extends": "./tsconfig.json", "compilerOptions": { "outDir": "../../dist/out-tsc", "types": ["jasmine", "node"] }, "files": ["src/test.ts"], "include": ["**/*.spec.ts", "**/*.d.ts"] } ================================================ FILE: libs/utils/tslint.json ================================================ { "extends": "../../tslint.json", "rules": { "directive-selector": [true, "attribute", "codelab", "camelCase"], "component-selector": [true, "element", "codelab", "kebab-case"] } } ================================================ FILE: ng2ts/.prettierignore ================================================ **/* ================================================ FILE: ng2ts/api.service.ts ================================================ import { VideoItem } from './video/video-item'; const FAKE_VIDEOS = [ { title: 'Scruffy McPurrrr', src: '/assets/images/cat-00.png', description: 'Scruffy McPurrrr is a world famous kitty known for his escapades around the world purring on laps!', views: 100, likes: 49329, date: '2016-11-25' }, { title: 'The Itty Bitty Kitty Comittee', src: '/assets/images/cat-01.jpg', description: 'The Itty Bitty Kitty Comittee are a collection of felines coming for you!', views: 100, likes: 20, date: '2016-11-21' }, { title: 'IT A KITTY!', src: '/assets/images/cat-02.jpg', description: 'It\'s a kitty, I mean, how are you not in love with him?', views: 100, likes: 20, date: '2016-10-02' }, { title: 'Cate Purrton\'s C.I. Purr Edition Cover', src: '/assets/images/cat-03.jpg', description: 'We don\'t hear any Cat\'s Illustrated readers complaining about Purrton\'s cover.', views: 100, likes: 20, date: '2016-09-02' }, { title: 'SINGLE SUNSHINE', src: '/assets/images/cat-04.jpg', description: 'It was cat for at least an hour. It was incredible. The camera could not capture the vivid intensity ' + 'and brightness. Look into the mirror, look into your soul! What it means.', views: 100, likes: 20, date: '2016-08-02' }, { title: 'Mittens', src: '/assets/images/cat-05.png', description: 'Mitty is a kitty with white mittens for paws!', views: 24, likes: 3, date: '2014-06-12' }, { title: 'Cinco de Gato', src: '/assets/images/cat-06.jpg', description: 'To be totally fair, we also wound up celebrating Moewloween, Purrster, Scratching Day, Good Kitty ' + 'Day, All Cats Day, and various bank holidays in the exact same way.', views: 5, likes: 5, date: '2017-05-05' }, { title: 'Oops, looks like a bug!', src: '/assets/images/dog.jpg', description: 'BAMBOOZLED!', views: 891, likes: 1, date: '2016-08-02' }, { title: 'Koffee Kat', src: '/assets/images/cat-07.jpg', description: 'Somedays you really did have too much coffee... That\'s everyday for Koffee Kat', views: 322, likes: 98, date: '2016-02-02' }, { title: 'Bengalore visist Bengalore', src: '/assets/images/cat-08.jpg', description: 'Bengalore vlog in India.', views: 404, likes: 22, date: '2016-08-02' }, { title: 'Scooby (CLICKBAIT!!!)', src: '/assets/images/cat-09.jpg', description: 'It\'s a cat not a doo.', views: 1969, likes: 987, date: '2016-08-02' }, { title: 'Kitty Tikki Masala', src: '/assets/images/cat-10.jpg', description: 'Watch a kitty eat chicken tikki masala.', views: 231, likes: 43, date: '2016-05-13' }, { title: 'ColourBall Paint', src: '/assets/images/cat-11.jpg', description: 'Speed tutorial of cat painting portrait', views: 1231, likes: 203, date: '2016-11-28' }, { title: 'Emerson Meows At Graduation', src: '/assets/images/cat-12.jpg', description: 'Emerson the cat graduates from Emerson College.', views: 385, likes: 34, date: '2015-05-12' }, { title: 'Pepperoni Has His First Pizza', src: '/assets/images/cat-13.jpg', description: 'A cat has a slice of pizza!', views: 432, likes: 56, date: '2016-08-02' }, { title: 'Nikki\'s First Feast', src: '/assets/images/cat-14.jpg', description: 'Nikki tries fancy cat food.', views: 99, likes: 1, date: '2016-09-23' }, { title: 'Thunder Scared Of Lightning', src: '/assets/images/cat-15.jpg', description: 'Thunder scared to meet the new member of our family.', views: 132, likes: 23, date: '2016-08-02' }, { title: 'Tacocat', src: '/assets/images/cat-16.jpg', description: 'It\'s a palindrome... And a cat eating a taco...', views: 314, likes: 15, date: '2015-03-14' }, { title: 'Perseus Flies', src: '/assets/images/cat-17.jpg', description: 'Perseus first trip on a plane.', views: 761, likes: 31, date: '2015-11-02' }, { title: 'Gus', src: '/assets/images/cat-18.jpg', description: 'Come on, it\'s a cat video', views: 893, likes: 243, date: '2017-05-10' }, ]; export const Api = { fetch(searchString: string): Array { return FAKE_VIDEOS.filter((video) => video.title.toLowerCase().indexOf(searchString.toLowerCase()) >= 0 ); } }; ================================================ FILE: ng2ts/app.component.ts ================================================ import { Component } from '@angular/core'; /*d:templateAddAction/trimLeading*/ import { VideoItem } from './video/video-item'; /*/d*//*d:diInjectService/trimLeading*/ import { VideoService } from './video/video.service'; /*/d*//*d:templateAllVideos:diInjectService*/ const FAKE_VIDEOS = [{ title: 'Cute kitten', src: '/assets/images/cat-01.jpg' }, { title: 'Kitten on the tree', src: '/assets/images/cat-05.png' }, { title: 'Serious cat', src: '/assets/images/cat-03.jpg' }]; /*/d*//*d:neverShow*//* tslint:disable *//*/d*//*d:createComponentSolved*/ @Component({ selector: 'my-app', /*/d*//*d:createComponentSolved:bootstrapSolved/trimBoth*/ template: '

        Hello MewTube!

        ', /*/d*//*d:templatePageSetup/trimBoth*/ templateUrl: 'app.html' /*/d*//*d:createComponentSolved/trimTrailing*/ }) export class AppComponent { /*/d*//*d:templateAddActionSolved/trimTrailing*/ videos: VideoItem[] = /*/d*//*d:templateAddActionSolved:templateAllVideos/trimTrailing*/ [] /*/d*//*d:neverShow*/ && /*/d*//*d:templateAllVideosSolved:templateAllVideosSolved/trimTrailing*/ FAKE_VIDEOS /*/d*//*d:neverShow*/ && /*/d*//*d:diInjectService/trimTrailing*/ [] /*/d*//*d:templateAddActionSolved*/; /*/d*//*d:templatePageSetup/trimTrailing*/ title = 'MewTube'; /*/d*//*d:diInjectServiceSolved*/ constructor(public videoService: VideoService) { } /*/d*//*d:templateAddActionSolved/trimTrailing*/ search(searchString: string) { /*/d*//*d:diInjectServiceSolved*/ this.videos = this.videoService.search(searchString); /*/d*//*d:templateAllVideosSolved:diInjectService*/ this.videos = FAKE_VIDEOS.filter(video => video.title.indexOf(searchString) >= 0); /*/d*//*d:templateAddActionSolved/trimBoth*/} /*/d*//*d:createComponentSolved/trimTrailing*/ } /*/d*//*d:neverShow*/ // Please ignore export function evalJs(string) { return string; } /*/d*/ ================================================ FILE: ng2ts/app.html ================================================
        /*d:templatePageSetupSolved:material*/

        {{title}}

        /*/d*//*d:materialSolved/trimTrailing*/ {{title}} /*/d*//*d:routerSolved/trimTrailing*/ /*/d*//*d:templatePageSetupSolved:templateAddAction/trimLeading*/ /*/d*//*d:templateAddActionSolved:routerPre*/ /*/d*//*d:templateAddActionSolved:routerPre*/
        No videos!
        /*/d*//*d:templateAllVideosSolved:videoComponentUse*/

        {{video.title}}

        /*/d*//*d:videoComponentUseSolved:routerPre*/ /*/d*/
        ================================================ FILE: ng2ts/app.module.ts ================================================ import { BrowserModule } from '@angular/platform-browser'; import { NgModule } from '@angular/core'; import { AppComponent } from './app.component'; /*d:thumbsComponentUse/trimLeading*/ import { ThumbsComponent } from './thumbs/thumbs.component'; /*/d*//*d:togglePanelComponentUse/trimLeading*/ import { TogglePanelComponent } from './toggle-panel/toggle-panel.component'; /*/d*//*d:diInjectService/trimLeading*/ import { VideoService } from './video/video.service'; /*/d*//*d:videoComponentUse/trimLeading*/ import { VideoComponent } from './video/video.component'; /*/d*//*d:contextComponentUse/trimLeading*/ import { ContextComponent } from './context/context.component'; /*/d*//*d:fuzzyPipeUse/trimLeading*/ import { FuzzyPipe } from './fuzzy-pipe/fuzzy.pipe'; /*/d*//*d:router/trimLeading*/ import { RouterModule, Routes } from '@angular/router'; import { SearchComponent } from './search/search.component'; import { UploadComponent } from './upload/upload.component'; /*/d*//*d:material/trimLeading*/ import { MatCardModule} from '@angular/material/card'; /*/d*//*d:forms*/ import { MatInputModule} from '@angular/material/input'; import { MatButtonModule} from '@angular/material/button'; /*/d*//*d:material*/ import { MatToolbarModule} from '@angular/material/toolbar'; /*/d*//*d:forms/trimLeading*/ import { FormsModule } from '@angular/forms'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; /*/d*//*d:createModuleSolved*/ @NgModule({ imports: [ BrowserModule, /*/d*//*d:materialSolved/trimLeading*/ MatToolbarModule, MatCardModule, /*/d*//*d:formsSolved/trimLeading*/ MatButtonModule, MatInputModule, FormsModule, /*/d*//*d:formsPre/trimLeading*/ NoopAnimationsModule, /*/d*//*d:routerSolved/trimLeading*/ RouterModule.forRoot([ {path: '', component: SearchComponent}, {path: 'upload', component: UploadComponent} ]) /*/d*//*d:createModuleSolved/trimTrailing*/ ], declarations: [AppComponent /*/d*//*d:videoComponentUseSolved/trimBoth*/, VideoComponent /*/d*//*d:thumbsComponentUseSolved/trimBoth*/, ThumbsComponent /*/d*//*d:togglePanelComponentUseSolved/trimBoth*/, TogglePanelComponent /*/d*//*d:contextComponentUse/trimBoth*/, ContextComponent /*/d*//*d:router/trimBoth*/, SearchComponent, UploadComponent /*/d*//*d:fuzzyPipeUseSolved/trimBoth*/, FuzzyPipe /*/d*//*d:createModuleSolved/trimBoth*/ ], bootstrap: [AppComponent]/*/d*//*d:diInjectServiceSolved/trimTrailing*/, providers: [VideoService] /*/d*//*d:createModuleSolved/trimTrailing*/ }) export class AppModule { /*/d*//*d:createModuleSolved*/ } /*/d*//*d:neverShow*/ // Needed for type checking export function evalJs(param) { return param; } /*/d*/ ================================================ FILE: ng2ts/code.ts ================================================ export declare const thumbs_thumbs_html: string; export declare const video_video_component_html: string; export declare const upload_upload_component_html: string; export declare const search_search_component_html: string; export declare const typescript_intro_Codelab_ts_AST: string; export declare const app_html: string; export declare const app_component_ts: string; export declare const app_module_ts: string; export declare const toggle_panel_toggle_panel_html: string; export declare const context_context_html: string; export declare const ts: any; export declare const babylon: any; export declare const babel_traverse: any; export declare const babel_types: any; ================================================ FILE: ng2ts/context/context.component.ts ================================================ import {Component} from '@angular/core'; import {ContextService} from './context.service'; import {VideoComponent} from '../video/video.component'; @Component({ selector: 'my-ad', templateUrl: 'context.html' }) export class ContextComponent { text: string; /*d:contextComponentUseSolved*/ constructor(public parent: VideoComponent, private service: ContextService) { } ngOnInit() { this.text = this.service .getAdText(this.parent.video.description); } /*/d*/ } ================================================ FILE: ng2ts/context/context.html ================================================ {{text}} ================================================ FILE: ng2ts/context/context.service.ts ================================================ export class ContextService { getAdText(description: string) { // Super secret algorithm, please don't share outside of this course. return description.indexOf('music') >= 0 ? 'Buy awesome speakers on our web site.' : 'Check out our web site'; } } ================================================ FILE: ng2ts/data-binding/DataBinding.ts ================================================ import {Component, Input} from '@angular/core'; import {DomSanitizer} from '@angular/platform-browser'; @Component({ selector: 'my-flag', template: ` ` }) export class ParentComponent { } @Component({ selector: 'my-rectangle', template: `
        1
        ` }) export class Rectangle { @Input() color: string; @Input() height: number; constructor(private sanitizer: DomSanitizer) { } getCss() { return this.sanitizer.bypassSecurityTrustStyle(` width: 300; height: 30px; background: ${this.color}; `); } } ================================================ FILE: ng2ts/data-binding/DataBindingModule.ts ================================================ import {BrowserModule} from '@angular/platform-browser'; import {NgModule} from '@angular/core'; import {ParentComponent, Rectangle} from './DataBinding'; @NgModule({ imports: [BrowserModule], declarations: [ParentComponent, Rectangle], bootstrap: [ParentComponent] }) export class AppModule { } ================================================ FILE: ng2ts/fuzzy-pipe/fuzzy.pipe.ts ================================================ import {Pipe, PipeTransform} from '@angular/core'; /*d:fuzzyPipeCreateSolved*/ @Pipe({name: 'fuzzy'}) export class FuzzyPipe implements PipeTransform { transform(value: string) { let date = new Date(value); let dateNow = new Date(); let millisecondsDifference = dateNow.getTime() - date.getTime(); let differenceDays = Math.floor(millisecondsDifference / (1000 * 3600 * 24)); let differenceYears = Math.floor(differenceDays / 365); if (differenceDays < 365) { return differenceDays + ' ' + 'days'; } return differenceYears + ' ' + 'years ago'; } } /*/d*//*d:neverShow*/ // Please ignore export function evalJs(string) { return string; } /*/d*/ ================================================ FILE: ng2ts/main.ts ================================================ import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app.module'; import { ApplicationRef, enableProdMode, NgModuleRef } from '@angular/core'; /*d:templatePageSetup/trimLeading*/ import { ResourceLoader } from '@angular/compiler'; import * as code from './code'; /* dark magic, please ignore */ try {enableProdMode();} catch (e) {} /* The code below is used to match the Components with the appropriate templates. */class MyResourceLoader extends ResourceLoader {get(url: string): Promise {const templateId = Object.keys(code).find(key => key.includes(url.replace(/[\/\.-]/gi, '_')));let template = code[templateId];if (!template) {console.log(template);debugger;}return Promise.resolve(template);};}function createNewHosts(cmps) {const components = Array.prototype.map.call(cmps, function (componentNode) {const newNode = document.createElement(componentNode.tagName);if (!componentNode.parentNode) {document.body.append(componentNode);}const parentNode = componentNode.parentNode;const currentDisplay = newNode.style.display;newNode.style.display = 'none';parentNode.insertBefore(newNode, componentNode);function removeOldHost() {newNode.style.display = currentDisplay;try {parentNode.removeChild(componentNode);} catch (e) {}}return removeOldHost;});return function removeOldHosts() {components.forEach(function (removeOldHost) {return removeOldHost();});};}export const hmrBootstrap = (ngModule: NgModuleRef) => {const appRef: ApplicationRef = ngModule.injector.get(ApplicationRef);const elements = appRef.components.map((c) => c.location.nativeElement);const makeVisible = createNewHosts(elements);ngModule.destroy();makeVisible();};if (window['ref']) {hmrBootstrap(window['ref']);window['ref'] = null;} /*/d*//*d:bootstrapSolved/trimTrailing*/ const platform = platformBrowserDynamic(); /*/d*//*d:bootstrapSolved:bootstrapSolved/trimTrailing*/ platform.bootstrapModule(AppModule); /*/d*//*d:templatePageSetup/trimTrailing*/ platform.bootstrapModule(AppModule, { providers: [ {provide: ResourceLoader, useClass: MyResourceLoader, deps: []} ] }).then((ngModuleRef: NgModuleRef) => { window['ref'] = ngModuleRef; }).catch(err => console.error(err)); /*/d*/ ================================================ FILE: ng2ts/ng2ts.ts ================================================ // tslint:disable:max-line-length TODO: Clean up this file and remove this comment. // TODO: This should be done using require.context import { Injectable } from '@angular/core'; import { createModuleTest } from './tests/createModuleTest'; import { createComponentTest } from './tests/createComponentTest'; import { createBootstrapTest } from './tests/bootstrapTest'; import { DiffFilesResolver } from '../libs/utils/src/lib/differ/diffFilesResolver'; declare const require; const preloadedFiles = { 'app.component.ts': require(`!raw-loader!./app.component.ts`), 'app.module.ts': require('!raw-loader!./app.module.ts'), 'app.html': require('!raw-loader!./app.html'), 'main.ts': require('!raw-loader!./main.ts'), 'style.css': require('!raw-loader!./style.css'), 'video/video-item.ts': require('!raw-loader!./video/video-item.ts'), 'api.service.ts': require('!raw-loader!./api.service.ts'), 'material.css': require('!!raw-loader!@angular/material/prebuilt-themes/indigo-pink.css'), 'search/search.component.html': require('!raw-loader!./search/search.component.html'), 'search/search.component.ts': require('!raw-loader!./search/search.component.ts'), 'upload/upload.component.html': require('!raw-loader!./upload/upload.component.html'), 'upload/upload.component.ts': require('!raw-loader!./upload/upload.component.ts'), 'video/video.service.ts': require('!raw-loader!./video/video.service.ts'), 'video/video.component.html': require('!raw-loader!./video/video.component.html'), 'video/video-materialized.component.html': require('!raw-loader!./video/video-materialized.component.html'), 'video/video.index.html': require('!raw-loader!./video/video.index.html'), 'video/video-wrapper.component.ts': require('!raw-loader!./video/video-wrapper.component.ts'), 'video/video.component.ts': require('!raw-loader!./video/video.component.ts'), 'thumbs/thumbs.component.ts': require('!raw-loader!./thumbs/thumbs.component.ts'), 'thumbs/thumbs.html': require('!raw-loader!./thumbs/thumbs.html'), 'toggle-panel/toggle-panel.html': require('!raw-loader!./toggle-panel/toggle-panel.html'), 'toggle-panel/toggle-panel.component.ts': require('!raw-loader!./toggle-panel/toggle-panel.component.ts'), 'wrapper.component.ts': require('!raw-loader!./wrapper.component.ts'), 'context/context.component.ts': require('!raw-loader!./context/context.component.ts'), 'context/context.service.ts': require('!raw-loader!./context/context.service.ts'), 'context/context.html': require('!raw-loader!./context/context.html'), 'typescript-intro/Codelab.ts': require('!raw-loader!./typescript-intro/Codelab.ts'), 'typescript-intro/Main.ts': require('!raw-loader!./typescript-intro/Main.ts'), 'typescript-intro/Guest.ts': require('!raw-loader!./typescript-intro/Guest.ts'), 'fuzzy-pipe/fuzzy.pipe.ts': require('!raw-loader!./fuzzy-pipe/fuzzy.pipe.ts'), 'tests/codelabTest.ts': require('!raw-loader!./tests/codelabTest.ts'), 'tests/createComponentTest.ts': require('!raw-loader!./tests/createComponentTest.ts'), 'tests/createModuleTest.ts': require('!raw-loader!./tests/createModuleTest.ts'), 'tests/bootstrapTest.ts': require('!raw-loader!./tests/bootstrapTest.ts'), 'tests/templatePageSetupTest.ts': require('!raw-loader!./tests/templatePageSetupTest.ts'), 'tests/routerTest.ts': require('!raw-loader!./tests/routerTest.ts'), 'tests/formsTest.ts': require('!raw-loader!./tests/formsTest.ts'), 'tests/materialTest.ts': require('!raw-loader!./tests/materialTest.ts'), 'tests/templateAddActionTest.ts': require('!raw-loader!./tests/templateAddActionTest.ts'), 'tests/templateAllVideosTest.ts': require('!raw-loader!./tests/templateAllVideosTest.ts'), 'tests/diInjectServiceTest.ts': require('!raw-loader!./tests/diInjectServiceTest.ts'), 'tests/videoComponentCreateTest.ts': require('!raw-loader!./tests/videoComponentCreateTest.ts'), 'tests/videoComponentUseTest.ts': require('!raw-loader!./tests/videoComponentUseTest.ts'), 'tests/ThumbsComponentCreateTest.ts': require('!raw-loader!./tests/ThumbsComponentCreateTest.ts'), 'tests/ThumbsComponentUseTest.ts': require('!raw-loader!./tests/ThumbsComponentUseTest.ts'), 'tests/togglePanelComponentCreateTest.ts': require('!raw-loader!./tests/togglePanelComponentCreateTest.ts'), 'tests/togglePanelComponentUseTest.ts': require('!raw-loader!./tests/togglePanelComponentUseTest.ts'), 'tests/contextComponentUseTest.ts': require('!raw-loader!./tests/contextComponentUseTest.ts'), 'tests/fuzzyPipeCreateTest.ts': require('!raw-loader!./tests/fuzzyPipeCreateTest.ts'), 'tests/fuzzyPipeUseTest.ts': require('!raw-loader!./tests/fuzzyPipeUseTest.ts'), 'thumbs.app.module.ts': require('!raw-loader!./thumbs.app.module.ts'), 'video.app.module.ts': require('!raw-loader!./video.app.module.ts'), 'toggle-panel.app.module.ts': require('!raw-loader!./toggle-panel.app.module.ts'), 'index.html': '
        ' // 'index.html': '' }; const files = { appComponent: 'app.component.ts', appModule: 'app.module.ts', appHtml: 'app.html', main: 'main.ts', video_videoItem: 'video/video-item.ts', apiService: 'api.service.ts', search_search_component_html: 'search/search.component.html', search_search_component: 'search/search.component.ts', upload_upload_component_html: 'upload/upload.component.html', upload_upload_component: 'upload/upload.component.ts', video_videoService: 'video/video.service.ts', video_video_component_html: 'video/video.component.html', video_video_component: 'video/video.component.ts', video_video_wrapper_component: 'video/video-wrapper.component.ts', video_video_index_html: 'video/video.index.html', thumbs_thumbs_component: 'thumbs/thumbs.component.ts', thumbs_thumbs_html: 'thumbs/thumbs.html', toggle_panel_toggle_panel_html: 'toggle-panel/toggle-panel.html', toggle_panel_toggle_panel: 'toggle-panel/toggle-panel.component.ts', wrapperComponent: 'wrapper.component.ts', contextComponent: 'context/context.component.ts', context_context_html: 'context/context.html', contextService: 'context/context.service.ts', typescript_intro_Codelab_ts: 'typescript-intro/Codelab.ts', typescript_intro_Main_ts: 'typescript-intro/Main.ts', typescript_intro_Guest_ts: 'typescript-intro/Guest.ts', fuzzyPipe_fuzzyPipe: 'fuzzy-pipe/fuzzy.pipe.ts', test: 'tests/test.ts', indexHtml: 'index.html', style_css: 'style.css', material_css: 'material.css' }; const fileOverrides = { 'index.html': { videoComponentCreate: 'video/video.index.html' }, 'app.module.ts': { videoComponentCreate: 'video.app.module.ts', thumbsComponentCreate: 'thumbs.app.module.ts', togglePanelComponentCreate: 'toggle-panel.app.module.ts' }, 'tests/test.ts': { codelab: 'tests/codelabTest.ts', createComponent: 'tests/createComponentTest.ts', createModule: 'tests/createModuleTest.ts', bootstrap: 'tests/bootstrapTest.ts', templatePageSetup: 'tests/templatePageSetupTest.ts', templateAddAction: 'tests/templateAddActionTest.ts', templateAllVideos: 'tests/templateAllVideosTest.ts', diInjectService: 'tests/diInjectServiceTest.ts', videoComponentCreate: 'tests/videoComponentCreateTest.ts', videoComponentUse: 'tests/videoComponentUseTest.ts', thumbsComponentCreate: 'tests/ThumbsComponentCreateTest.ts', thumbsComponentUse: 'tests/ThumbsComponentUseTest.ts', togglePanelComponentCreate: 'tests/togglePanelComponentCreateTest.ts', togglePanelComponentUse: 'tests/togglePanelComponentUseTest.ts', contextComponentUse: 'tests/contextComponentUseTest.ts', fuzzyPipeCreate: 'tests/fuzzyPipeCreateTest.ts', fuzzyPipeUse: 'tests/fuzzyPipeUseTest.ts', router: 'tests/routerTest.ts', material: 'tests/materialTest.ts', forms: 'tests/formsTest.ts' }, 'video/video.component.html': { material: 'video/video-materialized.component.html', forms: 'video/video-materialized.component.html' } }; const stageOverrides = { 'main.ts': { createComponent: 'bootstrapSolved', createModule: 'bootstrapSolved', }, 'app.module.ts': { createComponent: 'bootstrapSolved' } }; const stages: string[] = [ 'codelab', 'createComponent', 'createModule', 'bootstrap', 'templatePageSetup', 'templateAddAction', 'templateAllVideos', 'diInjectService', 'dataBinding', 'videoComponentCreate', 'videoComponentUse', 'router', 'material', 'forms', 'thumbsComponentCreate', 'thumbsComponentUse', 'togglePanelComponentCreate', 'togglePanelComponentUse', 'contextComponentUse', 'fuzzyPipeCreate', 'fuzzyPipeUse', 'neverShow' ]; const diffFilesResolver = new DiffFilesResolver(preloadedFiles, stages, { file: fileOverrides, stage: stageOverrides }); export interface CodelabConfigTemplate { name: string; id: string; defaultRunner: string; milestones: MilestoneConfigTemplate[]; } export interface SlideTemplate { slide: true; name: string; } export interface ExerciseConfigTemplate { slide?: false; name: string; skipTests?: boolean; runner?: string; files: { exercise?: string[]; reference?: string[]; hidden?: string[]; bootstrap?: string[]; test?: string[]; }; } export interface MilestoneConfigTemplate { name: string; exercises: Array; } function patchATestWithAFunctionINAHackyWay(exercisesFiles, path, callback) { return exercisesFiles.map(file => { if (file.path === path) { file.execute = callback; } return file; }); } export function convertExerciseToMap(exercise) { const convertFilesToMap = (prop = 'template') => (result, file) => { if (file[prop]) { result[file.path] = file[prop]; if (file.execute) { result[file.path + '.execute'] = file.execute; } } return result; }; const testBootstrap = exercise.files.find(({bootstrap, excludeFromTesting, template}) => template && bootstrap && !excludeFromTesting); const bootstrapFiles = exercise.files.find(({bootstrap, excludeFromTesting, template}) => template && bootstrap && excludeFromTesting); return { highlights: exercise.files.filter(({highlight}) => highlight).reduce((result, {highlight, path}) => (result[path] = highlight, result), {}), code: exercise.files.reduce(convertFilesToMap(), {}), codeSolutions: exercise.files.map(file => ((file.solution = file.solution || file.template), file)).reduce(convertFilesToMap('solution'), {}), test: exercise.files.filter(file => !file.excludeFromTesting).reduce(convertFilesToMap(), {}), bootstrap: bootstrapFiles && bootstrapFiles.moduleName, bootstrapTest: testBootstrap && testBootstrap.moduleName, file: exercise.files[0].path }; } export const ng2tsConfig: /*TODO: fix the type to be: CodelabConfigTemplate */any = { name: 'Angular 101 Codelab (beta)', id: 'ng2ts', defaultRunner: 'Angular', milestones: [ { name: 'Intro to TypeScript', exercises: [ { name: 'Intro', slide: true }, { name: 'TypeScript', runner: 'TypeScript', files: diffFilesResolver.resolve('codelab', { exercise: [ files.typescript_intro_Codelab_ts, files.typescript_intro_Guest_ts, files.typescript_intro_Main_ts ], test: [files.test], bootstrap: [ files.typescript_intro_Main_ts ] }), } ] }, { name: 'Bootstrapping your app', exercises: [ { name: 'Intro', slide: true }, { name: 'Create a component', files: patchATestWithAFunctionINAHackyWay(diffFilesResolver.resolve('createComponent', { exercise: [files.appComponent], reference: [files.appModule, files.main, files.style_css, files.indexHtml], bootstrap: [files.main], test: [files.test], }), 'tests/test.ts', createComponentTest) }, { name: 'Create a NgModule', files: patchATestWithAFunctionINAHackyWay(diffFilesResolver.resolve('createModule', { exercise: [files.appModule], reference: [files.appComponent], hidden: [files.main], test: [files.test], bootstrap: [files.main] }), 'tests/test.ts', createModuleTest) }, { name: 'Bootstrap the module', skipTests: true, files: patchATestWithAFunctionINAHackyWay(diffFilesResolver.resolve('bootstrap', { exercise: [files.main], reference: [files.appComponent, files.appModule], test: [files.test], bootstrap: [files.main] }), 'tests/test.ts', createBootstrapTest) } ] }, { name: 'Templates', exercises: [ { name: 'Intro', slide: true }, { name: 'Set up the page', files: diffFilesResolver.resolve('templatePageSetup', { exercise: [files.appHtml], reference: [files.appComponent, files.appModule, files.main, files.style_css, files.indexHtml], test: [files.test], bootstrap: [files.main] }) }, { name: 'Add some action', files: diffFilesResolver.resolve('templateAddAction', { exercise: [files.appComponent, files.appHtml], reference: [files.appModule, files.main, files.video_videoItem, files.style_css, files.indexHtml], test: [files.test], bootstrap: [files.main], }) }, { name: 'Display all videos', files: diffFilesResolver.resolve('templateAllVideos', { exercise: [files.appComponent, files.appHtml], reference: [files.appModule, files.main, files.video_videoItem, files.style_css, files.indexHtml], test: [files.test], bootstrap: [files.main] }) } ] }, { name: 'Dependency Injection', exercises: [ { name: 'Intro', slide: true }, { name: 'Service injection', files: diffFilesResolver.resolve('diInjectService', { exercise: [files.video_videoService, files.appModule, files.appComponent], reference: [ files.appHtml, files.apiService, files.video_videoItem, files.main, files.style_css, files.indexHtml ], test: [files.test], bootstrap: [files.main] }) } ] } , { name: 'Component Tree', exercises: [ { name: 'Intro', slide: true, }, { name: 'Create VideoComponent', files: diffFilesResolver.resolve('videoComponentCreate', { exercise: [files.video_video_component, files.video_video_component_html], reference: [ files.appModule, files.video_video_wrapper_component, files.video_videoService, files.appHtml, files.appComponent, files.video_videoItem, files.apiService, files.main, files.style_css, files.indexHtml ], test: [files.test], bootstrap: [files.main] }) }, { name: 'Use VideoComponent', files: diffFilesResolver.resolve('videoComponentUse', { exercise: [files.appModule, files.appHtml], reference: [ files.video_video_component_html, files.video_video_component, files.appComponent, files.video_videoService, files.video_videoItem, files.apiService, files.main, files.style_css, files.indexHtml ], test: [files.test], bootstrap: [files.main] }) } ] }, { name: 'Routing', exercises: [ { name: 'Router', files: diffFilesResolver.resolve('router', { exercise: [ files.appModule, files.appHtml, files.search_search_component, files.search_search_component_html, files.upload_upload_component, files.upload_upload_component_html, ], reference: [ files.video_video_component_html, files.video_video_component, files.appComponent, files.video_videoService, files.video_videoItem, files.apiService, files.main, files.style_css, files.indexHtml ], test: [files.test], bootstrap: [files.main] }) } ] }, { name: 'Material', exercises: [ { name: 'Material', files: diffFilesResolver.resolve('material', { exercise: [ files.appModule, files.video_video_component_html, files.appHtml, ], reference: [ files.video_video_component, files.appComponent, files.video_videoService, files.video_videoItem, files.apiService, files.main, files.style_css, files.indexHtml, files.search_search_component, files.search_search_component_html, files.upload_upload_component, files.upload_upload_component_html, files.material_css ], test: [files.test], bootstrap: [files.main] }) } ] }, { name: 'Forms', exercises: [ { name: 'Forms', files: diffFilesResolver.resolve('forms', { exercise: [ files.appModule, files.upload_upload_component_html, files.upload_upload_component, ], reference: [ files.appHtml, files.search_search_component, files.search_search_component_html, files.video_video_component_html, files.video_video_component, files.appComponent, files.video_videoService, files.video_videoItem, files.apiService, files.main, files.style_css, files.indexHtml, files.material_css ], test: [files.test], bootstrap: [files.main] }) } ] }, { name: 'Custom events', exercises: [ { name: 'Intro', slide: true }, { name: 'Create ThumbsComponent', files: diffFilesResolver.resolve('thumbsComponentCreate', { exercise: [files.thumbs_thumbs_component, files.thumbs_thumbs_html], reference: [files.apiService, files.appModule, files.main, files.style_css, files.indexHtml], test: [files.test], bootstrap: [files.main] }) }, { name: 'Use ThumbsComponent', files: diffFilesResolver.resolve('thumbsComponentUse', { exercise: [files.video_video_component, files.video_video_component_html, files.appModule], reference: [ files.thumbs_thumbs_component, files.thumbs_thumbs_html, files.appHtml, files.appComponent, files.video_videoService, files.video_videoItem, files.apiService, files.main, files.style_css, files.indexHtml ], test: [files.test], bootstrap: [files.main] }) } ] }, { name: 'Content projection', exercises: [ { name: 'Intro', slide: true }, { name: 'Add TogglePanelComponent', files: diffFilesResolver.resolve('togglePanelComponentCreate', { exercise: [files.toggle_panel_toggle_panel, files.toggle_panel_toggle_panel_html], reference: [ files.wrapperComponent, files.apiService, files.appModule, files.main, files.style_css, files.indexHtml ], test: [files.test], bootstrap: [files.main] }) }, { name: 'Use TogglePanelComponent', files: diffFilesResolver.resolve('togglePanelComponentUse', { exercise: [files.video_video_component_html, files.appModule], reference: [ files.video_video_component, files.toggle_panel_toggle_panel, files.toggle_panel_toggle_panel_html, files.thumbs_thumbs_component, files.thumbs_thumbs_html, files.appHtml, files.appComponent, files.video_videoService, files.video_videoItem, files.apiService, files.main, files.indexHtml ], test: [files.test], bootstrap: [files.main] }) } ] }, { name: 'Pipes (bonus)', exercises: [ { name: 'Create a pipe', files: diffFilesResolver.resolve('fuzzyPipeCreate', { exercise: [files.fuzzyPipe_fuzzyPipe], test: [files.test], bootstrap: [files.main] }) }, { name: 'Use the pipe', files: diffFilesResolver.resolve('fuzzyPipeUse', { exercise: [files.appModule, files.video_video_component_html], reference: [ files.fuzzyPipe_fuzzyPipe, files.contextService, files.contextComponent, files.context_context_html, files.video_video_component, files.toggle_panel_toggle_panel, files.toggle_panel_toggle_panel_html, files.thumbs_thumbs_component, files.thumbs_thumbs_html, files.appHtml, files.appComponent, files.video_videoService, files.video_videoItem, files.apiService, files.main, files.indexHtml ], test: [files.test], bootstrap: [files.main] }) } ] } , { name: 'Survey', exercises: [ { slide: true, name: 'All done!' } ] } ] }; @Injectable() export class Ng2TsExercises { getExercises(milestoneId: number, exerciseId: number): ExerciseConfigTemplate { return ng2tsConfig.milestones[milestoneId].exercises[exerciseId]; } } ================================================ FILE: ng2ts/search/search.component.html ================================================
        No videos!
        ================================================ FILE: ng2ts/search/search.component.ts ================================================ import { Component } from '@angular/core'; import { VideoItem } from '../video/video-item'; import { VideoService } from '../video/video.service'; @Component({ selector: 'slides-search-component', templateUrl: './search.component.html' }) export class SearchComponent { videos: VideoItem[] = []; title = 'MewTube'; constructor(public videoService: VideoService) { this.search(''); } search(searchString: string) { this.videos = this.videoService.search(searchString); } } ================================================ FILE: ng2ts/style.css ================================================ body, html { margin: 0; padding: 0; } ================================================ FILE: ng2ts/tests/ThumbsComponentCreateTest.ts ================================================ import { Api } from '../api.service'; import { TestBed } from '@angular/core/testing'; import { Thumbs, ThumbsComponent } from '../thumbs/thumbs.component'; import { thumbs_thumbs_html } from '../code'; import 'initTestBed'; const thumbs = Api.fetch('')[0]; beforeEach(() => { try { TestBed.resetTestingModule(); TestBed.configureTestingModule({ providers: [], declarations: [ThumbsComponent] }); TestBed.overrideComponent(ThumbsComponent, { set: { template: thumbs_thumbs_html } }); try { TestBed.compileComponents(); } catch(e) { console.log(e); } } catch (e) { } }); describe('Component Tree', () => { describe('Make sure things are displayed properly', () => { let fixture; beforeEach(() => { try { fixture = TestBed.createComponent(ThumbsComponent); fixture.detectChanges(); } catch (e) { } }); it(`thumbs.html: Add a button with a 'thumbs-up' CSS class.`, () => { chai.expect(fixture.nativeElement.querySelector('.thumbs-up'), `can't find thumbs up button`).to.be.ok; }); it(`thumbs.html: Add a button with a 'thumbs-down' CSS class.`, () => { chai.expect(fixture.nativeElement.querySelector('.thumbs-down'), `can't find thumbs down button`).to.be.ok; }); }); describe('Make sure things work', () => { it(`Thumbs.Component.ts: Add the '@Component' decorator and set its selector to be 'my-thumbs'.`, () => { const metadata = ThumbsComponent['__annotations__'][0]; chai.expect(metadata, `ThumbsComponent doesn't have a @Component() annotation`).is.not.undefined; chai.expect(metadata.selector, `ThumbsComponent's selector has to be 'my-thumbs'.`).equals('my-thumbs') }); it(`Thumbs.Component.ts: Set the templateUrl to load appropriate html file.`, () => { const metadata = ThumbsComponent['__annotations__'][0]; chai.expect(metadata, `ThumbsComponent doesn't have a @Component() annotation`).is.not.undefined; chai.expect(metadata.templateUrl, `ThumbsComponent's templateUrl should be set to './thumbs.html'`).equals('./thumbs.html') }); // TODO: split it(`Thumbs.Component.ts: Add an 'onThumbs' property and set the value to a new EventEmitter. Decorate with @Output()`, () => { const metadata = ThumbsComponent['__annotations__'][0]; chai.expect(metadata, `ThumbsComponent doesn't have any @Outputs()'s`).is.not.undefined; chai.expect(Object.keys(metadata).length, `ThumbsComponent doesn't have any @Outputs()'s`).equals(1); chai.expect(metadata.onThumbs, `ThumbsComponent's @Outputs()' should be called onThumbs.`).is.not.undefined; }); }); describe('Make sure things are displayed properly', () => { let fixture; beforeEach(() => { try { fixture = TestBed.createComponent(ThumbsComponent); fixture.detectChanges(); } catch (e) { } }); it(`thumbs.html: Make the 'thumbs-up' button emit the onThumbs event with the correct thumbs ENUM value.`, () => { let thumbs = null; fixture.componentInstance.onThumbs.subscribe((event) => { thumbs = event; }); chai.expect(thumbs, `OnThumbs was called without pressing the button`).to.be.not.ok; fixture.nativeElement.querySelector('.thumbs-up').click(); chai.expect(thumbs, `OnThumbs was not called when pressing the button with the 'thumbs-up' class.`).to.equal(Thumbs.UP); }); it(`thumbs.html: Make the 'thumbs-down' button emit the onThumbs event with the correct thumbs ENUM value.`, () => { let thumbs = null; fixture.componentInstance.onThumbs.subscribe((event) => { thumbs = event; }); chai.expect(thumbs, `OnThumbs was called without pressing the button`).to.be.not.ok; fixture.nativeElement.querySelector('.thumbs-down').click(); chai.expect(thumbs, `OnThumbs was not called when pressing the button with the 'thumbs-down' class.`).to.equal(Thumbs.DOWN); }); }); }); ================================================ FILE: ng2ts/tests/ThumbsComponentUseTest.ts ================================================ import { Api } from '../api.service'; import { AppModule } from '../app.module'; import { TestBed } from '@angular/core/testing'; import { thumbs_thumbs_html, video_video_component_html } from '../code'; import { ThumbsComponent } from '../thumbs/thumbs.component'; import { VideoComponent } from '../video/video.component'; import 'initTestBed'; beforeEach(() => { TestBed.resetTestingModule(); TestBed.configureTestingModule({ providers: [], declarations: [VideoComponent, ThumbsComponent] }); TestBed.overrideComponent(VideoComponent, { set: { template: video_video_component_html } }); TestBed.overrideComponent(ThumbsComponent, { set: { template: thumbs_thumbs_html } }); try { TestBed.compileComponents(); } catch(e) { console.log(e); } }); describe('Component Tree', () => { it(`app.module.ts: Add the ThumbsComponent to the AppModule 'declarations' property`, () => { let metadata; try { metadata = AppModule['__annotations__'][0]; } catch (e) { // Do nothing, we have assertions below for this case } chai.expect(metadata.declarations || [], `Thumbs component not found`).contains(ThumbsComponent); chai.expect(metadata.declarations || [], `Keep the app component`).contains(VideoComponent); }); it(`video/video.component.html: Use the thumbs component in the template`, () => { const fixture = TestBed.createComponent(VideoComponent); fixture.componentInstance.video = Api.fetch('')[0]; fixture.detectChanges(); chai.expect(fixture.nativeElement.querySelector('.thumbs-up')).is.ok; chai.expect(fixture.nativeElement.querySelector('.thumbs-down')).is.ok; }); it(`VideoComponent: Listen to the thumbs component onThumbs event, and update the amount of likes accordingly`, () => { const fixture = TestBed.createComponent(VideoComponent); fixture.componentInstance.video = Api.fetch('')[0]; fixture.detectChanges(); const likes = fixture.componentInstance.video.likes; // TODO: test it. fixture.nativeElement.querySelector('.thumbs-up').click(); chai.expect(fixture.nativeElement.querySelector('.thumbs-up'), 'Thumbs up component is not present').to.be.ok; chai.expect(fixture.componentInstance.video.likes).equals(likes + 1); fixture.nativeElement.querySelector('.thumbs-down').click(); chai.expect(fixture.nativeElement.querySelector('.thumbs-down'), 'Thumbs down component is not present').to.be.ok; chai.expect(fixture.componentInstance.video.likes).equals(likes); }); }); ================================================ FILE: ng2ts/tests/bootstrapTest.ts ================================================ import { isCallExpression, isIdentifier } from 'babel-types'; import { babelTestSuite } from '../../apps/codelab/src/app/components/babel-test-runner/babel-helpers'; const tests = [ { title: `@@allSetBootstrapApp`, condition: ({node, parent}) => isIdentifier(node, {name: 'AppModule'}) && isCallExpression(parent) && parent.callee.property.name === 'bootstrapModule' } ]; export const createBootstrapTest = babelTestSuite('main.ts', tests); ================================================ FILE: ng2ts/tests/codelabTest.ts ================================================ /** * In the test it's possible to get access to sourcecode, as well as to the code AST. */ import { ts, typescript_intro_Codelab_ts_AST } from '../code'; /** * This is a good sample sample of a codelab exercise. * * An exercise is just a folder with a bunch of files. * * the configuration is in app/codelab/codelab-config.ts. * * * There are * */ /** * solution/ prefix is used to let the test typechecked. * It will be stripped during runtime, and the Codelab module * will be loaded. */ import { Codelab, evalJs } from '../typescript-intro/Codelab'; const guests = [ {name: 'me', coming: true}, {name: 'notme', coming: false}, ]; function getConstructorNode(code) { let constructorNode = undefined; /** * Fancy: Require the actual source code, and search in it. */ function findConstructor(node) { if (node.kind === ts.SyntaxKind.Constructor) { constructorNode = node; } ts.forEachChild(node, findConstructor); } findConstructor(code); return constructorNode; } describe('Component', () => { it(`@@createClassCodelab`, () => { /** * We can use evalJs to get into the scope of the user's file. * Currently evalJs has to be manually added to the `before` * section in the file config. * * I expert the primary use case for eval js would be to remind * the user to export something. * * e.g. if the user created teh class, but haven't exported it this * test will still pass. */ chai.expect(typeof evalJs('Codelab')).equals('function'); }); it(`@@exportClass`, () => { /** * Require the class, assert it's a function (compile target is es5). */ chai.expect(typeof Codelab).equals('function'); }); it('@@addConstructor', () => { let hasConstructor = false; /** * Fancy: Require the actual source code, and search in it. */ function findConstructor(node) { if (node.kind === ts.SyntaxKind.Constructor) { hasConstructor = true; } ts.forEachChild(node, findConstructor); } findConstructor(typescript_intro_Codelab_ts_AST); chai.expect(hasConstructor, `Codelab doesn't have a constuctor`).is.true; }); it(`@@makeConstructorTakeParamGuests`, () => { const constructorNode = getConstructorNode(typescript_intro_Codelab_ts_AST); chai.expect(constructorNode, `Codelab doesn't have a constuctor`).to.be.ok; chai.expect(constructorNode.parameters.length, `Codelab's constructor should take a parameter`).to.equal(1); chai.expect(constructorNode.parameters[0].name.text, `Codelab constructor's parameter should be called 'guests'`).equals('guests'); }); it(`@@specifyTheTypeForGuests`, () => { const constructorNode = getConstructorNode(typescript_intro_Codelab_ts_AST); chai.expect(constructorNode, `Codelab doesn't have a constuctor`).to.be.ok; chai.expect(constructorNode.parameters.length, `Codelab's constructor should take a parameter`).to.equal(1); chai.expect(constructorNode.parameters[0].name.text, `Codelab constructor's parameter should be called 'guests'`).equals('guests'); const type = constructorNode.parameters[0].type; const isArrayOfGuest = /* Array */(type.kind === ts.SyntaxKind.TypeReference && type.typeName.text === 'Array' && type.typeArguments.length === 1 && type.typeArguments[0].typeName.text === 'Guest') || /* Guest[] */ (type.kind === ts.SyntaxKind.ArrayType && type.elementType.kind === ts.SyntaxKind.TypeReference && type.elementType.typeName.text === 'Guest'); chai.expect(isArrayOfGuest, `The type for guests should be Array of Guest (hint: Array is one way of doing it.)`).to.be.ok; }); it('@@makeParemeterPublic', () => { const constructorNode = getConstructorNode(typescript_intro_Codelab_ts_AST); const parameter = constructorNode.parameters[0]; chai.expect(parameter.modifiers.length === 1 && parameter.modifiers[0].kind === ts.SyntaxKind.PublicKeyword, `'guests' constructor parameter should have 'public' visibility.`).to.be.ok; }); it(`createNewMethodGetGuestsComing`, () => { chai.expect(typeof (new Codelab(guests).getGuestsComing)).equals('function'); }); it(`@@modifyGetGuestsComingToFilter`, () => { const guestsComing = new Codelab(guests).getGuestsComing(); chai.assert(Array.isArray(guestsComing), 'getGuestsComing must return an Array, current value: ' + guestsComing); chai.expect(guestsComing.length).equals(1); }); /* xit(`Let's debug the app! You'll need this if something goes wrong. * Open the dev tools in your browser * Put in the new method add 'debugger;' * The app will stop, and you'll be able to inspect local variables. * Get out using F8 * We can't really test this, so this test is marked as passed `, () => { }); */ }); ================================================ FILE: ng2ts/tests/contextComponentUseTest.ts ================================================ import { Api } from '../api.servie'; import { app_html, context_context_html, thumbs_thumbs_html, toggle_panel_toggle_panel_html, video_video_component_html } from '../code'; import { AppComponent } from '../app.component'; import { AppModule } from '../app.module'; import { ContextComponent } from '../context/context.component'; import { ContextService } from '../context/context.service'; import { TestBed } from '@angular/core/testing'; import { ThumbsComponent } from '../thumbs/thumbs.component'; import { TogglePanelComponent } from '../toggle-panel/toggle-panel.component'; import { VideoComponent } from '../video/video.component'; import { VideoService } from '../video/video.service'; import 'initTestBed'; function objectValues(object) { return Object.keys(object).reduce((result, key) => { result.push(object[key]); return result; }, []); } function objectFindPropOfType(object, Type) { return Object.keys(object).reduce((prop, key) => { if (prop) { return prop; } if (object[key] instanceof Type) { return key; } }, undefined); } function objectHasAn(object, Type) { return objectValues(object).some(val => val instanceof Type); } const sampleVideo = Api.fetch('')[0]; beforeEach(() => { TestBed.resetTestingModule(); TestBed.configureTestingModule({ providers: [VideoService, ContextService, /* that's a hack, to provide parent component */ VideoComponent], declarations: [AppComponent, VideoComponent, TogglePanelComponent, ContextComponent, ThumbsComponent] }); TestBed.overrideComponent(AppComponent, {set: {template: app_html}}); TestBed.overrideComponent(VideoComponent, {set: {template: video_video_component_html}}); TestBed.overrideComponent(TogglePanelComponent, {set: {template: toggle_panel_toggle_panel_html}}); TestBed.overrideComponent(ContextComponent, {set: {template: context_context_html}}); TestBed.overrideComponent(ThumbsComponent, {set: {template: thumbs_thumbs_html}}); try { TestBed.compileComponents(); } catch(e) { console.log(e); } }); describe('Children', () => { it(`ContextComponent: Inject the ContextService into the constructor and store it as a property.`, () => { const fixture = TestBed.createComponent(ContextComponent); chai.expect(objectHasAn(fixture.componentInstance, ContextService)).to.be.true; }); it(`ContextComponent: Inject the parent component (VideoComponent) into the constructor and store it as a property.`, () => { const fixture = TestBed.createComponent(ContextComponent); chai.expect(objectHasAn(fixture.componentInstance, VideoComponent)).to.be.true; }); it(`ContextComponent: Add an ngOnInit method to the component. (It's a special method angular will call when the component is created).`, () => { const fixture = TestBed.createComponent(ContextComponent); chai.expect(fixture.componentInstance.ngOnInit).is.a('function'); }); it(`ContextComponent: In the onOnInit method Call 'getAdText' on the service, and pass it the video 'description' provided by the injected video component. Assign the result to the declared text property.`, () => { const fixture = TestBed.createComponent(ContextComponent); const componentInstance = fixture.componentInstance; const vcProp = objectFindPropOfType(componentInstance, VideoComponent); chai.expect(vcProp, `'VideoComponent' was not injected.`).to.not.be.undefined; componentInstance[vcProp].video = sampleVideo; chai.expect(componentInstance.ngOnInit).is.a('function'); componentInstance[vcProp].video.description = 'music'; componentInstance.ngOnInit(); fixture.detectChanges(); chai.expect(fixture.nativeElement.innerHTML).to.contain('speakers'); componentInstance[vcProp].video.description = 'banana'; componentInstance.ngOnInit(); fixture.detectChanges(); chai.expect(fixture.nativeElement.innerHTML).to.contain('Check out our web site'); }); it(`app.module.ts: Add the ContextComponent to the AppModule declarations (We did this for you).`, () => { let metadata; try { metadata = AppModule['__annotations__'][0]; } catch (e) { // Do nothing, we have assertions below for this case } chai.expect(metadata.declarations || [], `Video component not found`).contains(ContextComponent); }); it(`video/video.component.html: Actually display the ad (We actually also did it for you).`, () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); // TODO: Actually write a test // chai.expect(fixture.nativeElement.querySelector('my-ad')).to.be.ok }); }); ================================================ FILE: ng2ts/tests/createComponentTest.ts ================================================ import { babelTestSuite, expectClass, expectDecorator, expectDecoratorPropertyStringValue, expectExportedClass } from '../../apps/codelab/src/app/components/babel-test-runner/babel-helpers'; const tests = [ { title: `@@createClassAppComponent`, condition: expectClass('AppComponent') }, { title: `@@exportClass`, condition: expectExportedClass('AppComponent') }, { title: `@@addComponentDecorator`, condition: expectDecorator('Component') }, { title: `@@addSelectorMyApp`, condition: expectDecoratorPropertyStringValue( 'Component', 'selector', 'my-app' ) }, { title: `@@addTemplateHelloMewTube`, condition: expectDecoratorPropertyStringValue( 'Component', 'template', /

        \s*Hello MewTube!\s*<\/h1>/ ) } ]; export const createComponentTest = babelTestSuite('app.component.ts', tests); ================================================ FILE: ng2ts/tests/createModuleTest.ts ================================================ import { isDecorator, isIdentifier, isObjectProperty } from 'babel-types'; import { babelTestSuite, expectClass, expectDecorator, expectExportedClass } from '../../apps/codelab/src/app/components/babel-test-runner/babel-helpers'; const tests = [ { title: `@@createClassAppModule`, condition: expectClass('AppModule') }, { title: `@@exportClass`, condition: expectExportedClass('AppModule') }, { title: `@@addNgModuleDecorator`, condition: expectDecorator('NgModule') }, { title: `@@addBrowserModuleToNgModule`, condition: (path) => { if (!isIdentifier(path.node, {name: 'BrowserModule'})) { return false; } const ObjectProperty = path.findParent(n => isObjectProperty(n)); const Decorator = path.findParent(n => isDecorator(n)); return ObjectProperty && Decorator && ObjectProperty.node.key.name === 'imports' && Decorator.node.expression.callee.name === 'NgModule'; } }, { title: `@@addAppComponentToDeclarations`, condition: (path) => { if (!isIdentifier(path.node, {name: 'AppComponent'})) { return false; } const ObjectProperty = path.findParent(n => isObjectProperty(n)); const Decorator = path.findParent(n => isDecorator(n)); return ObjectProperty && Decorator && ObjectProperty.node.key.name === 'declarations' && Decorator.node.expression.callee.name === 'NgModule'; } }, { title: `@@addAppComponentToBootstrap`, condition: (path) => { if (!isIdentifier(path.node, {name: 'AppComponent'})) { return false; } const ObjectProperty = path.findParent(n => isObjectProperty(n)); const Decorator = path.findParent(n => isDecorator(n)); return ObjectProperty && Decorator && ObjectProperty.node.key.name === 'bootstrap' && Decorator.node.expression.callee.name === 'NgModule'; } } ]; export const createModuleTest = babelTestSuite('app.module.ts', tests); ================================================ FILE: ng2ts/tests/diInjectServiceTest.ts ================================================ import { app_component_ts, app_html } from '../code'; import { AppComponent, evalJs } from '../app.component'; import { AppModule } from '../app.module'; import { TestBed } from '@angular/core/testing'; import { VideoService } from '../video/video.service'; import 'initTestBed'; beforeEach(() => { TestBed.resetTestingModule(); TestBed.configureTestingModule({ providers: [VideoService], declarations: [AppComponent] }); TestBed.overrideComponent(AppComponent, { set: { template: app_html, templateUrl: undefined } }); try { TestBed.compileComponents(); } catch (e) { console.log(e); } }); describe('Blabla', () => { it(`@@addIjectableDecoraterToClass`, () => { let metadata; try { metadata = VideoService['__annotations__'][0]; } catch (e) { // Do nothing, we have assertions below for this case } chai.expect(metadata, 'Class VideoService has no decorators attached to it') .not.undefined; }); it(`@@addVideoServiceToNgModule`, () => { let metadata; try { metadata = AppModule['__annotations__'][0]; } catch (e) { // Do nothing, we have assertions below for this case } chai.expect( metadata.providers, 'Can not find "providers" property in the NgModule decorator of the app module' ).not.to.be.undefined; chai .expect(metadata.providers.length, '"providers" array is empty') .to.be.greaterThan(0); chai .expect( metadata.providers[0], 'Expect the first provider to be VideoService' ) .equals(VideoService); }); it(`@@getRidOfFakeVideos`, () => { chai .expect( evalJs('typeof FAKE_VIDEOS;'), 'Variable FAKE_VIDEOS is still present in the code' ) .equals('undefined'); }); it(`@@injectVideoService`, () => { chai .expect( AppComponent.length, `App component constructor doesn't take any parameters` ) .to.equal(1); chai.expect(app_component_ts).matches(/VideoService/); }); it(`@@updateAppComponentSearchmethod`, () => { const fixture = TestBed.createComponent(AppComponent); fixture.componentInstance.search('Itty'); chai.expect(fixture.componentInstance.videos.length).to.equal(3); }); }); ================================================ FILE: ng2ts/tests/diInjectServiceTestBabel.ts ================================================ import { isDecorator, isIdentifier, isObjectProperty } from 'babel-types'; import { MiniTsQuery, tsAstTestSuite } from '../../apps/codelab/src/app/components/babel-test-runner/babel-helpers'; const tests = [ { title: `@@addIjectableDecoraterToClass`, file: 'video/video.service.ts', condition(ast: MiniTsQuery) { return ast.hasDecorator('Injectable'); } }, { title: `@@addVideoServiceToNgModule`, file: 'app.module.ts', condition(ast: MiniTsQuery) { return ast.hasProvider('VideoService'); } }, { title: `@@getRidOfFakeVideos`, file: 'app.component.ts', condition(ast: MiniTsQuery) { return !ast.hasVariableDeclaration('FAKE_VIDEOS'); } }, { title: `@@injectVideoService`, file: 'app.component.ts', condition(ast: MiniTsQuery) { return ast.hasConstructorParam('videoService', 'VideoService'); } }, ]; export const createDITest = tsAstTestSuite(tests); ================================================ FILE: ng2ts/tests/formsTest.ts ================================================ import { upload_upload_component_html } from '../code'; import { TestBed } from '@angular/core/testing'; import { MatInputModule } from '@angular/material/input'; import { AppModule } from '../app.module'; import { UploadComponent } from '../upload/upload.component'; import { FormsModule } from '@angular/forms'; import 'initTestBed'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; export function createFakeEvent(type: string) { const event = document.createEvent('Event'); event.initEvent(type, true, true); return event; } export function dispatchFakeEvent(node: Node | Window, type: string) { node.dispatchEvent(createFakeEvent(type)); } describe('forms', () => { beforeEach(() => { try { TestBed.resetTestingModule(); TestBed.configureTestingModule({ declarations: [UploadComponent], imports: [FormsModule, MatInputModule, NoopAnimationsModule] }); TestBed.overrideComponent(UploadComponent, { set: { template: upload_upload_component_html, templateUrl: undefined } }); try { TestBed.compileComponents(); } catch (e) { console.log(e); } } catch (e) {} }); it('AddFormsModule', () => { let metadata; try { metadata = AppModule['__annotations__'][0]; } catch (e) { // Do nothing, we have assertions below for this case } chai.expect(metadata.imports).to.contain(MatInputModule); chai.expect(metadata.imports).to.contain(FormsModule); }); it('AddInput', done => { const fixture = TestBed.createComponent(UploadComponent); fixture.detectChanges(); fixture.whenStable().then(() => { const input = fixture.nativeElement.querySelector('input'); chai.expect(input).is.ok; done(); }); }); it('AddTitle', done => { const fixture = TestBed.createComponent(UploadComponent); fixture.componentInstance.title = 'hello'; fixture.detectChanges(); fixture.whenStable().then(() => { const input = fixture.nativeElement.querySelector('input'); chai.expect(input.value).is.equal('hello'); fixture.componentInstance.title = 'greatTitle'; fixture.detectChanges(); fixture.whenStable().then(() => { chai.expect(input.value).to.equal(fixture.componentInstance.title); done(); }); }); }); it('AddDescription', done => { const fixture = TestBed.createComponent(UploadComponent); fixture.componentInstance.description = 'hello'; fixture.detectChanges(); fixture.whenStable().then(() => { const textarea = fixture.nativeElement.querySelector('textarea'); chai.expect(textarea).is.ok; chai.expect(textarea.value).is.equal('hello'); fixture.componentInstance.description = 'greatdescription'; fixture.detectChanges(); fixture.whenStable().then(() => { chai .expect(textarea.value) .to.equal(fixture.componentInstance.description); done(); }); }); }, 4000); }); ================================================ FILE: ng2ts/tests/fuzzyPipeCreateTest.ts ================================================ import { evalJs, FuzzyPipe } from '../fuzzy-pipe/fuzzy.pipe'; import 'reflect-metadata'; let metadata; beforeEach(() => { try { metadata = FuzzyPipe['__annotations__'][0]; } catch (e) { } }); const d = new Date(); d.setDate(d.getDate() - 2); const formattedDate = d.toISOString().slice(0, 10); describe('Pipe', () => { it(`Create a class called FuzzyPipe`, () => { chai.expect(typeof evalJs('FuzzyPipe')).equals('function'); }); it(`Export it`, () => { chai.expect(typeof FuzzyPipe).equals('function'); }); it(`Add a @Pipe() decorator`, () => { chai.expect(metadata).is.an('array') }); it(`Set the name to fuzzy`, () => { chai.expect(metadata.name).equals('fuzzy'); }); it(`Make it return '2 days ago for '${formattedDate}'`, () => { let fuzzyTime = new FuzzyPipe(); chai.expect(fuzzyTime.transform(d.toISOString().slice(0, 10)).toLowerCase()).equals('2 ' + 'days'); }); }); ================================================ FILE: ng2ts/tests/fuzzyPipeUseTest.ts ================================================ import {Api} from '../api.service'; import { app_html, context_context_html, thumbs_thumbs_html, toggle_panel_toggle_panel_html, video_video_component_html } from '../code'; import {AppComponent} from '../app.component'; import {AppModule} from '../app.module'; import {ContextComponent} from '../context/context.component'; import {ContextService} from '../context/context.service'; import {FuzzyPipe} from '../fuzzy-pipe/fuzzy.pipe'; import {TestBed} from '@angular/core/testing'; import {ThumbsComponent} from '../thumbs/thumbs.component'; import {TogglePanelComponent} from '../toggle-panel/toggle-panel.component'; import {VideoComponent} from '../video/video.component'; import {VideoService} from '../video/video.service'; import 'initTestBed'; const sampleVideo = Api.fetch('')[0]; beforeEach(() => { TestBed.resetTestingModule(); TestBed.configureTestingModule({ providers: [VideoService, ContextService, /* that's a hack, to provide parent component */ VideoComponent], declarations: [AppComponent, VideoComponent, ThumbsComponent, TogglePanelComponent, ContextComponent, FuzzyPipe] }); TestBed.overrideComponent(AppComponent, {set: {template: app_html}}); TestBed.overrideComponent(VideoComponent, {set: {template: video_video_component_html}}); TestBed.overrideComponent(ThumbsComponent, {set: {template: thumbs_thumbs_html}}); TestBed.overrideComponent(TogglePanelComponent, {set: {template: toggle_panel_toggle_panel_html}}); TestBed.overrideComponent(ContextComponent, {set: {template: context_context_html}}); try { TestBed.compileComponents(); } catch(e) { console.log(e); } }); function sampleFuzzy(value) { const date = new Date(value); const dateNow = new Date(); const millisecondsDifference = dateNow.getTime() - date.getTime(); const differenceDays = Math.floor(millisecondsDifference / (1000 * 3600 * 24)); return differenceDays + ' ' + 'days'; } describe('Pipes', () => { it(`app.module.ts: Add the FuzzyPipe to the AppModule declarations`, () => { let metadata; try { metadata = AppModule['__annotations__'][0]; } catch (e) { // Do nothing, we have assertions below for this case } chai.expect(metadata.declarations || [], `Fuzzy pipe not found`).contains(FuzzyPipe); }); it(`video/video.component.html: Use the pipe on the date.`, () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); fixture.nativeElement.querySelector('button').click(); fixture.detectChanges(); chai.expect(fixture.nativeElement.querySelector('my-video').innerHTML).contains(sampleFuzzy(sampleVideo.date)); }); }); ================================================ FILE: ng2ts/tests/materialTest.ts ================================================ import { MatCardModule } from '@angular/material/card'; import { MatToolbarModule } from '@angular/material/toolbar'; import { app_html, search_search_component_html, video_video_component_html } from '../code'; import { TestBed } from '@angular/core/testing'; import { VideoService } from '../video/video.service'; import { AppComponent } from '../app.component'; import { RouterModule } from '@angular/router'; import { VideoComponent } from '../video/video.component'; import 'initTestBed'; import { SearchComponent } from '../search/search.component'; import { Api } from '../api.service'; import { AppModule } from '../app.module'; function getCard() { const fixture = TestBed.createComponent(VideoComponent); fixture.componentInstance.video = Api.fetch('')[0]; fixture.detectChanges(); return fixture.nativeElement.querySelector('mat-card'); } describe('material', () => { beforeEach(() => { try { TestBed.resetTestingModule(); TestBed.configureTestingModule({ providers: [VideoService], declarations: [AppComponent, SearchComponent, VideoComponent], imports: [RouterModule.forRoot([{path: '', component: SearchComponent}]), MatToolbarModule, MatCardModule] }); TestBed.overrideComponent(AppComponent, { set: { template: app_html, templateUrl: undefined } }); TestBed.overrideComponent(VideoComponent, {set: {template: video_video_component_html, templateUrl: undefined}}); TestBed.overrideComponent(SearchComponent, { set: { template: search_search_component_html, templateUrl: undefined } }); try { TestBed.compileComponents(); } catch(e) { console.log(e); } } catch (e) { } }); it('AddMatModules', () => { let metadata; try { metadata = AppModule['__annotations__'][0]; } catch (e) { // Do nothing, we have assertions below for this case } chai.expect(metadata.imports).to.contain(MatCardModule); chai.expect(metadata.imports).to.contain(MatToolbarModule); }); it('AddMatToolbar', () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const toolbar = fixture.nativeElement.querySelector('mat-toolbar'); chai.expect(toolbar).is.ok; chai.expect(toolbar.innerHTML).to.contain('MewTube'); // TODO(kirjs): Make sure the title is there only once. }); it('UseMatCard', () => { chai.expect(getCard()).is.ok; }); it('AddMatCardTitle', () => { const title = getCard().querySelector('mat-card-title'); chai.expect(title).is.ok; chai.expect(title.innerText).to.contain(Api.fetch('')[0].title); }); it('AddMatCardSubtitle', () => { const subTitle = getCard().querySelector('mat-card-subtitle'); chai.expect(subTitle).is.ok; chai.expect(subTitle.innerText).to.contain(Api.fetch('')[0].description); }); it('AddMatImage', () => { const img = getCard().querySelector('img[mat-card-image]'); chai.expect(img).is.ok; }); it('MoveDataToNewComponent', () => { const content = getCard().querySelector('mat-card-content'); chai.expect(content).is.ok; chai.expect(content.innerText).contains(Api.fetch('')[0].likes); chai.expect(content.innerText).contains(Api.fetch('')[0].views); chai.expect(content.innerText).contains(Api.fetch('')[0].date); }); }); ================================================ FILE: ng2ts/tests/routerTest.ts ================================================ import { AppModule } from '../app.module'; import { RouterModule, ROUTES } from '@angular/router'; import { SearchComponent } from '../search/search.component'; import { app_html } from '../code'; function getRoutes() { let metadata; try { metadata = AppModule['__annotations__'][0]; const routerImport = metadata.imports.find(imp => imp.ngModule === RouterModule); function findRecByToken(dep) { if (Array.isArray(dep)) { for (let d = 0; d < dep.length; d++) { const rec = findRecByToken(dep[d]); if (rec) { return rec; } } return; } // Well... return dep.provide && dep.provide._desc && dep.provide._desc === 'ROUTES' && dep.useValue; } return findRecByToken(routerImport.providers); } catch (e) { // Do nothing, we have assertions below for this case } return undefined; } describe('router', () => { it('UseRouterModule', () => { chai.expect(Array.isArray(getRoutes()), 'Can\'t find any routes provided').to.be.true; }); it('AddEmptyRoute', () => { const routes = getRoutes(); chai.expect(routes.length, 'Empty array was provided, need actual routes.').to.be.above(0); const route = routes.find(r => r.path === ''); chai.expect(route, 'Can\'t find a route with empty path').not.to.be.undefined; chai.expect(route.component && route.component.toString() .includes('SearchComponent'), 'There\'s a module with empty path, but it doesn\'t display SearchComponent') .to.be.true; }); it('AddUploadRoute', () => { const routes = getRoutes(); chai.expect(routes.length, 'Empty array was provided, need actual routes.').to.be.above(0); const route = routes.find(r => r.path === 'upload'); chai.expect(route, 'Can\'t find a route with empty path').not.to.be.undefined; chai.expect(route.component && route.component.toString() .includes('UploadComponent'), 'There\'s a module with empty path, but it doesn\'t display SearchComponent') .to.be.true; }); it('AddRouterOutlet', () => { const div = document.createElement('div'); div.innerHTML = app_html; chai.expect(!!div.querySelector('router-outlet'), 'Can\'t find router outlet').to.be.true; }); it('AddSearchMenu', () => { const div = document.createElement('div'); div.innerHTML = app_html; const search = div.querySelector('[routerLink="/"]'); chai.expect(!!search, 'Can\'t find a menu item with a routerLink of "/"').to.be.true; chai.expect(search.innerHTML.toLowerCase().includes('search'), 'The menu item should contain word "search"').to.be.true; }); it('AddUploadMenu', () => { const div = document.createElement('div'); div.innerHTML = app_html; const search = div.querySelector('[routerLink="/upload"]'); chai.expect(!!search, 'Can\'t find a menu item with a routerLink of "/upload"').to.be.true; chai.expect(search.innerHTML.toLowerCase().includes('upload'), 'The menu item should contain word "upload"').to.be.true; }); }); ================================================ FILE: ng2ts/tests/templateAddActionTest.ts ================================================ import { app_html } from '../code'; import { AppComponent } from '../app.component'; import { TestBed } from '@angular/core/testing'; import 'initTestBed'; beforeEach(() => { TestBed.resetTestingModule(); TestBed.configureTestingModule({declarations: [AppComponent]}); TestBed.overrideComponent(AppComponent, { set: { template: app_html, templateUrl: undefined } }); try { TestBed.compileComponents(); } catch(e) { console.log(e); } }); describe('Blabla', () => { it(`@@addVideosProperty`, () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); chai.expect(fixture.componentInstance.videos, `No videos property on the component`).is.not.undefined; chai.expect(fixture.componentInstance.videos, `Videos property on the component is not an array.`).is.an('array'); }); it(`@@addSearchMethodOnComponent`, () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); chai.expect(fixture.componentInstance.search, `Search should be a function`).is.a('function'); chai.expect(fixture.componentInstance.search.length, `Search should take one parameter`).equals(1); }); it(`@@addClickHandlerToButtonCallSearch`, () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const input = fixture.nativeElement.querySelector('input'); const button = fixture.nativeElement.querySelector('button'); function testSearch(searchString) { let passedValue = undefined; let called = false; fixture.componentInstance.search = function (value) { called = true; passedValue = value; }; input.value = searchString; button.click(); chai.expect(called, `Search function should be called when the search button is pressed`).equals(true); chai.expect(passedValue, `Input value is not passed to the search function`).equals(input.value); } testSearch('Awesome kittens'); testSearch('Other value'); }); it(`@@addMessageNoVideos`, () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); fixture.componentInstance.videos = []; chai.expect(fixture.nativeElement.innerHTML.toLowerCase()).contains('no videos'); fixture.componentInstance.videos = [{title: 'Hi', src: 'Test'}]; fixture.detectChanges(); chai.expect(fixture.nativeElement.innerHTML.toLowerCase()).not.contains('no videos'); }); }); ================================================ FILE: ng2ts/tests/templateAllVideosTest.ts ================================================ import { app_html } from '../code'; import { AppComponent } from '../app.component'; import { TestBed } from '@angular/core/testing'; import 'initTestBed'; beforeEach(() => { TestBed.resetTestingModule(); TestBed.configureTestingModule({ declarations: [AppComponent] }); TestBed.overrideComponent(AppComponent, { set: { template: app_html, templateUrl: undefined } }); try { TestBed.compileComponents(); } catch(e) { console.log(e); } }); describe('Blabla', () => { it(`@@assignFakeVideosToComponent`, () => { const fixture = TestBed.createComponent(AppComponent); fixture.componentInstance.search(''); chai.expect(fixture.componentInstance.videos.length, 'Should have no dogs').equals(3); }); it(`@@IterateWithNgForAndDisplayTitle`, () => { const fixture = TestBed.createComponent(AppComponent); fixture.componentInstance.search('itten'); fixture.detectChanges(); chai.expect(fixture.nativeElement.innerHTML).contains(fixture.componentInstance.videos[0].title); chai.expect(fixture.nativeElement.innerHTML).contains(fixture.componentInstance.videos[1].title); fixture.componentInstance.search('cat'); fixture.detectChanges(); chai.expect(fixture.nativeElement.innerHTML).contains(fixture.componentInstance.videos[0].title); }); it(`@@alsoDisplayThumbnail`, () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); fixture.componentInstance.search(''); fixture.detectChanges(); const images = fixture.nativeElement.querySelectorAll('img'); chai.expect(images.length).equals(3); chai.expect(images[1].getAttribute('src')).equals(fixture.componentInstance.videos[1].src); chai.expect(images[0].getAttribute('src')).equals(fixture.componentInstance.videos[0].src); }); it(`@@insideSearchMethodFilterFakeVideos`, () => { const fixture = TestBed.createComponent(AppComponent); fixture.componentInstance.search('itten'); chai.expect(fixture.componentInstance.videos.length, 'Should have 2 kitten videos').equals(2); fixture.componentInstance.search('cat'); chai.expect(fixture.componentInstance.videos.length, 'Should have 1 cat').equals(1); fixture.componentInstance.search('dog'); chai.expect(fixture.componentInstance.videos.length, 'Should have no dogs').equals(0); }); // it(`#Bonus app.html: Make hitting enter work in the input trigger the search`, () => { // //TODO // }); it(`@@bonusDisplayAllVideosByDefault`, () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const images = fixture.nativeElement.querySelectorAll('img'); chai.expect(images.length).equals(3); }); }); ================================================ FILE: ng2ts/tests/templatePageSetupTest.ts ================================================ import { TestBed } from '@angular/core/testing'; import { AppComponent } from '../app.component'; import 'initTestBed'; import { app_html } from '../code'; beforeEach(() => { TestBed.resetTestingModule(); TestBed.configureTestingModule({declarations: [AppComponent]}); TestBed.overrideComponent(AppComponent, { set: { template: app_html, templateUrl: undefined } }); }); describe('Blabla', () => { it(`@@addH1HeaderWithATitle`, () => { try { TestBed.compileComponents(); } catch(e) { console.log(e); } const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const header = fixture.nativeElement.querySelector('h1'); chai.expect(header, `Can't find any h1 headers`).is.not.null; chai.expect(header.innerHTML).contains('MewTube'); fixture.componentInstance.title = 'SomethingElse'; fixture.detectChanges(); const header2 = fixture.nativeElement.querySelector('h1'); chai.expect(header2.innerHTML, `Use the curly braces to put component title property in the header`).contains('SomethingElse'); }); it(`@@addInputWithPlaceholderVideo`, () => { try { TestBed.compileComponents(); } catch(e) { console.log(e); } const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const input = fixture.nativeElement.querySelector('input'); chai.expect(input, `Can't find any inputs`).is.not.null; chai.expect(input.placeholder, `Input placeholder should contain word 'video'`).contains('video'); }); it(`@@addButtonWithtextSearch`, () => { try { TestBed.compileComponents(); } catch(e) { console.log(e); } const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const button = fixture.nativeElement.querySelector('button'); chai.expect(button, `Can't find any buttons`).is.not.null; chai.expect(button.innerHTML.toLowerCase()).contains('search'); }); }); ================================================ FILE: ng2ts/tests/test.ts ================================================ ================================================ FILE: ng2ts/tests/togglePanelComponentCreateTest.ts ================================================ import { By } from '@angular/platform-browser'; import { TestBed } from '@angular/core/testing'; import { toggle_panel_toggle_panel_html } from '../code'; import { TogglePanelComponent } from '../toggle-panel/toggle-panel.component'; import { WrapperComponent } from '../wrapper.component'; import 'initTestBed'; beforeEach(() => { TestBed.resetTestingModule(); TestBed.configureTestingModule({ providers: [], declarations: [TogglePanelComponent, WrapperComponent] }); TestBed.overrideComponent(TogglePanelComponent, { set: { template: toggle_panel_toggle_panel_html } }); try { TestBed.compileComponents(); } catch(e) { console.log(e); } }); describe('Content projection', () => { it(`TogglePanel.Component.ts: We added the template and the selector for you, enjoy!`, () => { }); it(`TogglePanel.Component.ts: Add a boolean property to the component. The property can have any name, and must have a default value.`, () => { let fixture = TestBed.createComponent(TogglePanelComponent); // the intent is to let them come up with the property name, so we assume there will be one. const props = Object.keys(fixture.componentInstance); chai.expect(props.length, `A property with a default value was not declared on the component.`).is.not.equal(0); chai.expect(props.length, `Too many properties were declared.`).is.not.greaterThan(1); const prop = props[0]; chai.expect(fixture.componentInstance[prop], `Property '${prop}' is not of type boolean`).is.a('boolean'); chai.expect(fixture.componentInstance[prop], `Property '${prop}' must have a default value`).is.not.undefined; }); it(`togglePanel.html: Use content projection to only display the content with the selector .description by default.`, () => { let fixture = TestBed.createComponent(WrapperComponent); fixture.detectChanges(); chai.expect(fixture.debugElement.query(By.css('.description')), `Description should be displayed`).not.null; chai.expect(fixture.debugElement.query(By.css('.extra')), `Extra information should be hidden`).is.null; }); it(`togglePanel.html: Add a button to show extra information`, () => { let fixture = TestBed.createComponent(WrapperComponent); fixture.detectChanges(); let buttons = fixture.nativeElement.querySelectorAll('button'); chai.expect(buttons.length, `Should show exactly one button`).to.equals(1); }); it(`togglePanel.html: When the button is pressed, switch the flag and only display the content with the '.extra' selector.`, () => { let fixture = TestBed.createComponent(WrapperComponent); fixture.detectChanges(); let button = fixture.nativeElement.querySelector('button'); button.click(); fixture.detectChanges(); chai.expect(fixture.debugElement.query(By.css('.description')), `Description should be hidden`).is.null; chai.expect(fixture.debugElement.query(By.css('.extra')), `Extra information should be displayed`).not.null; }); it(`togglePanel.html: Add a button to come back to the description`, () => { let fixture = TestBed.createComponent(WrapperComponent); fixture.detectChanges(); fixture.nativeElement.querySelector('button').click(); fixture.detectChanges(); fixture.nativeElement.querySelector('button').click(); fixture.detectChanges(); chai.expect(fixture.debugElement.query(By.css('.description')), `Description should be displayed`).not.null; chai.expect(fixture.debugElement.query(By.css('.extra')), `Extra information should be hidden`).is.null; }); }); ================================================ FILE: ng2ts/tests/togglePanelComponentUseTest.ts ================================================ import { Api } from '../api.service'; import { app_html, thumbs_thumbs_html, toggle_panel_toggle_panel_html, video_video_component_html } from '../code'; import { AppComponent } from '../app.component'; import { AppModule } from '../app.module'; import { TestBed } from '@angular/core/testing'; import { ThumbsComponent } from '../thumbs/thumbs.component'; import { TogglePanelComponent } from '../toggle-panel/toggle-panel.component'; import { VideoComponent } from '../video/video.component'; import { VideoService } from '../video/video.service'; import 'initTestBed'; const video = Api.fetch('')[0]; beforeEach(() => { TestBed.resetTestingModule(); TestBed.configureTestingModule({ providers: [VideoService], declarations: [AppComponent, VideoComponent, TogglePanelComponent, ThumbsComponent] }); TestBed.overrideComponent(AppComponent, { set: { template: app_html } }); TestBed.overrideComponent(ThumbsComponent, { set: { template: thumbs_thumbs_html } }); TestBed.overrideComponent(VideoComponent, { set: { template: video_video_component_html } }); TestBed.overrideComponent(TogglePanelComponent, { set: { template: toggle_panel_toggle_panel_html } }); try { TestBed.compileComponents(); } catch(e) { console.log(e); } }); describe('Component Tree', () => { it(`app.module.ts: Add the TogglePanelComponent to the declarations.`, () => { let metadata; try { metadata = AppModule['__annotations__'][0]; } catch (e) { // Do nothing, we have assertions below for this case } chai.expect(metadata.declarations || [], `Keep the video component`).contains(VideoComponent); chai.expect(metadata.declarations || [], `Keep the app component`).contains(AppComponent); chai.expect(metadata.declarations || [], `Add TogglePanelComponent`).contains(TogglePanelComponent); }); it(`video/video.component.html: Use the TogglePanel component in the template`, () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const panel = fixture.nativeElement.querySelector('my-toggle-panel'); chai.expect(panel).is.not.null; }); it(`video/video.component.html: Add .description as TogglePanel's content`, () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const panel = fixture.nativeElement.querySelector('my-toggle-panel'); chai.expect(panel.querySelector('.description')).is.not.null; chai.expect(panel.querySelector('.extra')).is.null; chai.expect(fixture.nativeElement.querySelector('my-video').innerHTML, `Should display description text.`).contains(video.description); chai.expect(fixture.nativeElement.querySelector('my-video').innerHTML, `Should not display likes `).not.contains(video.likes); }); it(`video/video.component.html: Add .extra as TogglePanel's content`, () => { const fixture = TestBed.createComponent(AppComponent); fixture.detectChanges(); const panel = fixture.nativeElement.querySelector('my-toggle-panel'); panel.querySelector('button').click(); fixture.detectChanges(); chai.expect(panel.querySelector('.description')).is.null.null; chai.expect(panel.querySelector('.extra')).is.not.null; chai.expect(fixture.nativeElement.querySelector('my-video').innerHTML, `Should not description text.`).not.contains(video.description); chai.expect(fixture.nativeElement.querySelector('my-video').innerHTML, `Should display likes`).contains(video.likes); }); }); ================================================ FILE: ng2ts/tests/videoComponentCreateTest.ts ================================================ import { Api } from '../api.service'; import { TestBed } from '@angular/core/testing'; import { video_video_component_html } from '../code'; import { VideoComponent } from '../video/video.component'; import { VideoService } from '../video/video.service'; import 'initTestBed'; const video = Api.fetch('')[0]; beforeEach(() => { try { TestBed.resetTestingModule(); TestBed.configureTestingModule({ providers: [VideoService], declarations: [VideoComponent] }); TestBed.overrideComponent(VideoComponent, { set: { template: video_video_component_html, templateUrl: undefined } }); try { TestBed.compileComponents(); } catch(e) { console.log(e); } } catch (e) { // whatever } }); describe('Component Tree', () => { describe('Make sure metadata is in place', () => { it(`@@addComponentDecoratorAndSetSelectorToMyVideo`, () => { const metadata = VideoComponent['__annotations__'][0]; chai.expect(metadata, `VideoComponent doesn't have a @Component() annotation`).is.not.undefined; chai.expect(metadata.selector, `VideoComponent's selector has to be 'my-video'.`).equals('my-video'); }); it(`@@setTemplateUrlToLoadAppropriateFile`, () => { const metadata = VideoComponent['__annotations__'][0]; chai.expect(metadata, `VideoComponent doesn't have a @Component() annotation`).is.not.undefined; chai.expect(metadata.templateUrl, `VideoComponent's templateUrl should be set to './video.component.html'`) .matches(/\.\/video\.component\.html/); }); it(`@@addVideoPropertyAndDecorateWithInput`, () => { const metadata = VideoComponent['__prop__metadata__']; chai.expect(metadata, `VideoComponent doesn't have any @Input()'s`).is.not.undefined; chai.expect(Object.keys(metadata).length, `VideoComponent doesn't have any @Input()'s`).equals(1); chai.expect(metadata.video, `VideoComponent's @Input()' should be called video.`).is.not.undefined; }); }); describe('Make sure things are displayed properly', () => { let fixture; beforeEach(() => { try { fixture = TestBed.createComponent(VideoComponent); fixture.componentInstance.video = video; fixture.detectChanges(); } catch (e) { } }); it(`displayVideoTitle`, () => { chai.expect(fixture.nativeElement.innerHTML, `can't find the video title`).contains(video.title); }); it(`@@displayVideoThumbnail`, () => { const image = fixture.nativeElement.querySelector('img'); chai.expect(image, `Can't find the thumbnail`).is.not.null; chai.expect(image.getAttribute('src')).equals(video.src); }); it(`@@displayVideoDescription`, () => { chai.expect(fixture.nativeElement.innerHTML, `can't find the video description`).contains(video.description); }); it(`@@displayVideoData`, () => { chai.expect(fixture.nativeElement.innerHTML, `can't find the video date`).contains(video.date); }); it(`@@displayNumberOfVideoLikes`, () => { chai.expect(fixture.nativeElement.innerHTML, `can't find the video like`).contains(video.likes); }); it(`@@displayNumberOfVideoViews`, () => { chai.expect(fixture.nativeElement.innerHTML, `can't find the video description`).contains(video.views); }); }); }); ================================================ FILE: ng2ts/tests/videoComponentUseTest.ts ================================================ import { Api } from '../api.service'; import { app_html, video_video_component_html } from '../code'; import { AppComponent } from '../app.component'; import { AppModule } from '../app.module'; import { TestBed } from '@angular/core/testing'; import { VideoComponent } from '../video/video.component'; import { VideoService } from '../video/video.service'; import 'initTestBed'; import { Component, Input } from '@angular/core'; function prepareTestingModule(videoComponent: any = VideoComponent) { TestBed.resetTestingModule(); TestBed.configureTestingModule({ providers: [VideoService], declarations: [AppComponent, videoComponent] }); TestBed.overrideComponent(AppComponent, { set: { template: app_html, templateUrl: undefined } }); TestBed.overrideComponent(videoComponent, { set: { template: video_video_component_html, templateUrl: undefined } }); try { TestBed.compileComponents(); } catch(e) { console.log(e); } } describe('Component Tree', () => { it(`@@addVideoComponentToAppModule`, () => { prepareTestingModule(); let metadata; try { metadata = AppModule['__annotations__'][0]; } catch (e) { // Do nothing, we have assertions below for this case } chai.expect(metadata.declarations || [], `Video component not found`).contains(VideoComponent); chai.expect(metadata.declarations || [], `Keep the app component`).contains(AppComponent); }); it(`@@replaceTitleAndThumbnail`, () => { @Component({ selector: '' + 'my-video', template: 'v' }) class MockVideoComponent { @Input() video = { title: 'Kittens coming soon!!!' }; } prepareTestingModule(MockVideoComponent); const fixture = TestBed.createComponent(AppComponent); fixture.componentInstance.videos = Api.fetch(''); // TODO: if the element is added, but the video prop is not present, this test will fail with // a useless message. Passing video prop should actually be tested in the next test, and this // one should pass. fixture.detectChanges(); const myVideos = fixture.nativeElement.querySelectorAll('my-video'); chai.expect(myVideos.length, `can't find any elements in the app component`).is.greaterThan(0); chai.expect(myVideos.length, `There should be one element for each video`).equals(fixture.componentInstance.videos.length); }); it(`@@useDataBindingToPassVideoToComponent`, () => { prepareTestingModule(); const fixture = TestBed.createComponent(AppComponent); fixture.componentInstance.videos = Api.fetch(''); fixture.detectChanges(); const video = fixture.nativeElement.querySelector('my-video'); chai.expect(video.getAttribute('ng-reflect-video')).equals('[object Object]'); }); }); ================================================ FILE: ng2ts/thumbs/thumbs.component.ts ================================================ import {Component, Output, EventEmitter} from '@angular/core'; /** * Yes, TypeScript has enums! * There's no nice way to use them in the template though. */ export enum Thumbs { UP, DOWN } /*d:thumbsComponentCreateSolved/trimTrailing*/ @Component({ selector: 'my-thumbs', templateUrl: 'thumbs.html' }) /*/d*/ export class ThumbsComponent { /*d:thumbsComponentCreateSolved/trimBoth*/ @Output() onThumbs: EventEmitter = new EventEmitter(); thumbsUp() { this.onThumbs.emit(Thumbs.UP) } thumbsDown() { this.onThumbs.emit(Thumbs.DOWN) } /*/d*/ } ================================================ FILE: ng2ts/thumbs/thumbs.html ================================================ /*d:initial:thumbsComponentCreate/trimBoth*/ /*/d*//*d:thumbsComponentCreateSolved/trimBoth*/ /*/d*/ ================================================ FILE: ng2ts/thumbs.app.module.ts ================================================ import {BrowserModule} from '@angular/platform-browser'; import {NgModule} from '@angular/core'; import {ThumbsComponent} from './thumbs/thumbs.component'; @NgModule({ imports: [BrowserModule], declarations: [ThumbsComponent], bootstrap: [ThumbsComponent] }) export class AppModule { } ================================================ FILE: ng2ts/toggle-panel/toggle-panel.component.ts ================================================ import {Component} from '@angular/core'; @Component({ selector: 'my-toggle-panel', templateUrl: 'toggle-panel.html' }) export class TogglePanelComponent { /*d:togglePanelComponentCreateSolved*/ showDescription = true;/*/d*/ } ================================================ FILE: ng2ts/toggle-panel/toggle-panel.html ================================================ /*d:togglePanelComponentCreate:togglePanelComponentCreate/trimBoth*/ /*/d*//*d:togglePanelComponentCreateSolved/trimBoth*/
        /*/d*/ ================================================ FILE: ng2ts/toggle-panel.app.module.ts ================================================ import {BrowserModule} from '@angular/platform-browser'; import {NgModule} from '@angular/core'; import {TogglePanelComponent} from './toggle-panel/toggle-panel.component'; import {WrapperComponent} from './wrapper.component'; @NgModule({ imports: [BrowserModule], declarations: [WrapperComponent, TogglePanelComponent], bootstrap: [WrapperComponent] }) export class AppModule { } ================================================ FILE: ng2ts/typescript-intro/Codelab.ts ================================================ import {Guest} from './Guest'; /*d:initial:initial*/// Add your code here /*/d*//*d:codelabSolved*/ export class Codelab { constructor(public guests: Guest[]) { } getGuestsComing() { return this.guests.filter(guest => guest.coming); } } /*/d*//*d:neverShow*/ // Needed for type checking export function evalJs(param){ return param;} /*/d*/ ================================================ FILE: ng2ts/typescript-intro/Guest.ts ================================================ export interface Guest { coming: boolean, name: string } ================================================ FILE: ng2ts/typescript-intro/Main.ts ================================================ import { Codelab } from './Codelab'; import { Guest } from './Guest'; // Use this file for reference. const guests: Guest[] = [ { coming: true, name: `Sir Isaac Newton` }, { coming: true, name: `Marie Curie` }, { coming: true, name: `Albert Einstein` }, { coming: false, name: `Charles Darwin` }]; let guestsOutput; try { const codelab = new Codelab(guests); guestsOutput = codelab.getGuestsComing && codelab.getGuestsComing().map((guest: Guest) => `
      • ${guest.name}
      • `).join(''); } catch (e) { /* swallow */ } if (guestsOutput) { // Angular is so much better than this: document.body.innerHTML = '
          ' + guestsOutput + '
        '; } else { document.body.innerHTML = 'make all tests pass to see the preview'; } ================================================ FILE: ng2ts/upload/upload.component.html ================================================ /*d:router:forms/trimLeading*/

        Uploading coming soon!

        /*/d*//*d:formsSolved/trimBoth*/
        Title is required
        /*/d*/ ================================================ FILE: ng2ts/upload/upload.component.ts ================================================ /*d:router/trimleading*/ import { Component } from '@angular/core'; @Component({ selector: 'slides-upload-component', templateUrl: './upload.component.html' }) export class UploadComponent { /*/d*//*d:forms/trimboth*/ title: string; description: string; /*/d*//*d:router/trimleading*/ } ================================================ FILE: ng2ts/video/video-item.ts ================================================ export interface VideoItem { title: string; src: string; // "?" after property name means the property is optional. description?: string; views?: number; likes?: number; date?: string; } ================================================ FILE: ng2ts/video/video-materialized.component.html ================================================ /*d:material:material/trimLeading*/

        {{video.title}}

        Date {{video.date}}
        Views {{video.views}}
        Likes {{video.likes}}
        Description {{video.description}}
        /*/d*//*d:materialSolved/trimLeading*/ {{video.title}} {{video.description}}
        Date {{video.date}}
        Views {{video.views}}
        Likes {{video.likes}}
        /*/d*/ ================================================ FILE: ng2ts/video/video-wrapper.component.ts ================================================ import {Component, Input} from '@angular/core'; @Component({ selector: 'video-wrapper', template: `` }) export class VideoWrapperComponent { private video = { date: new Date().toDateString(), title: 'Cute Cat', src: '/assets/images/cat-00.png', description: 'here is the descr', views: 10, likes: 9 }; } ================================================ FILE: ng2ts/video/video.component.html ================================================ /*d:initial:initial*/ /*/d*//*d:videoComponentCreateSolved/trimLeading*/

        {{video.title}}

        /*/d*//*d:videoComponentCreateSolved:fuzzyPipeUseSolved/trimLeading*/
        Date {{video.date}}
        /*/d*//*d:fuzzyPipeUseSolved*/
        Date {{video.date | fuzzy}}
        /*/d*//*d:videoComponentCreateSolved:togglePanelComponentUse/trimLeading*/
        Views {{video.views}}
        Likes {{video.likes}}
        Description {{video.description}}
        /*/d*//*d:thumbsComponentUseSolved/trimBoth*/ /*/d*//*d:togglePanelComponentUseSolved*/
        Description: {{video?.description}}
        Views: {{video?.views}}
        /*/d*/ ================================================ FILE: ng2ts/video/video.component.ts ================================================ import { Component, Input } from '@angular/core'; import { VideoItem } from './video-item'; /*d:thumbsComponentUse*/ import { Thumbs } from '../thumbs/thumbs.component'; /*/d*//*d:videoComponentCreateSolved/trimTrailing*/ @Component({ selector: 'my-video', templateUrl: './video.component.html' }) /*/d*/ export class VideoComponent { /*d:videoComponentCreateSolved/trimBoth*/ @Input() video: VideoItem; /*/d*//*d:thumbsComponentUseSolved/trimTrailing*/ onThumbs(thumbs: Thumbs) { if (thumbs === Thumbs.UP) { this.video.likes++; } if (thumbs === Thumbs.DOWN) { this.video.likes--; } } /*/d*/ } ================================================ FILE: ng2ts/video/video.index.html ================================================ ================================================ FILE: ng2ts/video/video.service.ts ================================================ import { Injectable } from '@angular/core'; import { Api } from '../api.service'; /*d:diInjectServiceSolved/trimTrailing*/ @Injectable() /*/d*//*d:initial*/ export class VideoService { search(searchString: string) { return Api.fetch(searchString); } } /*/d*/ ================================================ FILE: ng2ts/video.app.module.ts ================================================ import {BrowserModule} from '@angular/platform-browser'; import {NgModule} from '@angular/core'; import {VideoWrapperComponent} from './video/video-wrapper.component'; import {VideoComponent} from './video/video.component'; @NgModule({ imports: [BrowserModule], declarations: [VideoWrapperComponent, VideoComponent], bootstrap: [VideoWrapperComponent] }) export class AppModule { } ================================================ FILE: ng2ts/wrapper.component.ts ================================================ import {Component} from '@angular/core'; @Component({ selector: 'my-wrapper', // Just using template here to avoid extra files. // Please don't do this at home. template: `
        Either show me
        Or show me
        ` }) export class WrapperComponent { } ================================================ FILE: nx.json ================================================ { "npmScope": "codelab", "implicitDependencies": { "angular.json": "*", "package.json": "*", "tsconfig.json": "*", "tslint.json": "*", "nx.json": "*" }, "projects": { "codelab": { "tags": [] }, "browser": { "tags": [] }, "console": { "tags": [] }, "utils": { "tags": [] }, "kirjs": { "tags": [] }, "angular-ast-viz": { "tags": [] }, "angular-slides-to-pdf": { "tags": [] }, "feedback": { "tags": [] }, "slides": { "tags": [] }, "code-demos": { "tags": [] }, "live": { "tags": [] }, "firebase-login": { "tags": [] }, "angular-thirty-seconds": { "tags": [] }, "blog": { "tags": [] }, "lis": { "tags": [] }, "playground": { "tags": [] }, "firebase": { "tags": [] }, "intro": { "tags": [] } } } ================================================ FILE: package.json ================================================ { "name": "codelab", "version": "0.0.1", "license": "MIT", "scripts": { "ng": "node --max_old_space_size=16000 ./node_modules/@angular/cli/bin/ng", "//--------------- CODELAB ---------------------": "", "start": "ng serve", "build": "ng build", "build:prod": "ng build --prod", "build:bundler": "ts-node -P libs/code-demos/assets/runner/tsconfig.json libs/code-demos/assets/runner/ng-dts/bundler.ts && node libs/code-demos/assets/runner/ng2/ng2-runner.js", "build:intro": "ts-node -P libs/intro/generate/tsconfig.json libs/intro/generate/generate-thumbnails.ts", "build:intro:verbose": "NODE_DEBUG=cluster,net,http,fs,tls,module,timers ts-node -P libs/intro/generate/tsconfig.json libs/intro/generate/generate-thumbnails.ts", "test": "ng test", "lint": "ng lint", "i18n": "ng xi18n codelab --i18n-format xmb --output-path src/locale", "build:ru": "ng build codelab --configuration ru", "deploy:next": "npm run build:prod && npm run build:ru && npm run build:30s && firebase deploy --only hosting:codelab-next", "serve:ru": "npm run ng -- serve --configuration ru", "pre-deploy": "npm run build:prod && npm run build:ru && npm run build:30s", "deploy": "npm run pre-deploy && firebase deploy --only hosting:codelab", "cypress": "cypress", "cypress:approve": "npm run cypress run -- --env updateSnapshots=true", "cypress:open": "cypress open", "cypress:reporter": "npm run cypress run --reporter cypress-image-snapshot/reporter", "//--------------- KIRJS ---------------------": "", "deploy:kirjs": "ng build kirjs --prod && firebase deploy --only hosting:kirjs", "deploy:kirjs:ru": "ng build kirjs --configuration ru --prod && firebase deploy --only hosting:kirjs", "serve:kirjs:ru": "ng serve kirjs --configuration ru", "build:kirjs:ru": "ng build kirjs --configuration ru", "build:kirjs": "ng build kirjs --prod", "xi18n:kirjs": "ng xi18n kirjs --i18n-format xmb --output-path src/locale", "//--------------- Playground ---------------------": "", "deploy:playground": "ng build playground --prod && firebase deploy --only hosting:angular-ivy", "serve:playground": "ng serve playground", "build:playground": "ng build playground --prod", "//--------------- LIS ---------------------": "", "deploy:lis": "ng build lis --prod && firebase deploy --only hosting:lis", "serve:lis": "ng serve lis", "build:lis": "ng build lis --prod", "//--------------- 30s OF ANGULAR -------------------": "", "build:30s": "npm run ng build angular-thirty-seconds --prod", "serve:30s": "ng serve angular-thirty-seconds", "//--------------- NX ---------------------": "", "affected:apps": "./node_modules/.bin/nx affected:apps", "affected:libs": "./node_modules/.bin/nx affected:libs", "affected:build": "./node_modules/.bin/nx affected:build", "affected:test": "./node_modules/.bin/nx affected:test --watch=false", "affected:lint": "./node_modules/.bin/nx affected:lint", "affected:dep-graph": "./node_modules/.bin/nx affected:dep-graph", "format": "./node_modules/.bin/nx format:write", "format:write": "./node_modules/.bin/nx format:write", "format:check": "./node_modules/.bin/nx format:check", "update": "ng update @nrwl/workspace", "update:check": "ng update", "dep-graph": "./node_modules/.bin/nx dep-graph", "workspace-schematic": "./node_modules/.bin/nx workspace-schematic", "help": "./node_modules/.bin/nx help", "postinstall": "ngcc --properties es2015 browser module main --first-only --create-ivy-entry-points" }, "husky": { "hooks": { "pre-commit": "npm run format:write && git add ." } }, "repository": { "type": "git", "url": "git@github.com:codelab-fun/codelab.git" }, "public": true, "dependencies": { "@angular-builders/custom-webpack": "^9.0.0", "@angular-devkit/schematics": "9.0.4", "@angular/animations": "9.0.4", "@angular/cdk": "9.0.1", "@angular/common": "9.0.4", "@angular/compiler": "9.0.4", "@angular/core": "9.0.4", "@angular/fire": "5.4.2", "@angular/forms": "9.0.4", "@angular/localize": "9.0.4", "@angular/material": "9.0.1", "@angular/platform-browser": "9.0.4", "@angular/platform-browser-dynamic": "9.0.4", "@angular/router": "9.0.4", "@babel/core": "^7.8.4", "@babel/generator": "^7.8.4", "@babel/traverse": "^7.8.4", "binary-loader": "0.0.1", "binary-parser": "^1.3.2", "chai": "^4.2.0", "console": "^0.7.2", "core-js": "2.6.2", "dom-to-image": "^2.6.0", "firebase": "^7.8.2", "flamelink": "^1.0.0-alpha.27", "fullscreen-api-polyfill": "^1.1.2", "glob": "^7.1.6", "gray-matter": "^4.0.2", "husky": "^4.2.3", "immer": "^5.3.6", "mocha": "^7.0.1", "monaco-editor": "^0.20.0", "monaco-editor-webpack-plugin": "^1.8.2", "ngx-markdown": "^8.2.2", "raw-loader": "1.0.0", "rxjs": "^6.5.4", "slugify": "^1.3.6", "wabt": "^1.0.11", "zone.js": "^0.10.2" }, "devDependencies": { "@angular-devkit/build-angular": "^0.900.4", "@angular-devkit/build-ng-packagr": "0.900.4", "@angular/cli": "9.0.4", "@angular/compiler-cli": "9.0.4", "@angular/language-service": "9.0.4", "@bahmutov/add-typescript-to-cypress": "^2.0.0", "@nrwl/cypress": "9.3.0", "@nrwl/jest": "9.3.0", "@nrwl/workspace": "9.3.0", "@nrwl/angular": "9.3.0", "@types/fs-extra": "^9.0.1", "@types/jasmine": "3.5.3", "@types/jest": "25.1.2", "@types/node": "^13.7.1", "@types/node-fetch": "^2.5.7", "acorn": "^7.1.0", "babel-types": "^6.25.0", "babylon": "^6.17.3", "codelyzer": "^5.2.1", "cypress": "~4.0.1", "cypress-image-snapshot": "^3.0.0", "dotenv": "8.2.0", "eslint": "^6.8.0", "firebase-tools": "^7.13.0", "fs-extra": "^9.0.0", "github-api": "^3.0.0", "gomoku-tools": "^0.1.0", "googleapis": "^51.0.0", "jasmine-core": "~3.5.0", "jest": "25.1.0", "jest-preset-angular": "8.0.0", "karma": "^4.4.1", "karma-chrome-launcher": "~3.1.0", "karma-cli": "~2.0.0", "karma-coverage-istanbul-reporter": "^2.1.1", "karma-jasmine": "~3.1.1", "karma-jasmine-html-reporter": "^1.5.2", "ng-packagr": "^9.0.0", "node-fetch": "^2.6.0", "prettier": "1.19.1", "protractor": "^5.4.2", "systemjs": "0.20.12", "systemjs-builder": "^0.16.4", "ts-jest": "25.2.0", "ts-node": "^8.6.2", "tsickle": "^0.38.0", "tslib": "^1.10.0", "tslint": "^6.0.0", "typescript": "^3.7.5", "uglify-js": "^3.7.7" } } ================================================ FILE: tools/schematics/slide/README.md ================================================ # Generating new presentations You can generate new presentation using `npm run workspace-schematic slide modules/schematic-name -- --project=codelab` If you want to debug the schematic, you can run it this way: `node --inspect-brk ./node_modules/.bin/nx workspace-schematic slide modules/schematic-name --project codelab` npm run workspace-schematic slide modules/msk -- --project=kirjs ================================================ FILE: tools/schematics/slide/files/code.bs ================================================ import { SlidesRoutes } from '@ng360/slides'; import { RouterModule } from '@angular/router'; const routes = RouterModule.forChild( SlidesRoutes.get(EmptyComponent) ); ================================================ FILE: tools/schematics/slide/files/template.component.html ================================================

        Hello

        • a
        • b
        ================================================ FILE: tools/schematics/slide/index.ts ================================================ import { chain, externalSchematic, Rule, SchematicContext, Tree } from '@angular-devkit/schematics'; import { addImportToModule } from '@schematics/angular/utility/ast-utils'; import * as ts from '@schematics/angular/third_party/github.com/Microsoft/TypeScript/lib/typescript'; import * as fs from 'fs'; import { join } from 'path'; import { InsertChange } from '@schematics/angular/utility/change'; import { classify } from '@angular-devkit/core/src/utils/strings'; function overrideHtml(): Rule { return (host: Tree, context: SchematicContext) => { const path: string = host.actions.find(a => a.path.endsWith('.component.html') ).path; const html = fs .readFileSync(join(__dirname, './files/template.component.html'), 'utf-8') .toString(); host.overwrite(path, html); }; } interface SlideSchema { name: string; project: string; } function updateSlidesModule(schema: SlideSchema): Rule { return (host: Tree, context: SchematicContext) => { const modulePath: string = host.actions.find(a => a.path.endsWith('.module.ts') ).path; const sourceFile = ts.createSourceFile( modulePath, host.read(modulePath).toString('utf-8'), ts.ScriptTarget.Latest, true ); let code = fs .readFileSync(join(__dirname, './files/code.bs'), 'utf-8') .toString(); const componentName = classify(schema.name.split('/').pop()) + 'Component'; code = code.replace('EmptyComponent', componentName); const moduleImportChanges = addImportToModule( sourceFile, modulePath, 'SlidesModule', '@ng360/slides' ); const routingImportChanges = addImportToModule( sourceFile, modulePath, 'routes', null ); const changes = [...moduleImportChanges, ...routingImportChanges]; const recorder = host.beginUpdate(modulePath); changes.forEach((change: InsertChange) => { recorder.insertLeft(change.pos, change.toAdd); }); const classDeclaration = [...sourceFile.statements].find(s => ts.isClassDeclaration(s) ); recorder.insertLeft(classDeclaration.pos, code); [...sourceFile.statements].find(s => ts.isIdentifier(s)); host.commitUpdate(recorder); }; } export default function(schema: SlideSchema): Rule { return chain([ externalSchematic('@schematics/angular', 'module', { name: schema.name, project: schema.project }), updateSlidesModule(schema), externalSchematic('@schematics/angular', 'component', { name: schema.name, project: schema.project }), overrideHtml() ]); } ================================================ FILE: tools/schematics/slide/schema.json ================================================ { "$schema": "http://json-schema.org/schema", "id": "slide", "type": "object", "properties": { "name": { "type": "string", "description": "Presentation name", "$default": { "$source": "argv", "index": 0 } }, "project": { "type": "string", "description": "Presentation name", "$default": { "$source": "project" } } }, "required": ["name"] } ================================================ FILE: tools/tsconfig.tools.json ================================================ { "compilerOptions": { "outDir": "../dist/out-tsc", "rootDir": ".", "module": "commonjs", "target": "es6", "moduleResolution": "node", "types": ["jasmine", "node"] }, "include": ["**/*.ts"], "lib": ["es2018", "dom"], "module": "es2015" } ================================================ FILE: tsconfig.json ================================================ { "compileOnSave": false, "angularCompilerOptions": { "enableIvy": false }, "compilerOptions": { "outDir": "./dist/out-tsc", "baseUrl": ".", "sourceMap": true, "declaration": false, "module": "esnext", "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, "resolveJsonModule": true, "target": "es2015", "paths": { "jszip": ["node_modules/jszip/dist/jszip.min.js"], "@codelab/*": ["libs/*"], "@ng360/slides": ["libs/slides/src/index.ts"], "@codelab/browser": ["libs/browser/src/index.ts"], "@codelab/console": ["libs/console/src/index.ts"], "@codelab/utils": ["libs/utils/src/index.ts"], "@codelab/angular-ast-viz": ["libs/angular-ast-viz/src/index.ts"], "@codelab/angular-slides-to-pdf": [ "libs/angular-slides-to-pdf/src/index.ts" ], "vm": ["node_modules/vm-shim"], "@codelab/feedback": ["libs/feedback/src/index.ts"], "@codelab/code-demos": ["libs/code-demos/src/index.ts"], "@angular-presentation/live": ["libs/live/src/index.ts"], "@angular-presentation/firebase-login": [ "libs/firebase-login/src/index.ts" ], "@codelab/firebase": ["libs/firebase/src/index.ts"], "@codelab/intro": ["libs/intro/src/index.ts"] }, "typeRoots": ["node_modules/@types"], "lib": ["es2018", "esnext", "dom"], "rootDir": "." } } ================================================ FILE: tslint.json ================================================ { "rulesDirectory": [ "node_modules/codelyzer", "node_modules/@nrwl/workspace/src/tslint" ], "rules": { "arrow-return-shorthand": true, "callable-types": true, "class-name": true, "comment-format": [true, "check-space"], "curly": true, "deprecation": { "severity": "warn" }, "eofline": true, "forin": true, "import-blacklist": [true, "rxjs/Rx"], "import-spacing": true, "indent": [true, "spaces"], "interface-over-type-literal": true, "label-position": true, "max-line-length": [true, 200], "member-access": false, "member-ordering": [ true, { "order": [ "static-field", "instance-field", "static-method", "instance-method" ] } ], "no-arg": true, "no-bitwise": false, "no-console": [true, "debug", "info", "time", "timeEnd", "trace"], "no-construct": true, "no-debugger": true, "no-duplicate-super": true, "no-empty": false, "no-empty-interface": true, "no-eval": false, "no-inferrable-types": [true, "ignore-params"], "no-misused-new": true, "no-non-null-assertion": true, "no-redundant-jsdoc": true, "no-shadowed-variable": false, "no-string-literal": false, "no-string-throw": true, "no-switch-case-fall-through": true, "no-trailing-whitespace": true, "no-unnecessary-initializer": true, "no-unused-expression": true, "no-var-keyword": true, "object-literal-sort-keys": false, "one-line": [ true, "check-open-brace", "check-catch", "check-else", "check-whitespace" ], "prefer-const": true, "radix": true, "semicolon": [true, "always"], "triple-equals": [true, "allow-null-check"], "typedef-whitespace": [ true, { "call-signature": "nospace", "index-signature": "nospace", "parameter": "nospace", "property-declaration": "nospace", "variable-declaration": "nospace" } ], "unified-signatures": true, "variable-name": false, "whitespace": [ true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type" ], "no-output-on-prefix": true, "no-inputs-metadata-property": true, "no-outputs-metadata-property": true, "no-host-metadata-property": true, "no-input-rename": true, "no-output-rename": true, "use-lifecycle-interface": true, "use-pipe-transform-interface": true, "component-class-suffix": true, "directive-class-suffix": true } }