diff --git a/package.json b/package.json
index 3b376af3d6086c1199dc471ba4e696ba5316fbef..d0f89c87238bd596e908e1bdbd7d9e75cd8f7c40 100644
--- a/package.json
+++ b/package.json
@@ -122,6 +122,7 @@
         "typescript": "^5.0.2",
         "vrp-vue-resizable": "1.2.7",
         "vue": "^2.6.12",
+        "vue-dragscroll": "^3.0.1",
         "vue-gettext": "^2.1.12",
         "vue-loader": "^15.9.8",
         "vue-router": "^3.5.1",
diff --git a/public/assets/images/icons/black/hand.svg b/public/assets/images/icons/black/hand.svg
new file mode 100644
index 0000000000000000000000000000000000000000..56988ebde4d067ff8ee77f3abba10ced8d27bdc9
--- /dev/null
+++ b/public/assets/images/icons/black/hand.svg
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><style>.f{fill:none;}.g{fill:#000;}</style></defs><g id="c"><rect class="f" width="64" height="64"/></g><g id="d"><g id="e"><path class="g" d="m50.71,13h-.42c-.82,0-1.59.2-2.29.54v-1.04c0-1.97-1.04-3.69-2.59-4.66,0,0,0,0,0,0-.26-.16-.52-.3-.8-.42-.05-.02-.09-.03-.14-.05-.25-.09-.5-.17-.77-.23-.02,0-.04-.01-.06-.02,0,0-.01,0-.02,0-.32-.07-.65-.1-.99-.11-.03,0-.05,0-.08,0-.02,0-.03,0-.05,0-.3,0-.59.03-.87.08-.08.01-.16.03-.24.05-.22.05-.44.11-.65.18-.08.03-.16.05-.24.08-.27.11-.54.23-.79.38-.73-2.19-2.79-3.76-5.22-3.76-2.74,0-5,2-5.42,4.62-.74-.38-1.56-.62-2.45-.62h-.25c-2.97,0-5.37,2.41-5.37,5.37v19.91l-3.18-6c-.93-1.76-2.74-2.76-4.61-2.76-.82,0-1.65.19-2.42.6-2.55,1.33-3.53,4.45-2.19,6.98l12.17,22.9c2.01,3.99,4.02,4.99,7.04,4.99h17.2c7.18,0,11-5.82,11-12.99v-28.71c0-2.92-2.37-5.29-5.29-5.29Zm-5.71,43h-17.2c-1.32,0-2.05,0-3.46-2.8-.01-.03-.03-.05-.04-.08l-12.16-22.9c-.18-.35-.15-.67-.08-.87.07-.21.22-.5.6-.69.18-.09.37-.14.56-.14.36,0,.82.17,1.07.63l3.18,6c.71,1.33,2.08,2.13,3.53,2.13.32,0,.64-.04.97-.12,1.78-.44,3.03-2.04,3.03-3.88V13.37c0-.76.62-1.37,1.37-1.37h.25c.19,0,.39.06.61.17.04.02.08.03.12.04.39.24.64.67.64,1.16v16.13c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5V9.5c0-.08.01-.16.02-.24,0,0,0,0,0,0,.03-.17.09-.33.17-.48,0-.01.01-.03.02-.04.08-.13.18-.25.29-.35.02-.02.04-.04.07-.05.12-.09.24-.17.38-.23.02,0,.04-.01.07-.02.15-.05.31-.09.48-.09s.32.03.46.08c.03,0,.06.02.08.03.13.05.25.12.36.2.02.01.04.03.06.05.22.18.37.42.45.65.06.16.09.32.09.5v18c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5v-15c0-.56.31-1.04.76-1.3.1-.06.2-.1.31-.13.02,0,.04-.02.07-.02.12-.03.24-.05.36-.05.83,0,1.5.67,1.5,1.5v19c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5v-13.21c0-.5.29-.92.71-1.14,0,0,.02,0,.03-.01.06-.03.12-.05.18-.07.12-.04.24-.07.37-.07h.42c.71,0,1.29.58,1.29,1.29v28.71c0,2.11-.5,9-7,9Z"/></g></g></svg>
\ No newline at end of file
diff --git a/public/assets/images/icons/blue/hand.svg b/public/assets/images/icons/blue/hand.svg
new file mode 100644
index 0000000000000000000000000000000000000000..fccdf6ab90c2149914b7839489b286430cd59bbc
--- /dev/null
+++ b/public/assets/images/icons/blue/hand.svg
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><style>.f{fill:none;}.g{fill:#28497c;}</style></defs><g id="c"><rect class="f" width="64" height="64"/></g><g id="d"><g id="e"><path class="g" d="m50.71,13h-.42c-.82,0-1.59.2-2.29.54v-1.04c0-1.97-1.04-3.69-2.59-4.66,0,0,0,0,0,0-.26-.16-.52-.3-.8-.42-.05-.02-.09-.03-.14-.05-.25-.09-.5-.17-.77-.23-.02,0-.04-.01-.06-.02,0,0-.01,0-.02,0-.32-.07-.65-.1-.99-.11-.03,0-.05,0-.08,0-.02,0-.03,0-.05,0-.3,0-.59.03-.87.08-.08.01-.16.03-.24.05-.22.05-.44.11-.65.18-.08.03-.16.05-.24.08-.27.11-.54.23-.79.38-.73-2.19-2.79-3.76-5.22-3.76-2.74,0-5,2-5.42,4.62-.74-.38-1.56-.62-2.45-.62h-.25c-2.97,0-5.37,2.41-5.37,5.37v19.91l-3.18-6c-.93-1.76-2.74-2.76-4.61-2.76-.82,0-1.65.19-2.42.6-2.55,1.33-3.53,4.45-2.19,6.98l12.17,22.9c2.01,3.99,4.02,4.99,7.04,4.99h17.2c7.18,0,11-5.82,11-12.99v-28.71c0-2.92-2.37-5.29-5.29-5.29Zm-5.71,43h-17.2c-1.32,0-2.05,0-3.46-2.8-.01-.03-.03-.05-.04-.08l-12.16-22.9c-.18-.35-.15-.67-.08-.87.07-.21.22-.5.6-.69.18-.09.37-.14.56-.14.36,0,.82.17,1.07.63l3.18,6c.71,1.33,2.08,2.13,3.53,2.13.32,0,.64-.04.97-.12,1.78-.44,3.03-2.04,3.03-3.88V13.37c0-.76.62-1.37,1.37-1.37h.25c.19,0,.39.06.61.17.04.02.08.03.12.04.39.24.64.67.64,1.16v16.13c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5V9.5c0-.08.01-.16.02-.24,0,0,0,0,0,0,.03-.17.09-.33.17-.48,0-.01.01-.03.02-.04.08-.13.18-.25.29-.35.02-.02.04-.04.07-.05.12-.09.24-.17.38-.23.02,0,.04-.01.07-.02.15-.05.31-.09.48-.09s.32.03.46.08c.03,0,.06.02.08.03.13.05.25.12.36.2.02.01.04.03.06.05.22.18.37.42.45.65.06.16.09.32.09.5v18c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5v-15c0-.56.31-1.04.76-1.3.1-.06.2-.1.31-.13.02,0,.04-.02.07-.02.12-.03.24-.05.36-.05.83,0,1.5.67,1.5,1.5v19c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5v-13.21c0-.5.29-.92.71-1.14,0,0,.02,0,.03-.01.06-.03.12-.05.18-.07.12-.04.24-.07.37-.07h.42c.71,0,1.29.58,1.29,1.29v28.71c0,2.11-.5,9-7,9Z"/></g></g></svg>
\ No newline at end of file
diff --git a/public/assets/images/icons/green/hand.svg b/public/assets/images/icons/green/hand.svg
new file mode 100644
index 0000000000000000000000000000000000000000..5a9d85152e53c0eb9a51d0c6081407d259eeaf24
--- /dev/null
+++ b/public/assets/images/icons/green/hand.svg
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><style>.f{fill:none;}.g{fill:#00962d;}</style></defs><g id="c"><rect class="f" width="64" height="64"/></g><g id="d"><g id="e"><path class="g" d="m50.71,13h-.42c-.82,0-1.59.2-2.29.54v-1.04c0-1.97-1.04-3.69-2.59-4.66,0,0,0,0,0,0-.26-.16-.52-.3-.8-.42-.05-.02-.09-.03-.14-.05-.25-.09-.5-.17-.77-.23-.02,0-.04-.01-.06-.02,0,0-.01,0-.02,0-.32-.07-.65-.1-.99-.11-.03,0-.05,0-.08,0-.02,0-.03,0-.05,0-.3,0-.59.03-.87.08-.08.01-.16.03-.24.05-.22.05-.44.11-.65.18-.08.03-.16.05-.24.08-.27.11-.54.23-.79.38-.73-2.19-2.79-3.76-5.22-3.76-2.74,0-5,2-5.42,4.62-.74-.38-1.56-.62-2.45-.62h-.25c-2.97,0-5.37,2.41-5.37,5.37v19.91l-3.18-6c-.93-1.76-2.74-2.76-4.61-2.76-.82,0-1.65.19-2.42.6-2.55,1.33-3.53,4.45-2.19,6.98l12.17,22.9c2.01,3.99,4.02,4.99,7.04,4.99h17.2c7.18,0,11-5.82,11-12.99v-28.71c0-2.92-2.37-5.29-5.29-5.29Zm-5.71,43h-17.2c-1.32,0-2.05,0-3.46-2.8-.01-.03-.03-.05-.04-.08l-12.16-22.9c-.18-.35-.15-.67-.08-.87.07-.21.22-.5.6-.69.18-.09.37-.14.56-.14.36,0,.82.17,1.07.63l3.18,6c.71,1.33,2.08,2.13,3.53,2.13.32,0,.64-.04.97-.12,1.78-.44,3.03-2.04,3.03-3.88V13.37c0-.76.62-1.37,1.37-1.37h.25c.19,0,.39.06.61.17.04.02.08.03.12.04.39.24.64.67.64,1.16v16.13c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5V9.5c0-.08.01-.16.02-.24,0,0,0,0,0,0,.03-.17.09-.33.17-.48,0-.01.01-.03.02-.04.08-.13.18-.25.29-.35.02-.02.04-.04.07-.05.12-.09.24-.17.38-.23.02,0,.04-.01.07-.02.15-.05.31-.09.48-.09s.32.03.46.08c.03,0,.06.02.08.03.13.05.25.12.36.2.02.01.04.03.06.05.22.18.37.42.45.65.06.16.09.32.09.5v18c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5v-15c0-.56.31-1.04.76-1.3.1-.06.2-.1.31-.13.02,0,.04-.02.07-.02.12-.03.24-.05.36-.05.83,0,1.5.67,1.5,1.5v19c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5v-13.21c0-.5.29-.92.71-1.14,0,0,.02,0,.03-.01.06-.03.12-.05.18-.07.12-.04.24-.07.37-.07h.42c.71,0,1.29.58,1.29,1.29v28.71c0,2.11-.5,9-7,9Z"/></g></g></svg>
\ No newline at end of file
diff --git a/public/assets/images/icons/grey/hand.svg b/public/assets/images/icons/grey/hand.svg
new file mode 100644
index 0000000000000000000000000000000000000000..bcc47b068719fe0e15772e52f44ca74e7a8dc517
--- /dev/null
+++ b/public/assets/images/icons/grey/hand.svg
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><style>.f{fill:none;}.g{fill:#6e6e6e;}</style></defs><g id="c"><rect class="f" width="64" height="64"/></g><g id="d"><g id="e"><path class="g" d="m50.71,13h-.42c-.82,0-1.59.2-2.29.54v-1.04c0-1.97-1.04-3.69-2.59-4.66,0,0,0,0,0,0-.26-.16-.52-.3-.8-.42-.05-.02-.09-.03-.14-.05-.25-.09-.5-.17-.77-.23-.02,0-.04-.01-.06-.02,0,0-.01,0-.02,0-.32-.07-.65-.1-.99-.11-.03,0-.05,0-.08,0-.02,0-.03,0-.05,0-.3,0-.59.03-.87.08-.08.01-.16.03-.24.05-.22.05-.44.11-.65.18-.08.03-.16.05-.24.08-.27.11-.54.23-.79.38-.73-2.19-2.79-3.76-5.22-3.76-2.74,0-5,2-5.42,4.62-.74-.38-1.56-.62-2.45-.62h-.25c-2.97,0-5.37,2.41-5.37,5.37v19.91l-3.18-6c-.93-1.76-2.74-2.76-4.61-2.76-.82,0-1.65.19-2.42.6-2.55,1.33-3.53,4.45-2.19,6.98l12.17,22.9c2.01,3.99,4.02,4.99,7.04,4.99h17.2c7.18,0,11-5.82,11-12.99v-28.71c0-2.92-2.37-5.29-5.29-5.29Zm-5.71,43h-17.2c-1.32,0-2.05,0-3.46-2.8-.01-.03-.03-.05-.04-.08l-12.16-22.9c-.18-.35-.15-.67-.08-.87.07-.21.22-.5.6-.69.18-.09.37-.14.56-.14.36,0,.82.17,1.07.63l3.18,6c.71,1.33,2.08,2.13,3.53,2.13.32,0,.64-.04.97-.12,1.78-.44,3.03-2.04,3.03-3.88V13.37c0-.76.62-1.37,1.37-1.37h.25c.19,0,.39.06.61.17.04.02.08.03.12.04.39.24.64.67.64,1.16v16.13c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5V9.5c0-.08.01-.16.02-.24,0,0,0,0,0,0,.03-.17.09-.33.17-.48,0-.01.01-.03.02-.04.08-.13.18-.25.29-.35.02-.02.04-.04.07-.05.12-.09.24-.17.38-.23.02,0,.04-.01.07-.02.15-.05.31-.09.48-.09s.32.03.46.08c.03,0,.06.02.08.03.13.05.25.12.36.2.02.01.04.03.06.05.22.18.37.42.45.65.06.16.09.32.09.5v18c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5v-15c0-.56.31-1.04.76-1.3.1-.06.2-.1.31-.13.02,0,.04-.02.07-.02.12-.03.24-.05.36-.05.83,0,1.5.67,1.5,1.5v19c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5v-13.21c0-.5.29-.92.71-1.14,0,0,.02,0,.03-.01.06-.03.12-.05.18-.07.12-.04.24-.07.37-.07h.42c.71,0,1.29.58,1.29,1.29v28.71c0,2.11-.5,9-7,9Z"/></g></g></svg>
\ No newline at end of file
diff --git a/public/assets/images/icons/red/hand.svg b/public/assets/images/icons/red/hand.svg
new file mode 100644
index 0000000000000000000000000000000000000000..30443fb3d4d0f823dba8eb37f2d6eb425f369287
--- /dev/null
+++ b/public/assets/images/icons/red/hand.svg
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><style>.f{fill:none;}.g{fill:#cb1800;}</style></defs><g id="c"><rect class="f" width="64" height="64"/></g><g id="d"><g id="e"><path class="g" d="m50.71,13h-.42c-.82,0-1.59.2-2.29.54v-1.04c0-1.97-1.04-3.69-2.59-4.66,0,0,0,0,0,0-.26-.16-.52-.3-.8-.42-.05-.02-.09-.03-.14-.05-.25-.09-.5-.17-.77-.23-.02,0-.04-.01-.06-.02,0,0-.01,0-.02,0-.32-.07-.65-.1-.99-.11-.03,0-.05,0-.08,0-.02,0-.03,0-.05,0-.3,0-.59.03-.87.08-.08.01-.16.03-.24.05-.22.05-.44.11-.65.18-.08.03-.16.05-.24.08-.27.11-.54.23-.79.38-.73-2.19-2.79-3.76-5.22-3.76-2.74,0-5,2-5.42,4.62-.74-.38-1.56-.62-2.45-.62h-.25c-2.97,0-5.37,2.41-5.37,5.37v19.91l-3.18-6c-.93-1.76-2.74-2.76-4.61-2.76-.82,0-1.65.19-2.42.6-2.55,1.33-3.53,4.45-2.19,6.98l12.17,22.9c2.01,3.99,4.02,4.99,7.04,4.99h17.2c7.18,0,11-5.82,11-12.99v-28.71c0-2.92-2.37-5.29-5.29-5.29Zm-5.71,43h-17.2c-1.32,0-2.05,0-3.46-2.8-.01-.03-.03-.05-.04-.08l-12.16-22.9c-.18-.35-.15-.67-.08-.87.07-.21.22-.5.6-.69.18-.09.37-.14.56-.14.36,0,.82.17,1.07.63l3.18,6c.71,1.33,2.08,2.13,3.53,2.13.32,0,.64-.04.97-.12,1.78-.44,3.03-2.04,3.03-3.88V13.37c0-.76.62-1.37,1.37-1.37h.25c.19,0,.39.06.61.17.04.02.08.03.12.04.39.24.64.67.64,1.16v16.13c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5V9.5c0-.08.01-.16.02-.24,0,0,0,0,0,0,.03-.17.09-.33.17-.48,0-.01.01-.03.02-.04.08-.13.18-.25.29-.35.02-.02.04-.04.07-.05.12-.09.24-.17.38-.23.02,0,.04-.01.07-.02.15-.05.31-.09.48-.09s.32.03.46.08c.03,0,.06.02.08.03.13.05.25.12.36.2.02.01.04.03.06.05.22.18.37.42.45.65.06.16.09.32.09.5v18c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5v-15c0-.56.31-1.04.76-1.3.1-.06.2-.1.31-.13.02,0,.04-.02.07-.02.12-.03.24-.05.36-.05.83,0,1.5.67,1.5,1.5v19c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5v-13.21c0-.5.29-.92.71-1.14,0,0,.02,0,.03-.01.06-.03.12-.05.18-.07.12-.04.24-.07.37-.07h.42c.71,0,1.29.58,1.29,1.29v28.71c0,2.11-.5,9-7,9Z"/></g></g></svg>
\ No newline at end of file
diff --git a/public/assets/images/icons/white/hand.svg b/public/assets/images/icons/white/hand.svg
new file mode 100644
index 0000000000000000000000000000000000000000..7be6a2773204aa38ca4909fa779fd63eac8015a6
--- /dev/null
+++ b/public/assets/images/icons/white/hand.svg
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><style>.f{fill:none;}.g{fill:#fff;}</style></defs><g id="c"><rect class="f" width="64" height="64"/></g><g id="d"><g id="e"><path class="g" d="m50.71,13h-.42c-.82,0-1.59.2-2.29.54v-1.04c0-1.97-1.04-3.69-2.59-4.66,0,0,0,0,0,0-.26-.16-.52-.3-.8-.42-.05-.02-.09-.03-.14-.05-.25-.09-.5-.17-.77-.23-.02,0-.04-.01-.06-.02,0,0-.01,0-.02,0-.32-.07-.65-.1-.99-.11-.03,0-.05,0-.08,0-.02,0-.03,0-.05,0-.3,0-.59.03-.87.08-.08.01-.16.03-.24.05-.22.05-.44.11-.65.18-.08.03-.16.05-.24.08-.27.11-.54.23-.79.38-.73-2.19-2.79-3.76-5.22-3.76-2.74,0-5,2-5.42,4.62-.74-.38-1.56-.62-2.45-.62h-.25c-2.97,0-5.37,2.41-5.37,5.37v19.91l-3.18-6c-.93-1.76-2.74-2.76-4.61-2.76-.82,0-1.65.19-2.42.6-2.55,1.33-3.53,4.45-2.19,6.98l12.17,22.9c2.01,3.99,4.02,4.99,7.04,4.99h17.2c7.18,0,11-5.82,11-12.99v-28.71c0-2.92-2.37-5.29-5.29-5.29Zm-5.71,43h-17.2c-1.32,0-2.05,0-3.46-2.8-.01-.03-.03-.05-.04-.08l-12.16-22.9c-.18-.35-.15-.67-.08-.87.07-.21.22-.5.6-.69.18-.09.37-.14.56-.14.36,0,.82.17,1.07.63l3.18,6c.71,1.33,2.08,2.13,3.53,2.13.32,0,.64-.04.97-.12,1.78-.44,3.03-2.04,3.03-3.88V13.37c0-.76.62-1.37,1.37-1.37h.25c.19,0,.39.06.61.17.04.02.08.03.12.04.39.24.64.67.64,1.16v16.13c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5V9.5c0-.08.01-.16.02-.24,0,0,0,0,0,0,.03-.17.09-.33.17-.48,0-.01.01-.03.02-.04.08-.13.18-.25.29-.35.02-.02.04-.04.07-.05.12-.09.24-.17.38-.23.02,0,.04-.01.07-.02.15-.05.31-.09.48-.09s.32.03.46.08c.03,0,.06.02.08.03.13.05.25.12.36.2.02.01.04.03.06.05.22.18.37.42.45.65.06.16.09.32.09.5v18c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5v-15c0-.56.31-1.04.76-1.3.1-.06.2-.1.31-.13.02,0,.04-.02.07-.02.12-.03.24-.05.36-.05.83,0,1.5.67,1.5,1.5v19c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5v-13.21c0-.5.29-.92.71-1.14,0,0,.02,0,.03-.01.06-.03.12-.05.18-.07.12-.04.24-.07.37-.07h.42c.71,0,1.29.58,1.29,1.29v28.71c0,2.11-.5,9-7,9Z"/></g></g></svg>
\ No newline at end of file
diff --git a/public/assets/images/icons/yellow/hand.svg b/public/assets/images/icons/yellow/hand.svg
new file mode 100644
index 0000000000000000000000000000000000000000..f7c9d48cef5e3c0913920a52b08adb8c1263a06f
--- /dev/null
+++ b/public/assets/images/icons/yellow/hand.svg
@@ -0,0 +1 @@
+<?xml version="1.0" encoding="UTF-8"?><svg id="b" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64"><defs><style>.f{fill:none;}.g{fill:#ffad00;}</style></defs><g id="c"><rect class="f" width="64" height="64"/></g><g id="d"><g id="e"><path class="g" d="m50.71,13h-.42c-.82,0-1.59.2-2.29.54v-1.04c0-1.97-1.04-3.69-2.59-4.66,0,0,0,0,0,0-.26-.16-.52-.3-.8-.42-.05-.02-.09-.03-.14-.05-.25-.09-.5-.17-.77-.23-.02,0-.04-.01-.06-.02,0,0-.01,0-.02,0-.32-.07-.65-.1-.99-.11-.03,0-.05,0-.08,0-.02,0-.03,0-.05,0-.3,0-.59.03-.87.08-.08.01-.16.03-.24.05-.22.05-.44.11-.65.18-.08.03-.16.05-.24.08-.27.11-.54.23-.79.38-.73-2.19-2.79-3.76-5.22-3.76-2.74,0-5,2-5.42,4.62-.74-.38-1.56-.62-2.45-.62h-.25c-2.97,0-5.37,2.41-5.37,5.37v19.91l-3.18-6c-.93-1.76-2.74-2.76-4.61-2.76-.82,0-1.65.19-2.42.6-2.55,1.33-3.53,4.45-2.19,6.98l12.17,22.9c2.01,3.99,4.02,4.99,7.04,4.99h17.2c7.18,0,11-5.82,11-12.99v-28.71c0-2.92-2.37-5.29-5.29-5.29Zm-5.71,43h-17.2c-1.32,0-2.05,0-3.46-2.8-.01-.03-.03-.05-.04-.08l-12.16-22.9c-.18-.35-.15-.67-.08-.87.07-.21.22-.5.6-.69.18-.09.37-.14.56-.14.36,0,.82.17,1.07.63l3.18,6c.71,1.33,2.08,2.13,3.53,2.13.32,0,.64-.04.97-.12,1.78-.44,3.03-2.04,3.03-3.88V13.37c0-.76.62-1.37,1.37-1.37h.25c.19,0,.39.06.61.17.04.02.08.03.12.04.39.24.64.67.64,1.16v16.13c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5V9.5c0-.08.01-.16.02-.24,0,0,0,0,0,0,.03-.17.09-.33.17-.48,0-.01.01-.03.02-.04.08-.13.18-.25.29-.35.02-.02.04-.04.07-.05.12-.09.24-.17.38-.23.02,0,.04-.01.07-.02.15-.05.31-.09.48-.09s.32.03.46.08c.03,0,.06.02.08.03.13.05.25.12.36.2.02.01.04.03.06.05.22.18.37.42.45.65.06.16.09.32.09.5v18c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5v-15c0-.56.31-1.04.76-1.3.1-.06.2-.1.31-.13.02,0,.04-.02.07-.02.12-.03.24-.05.36-.05.83,0,1.5.67,1.5,1.5v19c0,1.38,1.12,2.5,2.5,2.5s2.5-1.12,2.5-2.5v-13.21c0-.5.29-.92.71-1.14,0,0,.02,0,.03-.01.06-.03.12-.05.18-.07.12-.04.24-.07.37-.07h.42c.71,0,1.29.58,1.29,1.29v28.71c0,2.11-.5,9-7,9Z"/></g></g></svg>
\ No newline at end of file
diff --git a/resources/assets/stylesheets/scss/courseware.scss b/resources/assets/stylesheets/scss/courseware.scss
index f1a71ff87826b80aed5f2420eda87cc81d94bccf..a2c1b35b11a733d59e4260119673db5b14165d54 100644
--- a/resources/assets/stylesheets/scss/courseware.scss
+++ b/resources/assets/stylesheets/scss/courseware.scss
@@ -3218,77 +3218,169 @@ c a n v a s  b l o c k  e n d
 d o c u m e n t  b l o c k
 * * * * * * * * * * * * */
 .cw-block-document {
-    .cw-pdf-header {
+    .cw-pdf-main-container {
+        width: calc(100% - 2px);
+        border: solid thin var(--content-color-40);
+        .cw-block-title {
+            border: none;
+            border-bottom: solid thin var(--content-color-40);
+        }
+    }
+    .cw-pdf-toolbar {
         position: relative;
+        display: flex;
+        flex-direction: row;
+        justify-content: flex-start;
+        align-items: baseline;
+        align-content: space-around;
+        background-color: var(--content-color-20);
+        padding: 4px 8px;
 
-        .cw-pdf-button-prev,
-        .cw-pdf-button-next {
-            position: absolute;
-            border: none;
-            background-repeat: no-repeat;
-            background-color: transparent;
-            height: 24px;
-            width: 24px;
-            margin: 2px 12px;
-            cursor: pointer;
+        button {
+            height: 100%;
+            margin: 0 2px 0 0;
+            padding: 4px;
+            
+            &.active {
+                background-color: var(--base-color);
+            }
         }
 
-        .cw-pdf-button-prev {
-            left: 0;
-            @include background-icon(arr_1left, clickable, 18);
-            &.inactive {
-                @include background-icon(arr_1left, inactive, 18);
+        .cw-pdf-toolbar-left {
+            position: relative;
+            display: flex;
+            flex-direction: row;
+            justify-content: flex-start;
+            align-items: baseline;
+            align-content: space-between;
+            width: 33%;
+        }
+        .cw-pdf-toolbar-middle {
+            position: relative;
+            display: flex;
+            justify-content: center;
+            width: 34%;
+
+            .cw-pdf-zoom-buttons {
+                margin-right: 8px;
+
+                button {
+                    margin: 0;
+                    padding: 4px 0;
+                }
             }
         }
+        .cw-pdf-toolbar-right {
+            display: flex;
+            flex-direction: row;
+            justify-content: flex-end;
+            align-items: baseline;
+            align-content: space-between;
+            position: relative;
+            width: 33%;
+            margin-right: 4px;
+
+        }
+        .cw-pdf-page-nav {
+            margin: 0 4px;
 
-        .cw-pdf-button-next {
-            right: 0;
-            @include background-icon(arr_1right, clickable, 18);
-            &.inactive {
-                @include background-icon(arr_1right, inactive, 18);
+            button {
+                margin: 0;
+                padding: 4px 0;
+            }
+            .cw-pdf-page-num {
+                text-align: right;
+                width: 2em;
             }
         }
 
-        .cw-pdf-download {
-            display: inline-block;
-            width: 18px;
-            height: 18px;
-            margin: 0 0.25em;
-            border: none;
-            cursor: pointer;
-            vertical-align: sub;
+        .cw-pdf-search-box {
+            position: absolute;
+            top: 33px;
+            left: 22px;
+            width: auto;
+            background-color: var(--content-color-20);
+            border-top: none;
+            padding: 6px;
+            z-index: 2;
+            line-height: normal;
 
-            background: no-repeat scroll 0 0;
-            @include background-icon(download, clickable, 18);
+            .cw-pdf-search-num {
+                margin: 4px 0 0 0;
+                display: block;
+            }
+            .cw-pdf-search-navs {
+                display: inline-block;
+                button {
+                    margin: 0;
+                    padding: 0;
+                }
+            }
         }
     }
-    .cw-pdf-canvas {
-        border: solid thin $content-color-40;
-        width: calc(100% - 2px);
-    }
-    .cw-pdf-downloadbox {
-        border: solid thin $content-color-40;
-        padding: 0.5em 1em;
+    .cw-pdf-outer-container {
+        position: relative;
+        width: 100%;
 
-        .cw-pdf-file-info {
-            @include background-icon(file, clickable, 24);
-            display: inline-block;
-            background-repeat: no-repeat;
-            padding-left: 26px;
-            margin: 1em;
-            line-height: 24px;
-            color: $base-color;
-            &.cw-pdf-fileicon-pdf {
-                @include background-icon(file-pdf, clickable, 24);
+        .cw-pdf-content {
+            display: flex;
+            flex-direction: row;
+
+            .cw-pdf-sidebar {
+                width: 25%;
+                min-width: 270px;;
+                align-self: stretch;
+                background-color: var(--white);
+                border-right: solid 1px var(--content-color-40);
+    
+                ul.cw-pdf-toc-list, ul.cw-pdf-toc-sub-list {
+                    padding: 0;
+                    list-style: none;
+    
+                    li {
+                        padding: 0.5em 1em;
+                    }
+                }
+                ul.cw-pdf-toc-list {
+                    margin-top: 1em;
+                }
             }
+
+            .cw-pdf-viewer-container {
+                width: 100%;
+                height: 100%;
+                overflow: hidden;
+                cursor: text;
+    
+                &.hand-cursor-grab {
+                    cursor: grab;
+                    &.grabbing {
+                        cursor: grabbing;
+                    }
+                }
+                &.has-error {
+                    display: none;
+                }
+                .page {
+                    position: relative;
+                    margin: 0 auto;
+                }
+            }
+
         }
-        .cw-pdf-download-icon {
-            float: right;
-            @include background-icon(download, clickable, 24);
-            height: 24px;
-            width: 24px;
-            background-repeat: no-repeat;
-            margin: 1em;
+
+
+        .cw-pdf-viewer-fake-container {
+            position: absolute;
+        }
+
+
+        .cw-pdf-error-page {
+            overflow: hidden;
+            width: calc(100% - 16px);
+            height: 100%;
+            padding: 8px;
+            display:table;
         }
     }
 }
diff --git a/resources/vue/components/courseware/CoursewareDocumentBlock.vue b/resources/vue/components/courseware/CoursewareDocumentBlock.vue
index 00a5136b0602bc2d6ca3e51f99e46fa83d0dbcbe..58b0af70db95f8771fca0fba782d3e3d4c8ab28f 100644
--- a/resources/vue/components/courseware/CoursewareDocumentBlock.vue
+++ b/resources/vue/components/courseware/CoursewareDocumentBlock.vue
@@ -10,35 +10,183 @@
             @closeEdit="initCurrentData"
         >
             <template #content>
-                <div v-if="hasFile" class="cw-pdf-header cw-block-title">
-                    <button class="cw-pdf-button-prev" :class="{ inactive: pageNum - 1 === 0 }" @click="prevPage" />
-                    <span class="cw-pdf-title">{{ currentTitle }}</span>
-                    <a v-if="fileDownloadable" :href="currentUrl" class="cw-pdf-download" download></a>
-                    <span>
-                        <translate :translate-params="{pageNum, pageCount}">
-                            (Seite %{ pageNum } von %{ pageCount })
-                        </translate>
-                    </span>
-                    <button class="cw-pdf-button-next" :class="{ inactive: pageNum === pageCount }" @click="nextPage" />
+                <div class="cw-pdf-main-container">
+                    <template v-if="hasFile">
+                        <div v-if="currentTitle !== ''" class="cw-block-title">
+                            {{ currentTitle }}
+                        </div>
+                        <div class="cw-pdf-toolbar">
+                            <div class="cw-pdf-toolbar-left">
+                                <div class="cw-pdf-toc">
+                                    <button
+                                        class="undecorated"
+                                        :class="{active: pdfTOCDisplay}"
+                                        :title="$gettext('Inhaltsverzeichnis')"
+                                        :aria-pressed="pdfTOCDisplay ? 'true' : 'false'"
+                                        @click="toggleTOCViewer"
+                                    >
+                                        <studip-icon
+                                            shape="table-of-contents"
+                                            :role="pdfTOC.length === 0 ? 'inactive' : pdfTOCDisplay ? 'info_alt' :'clickable'"
+                                            :size="18"
+                                            class="text-bottom"
+                                        />
+                                    </button>
+                                </div>
+                                <div class="cw-pdf-search-toggle-btn">
+                                    <button
+                                        class="undecorated"
+                                        :class="{active: showPdfSearchBox}"
+                                        :title="$gettext('Suche')"
+                                        :aria-pressed="showPdfSearchBox ? 'true' : 'false'"
+                                        @click="togglePdfSearchBox"
+                                    >
+                                        <studip-icon
+                                            shape="search"
+                                            :role="showPdfSearchBox ? 'info_alt' : 'clickable'"
+                                            :size="18"
+                                            class="text-bottom"
+                                        />
+                                    </button>
+                                </div>
+                                <div class="cw-pdf-search-box" v-show="showPdfSearchBox">
+                                    <input ref="pdfSearchInput" type="text" v-model="pdfSearch" @change="doSearchInPdf">
+                                    <div class="cw-pdf-search-navs" v-if="pdfSearchFoundNums > 1">
+                                        <button class="undecorated" @click="prevPdfSearch" :title="$gettext('Letzte')">
+                                            <studip-icon
+                                                shape="arr_1left"
+                                                :role="pdfSearchFoundSelectedIndex === 0 ? 'inactive' : 'clickable'"
+                                                :size="18"
+                                                class="text-bottom"
+                                            />
+                                        </button>
+                                        <button class="undecorated" @click="nextPdfSearch" :title="$gettext('Nächste')">
+                                            <studip-icon
+                                                shape="arr_1right"
+                                                :role="pdfSearchFoundSelectedIndex === pdfSearchFoundNums - 1 ? 'inactive' : 'clickable'"
+                                                :size="18"
+                                                class="text-bottom"
+                                            />
+                                        </button>
+                                    </div>
+                                    <span class="cw-pdf-search-num" v-if="pdfSearchFoundNums > 0">
+                                        {{ (pdfSearchFoundSelectedIndex + 1) }} / {{ pdfSearchFoundNums }} {{ $gettext('Treffer') }}
+                                    </span>
+                                </div>
+                                <div class="cw-pdf-page-nav">
+                                    <button class="undecorated" @click="prevPage" :title="$gettext('Eine Seite zurück')">
+                                        <studip-icon
+                                            shape="arr_1up"
+                                            :role="pageNum - 1 === 0 ? 'inactive' : 'clickable'"
+                                            :size="18"
+                                            class="text-bottom"
+                                        />
+                                    </button>
+                                    <button class="undecorated" @click="nextPage" :title="$gettext('Eine Seite vor')">
+                                        <studip-icon
+                                            shape="arr_1down"
+                                            :role="pageNum === pageCount ? 'inactive' : 'clickable'"
+                                            :size="18"
+                                            class="text-bottom"
+                                        />
+                                    </button>
+                                    <input
+                                        type="text"
+                                        ref="pageNumInput"
+                                        class="cw-pdf-page-num"
+                                        :aria-label="$gettext('Seite')"
+                                        :value="pageNum"
+                                        @change="updatePageNum"
+                                    >
+                                    <span>
+                                        {{ $gettext('von') }} {{ pageCount }}
+                                    </span>
+                                </div>
+                            </div>
+                            <div class="cw-pdf-toolbar-middle">
+                                <div class="cw-pdf-zoom-buttons">
+                                    <button class="undecorated" @click="zoomIn" :title="$gettext('Vergrößern')">
+                                        <studip-icon shape="add" :size="18" class="text-bottom" />
+                                    </button>
+                                    <button class="undecorated" @click="zoomOut" :title="$gettext('Verkleinern')">
+                                        <studip-icon shape="remove" :size="18" class="text-bottom" />
+                                    </button>
+                                    <select v-model="currentScale" :aria-label="$gettext('Zoom')" @change="updateZoom">
+                                        <option v-show="false" :value="currentScale">{{ formattedZoom }}%</option>
+                                        <option v-for="(value, index) in scaleValues" :key="index" :value="value">{{ value * 100 }}%</option>
+                                    </select>
+                                </div>
+                                <div class="cw-pdf-rotate">
+                                    <button class="undecorated" @click="doRotatePdf" :title="$gettext('Drehen')">
+                                        <studip-icon shape="rotate-right" :size="18" class="text-bottom" />
+                                    </button>
+                                </div>
+                            </div>
+                            <div class="cw-pdf-toolbar-right">
+                                <div class="cw-pdf-handtool">
+                                    <button
+                                        class="undecorated"
+                                        :class="{active: pdfHandTool}"
+                                        :title="$gettext('Hand-Werkzeug')"
+                                        :aria-pressed="pdfHandTool ? 'true' : 'false'"
+                                        @click="toggleHandTool"
+                                    >
+                                        <studip-icon
+                                            shape="hand"
+                                            :role="pdfHandTool ? 'info_alt' : 'clickable'"
+                                            :size="18"
+                                            class="text-bottom"
+                                        />
+                                    </button>
+                                </div>
+                                <div class="cw-pdf-download">
+                                    <a v-if="downloadable === 'true'" :href="currentUrl" download :title="$gettext('Speichern')">
+                                        <studip-icon shape="download" :size="18" class="text-bottom"/>
+                                    </a>
+                                </div>
+                            </div>
+                        </div>
+                        <div class="cw-pdf-outer-container" ref="outerContainer">
+                            <div class="cw-pdf-content">
+                                <span class="sr-only" aria-live="polite">{{ srMessage }}</span>
+                                <div class="cw-pdf-sidebar" v-show="pdfTOCDisplay">
+                                    <ul class="cw-pdf-toc-list">
+                                        <CoursewarePDFTableOfContent v-for="(item, index) in pdfTOC" :item="item" :key="index" @tocPageNav="tocPageNav" />
+                                    </ul>
+                                </div>
+                                <div
+                                    ref="container"
+                                    class="cw-pdf-viewer-container"
+                                    :class="{'hand-cursor-grab': pdfHandTool, 'grabbing':  pdfGrabbing, 'has-error': pdfError}"
+                                    v-dragscroll="pdfHandTool"
+                                    @mousedown="handleMouseDown"
+                                    @mouseup="handleMouseUp"
+                                >
+                                    <div class="pdfViewer"/>
+                                </div>
+                            </div>
+                            <div v-show="pdfError" class="cw-pdf-error-page">
+                                <courseware-companion-box 
+                                    mood="sad"
+                                    :msgCompanion="$gettext('Es gab einen Fehler. Bitte versuchen Sie es erneut!')"
+                                >
+                                </courseware-companion-box>
+                            </div>
+                            <div ref="fakeContainer" class="cw-pdf-viewer-fake-container">
+                                <div class="pdfViewer"/>
+                            </div>
+                        </div>
+                    </template>
                 </div>
-                <canvas
-                    v-if="hasFile"
-                    ref="pdfcanvas"
-                    class="cw-pdf-canvas"
-                    @mousedown="browse = true"
-                    @mouseup="browse = false"
-                    @mouseleave="browse = false"
-                    @mousemove="browsePdf"
-                />
             </template>
             <template v-if="canEdit" #edit>
                 <form class="default" @submit.prevent="">
                     <label>
-                        <translate>Ãœberschrift</translate>
+                        {{ $gettext('Ãœberschrift') }}
                         <input type="text" v-model="currentTitle" />
                     </label>
                     <label>
-                        <translate>Datei</translate>
+                        {{ $gettext('Datei') }}
                         <courseware-file-chooser
                             v-model="currentFileId"
                             :isDocument="true"
@@ -46,14 +194,14 @@
                         />
                     </label>
                     <label>
-                        <translate>Download-Icon anzeigen</translate>
+                        {{ $gettext('Download-Icon anzeigen') }}
                         <select v-model="currentDownloadable">
-                            <option value="true"><translate>Ja</translate></option>
-                            <option value="false"><translate>Nein</translate></option>
+                            <option value="true">{{ $gettext('Ja') }}</option>
+                            <option value="false">{{ $gettext('Nein') }}</option>
                         </select>
                     </label>
                     <label>
-                        <translate>Dateityp</translate>
+                        {{ $gettext('Dateityp') }}
                         <select v-model="currentDocType">
                             <option value="pdf">PDF</option>
                         </select>
@@ -61,27 +209,48 @@
                 </form>
             </template>
             <template #info>
-                <p><translate>Informationen zum Dokument-Block</translate></p>
+                <p>{{ $gettext('Informationen zum Dokument-Block') }}</p>
             </template>
         </courseware-default-block>
     </div>
 </template>
 
 <script>
+import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
 import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue';
 import CoursewareFileChooser from './CoursewareFileChooser.vue';
+import CoursewarePDFTableOfContent from './CoursewarePDFTableOfContent.vue';
 import { blockMixin } from './block-mixin.js';
-import * as pdfjsLib from 'pdfjs-dist';
+import { getDocument } from 'pdfjs-dist';
+import {
+    DefaultAnnotationLayerFactory,
+    DefaultTextLayerFactory,
+    DefaultXfaLayerFactory,
+    DefaultStructTreeLayerFactory,
+    PDFFindController,
+    PDFLinkService,
+    PDFPageView,
+    PDFViewer,
+    EventBus
+} from 'pdfjs-dist/web/pdf_viewer.js';
+// pdfjsWorker must be imported!
 import pdfjsWorker from 'pdfjs-dist/build/pdf.worker.entry';
+import { dragscroll } from 'vue-dragscroll'
 
 import { mapActions } from 'vuex';
+import 'pdfjs-dist/web/pdf_viewer.css';
 
 export default {
     name: 'courseware-document-block',
     mixins: [blockMixin],
     components: {
+        CoursewareCompanionBox,
         CoursewareDefaultBlock,
         CoursewareFileChooser,
+        CoursewarePDFTableOfContent
+    },
+    directives: {
+        dragscroll
     },
     props: {
         block: Object,
@@ -96,18 +265,38 @@ export default {
             currentDownloadable: '',
             currentDocType: '',
 
-            PdfViewer: true,
+            pdfError: false,
+            pdfBasePage: null,
+            pdfPage: null,
+            pdfTextContent: null,
+            pdfHandTool: false,
+            pdfGrabbing: false,
+            pdfTextLayer: null,
+            pdfAnnotationLayer: null,
+            pdfAnnotation: false,
+            pdfRotate: 0,
+            PdfViewer: null,
+            pdfEventBus: null,
+            pdfLinkService: null,
+            pdfFindController: null,
             pdfDoc: null,
+            pdfLoadingTask: null,
+            pdfSearch: '',
+            pdfSearchMatchesMapping: [],
+            pdfSearchFoundNums: 0,
+            pdfSearchFoundSelectedIndex: 0,
+            pdfSearchHighlightedList: [],
+            showPdfSearchBox: false,
+            pdfTOC: [],
+            pdfTOCDisplay: false,
             pageNum: 1,
-            pageRendering: false,
-            pageNumPending: null,
             pageCount: 0,
-            scale: 2,
-            canvas: {},
-            context: {},
-            browse: false,
-            browseDirection: [],
-            file: null
+            scale: 1,
+            currentScale: 1,
+            scaleValues: [0.5, 1, 1.5, 2, 3, 4],
+            file: null,
+
+            srMessage: ''
         };
     },
     computed: {
@@ -135,20 +324,30 @@ export default {
         },
         hasFile() {
             return this.currentFileId !== '';
-        }
+        },
+        formattedZoom () {
+            return Number.parseInt(this.scale * 100, 10);
+        },
     },
     watch: {
-        browseDirection: function (val) {
-            if (val.length > 6) {
-                this.evaluateBrowseAction();
-            }
+        scale(newValue) {
+            let overflow = newValue > 1 ? 'auto' : 'hidden';
+            let container = this.$refs.container;
+            container.style.overflow = overflow;
+            this.currentScale = newValue;
+        },
+        pageNum(newValue) {
+            this.resetPdfViewer();
         },
+        showPdfSearchBox() {
+            this.resetPdfSearch();
+        }
     },
     mounted() {
         this.loadFileRefs(this.block.id).then((response) => {
             this.file = response[0];
             this.currentFile = this.file;
-            this.loadPdfViewer();
+            this.initPdfTask();
         });
         this.initCurrentData();
     },
@@ -168,80 +367,380 @@ export default {
             this.currentFile = file;
             this.currentFileId = file.id;
         },
-        loadPdfViewer() {
-            if (this.PdfViewer && this.currentUrl) {
+        initPdfTask() {
+            if (this.currentUrl) {
                 let view = this;
-                this.canvas = this.$refs.pdfcanvas;
-                this.context = this.canvas.getContext('2d');
-                pdfjsLib.GlobalWorkerOptions.workerSrc = pdfjsWorker;
-                pdfjsLib.getDocument(this.currentUrl).promise.then(function (pdf) {
-                    view.pdfDoc = pdf;
-                    view.pageCount = view.pdfDoc.numPages;
-                    view.renderPage(view.pageNum);
+                view.pdfEventBus = new EventBus();
+                view.pdfLoadingTask = getDocument(this.currentUrl).promise;
+                view.pdfLoadingTask.__PDFDocumentLoadingTask = true;
+                // Link Service
+                view.pdfLinkService = new PDFLinkService({
+                    eventBus: view.pdfEventBus,
+                });
+                // Find Controller
+                view.pdfFindController = new PDFFindController({
+                    eventBus: view.pdfEventBus,
+                    linkService: view.pdfLinkService
+                });
+                // Annotation Layer
+                view.pdfAnnotationLayer = new DefaultAnnotationLayerFactory();
+                // Text Layer
+                view.pdfTextLayer = new DefaultTextLayerFactory();
+                // Load Pdf Document
+                view.loadPdfDocument();
+
+                // Handle search results.
+                view.pdfEventBus.on('updatetextlayermatches', ({source, pageIndex}) => {
+                    if (view.pdfViewer.pdfPage._pageIndex == pageIndex) {
+                        setTimeout(() => {
+                            view.handleSearchMatches();
+                        }, 260);
+                    }
                 });
             }
         },
-        renderPage(num) {
-            let view = this;
-            this.pageRendering = true;
-            this.pdfDoc.getPage(num).then(function (page) {
-                let viewport = page.getViewport({ scale: view.scale });
-                view.canvas.height = viewport.height;
-                view.canvas.width = viewport.width;
+        loadPdfDocument() {
+            if (this.pdfLoadingTask) {
+                let view = this;
+                view.pdfLoadingTask.then((pdfDocument) => {
+                    view.pdfDoc = pdfDocument;
+                    view.pageCount = pdfDocument.numPages;
+                    // get table of contents if any.
+                    view.loadPdfTOC();
+                    // Rendering PDF viewer
+                    view.loadPdfViewer();
+                    view.pdfLinkService.setDocument(view.pdfDoc, null);
+                    view.pdfFindController.setDocument(view.pdfDoc);
+                });
+            }
+        },
+        loadPdfTOC() {
+            if (this.pdfDoc) {
+                let view = this;
+                view.pdfTOC = [];
+                // Get the tree outline
+                view.pdfDoc.getOutline().then((outline) => {
+                    if (outline) {
+                        view.pdfTOC = outline;
+                    }
+                });
+            }
+        },
+        loadPdfViewer() {
+            if (this.pdfDoc) {
+                let view = this;
+                this.pdfError = false;
+                let container = this.$refs.container;
+                let outerContainer = this.$refs.outerContainer;
+                let fakeContainer = this.$refs.fakeContainer;
+                this.pdfDoc.getPage(parseInt(view.pageNum)).then((pdfPage) => {
+                    view.pdfPage = pdfPage;
+                    // Creating the page view with default parameters.
+                    let defaultViewport = pdfPage.getViewport({
+                        scale: 1.35,
+                    });
 
-                let renderContext = {
-                    canvasContext: view.context,
-                    viewport: viewport,
-                };
-                let renderTask = page.render(renderContext);
+                    view.pdfBasePage = new PDFViewer({
+                        container: fakeContainer,
+                        eventBus: view.pdfEventBus,
+                        findController: view.pdfFindController
+                    });
 
-                renderTask.promise.then(function () {
-                    view.pageRendering = false;
-                    if (view.pageNumPending !== null) {
-                        view.renderPage(view.pageNumPending);
-                        view.pageNumPending = null;
+                    let pdfPageViewOptions = {
+                        container: container,
+                        id: view.pageNum,
+                        scale: view.scale,
+                        defaultViewport: defaultViewport,
+                        eventBus: view.pdfEventBus,
+                        findController: view.pdfFindController,
+                        textHighlighterFactory: view.pdfBasePage,
+                        xfaLayerFactory: view.pdfDoc.isPureXfa
+                            ? new DefaultXfaLayerFactory()
+                            : null,
+                        structTreeLayerFactory: new DefaultStructTreeLayerFactory()
+                    };
+                    if (view.pdfHandTool === false) {
+                        pdfPageViewOptions.textLayerFactory = view.pdfTextLayer;
+                        pdfPageViewOptions.annotationLayerFactory = view.pdfAnnotationLayer;
+                    } else {
+                        pdfPageViewOptions.textLayerMode = 0;
+                        pdfPageViewOptions.annotationMode = 0;
+                    }
+                    // Force annotation to be disabled.
+                    if (!this.pdfAnnotation && pdfPageViewOptions?.annotationLayerFactory) {
+                        pdfPageViewOptions.annotationLayerFactory = null;
+                        pdfPageViewOptions.annotationMode = 0;
                     }
+                    view.pdfViewer = new PDFPageView(pdfPageViewOptions);
+                    // Associates the actual page with the view, and drawing it
+                    view.pdfViewer.setPdfPage(view.pdfPage);
+                    // Set LinkService viewer
+                    view.pdfLinkService.setViewer(view.pdfViewer);
+                    // Set outer container height
+                    outerContainer.style.height = container.offsetHeight + 'px';
+                    view.renderPage();
+                }).catch(err => {
+                    console.log(err);
+                    outerContainer.style.minHeight = '350px';
+                    view.pdfError = true;
                 });
-            });
+            }
         },
-        queueRenderPage(num) {
-            if (this.pageRendering) {
-                this.pageNumPending = num;
-            } else {
-                this.renderPage(num);
+        renderPage() {
+            if (this.pdfViewer) {
+                this.updatePdfViewer();
+                this.pdfViewer.draw();
+                if (!this.pdfHandTool) {
+                    this.pdfViewer.textLayer.findController = this.pdfFindController;
+                }
+                if (this.pdfPage) {
+                    this.pdfPage.getTextContent().then((textContent) => {
+                        this.pdfTextContent = textContent;
+                    });
+                }
+                if (this.pdfSearchMatchesMapping.length) {
+                    this.pdfSearchDisplayHandler();
+                }
             }
         },
+        resetPdfViewer() {
+            this.pdfViewer.destroy();
+            let container = this.$refs.container;
+            while (!container.lastChild.classList.contains('pdfViewer')) {
+                container.removeChild(container.lastChild);
+            }
+            this.loadPdfViewer();
+        },
+        updatePdfViewer(resetScale = false) {
+            let updateArgs = {
+                scale: resetScale ? 1 : this.scale,
+                rotation: this.pdfRotate,
+            };
+            this.pdfViewer.update(updateArgs);
+        },
         prevPage() {
             if (this.pageNum <= 1) {
                 return;
             }
             this.pageNum--;
-            this.queueRenderPage(this.pageNum);
         },
         nextPage() {
             if (this.pageNum >= this.pdfDoc.numPages) {
                 return;
             }
             this.pageNum++;
-            this.queueRenderPage(this.pageNum);
         },
-        browsePdf(e) {
-            if (this.browse) {
-                this.browseDirection.push(e.clientX);
+        goToPage(page) {
+            const pageNum = Number.parseInt(page, 10);
+            if (pageNum < 1 || pageNum > this.pdfDoc.numPages) {
+                return;
             }
+            this.pageNum = pageNum;
         },
-        evaluateBrowseAction() {
-            this.browse = false;
-            let first = this.browseDirection[0];
-            let last = this.browseDirection.pop();
-            this.browseDirection = [];
-            if (first < last) {
-                this.prevPage();
+        tocPageNav(dest) {
+            let view = this;
+            let destObj = dest.find((ref) =>
+                typeof ref === "object" && ref !== null &&
+                Number.isInteger(ref.num) && ref.num >= 0 &&
+                Number.isInteger(ref.gen) && ref.gen >= 0);
+            if (destObj) {
+                view.pdfDoc.getPageIndex(destObj).then((pageIndex) => {
+                    view.goToPage(pageIndex + 1);
+                });
+            }
+        },
+        updatePageNum() {
+            let pageNumInput = this.$refs.pageNumInput;
+            let value = Number.parseInt(pageNumInput.value, 10);
+            if (Number.isInteger(value) && value > 0 && value <= Number.parseInt(this.pageCount, 10)) {
+                this.pageNum = value;
             } else {
-                this.nextPage();
+                pageNumInput.value = this.pageNum;
+            }
+        },
+        doRotatePdf() {
+            let rotationDegs = [0, 90, 180, 270, 360];
+            let index = rotationDegs.indexOf(this.pdfRotate);
+            let nextIndex = index + 1 >= rotationDegs.length ? 0 : index + 1;
+            let nextDeg = rotationDegs[nextIndex];
+            this.pdfRotate = nextDeg;
+            this.renderPage();
+            this.updateSrMessage(this.$gettext('gedreht'));
+        },
+        zoomIn() {
+            this.scale = this.scale < 4 ? (this.scale * 10 + 1) / 10 : this.scale;
+            this.renderPage();
+            this.updateSrMessage(this.$gettext('vergrößert'));
+        },
+        zoomOut() {
+            this.scale = this.scale > 0.1 ? (this.scale * 10 - 1) / 10 : this.scale;
+            this.renderPage();
+            this.updateSrMessage(this.$gettext('verkleinert'));
+        },
+        updateZoom(e) {
+            const value = e.target.value;
+            if (this.scale === value) {
+                return;
+            }
+            this.scale = value;
+            this.renderPage();
+            this.updateSrMessage(this.$gettext('Zoom Stufe ausgweählt'));
+        },
+        toggleHandTool() {
+            this.pdfHandTool = !this.pdfHandTool;
+            this.resetPdfViewer();
+            this.showPdfSearchBox = false;
+        },
+        handleHandToolDisplay(event) {
+            this.pdfGrabbing = event.type === 'mousedown';
+        },
+        handleMouseDown(e) {
+            this.handleHandToolDisplay(e);
+        },
+        handleMouseUp(e) {
+            this.handleHandToolDisplay(e);
+        },
+        togglePdfSearchBox() {
+            this.showPdfSearchBox = this.pdfHandTool ? false : !this.showPdfSearchBox;
+            if (this.showPdfSearchBox) {
+                this.$nextTick(() => {
+                    this.$refs.pdfSearchInput.focus();
+                });
+            }
+        },
+        handleSearchMatches() {
+            let view = this;
+            let allMatches = view.pdfFindController.pageMatches;
+            let totalMatches = 0;
+            let searchSelectIndex = 0;
+            let matchesPageCount = 0;
+            view.pdfSearchMatchesMapping = [];
+            for (let pageIndex = 0; pageIndex < view.pageCount; pageIndex++) {
+                let pageNum = pageIndex + 1;
+                let pageMatches = allMatches[pageIndex];
+                totalMatches += pageMatches.length;
+                if (pageMatches.length) {
+                    matchesPageCount++;
+                }
+                for (let i in pageMatches) {
+                    let matchIndex = parseInt(i, 10);
+                    let mappingObj = {
+                        selectIndex: searchSelectIndex,
+                        matchIndex: matchIndex,
+                        pageNum: pageNum,
+                    }
+                    view.pdfSearchMatchesMapping.push(mappingObj);
+                    searchSelectIndex++;
+                }
+            }
+            // Find next match if there the current page has nothing.
+            if (
+                view.pdfSearchFoundSelectedIndex === 0
+                && view.pdfViewer.pdfPage._pageIndex > 0
+                && matchesPageCount > 0
+            ) {
+                let nextMapped = view.pdfSearchMatchesMapping.filter(map => map.pageNum >= view.pdfViewer.pdfPage._pageIndex + 1);
+                if (nextMapped.length) {
+                    view.pdfSearchFoundSelectedIndex = nextMapped[0].selectIndex;
+                }
+            }
+            view.pdfSearchFoundNums = totalMatches;
+            view.pdfSearchDisplayHandler();
+        },
+        doSearchInPdf() {
+            let findObj = {
+                type: '',
+                query: this.pdfSearch,
+                phraseSearch: true,
+                caseSensitive: false,
+                entireWord: true,
+                highlightAll: true,
+                findPrevious: false,
+                matchDiacritics: false
+            };
+            this.pdfEventBus.dispatch('find', findObj);
+        },
+        prevPdfSearch() {
+            if (this.pdfSearchFoundSelectedIndex === 0) {
+                return;
+            }
+            this.pdfSearchFoundSelectedIndex--;
+            this.pdfSearchDisplayHandler();
+        },
+        nextPdfSearch() {
+            if (this.pdfSearchFoundSelectedIndex === this.pdfSearchFoundNums - 1) {
+                return;
+            }
+            this.pdfSearchFoundSelectedIndex++;
+            this.pdfSearchDisplayHandler();
+        },
+        pdfSearchDisplayHandler() {
+            // Go to page based on selected index.
+            let pageMatches = this.pdfSearchMatchesMapping.filter(map => 
+                map.selectIndex === this.pdfSearchFoundSelectedIndex
+            );
+            if (pageMatches.length) {
+                let matchObj = pageMatches[0];
+                // A timeout of > 250ms is needed when page is changed!
+                let highlightRenderTimeout = 0;
+                if (matchObj.pageNum !== this.pageNum) {
+                    this.goToPage(matchObj.pageNum);
+                    highlightRenderTimeout = 260;
+                }
+                setTimeout(() => {
+                    this.setPdfSearchHighlighted();
+                    this.scrollToSearchFounds(matchObj.matchIndex);
+                }, highlightRenderTimeout);
+            }
+        },
+        scrollToSearchFounds(matchIndex) {
+            if (this.pdfSearchHighlightedList?.length) {
+                let selectedSpan = this.pdfSearchHighlightedList[matchIndex];
+                if (selectedSpan) {
+                    selectedSpan.classList.add('selected');
+                    selectedSpan.scrollIntoView({ behavior: 'smooth', block: "center" });
+                }
+            }
+        },
+        setPdfSearchHighlighted() {
+            if (this.pdfViewer?.textLayer?.textDivs) {
+                let textDivs = this.pdfViewer.textLayer.textDivs;
+                let highlightedSpans = [];
+                for (let textSpan of textDivs) {
+                    if (textSpan?.children) {
+                        let children = [...textSpan.children];
+                        for (let child of children) {
+                            if (child.nodeName == 'SPAN' && child.classList.contains('highlight')) {
+                                child.classList.remove('selected');
+                                highlightedSpans.push(child);
+                            }
+                        }
+                    }
+                }
+                // Sort the array based on the top of the span.
+                highlightedSpans.sort((current, next) => {
+                    let currentTop = parseInt(current.parentNode.style.top, 10);
+                    let nextTop = parseInt(next.parentNode.style.top, 10);
+                    return currentTop > nextTop;
+                });
+                this.pdfSearchHighlightedList = highlightedSpans;
+            }
+        },
+        resetPdfSearch() {
+            this.pdfSearch = '';
+            this.pdfSearchFoundNums = 0;
+            this.pdfSearchFoundSelectedIndex = 0;
+            this.pdfSearchHighlightedList = [];
+            this.pdfSearchMatchesMapping = [];
+            this.doSearchInPdf();
+        },
+        toggleTOCViewer() {
+            if (this.pdfTOC.length) {
+                this.pdfTOCDisplay = !this.pdfTOCDisplay;
+            } else {
+                this.pdfTOCDisplay = false;
             }
         },
-
         storeBlock() {
             if (this.currentFile === undefined) {
                 this.companionWarning({
@@ -253,7 +752,7 @@ export default {
                 attributes.payload = {};
                 attributes.payload.title = this.currentTitle;
                 attributes.payload.file_id = this.currentFile.id;
-                attributes.payload.downloadable = this.currentDownloadable;
+                attributes.payload.downloadable = this.currentDownloadable.toString();
                 attributes.payload.doc_type = this.currentDocType;
 
                 this.updateBlock({
@@ -263,6 +762,10 @@ export default {
                 });
             }
         },
+        updateSrMessage(message) {
+            this.srMessage = '';
+            this.srMessage = message;
+        }
     },
 };
 </script>
diff --git a/resources/vue/components/courseware/CoursewarePDFTableOfContent.vue b/resources/vue/components/courseware/CoursewarePDFTableOfContent.vue
new file mode 100644
index 0000000000000000000000000000000000000000..989e2edabfa90e3a17c53e24681c8c8033ba399a
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewarePDFTableOfContent.vue
@@ -0,0 +1,30 @@
+<template>
+    <li class="cw-pdf-toc-item">
+        <a href="#" @click.prevent="tocPageNav(item.dest)">
+            <strong v-if="item.bold">{{ item.title }}</strong>
+            <span v-else>{{ item.title }}</span>
+        </a>
+        <template v-if="item.items">
+            <ul class="cw-pdf-toc-sub-list">
+                <courseware-pdf-toc-item v-for="(item, index) in item.items" :item="item" :key="index" @tocPageNav="tocPageNav"></courseware-pdf-toc-item>
+            </ul>
+        </template>
+    </li>
+</template>
+
+<script>
+export default {
+    name: 'courseware-pdf-toc-item',
+    props: {
+        item: {
+            type: Object,
+            required: true,
+        },
+    },
+    methods: {
+        tocPageNav(dest) {
+            this.$emit('tocPageNav', dest);
+        }
+    },
+}
+</script>