diff --git a/angular.json b/angular.json
index a1cf4f69c7fa2591a298a70475469dcd2efa40ca..94cb7e5259bcd28c9dd604dd9d4629300e7244f4 100644
--- a/angular.json
+++ b/angular.json
@@ -59,7 +59,8 @@
         "serve": {
           "builder": "@angular-devkit/build-angular:dev-server",
           "options": {
-            "browserTarget": "arsnova-angular-frontend:build"
+            "browserTarget": "arsnova-angular-frontend:build",
+            "proxyConfig": "proxy.conf.json"
           },
           "configurations": {
             "production": {
diff --git a/package-lock.json b/package-lock.json
index 637d9c346f53eafcda1a2bf24900f56f2f414ea4..6ee4ddaa1060204a511f0c9f88180a134d23b096 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1951,11 +1951,6 @@
         "yargs": "8.0.2"
       },
       "dependencies": {
-        "ansi-regex": {
-          "version": "3.0.0",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
-          "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
-        },
         "ansi-styles": {
           "version": "3.2.1",
           "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
@@ -2056,11 +2051,6 @@
           "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=",
           "dev": true
         },
-        "is-fullwidth-code-point": {
-          "version": "2.0.0",
-          "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
-          "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
-        },
         "minimist": {
           "version": "0.0.8",
           "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
@@ -2115,14 +2105,6 @@
             }
           }
         },
-        "strip-ansi": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
-          "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
-          "requires": {
-            "ansi-regex": "^3.0.0"
-          }
-        },
         "supports-color": {
           "version": "4.5.0",
           "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz",
@@ -3881,7 +3863,8 @@
     "balanced-match": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
-      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
+      "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+      "dev": true
     },
     "base": {
       "version": "0.11.2",
@@ -4166,6 +4149,7 @@
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
       "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+      "dev": true,
       "requires": {
         "balanced-match": "^1.0.0",
         "concat-map": "0.0.1"
@@ -4943,7 +4927,8 @@
     "concat-map": {
       "version": "0.0.1",
       "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
-      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
+      "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+      "dev": true
     },
     "concat-stream": {
       "version": "1.6.2",
@@ -5777,15 +5762,6 @@
       "integrity": "sha512-eyTcpKOcamdhWJXj56DpQMo1ylSQpcGtGKXcU0Tb97+K56/CF5amAqqqNj0+KvA0iw2ynxtHWFsPDSClCxe48w==",
       "dev": true
     },
-    "debug": {
-      "version": "2.6.9",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
-      "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
-      "dev": true,
-      "requires": {
-        "ms": "2.0.0"
-      }
-    },
     "decamelize": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
@@ -6448,36 +6424,9 @@
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
       "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+      "dev": true,
       "requires": {
         "is-arrayish": "^0.2.1"
-      },
-      "dependencies": {
-        "chalk": {
-          "version": "4.1.1",
-          "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz",
-          "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==",
-          "requires": {
-            "supports-color": "^7.1.0"
-          }
-        },
-        "has-flag": {
-          "version": "4.0.0",
-          "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
-          "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
-        },
-        "parse5": {
-          "version": "6.0.1",
-          "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
-          "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="
-        },
-        "supports-color": {
-          "version": "7.2.0",
-          "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
-          "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
-          "requires": {
-            "has-flag": "^4.0.0"
-          }
-        }
       }
     },
     "es-to-primitive": {
@@ -7335,10 +7284,20 @@
           "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=",
           "dev": true
         },
+        "debug": {
+          "version": "2.6.9",
+          "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
+          "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+          "dev": true,
+          "requires": {
+            "ms": "2.0.0"
+          }
+        },
         "ms": {
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
-          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
         },
         "qs": {
           "version": "6.7.0",
@@ -7648,6 +7607,12 @@
           "requires": {
             "ms": "2.0.0"
           }
+        },
+        "ms": {
+          "version": "2.0.0",
+          "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+          "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=",
+          "dev": true
         }
       }
     },
@@ -7934,7 +7899,8 @@
     "fs.realpath": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
-      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
+      "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+      "dev": true
     },
     "fsevents": {
       "version": "2.3.2",
@@ -8064,6 +8030,7 @@
       "version": "7.1.4",
       "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
       "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
+      "dev": true,
       "requires": {
         "fs.realpath": "^1.0.0",
         "inflight": "^1.0.4",
@@ -8122,7 +8089,8 @@
     "graceful-fs": {
       "version": "4.2.2",
       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.2.tgz",
-      "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q=="
+      "integrity": "sha512-IItsdsea19BoLC7ELy13q1iJFNmd7ofZH5+X/pJr90/nRoPEX0DJo1dHDbgtYWOhJhcCgMDTOw84RZ72q6lB+Q==",
+      "dev": true
     },
     "handle-thing": {
       "version": "2.0.1",
@@ -8848,6 +8816,7 @@
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
       "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+      "dev": true,
       "requires": {
         "once": "^1.3.0",
         "wrappy": "1"
@@ -8856,7 +8825,8 @@
     "inherits": {
       "version": "2.0.4",
       "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
-      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+      "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+      "dev": true
     },
     "ini": {
       "version": "2.0.0",
@@ -9002,7 +8972,8 @@
     "invert-kv": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz",
-      "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY="
+      "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=",
+      "dev": true
     },
     "ip": {
       "version": "1.1.5",
@@ -9060,7 +9031,8 @@
     "is-arrayish": {
       "version": "0.2.1",
       "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
-      "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0="
+      "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+      "dev": true
     },
     "is-bigint": {
       "version": "1.0.1",
@@ -9246,10 +9218,9 @@
       }
     },
     "is-promise": {
-      "version": "2.2.2",
-      "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
-      "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==",
-      "dev": true
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz",
+      "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ=="
     },
     "is-regex": {
       "version": "1.0.5",
@@ -9269,7 +9240,8 @@
     "is-stream": {
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
-      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
+      "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
+      "dev": true
     },
     "is-string": {
       "version": "1.0.5",
@@ -9330,7 +9302,8 @@
     "isexe": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
-      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
+      "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+      "dev": true
     },
     "isobject": {
       "version": "3.0.1",
@@ -9997,6 +9970,7 @@
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz",
       "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=",
+      "dev": true,
       "requires": {
         "invert-kv": "^1.0.0"
       }
@@ -10144,6 +10118,7 @@
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz",
       "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=",
+      "dev": true,
       "requires": {
         "graceful-fs": "^4.1.2",
         "parse-json": "^2.2.0",
@@ -10155,6 +10130,7 @@
           "version": "2.2.0",
           "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
           "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
+          "dev": true,
           "requires": {
             "error-ex": "^1.2.0"
           }
@@ -10162,7 +10138,8 @@
         "pify": {
           "version": "2.3.0",
           "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
-          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
+          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+          "dev": true
         }
       }
     },
@@ -10439,6 +10416,7 @@
       "version": "1.1.0",
       "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz",
       "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=",
+      "dev": true,
       "requires": {
         "mimic-fn": "^1.0.0"
       },
@@ -10446,7 +10424,8 @@
         "mimic-fn": {
           "version": "1.2.0",
           "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
-          "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ=="
+          "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+          "dev": true
         }
       }
     },
@@ -10652,6 +10631,7 @@
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
       "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+      "dev": true,
       "requires": {
         "brace-expansion": "^1.1.7"
       }
@@ -11150,6 +11130,7 @@
       "version": "2.5.0",
       "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
       "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+      "dev": true,
       "requires": {
         "hosted-git-info": "^2.1.4",
         "resolve": "^1.10.0",
@@ -11160,7 +11141,8 @@
         "hosted-git-info": {
           "version": "2.8.9",
           "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
-          "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw=="
+          "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
+          "dev": true
         }
       }
     },
@@ -11380,6 +11362,7 @@
       "version": "2.0.2",
       "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
       "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
+      "dev": true,
       "requires": {
         "path-key": "^2.0.0"
       }
@@ -11603,6 +11586,7 @@
       "version": "1.4.0",
       "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
       "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+      "dev": true,
       "requires": {
         "wrappy": "1"
       }
@@ -11766,6 +11750,7 @@
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz",
       "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==",
+      "dev": true,
       "requires": {
         "execa": "^0.7.0",
         "lcid": "^1.0.0",
@@ -11776,6 +11761,7 @@
           "version": "5.1.0",
           "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz",
           "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=",
+          "dev": true,
           "requires": {
             "lru-cache": "^4.0.1",
             "shebang-command": "^1.2.0",
@@ -11786,6 +11772,7 @@
           "version": "0.7.0",
           "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz",
           "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=",
+          "dev": true,
           "requires": {
             "cross-spawn": "^5.0.1",
             "get-stream": "^3.0.0",
@@ -11799,12 +11786,14 @@
         "get-stream": {
           "version": "3.0.0",
           "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz",
-          "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ="
+          "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=",
+          "dev": true
         },
         "lru-cache": {
           "version": "4.1.5",
           "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
           "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
+          "dev": true,
           "requires": {
             "pseudomap": "^1.0.2",
             "yallist": "^2.1.2"
@@ -11813,7 +11802,8 @@
         "yallist": {
           "version": "2.1.2",
           "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
-          "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
+          "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+          "dev": true
         }
       }
     },
@@ -11826,7 +11816,8 @@
     "p-finally": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
-      "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4="
+      "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
+      "dev": true
     },
     "p-map": {
       "version": "4.0.0",
@@ -11952,6 +11943,14 @@
       "dev": true,
       "requires": {
         "callsites": "^3.0.0"
+      },
+      "dependencies": {
+        "callsites": {
+          "version": "3.1.0",
+          "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+          "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+          "dev": true
+        }
       }
     },
     "parse-asn1": {
@@ -12108,7 +12107,8 @@
     "path-is-absolute": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
-      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
+      "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+      "dev": true
     },
     "path-is-inside": {
       "version": "1.0.2",
@@ -12119,12 +12119,14 @@
     "path-key": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
-      "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A="
+      "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+      "dev": true
     },
     "path-parse": {
       "version": "1.0.6",
       "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
-      "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw=="
+      "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+      "dev": true
     },
     "path-to-regexp": {
       "version": "0.1.7",
@@ -12810,11 +12812,6 @@
           "integrity": "sha1-vShOV8hPEyXacCur/IKlMoGQwMU=",
           "dev": true
         },
-        "ansi-regex": {
-          "version": "5.0.0",
-          "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
-          "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
-        },
         "ansi-styles": {
           "version": "2.2.1",
           "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz",
@@ -13211,7 +13208,8 @@
     "pseudomap": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
-      "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM="
+      "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
+      "dev": true
     },
     "psl": {
       "version": "1.8.0",
@@ -13438,6 +13436,7 @@
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz",
       "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=",
+      "dev": true,
       "requires": {
         "load-json-file": "^2.0.0",
         "normalize-package-data": "^2.3.2",
@@ -13448,6 +13447,7 @@
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz",
           "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=",
+          "dev": true,
           "requires": {
             "pify": "^2.0.0"
           }
@@ -13455,7 +13455,8 @@
         "pify": {
           "version": "2.3.0",
           "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
-          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw="
+          "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=",
+          "dev": true
         }
       }
     },
@@ -13463,6 +13464,7 @@
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz",
       "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=",
+      "dev": true,
       "requires": {
         "find-up": "^2.0.0",
         "read-pkg": "^2.0.0"
@@ -13472,6 +13474,7 @@
           "version": "2.1.0",
           "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
           "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=",
+          "dev": true,
           "requires": {
             "locate-path": "^2.0.0"
           }
@@ -13480,6 +13483,7 @@
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz",
           "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=",
+          "dev": true,
           "requires": {
             "p-locate": "^2.0.0",
             "path-exists": "^3.0.0"
@@ -13489,6 +13493,7 @@
           "version": "1.3.0",
           "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz",
           "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==",
+          "dev": true,
           "requires": {
             "p-try": "^1.0.0"
           }
@@ -13497,6 +13502,7 @@
           "version": "2.0.0",
           "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz",
           "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=",
+          "dev": true,
           "requires": {
             "p-limit": "^1.1.0"
           }
@@ -13504,7 +13510,8 @@
         "p-try": {
           "version": "1.0.0",
           "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz",
-          "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M="
+          "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=",
+          "dev": true
         }
       }
     },
@@ -13703,6 +13710,7 @@
       "version": "1.11.1",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz",
       "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==",
+      "dev": true,
       "requires": {
         "path-parse": "^1.0.6"
       }
@@ -13927,6 +13935,14 @@
       "dev": true,
       "requires": {
         "is-promise": "^2.1.0"
+      },
+      "dependencies": {
+        "is-promise": {
+          "version": "2.2.2",
+          "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.2.2.tgz",
+          "integrity": "sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==",
+          "dev": true
+        }
       }
     },
     "run-parallel": {
@@ -14198,7 +14214,8 @@
     "semver": {
       "version": "5.7.0",
       "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
-      "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
+      "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
+      "dev": true
     },
     "semver-intersect": {
       "version": "1.4.0",
@@ -14401,6 +14418,7 @@
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
       "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+      "dev": true,
       "requires": {
         "shebang-regex": "^1.0.0"
       }
@@ -14408,12 +14426,14 @@
     "shebang-regex": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
-      "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM="
+      "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+      "dev": true
     },
     "signal-exit": {
       "version": "3.0.2",
       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
-      "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
+      "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+      "dev": true
     },
     "simple-swizzle": {
       "version": "0.2.2",
@@ -14894,6 +14914,7 @@
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
       "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
+      "dev": true,
       "requires": {
         "spdx-expression-parse": "^3.0.0",
         "spdx-license-ids": "^3.0.0"
@@ -14902,12 +14923,14 @@
     "spdx-exceptions": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
-      "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA=="
+      "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
+      "dev": true
     },
     "spdx-expression-parse": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
       "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
+      "dev": true,
       "requires": {
         "spdx-exceptions": "^2.1.0",
         "spdx-license-ids": "^3.0.0"
@@ -14916,7 +14939,8 @@
     "spdx-license-ids": {
       "version": "3.0.5",
       "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
-      "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q=="
+      "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
+      "dev": true
     },
     "spdy": {
       "version": "4.0.2",
@@ -15306,12 +15330,14 @@
     "strip-bom": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
-      "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM="
+      "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+      "dev": true
     },
     "strip-eof": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
-      "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8="
+      "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
+      "dev": true
     },
     "strip-json-comments": {
       "version": "2.0.1",
@@ -16317,9 +16343,9 @@
       }
     },
     "url-parse": {
-      "version": "1.4.7",
-      "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
-      "integrity": "sha512-d3uaVyzDB9tQoSXFvuSUNFibTd9zxd2bkVrDRvF5TmvWWQwqE4lgYJ5m+x1DbecWkw+LK4RNl2CU1hHuOKPVlg==",
+      "version": "1.5.1",
+      "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.1.tgz",
+      "integrity": "sha512-HOfCOUJt7iSYzEx/UqgtwKRMC6EU91NFhsCHMv9oM03VJcVo2Qrp8T8kI9D7amFf1cu+/3CEhgb3rF9zL7k85Q==",
       "dev": true,
       "requires": {
         "querystringify": "^2.1.1",
@@ -16386,6 +16412,7 @@
       "version": "3.0.4",
       "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
       "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+      "dev": true,
       "requires": {
         "spdx-correct": "^3.0.0",
         "spdx-expression-parse": "^3.0.0"
@@ -17665,6 +17692,7 @@
       "version": "1.3.1",
       "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
       "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+      "dev": true,
       "requires": {
         "isexe": "^2.0.0"
       }
@@ -17784,7 +17812,8 @@
     "wrappy": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
-      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
+      "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+      "dev": true
     },
     "xml2js": {
       "version": "0.4.23",
@@ -17964,9 +17993,9 @@
       "dev": true
     },
     "zone.js": {
-      "version": "0.10.3",
-      "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.10.3.tgz",
-      "integrity": "sha512-LXVLVEq0NNOqK/fLJo3d0kfzd4sxwn2/h67/02pjCjfKDxgx1i9QqpvtHD8CrBnSSwMw5+dy11O7FRX5mkO7Cg=="
+      "version": "0.10.2",
+      "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.10.2.tgz",
+      "integrity": "sha512-UAYfiuvxLN4oyuqhJwd21Uxb4CNawrq6fPS/05Su5L4G+1TN+HVDJMUHNMobVQDFJRir2cLAODXwluaOKB7HFg=="
     }
   }
 }
diff --git a/package.json b/package.json
index 8a0376f0f305f79aba9ed5e33e43f9bbc27cd1a8..0490a9415e23746ff989add14c41778ccbdfee5c 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
   "license": "MIT",
   "scripts": {
     "ng": "ng",
-    "start": "ng serve --proxy-config proxy.conf.json",
+    "start": "ng serve",
     "build": "ng build --prod",
     "test": "ng test --watch=false --browsers=ChromeHeadlessCustom --source-map=false --code-coverage",
     "lint": "ng lint",
@@ -33,6 +33,7 @@
     "chart.js": "^2.7.3",
     "core-js": "^2.5.7",
     "is-docker": "^1.1.0",
+    "is-promise": "^4.0.0",
     "material-design-icons": "^3.0.1",
     "ngx-markdown": "^11.1.3",
     "ngx-matomo-v9": "^0.3.0",
diff --git a/proxy.conf.json b/proxy.conf.json
index d77258a7a9e95751643c69901ad3180eabda246e..d9c429863b7cd7022076d74148411dac622414e8 100644
--- a/proxy.conf.json
+++ b/proxy.conf.json
@@ -5,7 +5,8 @@
     "changeOrigin": true,
     "pathRewrite": {
       "^/spacy": ""
-    }
+    },
+    "logLevel": "debug"
   },
   "/api/ws/websocket": {
     "target": "ws://localhost:8080",
diff --git a/src/app/components/shared/header/header.component.html b/src/app/components/shared/header/header.component.html
index 6b6736b44a8e50281069a4066190c4c9c56de869..0c56aa0ccfd73a9ced7a025b6e46cdce9f75e8d4 100644
--- a/src/app/components/shared/header/header.component.html
+++ b/src/app/components/shared/header/header.component.html
@@ -65,7 +65,8 @@
 
           <!-- Moderator board / index -->
 
-          <ng-container *ngIf="user && user.role > 0 && (router.url.endsWith('/moderator/comments') || router.url.includes('/comment/'))">
+          <ng-container
+            *ngIf="user && user.role > 0 && (router.url.endsWith('/moderator/comments') || router.url.includes('/comment/'))">
 
             <button mat-menu-item
                     tabindex="0"
@@ -138,11 +139,8 @@
               </button>
 
 
-
-
               <button mat-menu-item
                       tabindex="0"
-                      *ngIf="deviceType !== 'mobile'"
                       routerLink="participant/room/{{shortId}}/comments/tagcloud">
                 <mat-icon>cloud
                 </mat-icon>
@@ -218,7 +216,8 @@
 
           <!-- Room General Options - bot -->
 
-          <ng-container *ngIf="user && user.role == 3 && !router.url.includes('/participant') && !router.url.includes('/comment/')">
+          <ng-container
+            *ngIf="user && user.role == 3 && !router.url.includes('/participant') && !router.url.includes('/comment/')">
 
             <button mat-menu-item
                     *ngIf="user"
@@ -239,7 +238,7 @@
           </ng-container>
 
           <button mat-menu-item
-                  *ngIf="user && user.role > 0 && !router.url.includes('/comment/')"
+                  *ngIf="user && user.role > 0 && !router.url.includes('/comment/') && !router.url.endsWith('/tagcloud')"
                   tabindex="0"
                   (click)="showQRDialog();">
             <mat-icon svgIcon="qrcode"
@@ -248,6 +247,42 @@
             <span>{{'header.room-qr' | translate}}</span>
           </button>
 
+          <button mat-menu-item
+                  *ngIf="router.url.endsWith('/tagcloud')"
+                  tabindex="0"
+                  (click)="navigateTopicCloudConfig()">
+            <mat-icon aria-label="Configuration Icon">cloud</mat-icon>
+            <span>{{'header.tag-cloud-config' | translate}}</span>
+          </button>
+
+          <button mat-menu-item
+                  *ngIf="router.url.endsWith('/tagcloud')"
+                  tabindex="0"
+                  (click)="navigateTopicCloudAdministration()">
+            <mat-icon aria-hidden="false" aria-label="Control Icon">edit</mat-icon>
+            <span>{{'header.tag-cloud-administration' | translate}}</span>
+          </button>
+
+          <button mat-menu-item
+                  *ngIf="router.url.endsWith('/tagcloud')"
+                  tabindex="0"
+                  (click)="navigateCreateQuestion();">
+            <mat-icon>
+              add
+            </mat-icon>
+            <span>{{'header.create-question' | translate}}</span>
+          </button>
+
+          <button mat-menu-item
+                  tabindex="0"
+                  *ngIf="router.url.endsWith('/tagcloud')"
+                  routerLink="participant/room/{{shortId}}/comments">
+            <mat-icon>
+              forum
+            </mat-icon>
+            <span>{{'header.back-to-questionboard' | translate}}</span>
+          </button>
+
         </ng-container>
 
       </div>
diff --git a/src/app/components/shared/header/header.component.ts b/src/app/components/shared/header/header.component.ts
index af57ebfd933f8e6c31dbf413172f57a926fbad05..10c46b58c5d226e313c1e82b8a901089ee5a4a4e 100644
--- a/src/app/components/shared/header/header.component.ts
+++ b/src/app/components/shared/header/header.component.ts
@@ -281,4 +281,12 @@ export class HeaderComponent implements OnInit {
     this.eventService.broadcast('navigate', 'createQuestion');
   }
 
+  public navigateTopicCloudConfig() {
+    this.eventService.broadcast('navigate', 'topicCloudConfig');
+  }
+
+  public navigateTopicCloudAdministration() {
+    this.eventService.broadcast('navigate', 'topicCloudAdministration');
+  }
+
 }
diff --git a/src/app/components/shared/tag-cloud/tag-cloud.component.html b/src/app/components/shared/tag-cloud/tag-cloud.component.html
index 5cdc8bf3a72815c8d63038145b163fe506e9f75a..b818b9809361b0c1c17050f04154a7fda5b2a9f3 100644
--- a/src/app/components/shared/tag-cloud/tag-cloud.component.html
+++ b/src/app/components/shared/tag-cloud/tag-cloud.component.html
@@ -1,38 +1,28 @@
 <ars-screen ars-flex-box>
-  <ars-row [height]="100">
-
+  <ars-row [height]="65">
   </ars-row>
-  <ars-row ars-flex-box>
-    <ars-col>
-
-    </ars-col>
-    <ars-col class="config">
-      <button mat-menu-item>
-                <mat-icon aria-hidden="false" aria-label="Configuration Icon">cloud</mat-icon>
-        <span>{{'tag-cloud.config' | translate}}</span>
-      </button>
-      <button mat-menu-item (click)="openAdministrationDialog()">
-                <mat-icon aria-hidden="false" aria-label="Control Icon">edit</mat-icon>
-        <span>{{'tag-cloud.administration' | translate}}</span>
-      </button>
-    </ars-col>
-  </ars-row>
-
-  <ars-fill ars-flex-box style="width:100%;height:100%;">
+  <mat-drawer-container class="spacyTagCloudContainer">
+    <mat-drawer [(opened)]="configurationOpen" position="start">
+      Test <!-- TODO: Gruppe 4 -->
+    </mat-drawer>
+    <mat-drawer-content>
+      <ars-fill ars-flex-box>
         <angular-tag-cloud
           class="spacyTagCloud"
+          (window:resize)="onResize($event)"
+          (afterInit)="initTagCloud()"
           [data]="data"
           [width]="options.width"
           [height]="options.height"
           [overflow]="options.overflow"
+          [delay]="options.delay"
+          [randomizeAngle]="false"
           [zoomOnHover]="zoomOnHoverOptions"
-          [realignOnResize]="true"
-          [log]='"debug"'>
+          [realignOnResize]="false">
         </angular-tag-cloud>
-  </ars-fill>
-
-  <ars-row [height]="100">
-
+      </ars-fill>
+    </mat-drawer-content>
+  </mat-drawer-container>
+  <ars-row [height]="37">
   </ars-row>
-
 </ars-screen>
diff --git a/src/app/components/shared/tag-cloud/tag-cloud.component.scss b/src/app/components/shared/tag-cloud/tag-cloud.component.scss
index 3520bd22be8fb8bd3be3c52a59aa8e0a2a1aeecd..c7f5e1cf8ec487752240700f43686de7bbec8c19 100644
--- a/src/app/components/shared/tag-cloud/tag-cloud.component.scss
+++ b/src/app/components/shared/tag-cloud/tag-cloud.component.scss
@@ -1,63 +1,15 @@
-.config {
-    span {
-        display: none;
-    }
-
-    mat-icon {
-        margin: 0;
-    }
+ars-fill {
+  width: calc(100% - 30px);
+  height: calc(100% - 30px);
+  margin: 15px;
 }
 
-.config:hover {
-    background-color:#052338;
-    span {
-        margin-left: 16px;
-        display: inline;
-    }
+mat-drawer-container {
+  height: 100%;
+  width: 100%;
+  position: fixed;
 }
 
-::ng-deep .spacyTagCloud > span {
-  &.w10 {
-    color: brown !important;
-    font-size: 380% !important;
-  }
-  &.w9 {
-    color: white !important;
-    font-size: 330% !important;
-  }
-  &.w8 {
-    color: tomato !important;
-    font-size: 280% !important;
-  }
-  &.w7 {
-    color: lightgreen !important;
-    font-size: 240% !important;
-  }
-  &.w6 {
-    color: gray !important;
-    font-size: 210% !important;
-  }
-  &.w5 {
-    color: pink !important;
-    font-size: 180% !important;
-  }
-  &.w4 {
-    color: orange !important;
-    font-size: 160% !important;
-  }
-  &.w3 {
-    color: yellow !important;
-    font-size: 140% !important;
-  }
-  &.w2 {
-    color: green !important;
-    font-size: 120% !important;
-  }
-  &.w1 {
-    color: lightblue !important;
-    font-size: 100% !important;
-  }
-  &:hover {
-    color: greenyellow !important;
-  }
+mat-drawer {
+  background-color: var(--dialog);
 }
diff --git a/src/app/components/shared/tag-cloud/tag-cloud.component.ts b/src/app/components/shared/tag-cloud/tag-cloud.component.ts
index 6891443b59810b28733c34d6aeddfa216796b13d..4cfd621062c94d1811e4b2e81b1151b796aa86e6 100644
--- a/src/app/components/shared/tag-cloud/tag-cloud.component.ts
+++ b/src/app/components/shared/tag-cloud/tag-cloud.component.ts
@@ -1,4 +1,4 @@
-import {Component, OnInit, ViewChild} from '@angular/core';
+import {Component, OnInit, ViewChild, Input} from '@angular/core';
 
 import {
   CloudData,
@@ -10,11 +10,36 @@ import {
 import {CommentService} from '../../../services/http/comment.service';
 import {Result, SpacyService} from '../../../services/http/spacy.service';
 import {Comment} from '../../../models/comment';
-import {LanguageService} from "../../../services/util/language.service";
-import {TranslateService} from "@ngx-translate/core";
-import {QuestionWallComment} from "../questionwall/QuestionWallComment";
-import { MatDialog } from '@angular/material/dialog';
-import { TopicCloudAdministrationComponent } from '../_dialogs/topic-cloud-administration/topic-cloud-administration.component';
+import {LanguageService} from '../../../services/util/language.service';
+import {TranslateService} from '@ngx-translate/core';
+import {CreateCommentComponent} from '../_dialogs/create-comment/create-comment.component';
+import {MatDialog} from '@angular/material/dialog';
+import {User} from '../../../models/user';
+import {Room} from '../../../models/room';
+import {NotificationService} from '../../../services/util/notification.service';
+import {EventService} from '../../../services/util/event.service';
+import {AuthenticationService} from '../../../services/http/authentication.service';
+import {ActivatedRoute} from '@angular/router';
+import {UserRole} from '../../../models/user-roles.enum';
+import {RoomService} from '../../../services/http/room.service';
+import {ThemeService} from '../../../../theme/theme.service';
+import {CloudParameters} from './tag-cloud.interface';
+
+class CustomPosition implements Position {
+  left: number;
+  top: number;
+
+  constructor(public relativeLeft: number,
+              public relativeTop: number) {
+  }
+
+  updatePosition(width: number, height: number, text: string, style: CSSStyleDeclaration) {
+    const offsetY = parseFloat(style.height) / 2;
+    const offsetX = parseFloat(style.width) / 2;
+    this.left = width * this.relativeLeft - offsetX;
+    this.top = height * this.relativeTop - offsetY;
+  }
+}
 
 class TagComment implements CloudData {
   constructor(public color: string,
@@ -28,17 +53,90 @@ class TagComment implements CloudData {
   }
 }
 
-const weight2color = {
-  1: 'blue',
-  2: 'green',
-  3: 'yellow',
-  4: 'orange',
-  5: 'pink',
-  6: 'gray',
-  7: 'lightgreen',
-  8: 'tomato',
-  9: 'white',
-  10: 'brown'
+//CSS styles Array
+type TagCloudStyleData = [
+  string, // hover
+  string, // w1
+  string, // w2
+  string, // w3
+  string, // w4
+  string, // w5
+  string, // w6
+  string, // w7
+  string, // w8
+  string, // w9
+  string, // w10
+  string // background
+];
+
+const colorRegex = /rgba?\((\d+), (\d+), (\d+)(?:, (\d(?:\.\d+)?))?\)/;
+const defaultColors: string[] = [
+  // variable, fallback
+  'var(--secondary, greenyellow)', // hover
+  'var(--moderator, lightblue)', // w1
+  'var(--blue, green)', // w2
+  'var(--grey, yellow)', // w3
+  'var(--red, orange)', // w4
+  'var(--primary, pink)', // w5
+  'var(--yellow, gray)', // w6
+  'var(--on-background, lightgreen)', // w7
+  'var(--purple, tomato)', // w8
+  'var(--magenta, white)', // w9
+  'var(--light-green, brown)', // w10
+  'var(--background, black)' //background
+];
+
+const getResolvedDefaultColors = (): string[] => {
+  const elem = document.createElement('p');
+  elem.style.display = 'none';
+  document.body.appendChild(elem);
+  const results = [];
+  for (const color of defaultColors) {
+    elem.style.backgroundColor = 'rgb(0, 0, 0)'; // fallback
+    elem.style.backgroundColor = color;
+    const result = window.getComputedStyle(elem).backgroundColor.match(colorRegex);
+    const r = parseInt(result[1], 10);
+    const g = parseInt(result[2], 10);
+    const b = parseInt(result[3], 10);
+    results.push(`#${((r * 256 + g) * 256 + b).toString(16).padStart(6, '0')}`);
+  }
+  elem.remove();
+  return results;
+};
+
+const setGlobalStyles = (styles: TagCloudStyleData): void => {
+  let customTagCloudStyles = document.getElementById('tagCloudStyles') as HTMLStyleElement;
+  if (!customTagCloudStyles) {
+    customTagCloudStyles = document.createElement('style');
+    customTagCloudStyles.id = 'tagCloudStyles';
+    document.head.appendChild(customTagCloudStyles);
+  }
+  const rules = customTagCloudStyles.sheet.cssRules;
+  for (let i = rules.length - 1; i >= 0; i--) {
+    customTagCloudStyles.sheet.deleteRule(i);
+  }
+  for (let i = 1; i <= 10; i++) {
+    customTagCloudStyles.sheet.insertRule('.spacyTagCloud > span.w' + i + ' { ' + styles[i] + ' }', rules.length);
+    customTagCloudStyles.sheet.insertRule('.spacyTagCloud > span.w' + i + ' > a { ' + styles[i] + ' }', rules.length);
+  }
+  customTagCloudStyles.sheet.insertRule('.spacyTagCloud > span:hover { ' + styles[0] + ' }', rules.length);
+  customTagCloudStyles.sheet.insertRule('.spacyTagCloud > span:hover > a { ' + styles[0] + ' }', rules.length);
+  customTagCloudStyles.sheet.insertRule('.spacyTagCloudContainer {' + styles[11] + '}', rules.length);
+};
+
+const getDefaultCloudParameters = (): CloudParameters => {
+  const resDefaultColors = getResolvedDefaultColors();
+  return {
+    backgroundColor: resDefaultColors[11],
+    fontColor: resDefaultColors[0],
+    fontSizeMin: 100,
+    fontSizeMax: 380,
+    hoverScale: 1.3,
+    hoverTime: 0.6,
+    hoverDelay: 0.4,
+    delayWord: 0,
+    randomAngles: false
+  }
 };
 
 @Component({
@@ -49,30 +147,45 @@ const weight2color = {
 export class TagCloudComponent implements OnInit {
 
   @ViewChild(TCloudComponent, {static: false}) child: TCloudComponent;
-  roomId: string;
+  @Input() user: User;
+  @Input() roomId: string;
+  room: Room;
+  headerInterface = null;
+  directSend = true;
+  shortId: string;
   options: CloudOptions = {
     // if width is between 0 and 1 it will be set to the width of the upper element multiplied by the value
-    width: 1,
+    width: 0.99,
     // if height is between 0 and 1 it will be set to the height of the upper element multiplied by the value
-    height: 1,
+    height: 0.99,
     overflow: false,
-    font: 'Georgia' // not working
+    font: 'Georgia', // not working
+    delay: 0
   };
   zoomOnHoverOptions: ZoomOnHoverOptions = {
     scale: 1.3, // Elements will become 130 % of current size on hover
     transitionTime: 0.6, // it will take 0.6 seconds until the zoom level defined in scale property has been reached
-    delay: 0.4,// Zoom will take affect after 0.4 seconds
-    color: 'red'
+    delay: 0.4 // Zoom will take affect after 0.4 seconds
   };
-
+  userRole: UserRole;
   data: CloudData[] = [];
-
+  sorted = false;
+  debounceTimer = 0;
+  lastDebounceTime = 0;
+  configurationOpen = false;
+  randomizeAngle = false;
 
   constructor(private commentService: CommentService,
               private spacyService: SpacyService,
               private langService: LanguageService,
               private translateService: TranslateService,
-              public dialog: MatDialog) {
+              public dialog: MatDialog,
+              private notificationService: NotificationService,
+              public eventService: EventService,
+              private authenticationService: AuthenticationService,
+              private route: ActivatedRoute,
+              protected roomService: RoomService,
+              private themeService: ThemeService) {
     this.roomId = localStorage.getItem('roomId');
     this.langService.langEmitter.subscribe(lang => {
       this.translateService.use(lang);
@@ -80,10 +193,120 @@ export class TagCloudComponent implements OnInit {
   }
 
   ngOnInit(): void {
+    this.headerInterface = this.eventService.on<string>('navigate').subscribe(e => {
+      if (e === 'createQuestion') {
+        this.openCreateDialog();
+      } else if (e === 'topicCloudConfig') {
+        this.configurationOpen = !this.configurationOpen;
+      } else if (e === 'topicCloudAdministration') {
+        // TODO Group 5: OPEN Topic Cloud Administration
+      }
+    });
+    this.authenticationService.watchUser.subscribe(newUser => {
+      if (newUser) {
+        this.user = newUser;
+      }
+    });
+    this.userRole = this.route.snapshot.data.roles[0];
+    this.route.params.subscribe(params => {
+      this.shortId = params['shortId'];
+      this.authenticationService.guestLogin(UserRole.PARTICIPANT).subscribe(r => {
+        this.roomService.getRoomByShortId(this.shortId).subscribe(room => {
+          this.room = room;
+          this.roomId = room.id;
+          this.directSend = this.room.directSend;
+          if (!this.authenticationService.hasAccess(this.shortId, UserRole.PARTICIPANT)) {
+            this.roomService.addToHistory(this.room.id);
+            this.authenticationService.setAccess(this.shortId, UserRole.PARTICIPANT);
+          }
+        });
+      });
+    });
     this.translateService.use(localStorage.getItem('currentLang'));
     this.commentService.getAckComments(this.roomId).subscribe((comments: Comment[]) => {
       this.analyse(comments);
     });
+    this.themeService.getTheme().subscribe(() => {
+      if (this.child) {
+        setTimeout(() => {
+          this.setCloudParameters(this.getCurrentCloudParameters(), false);
+          this.updateTagCloud();
+        }, 1);
+      }
+    });
+  }
+
+  initTagCloud() {
+    setTimeout(() => {
+      this.setCloudParameters(this.getCurrentCloudParameters(), false);
+    });
+  }
+
+  resetColorsToTheme() {
+    this.setCloudParameters(getDefaultCloudParameters());
+  }
+
+  getCurrentCloudParameters(): CloudParameters {
+    const jsonData = localStorage.getItem('tagCloudConfiguration');
+    const elem: CloudParameters = jsonData != null ? JSON.parse(jsonData) : null;
+    return elem || getDefaultCloudParameters();
+  }
+
+  setCloudParameters(data: CloudParameters, save = true): void {
+    const arr = getResolvedDefaultColors();
+    arr[0] = data.fontColor;
+    arr[11] = data.backgroundColor;
+    const fontRange = (data.fontSizeMax - data.fontSizeMin) / 10;
+    const styles = arr.map((e, i) => {
+      if (i > 10) {
+        return 'background-color: ' + e + ';';
+      } else if (i > 0) {
+        return 'color: ' + e + '; font-size: ' + (data.fontSizeMin + fontRange * i).toFixed(0) + '%;';
+      } else {
+        return 'color: ' + e + ';';
+      }
+    });
+    setGlobalStyles(styles as TagCloudStyleData);
+    this.zoomOnHoverOptions.delay = data.hoverDelay;
+    this.zoomOnHoverOptions.scale = data.hoverScale;
+    this.zoomOnHoverOptions.transitionTime = data.hoverTime;
+    this.options.delay = data.delayWord;
+    this.randomizeAngle = data.randomAngles;
+    if (this.randomizeAngle) {
+      this.data.forEach(e => e.rotate = Math.floor(Math.random() * 30 - 15));
+    } else {
+      this.data.forEach(e => e.rotate = 0);
+    }
+    this.updateTagCloud();
+    if (save) {
+      localStorage.setItem('tagCloudConfiguration', JSON.stringify(data));
+    }
+  }
+
+  onResize(event: UIEvent): any {
+    this.updateTagCloud();
+  }
+
+  sortPositionsAlphabetically(sort: boolean): void {
+    if (!sort) {
+      this.sorted = false;
+      this.data.forEach(e => e.position = null);
+      return;
+    }
+    this.sorted = true;
+    if (!this.data.length) {
+      return;
+    }
+    this.data.sort((a, b) => a.text.localeCompare(b.text));
+    const lines = Math.floor(Math.sqrt(this.data.length - 1) + 1);
+    const divided = Math.floor(this.data.length / lines);
+    let remainder = this.data.length - divided * lines;
+    for (let i = 0, line = 0; line < lines; line++) {
+      const size = divided + (--remainder >= 0 ? 1 : 0);
+      for (let k = 0; k < size; k++, i++) {
+        this.data[i].position = new CustomPosition((k + 1) / (size + 1), (line + 1) / (lines + 1));
+      }
+    }
   }
 
   analyse(comments: Comment[]) {
@@ -91,31 +314,93 @@ export class TagCloudComponent implements OnInit {
 
     this.spacyService.analyse(commentsConcatenated, 'de').subscribe((res: Result) => {
       const map = new Map<string, number>();
-      let maxCount = 0;
       res.words.filter(w => ['NE', 'NN', 'NMP', 'NNE'].indexOf(w.tag) >= 0).forEach(elem => {
         const count = (map.get(elem.text) || 0) + 1;
-        if (count > maxCount) {
-          maxCount = count;
-        }
         map.set(elem.text, count);
       });
-      this.data.length = 0; // Clear array
       map.forEach((val, key) => {
-          const weight = 9 * val / maxCount + 1;
-          console.log(weight + ' ' + typeof weight);
           this.data.push(new TagComment(null,
             true, null, null,
-            /*Math.floor(Math.random() * 30 - 15)*/0, key,
-            'TODO', weight));
+            this.randomizeAngle ? Math.floor(Math.random() * 30 - 15) : 0, key,
+            'TODO', val));
         }
       );
+      this.sortPositionsAlphabetically(this.sorted);
+      this.updateTagCloud();
+    });
+  }
+
+  updateTagCloud() {
+    if (this.sorted && this.data.length) {
+      if (!this.child.cloudDataHtmlElements || !this.child.cloudDataHtmlElements.length) {
+        this.child.reDraw();
+      }
+      const width = this.child.calculatedWidth;
+      const height = this.child.calculatedHeight;
+      this.data.forEach((e, i) => {
+        (e.position as CustomPosition).updatePosition(width, height, e.text,
+          window.getComputedStyle(this.child.cloudDataHtmlElements[i]));
+      });
+    }
+    const debounceTime = 1_000;
+    const current = new Date().getTime();
+    const diff = current - this.lastDebounceTime;
+    if (diff >= debounceTime) {
+      this.lastDebounceTime = current;
       this.child.reDraw();
+    } else {
+      clearTimeout(this.debounceTimer);
+      this.debounceTimer = setTimeout(() => {
+        this.lastDebounceTime = new Date().getTime();
+        this.child.reDraw();
+      }, debounceTime - diff);
+    }
+  }
+
+  openCreateDialog(): void {
+    const dialogRef = this.dialog.open(CreateCommentComponent, {
+      width: '900px',
+      maxWidth: 'calc( 100% - 50px )',
+      maxHeight: 'calc( 100vh - 50px )',
+      autoFocus: false,
     });
+    dialogRef.componentInstance.user = this.user;
+    dialogRef.componentInstance.roomId = this.roomId;
+    let tags;
+    tags = [];
+    if (this.room.tags) {
+      tags = this.room.tags;
+    }
+    dialogRef.componentInstance.tags = tags;
+    dialogRef.afterClosed()
+      .subscribe(result => {
+        if (result) {
+          this.send(result);
+        } else {
+          return;
+        }
+      });
   }
 
-  openAdministrationDialog(){
-      this.dialog.open(TopicCloudAdministrationComponent, {
-        minWidth: '50%'
+  send(comment: Comment): void {
+    if (this.directSend) {
+      this.translateService.get('comment-list.comment-sent').subscribe(msg => {
+        this.notificationService.show(msg);
       });
+      comment.ack = true;
+    } else {
+      if (this.userRole === 1 || this.userRole === 2 || this.userRole === 3) {
+        this.translateService.get('comment-list.comment-sent').subscribe(msg => {
+          this.notificationService.show(msg);
+        });
+        comment.ack = true;
+      }
+      if (this.userRole === 0) {
+        this.translateService.get('comment-list.comment-sent-to-moderator').subscribe(msg => {
+          this.notificationService.show(msg);
+        });
+      }
+    }
+    this.commentService.addComment(comment).subscribe();
   }
 }
diff --git a/src/app/components/shared/tag-cloud/tag-cloud.interface.ts b/src/app/components/shared/tag-cloud/tag-cloud.interface.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b3faae6f98bc4f485fc2ed4bbaeaefdbc71be061
--- /dev/null
+++ b/src/app/components/shared/tag-cloud/tag-cloud.interface.ts
@@ -0,0 +1,38 @@
+export interface CloudParameters {
+  /**
+   * Background color of the Tag-cloud
+   */
+  backgroundColor: string;
+  /**
+   * Color when hovering over the elements
+   */
+  fontColor: string;
+  /**
+   * Percentage values for the weight classes, by interpolation all classes are filled with values
+   */
+  fontSizeMin: number;
+  /**
+   * Percentage values fot the weight classes, by interpolation all classes are filled with values
+   */
+  fontSizeMax: number;
+  /**
+   * Describes scaling when hovering
+   */
+  hoverScale: number;
+  /**
+   * Time for hovering in ms
+   */
+  hoverTime: number;
+  /**
+   * Time before hover animation starts in ms
+   */
+  hoverDelay: number;
+  /**
+   * Time for delay in ms between each word during build-up
+   */
+  delayWord: number;
+  /**
+   * Enables random angles
+   */
+  randomAngles: boolean;
+}
diff --git a/src/assets/i18n/home/de.json b/src/assets/i18n/home/de.json
index 75c63c88f35ba3bd3d76f93bf6a75885d9edb36e..867cde48d834dcf2268d4cff10388c7b462c31c2 100644
--- a/src/assets/i18n/home/de.json
+++ b/src/assets/i18n/home/de.json
@@ -82,9 +82,11 @@
     "moderationboard": "Zum Index",
     "create-question": "Frage stellen",
     "questionwall": "Fragen-Fokus",
-    "tag-cloud": "Wortwolke",
+    "tag-cloud": "Themenwolke",
     "fullscreen": "Text skalieren",
-    "motd": "News"
+    "motd": "News",
+    "tag-cloud-config": "Wolkenansicht ändern",
+    "tag-cloud-administration": "Wolkenthemen editieren"
   },
   "help": {
     "cancel": "Schließen",
diff --git a/src/assets/i18n/home/en.json b/src/assets/i18n/home/en.json
index 51e5bfc71d3a61e081631a1c2303dbca1a456c27..aa0bf64d612e7d8cfe9fec3b78e6051dd101d00d 100644
--- a/src/assets/i18n/home/en.json
+++ b/src/assets/i18n/home/en.json
@@ -85,7 +85,9 @@
     "questionwall": "Question focus",
     "tag-cloud": "Topic cloud",
     "fullscreen": "Text scaling",
-    "motd": "News"
+    "motd": "News",
+    "tag-cloud-config": "Modify cloud view",
+    "tag-cloud-administration": "Edit cloud topics"
   },
   "help": {
     "cancel": "Close",