diff --git a/angular.json b/angular.json index 68911658a..767e932b7 100644 --- a/angular.json +++ b/angular.json @@ -984,6 +984,104 @@ } }, "schematics": {} + }, + "playground-quiz": { + "projectType": "application", + "schematics": { + "@nrwl/angular:component": { + "style": "scss" + } + }, + "root": "apps/playground/quiz", + "sourceRoot": "apps/playground/quiz/src", + "prefix": "codelab", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/apps/playground/quiz", + "index": "apps/playground/quiz/src/index.html", + "main": "apps/playground/quiz/src/main.ts", + "polyfills": "apps/playground/quiz/src/polyfills.ts", + "tsConfig": "apps/playground/quiz/tsconfig.app.json", + "aot": true, + "assets": [ + "apps/playground/quiz/src/favicon.ico", + "apps/playground/quiz/src/assets" + ], + "styles": [ + "node_modules/bootstrap/dist/css/bootstrap.min.css", + "apps/playground/quiz/src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "apps/playground/quiz/src/environments/environment.ts", + "with": "apps/playground/quiz/src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "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": "playground-quiz:build" + }, + "configurations": { + "production": { + "browserTarget": "playground-quiz:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "playground-quiz:build" + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "apps/playground/quiz/tsconfig.app.json", + "apps/playground/quiz/tsconfig.spec.json" + ], + "exclude": ["**/node_modules/**", "!apps/playground/quiz/**"] + } + }, + "test": { + "builder": "@nrwl/jest:jest", + "options": { + "jestConfig": "apps/playground/quiz/jest.config.js", + "tsConfig": "apps/playground/quiz/tsconfig.spec.json", + "setupFile": "apps/playground/quiz/src/test-setup.ts" + } + } + } } }, "defaultProject": "codelab", diff --git a/apps/playground/angular.json b/apps/playground/angular.json new file mode 100644 index 000000000..767e932b7 --- /dev/null +++ b/apps/playground/angular.json @@ -0,0 +1,1119 @@ +{ + "$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-devkit/build-angular:browser", + "options": { + "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", + { + "glob": "**/*", + "input": "node_modules/monaco-editor/", + "output": "./assets/monaco/" + }, + { + "glob": "**/*", + "input": "libs/code-demos/assets/runner/", + "output": "./assets/runner/" + }, + "apps/codelab/src/manifest.webmanifest" + ], + "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": true, + "ngswConfigPath": "apps/codelab/ngsw-config.json" + } + } + }, + "serve": { + "builder": "@angular-devkit/build-angular: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": "@angular-devkit/build-ng-packagr:build", + "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": "@angular-devkit/build-ng-packagr:build", + "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": "@angular-devkit/build-ng-packagr:build", + "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/**"] + } + } + } + }, + "slides": { + "root": "libs/slides", + "sourceRoot": "libs/slides/src", + "projectType": "library", + "prefix": "codelab", + "architect": { + "build": { + "builder": "@angular-devkit/build-ng-packagr:build", + "options": { + "tsConfig": "libs/slides/tsconfig.lib.json", + "project": "libs/slides/ng-package.json" + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "libs/slides/tsconfig.lib.json", + "libs/slides/tsconfig.spec.json" + ], + "exclude": ["**/node_modules/**"] + } + }, + "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" + } + } + } + }, + "code-demos": { + "root": "libs/code-demos", + "sourceRoot": "libs/code-demos/src", + "projectType": "library", + "prefix": "codelab", + "architect": { + "build": { + "builder": "@angular-devkit/build-ng-packagr:build", + "options": { + "tsConfig": "libs/code-demos/tsconfig.lib.json", + "project": "libs/code-demos/ng-package.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": "@angular-devkit/build-ng-packagr:build", + "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": "@angular-devkit/build-ng-packagr:build", + "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": {} + }, + "playground-quiz": { + "projectType": "application", + "schematics": { + "@nrwl/angular:component": { + "style": "scss" + } + }, + "root": "apps/playground/quiz", + "sourceRoot": "apps/playground/quiz/src", + "prefix": "codelab", + "architect": { + "build": { + "builder": "@angular-devkit/build-angular:browser", + "options": { + "outputPath": "dist/apps/playground/quiz", + "index": "apps/playground/quiz/src/index.html", + "main": "apps/playground/quiz/src/main.ts", + "polyfills": "apps/playground/quiz/src/polyfills.ts", + "tsConfig": "apps/playground/quiz/tsconfig.app.json", + "aot": true, + "assets": [ + "apps/playground/quiz/src/favicon.ico", + "apps/playground/quiz/src/assets" + ], + "styles": [ + "node_modules/bootstrap/dist/css/bootstrap.min.css", + "apps/playground/quiz/src/styles.scss" + ], + "scripts": [] + }, + "configurations": { + "production": { + "fileReplacements": [ + { + "replace": "apps/playground/quiz/src/environments/environment.ts", + "with": "apps/playground/quiz/src/environments/environment.prod.ts" + } + ], + "optimization": true, + "outputHashing": "all", + "sourceMap": false, + "extractCss": true, + "namedChunks": false, + "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": "playground-quiz:build" + }, + "configurations": { + "production": { + "browserTarget": "playground-quiz:build:production" + } + } + }, + "extract-i18n": { + "builder": "@angular-devkit/build-angular:extract-i18n", + "options": { + "browserTarget": "playground-quiz:build" + } + }, + "lint": { + "builder": "@angular-devkit/build-angular:tslint", + "options": { + "tsConfig": [ + "apps/playground/quiz/tsconfig.app.json", + "apps/playground/quiz/tsconfig.spec.json" + ], + "exclude": ["**/node_modules/**", "!apps/playground/quiz/**"] + } + }, + "test": { + "builder": "@nrwl/jest:jest", + "options": { + "jestConfig": "apps/playground/quiz/jest.config.js", + "tsConfig": "apps/playground/quiz/tsconfig.spec.json", + "setupFile": "apps/playground/quiz/src/test-setup.ts" + } + } + } + } + }, + "defaultProject": "codelab", + "schematics": { + "@schematics/angular:component": { + "prefix": "slides", + "styleext": "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" + } +} diff --git a/apps/playground/quiz/browserslist b/apps/playground/quiz/browserslist new file mode 100644 index 000000000..80848532e --- /dev/null +++ b/apps/playground/quiz/browserslist @@ -0,0 +1,12 @@ +# 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'. \ No newline at end of file diff --git a/apps/playground/quiz/jest.config.js b/apps/playground/quiz/jest.config.js new file mode 100644 index 000000000..12b435256 --- /dev/null +++ b/apps/playground/quiz/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + name: 'playground-quiz', + preset: '../../../jest.config.js', + coverageDirectory: '../../../coverage/apps/playground/quiz', + snapshotSerializers: [ + 'jest-preset-angular/AngularSnapshotSerializer.js', + 'jest-preset-angular/HTMLCommentSerializer.js' + ] +}; diff --git a/apps/playground/quiz/src/app/app-routing.module.ts b/apps/playground/quiz/src/app/app-routing.module.ts new file mode 100644 index 000000000..cc1581282 --- /dev/null +++ b/apps/playground/quiz/src/app/app-routing.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { Route, RouterModule } from '@angular/router'; +import { IntroductionComponent } from './containers/introduction/introduction.component'; +import { QuestionComponent } from './containers/question/question.component'; +import { ResultsComponent } from './containers/results/results.component'; + +const routes: Route[] = [ + { path: 'intro', component: IntroductionComponent, pathMatch: 'full' }, + { path: 'question', component: QuestionComponent, pathMatch: 'full' }, + { path: 'question/:questionId', component: QuestionComponent, pathMatch: 'full' }, + { path: 'results', component: ResultsComponent, pathMatch: 'full' }, + { path: '', redirectTo: 'intro', pathMatch: 'full' } +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes, { + // enableTracing: true + })], + exports: [RouterModule] +}) +export class AppRoutingModule { } diff --git a/apps/playground/quiz/src/app/app.component.html b/apps/playground/quiz/src/app/app.component.html new file mode 100644 index 000000000..6c46b1de8 --- /dev/null +++ b/apps/playground/quiz/src/app/app.component.html @@ -0,0 +1 @@ + diff --git a/apps/playground/quiz/src/app/app.component.scss b/apps/playground/quiz/src/app/app.component.scss new file mode 100644 index 000000000..5401b8eb8 --- /dev/null +++ b/apps/playground/quiz/src/app/app.component.scss @@ -0,0 +1,133 @@ +/* + * 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; +} diff --git a/apps/playground/quiz/src/app/app.component.spec.ts b/apps/playground/quiz/src/app/app.component.spec.ts new file mode 100644 index 000000000..d93819206 --- /dev/null +++ b/apps/playground/quiz/src/app/app.component.spec.ts @@ -0,0 +1,35 @@ +import { TestBed, async } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AppComponent } from './app.component'; + +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 'quiz'`, () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app.title).toEqual('quiz'); + }); + + it('should render title', () => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('.content span').textContent).toContain('quiz app is running!'); + }); +}); diff --git a/apps/playground/quiz/src/app/app.component.ts b/apps/playground/quiz/src/app/app.component.ts new file mode 100644 index 000000000..7f4f16ed5 --- /dev/null +++ b/apps/playground/quiz/src/app/app.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'codelab-quiz', + templateUrl: './app.component.html', + styleUrls: [ './app.component.css' ] +}) +export class AppComponent { + name = 'Angular'; +} diff --git a/apps/playground/quiz/src/app/app.module.ts b/apps/playground/quiz/src/app/app.module.ts new file mode 100644 index 000000000..e46bffd5b --- /dev/null +++ b/apps/playground/quiz/src/app/app.module.ts @@ -0,0 +1,48 @@ +import { NgModule, NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatCardModule } from '@angular/material/card'; +import { MatRadioModule, MAT_RADIO_DEFAULT_OPTIONS } from '@angular/material/radio'; +import { MatIconModule } from '@angular/material/icon'; +import { MatButtonModule } from '@angular/material/button'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; + +import { AppRoutingModule } from './app-routing.module'; +import { AppComponent } from './app.component'; +import { IntroductionComponent } from './containers/introduction/introduction.component'; +import { QuestionComponent } from './containers/question/question.component'; +import * as QuestionComponent2 from './components/question/question.component'; +import { ResultsComponent } from './containers/results/results.component'; + + +@NgModule({ + declarations: [ + AppComponent, + IntroductionComponent, + QuestionComponent, + QuestionComponent2.QuestionComponent, + ResultsComponent + ], + imports: [ + BrowserModule, + BrowserAnimationsModule, + AppRoutingModule, + ReactiveFormsModule, + MatCardModule, + MatRadioModule, + MatIconModule, + MatButtonModule, + NgbModule + ], + providers: [{ + provide: MAT_RADIO_DEFAULT_OPTIONS, + useValue: { color: 'accent' }, + }], + bootstrap: [ AppComponent ], + schemas: [ + CUSTOM_ELEMENTS_SCHEMA, + NO_ERRORS_SCHEMA + ] +}) +export class AppModule { } diff --git a/apps/playground/quiz/src/app/components/question/question.component.html b/apps/playground/quiz/src/app/components/question/question.component.html new file mode 100644 index 000000000..a328af71b --- /dev/null +++ b/apps/playground/quiz/src/app/components/question/question.component.html @@ -0,0 +1,28 @@ + + +
+
    + +
    + +
  1. {{ option.optionText }}
  2. + + +
    + +
    +
    + sentiment_very_satisfied +
      You're right! The correct answer is Option {{ question.answer }}.
    +
    +
    + sentiment_very_dissatisfied +
      That's wrong. The correct answer is Option {{ question.answer }}.
    +
    +
    +
    +
    +
+
diff --git a/apps/playground/quiz/src/app/components/question/question.component.scss b/apps/playground/quiz/src/app/components/question/question.component.scss new file mode 100644 index 000000000..e5a9874f8 --- /dev/null +++ b/apps/playground/quiz/src/app/components/question/question.component.scss @@ -0,0 +1,99 @@ +$font-stack: Space Mono, monospace; +$font-weight-max: 900; + +ol { + margin-top: 15px; + margin-left: -40px; + cursor: pointer; +} +ol li { + margin-left: 30px; +} + +.radio-options { + margin-bottom: 5px; + margin-left: 0.5rem; + padding: 4px; +} + +.option { + border: 2px solid #979797; + font-family: $font-stack; + font-size: 20px; + color: #0f0900; + background-color: #f5f5f5; + width: 39rem !important; + height: auto; + padding: 5px 5px 0 30px; + margin-left: -5px; + vertical-align: middle; +} +.option:hover { + outline: 2px solid #007aff; +} + +section.messages { + display: flex; + justify-content: center; + + .message { + font-family: $font-stack; + font-weight: $font-weight-max; + font-size: 16px; + font-style: italic; + text-align: center !important; + margin: 10px 0 0 0; + padding: 10px !important; + width: 32rem !important; + display: inline-flex; + align-items: center; + vertical-align: middle; + justify-content: center !important; + margin: 0 auto !important; + margin-top: 10px !important; + } + .correct-message { + font-weight: $font-weight-max; + font-style: italic; + border: 2px solid #007aff; + border-radius: 5px; + color: #00c853 !important; + } + .wrong-message { + font-weight: $font-weight-max; + font-style: italic; + border: 2px solid #ff0000; + border-radius: 5px; + color: #ff0000 !important; + padding: 5px; + } + mat-icon.sentiment { + font-size: 30px !important; + color: #9acd32; + margin-right: -50px !important; + vertical-align: top; + margin-top: 18px; + } + pre { + font-size: 17px; + margin-top: 10px; + } +} + +::ng-deep .mat-radio-button .mat-radio-container { + display: none; +} + +.feedback-icon { + position: absolute; + right: 0; + margin-right: 40px; + margin-top: -25px; +} + +.is-correct { + background-color: #00c853 !important; +} +.is-incorrect { + background-color: #ff0000 !important; +} diff --git a/apps/playground/quiz/src/app/components/question/question.component.ts b/apps/playground/quiz/src/app/components/question/question.component.ts new file mode 100644 index 000000000..2fbbf3a4e --- /dev/null +++ b/apps/playground/quiz/src/app/components/question/question.component.ts @@ -0,0 +1,65 @@ +import { Component, OnInit, OnChanges, SimpleChanges, Input, Output, EventEmitter } from '@angular/core'; +import { FormGroup, FormControl, FormBuilder, Validators } from '@angular/forms'; + +import { QuizQuestion } from '../../model/QuizQuestion'; + +@Component({ + selector: 'codelab-quiz-question', + templateUrl: './question.component.html', + styleUrls: ['./question.component.scss'] +}) +export class QuestionComponent implements OnInit, OnChanges { + @Output() answer = new EventEmitter(); + @Output() formGroup: FormGroup; + @Input() question: QuizQuestion; + @Input() allQuestions: QuizQuestion[]; + @Input() totalQuestions: number; + option = ''; + selectedOption = ''; + grayBorder = '2px solid #979797'; + + constructor(private fb: FormBuilder) {} + + ngOnInit() { + this.buildForm(); + } + + ngOnChanges(changes: SimpleChanges) { + if (changes.question && changes.question.currentValue && !changes.question.firstChange) { + this.formGroup.patchValue({answer: ''}); + } + } + + private buildForm() { + this.formGroup = this.fb.group({ + answer: new FormControl(['', Validators.required]) + // answer: ['', Validators.required] + }); + } + + radioChange(answer: string) { + this.question.selectedOption = answer; + this.answer.emit(answer); + this.displayExplanation(); + } + + displayExplanation(): void { + document.getElementById('question').innerHTML = + 'Option ' + this.question.answer + ' was correct because ' + this.question.explanation + '.'; + document.getElementById('question').style.border = this.grayBorder; + } + + isCorrect(option: string): boolean { + // mark the correct answer regardless of which option is selected once answered + return this.question.selectedOption && option === this.question.answer; + } + + isIncorrect(option: string): boolean { + // mark incorrect answer if selected + return option !== this.question.answer && option === this.question.selectedOption; + } + + onSubmit() { + this.formGroup.reset({answer: null}); + } +} diff --git a/apps/playground/quiz/src/app/containers/introduction/introduction.component.html b/apps/playground/quiz/src/app/containers/introduction/introduction.component.html new file mode 100644 index 000000000..eab186ad5 --- /dev/null +++ b/apps/playground/quiz/src/app/containers/introduction/introduction.component.html @@ -0,0 +1,17 @@ + + + + Dependency Injection Quiz + How well do you know Dependency Injection?
+ Take the quiz and find out!
+
+ Photo of Dependency Injection diagram + +

Take this awesome quiz that will help improve your understanding of Dependency Injection. The timed questionnaire + with automatic scoring provides you with a final score at the end. Match wits with your friends! Practice to + increase your knowledge. Good luck and have fun with this quiz. Share and enjoy!

+
+ + + +
diff --git a/apps/playground/quiz/src/app/containers/introduction/introduction.component.scss b/apps/playground/quiz/src/app/containers/introduction/introduction.component.scss new file mode 100644 index 000000000..d1bc37246 --- /dev/null +++ b/apps/playground/quiz/src/app/containers/introduction/introduction.component.scss @@ -0,0 +1,8 @@ +mat-card-actions { + button { + margin-bottom: 10px !important; + } + button:hover { + border: 1px solid #007aff; + } +} diff --git a/apps/playground/quiz/src/app/containers/introduction/introduction.component.ts b/apps/playground/quiz/src/app/containers/introduction/introduction.component.ts new file mode 100644 index 000000000..cff523c77 --- /dev/null +++ b/apps/playground/quiz/src/app/containers/introduction/introduction.component.ts @@ -0,0 +1,17 @@ +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'codelab-quiz-intro', + templateUrl: './introduction.component.html', + styleUrls: ['./introduction.component.scss'] +}) +export class IntroductionComponent { + QUIZ_TOPIC_IMAGE = '../../../assets/images/dependency-injection-diagram.png'; + + constructor(private router: Router) {} + + startQuiz() { + this.router.navigateByUrl('/question/1'); + } +} diff --git a/apps/playground/quiz/src/app/containers/question/question.component.html b/apps/playground/quiz/src/app/containers/question/question.component.html new file mode 100644 index 000000000..4565917fc --- /dev/null +++ b/apps/playground/quiz/src/app/containers/question/question.component.html @@ -0,0 +1,81 @@ + + +
+ Dependency Injection Quiz + + Assess your knowledge of Dependency Injection (DI) + +
+ + +
+
+
+ Score + + {{ correctAnswersCount }}/{{ totalQuestions }} + +
+
+ Question {{ question.questionId }} of {{ totalQuestions }} +
+
+ Time + + 0:0{{ timeLeft }} + +
+
+
+ + +
{{ question.question }}
+
+
+ + +
+ +
+ +
+
+ +
+
+
+ +
+ + {{ progressValue.toFixed(0) }}% + + +
+
+
+ + + + + diff --git a/apps/playground/quiz/src/app/containers/question/question.component.scss b/apps/playground/quiz/src/app/containers/question/question.component.scss new file mode 100644 index 000000000..058c15297 --- /dev/null +++ b/apps/playground/quiz/src/app/containers/question/question.component.scss @@ -0,0 +1,136 @@ +$font-stack: Space Mono, monospace; +$font-weight-max: 900; + +@font-face { + font-family: "Alarm Clock"; + src: url("../../../assets/alarm-clock.ttf") format("truetype"); +} + +section.scoreboard { + margin-top: 10px !important; + + .row { + display: inline; + } + .score { + float: left; + margin-left: 1rem; + } + .score .leader { + margin-left: 15px; + } + .badge { + float: left; + margin: 20px 10px 0 100px; + font-family: $font-stack; + font-size: 24px; + font-weight: $font-weight-max; + font-style: italic; + } + .time-left { + float: right; + margin-right: 1rem; + } + .time-left .leader { + margin-left: 20px; + } + .scoreboard { + font-family: "Alarm Clock", $font-stack; + font-weight: $font-weight-max; + font-size: 30px; + color: #006400; + display: inline-block; + margin: -5px 0 0 15px; + width: auto; + } + .leader { + display: block; + font-weight: $font-weight-max; + font-size: 18px; + text-transform: uppercase; + position: relative; + top: -5px; + } +} + +section.time-expired { + margin: 50px 0 20px 0 !important; + display: flex; + justify-content: center; + + button.time-expired-btn { + font-family: $font-stack; + font-weight: $font-weight-max; + text-align: center; + font-style: italic; + color: #006400 !important; + width: 26.5rem; + border: 1px solid #ff0000; + border-radius: 5px; + padding: 5px; + margin-bottom: 20px; + } + .timer-expired-icon, .qa-icon { + font-weight: $font-weight-max; + font-size: 30px !important; + color: #9acd32; + } + span.proceed, span.viewResults { + margin-top: -30px; + } +} + +#question { + font-family: $font-stack; + font-weight: 700; + font-size: 30px !important; + margin: 0 0 10px 0.4rem; + float: left; + border: 2px solid #007aff; + padding: 5px 10px 15px 20px; + background-color: #f5f5f5; + color: #0f0900; + width: 39rem !important; + height: auto; + vertical-align: middle; +} + +section.paging { + width: 40rem; + + mat-card-actions { + margin: -10px 0 10px 0; + + .previousQuestionNav { + float: left; + margin-left: 1.5rem; + } + .nextQuestionNav { + float: right; + margin-right: -0.35rem; + } + .previousQuestionNav:hover, .nextQuestionNav:hover { + border: 1px solid #007aff; + } + } +} + +section.progress-bar { + margin: 40px 0 10px 1.5rem; + width: 39rem; + height: auto; + + ngb-progressbar { + border-radius: 10px; + } + + .progress-note { + color: #ffff00; + font-family: $font-stack; + font-weight: $font-weight-max; + font-style: italic; + font-size: 20px; + padding-top: 5px; + margin-top: 5px; + } +} diff --git a/apps/playground/quiz/src/app/containers/question/question.component.ts b/apps/playground/quiz/src/app/containers/question/question.component.ts new file mode 100644 index 000000000..6399281fb --- /dev/null +++ b/apps/playground/quiz/src/app/containers/question/question.component.ts @@ -0,0 +1,344 @@ +import { Component, OnInit, Input, Output } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FormGroup } from '@angular/forms'; + +import { QuizQuestion } from '../../model/QuizQuestion'; + +@Component({ + selector: 'codelab-question-container', + templateUrl: './question.component.html', + styleUrls: ['./question.component.scss'] +}) +export class QuestionComponent implements OnInit { + @Input() formGroup: FormGroup; + @Output() question: QuizQuestion; + @Output() totalQuestions: number; + @Output() totalSelections = 0; + @Output() totalQuestionsAttempted = 0; + @Output() correctAnswersCount = 0; + @Output() percentage = 0; + @Output() completionTime: number; + + questionID = 0; + currentQuestion = 0; + questionIndex: number; + optionIndex: number; + correctAnswer: boolean; + disabled: boolean; + progressValue: number; + timeLeft: number; + timePerQuestion = 20; + interval: any; + elapsedTime: number; + elapsedTimes = []; + blueBorder = '2px solid #007aff'; + + @Output() allQuestions: QuizQuestion[] = [ + { + questionId: 1, + question: 'What is the objective of dependency injection?', + options: [ + { optionValue: '1', optionText: 'Pass the service to the client.' }, + { optionValue: '2', optionText: 'Allow the client to find service.' }, + { optionValue: '3', optionText: 'Allow the client to build service.' }, + { optionValue: '4', optionText: 'Give the client part service.' } + ], + answer: '1', + explanation: 'a service gets passed to the client during DI', + selectedOption: '' + }, + { + questionId: 2, + question: 'Which of the following benefit from dependency injection?', + options: [ + { optionValue: '1', optionText: 'Programming' }, + { optionValue: '2', optionText: 'Testability' }, + { optionValue: '3', optionText: 'Software design' }, + { optionValue: '4', optionText: 'All of the above.' }, + ], + answer: '4', + explanation: 'DI simplifies both programming and testing as well as being a popular design pattern', + selectedOption: '' + }, + { + questionId: 3, + question: 'Which of the following is the first step in setting up dependency injection?', + options: [ + { optionValue: '1', optionText: 'Require in the component.' }, + { optionValue: '2', optionText: 'Provide in the module.' }, + { optionValue: '3', optionText: 'Mark dependency as @Injectable().' } + ], + answer: '3', + explanation: 'the first step is marking the class as @Injectable()', + selectedOption: '' + }, + { + questionId: 4, + question: 'In which of the following does dependency injection occur?', + options: [ + { optionValue: '1', optionText: '@Injectable()' }, + { optionValue: '2', optionText: 'constructor' }, + { optionValue: '3', optionText: 'function' }, + { optionValue: '4', optionText: 'NgModule' }, + ], + answer: '2', + explanation: 'object instantiations are taken care of by the constructor by Angular', + selectedOption: '' + }, + { + questionId: 5, + question: 'Which access modifier is typically used in DI to make a service accessible in a class?', + options: [ + { optionValue: '1', optionText: 'public' }, + { optionValue: '2', optionText: 'protected' }, + { optionValue: '3', optionText: 'private' }, + { optionValue: '4', optionText: 'static' }, + ], + answer: '3', + explanation: 'the private keyword, when used within the constructor, tells Angular that the service is accessible', + selectedOption: '' + }, + { + questionId: 6, + question: 'How does Angular know that a service is available?', + options: [ + { optionValue: '1', optionText: 'If listed in the constructor.' }, + { optionValue: '2', optionText: 'If listed in the providers section of NgModule.' }, + { optionValue: '3', optionText: 'If the service is declared as an interface.' }, + { optionValue: '4', optionText: 'If the service is lazy-loaded.' }, + ], + answer: '2', + explanation: 'Angular looks at the providers section of NgModule to locate services that are available', + selectedOption: '' + }, + { + questionId: 7, + question: 'How does Angular avoid conflicts caused by using hardcoded strings as tokens?', + options: [ + { optionValue: '1', optionText: 'Use an InjectionToken class' }, + { optionValue: '2', optionText: 'Use @Inject()' }, + { optionValue: '3', optionText: 'Use useFactory' }, + { optionValue: '4', optionText: 'Use useValue' }, + ], + answer: '1', + explanation: 'an InjectionToken class is preferable to using strings', + selectedOption: '' + }, + { + questionId: 8, + question: 'Which is the preferred method for getting necessary data from a backend?', + options: [ + { optionValue: '1', optionText: 'HttpClient' }, + { optionValue: '2', optionText: 'WebSocket' }, + { optionValue: '3', optionText: 'NgRx' }, + { optionValue: '4', optionText: 'JSON' } + ], + answer: '1', + explanation: 'a server makes an HTTP request using the HttpClient service', + selectedOption: '' + }, + { + questionId: 9, + question: 'In which of the following can Angular use services?', + options: [ + { optionValue: '1', optionText: 'Lazy-loaded modules' }, + { optionValue: '2', optionText: 'Eagerly loaded modules' }, + { optionValue: '3', optionText: 'Feature modules' }, + { optionValue: '4', optionText: 'All of the above.' }, + ], + answer: '4', + explanation: 'Angular can utilize services with any of these methods', + selectedOption: '' + }, + { + questionId: 10, + question: 'Which of the following is true concerning dependency injection?', + options: [ + { optionValue: '1', optionText: 'It is a software design pattern.' }, + { optionValue: '2', optionText: 'Injectors form a hierarchy.' }, + { optionValue: '3', optionText: 'Providers register objects for future injection.' }, + { optionValue: '4', optionText: 'All of the above.' } + ], + answer: '4', + explanation: 'all of these are correct statements about dependency injection', + selectedOption: '' + } + ]; + + constructor(private route: ActivatedRoute, private router: Router) { + this.route.paramMap.subscribe(params => { + // get the question ID and store it. + this.setQuestionID(+params.get('questionId')); + this.question = this.getQuestion; + }); + } + + ngOnInit() { + this.question = this.getQuestion; + this.totalQuestions = this.allQuestions.length; + this.timeLeft = this.timePerQuestion; + this.progressValue = 100 * (this.currentQuestion + 1) / this.totalQuestions; + this.countDown(); + } + + displayNextQuestionWithOptions() { + this.resetTimer(); + this.increaseProgressValue(); + + this.questionIndex = this.questionID++; + document.getElementById('question').innerHTML = this.allQuestions[this.questionIndex].question; + document.getElementById('question').style.border = this.blueBorder; + + for (this.optionIndex = 0; this.optionIndex < 4; this.optionIndex++) { + document.getElementsByTagName('li')[this.optionIndex].innerHTML = + this.allQuestions[this.questionIndex].options[this.optionIndex].optionText; // add option text for list items + } + } + + displayPreviousQuestion() { + this.resetTimer(); + this.decreaseProgressValue(); + + this.questionIndex = this.currentQuestion -= 1; // decrease the question index by 2 for previous question + document.getElementById('question').innerHTML = this.allQuestions[this.questionIndex].question; + document.getElementById('question').style.border = this.blueBorder; + } + + navigateToNextQuestion(): void { + this.currentQuestion++; + + if (this.isThereAnotherQuestion()) { + this.router.navigate(['/question', this.getQuestionID() + 1]); // navigates to the next question + this.displayNextQuestionWithOptions(); // displays the next question + } + + this.resetTimer(); + } + + navigateToPreviousQuestion(): void { + this.currentQuestion--; + this.router.navigate(['/question', this.getQuestionID() - 1]); // navigates to the previous question + this.displayPreviousQuestion(); // display the previous question + } + + // increase the correct answer count when the correct answer is selected + incrementCorrectAnswersCount() { + if (this.question && this.question.selectedOption === this.question.answer) { + this.correctAnswersCount++; + this.correctAnswer = true; + } else { + this.correctAnswer = false; + } + } + + // checks whether the question is a valid question and is answered correctly + checkIfValidAndCorrect(): void { + if (this.question && this.currentQuestion <= this.totalQuestions && + this.question.selectedOption === this.question.answer) { + this.incrementCorrectAnswersCount(); + this.disabled = false; + this.elapsedTime = Math.floor(this.timePerQuestion - this.timeLeft); + this.elapsedTimes.push(this.elapsedTime); + this.quizDelay(3000); + this.navigateToNextQuestion(); + } + } + + // increase the progress value when the user presses the next button + increaseProgressValue() { + this.progressValue = 100 * (this.currentQuestion + 1) / this.totalQuestions; + } + + // decrease the progress value when the user presses the previous button + decreaseProgressValue() { + this.progressValue = (100 / this.totalQuestions) * (this.getQuestionID() - 1); + } + + // determine the percentage from amount of correct answers given and the total number of questions + calculatePercentage() { + this.percentage = 100 * (this.correctAnswersCount + 1) / this.totalQuestions; + } + + recordSelections() { + if (this.question.selectedOption !== '') { + this.totalSelections++; + } + } + + /**************** public API ***************/ + getQuestionID() { + return this.questionID; + } + + setQuestionID(id: number) { + return this.questionID = id; + } + + isThereAnotherQuestion(): boolean { + return this.questionID <= this.allQuestions.length; + } + + get getQuestion(): QuizQuestion { + return this.allQuestions.filter( + question => question.questionId === this.questionID + )[0]; + } + + // countdown timer and associated methods + private countDown() { + this.interval = setInterval(() => { + if (this.timeLeft > 0) { + this.timeLeft--; + this.recordSelections(); + + // utilized for disabling the next button until an option has been selected + if (this.question.selectedOption === '') { + this.disabled = true; + } else { + this.disabled = false; + } + + if (this.question && this.currentQuestion <= this.totalQuestions && this.question.selectedOption !== null) { + this.totalQuestionsAttempted++; + } + + this.checkIfValidAndCorrect(); + this.calculatePercentage(); + this.calculateTotalElapsedTime(this.elapsedTimes); + + // check if the timer is expired + if (this.timeLeft === 0 && this.question && this.currentQuestion <= this.totalQuestions) { + this.question.questionId++; + this.displayNextQuestionWithOptions(); + this.resetTimer(); + } + + if (this.question.questionId > this.totalQuestions) { + this.router.navigateByUrl('/results'); // todo: pass the data to results! + } + } + }, 1000); + } + + private resetTimer() { + this.timeLeft = this.timePerQuestion; + } + private stopTimer() { + this.timeLeft = 0; + } + + private calculateTotalElapsedTime(elapsedTimes) { + this.completionTime = elapsedTimes.reduce((acc, cur) => acc + cur, 0); + } + + quizDelay(milliseconds) { + const start = new Date().getTime(); + let counter = 0; + let end = 0; + + while (counter < milliseconds) { + end = new Date().getTime(); + counter = end - start; + } + } +} diff --git a/apps/playground/quiz/src/app/containers/results/results.component.html b/apps/playground/quiz/src/app/containers/results/results.component.html new file mode 100644 index 000000000..8c21750cc --- /dev/null +++ b/apps/playground/quiz/src/app/containers/results/results.component.html @@ -0,0 +1,93 @@ + + +
+ Dependency Injection Quiz +

Results

+
+ + +
+
+

Statistics

+ You answered {{ correctAnswersCount }} out of {{ totalQuestions }} questions correctly. + You completed the quiz in {{ elapsedMinutes }} minutes and {{ elapsedSeconds }} seconds. + +
+
+ Photo of Angular Trophy +

Great job!

+
+
+ Photo of 'Not Bad' +

Not bad!

+
+
+ Photo of 'Try Again'
+

Try again!

+
+ You scored {{ percentage }}% correctly (and quickly)! + You scored {{ percentage }}% correct. +
+
+ +
+
+ View a more detailed summary of your quiz +
+
+
+
+ + Question #{{ question.questionId }}: {{ question.question }} + +
+
+ + Correct Answer: + Option {{ question.answer }} — {{ question.options[question.answer - 1].optionText }} + +
+
+ + Your Answer: + Option {{ question.selectedOption }} — {{ question.options[question.selectedOption - 1].optionText }}  + done + clear + (no answer provided) + +
+
+ + Explanation: + Option {{ question.answer }} was correct because {{ question.explanation }}. + +
+
+
+
+
+
+
+ +
+ + Restart Quiz + Back to Codelab + +
+ +
+ +
+

Challenge your friends on social media!

+
+ + + +
+
+
+
diff --git a/apps/playground/quiz/src/app/containers/results/results.component.scss b/apps/playground/quiz/src/app/containers/results/results.component.scss new file mode 100644 index 000000000..7a5d6f16d --- /dev/null +++ b/apps/playground/quiz/src/app/containers/results/results.component.scss @@ -0,0 +1,167 @@ +$font-stack: Space Mono, monospace; +$font-weight-max: 900; + +section.results { + img { + width: 150px; + height: 150px; + margin: 0 auto !important; + display: flex; + justify-content: center; + text-align: center; + } + h3, p { + text-align: center; + } + + section.statistics { + h3, span, div.quiz-feedback, div span { + text-align: center; + } + + span { + display: block; + } + + div.quiz-feedback { + margin-top: 20px; + } + } + + section.quizSummary { + display: flex; + flex-direction: column; + + details { + text-align: center !important; + + summary { + font-size: 16px; + color: #007aff; + font-weight: $font-weight-max; + } + + .quiz-summary-question { + font-family: $font-stack; + font-size: 14px; + border: 2px solid #385d8a; + border-radius: 5px; + color: black; + margin-bottom: 15px; + padding: 10px; + text-align: left; + + .quiz-summary-field { + display: block !important; + margin-bottom: 10px; + text-align: left; + + span { + display: inline !important; + text-align: left; + font-size: 16px; + color: #00008b; + } + span.leader { + font-weight: $font-weight-max; + } + + mat-icon { + font-size: larger; + top: 10px !important; + } + mat-icon.correct { + color: #006400 !important; + font-weight: $font-weight-max; + } + mat-icon.incorrect { + color: #ff0000 !important; + font-weight: $font-weight-max; + } + } + } + } + } +} + + +section.return { + text-align: center; + margin-top: -10px; + + a.btn { + margin-right: 15px; + background-color: #20b2aa; + color: white; + border: 1px solid black; + border-radius: 5px; + } +} + +section.challenge-social { + text-align: center; + + a.btn { + text-decoration: none; + margin-right: 20px; + padding: 9px 15px 8px 42px; + background: no-repeat 10px 5px; + background-size: 25px 25px; + color: white; + } + a.btn.facebook { + background-color: #4267b2; + background-image: url('../../../assets/images/facebook.gif'); + top: 5px; + } + a.btn.twitter { + background-color: #59adeb; + background-image: url('../../../assets/images/twitter.svg'); + top: 5px; + } + a.btn.email { + background-color: #f0a121; + background-image: url('../../../assets/images/email.svg'); + top: 5px; + } +} + +/* add ripple effect to buttons on ResultsComponent */ +a.btn { + position: relative; + overflow: hidden; +} + +a.btn::after { + display: none; + content: ""; + position: absolute; + border-radius: 50%; + background-color: rgba(0, 0, 0, 0.3); + + width: 100px; + height: 100px; + margin-top: -50px; + margin-left: -50px; + + /* Center the ripple */ + top: 50%; + left: 50%; + + animation: ripple 1s; + opacity: 0; +} +a.btn:focus:not(:active)::after { + display: block; +} + +@keyframes ripple { + from { + opacity: 1; + transform: scale(0); + } + to { + opacity: 0; + transform: scale(10); + } +} diff --git a/apps/playground/quiz/src/app/containers/results/results.component.ts b/apps/playground/quiz/src/app/containers/results/results.component.ts new file mode 100644 index 000000000..b3c98c886 --- /dev/null +++ b/apps/playground/quiz/src/app/containers/results/results.component.ts @@ -0,0 +1,45 @@ +import { Component, OnInit, Input } from '@angular/core'; + +import { QuizQuestion } from '../../model/QuizQuestion'; + +@Component({ + selector: 'codelab-quiz-results', + templateUrl: './results.component.html', + styleUrls: ['./results.component.scss'] +}) +export class ResultsComponent implements OnInit { + @Input() question: QuizQuestion; + @Input() allQuestions: QuizQuestion[]; + @Input() totalQuestions: number; + @Input() totalQuestionsAttempted: number; + @Input() correctAnswersCount: number; + @Input() percentage: number; + @Input() completionTime: number; + @Input() totalSelections: number; + + elapsedMinutes: number; + elapsedSeconds: number; + + ANGULAR_TROPHY = '../../../assets/images/ng-trophy.png'; + NOT_BAD = '../../../assets/images/not-bad.jpg'; + TRY_AGAIN = '../../../assets/images/try-again.jpeg'; + codelabUrl = 'https://www.codelab.fun'; + + constructor() {} + + ngOnInit() { + if (this.percentage < 0) { + this.percentage = 0; + } + if (this.percentage > 100) { + this.percentage = 100; + } + + if (this.correctAnswersCount > this.totalQuestions) { + this.correctAnswersCount = this.totalQuestions; + } + + this.elapsedMinutes = Math.floor(this.completionTime / 60); + this.elapsedSeconds = this.completionTime % 60; + } +} diff --git a/apps/playground/quiz/src/app/model/Option.ts b/apps/playground/quiz/src/app/model/Option.ts new file mode 100644 index 000000000..46f75a2aa --- /dev/null +++ b/apps/playground/quiz/src/app/model/Option.ts @@ -0,0 +1,4 @@ +export interface Option { + optionValue: string; + optionText: string; +} diff --git a/apps/playground/quiz/src/app/model/QuizQuestion.ts b/apps/playground/quiz/src/app/model/QuizQuestion.ts new file mode 100644 index 000000000..7acde9151 --- /dev/null +++ b/apps/playground/quiz/src/app/model/QuizQuestion.ts @@ -0,0 +1,10 @@ +import { Option } from './Option'; + +export interface QuizQuestion { + questionId: number; + question: string; + options: Option[]; + answer: string; + explanation: string; + selectedOption: string; +} diff --git a/apps/playground/quiz/src/assets/DjbUpOnTheScoreboard.ttf b/apps/playground/quiz/src/assets/DjbUpOnTheScoreboard.ttf new file mode 100644 index 000000000..ded21514c Binary files /dev/null and b/apps/playground/quiz/src/assets/DjbUpOnTheScoreboard.ttf differ diff --git a/apps/playground/quiz/src/assets/alarm-clock.ttf b/apps/playground/quiz/src/assets/alarm-clock.ttf new file mode 100644 index 000000000..9e9b59345 Binary files /dev/null and b/apps/playground/quiz/src/assets/alarm-clock.ttf differ diff --git a/apps/playground/quiz/src/assets/images/angular.png b/apps/playground/quiz/src/assets/images/angular.png new file mode 100644 index 000000000..6c115fba8 Binary files /dev/null and b/apps/playground/quiz/src/assets/images/angular.png differ diff --git a/apps/playground/quiz/src/assets/images/angular.svg b/apps/playground/quiz/src/assets/images/angular.svg new file mode 100644 index 000000000..bf081acb1 --- /dev/null +++ b/apps/playground/quiz/src/assets/images/angular.svg @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/apps/playground/quiz/src/assets/images/congratulations.jpg b/apps/playground/quiz/src/assets/images/congratulations.jpg new file mode 100644 index 000000000..06bcb7b38 Binary files /dev/null and b/apps/playground/quiz/src/assets/images/congratulations.jpg differ diff --git a/apps/playground/quiz/src/assets/images/dependency-injection-diagram.png b/apps/playground/quiz/src/assets/images/dependency-injection-diagram.png new file mode 100644 index 000000000..8aab83cbc Binary files /dev/null and b/apps/playground/quiz/src/assets/images/dependency-injection-diagram.png differ diff --git a/apps/playground/quiz/src/assets/images/email.svg b/apps/playground/quiz/src/assets/images/email.svg new file mode 100644 index 000000000..77c5db3ca --- /dev/null +++ b/apps/playground/quiz/src/assets/images/email.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/apps/playground/quiz/src/assets/images/facebook.gif b/apps/playground/quiz/src/assets/images/facebook.gif new file mode 100644 index 000000000..cb02b6d11 Binary files /dev/null and b/apps/playground/quiz/src/assets/images/facebook.gif differ diff --git a/apps/playground/quiz/src/assets/images/headgears.png b/apps/playground/quiz/src/assets/images/headgears.png new file mode 100644 index 000000000..a0b92b19d Binary files /dev/null and b/apps/playground/quiz/src/assets/images/headgears.png differ diff --git a/apps/playground/quiz/src/assets/images/ng-trophy.png b/apps/playground/quiz/src/assets/images/ng-trophy.png new file mode 100644 index 000000000..282d7c434 Binary files /dev/null and b/apps/playground/quiz/src/assets/images/ng-trophy.png differ diff --git a/apps/playground/quiz/src/assets/images/not-bad.jpg b/apps/playground/quiz/src/assets/images/not-bad.jpg new file mode 100644 index 000000000..e8a2f9f31 Binary files /dev/null and b/apps/playground/quiz/src/assets/images/not-bad.jpg differ diff --git a/apps/playground/quiz/src/assets/images/try-again.jpeg b/apps/playground/quiz/src/assets/images/try-again.jpeg new file mode 100644 index 000000000..9b6fdaa1e Binary files /dev/null and b/apps/playground/quiz/src/assets/images/try-again.jpeg differ diff --git a/apps/playground/quiz/src/assets/images/twitter.svg b/apps/playground/quiz/src/assets/images/twitter.svg new file mode 100644 index 000000000..a4ed81154 --- /dev/null +++ b/apps/playground/quiz/src/assets/images/twitter.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/playground/quiz/src/environments/environment.prod.ts b/apps/playground/quiz/src/environments/environment.prod.ts new file mode 100644 index 000000000..3612073bc --- /dev/null +++ b/apps/playground/quiz/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true +}; diff --git a/apps/playground/quiz/src/environments/environment.ts b/apps/playground/quiz/src/environments/environment.ts new file mode 100644 index 000000000..7b4f817ad --- /dev/null +++ b/apps/playground/quiz/src/environments/environment.ts @@ -0,0 +1,16 @@ +// 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. diff --git a/apps/playground/quiz/src/favicon.ico b/apps/playground/quiz/src/favicon.ico new file mode 100644 index 000000000..317ebcb23 Binary files /dev/null and b/apps/playground/quiz/src/favicon.ico differ diff --git a/apps/playground/quiz/src/index.html b/apps/playground/quiz/src/index.html new file mode 100644 index 000000000..f266651f1 --- /dev/null +++ b/apps/playground/quiz/src/index.html @@ -0,0 +1,17 @@ + + + + + Angular/TypeScript Codelab Quiz + + + + + + + + + + + + diff --git a/apps/playground/quiz/src/main.ts b/apps/playground/quiz/src/main.ts new file mode 100644 index 000000000..fa4e0aef3 --- /dev/null +++ b/apps/playground/quiz/src/main.ts @@ -0,0 +1,13 @@ +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)); diff --git a/apps/playground/quiz/src/polyfills.ts b/apps/playground/quiz/src/polyfills.ts new file mode 100644 index 000000000..2f258e56c --- /dev/null +++ b/apps/playground/quiz/src/polyfills.ts @@ -0,0 +1,62 @@ +/** + * 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 + */ diff --git a/apps/playground/quiz/src/styles.scss b/apps/playground/quiz/src/styles.scss new file mode 100644 index 000000000..f2a223ac2 --- /dev/null +++ b/apps/playground/quiz/src/styles.scss @@ -0,0 +1,56 @@ +@import "../../../../node_modules/@angular/material/prebuilt-themes/indigo-pink.css"; +$font-stack: Space Mono, monospace; +$font-weight-max: 900; + +mat-card { + margin: 0 auto; + margin-bottom: 20px; + margin-top: 5%; + margin-left: 25%; + width: 42rem; + height: inherit; + padding: 20px; + display: flex; + flex-direction: column; + justify-content: center; + position: relative; + border: 1px solid black; + border-radius: 10px !important; +} +mat-card:hover { + box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2) !important; + transition: 0.3s !important; +} + +mat-card-header { + text-align: center; + display: flex; + justify-content: center; + + .header-image { + background-image: url('assets/images/angular.png'); + background-size: cover; + margin-left: -10px; + margin-top: -10px; + height: 100px !important; + width: 100px !important; + } + + mat-card-title { + font-family: $font-stack; + font-weight: $font-weight-max; + font-size: 30px !important; + margin: -10px 0 10px 10px; + color: #007aff; + text-align: center; + } + mat-card-subtitle { + font-family: $font-stack; + font-weight: $font-weight-max; + font-size: 17.5px !important; + font-style: italic; + color: #808080; + text-align: center; + } +} + diff --git a/apps/playground/quiz/src/test-setup.ts b/apps/playground/quiz/src/test-setup.ts new file mode 100644 index 000000000..8d88704e8 --- /dev/null +++ b/apps/playground/quiz/src/test-setup.ts @@ -0,0 +1 @@ +import 'jest-preset-angular'; diff --git a/apps/playground/quiz/tsconfig.app.json b/apps/playground/quiz/tsconfig.app.json new file mode 100644 index 000000000..8925f33e8 --- /dev/null +++ b/apps/playground/quiz/tsconfig.app.json @@ -0,0 +1,10 @@ +{ + "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"] +} diff --git a/apps/playground/quiz/tsconfig.json b/apps/playground/quiz/tsconfig.json new file mode 100644 index 000000000..08c7db8c9 --- /dev/null +++ b/apps/playground/quiz/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "types": ["node", "jest"] + }, + "include": ["**/*.ts"] +} diff --git a/apps/playground/quiz/tsconfig.spec.json b/apps/playground/quiz/tsconfig.spec.json new file mode 100644 index 000000000..fd405a65e --- /dev/null +++ b/apps/playground/quiz/tsconfig.spec.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "files": ["src/test-setup.ts"], + "include": ["**/*.spec.ts", "**/*.d.ts"] +} diff --git a/apps/playground/quiz/tslint.json b/apps/playground/quiz/tslint.json new file mode 100644 index 000000000..b6ad5c3a5 --- /dev/null +++ b/apps/playground/quiz/tslint.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tslint.json", + "rules": { + "directive-selector": [true, "attribute", "codelab", "camelCase"], + "component-selector": [true, "element", "codelab", "kebab-case"] + } +} diff --git a/quiz/browserslist b/quiz/browserslist new file mode 100644 index 000000000..80848532e --- /dev/null +++ b/quiz/browserslist @@ -0,0 +1,12 @@ +# 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'. \ No newline at end of file diff --git a/quiz/jest.config.js b/quiz/jest.config.js new file mode 100644 index 000000000..12b435256 --- /dev/null +++ b/quiz/jest.config.js @@ -0,0 +1,9 @@ +module.exports = { + name: 'playground-quiz', + preset: '../../../jest.config.js', + coverageDirectory: '../../../coverage/apps/playground/quiz', + snapshotSerializers: [ + 'jest-preset-angular/AngularSnapshotSerializer.js', + 'jest-preset-angular/HTMLCommentSerializer.js' + ] +}; diff --git a/quiz/src/app/app-routing.module.ts b/quiz/src/app/app-routing.module.ts new file mode 100644 index 000000000..cc1581282 --- /dev/null +++ b/quiz/src/app/app-routing.module.ts @@ -0,0 +1,21 @@ +import { NgModule } from '@angular/core'; +import { Route, RouterModule } from '@angular/router'; +import { IntroductionComponent } from './containers/introduction/introduction.component'; +import { QuestionComponent } from './containers/question/question.component'; +import { ResultsComponent } from './containers/results/results.component'; + +const routes: Route[] = [ + { path: 'intro', component: IntroductionComponent, pathMatch: 'full' }, + { path: 'question', component: QuestionComponent, pathMatch: 'full' }, + { path: 'question/:questionId', component: QuestionComponent, pathMatch: 'full' }, + { path: 'results', component: ResultsComponent, pathMatch: 'full' }, + { path: '', redirectTo: 'intro', pathMatch: 'full' } +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes, { + // enableTracing: true + })], + exports: [RouterModule] +}) +export class AppRoutingModule { } diff --git a/quiz/src/app/app.component.html b/quiz/src/app/app.component.html new file mode 100644 index 000000000..6c46b1de8 --- /dev/null +++ b/quiz/src/app/app.component.html @@ -0,0 +1 @@ + diff --git a/quiz/src/app/app.component.scss b/quiz/src/app/app.component.scss new file mode 100644 index 000000000..5401b8eb8 --- /dev/null +++ b/quiz/src/app/app.component.scss @@ -0,0 +1,133 @@ +/* + * 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; +} diff --git a/quiz/src/app/app.component.spec.ts b/quiz/src/app/app.component.spec.ts new file mode 100644 index 000000000..d93819206 --- /dev/null +++ b/quiz/src/app/app.component.spec.ts @@ -0,0 +1,35 @@ +import { TestBed, async } from '@angular/core/testing'; +import { RouterTestingModule } from '@angular/router/testing'; +import { AppComponent } from './app.component'; + +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 'quiz'`, () => { + const fixture = TestBed.createComponent(AppComponent); + const app = fixture.debugElement.componentInstance; + expect(app.title).toEqual('quiz'); + }); + + it('should render title', () => { + const fixture = TestBed.createComponent(AppComponent); + fixture.detectChanges(); + const compiled = fixture.debugElement.nativeElement; + expect(compiled.querySelector('.content span').textContent).toContain('quiz app is running!'); + }); +}); diff --git a/quiz/src/app/app.component.ts b/quiz/src/app/app.component.ts new file mode 100644 index 000000000..7f4f16ed5 --- /dev/null +++ b/quiz/src/app/app.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; + +@Component({ + selector: 'codelab-quiz', + templateUrl: './app.component.html', + styleUrls: [ './app.component.css' ] +}) +export class AppComponent { + name = 'Angular'; +} diff --git a/quiz/src/app/app.module.ts b/quiz/src/app/app.module.ts new file mode 100644 index 000000000..e46bffd5b --- /dev/null +++ b/quiz/src/app/app.module.ts @@ -0,0 +1,48 @@ +import { NgModule, NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { ReactiveFormsModule } from '@angular/forms'; +import { MatCardModule } from '@angular/material/card'; +import { MatRadioModule, MAT_RADIO_DEFAULT_OPTIONS } from '@angular/material/radio'; +import { MatIconModule } from '@angular/material/icon'; +import { MatButtonModule } from '@angular/material/button'; +import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; + +import { AppRoutingModule } from './app-routing.module'; +import { AppComponent } from './app.component'; +import { IntroductionComponent } from './containers/introduction/introduction.component'; +import { QuestionComponent } from './containers/question/question.component'; +import * as QuestionComponent2 from './components/question/question.component'; +import { ResultsComponent } from './containers/results/results.component'; + + +@NgModule({ + declarations: [ + AppComponent, + IntroductionComponent, + QuestionComponent, + QuestionComponent2.QuestionComponent, + ResultsComponent + ], + imports: [ + BrowserModule, + BrowserAnimationsModule, + AppRoutingModule, + ReactiveFormsModule, + MatCardModule, + MatRadioModule, + MatIconModule, + MatButtonModule, + NgbModule + ], + providers: [{ + provide: MAT_RADIO_DEFAULT_OPTIONS, + useValue: { color: 'accent' }, + }], + bootstrap: [ AppComponent ], + schemas: [ + CUSTOM_ELEMENTS_SCHEMA, + NO_ERRORS_SCHEMA + ] +}) +export class AppModule { } diff --git a/quiz/src/app/components/question/question.component.html b/quiz/src/app/components/question/question.component.html new file mode 100644 index 000000000..a328af71b --- /dev/null +++ b/quiz/src/app/components/question/question.component.html @@ -0,0 +1,28 @@ + + +
+
    + +
    + +
  1. {{ option.optionText }}
  2. + + +
    + +
    +
    + sentiment_very_satisfied +
      You're right! The correct answer is Option {{ question.answer }}.
    +
    +
    + sentiment_very_dissatisfied +
      That's wrong. The correct answer is Option {{ question.answer }}.
    +
    +
    +
    +
    +
+
diff --git a/quiz/src/app/components/question/question.component.scss b/quiz/src/app/components/question/question.component.scss new file mode 100644 index 000000000..e5a9874f8 --- /dev/null +++ b/quiz/src/app/components/question/question.component.scss @@ -0,0 +1,99 @@ +$font-stack: Space Mono, monospace; +$font-weight-max: 900; + +ol { + margin-top: 15px; + margin-left: -40px; + cursor: pointer; +} +ol li { + margin-left: 30px; +} + +.radio-options { + margin-bottom: 5px; + margin-left: 0.5rem; + padding: 4px; +} + +.option { + border: 2px solid #979797; + font-family: $font-stack; + font-size: 20px; + color: #0f0900; + background-color: #f5f5f5; + width: 39rem !important; + height: auto; + padding: 5px 5px 0 30px; + margin-left: -5px; + vertical-align: middle; +} +.option:hover { + outline: 2px solid #007aff; +} + +section.messages { + display: flex; + justify-content: center; + + .message { + font-family: $font-stack; + font-weight: $font-weight-max; + font-size: 16px; + font-style: italic; + text-align: center !important; + margin: 10px 0 0 0; + padding: 10px !important; + width: 32rem !important; + display: inline-flex; + align-items: center; + vertical-align: middle; + justify-content: center !important; + margin: 0 auto !important; + margin-top: 10px !important; + } + .correct-message { + font-weight: $font-weight-max; + font-style: italic; + border: 2px solid #007aff; + border-radius: 5px; + color: #00c853 !important; + } + .wrong-message { + font-weight: $font-weight-max; + font-style: italic; + border: 2px solid #ff0000; + border-radius: 5px; + color: #ff0000 !important; + padding: 5px; + } + mat-icon.sentiment { + font-size: 30px !important; + color: #9acd32; + margin-right: -50px !important; + vertical-align: top; + margin-top: 18px; + } + pre { + font-size: 17px; + margin-top: 10px; + } +} + +::ng-deep .mat-radio-button .mat-radio-container { + display: none; +} + +.feedback-icon { + position: absolute; + right: 0; + margin-right: 40px; + margin-top: -25px; +} + +.is-correct { + background-color: #00c853 !important; +} +.is-incorrect { + background-color: #ff0000 !important; +} diff --git a/quiz/src/app/components/question/question.component.ts b/quiz/src/app/components/question/question.component.ts new file mode 100644 index 000000000..7671b7e2e --- /dev/null +++ b/quiz/src/app/components/question/question.component.ts @@ -0,0 +1,64 @@ +import { Component, OnInit, OnChanges, SimpleChanges, Input, Output, EventEmitter } from '@angular/core'; +import { FormGroup, FormControl, FormBuilder, Validators } from '@angular/forms'; + +import { QuizQuestion } from '../../model/QuizQuestion'; + +@Component({ + selector: 'codelab-quiz-question', + templateUrl: './question.component.html', + styleUrls: ['./question.component.scss'] +}) +export class QuestionComponent implements OnInit, OnChanges { + @Output() answer = new EventEmitter(); + @Output() formGroup: FormGroup; + @Input() question: QuizQuestion; + @Input() allQuestions: QuizQuestion[]; + @Input() totalQuestions: number; + option = ''; + selectedOption = ''; + grayBorder = '2px solid #979797'; + + constructor(private fb: FormBuilder) {} + + ngOnInit() { + this.buildForm(); + } + + ngOnChanges(changes: SimpleChanges) { + if (changes.question && changes.question.currentValue && !changes.question.firstChange) { + this.formGroup.patchValue({answer: ''}); + } + } + + private buildForm() { + this.formGroup = this.fb.group({ + answer: new FormControl(['', Validators.required]) + }); + } + + radioChange(answer: string) { + this.question.selectedOption = answer; + this.answer.emit(answer); + this.displayExplanation(); + } + + displayExplanation(): void { + document.getElementById('question').innerHTML = + 'Option ' + this.question.answer + ' was correct because ' + this.question.explanation + '.'; + document.getElementById('question').style.border = this.grayBorder; + } + + isCorrect(option: string): boolean { + // mark the correct answer regardless of which option is selected once answered + return this.question.selectedOption && option === this.question.answer; + } + + isIncorrect(option: string): boolean { + // mark incorrect answer if selected + return option !== this.question.answer && option === this.question.selectedOption; + } + + onSubmit() { + this.formGroup.reset({answer: null}); + } +} diff --git a/quiz/src/app/containers/introduction/introduction.component.html b/quiz/src/app/containers/introduction/introduction.component.html new file mode 100644 index 000000000..86c463776 --- /dev/null +++ b/quiz/src/app/containers/introduction/introduction.component.html @@ -0,0 +1,17 @@ + + + + Dependency Injection Quiz + How well do you know Dependency Injection?
+ Take the quiz and find out!
+
+ Photo of Dependency Injection diagram + +

Take this awesome quiz that will help improve your understanding of Dependency Injection. The timed questionnaire + with automatic scoring provides you with a final score at the end. Match wits with your friends! Practice to + increase your knowledge. Good luck and have fun with this quiz. Share and enjoy!

+
+ + + +
diff --git a/quiz/src/app/containers/introduction/introduction.component.scss b/quiz/src/app/containers/introduction/introduction.component.scss new file mode 100644 index 000000000..935a67ff6 --- /dev/null +++ b/quiz/src/app/containers/introduction/introduction.component.scss @@ -0,0 +1,14 @@ +mat-card-content p { + text-align: justify; +} + +mat-card-actions { + text-align: center; + + button { + margin-bottom: 10px !important; + } + button:hover { + border: 1px solid #007aff; + } +} diff --git a/quiz/src/app/containers/introduction/introduction.component.ts b/quiz/src/app/containers/introduction/introduction.component.ts new file mode 100644 index 000000000..cff523c77 --- /dev/null +++ b/quiz/src/app/containers/introduction/introduction.component.ts @@ -0,0 +1,17 @@ +import { Component } from '@angular/core'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'codelab-quiz-intro', + templateUrl: './introduction.component.html', + styleUrls: ['./introduction.component.scss'] +}) +export class IntroductionComponent { + QUIZ_TOPIC_IMAGE = '../../../assets/images/dependency-injection-diagram.png'; + + constructor(private router: Router) {} + + startQuiz() { + this.router.navigateByUrl('/question/1'); + } +} diff --git a/quiz/src/app/containers/question/question.component.html b/quiz/src/app/containers/question/question.component.html new file mode 100644 index 000000000..175bba0c2 --- /dev/null +++ b/quiz/src/app/containers/question/question.component.html @@ -0,0 +1,76 @@ + + +
+ Dependency Injection Quiz + + Assess your knowledge of Dependency Injection (DI) + +
+ + +
+
+
+ Score + + {{ correctAnswersCount }}/{{ totalQuestions }} + +
+
+ Question {{ question.questionId }} of {{ totalQuestions }} +
+
+ Time + + 0:0{{ timeLeft }} + +
+
+
+ + +
{{ question.question }}
+
+
+ + +
+ +
+ +
+
+ +
+
+
+ +
+ + {{ progressValue.toFixed(0) }}% + +
+
+
+ + + + + diff --git a/quiz/src/app/containers/question/question.component.scss b/quiz/src/app/containers/question/question.component.scss new file mode 100644 index 000000000..058c15297 --- /dev/null +++ b/quiz/src/app/containers/question/question.component.scss @@ -0,0 +1,136 @@ +$font-stack: Space Mono, monospace; +$font-weight-max: 900; + +@font-face { + font-family: "Alarm Clock"; + src: url("../../../assets/alarm-clock.ttf") format("truetype"); +} + +section.scoreboard { + margin-top: 10px !important; + + .row { + display: inline; + } + .score { + float: left; + margin-left: 1rem; + } + .score .leader { + margin-left: 15px; + } + .badge { + float: left; + margin: 20px 10px 0 100px; + font-family: $font-stack; + font-size: 24px; + font-weight: $font-weight-max; + font-style: italic; + } + .time-left { + float: right; + margin-right: 1rem; + } + .time-left .leader { + margin-left: 20px; + } + .scoreboard { + font-family: "Alarm Clock", $font-stack; + font-weight: $font-weight-max; + font-size: 30px; + color: #006400; + display: inline-block; + margin: -5px 0 0 15px; + width: auto; + } + .leader { + display: block; + font-weight: $font-weight-max; + font-size: 18px; + text-transform: uppercase; + position: relative; + top: -5px; + } +} + +section.time-expired { + margin: 50px 0 20px 0 !important; + display: flex; + justify-content: center; + + button.time-expired-btn { + font-family: $font-stack; + font-weight: $font-weight-max; + text-align: center; + font-style: italic; + color: #006400 !important; + width: 26.5rem; + border: 1px solid #ff0000; + border-radius: 5px; + padding: 5px; + margin-bottom: 20px; + } + .timer-expired-icon, .qa-icon { + font-weight: $font-weight-max; + font-size: 30px !important; + color: #9acd32; + } + span.proceed, span.viewResults { + margin-top: -30px; + } +} + +#question { + font-family: $font-stack; + font-weight: 700; + font-size: 30px !important; + margin: 0 0 10px 0.4rem; + float: left; + border: 2px solid #007aff; + padding: 5px 10px 15px 20px; + background-color: #f5f5f5; + color: #0f0900; + width: 39rem !important; + height: auto; + vertical-align: middle; +} + +section.paging { + width: 40rem; + + mat-card-actions { + margin: -10px 0 10px 0; + + .previousQuestionNav { + float: left; + margin-left: 1.5rem; + } + .nextQuestionNav { + float: right; + margin-right: -0.35rem; + } + .previousQuestionNav:hover, .nextQuestionNav:hover { + border: 1px solid #007aff; + } + } +} + +section.progress-bar { + margin: 40px 0 10px 1.5rem; + width: 39rem; + height: auto; + + ngb-progressbar { + border-radius: 10px; + } + + .progress-note { + color: #ffff00; + font-family: $font-stack; + font-weight: $font-weight-max; + font-style: italic; + font-size: 20px; + padding-top: 5px; + margin-top: 5px; + } +} diff --git a/quiz/src/app/containers/question/question.component.ts b/quiz/src/app/containers/question/question.component.ts new file mode 100644 index 000000000..176dbc08b --- /dev/null +++ b/quiz/src/app/containers/question/question.component.ts @@ -0,0 +1,344 @@ +import { Component, OnInit, Input, Output } from '@angular/core'; +import { ActivatedRoute, Router } from '@angular/router'; +import { FormGroup } from '@angular/forms'; + +import { QuizQuestion } from '../../model/QuizQuestion'; + +@Component({ + selector: 'codelab-question-container', + templateUrl: './question.component.html', + styleUrls: ['./question.component.scss'] +}) +export class QuestionComponent implements OnInit { + @Input() formGroup: FormGroup; + @Output() question: QuizQuestion; + @Output() totalQuestions: number; + @Output() totalSelections = 0; + @Output() totalQuestionsAttempted = 0; + @Output() correctAnswersCount = 0; + @Output() percentage = 0; + @Output() completionTime: number; + + questionID = 0; + currentQuestion = 0; + questionIndex: number; + optionIndex: number; + correctAnswer: boolean; + disabled: boolean; + progressValue: number; + timeLeft: number; + timePerQuestion = 20; + interval: any; + elapsedTime: number; + elapsedTimes = []; + blueBorder = '2px solid #007aff'; + + @Output() allQuestions: QuizQuestion[] = [ + { + questionId: 1, + question: 'What is the objective of dependency injection?', + options: [ + { optionValue: '1', optionText: 'Pass the service to the client.' }, + { optionValue: '2', optionText: 'Allow the client to find service.' }, + { optionValue: '3', optionText: 'Allow the client to build service.' }, + { optionValue: '4', optionText: 'Give the client part service.' } + ], + answer: '1', + explanation: 'a service gets passed to the client during DI', + selectedOption: '' + }, + { + questionId: 2, + question: 'Which of the following benefit from dependency injection?', + options: [ + { optionValue: '1', optionText: 'Programming' }, + { optionValue: '2', optionText: 'Testability' }, + { optionValue: '3', optionText: 'Software design' }, + { optionValue: '4', optionText: 'All of the above.' }, + ], + answer: '4', + explanation: 'DI simplifies both programming and testing as well as being a popular design pattern', + selectedOption: '' + }, + { + questionId: 3, + question: 'Which of the following is the first step in setting up dependency injection?', + options: [ + { optionValue: '1', optionText: 'Require in the component.' }, + { optionValue: '2', optionText: 'Provide in the module.' }, + { optionValue: '3', optionText: 'Mark dependency as @Injectable().' } + ], + answer: '3', + explanation: 'the first step is marking the class as @Injectable()', + selectedOption: '' + }, + { + questionId: 4, + question: 'In which of the following does dependency injection occur?', + options: [ + { optionValue: '1', optionText: '@Injectable()' }, + { optionValue: '2', optionText: 'constructor' }, + { optionValue: '3', optionText: 'function' }, + { optionValue: '4', optionText: 'NgModule' }, + ], + answer: '2', + explanation: 'object instantiations are taken care of by the constructor by Angular', + selectedOption: '' + }/*, + { + questionId: 5, + question: 'Which access modifier is typically used in DI to make a service accessible in a class?', + options: [ + { optionValue: '1', optionText: 'public' }, + { optionValue: '2', optionText: 'protected' }, + { optionValue: '3', optionText: 'private' }, + { optionValue: '4', optionText: 'static' }, + ], + answer: '3', + explanation: 'the private keyword, when used within the constructor, tells Angular that the service is accessible', + selectedOption: '' + }, + { + questionId: 6, + question: 'How does Angular know that a service is available?', + options: [ + { optionValue: '1', optionText: 'If listed in the constructor.' }, + { optionValue: '2', optionText: 'If listed in the providers section of NgModule.' }, + { optionValue: '3', optionText: 'If the service is declared as an interface.' }, + { optionValue: '4', optionText: 'If the service is lazy-loaded.' }, + ], + answer: '2', + explanation: 'Angular looks at the providers section of NgModule to locate services that are available', + selectedOption: '' + }, + { + questionId: 7, + question: 'How does Angular avoid conflicts caused by using hardcoded strings as tokens?', + options: [ + { optionValue: '1', optionText: 'Use an InjectionToken class' }, + { optionValue: '2', optionText: 'Use @Inject()' }, + { optionValue: '3', optionText: 'Use useFactory' }, + { optionValue: '4', optionText: 'Use useValue' }, + ], + answer: '1', + explanation: 'an InjectionToken class is preferable to using strings', + selectedOption: '' + }, + { + questionId: 8, + question: 'Which is the preferred method for getting necessary data from a backend?', + options: [ + { optionValue: '1', optionText: 'HttpClient' }, + { optionValue: '2', optionText: 'WebSocket' }, + { optionValue: '3', optionText: 'NgRx' }, + { optionValue: '4', optionText: 'JSON' } + ], + answer: '1', + explanation: 'a server makes an HTTP request using the HttpClient service', + selectedOption: '' + }, + { + questionId: 9, + question: 'In which of the following can Angular use services?', + options: [ + { optionValue: '1', optionText: 'Lazy-loaded modules' }, + { optionValue: '2', optionText: 'Eagerly loaded modules' }, + { optionValue: '3', optionText: 'Feature modules' }, + { optionValue: '4', optionText: 'All of the above.' }, + ], + answer: '4', + explanation: 'Angular can utilize services with any of these methods', + selectedOption: '' + }, + { + questionId: 10, + question: 'Which of the following is true concerning dependency injection?', + options: [ + { optionValue: '1', optionText: 'It is a software design pattern.' }, + { optionValue: '2', optionText: 'Injectors form a hierarchy.' }, + { optionValue: '3', optionText: 'Providers register objects for future injection.' }, + { optionValue: '4', optionText: 'All of the above.' } + ], + answer: '4', + explanation: 'all of these are correct statements about dependency injection', + selectedOption: '' + } */ + ]; + + constructor(private route: ActivatedRoute, private router: Router) { + this.route.paramMap.subscribe(params => { + // get the question ID and store it. + this.setQuestionID(+params.get('questionId')); + this.question = this.getQuestion; + }); + } + + ngOnInit() { + this.question = this.getQuestion; + this.totalQuestions = this.allQuestions.length; + this.timeLeft = this.timePerQuestion; + this.progressValue = 100 * (this.currentQuestion + 1) / this.totalQuestions; + this.countDown(); + } + + displayNextQuestionWithOptions() { + this.resetTimer(); + this.increaseProgressValue(); + + this.questionIndex = this.questionID++; + document.getElementById('question').innerHTML = this.allQuestions[this.questionIndex].question; + document.getElementById('question').style.border = this.blueBorder; + + for (this.optionIndex = 0; this.optionIndex < 4; this.optionIndex++) { + document.getElementsByTagName('li')[this.optionIndex].innerHTML = + this.allQuestions[this.questionIndex].options[this.optionIndex].optionText; // add option text for list items + } + } + + displayPreviousQuestion() { + this.resetTimer(); + this.decreaseProgressValue(); + + this.questionIndex = this.currentQuestion -= 1; // decrease the question index by 2 for previous question + document.getElementById('question').innerHTML = this.allQuestions[this.questionIndex].question; + document.getElementById('question').style.border = this.blueBorder; + } + + navigateToNextQuestion(): void { + this.currentQuestion++; + + if (this.isThereAnotherQuestion()) { + this.router.navigate(['/question', this.getQuestionID() + 1]); // navigates to the next question + this.displayNextQuestionWithOptions(); // displays the next question + } + + this.resetTimer(); + } + + navigateToPreviousQuestion(): void { + this.currentQuestion--; + this.router.navigate(['/question', this.getQuestionID() - 1]); // navigates to the previous question + this.displayPreviousQuestion(); // display the previous question + } + + // increase the correct answer count when the correct answer is selected + incrementCorrectAnswersCount() { + if (this.question && this.question.selectedOption === this.question.answer) { + this.correctAnswersCount++; + this.correctAnswer = true; + } else { + this.correctAnswer = false; + } + } + + // checks whether the question is a valid question and is answered correctly + checkIfValidAndCorrect(): void { + if (this.question && this.currentQuestion <= this.totalQuestions && + this.question.selectedOption === this.question.answer) { + this.incrementCorrectAnswersCount(); + this.disabled = false; + this.elapsedTime = Math.floor(this.timePerQuestion - this.timeLeft); + this.elapsedTimes.push(this.elapsedTime); + this.quizDelay(3000); + this.navigateToNextQuestion(); + } + } + + // increase the progress value when the user presses the next button + increaseProgressValue() { + this.progressValue = 100 * (this.currentQuestion + 1) / this.totalQuestions; + } + + // decrease the progress value when the user presses the previous button + decreaseProgressValue() { + this.progressValue = (100 / this.totalQuestions) * (this.getQuestionID() - 1); + } + + // determine the percentage from amount of correct answers given and the total number of questions + calculatePercentage() { + this.percentage = 100 * (this.correctAnswersCount + 1) / this.totalQuestions; + } + + recordSelections() { + if (this.question.selectedOption !== '') { + this.totalSelections++; + } + } + + /**************** public API ***************/ + getQuestionID() { + return this.questionID; + } + + setQuestionID(id: number) { + return this.questionID = id; + } + + isThereAnotherQuestion(): boolean { + return this.questionID <= this.allQuestions.length; + } + + get getQuestion(): QuizQuestion { + return this.allQuestions.filter( + question => question.questionId === this.questionID + )[0]; + } + + // countdown timer and associated methods + private countDown() { + this.interval = setInterval(() => { + if (this.timeLeft > 0) { + this.timeLeft--; + this.recordSelections(); + + // utilized for disabling the next button until an option has been selected + if (this.question.selectedOption === '') { + this.disabled = true; + } else { + this.disabled = false; + } + + if (this.question && this.currentQuestion <= this.totalQuestions && this.question.selectedOption !== null) { + this.totalQuestionsAttempted++; + } + + this.checkIfValidAndCorrect(); + this.calculatePercentage(); + this.calculateTotalElapsedTime(this.elapsedTimes); + + // check if the timer is expired + if (this.timeLeft === 0 && this.question && this.currentQuestion <= this.totalQuestions) { + this.question.questionId++; + this.displayNextQuestionWithOptions(); + this.resetTimer(); + } + + if (this.question.questionId > this.totalQuestions) { + this.router.navigateByUrl('/results'); // todo: pass the data to results! + } + } + }, 1000); + } + + private resetTimer() { + this.timeLeft = this.timePerQuestion; + } + private stopTimer() { + this.timeLeft = 0; + } + + private calculateTotalElapsedTime(elapsedTimes) { + this.completionTime = elapsedTimes.reduce((acc, cur) => acc + cur, 0); + } + + quizDelay(milliseconds) { + const start = new Date().getTime(); + let counter = 0; + let end = 0; + + while (counter < milliseconds) { + end = new Date().getTime(); + counter = end - start; + } + } +} diff --git a/quiz/src/app/containers/results/results.component.html b/quiz/src/app/containers/results/results.component.html new file mode 100644 index 000000000..b3fe2f6af --- /dev/null +++ b/quiz/src/app/containers/results/results.component.html @@ -0,0 +1,89 @@ + + +
+ Dependency Injection Quiz +

Results

+
+ + +
+
+

Statistics

+ You answered {{ correctAnswersCount }} out of {{ totalQuestions }} questions correctly. + You completed the quiz in {{ elapsedMinutes }} minutes and {{ elapsedSeconds }} seconds. + +
+
+ Photo of Angular Trophy +

Great job!

+
+
+ Photo of 'Not Bad' +

Not bad!

+
+
+ Photo of 'Try Again'
+

Try again!

+
+ You scored {{ percentage }}% correctly (and quickly)! + You scored {{ percentage }}% correct. +
+
+ +
+
+ View a more detailed summary of your quiz +
+
+
+ + Question #{{ question.questionId }}: {{ question.question }} + +
+
+ + Correct Answer: + Option {{ question.answer }} — {{ question.options[question.answer - 1].optionText }} + +
+
+ + Your Answer: + Option {{ question.selectedOption }} — {{ question.options[question.selectedOption - 1].optionText }}  + done + clear + (no answer provided) + +
+
+ + Explanation: + Option {{ question.answer }} was correct because {{ question.explanation }}. + +
+
+
+
+
+
+ +
+ + Restart Quiz + Back to Codelab + +
+ +
+ +
+

Challenge your friends on social media!

+ + + +
+
+
diff --git a/quiz/src/app/containers/results/results.component.scss b/quiz/src/app/containers/results/results.component.scss new file mode 100644 index 000000000..3a788c508 --- /dev/null +++ b/quiz/src/app/containers/results/results.component.scss @@ -0,0 +1,173 @@ +$font-stack: Space Mono, monospace; +$font-weight-max: 900; + +section.results { + h3 { + text-align: center; + } + img { + width: 150px; + height: 150px; + margin: 0 auto !important; + display: flex; + justify-content: center; + text-align: center; + } + + section.statistics { + h3, span, .quiz-feedback, div span { + text-align: center; + } + + span { + display: block; + } + + .quiz-feedback { + margin-top: 20px; + } + } + + section.quizSummary { + display: flex; + flex-direction: column; + + details { + text-align: center !important; + + summary { + font-size: 16px; + color: #007aff; + font-weight: $font-weight-max; + margin-bottom: 20px; + outline: none; + } + + .allQuestions { + clear: left; + } + + .quiz-summary-question { + font-family: $font-stack; + font-size: 14px; + border: 2px solid #385d8a; + border-radius: 5px; + color: black; + margin-bottom: 15px; + padding: 10px; + text-align: left; + + .quiz-summary-field { + display: block !important; + margin-bottom: 10px; + text-align: left; + + span { + display: inline !important; + text-align: left; + font-size: 16px; + color: #00008b; + } + span.leader { + font-weight: $font-weight-max; + } + + mat-icon { + font-size: larger; + top: 10px !important; + } + mat-icon.correct { + color: #006400 !important; + font-weight: $font-weight-max; + } + mat-icon.incorrect { + color: #ff0000 !important; + font-weight: $font-weight-max; + } + } + } + } + } +} + +section.return { + clear: left; + text-align: center; + margin-top: -10px; + + a.btn { + margin-right: 15px; + background-color: #20b2aa; + color: white; + border: 1px solid black; + border-radius: 5px; + } +} + +section.challenge-social { + text-align: center; + + a.btn { + text-decoration: none; + margin-right: 20px; + padding: 9px 15px 8px 42px; + background: no-repeat 10px 5px; + background-size: 25px 25px; + color: white; + } + a.btn.facebook { + background-color: #4267b2; + background-image: url('../../../assets/images/facebook.gif'); + top: 5px; + } + a.btn.twitter { + background-color: #59adeb; + background-image: url('../../../assets/images/twitter.svg'); + top: 5px; + } + a.btn.email { + background-color: #f0a121; + background-image: url('../../../assets/images/email.svg'); + top: 5px; + } +} + +/* add ripple effect to buttons */ +a.btn { + position: relative; + overflow: hidden; +} + +a.btn::after { + display: none; + content: ""; + position: absolute; + border-radius: 50%; + background-color: rgba(0, 0, 0, 0.3); + + width: 100px; + height: 100px; + margin-top: -50px; + margin-left: -50px; + + /* Center the ripple */ + top: 50%; + left: 50%; + + animation: ripple 1s; + opacity: 0; +} +a.btn:focus:not(:active)::after { + display: block; +} + +@keyframes ripple { + from { + opacity: 1; + transform: scale(0); + } + to { + opacity: 0; + transform: scale(10); + } +} diff --git a/quiz/src/app/containers/results/results.component.ts b/quiz/src/app/containers/results/results.component.ts new file mode 100644 index 000000000..b3c98c886 --- /dev/null +++ b/quiz/src/app/containers/results/results.component.ts @@ -0,0 +1,45 @@ +import { Component, OnInit, Input } from '@angular/core'; + +import { QuizQuestion } from '../../model/QuizQuestion'; + +@Component({ + selector: 'codelab-quiz-results', + templateUrl: './results.component.html', + styleUrls: ['./results.component.scss'] +}) +export class ResultsComponent implements OnInit { + @Input() question: QuizQuestion; + @Input() allQuestions: QuizQuestion[]; + @Input() totalQuestions: number; + @Input() totalQuestionsAttempted: number; + @Input() correctAnswersCount: number; + @Input() percentage: number; + @Input() completionTime: number; + @Input() totalSelections: number; + + elapsedMinutes: number; + elapsedSeconds: number; + + ANGULAR_TROPHY = '../../../assets/images/ng-trophy.png'; + NOT_BAD = '../../../assets/images/not-bad.jpg'; + TRY_AGAIN = '../../../assets/images/try-again.jpeg'; + codelabUrl = 'https://www.codelab.fun'; + + constructor() {} + + ngOnInit() { + if (this.percentage < 0) { + this.percentage = 0; + } + if (this.percentage > 100) { + this.percentage = 100; + } + + if (this.correctAnswersCount > this.totalQuestions) { + this.correctAnswersCount = this.totalQuestions; + } + + this.elapsedMinutes = Math.floor(this.completionTime / 60); + this.elapsedSeconds = this.completionTime % 60; + } +} diff --git a/quiz/src/app/model/Option.ts b/quiz/src/app/model/Option.ts new file mode 100644 index 000000000..46f75a2aa --- /dev/null +++ b/quiz/src/app/model/Option.ts @@ -0,0 +1,4 @@ +export interface Option { + optionValue: string; + optionText: string; +} diff --git a/quiz/src/app/model/QuizQuestion.ts b/quiz/src/app/model/QuizQuestion.ts new file mode 100644 index 000000000..7acde9151 --- /dev/null +++ b/quiz/src/app/model/QuizQuestion.ts @@ -0,0 +1,10 @@ +import { Option } from './Option'; + +export interface QuizQuestion { + questionId: number; + question: string; + options: Option[]; + answer: string; + explanation: string; + selectedOption: string; +} diff --git a/quiz/src/assets/DjbUpOnTheScoreboard.ttf b/quiz/src/assets/DjbUpOnTheScoreboard.ttf new file mode 100644 index 000000000..ded21514c Binary files /dev/null and b/quiz/src/assets/DjbUpOnTheScoreboard.ttf differ diff --git a/quiz/src/assets/alarm-clock.ttf b/quiz/src/assets/alarm-clock.ttf new file mode 100644 index 000000000..9e9b59345 Binary files /dev/null and b/quiz/src/assets/alarm-clock.ttf differ diff --git a/quiz/src/assets/images/angular.png b/quiz/src/assets/images/angular.png new file mode 100644 index 000000000..6c115fba8 Binary files /dev/null and b/quiz/src/assets/images/angular.png differ diff --git a/quiz/src/assets/images/angular.svg b/quiz/src/assets/images/angular.svg new file mode 100644 index 000000000..bf081acb1 --- /dev/null +++ b/quiz/src/assets/images/angular.svg @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/quiz/src/assets/images/congratulations.jpg b/quiz/src/assets/images/congratulations.jpg new file mode 100644 index 000000000..06bcb7b38 Binary files /dev/null and b/quiz/src/assets/images/congratulations.jpg differ diff --git a/quiz/src/assets/images/dependency-injection-diagram.png b/quiz/src/assets/images/dependency-injection-diagram.png new file mode 100644 index 000000000..8aab83cbc Binary files /dev/null and b/quiz/src/assets/images/dependency-injection-diagram.png differ diff --git a/quiz/src/assets/images/email.svg b/quiz/src/assets/images/email.svg new file mode 100644 index 000000000..77c5db3ca --- /dev/null +++ b/quiz/src/assets/images/email.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/quiz/src/assets/images/facebook.gif b/quiz/src/assets/images/facebook.gif new file mode 100644 index 000000000..cb02b6d11 Binary files /dev/null and b/quiz/src/assets/images/facebook.gif differ diff --git a/quiz/src/assets/images/headgears.png b/quiz/src/assets/images/headgears.png new file mode 100644 index 000000000..a0b92b19d Binary files /dev/null and b/quiz/src/assets/images/headgears.png differ diff --git a/quiz/src/assets/images/ng-trophy.png b/quiz/src/assets/images/ng-trophy.png new file mode 100644 index 000000000..282d7c434 Binary files /dev/null and b/quiz/src/assets/images/ng-trophy.png differ diff --git a/quiz/src/assets/images/not-bad.jpg b/quiz/src/assets/images/not-bad.jpg new file mode 100644 index 000000000..e8a2f9f31 Binary files /dev/null and b/quiz/src/assets/images/not-bad.jpg differ diff --git a/quiz/src/assets/images/try-again.jpeg b/quiz/src/assets/images/try-again.jpeg new file mode 100644 index 000000000..9b6fdaa1e Binary files /dev/null and b/quiz/src/assets/images/try-again.jpeg differ diff --git a/quiz/src/assets/images/twitter.svg b/quiz/src/assets/images/twitter.svg new file mode 100644 index 000000000..a4ed81154 --- /dev/null +++ b/quiz/src/assets/images/twitter.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/quiz/src/environments/environment.prod.ts b/quiz/src/environments/environment.prod.ts new file mode 100644 index 000000000..3612073bc --- /dev/null +++ b/quiz/src/environments/environment.prod.ts @@ -0,0 +1,3 @@ +export const environment = { + production: true +}; diff --git a/quiz/src/environments/environment.ts b/quiz/src/environments/environment.ts new file mode 100644 index 000000000..7b4f817ad --- /dev/null +++ b/quiz/src/environments/environment.ts @@ -0,0 +1,16 @@ +// 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. diff --git a/quiz/src/favicon.ico b/quiz/src/favicon.ico new file mode 100644 index 000000000..317ebcb23 Binary files /dev/null and b/quiz/src/favicon.ico differ diff --git a/quiz/src/index.html b/quiz/src/index.html new file mode 100644 index 000000000..f266651f1 --- /dev/null +++ b/quiz/src/index.html @@ -0,0 +1,17 @@ + + + + + Angular/TypeScript Codelab Quiz + + + + + + + + + + + + diff --git a/quiz/src/main.ts b/quiz/src/main.ts new file mode 100644 index 000000000..fa4e0aef3 --- /dev/null +++ b/quiz/src/main.ts @@ -0,0 +1,13 @@ +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)); diff --git a/quiz/src/polyfills.ts b/quiz/src/polyfills.ts new file mode 100644 index 000000000..2f258e56c --- /dev/null +++ b/quiz/src/polyfills.ts @@ -0,0 +1,62 @@ +/** + * 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 + */ diff --git a/quiz/src/styles.scss b/quiz/src/styles.scss new file mode 100644 index 000000000..f2a223ac2 --- /dev/null +++ b/quiz/src/styles.scss @@ -0,0 +1,56 @@ +@import "../../../../node_modules/@angular/material/prebuilt-themes/indigo-pink.css"; +$font-stack: Space Mono, monospace; +$font-weight-max: 900; + +mat-card { + margin: 0 auto; + margin-bottom: 20px; + margin-top: 5%; + margin-left: 25%; + width: 42rem; + height: inherit; + padding: 20px; + display: flex; + flex-direction: column; + justify-content: center; + position: relative; + border: 1px solid black; + border-radius: 10px !important; +} +mat-card:hover { + box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2) !important; + transition: 0.3s !important; +} + +mat-card-header { + text-align: center; + display: flex; + justify-content: center; + + .header-image { + background-image: url('assets/images/angular.png'); + background-size: cover; + margin-left: -10px; + margin-top: -10px; + height: 100px !important; + width: 100px !important; + } + + mat-card-title { + font-family: $font-stack; + font-weight: $font-weight-max; + font-size: 30px !important; + margin: -10px 0 10px 10px; + color: #007aff; + text-align: center; + } + mat-card-subtitle { + font-family: $font-stack; + font-weight: $font-weight-max; + font-size: 17.5px !important; + font-style: italic; + color: #808080; + text-align: center; + } +} + diff --git a/quiz/src/test-setup.ts b/quiz/src/test-setup.ts new file mode 100644 index 000000000..8d88704e8 --- /dev/null +++ b/quiz/src/test-setup.ts @@ -0,0 +1 @@ +import 'jest-preset-angular'; diff --git a/quiz/tsconfig.app.json b/quiz/tsconfig.app.json new file mode 100644 index 000000000..8925f33e8 --- /dev/null +++ b/quiz/tsconfig.app.json @@ -0,0 +1,10 @@ +{ + "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"] +} diff --git a/quiz/tsconfig.json b/quiz/tsconfig.json new file mode 100644 index 000000000..08c7db8c9 --- /dev/null +++ b/quiz/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tsconfig.json", + "compilerOptions": { + "types": ["node", "jest"] + }, + "include": ["**/*.ts"] +} diff --git a/quiz/tsconfig.spec.json b/quiz/tsconfig.spec.json new file mode 100644 index 000000000..fd405a65e --- /dev/null +++ b/quiz/tsconfig.spec.json @@ -0,0 +1,10 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "../../../dist/out-tsc", + "module": "commonjs", + "types": ["jest", "node"] + }, + "files": ["src/test-setup.ts"], + "include": ["**/*.spec.ts", "**/*.d.ts"] +} diff --git a/quiz/tslint.json b/quiz/tslint.json new file mode 100644 index 000000000..b6ad5c3a5 --- /dev/null +++ b/quiz/tslint.json @@ -0,0 +1,7 @@ +{ + "extends": "../../../tslint.json", + "rules": { + "directive-selector": [true, "attribute", "codelab", "camelCase"], + "component-selector": [true, "element", "codelab", "kebab-case"] + } +}