From 06b3ae969cf180ca257c5eb8506dfa203e850bcf Mon Sep 17 00:00:00 2001
From: mechtelm <nicht@mehr.fragen>
Date: Wed, 29 Apr 2020 21:58:23 +0200
Subject: [PATCH] new booklet config parameters implemented: page_navibuttons,
 unit_menu, unit_screenheader, unit_title (not all options)

---
 package.json                                  |   2 +-
 src/app/app-root/login/login.component.ts     |   6 +-
 ...-routing-guards.ts => app-route-guards.ts} |  63 ++++++
 src/app/app-routing.module.ts                 |  49 ++++-
 src/app/app.interceptor.ts                    | 179 +++++++++---------
 src/app/maindata.service.ts                   |   4 +
 .../test-controller.component.css             |  43 ++---
 .../test-controller.component.html            |  39 ++--
 .../test-controller.component.ts              |   6 +-
 .../test-status/test-status.component.css     |   3 -
 .../unithost/unithost.component.css           |  21 +-
 .../unithost/unithost.component.html          |  12 +-
 .../unithost/unithost.component.ts            |   2 +-
 src/environments/environment.build.ts         |   4 +-
 src/environments/environment.prod.ts          |   4 +-
 src/environments/environment.ts               |   4 +-
 src/styles.css                                |   6 +-
 17 files changed, 266 insertions(+), 181 deletions(-)
 rename src/app/{app-routing-guards.ts => app-route-guards.ts} (65%)

diff --git a/package.json b/package.json
index 34de28b5..a2dc17d7 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "itc-ng",
-  "version": "3.1.0",
+  "version": "2.0.0-beta.4",
   "scripts": {
     "ng": "ng",
     "start": "ng serve",
diff --git a/src/app/app-root/login/login.component.ts b/src/app/app-root/login/login.component.ts
index df90df85..f192d027 100644
--- a/src/app/app-root/login/login.component.ts
+++ b/src/app/app-root/login/login.component.ts
@@ -62,7 +62,11 @@ export class LoginComponent  implements OnInit, OnDestroy {
           this.mds.setAuthData(authDataTyped);
 
           if (this.returnTo) {
-            this.router.navigateByUrl(this.returnTo);
+            this.router.navigateByUrl(this.returnTo).then(navOk => {
+              if (!navOk) {
+                this.router.navigate(['/r']);
+              }
+            });
           } else {
             this.router.navigate(['/r']);
           }
diff --git a/src/app/app-routing-guards.ts b/src/app/app-route-guards.ts
similarity index 65%
rename from src/app/app-routing-guards.ts
rename to src/app/app-route-guards.ts
index 5e3efd27..59d0392d 100644
--- a/src/app/app-routing-guards.ts
+++ b/src/app/app-route-guards.ts
@@ -86,3 +86,66 @@ export class CodeInputComponentActivateGuard implements CanActivate {
     }
   }
 }
+
+@Injectable({
+  providedIn: 'root'
+})
+export class AdminComponentActivateGuard implements CanActivate {
+  canActivate(
+    next: ActivatedRouteSnapshot,
+    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
+
+    const authData = MainDataService.getAuthData();
+    if (authData) {
+      if (authData.access) {
+        if (authData.access[AuthAccessKeyType.WORKSPACE_ADMIN]) {
+          return true;
+        }
+      }
+    } else {
+      return false
+    }
+  }
+}
+
+@Injectable({
+  providedIn: 'root'
+})
+export class SuperAdminComponentActivateGuard implements CanActivate {
+  canActivate(
+    next: ActivatedRouteSnapshot,
+    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
+
+    const authData = MainDataService.getAuthData();
+    if (authData) {
+      if (authData.access) {
+        if (authData.access[AuthAccessKeyType.SUPER_ADMIN]) {
+          return true;
+        }
+      }
+    } else {
+      return false
+    }
+  }
+}
+
+@Injectable({
+  providedIn: 'root'
+})
+export class TestComponentActivateGuard implements CanActivate {
+  canActivate(
+    next: ActivatedRouteSnapshot,
+    state: RouterStateSnapshot): Observable<boolean> | Promise<boolean> | boolean {
+
+    const authData = MainDataService.getAuthData();
+    if (authData) {
+      if (authData.access) {
+        if (authData.access[AuthAccessKeyType.TEST]) {
+          return true;
+        }
+      }
+    } else {
+      return false
+    }
+  }
+}
diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts
index f3076c50..11a09358 100644
--- a/src/app/app-routing.module.ts
+++ b/src/app/app-routing.module.ts
@@ -6,10 +6,11 @@ import {SysCheckStarterComponent} from "./app-root/sys-check-starter/sys-check-s
 import {AdminStarterComponent} from "./app-root/admin-starter/admin-starter.component";
 import {CodeInputComponent} from "./app-root/code-input/code-input.component";
 import {
+  AdminComponentActivateGuard,
   CodeInputComponentActivateGuard,
   DirectLoginActivateGuard,
-  RouteDispatcherActivateGuard
-} from "./app-routing-guards";
+  RouteDispatcherActivateGuard, SuperAdminComponentActivateGuard, TestComponentActivateGuard
+} from "./app-route-guards";
 import {TestStarterComponent} from "./app-root/test-starter/test-starter.component";
 import {RouteDispatcherComponent} from "./app-root/route-dispatcher/route-dispatcher.component";
 import {PrivacyComponent} from "./app-root/privacy/privacy.component";
@@ -27,25 +28,53 @@ const routes: Routes = [
       {path: 'login', redirectTo: 'route-dispatcher', pathMatch: 'full'},
       {path: 'login/:returnTo', component: LoginComponent},
       {path: 'check-starter', component: SysCheckStarterComponent},
-      {path: 'test-starter', component: TestStarterComponent},
-      {path: 'admin-starter', component: AdminStarterComponent},
-      {path: 'route-dispatcher', component: RouteDispatcherComponent, canActivate: [RouteDispatcherActivateGuard]},
-      {path: 'code-input', component: CodeInputComponent, canActivate: [CodeInputComponentActivateGuard]}
+      {
+        path: 'test-starter',
+        component: TestStarterComponent,
+        canActivate: [TestComponentActivateGuard]
+      },
+      {
+        path: 'admin-starter',
+        component: AdminStarterComponent,
+        canActivate: [AdminComponentActivateGuard, SuperAdminComponentActivateGuard]
+      },
+      {
+        path: 'route-dispatcher',
+        component: RouteDispatcherComponent,
+        canActivate: [RouteDispatcherActivateGuard]},
+      {
+        path: 'code-input',
+        component: CodeInputComponent,
+        canActivate: [CodeInputComponentActivateGuard]
+      }
     ]
   },
   {path: 'priv', component: PrivacyComponent},
   {path: 'check', loadChildren: () => import('./sys-check/sys-check.module').then(m => m.SysCheckModule)},
-  {path: 'admin', loadChildren: () => import('./workspace-admin/workspace.module').then(m => m.WorkspaceModule)},
-  {path: 'superadmin', loadChildren: () => import('./superadmin/superadmin.module').then(m => m.SuperadminModule)},
+  {
+    path: 'admin',
+    loadChildren: () => import('./workspace-admin/workspace.module').then(m => m.WorkspaceModule),
+    canActivate: [AdminComponentActivateGuard]
+  },
+  {
+    path: 'superadmin',
+    loadChildren: () => import('./superadmin/superadmin.module').then(m => m.SuperadminModule),
+    canActivate: [SuperAdminComponentActivateGuard]
+  },
   {path: 'wm', loadChildren: () => import('./workspace-monitor/workspace-monitor.module').then(m => m.WorkspaceMonitorModule)},
   {path: 'gm', loadChildren: () => import('./group-monitor/group-monitor.module').then(m => m.GroupMonitorModule)},
-  {path: 't', loadChildren: () => import('./test-controller/test-controller.module').then(m => m.TestControllerModule)},
+  {
+    path: 't',
+    loadChildren: () => import('./test-controller/test-controller.module').then(m => m.TestControllerModule),
+    canActivate: [TestComponentActivateGuard]
+  },
   {path: '**', component: RouteDispatcherComponent, canActivate: [DirectLoginActivateGuard]}
 ];
 
 @NgModule({
   imports: [RouterModule.forRoot(routes)],
   exports: [RouterModule],
-  providers: [RouteDispatcherActivateGuard, DirectLoginActivateGuard, CodeInputComponentActivateGuard]
+  providers: [RouteDispatcherActivateGuard, DirectLoginActivateGuard, CodeInputComponentActivateGuard,
+    AdminComponentActivateGuard, SuperAdminComponentActivateGuard, TestComponentActivateGuard]
 })
 export class AppRoutingModule { }
diff --git a/src/app/app.interceptor.ts b/src/app/app.interceptor.ts
index f672651d..371ac9e3 100644
--- a/src/app/app.interceptor.ts
+++ b/src/app/app.interceptor.ts
@@ -16,104 +16,101 @@ export class AuthInterceptor implements HttpInterceptor {
     private router: Router) {}
 
   intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
-    if (!this.mds.isApiValid) {
-      this.mds.appError$.next({
-        label: "Server-Problem: API-Version ungültig",
-        description: "Keine weiteren Server-Aufrufe erlaubt",
-        category: "FATAL"
-      });
-      return throwError(new ApiError(500, "API-Version ungültig"));
-    }
-
-    // if (request.headers.get('AuthToken') !== null) {
-    //   return next.handle(request);
-    // }
-    let tokenStr = '';
-    const authData = MainDataService.getAuthData();
-    if (authData) {
-      if (authData.token) {
-        tokenStr = authData.token;
+    if (this.mds.isApiValid) {
+      let tokenStr = '';
+      const authData = MainDataService.getAuthData();
+      if (authData) {
+        if (authData.token) {
+          tokenStr = authData.token;
+        }
       }
-    }
 
-    const requestA = request.clone({
-      setHeaders: {
-        AuthToken: tokenStr
-      }
-    });
+      const requestA = request.clone({
+        setHeaders: {
+          AuthToken: tokenStr
+        }
+      });
 
-    return next.handle(requestA).pipe(
-      catchError(e => {
-        let apiError = new ApiError(999);
-        if (e instanceof HttpErrorResponse) {
-          const httpError = e as HttpErrorResponse;
-          apiError.code = httpError.status;
-          apiError.info = httpError.message + " // " + httpError.error;
-          if (httpError.error instanceof ErrorEvent) {
-            this.mds.appError$.next({
-              label: 'Fehler in der Netzwerkverbindung',
-              description: httpError.message,
-              category: "PROBLEM"
-            })
-          } else {
-            let ignoreError = false;
-            let goToLoginPage = false;
-            let label = 'Unbekanntes Verbindungsproblem';
-            switch (httpError.status) {
-              case 400: {
-                ignoreError = true;
-                // apiError.info = ?? TODO - from request body
-                break;
-              }
-              case 401: {
-                goToLoginPage = true;
-                label = 'Bitte für diese Aktion erst anmelden!';
-                break;
+      return next.handle(requestA).pipe(
+        catchError(e => {
+          let apiError = new ApiError(999);
+          if (e instanceof HttpErrorResponse) {
+            const httpError = e as HttpErrorResponse;
+            apiError.code = httpError.status;
+            apiError.info = httpError.message + " // " + httpError.error;
+            if (httpError.error instanceof ErrorEvent) {
+              this.mds.appError$.next({
+                label: 'Fehler in der Netzwerkverbindung',
+                description: httpError.message,
+                category: "PROBLEM"
+              })
+            } else {
+              let ignoreError = false;
+              let goToLoginPage = false;
+              let label = 'Unbekanntes Verbindungsproblem';
+              switch (httpError.status) {
+                case 400: {
+                  ignoreError = true;
+                  // apiError.info = ?? TODO - from request body
+                  break;
+                }
+                case 401: {
+                  goToLoginPage = true;
+                  label = 'Bitte für diese Aktion erst anmelden!';
+                  break;
+                }
+                case 403: {
+                  label = 'Für diese Funktion haben Sie keine Berechtigung.';
+                  break;
+                }
+                case 404: {
+                  label = 'Daten/Objekt nicht gefunden.';
+                  break;
+                }
+                case 410: {
+                  goToLoginPage = true;
+                  label = 'Anmeldung abgelaufen. Bitte erneut anmelden!';
+                  break;
+                }
+                case 422: {
+                  ignoreError = true;
+                  // apiError.info = ?? TODO - from request body
+                  label = 'Die übermittelten Objekte sind fehlerhaft!';
+                  break;
+                }
+                case 500: {
+                  label = 'Allgemeines Server-Problem.';
+                  break;
+                }
               }
-              case 403: {
-                label = 'Für diese Funktion haben Sie keine Berechtigung.';
-                break;
-              }
-              case 404: {
-                label = 'Daten/Objekt nicht gefunden.';
-                break;
-              }
-              case 410: {
-                goToLoginPage = true;
-                label = 'Anmeldung abgelaufen. Bitte erneut anmelden!';
-                break;
-              }
-              case 422: {
-                ignoreError = true;
-                // apiError.info = ?? TODO - from request body
-                label = 'Die übermittelten Objekte sind fehlerhaft!';
-                break;
-              }
-              case 500: {
-                label = 'Allgemeines Server-Problem.';
-                break;
-              }
-            }
-            if (!ignoreError) {
-              if (goToLoginPage) {
-                console.warn('AuthError' + httpError.status + ' (' + label + ')');
-                MainDataService.resetAuthData();
-                const state: RouterState = this.router.routerState;
-                const snapshot: RouterStateSnapshot = state.snapshot;
-                this.router.navigate(['/r/login', snapshot.url]);
-              } else {
-                this.mds.appError$.next({
-                  label: label,
-                  description: httpError.message,
-                  category: "PROBLEM"
-                });
+              if (!ignoreError) {
+                if (goToLoginPage) {
+                  console.warn('AuthError' + httpError.status + ' (' + label + ')');
+                  MainDataService.resetAuthData();
+                  const state: RouterState = this.router.routerState;
+                  const snapshot: RouterStateSnapshot = state.snapshot;
+                  this.router.navigate(['/r/login', snapshot.url]);
+                } else {
+                  this.mds.appError$.next({
+                    label: label,
+                    description: httpError.message,
+                    category: "PROBLEM"
+                  });
+                }
               }
             }
           }
-        }
 
-        return throwError(apiError);
-      })
-    )
+          return throwError(apiError);
+        })
+      )
+    } else {
+      this.mds.appError$.next({
+        label: "Server-Problem: API-Version ungültig",
+        description: "Keine weiteren Server-Aufrufe erlaubt",
+        category: "FATAL"
+      });
+      return throwError(new ApiError(500, "API-Version ungültig"));
+    }
   }
 }
diff --git a/src/app/maindata.service.ts b/src/app/maindata.service.ts
index 4d6214bc..a6cc5086 100644
--- a/src/app/maindata.service.ts
+++ b/src/app/maindata.service.ts
@@ -24,6 +24,10 @@ export class MainDataService {
   public appConfig: AppConfig = null;
   public sysCheckAvailable = false;
 
+  public defaultTcHeaderHeight = document.documentElement.style.getPropertyValue('--tc-header-height');
+  public defaultTcUnitTitleHeight = document.documentElement.style.getPropertyValue('--tc-unit-title-height');
+  public defaultTcUnitPageNavHeight = document.documentElement.style.getPropertyValue('--tc-unit-page-nav-height');
+
   // set by app.component.ts
   public postMessage$ = new Subject<MessageEvent>();
 
diff --git a/src/app/test-controller/test-controller.component.css b/src/app/test-controller/test-controller.component.css
index bf56d7b1..f8104fea 100644
--- a/src/app/test-controller/test-controller.component.css
+++ b/src/app/test-controller/test-controller.component.css
@@ -22,38 +22,36 @@
   margin: 15px 10px 0 10px;
 }
 
-.errorMsg, .statusMsg {
-  margin: 20px;
-  max-width: 300px;
-}
-
-.errorMsg {
-  color: brown;
-}
-
 .tc-body {
   overflow-x: auto;
   position: absolute;
   width: 100%;
-  top: var(--tc-topmargin);
+  top: var(--tc-header-height);
   bottom: 0;
 }
 
 /* --------------------------------------------- */
-#buttonsContainer {
+#header {
   position: absolute;
-  right: 5px;
-  top: 2px;
-  left: 5px;
+  width: 100%;
+  padding-right: 5px;
+  padding-top: 2px;
+  padding-left: 25px;
   color: white;
+  z-index: 444;
 }
-#buttonsContainer .material-icons {
-  font-size: 2.0rem;
-}
-#buttonsContainer img {
-  width: 100px;
+#header .material-icons {
+  /* font-size: 2.0rem; */
+  position: relative;
+  top: -8px;
+  font-size: 36px;
+  padding: 2px;
 }
 
+#header .plus-buttons {
+  background-color: rgba(0, 51, 51, 0.4);
+  height: 48px;
+}
 /* --------------------------------------------- */
 mat-toolbar {
   position: fixed;
@@ -62,13 +60,6 @@ mat-toolbar {
   right: 90px;
 }
 
-#buttonsContainer .material-icons {
-  position: relative;
-  top: -8px;
-  font-size: 36px;
-  padding: 2px;
-}
-
 .mat-fab {
   margin: 0 6px;
 }
diff --git a/src/app/test-controller/test-controller.component.html b/src/app/test-controller/test-controller.component.html
index 86a71bc5..5b0342e7 100644
--- a/src/app/test-controller/test-controller.component.html
+++ b/src/app/test-controller/test-controller.component.html
@@ -1,14 +1,10 @@
-<div id="buttonsContainer" fxLayout="row" fxLayoutAlign="space-between center">
-  <a [routerLink]="['/']">
-    <img src="assets/IQB-LogoA.png" matTooltip="Startseite"/>
-  </a>
-  <div fxLayout="row" fxLayoutAlign="end center">
+<div id="header" fxLayout="row" fxLayoutAlign="end center">
     <p class="timer" *ngIf="tcs.testMode.showTimeLeft">{{ timerValue?.timeLeftString }}</p>
     <button mat-fab [disabled]="!tcs.unitPrevEnabled" *ngIf="(tcs.bookletConfig.unit_navibuttons !== 'OFF') && ((tcs.testStatus$ | async) === tcs.testStatusEnum.RUNNING)" color="accent"
             (click)="tcs.setUnitNavigationRequest(unitNavigationTarget.PREVIOUS)" matTooltip="Zurück" fxFlex="none">
       <i class="material-icons">chevron_left</i>
     </button>
-    <div *ngIf="(tcs.bookletConfig.unit_navibuttons === 'FULL') && ((tcs.testStatus$ | async) === tcs.testStatusEnum.RUNNING)" fxLayout="row wrap" fxLayoutAlign="start center" fxFlex="0 3 100%">
+    <div *ngIf="(tcs.bookletConfig.unit_navibuttons === 'FULL') && ((tcs.testStatus$ | async) === tcs.testStatusEnum.RUNNING)" fxLayout="row wrap" fxLayoutAlign="start center">
       <div *ngFor="let u of tcs.unitListForNaviButtons">
         <div fxLayout="column" fxLayoutAlign="start center">
             <button (click)="tcs.setUnitNavigationRequest(u.sequenceId.toString())" class="unit-button" [disabled]="u.disabled">{{ u.shortLabel }}</button>
@@ -21,24 +17,18 @@
             (click)="tcs.setUnitNavigationRequest(unitNavigationTarget.NEXT)" matTooltip="Weiter" fxFlex="none">
       <i class="material-icons">chevron_right</i>
     </button>
-    <button mat-button (click)="showReviewDialog()" *ngIf="tcs.testMode.canReview" matTooltip="Kommentar senden" fxFlex="none">
-      <mat-icon>rate_review</mat-icon>
-    </button>
-    <button mat-button (click)="tcs.setUnitNavigationRequest(unitNavigationTarget.MENU)"
-            *ngIf="(tcs.bookletConfig.unit_menu !== 'OFF') || tcs.testMode.showUnitMenu"
-            matTooltip="Zur Aufgabenliste" fxFlex="none">
-      <mat-icon>menu</mat-icon>
-    </button>
-    <!--
-    <button mat-button (click)="topMargin()" fxFlex="none" style="z-index: 888;">
-      <mat-icon>vertical_align_top</mat-icon>
-    </button>
-    <button mat-button (click)="bottomMargin()" fxFlex="none" style="z-index: 888;">
-      <mat-icon>vertical_align_bottom</mat-icon>
-    </button>
-    -->
-  </div>
+    <div class="plus-buttons" fxFlex="none" fxLayout="row">
+      <button mat-button (click)="showReviewDialog()" *ngIf="tcs.testMode.canReview" matTooltip="Kommentar senden" fxFlex="none">
+        <mat-icon>rate_review</mat-icon>
+      </button>
+      <button mat-button (click)="tcs.setUnitNavigationRequest(unitNavigationTarget.MENU)"
+              *ngIf="(tcs.bookletConfig.unit_menu !== 'OFF') || tcs.testMode.showUnitMenu"
+              matTooltip="Zur Aufgabenliste" fxFlex="none">
+        <mat-icon>menu</mat-icon>
+      </button>
+    </div>
 </div>
+
 <mat-card *ngIf="(tcs.testStatus$ | async) === tcs.testStatusEnum.WAITING_LOAD_COMPLETE" class="progress-bar">
   <mat-card-content fxLayout="column">
     <h2>Lade Aufgaben... bitte warten</h2>
@@ -49,6 +39,7 @@
     </mat-progress-bar>
   </mat-card-content>
 </mat-card>
-<div class="tc-body">
+
+<div class="tc-body" fxLayout="row" fxLayoutAlign="start stretch">
     <router-outlet></router-outlet>
 </div>
diff --git a/src/app/test-controller/test-controller.component.ts b/src/app/test-controller/test-controller.component.ts
index 9b55b351..d5a85d4d 100644
--- a/src/app/test-controller/test-controller.component.ts
+++ b/src/app/test-controller/test-controller.component.ts
@@ -404,9 +404,9 @@ export class TestControllerComponent implements OnInit, OnDestroy {
 
               this.tcs.rootTestlet = this.getBookletFromXml(testData.xml, testData.mode);
 
-              document.documentElement.style.setProperty('--tc-unithost-bottommargin', this.tcs.bookletConfig.page_navibuttons === 'SEPARATE_BOTTOM' ? '45px' : '0');
-              document.documentElement.style.setProperty('--tc-unithost-topmargin', this.tcs.bookletConfig.unit_title === 'ON' ? '40px' : '0');
-              document.documentElement.style.setProperty('--tc-topmargin', this.tcs.bookletConfig.unit_screenheader === 'OFF' ? '0' : '65px');
+              document.documentElement.style.setProperty('--tc-unit-title-height', this.tcs.bookletConfig.unit_title === 'ON' ? this.mds.defaultTcUnitTitleHeight : '0');
+              document.documentElement.style.setProperty('--tc-header-height', this.tcs.bookletConfig.unit_screenheader === 'OFF' ? '0' : this.mds.defaultTcHeaderHeight);
+              document.documentElement.style.setProperty('--tc-unit-page-nav-height', this.tcs.bookletConfig.page_navibuttons === 'SEPARATE_BOTTOM' ? this.mds.defaultTcUnitPageNavHeight : '0');
 
               if (this.tcs.rootTestlet === null) {
                 this.mds.appError$.next({
diff --git a/src/app/test-controller/test-status/test-status.component.css b/src/app/test-controller/test-status/test-status.component.css
index ac64082f..ada61395 100644
--- a/src/app/test-controller/test-status/test-status.component.css
+++ b/src/app/test-controller/test-status/test-status.component.css
@@ -1,9 +1,6 @@
 .status-body {
   position: absolute;
   width: 100%;
-  top: 40px;
-  bottom: 45px;
-  padding: 0;
 }
 
 mat-card {
diff --git a/src/app/test-controller/unithost/unithost.component.css b/src/app/test-controller/unithost/unithost.component.css
index 649e164e..94bb928c 100644
--- a/src/app/test-controller/unithost/unithost.component.css
+++ b/src/app/test-controller/unithost/unithost.component.css
@@ -1,19 +1,29 @@
-.unit-body {
+#iFrameHost {
   position: absolute;
   width: 100%;
-  top: var(--tc-unithost-topmargin);
-  bottom: var(--tc-unithost-bottommargin);
+  top: var(--tc-unit-title-height);
+  bottom: var(--tc-unit-page-nav-height);
+  padding: 0;
+  background-color: white;
+}
+
+.unitHost {
+  position: absolute;
+  width: 100%;
+  top: 0;
   padding: 0;
   background-color: white;
 }
 
 #unit-title {
+  position: absolute;
   width: 100%;
+  /* top: set by .tc-body */
+  height: 39px;
   padding: 0;
-  height: 40px;
   font-size: 1.5em;
   background-color: white;
-  border-bottom: solid 2px black;
+  border-bottom: solid 1px black;
 }
 
 #pageNav {
@@ -21,7 +31,6 @@
   width: 100%;
   height: 45px;
   bottom: 0;
-  /* background-color: silver; */
   padding: 0 30px;
   font-size: 1.2em;
 }
diff --git a/src/app/test-controller/unithost/unithost.component.html b/src/app/test-controller/unithost/unithost.component.html
index e8e9ebf3..4984d5e9 100644
--- a/src/app/test-controller/unithost/unithost.component.html
+++ b/src/app/test-controller/unithost/unithost.component.html
@@ -1,11 +1,11 @@
-<div id="unit-title" fxLayoutAlign="center center">
-    {{ unitTitle }}
-</div>
-<div id="iFrameHost" iqbResizeIFrameChild class="unit-body">
+  <div id="unit-title" *ngIf="tcs.bookletConfig.unit_title === 'ON'" fxLayout="column" fxLayoutAlign="center center">
+    <p>{{ unitTitle }}</p>
+  </div>
 
-</div>
-  <div id="pageNav" fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="10px" *ngIf="tcs.bookletConfig.page_navibuttons !== 'OFF'">
+  <div id="iFrameHost" iqbResizeIFrameChild>
+  </div>
 
+  <div id="pageNav" fxLayout="row" fxLayoutAlign="end center" fxLayoutGap="10px" *ngIf="tcs.bookletConfig.page_navibuttons === 'SEPARATE_BOTTOM'">
     <div fxLayout="row" fxLayoutAlign="space-between center" *ngIf="showPageNav">
         <div id="pageNavPrompt">
             Wähle hier andere Seiten dieser Aufgabe:
diff --git a/src/app/test-controller/unithost/unithost.component.ts b/src/app/test-controller/unithost/unithost.component.ts
index 51ffa202..4eecd89e 100644
--- a/src/app/test-controller/unithost/unithost.component.ts
+++ b/src/app/test-controller/unithost/unithost.component.ts
@@ -189,7 +189,7 @@ export class UnithostComponent implements OnInit, OnDestroy {
           // this.iFrameItemplayer.setAttribute('srcdoc', this.tcs.getPlayer(currentUnit.unitDef.playerId));
           this.iFrameItemplayer.setAttribute('sandbox', 'allow-forms allow-scripts allow-same-origin');
           this.iFrameItemplayer.setAttribute('class', 'unitHost');
-          this.iFrameItemplayer.setAttribute('height', String(this.iFrameHostElement.clientHeight));
+          this.iFrameItemplayer.setAttribute('height', String(this.iFrameHostElement.clientHeight - 5));
 
           if (this.tcs.hasUnitRestorePoint(this.myUnitSequenceId)) {
             this.pendingUnitRestorePoint = {tag: this.itemplayerSessionId, value: this.tcs.getUnitRestorePoint(this.myUnitSequenceId)};
diff --git a/src/environments/environment.build.ts b/src/environments/environment.build.ts
index 4cb6d670..d376167b 100644
--- a/src/environments/environment.build.ts
+++ b/src/environments/environment.build.ts
@@ -5,6 +5,6 @@ export const environment = {
   testcenterUrl: '/',
   appName: 'IQB-Testcenter',
   appPublisher: 'IQB - Institut zur Qualitätsentwicklung im Bildungswesen',
-  appVersion: '1.5.2 - 12.2.2020',
-  apiVersionExpected: '2.2.0'
+  appVersion: '2.0.0-beta.4 - 29.4.2020',
+  apiVersionExpected: '3.0.1'
 };
diff --git a/src/environments/environment.prod.ts b/src/environments/environment.prod.ts
index 837a9d16..46ecc35f 100644
--- a/src/environments/environment.prod.ts
+++ b/src/environments/environment.prod.ts
@@ -5,6 +5,6 @@ export const environment = {
   testcenterUrl: '/api/',
   appName: 'IQB-Testcenter',
   appPublisher: 'IQB - Institut zur Qualitätsentwicklung im Bildungswesen',
-  appVersion: '2.0.0-beta.3 - 29.4.2020',
-  apiVersionExpected: '2.2.0'
+  appVersion: '2.0.0-beta.4 - 29.4.2020',
+  apiVersionExpected: '3.0.1'
 };
diff --git a/src/environments/environment.ts b/src/environments/environment.ts
index fefaeffc..9ccebd9c 100644
--- a/src/environments/environment.ts
+++ b/src/environments/environment.ts
@@ -10,8 +10,8 @@ export const environment = {
   // testcenterUrl: 'http://localhost/api/',
   appName: 'IQB-Testcenter',
   appPublisher: 'IQB - Institut zur Qualitätsentwicklung im Bildungswesen',
-  appVersion: '2.0.0-beta.2 - 27.4.2020',
-  apiVersionExpected: '3.0.0'
+  appVersion: '2.0.0-beta.4 - 29.4.2020',
+  apiVersionExpected: '3.0.1'
 };
 
 /*
diff --git a/src/styles.css b/src/styles.css
index 9281e1f7..7ff1e088 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -1,7 +1,7 @@
 :root {
-  --tc-topmargin: 65px;
-  --tc-unithost-topmargin: 40px;
-  --tc-unithost-bottommargin: 45px;
+  --tc-header-height: 65px;
+  --tc-unit-title-height: 40px;
+  --tc-unit-page-nav-height: 45px;
 }
 
 /* orienta-regular - latin */
-- 
GitLab