Add serverside rendering

parent 642b2be3
......@@ -10,10 +10,18 @@
"prefix": "app",
"schematics": {},
"architect": {
"server": {
"builder": "@angular-devkit/build-angular:server",
"options": {
"outputPath": "dist/server",
"main": "src/main.server.ts",
"tsConfig": "src/tsconfig.server.json"
}
},
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist",
"outputPath": "dist/browser",
"index": "src/index.html",
"main": "src/main.ts",
"tsConfig": "src/tsconfig.app.json",
......
This diff is collapsed.
......@@ -9,18 +9,22 @@
"description": "Version 2 of arsnova.click (Frontend WebApp)",
"scripts": {
"ng": "ng",
"start:SSR": "node dist/server",
"start:DEV": "ng serve --host 0.0.0.0 --port 4200 --disable-host-check --aot",
"build:DEV": "ng serve --host 0.0.0.0 --port 4200 --disable-host-check --aot --prod",
"build:SSR": "npm run build:PROD && npm run build:SERVER && npm run webpack:SERVER",
"build:SERVER": "ng run frontend:server",
"build:PROD": "ng build --prod",
"build:PROD-STATS": "ng build --prod --stats-json",
"bundle-report": "webpack-bundle-analyzer dist/stats.json",
"bundle-report": "webpack-bundle-analyzer dist/browser/stats.json",
"webpack:SERVER": "webpack --config webpack.server.config.js --progress --colors",
"test": "ng test --browsers=ChromeHeadless --watch=false",
"lint": "ng lint",
"pree2e": "webdriver-manager update --standalone false --gecko false",
"e2e": "ng e2e --no-webdriver-update",
"precompress": "node purifycss.js",
"compress": "gzip dist/** -r",
"http-startup": "http-server dist/ -p 4711 --gzip",
"compress": "gzip dist/browser/** -r",
"http-startup": "http-server dist/browser/ -p 4711 --gzip",
"prod-test": "npm run build:PROD && npm run compress && npm run http-startup"
},
"private": true,
......@@ -33,14 +37,18 @@
"@angular/http": "^6.0.2",
"@angular/platform-browser": "^6.0.2",
"@angular/platform-browser-dynamic": "^6.0.2",
"@angular/platform-server": "^6.0.2",
"@angular/router": "^6.0.2",
"@angular/service-worker": "^6.0.2",
"@ng-bootstrap/ng-bootstrap": "^2.0.0-alpha.0",
"@ng-bootstrap/schematics": "^2.0.0-alpha.1",
"@nguniversal/express-engine": "^6.0.0",
"@nguniversal/module-map-ngfactory-loader": "^6.0.0",
"@ngx-translate/core": "^10.0.1",
"@ngx-translate/http-loader": "~3.0.1",
"@techiediaries/ngx-qrcode": "0.0.5",
"angulartics2": "^6.1.0",
"bootstrap": "^4.1.1",
"classlist.js": "^1.1.20150312",
"highlight.js": "^9.12.0",
"intro.js": "^2.9.3",
......@@ -49,8 +57,8 @@
"ngx-qrcode2": "^0.1.0",
"ngx-translate-messageformat-compiler": "^4.1.1",
"rxjs": "^6.1.0",
"zone.js": "^0.8.26",
"bootstrap": "^4.1.1"
"ts-loader": "^4.3.0",
"zone.js": "^0.8.26"
},
"devDependencies": {
"@angular-devkit/build-angular": "^0.6.3",
......@@ -79,7 +87,8 @@
"purify-css": "~1.2.5",
"ts-node": "~6.0.3",
"tslint": "~5.10.0",
"typescript": "^2.7"
"typescript": "^2.7",
"webpack-cli": "^2.1.3"
},
"keywords": [
"arsnova",
......
......@@ -22,8 +22,8 @@ function fromDir(startPath, filter, callback) {
}
}
fromDir('./dist', /\.css/, function (filename) {
const content = ['./dist/*.js', './dist/*.html'];
fromDir('./dist/browser', /\.css/, function (filename) {
const content = ['./dist/browser/*.js', './dist/browser/*.html'];
const css = [filename];
const options = {
......
// These are important and needed before anything else
import 'zone.js/dist/zone-node';
import 'reflect-metadata';
import { enableProdMode } from '@angular/core';
import * as express from 'express';
import { join } from 'path';
// Express Engine
import { ngExpressEngine } from '@nguniversal/express-engine';
// Import module map for lazy loading
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();
// Express server
const app = express();
const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist');
// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const { RootServerModuleNgFactory, LAZY_MODULE_MAP } = require('./dist/server/main');
app.engine('html', ngExpressEngine({
bootstrap: RootServerModuleNgFactory,
providers: [
provideModuleMap(LAZY_MODULE_MAP)
]
}));
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));
// TODO: implement data requests securely
app.get('/api/*', (req, res) => {
res.status(404).send('data requests are not supported');
});
// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));
// All regular routes use the Universal engine
app.get('*', (req, res) => {
res.render('index', { req });
});
// Start up the Node server
app.listen(PORT, () => {
console.log(`Node server listening on http://localhost:${PORT}`);
});
import {Component, Input, OnDestroy, OnInit} from '@angular/core';
import {Component, Inject, Input, OnDestroy, OnInit, PLATFORM_ID} from '@angular/core';
import {FooterbarElement, FooterBarService} from '../../service/footer-bar.service';
import {Router} from '@angular/router';
import {Subscription} from 'rxjs';
import {FileUploadService} from '../../service/file-upload.service';
import {CurrentQuizService} from '../../service/current-quiz.service';
import {TrackingService} from '../../service/tracking.service';
import {isPlatformBrowser} from '@angular/common';
@Component({
selector: 'app-footer-bar',
......@@ -44,6 +45,7 @@ export class FooterBarComponent implements OnInit, OnDestroy {
private _hasRightScrollElement = false;
constructor(
@Inject(PLATFORM_ID) private platformId: Object,
private router: Router,
private footerBarService: FooterBarService,
private currentQuizService: CurrentQuizService,
......@@ -54,12 +56,14 @@ export class FooterBarComponent implements OnInit, OnDestroy {
ngOnInit() {
this._routerSubscription = this.router.events.subscribe((val) => {
const navbarFooter = document.getElementById('navbar-footer-container');
this.footerElemIndex = 1;
if (navbarFooter) {
navbarFooter.scrollLeft = 0;
this.hideElement('left');
if (isPlatformBrowser(this.platformId)) {
const navbarFooter = document.getElementById('navbar-footer-container');
if (navbarFooter) {
navbarFooter.scrollLeft = 0;
this.hideElement('left');
}
}
this.footerElemIndex = 1;
if (val.hasOwnProperty('url')) {
this.footerBarService.footerElemTheme.linkTarget = val['url'].indexOf('lobby') > -1 ? '/quiz/flow/theme' : '/themes';
}
......@@ -124,32 +128,40 @@ export class FooterBarComponent implements OnInit, OnDestroy {
break;
}
return document.getElementsByClassName('footer-bar-wrapper').item(0).getElementsByClassName(className).item(0);
if (isPlatformBrowser(this.platformId)) {
return document.getElementsByClassName('footer-bar-wrapper').item(0).getElementsByClassName(className).item(0);
}
return null;
}
public moveLeft(): void {
const navbarFooter = document.getElementById('navbar-footer-container');
const right = navbarFooter.children.item(--this.footerElemIndex).getBoundingClientRect().right;
const firstChild = navbarFooter.children.item(1);
if (isPlatformBrowser(this.platformId)) {
const navbarFooter = document.getElementById('navbar-footer-container');
const right = navbarFooter.children.item(--this.footerElemIndex).getBoundingClientRect().right;
const firstChild = navbarFooter.children.item(1);
navbarFooter.scrollLeft += right;
this.hasRightScrollElement = true;
navbarFooter.scrollLeft += right;
this.hasRightScrollElement = true;
if (firstChild.getBoundingClientRect().right >= 0) {
this.hideElement('left');
if (firstChild.getBoundingClientRect().right >= 0) {
this.hideElement('left');
}
}
}
public moveRight(): void {
const navbarFooter = document.getElementById('navbar-footer-container');
const right = navbarFooter.children.item(++this.footerElemIndex).getBoundingClientRect().right;
const lastChild = navbarFooter.children.item(navbarFooter.children.length - 1);
if (isPlatformBrowser(this.platformId)) {
const navbarFooter = document.getElementById('navbar-footer-container');
const right = navbarFooter.children.item(++this.footerElemIndex).getBoundingClientRect().right;
const lastChild = navbarFooter.children.item(navbarFooter.children.length - 1);
navbarFooter.scrollLeft += right;
this.showElement('left');
navbarFooter.scrollLeft += right;
this.showElement('left');
if (navbarFooter.scrollLeft >= lastChild.getBoundingClientRect().left) {
this.hasRightScrollElement = false;
if (navbarFooter.scrollLeft >= lastChild.getBoundingClientRect().left) {
this.hasRightScrollElement = false;
}
}
}
}
import {Component, OnInit} from '@angular/core';
import {Component, Inject, OnInit, PLATFORM_ID} from '@angular/core';
import {Router} from '@angular/router';
import {NgbModal} from '@ng-bootstrap/ng-bootstrap';
import {ConnectionService} from '../../service/connection.service';
import {HeaderLabelService} from '../../service/header-label.service';
import {TrackingService} from '../../service/tracking.service';
import {isPlatformBrowser} from '@angular/common';
function isLocalStorageSupported(): boolean {
try {
......@@ -61,7 +62,7 @@ export class HeaderComponent implements OnInit {
return this._inHomeRoute;
}
private _origin: string = location.hostname;
private _origin: string = isPlatformBrowser(this.platformId) ? location.hostname : '';
private _inHomeRoute: boolean;
private _localStorageAvailable: boolean = isLocalStorageSupported();
private _sessionStorageAvailable: boolean = isSessionStorageSupported();
......@@ -80,6 +81,7 @@ export class HeaderComponent implements OnInit {
}
constructor(
@Inject(PLATFORM_ID) private platformId: Object,
private router: Router,
private modalService: NgbModal,
public headerLabelService: HeaderLabelService,
......
import {Component, OnInit} from '@angular/core';
import {Component, Inject, OnInit, PLATFORM_ID} from '@angular/core';
import {NgbActiveModal} from '@ng-bootstrap/ng-bootstrap';
import {ActiveQuestionGroupService} from '../../service/active-question-group.service';
import {questionGroupReflection} from 'arsnova-click-v2-types/src/questions/questionGroup_reflection';
......@@ -38,6 +38,7 @@ export class AvailableQuizzesComponent implements OnInit, IModal {
}
constructor(
@Inject(PLATFORM_ID) private platformId: Object,
private activeModal: NgbActiveModal,
private http: HttpClient,
private router: Router,
......
import {Component, OnDestroy, OnInit} from '@angular/core';
import {Component, Inject, OnDestroy, OnInit, PLATFORM_ID} from '@angular/core';
import {DefaultSettings} from '../../../../lib/default.settings';
import {IMessage} from 'arsnova-click-v2-types/src/common';
import {CurrentQuizService} from '../../../service/current-quiz.service';
......@@ -24,6 +24,7 @@ export class ConfidenceRateComponent implements OnInit, OnDestroy {
private _confidenceValue = 100;
constructor(
@Inject(PLATFORM_ID) private platformId: Object,
private connectionService: ConnectionService,
private attendeeService: AttendeeService,
private http: HttpClient,
......
import {Component, OnDestroy, OnInit, SecurityContext} from '@angular/core';
import {Component, Inject, OnDestroy, OnInit, PLATFORM_ID, SecurityContext} from '@angular/core';
import {FooterBarService} from '../../../service/footer-bar.service';
import {HeaderLabelService} from '../../../service/header-label.service';
import {ThemesService} from '../../../service/themes.service';
......@@ -15,6 +15,7 @@ import {IMessage, INickname} from 'arsnova-click-v2-types/src/common';
import {questionGroupReflection} from 'arsnova-click-v2-types/src/questions/questionGroup_reflection';
import {parseGithubFlavoredMarkdown} from '../../../../lib/markdown/markdown';
import {TrackingService} from '../../../service/tracking.service';
import {isPlatformBrowser} from '@angular/common';
@Component({
selector: 'app-quiz-lobby',
......@@ -46,6 +47,7 @@ export class QuizLobbyComponent implements OnInit, OnDestroy {
private _kickMemberModalRef: NgbActiveModal;
constructor(
@Inject(PLATFORM_ID) private platformId: Object,
public currentQuizService: CurrentQuizService,
public attendeeService: AttendeeService,
private footerBarService: FooterBarService,
......@@ -79,7 +81,9 @@ export class QuizLobbyComponent implements OnInit, OnDestroy {
this.footerBarService.footerElemResponseProgress,
this.footerBarService.footerElemConfidenceSlider,
]);
this.qrCodeContent = `${document.location.origin}/quiz/${encodeURIComponent(this.currentQuizService.quiz.hashtag.toLowerCase())}`;
if (isPlatformBrowser(this.platformId)) {
this.qrCodeContent = `${document.location.origin}/quiz/${encodeURIComponent(this.currentQuizService.quiz.hashtag.toLowerCase())}`;
}
this.footerBarService.footerElemStartQuiz.onClickCallback = () => {
if (!this.attendeeService.attendees.length) {
return;
......@@ -94,15 +98,19 @@ export class QuizLobbyComponent implements OnInit, OnDestroy {
});
};
this.footerBarService.footerElemQRCode.onClickCallback = () => {
const classList = document.getElementsByClassName('qr-code-element').item(0).classList;
classList.toggle('d-none');
classList.toggle('d-flex');
if (isPlatformBrowser(this.platformId)) {
const classList = document.getElementsByClassName('qr-code-element').item(0).classList;
classList.toggle('d-none');
classList.toggle('d-flex');
}
};
this.footerBarService.footerElemEditQuiz.onClickCallback = () => {
if (currentQuizService.quiz) {
const questionGroupSerialized = JSON.parse(window.localStorage.getItem(currentQuizService.quiz.hashtag));
this.activeQuestionGroupService.activeQuestionGroup =
questionGroupReflection[questionGroupSerialized.TYPE](questionGroupSerialized);
if (isPlatformBrowser(this.platformId)) {
const questionGroupSerialized = JSON.parse(window.localStorage.getItem(currentQuizService.quiz.hashtag));
this.activeQuestionGroupService.activeQuestionGroup =
questionGroupReflection[questionGroupSerialized.TYPE](questionGroupSerialized);
}
currentQuizService.cleanUp();
attendeeService.cleanUp();
connectionService.cleanUp();
......@@ -180,9 +188,11 @@ export class QuizLobbyComponent implements OnInit, OnDestroy {
this.router.navigate(['/quiz', 'flow', 'reading-confirmation']);
break;
case 'MEMBER:REMOVED':
const existingNickname = window.sessionStorage.getItem(`config.nick`);
if (existingNickname === data.payload.name) {
this.router.navigate(['/']);
if (isPlatformBrowser(this.platformId)) {
const existingNickname = window.sessionStorage.getItem(`config.nick`);
if (existingNickname === data.payload.name) {
this.router.navigate(['/']);
}
}
break;
case 'LOBBY:CLOSED':
......
import {Component, OnDestroy, OnInit} from '@angular/core';
import {Component, Inject, OnDestroy, OnInit, PLATFORM_ID} from '@angular/core';
import {FooterBarService} from '../../../service/footer-bar.service';
import {ThemesService} from '../../../service/themes.service';
import {CurrentQuizService} from '../../../service/current-quiz.service';
import {isPlatformBrowser} from '@angular/common';
@Component({
selector: 'app-quiz-theme',
......@@ -14,6 +15,7 @@ export class QuizThemeComponent implements OnInit, OnDestroy {
private previewThemeBackup: string;
constructor(
@Inject(PLATFORM_ID) private platformId: Object,
private footerBarService: FooterBarService,
private currentQuizService: CurrentQuizService,
private themesService: ThemesService
......@@ -23,7 +25,9 @@ export class QuizThemeComponent implements OnInit, OnDestroy {
footerBarService.replaceFooterElements([
this.footerBarService.footerElemBack
]);
this.previewThemeBackup = document.getElementsByTagName('html').item(0).dataset['theme'];
if (isPlatformBrowser(this.platformId)) {
this.previewThemeBackup = document.getElementsByTagName('html').item(0).dataset['theme'];
}
}
ngOnInit() {
......@@ -35,26 +39,31 @@ export class QuizThemeComponent implements OnInit, OnDestroy {
}
updateTheme(id: string) {
document.getElementsByTagName('html').item(0).dataset['theme'] = id;
this.previewThemeBackup = document.getElementsByTagName('html').item(0).dataset['theme'];
if (isPlatformBrowser(this.platformId)) {
document.getElementsByTagName('html').item(0).dataset['theme'] = id;
this.previewThemeBackup = document.getElementsByTagName('html').item(0).dataset['theme'];
}
this.currentQuizService.quiz.sessionConfig.theme = id;
this.currentQuizService.toggleSettingByName('theme', id);
}
previewTheme(id) {
document.getElementsByTagName('html').item(0).dataset['theme'] = id;
if (isPlatformBrowser(this.platformId)) {
document.getElementsByTagName('html').item(0).dataset['theme'] = id;
}
}
restoreTheme() {
const themeDataset = document.getElementsByTagName('html').item(0).dataset['theme'];
if (isPlatformBrowser(this.platformId)) {
const themeDataset = document.getElementsByTagName('html').item(0).dataset['theme'];
document.getElementsByTagName('html').item(0).dataset['theme'] = this.previewThemeBackup;
document.getElementsByTagName('html').item(0).dataset['theme'] = this.previewThemeBackup;
if (themeDataset === this.previewThemeBackup) {
return;
if (themeDataset === this.previewThemeBackup) {
return;
}
}
this.themesService.reloadLinkNodes(this.previewThemeBackup);
}
......
import {Component, OnDestroy, OnInit} from '@angular/core';
import {Component, Inject, OnDestroy, OnInit, PLATFORM_ID} from '@angular/core';
import {ConnectionService} from '../../../service/connection.service';
import {IMessage} from 'arsnova-click-v2-types/src/common';
import {DefaultSettings} from '../../../../lib/default.settings';
......@@ -23,6 +23,7 @@ export class ReadingConfirmationComponent implements OnInit, OnDestroy {
public questionText: string;
constructor(
@Inject(PLATFORM_ID) private platformId: Object,
private connectionService: ConnectionService,
private attendeeService: AttendeeService,
private router: Router,
......
import {Component, OnDestroy, OnInit} from '@angular/core';
import {Component, Inject, OnDestroy, OnInit, PLATFORM_ID} from '@angular/core';
import {Subscription} from 'rxjs';
import {ActivatedRoute, Router} from '@angular/router';
import {HttpClient} from '@angular/common/http';
......@@ -20,6 +20,7 @@ export class QuizJoinComponent implements OnInit, OnDestroy {
private _routerSubscription: Subscription;
constructor(
@Inject(PLATFORM_ID) private platformId: Object,
private http: HttpClient,
private route: ActivatedRoute,
private router: Router,
......
import {Component, OnDestroy, OnInit} from '@angular/core';
import {Component, Inject, OnDestroy, OnInit, PLATFORM_ID} from '@angular/core';
import {FooterBarService} from '../../../../service/footer-bar.service';
import {QuestionTextService} from '../../../../service/question-text.service';
import {Subscription} from 'rxjs';
......@@ -6,6 +6,7 @@ import {ActivatedRoute} from '@angular/router';
import {ActiveQuestionGroupService} from '../../../../service/active-question-group.service';
import {DEVICE_TYPES, LIVE_PREVIEW_ENVIRONMENT} from '../../../../../environments/environment';
import {HeaderLabelService} from '../../../../service/header-label.service';
import {isPlatformBrowser} from '@angular/common';
@Component({
selector: 'app-questiontext',
......@@ -23,6 +24,7 @@ export class QuestiontextComponent implements OnInit, OnDestroy {
private _routerSubscription: Subscription;
constructor(
@Inject(PLATFORM_ID) private platformId: Object,
private headerLabelService: HeaderLabelService,
private activeQuestionGroupService: ActiveQuestionGroupService,
private footerBarService: FooterBarService,
......@@ -147,9 +149,11 @@ export class QuestiontextComponent implements OnInit, OnDestroy {
}
computeQuestionTextInputHeight() {
const questionTextElem = <HTMLInputElement>document.getElementById('questionText');
const lineHeight = window.getComputedStyle(questionTextElem).getPropertyValue('line-height').replace('px', '');
questionTextElem.style.height = (parseInt(lineHeight, 10)) * (questionTextElem.value.split('\n').length + 2) + 'px';
if (isPlatformBrowser(this.platformId)) {
const questionTextElem = <HTMLInputElement>document.getElementById('questionText');
const lineHeight = window.getComputedStyle(questionTextElem).getPropertyValue('line-height').replace('px', '');
questionTextElem.style.height = (parseInt(lineHeight, 10)) * (questionTextElem.value.split('\n').length + 2) + 'px';
}
}
fireEvent(event) {
......@@ -163,9 +167,11 @@ export class QuestiontextComponent implements OnInit, OnDestroy {
this._routerSubscription = this.route.params.subscribe(params => {
this._questionIndex = +params['questionIndex'];
});
this.questionTextElement = <HTMLTextAreaElement>document.getElementById('questionText');
this.questionTextElement.value = this.activeQuestionGroupService.activeQuestionGroup.questionList[this._questionIndex].questionText;
this.questionTextService.change(this.activeQuestionGroupService.activeQuestionGroup.questionList[this._questionIndex].questionText);
if (isPlatformBrowser(this.platformId)) {
this.questionTextElement = <HTMLTextAreaElement>document.getElementById('questionText');
this.questionTextElement.value = this.activeQuestionGroupService.activeQuestionGroup.questionList[this._questionIndex].questionText;
this.questionTextService.change(this.activeQuestionGroupService.activeQuestionGroup.questionList[this._questionIndex].questionText);
}
this.computeQuestionTextInputHeight();
}
......
import {Component, OnDestroy, OnInit} from '@angular/core';
import {Component, Inject, OnDestroy, OnInit, PLATFORM_ID} from '@angular/core';
import {FooterBarService} from '../../../service/footer-bar.service';
import {TranslateService} from '@ngx-translate/core';
import {IMusicSessionConfiguration} from 'arsnova-click-v2-types/src/session_configuration/interfaces';
import {DefaultSettings} from '../../../../lib/default.settings';
import {ISong} from 'arsnova-click-v2-types/src/common';
import {CurrentQuizService} from '../../../service/current-quiz.service';
import {isPlatformBrowser} from '@angular/common';
@Component({
selector: 'app-sound-manager',
......@@ -38,9 +39,11 @@ export class SoundManagerComponent implements OnInit, OnDestroy {
private _currentlyPlayedMusic = null;
constructor(
@Inject(PLATFORM_ID) private platformId: Object,
private translateService: TranslateService,
private footerBarService: FooterBarService,
private currentQuizService: CurrentQuizService) {
private currentQuizService: CurrentQuizService,
) {
this.footerBarService.TYPE_REFERENCE = SoundManagerComponent.TYPE;
footerBarService.replaceFooterElements([
......@@ -79,9 +82,11 @@ export class SoundManagerComponent implements OnInit, OnDestroy {
toggleMusicPreview(target: 'lobby' | 'countdownRunning' | 'countdownEnd') {
this._currentlyPlayedMusic = `${DefaultSettings.httpApiEndpoint}/files/sound/${target}/${this.config.titleConfig[target]}.mp3`;
const audioElements = document.getElementsByTagName('audio');
for (let i = 0; i < audioElements.length; i++) {
(<HTMLAudioElement>audioElements.item(i)).volume = (this.config.volumeConfig[target] || 60) / 100;
if (isPlatformBrowser(this.platformId)) {
const audioElements = document.getElementsByTagName('audio');
for (let i = 0; i < audioElements.length; i++) {
(<HTMLAudioElement>audioElements.item(i)).volume = (this.config.volumeConfig[target] || 60) / 100;
}
}
}
......@@ -91,11 +96,13 @@ export class SoundManagerComponent implements OnInit, OnDestroy {
openTab(id: string): void {
// TODO: Workaround because Bootstrap v4 Beta carousel not working! (02.10.2017)
const tabs = document.getElementsByClassName('collapse');
for (let i = 0; i < tabs.length; i++) {
tabs.item(i).classList.remove('show');
if (isPlatformBrowser(this.platformId)) {
const tabs = document.getElementsByClassName('collapse');
for (let i = 0; i < tabs.length; i++) {
tabs.item(i).classList.remove('show');
}
document.getElementById(id).classList.add('show');
}
document.getElementById(id).classList.add('show');
this.toggleMusicPreview(<'lobby' | 'countdownRunning' | 'countdownEnd'>id.replace('panel-', ''));
}
......
import {Component, OnInit} from '@angular/core';
import {Component, Inject, OnInit, PLATFORM_ID} from '@angular/core';
import {FooterBarService} from '../../service/footer-bar.service';
import {HeaderLabelService} from '../../service/header-label.service';
import {ActiveQuestionGroupService} from '../../service/active-question-group.service';
......@@ -9,6 +9,7 @@ import {DefaultSettings} from '../../../lib/default.settings';
import {IMessage} from 'arsnova-click-v2-types/src/common';
import {CurrentQuizService} from '../../service/current-quiz.service';
import {TrackingService} from '../../service/tracking.service';
import {isPlatformBrowser} from '@angular/common';
@Component({
selector: 'app-quiz-overview',
......@@ -25,13 +26,14 @@ export class QuizOverviewComponent implements OnInit {
private readonly _sessions: Array<string> = [];
constructor(
@Inject(PLATFORM_ID) private platformId: Object,
private footerBarService: FooterBarService,
private http: HttpClient,
private headerLabelService: HeaderLabelService,
private currentQuizService: CurrentQuizService,
private activeQuestionGroupService: ActiveQuestionGroupService,
private router: Router,
private trackingService: TrackingService
private trackingService: TrackingService,
) {
this.footerBarService.TYPE_REFERENCE = QuizOverviewComponent.TYPE;
......@@ -44,91 +46,105 @@ export class QuizOverviewComponent implements OnInit {
this.footerBarService.footerElemImport,
]);
headerLabelService.headerLabel = 'component.hashtag_management.session_management';
this._sessions = JSON.parse(window.localStorage.getItem('config.owned_quizzes')) || [];
if (isPlatformBrowser(this.platformId)) {
this._sessions = JSON.parse(window.localStorage.getItem('config.owned_quizzes')) || [];
}
}
ngOnInit() {
}
isValid(session: string): boolean {
const questionGroupSerialized = JSON.parse(window.localStorage.getItem(session));
return questionGroupReflection[questionGroupSerialized.TYPE](questionGroupSerialized).isValid();
if (isPlatformBrowser(this.platformId)) {
const questionGroupSerialized = JSON.parse(window.localStorage.getItem(session));
return questionGroupReflection[questionGroupSerialized.TYPE](questionGroupSerialized).isValid();
}
}
startQuiz(sessionName: string): void {
const sessionSerialized = JSON.parse(window.localStorage.getItem(sessionName));
const session = new questionGroupReflection[sessionSerialized.TYPE](sessionSerialized);
if (isPlatformBrowser(this.platformId)) {
const sessionSerialized = JSON.parse(window.localStorage.getItem(sessionName));
const session = new questionGroupReflection[sessionSerialized.TYPE](sessionSerialized);
this.trackingService.trackClickEvent({
action: QuizOverviewComponent.TYPE,
label: `start-quiz`,
});
new Promise((resolve, reject) => {
this.http.get(`${DefaultSettings.httpApiEndpoint}/quiz/status/${session.hashtag}`).subscribe((data: IMessage) => {
if (data.status === 'STATUS:SUCCESSFUL') {
if (data.step === 'QUIZ:UNDEFINED') {
this.http.post(`${DefaultSettings.httpApiEndpoint}/quiz/reserve/override`, {
quizName: session.hashtag,
privateKey: window.localStorage.getItem('config.private_key')
}).subscribe((reserveResponse: IMessage) => {
if (reserveResponse.status === 'STATUS:SUCCESSFUL') {
resolve();
this.trackingService.trackClickEvent({
action: QuizOverviewComponent.TYPE,
label: `start-quiz`,
});
const run = async () => {
await new Promise((resolve, reject) => {
this.http.get(`${DefaultSettings.httpApiEndpoint}/quiz/status/${session.hashtag}`).subscribe((data: IMessage) => {