Moves the images and their generation to the frontend node server

parent 267cf108
Pipeline #16187 failed with stages
in 2 minutes and 20 seconds
......@@ -29,10 +29,12 @@
"assets": [
"src/assets/fonts",
"src/assets/i18n",
"src/assets/icons",
"src/assets/images",
"src/assets/styles",
"src/assets/mathjax",
"src/assets/serverEndpoint.json"
"src/assets/jobs",
"src/assets/serverEndpoint.json",
"src/assets/imageDerivates.json",
"src/assets/themeData.json"
],
"styles": [
"src/test.scss",
......@@ -96,10 +98,12 @@
"assets": [
"src/assets/fonts",
"src/assets/i18n",
"src/assets/icons",
"src/assets/images",
"src/assets/styles",
"src/assets/mathjax",
"src/assets/serverEndpoint.json"
"src/assets/jobs",
"src/assets/serverEndpoint.json",
"src/assets/imageDerivates.json",
"src/assets/themeData.json"
]
}
},
......
This source diff could not be displayed because it is too large. You can view the blob instead.
......@@ -26,14 +26,17 @@
"compress": "gzip dist/browser/** -r",
"http-startup": "http-server dist/browser/ -p 4711 --gzip",
"prod-test": "npm run build:PROD && npm run purify && npm run compress && npm run http-startup",
"prod-test:SSR": "npm run build:SSR && npm run start:SSR"
"prod-test:SSR": "npm run build:SSR && npm run start:SSR",
"job:images:logo": "cd dist/jobs/; node --experimental-modules GenerateImages.mjs --command=generateLogoImages",
"job:images:frontend": "cd dist/jobs/; node --experimental-modules GenerateImages.mjs --command=generateFrontendPreview",
"job:images": "cd dist/jobs/; node --experimental-modules GenerateImages.mjs --command=all"
},
"private": true,
"dependencies": {
"@angular/animations": "^6.0.2",
"@angular/common": "^6.0.2",
"@angular/compiler": "^6.0.2",
"@angular/core": "^6.0.2",
"@angular/compiler": "^6.0.2",
"@angular/animations": "^6.0.2",
"@angular/forms": "^6.0.2",
"@angular/http": "^6.0.2",
"@angular/platform-browser": "^6.0.2",
......@@ -63,9 +66,14 @@
"cors": "^2.8.4"
},
"devDependencies": {
"minimist": "^1.2.0",
"@angular/compiler-cli": "^6.0.2",
"@angular-devkit/build-angular": "^0.6.3",
"tsickle": ">=0.25.5",
"tslib": "^1.7.1",
"typescript": "^2.7",
"chrome-remote-interface": "^0.25.6",
"@angular/cli": "^6.0.3",
"@angular/compiler-cli": "^6.0.2",
"@angular/language-service": "^6.0.2",
"@types/jasmine": "~2.8.7",
"@types/jasminewd2": "~2.0.3",
......@@ -83,11 +91,13 @@
"karma-jasmine-html-reporter": "^1.1.0",
"karma-mocha-reporter": "^2.2.5",
"karma-phantomjs-launcher": "^1.0.4",
"gm": "^1.23.1",
"imagemin": "^5.3.1",
"imagemin-pngquant": "^5.1.0",
"protractor": "^5.3.2",
"purify-css": "~1.2.5",
"ts-node": "~6.0.3",
"tslint": "~5.10.0",
"typescript": "^2.7",
"webpack-cli": "^2.1.4"
},
"keywords": [
......
......@@ -4,14 +4,14 @@ import { ngExpressEngine } from '@nguniversal/express-engine';
// Import module map for lazy loading
import { provideModuleMap } from '@nguniversal/module-map-ngfactory-loader';
import * as bodyParser from 'body-parser';
import { spawnSync } from 'child_process';
import * as child_process from 'child_process';
import * as compress from 'compression';
import * as cors from 'cors';
import * as express from 'express';
import * as fs from 'fs';
import * as path from 'path';
import { join } from 'path';
import 'reflect-metadata';
// These are important and needed before anything else
import 'zone.js/dist/zone-node';
......@@ -22,13 +22,15 @@ enableProdMode();
// Express server
const app = express();
const PORT = process.env.PORT || 4000;
const DIST_FOLDER = join(process.cwd(), 'dist');
const DIST_FOLDER = path.join(process.cwd());
const JOBS_FOLDER = path.join(DIST_FOLDER, 'browser', 'assets', 'jobs');
const corsOptions = require('./cors.config.ts');
const cache = { 'arsnova-click-v2-frontend': {} };
//const cache = { 'arsnova-click-v2-frontend': {} };
const cache = {};
const availableLangs = ['en', 'de', 'fr', 'es', 'it'];
const projectGitLocation = {
'arsnova-click-v2-frontend': path.join(__dirname, 'browser'),
'arsnova-click-v2-frontend': path.join(DIST_FOLDER, 'browser'),
};
const projectBaseLocation = {
'arsnova-click-v2-frontend': path.join(projectGitLocation['arsnova-click-v2-frontend']),
......@@ -186,7 +188,7 @@ const getUnusedKeys = (req) => {
const getBranch = (req) => {
const command = `git branch 2> /dev/null | sed -e '/^[^*]/d' -e "s/* \\(.*\\)/\\1/"`;
const child = spawnSync('/bin/sh', [`-c`, command], { cwd: req.projectGitLocation });
const child = child_process.spawnSync('/bin/sh', [`-c`, command], { cwd: req.projectGitLocation });
return child.stdout.toString().replace('\n', '');
};
......@@ -198,7 +200,7 @@ app.engine('html', ngExpressEngine({
}));
app.set('view engine', 'html');
app.set('views', join(DIST_FOLDER, 'browser'));
app.set('views', path.join(DIST_FOLDER, 'browser'));
// TODO: implement data requests securely
app.get('/api/v1/plugin/i18nator/:project/langFile', async (req, res) => {
......@@ -261,17 +263,14 @@ app.get('/api/*', (req, res) => {
});
// Server static files from /browser
app.get('*.*', express.static(join(DIST_FOLDER, 'browser')));
app.get('*.*', express.static(path.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}`);
const buildCache = () => {
Object.keys(cache).forEach(projectName => {
console.log(``);
console.log(`------- Building cache for '${projectName}' -------`);
......@@ -312,4 +311,34 @@ app.listen(PORT, () => {
});
console.log(``);
console.log(`Cache built successfully`);
};
const buildImages = () => {
console.log(``);
console.log(`------- Building preview screenshots and logo derivates -------`);
const params = [
'--experimental-modules',
'GenerateImages.mjs',
'--command=all',
`--host=http://localhost:${PORT}`,
];
const instance = child_process.spawn(`node`, params, { cwd: JOBS_FOLDER });
instance.stdout.on('data', (data) => {
console.log(`GenerateImages::all (stdout): ${data.toString().replace('\n', '')}`);
});
instance.stderr.on('data', (data) => {
console.log(`GenerateImages::all (stderr): ${data.toString().replace('\n', '')}`);
});
instance.on('exit', () => {
console.log(``);
console.log(`GenerateImages::all (exit): Preview screenshots and logo derivates built successfully`);
});
};
// Start up the Node server
app.listen(PORT, () => {
console.log(`Node server listening on http://localhost:${PORT}`);
buildCache();
buildImages();
});
import { isPlatformBrowser } from '@angular/common';
import { isPlatformBrowser, isPlatformServer } from '@angular/common';
import { Component, Inject, OnInit, PLATFORM_ID } from '@angular/core';
import { DomSanitizer, SafeStyle } from '@angular/platform-browser';
import { Router } from '@angular/router';
......@@ -133,6 +133,10 @@ export class HeaderComponent implements OnInit {
}
private sanitizeStyle(value: string): SafeStyle {
if (isPlatformServer(this.platformId)) {
return value;
}
value = value.replace(/\s/g, '');
return this.sanitizer.bypassSecurityTrustStyle(`${value}`);
}
......
......@@ -2,7 +2,7 @@
<div *ngIf="targetEnvironment === ENVIRONMENT_TYPE.ANSWEROPTIONS"
id="answer-option-preview"
[class]="'preview-frame overflow-auto relative ' + deviceClass()">
<img src="/assets/icons/phone_empty.png"
<img src="/assets/images/phone_empty.png"
class="w-100 position-absolute"
draggable="false"/>
<div class="px-4 preview-content relative overflow-auto">
......@@ -28,7 +28,7 @@
<div *ngIf="targetEnvironment === ENVIRONMENT_TYPE.QUESTION"
id="question-preview"
[class]="'preview-frame overflow-auto relative ' + deviceClass()">
<img src="/assets/icons/phone_empty.png"
<img src="/assets/images/phone_empty.png"
class="w-100 position-absolute"
draggable="false"/>
<div class="px-4 preview-content relative overflow-auto">
......
......@@ -11,14 +11,14 @@
</button>
</div>
<div class="modal-body">
<div class="d-flex">
<div *ngFor="let elem of sessions"
class="text-light d-inline mr-2 px-4 py-2 rounded pointer available-quiz"
[class.bg-success]="elem.isValid()"
[class.bg-danger]="!elem.isValid()"
(click)="elem.isValid() ? startQuiz(elem) : editQuiz(elem)">
<div class="d-flex flex-wrap">
<span *ngFor="let elem of sessions"
class="text-light text-nowrap d-inline mr-2 my-1 px-4 py-2 rounded pointer available-quiz"
[class.bg-success]="elem.isValid()"
[class.bg-danger]="!elem.isValid()"
(click)="elem.isValid() ? startQuiz(elem) : editQuiz(elem)">
{{elem.hashtag}}
</div>
</span>
</div>
</div>
<div class="modal-footer">
......
......@@ -9,6 +9,7 @@ import { SingleChoiceQuestion } from 'arsnova-click-v2-types/src/questions/quest
import { DefaultQuestionGroup } from 'arsnova-click-v2-types/src/questions/questiongroup_default';
import { SessionConfiguration } from 'arsnova-click-v2-types/src/session_configuration/session_config';
import { TranslateMessageFormatCompiler } from 'ngx-translate-messageformat-compiler';
import { of } from 'rxjs/index';
import { DefaultSettings } from '../../../lib/default.settings';
import { createTranslateLoader } from '../../../lib/translation.factory';
import { ActiveQuestionGroupMockService } from '../../service/active-question-group/active-question-group.mock.service';
......@@ -149,7 +150,7 @@ describe('QuizOverviewComponent', () => {
spyOn(currentQuizService, 'cacheQuiz').and.callThrough();
spyOn(router, 'navigate').and.callFake(() => {});
component.startQuiz(quizName).subscribe(() => {}, (data) => {throw new Error(data); }, () => {
of(component.startQuiz(quizName)).subscribe(() => {}, (data) => {throw new Error(data); }, () => {
backend.expectOne(`${DefaultSettings.httpApiEndpoint}/quiz/status/${quizName}`).flush({
status: 'STATUS:SUCCESSFUL',
......
......@@ -5,7 +5,7 @@ import { Router } from '@angular/router';
import { IMessage } from 'arsnova-click-v2-types/src/common';
import { IQuestionGroup } from 'arsnova-click-v2-types/src/questions';
import { questionGroupReflection } from 'arsnova-click-v2-types/src/questions/questionGroup_reflection';
import { Observable, of } from 'rxjs/index';
import { Observable, of, Subscription } from 'rxjs/index';
import { DefaultSettings } from '../../../lib/default.settings';
import { ActiveQuestionGroupService } from '../../service/active-question-group/active-question-group.service';
import { CurrentQuizService } from '../../service/current-quiz/current-quiz.service';
......@@ -64,7 +64,7 @@ export class QuizOverviewComponent {
return questionGroupReflection[questionGroupSerialized.TYPE](questionGroupSerialized).isValid();
}
public startQuiz(sessionName: string): Observable<any> {
public startQuiz(sessionName: string): Subscription {
if (isPlatformServer(this.platformId)) {
return;
}
......@@ -91,7 +91,7 @@ export class QuizOverviewComponent {