diff --git a/apps/playground/assets/DjbUpOnTheScoreboard.ttf b/apps/playground/assets/DjbUpOnTheScoreboard.ttf
new file mode 100644
index 000000000..ded21514c
Binary files /dev/null and b/apps/playground/assets/DjbUpOnTheScoreboard.ttf differ
diff --git a/apps/playground/assets/alarm-clock.ttf b/apps/playground/assets/alarm-clock.ttf
new file mode 100644
index 000000000..9e9b59345
Binary files /dev/null and b/apps/playground/assets/alarm-clock.ttf differ
diff --git a/apps/playground/assets/images/DIDiagram.png b/apps/playground/assets/images/DIDiagram.png
new file mode 100644
index 000000000..8aab83cbc
Binary files /dev/null and b/apps/playground/assets/images/DIDiagram.png differ
diff --git a/apps/playground/assets/images/angular.png b/apps/playground/assets/images/angular.png
new file mode 100644
index 000000000..6c115fba8
Binary files /dev/null and b/apps/playground/assets/images/angular.png differ
diff --git a/apps/playground/assets/images/angular.svg b/apps/playground/assets/images/angular.svg
new file mode 100644
index 000000000..bf081acb1
--- /dev/null
+++ b/apps/playground/assets/images/angular.svg
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/playground/assets/images/congratulations.jpg b/apps/playground/assets/images/congratulations.jpg
new file mode 100644
index 000000000..06bcb7b38
Binary files /dev/null and b/apps/playground/assets/images/congratulations.jpg differ
diff --git a/apps/playground/assets/images/email.svg b/apps/playground/assets/images/email.svg
new file mode 100644
index 000000000..77c5db3ca
--- /dev/null
+++ b/apps/playground/assets/images/email.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
diff --git a/apps/playground/assets/images/facebook.gif b/apps/playground/assets/images/facebook.gif
new file mode 100644
index 000000000..cb02b6d11
Binary files /dev/null and b/apps/playground/assets/images/facebook.gif differ
diff --git a/apps/playground/assets/images/headgears.png b/apps/playground/assets/images/headgears.png
new file mode 100644
index 000000000..a0b92b19d
Binary files /dev/null and b/apps/playground/assets/images/headgears.png differ
diff --git a/apps/playground/assets/images/ngtrophy.png b/apps/playground/assets/images/ngtrophy.png
new file mode 100644
index 000000000..282d7c434
Binary files /dev/null and b/apps/playground/assets/images/ngtrophy.png differ
diff --git a/apps/playground/assets/images/notbad.jpg b/apps/playground/assets/images/notbad.jpg
new file mode 100644
index 000000000..e8a2f9f31
Binary files /dev/null and b/apps/playground/assets/images/notbad.jpg differ
diff --git a/apps/playground/assets/images/tryagain.jpeg b/apps/playground/assets/images/tryagain.jpeg
new file mode 100644
index 000000000..9b6fdaa1e
Binary files /dev/null and b/apps/playground/assets/images/tryagain.jpeg differ
diff --git a/apps/playground/assets/images/twitter.svg b/apps/playground/assets/images/twitter.svg
new file mode 100644
index 000000000..a4ed81154
--- /dev/null
+++ b/apps/playground/assets/images/twitter.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/playground/src/app/app.component.html b/apps/playground/src/app/app.component.html
index 0680b43f9..6c46b1de8 100644
--- a/apps/playground/src/app/app.component.html
+++ b/apps/playground/src/app/app.component.html
@@ -1 +1 @@
-
+
diff --git a/apps/playground/src/app/app.component.scss b/apps/playground/src/app/app.component.scss
index bd8c23046..5401b8eb8 100644
--- a/apps/playground/src/app/app.component.scss
+++ b/apps/playground/src/app/app.component.scss
@@ -1,133 +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;
-}
+/*
+ * 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/src/app/app.component.spec.ts b/apps/playground/src/app/app.component.spec.ts
index 45840ff39..c31a71a50 100644
--- a/apps/playground/src/app/app.component.spec.ts
+++ b/apps/playground/src/app/app.component.spec.ts
@@ -1,33 +1,33 @@
-import { async, TestBed } from '@angular/core/testing';
-import { AppComponent } from './app.component';
-import { RouterTestingModule } from '@angular/router/testing';
-import { AppModule } from './app.module';
-
-describe('AppComponent', () => {
- beforeEach(async(() => {
- TestBed.configureTestingModule({
- imports: [RouterTestingModule, AppModule]
- }).compileComponents();
- }));
-
- it('should create the app', () => {
- const fixture = TestBed.createComponent(AppComponent);
- const app = fixture.debugElement.componentInstance;
- expect(app).toBeTruthy();
- });
-
- it(`should have as title 'playground'`, () => {
- const fixture = TestBed.createComponent(AppComponent);
- const app = fixture.debugElement.componentInstance;
- expect(app.title).toEqual('playground');
- });
-
- it('should render title', () => {
- const fixture = TestBed.createComponent(AppComponent);
- fixture.detectChanges();
- const compiled = fixture.debugElement.nativeElement;
- expect(compiled.querySelector('h1').textContent).toContain(
- 'Welcome to playground!'
- );
- });
-});
+import { async, TestBed } from '@angular/core/testing';
+import { AppComponent } from './app.component';
+import { RouterTestingModule } from '@angular/router/testing';
+import { AppModule } from './app.module';
+
+describe('AppComponent', () => {
+ beforeEach(async(() => {
+ TestBed.configureTestingModule({
+ imports: [RouterTestingModule, AppModule]
+ }).compileComponents();
+ }));
+
+ it('should create the app', () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ const app = fixture.debugElement.componentInstance;
+ expect(app).toBeTruthy();
+ });
+
+ it(`should have as title 'playground'`, () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ const app = fixture.debugElement.componentInstance;
+ expect(app.title).toEqual('playground');
+ });
+
+ it('should render title', () => {
+ const fixture = TestBed.createComponent(AppComponent);
+ fixture.detectChanges();
+ const compiled = fixture.debugElement.nativeElement;
+ expect(compiled.querySelector('h1').textContent).toContain(
+ 'Welcome to playground!'
+ );
+ });
+});
diff --git a/apps/playground/src/app/app.component.ts b/apps/playground/src/app/app.component.ts
index a5db690f0..394aaf7af 100644
--- a/apps/playground/src/app/app.component.ts
+++ b/apps/playground/src/app/app.component.ts
@@ -1,10 +1,10 @@
-import { Component } from '@angular/core';
-
-@Component({
- selector: 'codelab-root',
- templateUrl: './app.component.html',
- styleUrls: ['./app.component.scss']
-})
-export class AppComponent {
- title = 'playground';
-}
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'codelab-root',
+ templateUrl: './app.component.html',
+ styleUrls: ['./app.component.scss']
+})
+export class AppComponent {
+ title = 'playground';
+}
diff --git a/apps/playground/src/app/app.module.ts b/apps/playground/src/app/app.module.ts
index ab8fda53c..ded9fbd03 100644
--- a/apps/playground/src/app/app.module.ts
+++ b/apps/playground/src/app/app.module.ts
@@ -1,54 +1,59 @@
-import { BrowserModule } from '@angular/platform-browser';
-import { APP_INITIALIZER, NgModule } from '@angular/core';
-
-import { AppComponent } from './app.component';
-import { RouterModule } from '@angular/router';
-import { monacoReady } from '@codelab/code-demos';
-import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
-import { environment } from '../environments/environment';
-import { AngularFireModule } from '@angular/fire';
-import { AngularFireDatabaseModule } from '@angular/fire/database';
-import { AngularFireAuthModule } from '@angular/fire/auth';
-
-const routes = [
- {
- path: '',
- redirectTo: 'code-sync',
- pathMatch: 'full'
- },
- {
- path: 'angular',
- loadChildren: () =>
- import('./playground/playground.module').then(m => m.PlaygroundModule)
- },
- {
- path: 'code-sync',
- loadChildren: () =>
- import('./code-sync/code-sync.module').then(m => m.CodeSyncModule)
- }
-];
-
-export const AngularFireApp = AngularFireModule.initializeApp(
- environment.firebaseConfig
-);
-
-@NgModule({
- declarations: [AppComponent],
- imports: [
- BrowserModule,
- BrowserAnimationsModule,
- RouterModule.forRoot(routes, { initialNavigation: 'enabled' }),
- AngularFireApp,
- AngularFireDatabaseModule,
- AngularFireAuthModule
- ],
- providers: [
- {
- provide: APP_INITIALIZER,
- useValue: monacoReady,
- multi: true
- }
- ],
- bootstrap: [AppComponent]
-})
-export class AppModule {}
+import { BrowserModule } from '@angular/platform-browser';
+import { APP_INITIALIZER, NgModule } from '@angular/core';
+
+import { AppComponent } from './app.component';
+import { RouterModule } from '@angular/router';
+import { monacoReady } from '@codelab/code-demos';
+import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
+import { environment } from '../environments/environment';
+import { AngularFireModule } from '@angular/fire';
+import { AngularFireDatabaseModule } from '@angular/fire/database';
+import { AngularFireAuthModule } from '@angular/fire/auth';
+
+const routes = [
+ {
+ path: '',
+ redirectTo: 'code-sync',
+ pathMatch: 'full'
+ },
+ {
+ path: 'angular',
+ loadChildren: () =>
+ import('./playground/playground.module').then(m => m.PlaygroundModule)
+ },
+ {
+ path: 'code-sync',
+ loadChildren: () =>
+ import('./code-sync/code-sync.module').then(m => m.CodeSyncModule)
+ },
+ {
+ path: 'quiz',
+ loadChildren: () =>
+ import('./quiz/app.module').then(m => m.AppModule)
+ }
+];
+
+export const AngularFireApp = AngularFireModule.initializeApp(
+ environment.firebaseConfig
+);
+
+@NgModule({
+ declarations: [AppComponent],
+ imports: [
+ BrowserModule,
+ BrowserAnimationsModule,
+ RouterModule.forRoot(routes, { initialNavigation: 'enabled' }),
+ AngularFireApp,
+ AngularFireDatabaseModule,
+ AngularFireAuthModule
+ ],
+ providers: [
+ {
+ provide: APP_INITIALIZER,
+ useValue: monacoReady,
+ multi: true
+ }
+ ],
+ bootstrap: [AppComponent]
+})
+export class AppModule {}
diff --git a/apps/playground/src/app/quiz/app-routing.module.ts b/apps/playground/src/app/quiz/app-routing.module.ts
new file mode 100644
index 000000000..e9fb4e4f6
--- /dev/null
+++ b/apps/playground/src/app/quiz/app-routing.module.ts
@@ -0,0 +1,19 @@
+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 },
+ { path: 'question', component: QuestionComponent },
+ { path: 'question/:questionId', component: QuestionComponent },
+ { path: 'results', component: ResultsComponent },
+ { path: '', redirectTo: 'intro' }
+];
+
+@NgModule({
+ imports: [RouterModule.forChild(routes)],
+ exports: [RouterModule]
+})
+export class AppRoutingModule { }
diff --git a/apps/playground/src/app/quiz/app.component.html b/apps/playground/src/app/quiz/app.component.html
new file mode 100644
index 000000000..6c46b1de8
--- /dev/null
+++ b/apps/playground/src/app/quiz/app.component.html
@@ -0,0 +1 @@
+
diff --git a/apps/playground/src/app/quiz/app.component.scss b/apps/playground/src/app/quiz/app.component.scss
new file mode 100644
index 000000000..5401b8eb8
--- /dev/null
+++ b/apps/playground/src/app/quiz/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/src/app/quiz/app.component.spec.ts b/apps/playground/src/app/quiz/app.component.spec.ts
new file mode 100644
index 000000000..d93819206
--- /dev/null
+++ b/apps/playground/src/app/quiz/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/src/app/quiz/app.component.ts b/apps/playground/src/app/quiz/app.component.ts
new file mode 100644
index 000000000..7f4f16ed5
--- /dev/null
+++ b/apps/playground/src/app/quiz/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/src/app/quiz/app.module.ts b/apps/playground/src/app/quiz/app.module.ts
new file mode 100644
index 000000000..e0e34cf39
--- /dev/null
+++ b/apps/playground/src/app/quiz/app.module.ts
@@ -0,0 +1,45 @@
+import { NgModule, NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
+import { CommonModule } from "@angular/common";
+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: [
+ CommonModule,
+ 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/src/app/quiz/browserslist b/apps/playground/src/app/quiz/browserslist
new file mode 100644
index 000000000..80848532e
--- /dev/null
+++ b/apps/playground/src/app/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/src/app/quiz/components/question/question.component.html b/apps/playground/src/app/quiz/components/question/question.component.html
new file mode 100644
index 000000000..02ee38246
--- /dev/null
+++ b/apps/playground/src/app/quiz/components/question/question.component.html
@@ -0,0 +1,30 @@
+
+
+
diff --git a/apps/playground/src/app/quiz/components/question/question.component.scss b/apps/playground/src/app/quiz/components/question/question.component.scss
new file mode 100644
index 000000000..32e48c72e
--- /dev/null
+++ b/apps/playground/src/app/quiz/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;
+}
+
+.is-correct {
+ background-color: #00c853 !important;
+}
+.is-incorrect {
+ background-color: #ff0000 !important;
+}
+
+.feedback-icon {
+ position: absolute;
+ right: 0;
+ margin-right: 40px;
+ margin-top: -25px;
+}
+
+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;
+}
diff --git a/apps/playground/src/app/quiz/components/question/question.component.ts b/apps/playground/src/app/quiz/components/question/question.component.ts
new file mode 100644
index 000000000..ddbd1ee5f
--- /dev/null
+++ b/apps/playground/src/app/quiz/components/question/question.component.ts
@@ -0,0 +1,63 @@
+import { Component, OnInit, OnChanges, SimpleChanges, Input, Output, EventEmitter } from '@angular/core';
+import { FormGroup, FormControl, 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;
+ option = '';
+ grayBorder = '2px solid #979797';
+
+ constructor() {}
+
+ ngOnInit() {
+ this.buildForm();
+ }
+
+ ngOnChanges(changes: SimpleChanges) {
+ if (changes.question && changes.question.currentValue && !changes.question.firstChange) {
+ this.formGroup.patchValue({answer: ''});
+ }
+ }
+
+ buildForm() {
+ this.formGroup = new FormGroup({
+ answer: new FormControl(['', Validators.required])
+ });
+ }
+
+ radioChange(answer: string) {
+ this.question.selectedOption = answer;
+ this.answer.emit(answer);
+ this.displayExplanation();
+ }
+
+ displayExplanation(): void {
+ const questionElem = document.getElementById('question');
+ if (questionElem !== null) {
+ questionElem.innerHTML = 'Option ' + this.question.answer + ' was correct because ' + this.question.explanation + '.';
+ questionElem.style.border = this.grayBorder;
+ }
+ }
+
+ // mark the correct answer regardless of which option is selected once answered
+ isCorrect(option: string): boolean {
+ return this.question.selectedOption && option === this.question.answer;
+ }
+
+ // mark incorrect answer if selected
+ isIncorrect(option: string): boolean {
+ return option !== this.question.answer && option === this.question.selectedOption;
+ }
+
+ onSubmit() {
+ this.formGroup.reset({answer: null});
+ }
+}
diff --git a/apps/playground/src/app/quiz/containers/introduction/introduction.component.html b/apps/playground/src/app/quiz/containers/introduction/introduction.component.html
new file mode 100644
index 000000000..4a00535d8
--- /dev/null
+++ b/apps/playground/src/app/quiz/containers/introduction/introduction.component.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+ Dependency Injection Quiz
+
+ How well do you know Dependency Injection?
+ Take the quiz and find out!
+
+
+
+
+
+
+
+ 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!
+
+
+ Start the Quiz!
+
+
diff --git a/apps/playground/src/app/quiz/containers/introduction/introduction.component.scss b/apps/playground/src/app/quiz/containers/introduction/introduction.component.scss
new file mode 100644
index 000000000..9a3e778d9
--- /dev/null
+++ b/apps/playground/src/app/quiz/containers/introduction/introduction.component.scss
@@ -0,0 +1,43 @@
+mat-card-header {
+ display: block;
+
+ .header-image {
+ margin-left: 15px;
+ }
+
+ .mat-card-container {
+ float: right;
+ margin-top: 0;
+ margin-right: 40px;
+
+ .subtitle2 {
+ display: block;
+ }
+ }
+
+ .quiz-topic-img {
+ display: flex;
+ justify-content: center;
+ background: url('../../../../../assets/images/DIDiagram.png') no-repeat center center;
+ background-size: 100% !important;
+ margin: 90px -10px 0 5px;
+ height: 300px !important;
+ width: 100% !important;
+ }
+}
+
+mat-card-content p {
+ text-align: justify;
+ margin-top: -25px;
+}
+
+mat-card-actions {
+ text-align: center;
+
+ button {
+ margin-bottom: 10px !important;
+ }
+ button:hover {
+ border: 1px solid #007aff;
+ }
+}
diff --git a/apps/playground/src/app/quiz/containers/introduction/introduction.component.ts b/apps/playground/src/app/quiz/containers/introduction/introduction.component.ts
new file mode 100644
index 000000000..4e0066666
--- /dev/null
+++ b/apps/playground/src/app/quiz/containers/introduction/introduction.component.ts
@@ -0,0 +1,16 @@
+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 {
+
+ constructor(private router: Router) {}
+
+ startQuiz() {
+ this.router.navigateByUrl('/quiz/question/1');
+ }
+}
diff --git a/apps/playground/src/app/quiz/containers/question/question.component.html b/apps/playground/src/app/quiz/containers/question/question.component.html
new file mode 100644
index 000000000..dcb41c6a1
--- /dev/null
+++ b/apps/playground/src/app/quiz/containers/question/question.component.html
@@ -0,0 +1,59 @@
+
+
+
+ Dependency Injection Quiz
+
+ Assess your knowledge of Dependency Injection (DI )
+
+
+
+
+
+
+
+ Score
+
+ {{ correctAnswersCount }}/{{ totalQuestions }}
+
+
+
+ Question {{ question.questionId }} of {{ totalQuestions }}
+
+
+ Time
+
+ 0:0 {{ timeLeft }}
+
+
+
+
+
+ {{ question.questionText }}
+
+
+
+
+
+
+
+
+ Next »
+
+
+
+
+ Show Your Score
+
+
+
+
+
+
+ = 0 && progressValue <= 100) &&
+ question && question.questionId <= totalQuestions"
+ type="success" [striped]="true" [animated]="true" [value]="progressValue">
+ {{ progressValue.toFixed(0) }}%
+
+
+
+
diff --git a/apps/playground/src/app/quiz/containers/question/question.component.scss b/apps/playground/src/app/quiz/containers/question/question.component.scss
new file mode 100644
index 000000000..79b9d25b1
--- /dev/null
+++ b/apps/playground/src/app/quiz/containers/question/question.component.scss
@@ -0,0 +1,106 @@
+$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#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;
+
+ .nextQuestionNav {
+ float: right;
+ margin-right: -0.4rem;
+ }
+
+ .showScoreNav {
+ width: 150px;
+ text-align: center;
+ margin: 0 auto;
+ }
+
+ .nextQuestionNav:hover, .showScoreNav:hover {
+ border: 1px solid #007aff;
+ }
+
+ .showScoreNav:hover {
+ width: 141.5px !important;
+ }
+ }
+}
+
+section.progress-bar {
+ margin: 40px 0 10px 1.5rem;
+ width: 39rem;
+ height: auto;
+
+ ngb-progressbar {
+ border-radius: 10px;
+ }
+}
diff --git a/apps/playground/src/app/quiz/containers/question/question.component.ts b/apps/playground/src/app/quiz/containers/question/question.component.ts
new file mode 100644
index 000000000..12e631128
--- /dev/null
+++ b/apps/playground/src/app/quiz/containers/question/question.component.ts
@@ -0,0 +1,279 @@
+import { Component, OnInit, Input, Output } from '@angular/core';
+import { ActivatedRoute, NavigationExtras, 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() answer: string;
+ @Input() formGroup: FormGroup;
+ @Output() question: QuizQuestion;
+ totalQuestions: number;
+ percentage: number;
+ completionTime: number;
+ correctAnswersCount = 0;
+
+ questionID = 0;
+ currentQuestion = 0;
+ questionIndex: number;
+ correctAnswer: boolean;
+ hasAnswer: boolean;
+ disabled: boolean;
+ quizIsOver: boolean;
+ progressValue: number;
+ timeLeft: number;
+ timePerQuestion = 20;
+ interval: any;
+ elapsedTime: number;
+ elapsedTimes = [];
+ blueBorder = '2px solid #007aff';
+
+ allQuestions: QuizQuestion[] = [
+ {
+ questionId: 1,
+ questionText: '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,
+ questionText: '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,
+ questionText: '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().' },
+ { optionValue: '4', optionText: 'Declare an object.' }
+ ],
+ answer: '3',
+ explanation: 'the first step is marking the class as @Injectable()',
+ selectedOption: ''
+ },
+ {
+ questionId: 4,
+ questionText: '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 in Angular',
+ selectedOption: ''
+ }
+ ];
+
+ constructor(private route: ActivatedRoute, private router: Router) {
+ this.route.paramMap.subscribe(params => {
+ this.setQuestionID(+params.get('questionId')); // get the question ID and store it
+ 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();
+ }
+
+ displayNextQuestion() {
+ this.resetTimer();
+ this.increaseProgressValue();
+
+ this.questionIndex = this.questionID++;
+
+ if (typeof document.getElementById('question') !== 'undefined') {
+ document.getElementById('question').innerHTML = this.allQuestions[this.questionIndex]['questionText'];
+ document.getElementById('question').style.border = this.blueBorder;
+ }
+ }
+
+ navigateToNextQuestion(): void {
+ if (this.question.questionId < this.totalQuestions) {
+ // this.currentQuestion++;
+ this.router.navigate(['/quiz/question', this.getQuestionID() + 1]);
+ this.displayNextQuestion();
+ } else {
+ this.navigateToResults();
+ }
+ }
+
+ navigateToResults(): void {
+ const navigationExtras: NavigationExtras = {
+ queryParams: {
+ totalQuestions: this.totalQuestions,
+ correctAnswersCount: this.correctAnswersCount,
+ percentage: this.percentage,
+ completionTime: this.completionTime,
+ allQuestions: JSON.stringify(this.allQuestions)
+ }
+ };
+ this.router.navigate(['/quiz/results'], navigationExtras);
+ }
+
+ // checks whether the question is a valid question and is answered correctly
+ checkIfAnsweredCorrectly() {
+ if (this.isThereAnotherQuestion() && this.question.selectedOption === this.question.answer) {
+ this.correctAnswer = true;
+ this.hasAnswer = true;
+ this.incrementCorrectAnswersCount();
+ this.disabled = false;
+
+ this.elapsedTime = Math.floor(this.timePerQuestion - this.timeLeft);
+ if (this.correctAnswersCount <= this.totalQuestions) {
+ this.elapsedTimes.push(this.elapsedTime);
+ } else {
+ this.elapsedTimes.push(0);
+ this.elapsedTime = 0;
+ this.completionTime = this.calculateTotalElapsedTime(this.elapsedTimes);
+ }
+
+ this.quizDelay(3000);
+ this.navigateToNextQuestion();
+ }
+ }
+
+ // increase the correct answer count when the correct answer is selected
+ incrementCorrectAnswersCount() {
+ if (this.questionID <= this.totalQuestions) {
+ if (this.question && this.question.selectedOption === this.question.answer) {
+ if (this.correctAnswersCount === this.totalQuestions) {
+ return this.correctAnswersCount;
+ } else {
+ this.correctAnswer = true;
+ this.hasAnswer = true;
+ return this.correctAnswersCount++;
+ }
+ } else {
+ this.correctAnswer = false;
+ this.hasAnswer = false;
+ }
+ }
+ }
+
+ // increase the progress value when the user presses the next button
+ increaseProgressValue() {
+ this.progressValue = 100 * (this.getQuestionID() + 1) / this.totalQuestions;
+ }
+
+ // determine the percentage from amount of correct answers given and the total number of questions
+ calculateQuizPercentage() {
+ if (this.question.questionId < this.totalQuestions && this.correctAnswersCount === this.totalQuestions) {
+ this.percentage = 100;
+ } else {
+ this.percentage = 100 * this.correctAnswersCount / this.totalQuestions;
+ }
+ return this.percentage;
+ }
+
+ calculateTotalElapsedTime(elapsedTimes) {
+ if (this.question.questionId < this.totalQuestions) {
+ this.completionTime = elapsedTimes.reduce((acc, cur) => acc + cur, 0);
+ return this.completionTime;
+ }
+ }
+
+ /**************** 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
+ private countDown() {
+ if (this.questionID <= this.totalQuestions) {
+ this.interval = setInterval(() => {
+ if (this.timeLeft > 0) {
+ this.timeLeft--;
+
+ this.checkIfAnsweredCorrectly();
+
+ if (this.correctAnswersCount <= this.totalQuestions) {
+ this.calculateQuizPercentage();
+ this.calculateTotalElapsedTime(this.elapsedTimes);
+ }
+
+ // check if timer is expired and if the question is less than the last question
+ if (this.timeLeft === 0 && this.question && this.currentQuestion < this.totalQuestions) {
+ this.navigateToNextQuestion();
+ }
+
+ // check if timer is expired and if the question is the last question
+ if (this.timeLeft === 0 && this.question && this.currentQuestion === this.totalQuestions) {
+ this.elapsedTime = 0;
+ // this.completionTime = this.calculateTotalElapsedTime(this.elapsedTimes);
+ this.navigateToResults();
+ }
+
+ // check if last question has been answered
+ if (this.question && this.currentQuestion === this.totalQuestions && this.hasAnswer === true) {
+ this.elapsedTime = 0;
+ // this.completionTime = this.calculateTotalElapsedTime(this.elapsedTimes);
+ this.navigateToResults();
+ this.quizIsOver = true;
+ }
+
+ // disable the next button until an option has been selected
+ if (typeof this.question !== 'undefined') {
+ this.question.selectedOption === '' ? this.disabled = true : this.disabled = false;
+ }
+ }
+ }, 1000);
+ }
+ }
+
+ private resetTimer() {
+ this.timeLeft = this.timePerQuestion;
+ }
+
+ 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/src/app/quiz/containers/results/results.component.html b/apps/playground/src/app/quiz/containers/results/results.component.html
new file mode 100644
index 000000000..7e8e727d6
--- /dev/null
+++ b/apps/playground/src/app/quiz/containers/results/results.component.html
@@ -0,0 +1,90 @@
+
+
+
+ Dependency Injection Quiz
+ Results
+
+
+
+
+
+ Statistics
+ You scored {{ correctAnswersCount }} out of {{ totalQuestions }} questions correctly.
+ You completed the quiz in {{ elapsedMinutes }} minutes and {{ elapsedSeconds }} seconds.
+
+
+
+
= 60 && percentage < 80">
+
+
Not bad!
+
+
+
+
You scored {{ percentage }}% correctly (and quickly)!
+
1">You scored {{ percentage }}% correct.
+
+
+
+
+
+ View a more detailed summary of your quiz
+
+
+
+
+ Question #{{ question.questionId }}: {{ question.questionText }}
+
+
+
+
+ Your Answer:
+ Option {{ question.selectedOption }} — {{ question.options[question.selectedOption - 1].optionText }}
+ done
+ clear
+ (no answer provided)
+
+
+
+
+ Correct Answer:
+ Option {{ question.answer }} — {{ question.options[question.answer - 1].optionText }}
+
+
+
+
+ Explanation:
+ Option {{ question.answer }} was correct because {{ question.explanation }}.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Challenge your friends!
+
+
+
+
diff --git a/apps/playground/src/app/quiz/containers/results/results.component.scss b/apps/playground/src/app/quiz/containers/results/results.component.scss
new file mode 100644
index 000000000..6f758b8e6
--- /dev/null
+++ b/apps/playground/src/app/quiz/containers/results/results.component.scss
@@ -0,0 +1,196 @@
+$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;
+
+ .great-job {
+ background: url('../../../../../../playground/assets/images/ngtrophy.jpg') no-repeat center center;
+ }
+ .not-bad {
+ background: url('../../../../../../playground/assets/images/notbad.jpg') no-repeat center center;
+ }
+ .try-again {
+ background: url('../../../../../../playground/assets/images/tryagain.jpeg') no-repeat center center;
+ }
+ .great-job, .not-bad, .try-again {
+ display: inline-block !important;
+ background-size: 100% !important;
+ border: 0 !important;
+ margin: -15px 0 0 -5px;
+ height: 160px !important;
+ width: 160px !important;
+ }
+ .great-job h3, .not-bad h3, .try-again h3 {
+ margin-top: -40px !important;
+ }
+ }
+ }
+
+ 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: #0f0900;
+ background-color: #f5f5f5;
+ 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;
+
+ .social-buttons {
+ display: block;
+
+ 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.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/apps/playground/src/app/quiz/containers/results/results.component.ts b/apps/playground/src/app/quiz/containers/results/results.component.ts
new file mode 100644
index 000000000..73467460a
--- /dev/null
+++ b/apps/playground/src/app/quiz/containers/results/results.component.ts
@@ -0,0 +1,37 @@
+import { Component, OnInit, Input } from '@angular/core';
+import { ActivatedRoute, Params } from '@angular/router';
+
+import { QuizQuestion } from '../../model/QuizQuestion';
+
+@Component({
+ selector: 'codelab-quiz-results',
+ templateUrl: './results.component.html',
+ styleUrls: ['./results.component.scss']
+})
+export class ResultsComponent implements OnInit {
+ @Input() answer: string;
+ @Input() question: QuizQuestion;
+ allQuestions: QuizQuestion[];
+ totalQuestions: number;
+ correctAnswersCount: number;
+ percentage: number;
+ completionTime: number;
+ elapsedMinutes: number;
+ elapsedSeconds: number;
+ codelabUrl = 'https://www.codelab.fun';
+
+ constructor(private route: ActivatedRoute) {
+ this.route.queryParams.subscribe((params: Params) => {
+ this.totalQuestions = +params['totalQuestions'];
+ this.correctAnswersCount = +params['correctAnswersCount'];
+ this.percentage = +params['percentage'];
+ this.completionTime = +params['completionTime'];
+ this.allQuestions = JSON.parse(params['allQuestions']);
+ });
+ }
+
+ ngOnInit() {
+ this.elapsedMinutes = Math.floor(this.completionTime / 60);
+ this.elapsedSeconds = this.completionTime % 60;
+ }
+}
diff --git a/apps/playground/src/app/quiz/jest.config.js b/apps/playground/src/app/quiz/jest.config.js
new file mode 100644
index 000000000..12b435256
--- /dev/null
+++ b/apps/playground/src/app/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/src/app/quiz/model/Option.ts b/apps/playground/src/app/quiz/model/Option.ts
new file mode 100644
index 000000000..46f75a2aa
--- /dev/null
+++ b/apps/playground/src/app/quiz/model/Option.ts
@@ -0,0 +1,4 @@
+export interface Option {
+ optionValue: string;
+ optionText: string;
+}
diff --git a/apps/playground/src/app/quiz/model/QuizQuestion.ts b/apps/playground/src/app/quiz/model/QuizQuestion.ts
new file mode 100644
index 000000000..8a345d612
--- /dev/null
+++ b/apps/playground/src/app/quiz/model/QuizQuestion.ts
@@ -0,0 +1,10 @@
+import { Option } from './Option';
+
+export interface QuizQuestion {
+ questionId: number;
+ questionText: string;
+ options: Option[];
+ answer: string;
+ explanation: string;
+ selectedOption: string;
+}
diff --git a/apps/playground/src/app/quiz/tsconfig.app.json b/apps/playground/src/app/quiz/tsconfig.app.json
new file mode 100644
index 000000000..8925f33e8
--- /dev/null
+++ b/apps/playground/src/app/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/src/app/quiz/tsconfig.json b/apps/playground/src/app/quiz/tsconfig.json
new file mode 100644
index 000000000..08c7db8c9
--- /dev/null
+++ b/apps/playground/src/app/quiz/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../../tsconfig.json",
+ "compilerOptions": {
+ "types": ["node", "jest"]
+ },
+ "include": ["**/*.ts"]
+}
diff --git a/apps/playground/src/app/quiz/tsconfig.spec.json b/apps/playground/src/app/quiz/tsconfig.spec.json
new file mode 100644
index 000000000..fd405a65e
--- /dev/null
+++ b/apps/playground/src/app/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/src/app/quiz/tslint.json b/apps/playground/src/app/quiz/tslint.json
new file mode 100644
index 000000000..b6ad5c3a5
--- /dev/null
+++ b/apps/playground/src/app/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/apps/playground/src/styles.scss b/apps/playground/src/styles.scss
index 524dd1092..3e051aed5 100644
--- a/apps/playground/src/styles.scss
+++ b/apps/playground/src/styles.scss
@@ -1 +1,53 @@
-@import '~@angular/material/prebuilt-themes/indigo-pink.css';
+@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: 5% 0 20px 25% !important;
+ width: 42rem;
+ height: inherit;
+ padding: 20px;
+ display: flex;
+ justify-content: center;
+ flex-direction: column;
+ 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 {
+ float: left;
+ background: url('../assets/images/angular.png') no-repeat center center;
+ background-size: cover;
+ margin: -10px 0 0 -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;
+ }
+}