From d436a45e1576a0080d8c966529095f8d187a5941 Mon Sep 17 00:00:00 2001
From: Ron Lucke <lucke@elan-ev.de>
Date: Fri, 6 Jan 2023 10:19:34 +0000
Subject: [PATCH] Optimierung der Darstellungsstruktur mehrerer Lernmaterialien
 in Courseware

Closes #1599

Merge request studip/studip!1201
---
 app/controllers/contents/courseware.php       | 223 +----
 app/controllers/course/courseware.php         | 293 +------
 app/controllers/courseware_controller.php     |  76 ++
 app/views/contents/courseware/bookmarks.php   |   2 +-
 app/views/contents/courseware/courseware.php  |  10 +-
 app/views/contents/courseware/index.php       |   8 +-
 app/views/course/courseware/activities.php    |   6 +
 app/views/course/courseware/courseware.php    |  10 +
 app/views/course/courseware/dashboard.php     |  12 -
 app/views/course/courseware/index.php         |   7 +-
 app/views/course/courseware/tasks.php         |   6 +
 .../5.3.16_create_cw_units_table.php          |  52 ++
 db/migrations/5.3.17_change_cw_config.php     |  56 ++
 lib/activities/CoursewareProvider.php         | 417 ++++++----
 lib/classes/JsonApi/RouteMap.php              |  12 +
 .../JsonApi/Routes/Courseware/Authority.php   |  79 +-
 .../Routes/Courseware/CoursesUnitsIndex.php   |  46 ++
 .../Courseware/CoursewareInstancesHelper.php  |  18 +-
 .../Courseware/CoursewareInstancesUpdate.php  |   7 +-
 .../Courseware/StructuralElementsCopy.php     |   7 +
 .../Courseware/StructuralElementsShow.php     |   3 +-
 .../JsonApi/Routes/Courseware/UnitsCopy.php   |  46 ++
 .../JsonApi/Routes/Courseware/UnitsCreate.php | 122 +++
 .../JsonApi/Routes/Courseware/UnitsDelete.php |  34 +
 .../JsonApi/Routes/Courseware/UnitsIndex.php  |  35 +
 .../JsonApi/Routes/Courseware/UnitsShow.php   |  42 +
 .../JsonApi/Routes/Courseware/UnitsUpdate.php |  95 +++
 .../Courseware/UserProgressesOfUnitsShow.php  | 191 +++++
 .../Routes/Courseware/UsersUnitsIndex.php     |  46 ++
 lib/classes/JsonApi/SchemaMap.php             |   1 +
 .../JsonApi/Schemas/Courseware/Instance.php   |   8 +-
 .../Schemas/Courseware/StructuralElement.php  |  24 +
 .../JsonApi/Schemas/Courseware/Unit.php       |  79 ++
 lib/models/Courseware/Instance.php            |  52 +-
 lib/models/Courseware/StructuralElement.php   |  49 +-
 lib/models/Courseware/Unit.php                | 112 +++
 lib/modules/CoursewareModule.class.php        |  38 +-
 lib/navigation/ContentsNavigation.php         |   8 +-
 package-lock.json                             |  12 +-
 .../javascripts/bootstrap/courseware.js       |  35 +-
 .../assets/stylesheets/scss/buttons.scss      |  10 +
 .../assets/stylesheets/scss/courseware.scss   | 594 +++++++-------
 resources/assets/stylesheets/scss/dialog.scss | 111 +++
 resources/assets/stylesheets/scss/wizard.scss | 214 +++++
 resources/assets/stylesheets/studip.scss      |   7 +-
 resources/vue/components/StudipDialog.vue     |  74 +-
 .../vue/components/StudipWizardDialog.vue     | 254 ++++++
 .../components/courseware/ActivitiesApp.vue   |  31 +
 .../vue/components/courseware/AdminApp.vue    |   8 +-
 .../courseware/ContentOverviewApp.vue         |  25 -
 .../courseware/CoursewareActionWidget.vue     | 146 +---
 .../courseware/CoursewareActivities.vue       | 124 +++
 .../CoursewareActivitiesWidgetFilterType.vue  |  61 ++
 .../CoursewareActivitiesWidgetFilterUnit.vue  |  58 ++
 .../courseware/CoursewareActivityItem.vue     |  66 +-
 .../CoursewareAdminActionWidget.vue           |  18 +-
 .../courseware/CoursewareAdminTemplates.vue   |   5 +-
 .../courseware/CoursewareAdminViewWidget.vue  |  13 +-
 .../courseware/CoursewareBlockAdderArea.vue   |  23 +-
 .../courseware/CoursewareBlockComments.vue    |  10 +-
 .../courseware/CoursewareBlockEdit.vue        |  11 +-
 .../courseware/CoursewareBlockFeedback.vue    |  10 +-
 .../courseware/CoursewareBlockInfo.vue        |   8 +-
 .../courseware/CoursewareBlockadderItem.vue   |  11 +-
 .../courseware/CoursewareCanvasBlock.vue      |   3 +-
 .../courseware/CoursewareCompanionBox.vue     |   2 +-
 .../courseware/CoursewareCompanionOverlay.vue |   7 +-
 .../courseware/CoursewareConfirmBlock.vue     |   3 +-
 .../CoursewareContentOverviewActionWidget.vue |  25 -
 .../CoursewareContentOverviewElements.vue     | 622 --------------
 .../CoursewareContentOverviewFilterWidget.vue |  99 ---
 .../courseware/CoursewareCourseDashboard.vue  |  93 ---
 .../CoursewareDashboardActivities.vue         | 110 ---
 .../CoursewareDashboardStudents.vue           |  15 +-
 .../CoursewareDashboardViewWidget.vue         |  56 --
 .../courseware/CoursewareDefaultContainer.vue |   6 +-
 .../courseware/CoursewareDownloadBlock.vue    |   3 +-
 .../courseware/CoursewareEmptyElementBox.vue  |  25 +-
 .../courseware/CoursewareExportWidget.vue     |   8 +-
 .../courseware/CoursewareHeadlineBlock.vue    |  38 +-
 .../courseware/CoursewareImportWidget.vue     |  42 +
 .../courseware/CoursewareKeyPointBlock.vue    |  36 +-
 .../courseware/CoursewareManagerElement.vue   |   4 +-
 .../courseware/CoursewareManagerFiling.vue    |  15 +-
 .../courseware/CoursewareRibbon.vue           |  30 +-
 .../courseware/CoursewareRibbonToolbar.vue    |  24 +-
 .../courseware/CoursewareSearchWidget.vue     |  78 +-
 .../CoursewareShelfActionWidget.vue           |  30 +
 .../courseware/CoursewareShelfDialogAdd.vue   | 277 +++++++
 .../courseware/CoursewareShelfDialogCopy.vue  | 362 +++++++++
 .../CoursewareShelfDialogImport.vue           | 371 +++++++++
 .../CoursewareShelfImportWidget.vue           |  37 +
 .../CoursewareStructuralElement.vue           | 274 +++----
 .../CoursewareStructuralElementComments.vue   |  10 +-
 .../CoursewareStructuralElementDialogCopy.vue | 466 +++++++++++
 ...oursewareStructuralElementDialogImport.vue | 198 +++++
 .../CoursewareStructuralElementDialogLink.vue | 264 ++++++
 .../CoursewareStructuralElementFeedback.vue   |  10 +-
 .../CoursewareTableOfContentsBlock.vue        |   2 +-
 .../CoursewareTasksActionWidget.vue           |  31 +
 .../CoursewareTasksDialogDistribute.vue       | 633 +++++++++++++++
 .../components/courseware/CoursewareTile.vue  | 137 ++++
 .../courseware/CoursewareTimelineBlock.vue    |  24 +-
 .../courseware/CoursewareToolsAdmin.vue       | 272 -------
 .../courseware/CoursewareToolsBlockadder.vue  |  13 +-
 .../courseware/CoursewareUnitItem.vue         | 184 +++++
 .../CoursewareUnitItemDialogExport.vue        | 132 +++
 .../CoursewareUnitItemDialogSettings.vue      | 311 +++++++
 .../courseware/CoursewareUnitItems.vue        |  77 ++
 ...rogress.vue => CoursewareUnitProgress.vue} |  40 +-
 ...tem.vue => CoursewareUnitProgressItem.vue} |   8 +-
 .../courseware/CoursewareViewWidget.vue       |   2 +-
 ...Screen.vue => CoursewareWelcomeScreen.vue} |  57 +-
 .../components/courseware/DashboardApp.vue    |  36 -
 .../vue/components/courseware/IndexApp.vue    |  25 +-
 .../vue/components/courseware/ShelfApp.vue    |  65 ++
 .../vue/components/courseware/TasksApp.vue    |  31 +
 ...ew-app.js => courseware-activities-app.js} |  53 +-
 resources/vue/courseware-index-app.js         |  14 +-
 resources/vue/courseware-shelf-app.js         |  93 +++
 ...shboard-app.js => courseware-tasks-app.js} |  11 +-
 resources/vue/mixins/courseware/colors.js     | 149 ++++
 resources/vue/mixins/courseware/export.js     |  63 +-
 resources/vue/mixins/courseware/import.js     |  45 +-
 .../courseware-activities.module.js           |  49 ++
 .../courseware/courseware-shelf.module.js     | 766 ++++++++++++++++++
 .../courseware/courseware-tasks.module.js     |  37 +
 .../vue/store/courseware/courseware.module.js |  95 ++-
 .../vue/store/courseware/structure.module.js  |   6 +-
 129 files changed, 8262 insertions(+), 3113 deletions(-)
 create mode 100644 app/controllers/courseware_controller.php
 create mode 100644 app/views/course/courseware/activities.php
 create mode 100644 app/views/course/courseware/courseware.php
 delete mode 100644 app/views/course/courseware/dashboard.php
 create mode 100644 app/views/course/courseware/tasks.php
 create mode 100644 db/migrations/5.3.16_create_cw_units_table.php
 create mode 100644 db/migrations/5.3.17_change_cw_config.php
 create mode 100644 lib/classes/JsonApi/Routes/Courseware/CoursesUnitsIndex.php
 create mode 100644 lib/classes/JsonApi/Routes/Courseware/UnitsCopy.php
 create mode 100644 lib/classes/JsonApi/Routes/Courseware/UnitsCreate.php
 create mode 100644 lib/classes/JsonApi/Routes/Courseware/UnitsDelete.php
 create mode 100644 lib/classes/JsonApi/Routes/Courseware/UnitsIndex.php
 create mode 100644 lib/classes/JsonApi/Routes/Courseware/UnitsShow.php
 create mode 100644 lib/classes/JsonApi/Routes/Courseware/UnitsUpdate.php
 create mode 100644 lib/classes/JsonApi/Routes/Courseware/UserProgressesOfUnitsShow.php
 create mode 100644 lib/classes/JsonApi/Routes/Courseware/UsersUnitsIndex.php
 create mode 100644 lib/classes/JsonApi/Schemas/Courseware/Unit.php
 create mode 100644 lib/models/Courseware/Unit.php
 create mode 100644 resources/assets/stylesheets/scss/wizard.scss
 create mode 100644 resources/vue/components/StudipWizardDialog.vue
 create mode 100644 resources/vue/components/courseware/ActivitiesApp.vue
 delete mode 100644 resources/vue/components/courseware/ContentOverviewApp.vue
 create mode 100644 resources/vue/components/courseware/CoursewareActivities.vue
 create mode 100644 resources/vue/components/courseware/CoursewareActivitiesWidgetFilterType.vue
 create mode 100644 resources/vue/components/courseware/CoursewareActivitiesWidgetFilterUnit.vue
 delete mode 100644 resources/vue/components/courseware/CoursewareContentOverviewActionWidget.vue
 delete mode 100644 resources/vue/components/courseware/CoursewareContentOverviewElements.vue
 delete mode 100644 resources/vue/components/courseware/CoursewareContentOverviewFilterWidget.vue
 delete mode 100644 resources/vue/components/courseware/CoursewareCourseDashboard.vue
 delete mode 100644 resources/vue/components/courseware/CoursewareDashboardActivities.vue
 delete mode 100644 resources/vue/components/courseware/CoursewareDashboardViewWidget.vue
 create mode 100644 resources/vue/components/courseware/CoursewareImportWidget.vue
 create mode 100644 resources/vue/components/courseware/CoursewareShelfActionWidget.vue
 create mode 100644 resources/vue/components/courseware/CoursewareShelfDialogAdd.vue
 create mode 100644 resources/vue/components/courseware/CoursewareShelfDialogCopy.vue
 create mode 100644 resources/vue/components/courseware/CoursewareShelfDialogImport.vue
 create mode 100644 resources/vue/components/courseware/CoursewareShelfImportWidget.vue
 create mode 100644 resources/vue/components/courseware/CoursewareStructuralElementDialogCopy.vue
 create mode 100644 resources/vue/components/courseware/CoursewareStructuralElementDialogImport.vue
 create mode 100644 resources/vue/components/courseware/CoursewareStructuralElementDialogLink.vue
 create mode 100644 resources/vue/components/courseware/CoursewareTasksActionWidget.vue
 create mode 100644 resources/vue/components/courseware/CoursewareTasksDialogDistribute.vue
 create mode 100644 resources/vue/components/courseware/CoursewareTile.vue
 delete mode 100644 resources/vue/components/courseware/CoursewareToolsAdmin.vue
 create mode 100644 resources/vue/components/courseware/CoursewareUnitItem.vue
 create mode 100644 resources/vue/components/courseware/CoursewareUnitItemDialogExport.vue
 create mode 100644 resources/vue/components/courseware/CoursewareUnitItemDialogSettings.vue
 create mode 100644 resources/vue/components/courseware/CoursewareUnitItems.vue
 rename resources/vue/components/courseware/{CoursewareDashboardProgress.vue => CoursewareUnitProgress.vue} (72%)
 rename resources/vue/components/courseware/{CoursewareDashboardProgressItem.vue => CoursewareUnitProgressItem.vue} (71%)
 rename resources/vue/components/courseware/{CoursewareWellcomeScreen.vue => CoursewareWelcomeScreen.vue} (57%)
 delete mode 100644 resources/vue/components/courseware/DashboardApp.vue
 create mode 100644 resources/vue/components/courseware/ShelfApp.vue
 create mode 100644 resources/vue/components/courseware/TasksApp.vue
 rename resources/vue/{courseware-content-overview-app.js => courseware-activities-app.js} (64%)
 create mode 100644 resources/vue/courseware-shelf-app.js
 rename resources/vue/{courseware-dashboard-app.js => courseware-tasks-app.js} (88%)
 create mode 100644 resources/vue/mixins/courseware/colors.js
 create mode 100644 resources/vue/store/courseware/courseware-activities.module.js
 create mode 100644 resources/vue/store/courseware/courseware-shelf.module.js
 create mode 100644 resources/vue/store/courseware/courseware-tasks.module.js

diff --git a/app/controllers/contents/courseware.php b/app/controllers/contents/courseware.php
index a91569c7b83..6ee2b786707 100644
--- a/app/controllers/contents/courseware.php
+++ b/app/controllers/contents/courseware.php
@@ -1,8 +1,11 @@
 <?php
 
-use \Courseware\StructuralElement;
+require_once __DIR__.'/../courseware_controller.php';
 
-class Contents_CoursewareController extends AuthenticatedController
+use Courseware\StructuralElement;
+use Courseware\Unit;
+
+class Contents_CoursewareController extends CoursewareController
 {
     /**
      * Callback function being called before an action is executed.
@@ -10,7 +13,7 @@ class Contents_CoursewareController extends AuthenticatedController
      * @SuppressWarnings(PHPMD.CamelCaseMethodName)
      * @SuppressWarnings(PHPMD.Superglobals)
      */
-    public function before_filter(&$action, &$args)
+    public function before_filter(&$action, &$args): void
     {
         parent::before_filter($action, $args);
 
@@ -30,34 +33,20 @@ class Contents_CoursewareController extends AuthenticatedController
      * @SuppressWarnings(PHPMD.Superglobals)
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    public function index_action()
+    public function index_action(): void
     {
-        Navigation::activateItem('/contents/courseware/overview');
+        Navigation::activateItem('/contents/courseware/shelf');
         $this->user_id = $GLOBALS['user']->id;
-        $this->setOverviewSidebar();
-        $this->courseware_root = \Courseware\StructuralElement::getCoursewareUser($this->user_id);
-        if (!$this->courseware_root) {
-            // create initial courseware dataset
-            $new = \Courseware\StructuralElement::createEmptyCourseware($this->user_id, 'user');
-            $this->courseware_root = $new->getRoot();
-        }
-        $this->licenses = $this->getLicences();
+        $this->setShelfSidebar();
+
+        $this->licenses = $this->getLicenses();
     }
 
-    private function setOverviewSidebar()
+    private function setShelfSidebar(): void
     {
         $sidebar = Sidebar::Get();
-        $views = new TemplateWidget(
-            _('Aktionen'),
-            $this->get_template_factory()->open('contents/courseware/overview_action_widget')
-        );
-        $sidebar->addWidget($views)->addLayoutCSSClass('courseware-overview-filter-widget');
-
-        $views = new TemplateWidget(
-            _('Filter'),
-            $this->get_template_factory()->open('contents/courseware/overview_filter_widget')
-        );
-        $sidebar->addWidget($views)->addLayoutCSSClass('courseware-overview-filter-widget');
+        $sidebar->addWidget(new VueWidget('courseware-action-widget'));
+        $sidebar->addWidget(new VueWidget('courseware-import-widget'));
     }
 
     /**
@@ -69,90 +58,29 @@ class Contents_CoursewareController extends AuthenticatedController
      * @SuppressWarnings(PHPMD.Superglobals)
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    public function courseware_action($action = false, $widgetId = null)
+    public function courseware_action($unit_id = null): void
     {
         global $perm, $user;
 
-        Navigation::activateItem('/contents/courseware/courseware');
         $this->user_id = $user->id;
-
+        /** @var array<mixed> $last */
         $last = UserConfig::get($this->user_id)->getValue('COURSEWARE_LAST_ELEMENT');
 
-        if (!empty($last[$this->user_id])) {
-            $this->entry_element_id = $last['global'];
-            $struct = \Courseware\StructuralElement::findOneBySQL(
-                "id = ? AND range_id = ? AND range_type = 'user'",
-                [$this->entry_element_id, $this->user_id]
-            );
-        }
-
-        // load courseware for current user
-        if (!$this->entry_element_id || !$struct || !$struct->canRead($user)) {
-
-            if (!$user->courseware) {
-                // create initial courseware dataset
-                $struct = StructuralElement::createEmptyCourseware($this->user_id, 'user');
-            }
-
-            $this->entry_element_id = $user->courseware->id;
-        }
-
-        $last[$this->user_id] = $this->entry_element_id;
-        UserConfig::get($this->user_id)->store('COURSEWARE_LAST_ELEMENT', $last);
+        if ($unit_id === null) {
+            $this->redirectToFirstUnit('user', $this->user_id, $last);
 
-        $this->licenses = $this->getLicences();
-
-        $this->oer_enabled = Config::get()->OERCAMPUS_ENABLED && $perm->have_perm(Config::get()->OER_PUBLIC_STATUS);
-
-        // Make sure struct has value., to evaluate the export (edit) capability.
-        if (!isset($struct)) {
-            $struct = \Courseware\StructuralElement::findOneBySQL(
-                "id = ? AND range_id = ? AND range_type = 'user'",
-                [$this->entry_element_id, $this->user_id]
-            );
+            return;
         }
-        $this->setCoursewareSidebar();
-    }
-
-    private function setCoursewareSidebar()
-    {
-        $sidebar = \Sidebar::Get();
-        $sidebar->addWidget(new VueWidget('courseware-action-widget'));
 
-        $views = new TemplateWidget(
-            _('Suche'),
-            $this->get_template_factory()->open('course/courseware/search_widget')
-        );
-        $sidebar->addWidget($views)->addLayoutCSSClass('courseware-search-widget');
-
-        $sidebar->addWidget(new VueWidget('courseware-view-widget'));
-        $sidebar->addWidget(new VueWidget('courseware-export-widget'));
-    }
-
-    private function getLicences()
-    {
-        $licenses = array();
-        $sorm_licenses = License::findBySQL("1 ORDER BY name ASC");
-        foreach($sorm_licenses as $license) {
-            array_push($licenses, $license->toArray());
+        $this->entry_element_id = null;
+        $this->unit_id = null;
+        $unit = Unit::find($unit_id);
+        if (isset($unit)) {
+            $this->setEntryElement('user', $unit, $last, $this->user_id);
+            Navigation::activateItem('/contents/courseware/courseware');
+            $this->licenses = $this->getLicenses();
+            $this->setCoursewareSidebar();
         }
-        return json_encode($licenses);
-    }
-
-    /**
-     * displays the courseware manager
-     *
-     * @param string $action
-     * @param string $widgetId
-     * @SuppressWarnings(PHPMD.CamelCaseMethodName)
-     * @SuppressWarnings(PHPMD.Superglobals)
-     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
-     */
-    public function courseware_manager_action($action = false, $widgetId = null)
-    {
-        Navigation::activateItem('/contents/courseware/courseware_manager');
-
-        $this->user_id = $GLOBALS['user']->id;
     }
 
     /**
@@ -165,7 +93,7 @@ class Contents_CoursewareController extends AuthenticatedController
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
 
-    public function bookmarks_action()
+    public function bookmarks_action(): void
     {
         Navigation::activateItem('/contents/courseware/bookmarks');
         $this->user_id = $GLOBALS['user']->id;
@@ -180,13 +108,13 @@ class Contents_CoursewareController extends AuthenticatedController
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
 
-    public function releases_action()
+    public function releases_action(): void
     {
         Navigation::activateItem('/contents/courseware/releases');
         $this->user_id = $GLOBALS['user']->id;
     }
 
-    private function setBookmarkSidebar()
+    private function setBookmarkSidebar(): void
     {
         $sidebar = Sidebar::Get();
         $views = new TemplateWidget(
@@ -205,7 +133,7 @@ class Contents_CoursewareController extends AuthenticatedController
      * @SuppressWarnings(PHPMD.Superglobals)
      * @SuppressWarnings(PHPMD.UnusedFormalParameter)
      */
-    public function courses_overview_action($action = false, $widgetId = null)
+    public function courses_overview_action($action = false, $widgetId = null): void
     {
         Navigation::activateItem('/contents/courseware/courses_overview');
 
@@ -244,7 +172,7 @@ class Contents_CoursewareController extends AuthenticatedController
      *
      * @return array
      */
-    private function getCoursewareCourses($sem_key)
+    private function getCoursewareCourses($sem_key): array
     {
         $this->current_semester = Semester::findCurrent();
 
@@ -326,7 +254,7 @@ class Contents_CoursewareController extends AuthenticatedController
      * @param  string  $course_id  the course to check
      * @return boolean             true if courseware is enabled, false otherwise
      */
-    private function isCoursewareEnabled($course_id)
+    private function isCoursewareEnabled($course_id): bool
     {
         $studip_module = PluginManager::getInstance()->getPlugin('CoursewareModule');
 
@@ -338,7 +266,7 @@ class Contents_CoursewareController extends AuthenticatedController
     }
 
 
-    private function getProjects($purpose)
+    private function getProjects($purpose): array
     {
         $elements = StructuralElement::findProjects($this->user->id, $purpose);
         foreach($elements as &$element) {
@@ -348,85 +276,8 @@ class Contents_CoursewareController extends AuthenticatedController
         return $elements;
     }
 
-    public function create_project_action($action = false, $widgetId = null)
-    {
-        PageLayout::setTitle(_('Neues Lernmaterial'));
-
-        if (!Request::submitted('create_project')) {
-            return;
-        }
-
-        CSRFProtection::verifyUnsafeRequest();
-        $this->user_id = $GLOBALS['user']->id;
-
-        $structural_element = new StructuralElement();
-        $structural_element->title = Request::get('title');
-        $structural_element->purpose = Request::get('project_type');
-        $structural_element->owner_id = $this->user_id;
-        $structural_element->editor_id = $this->user_id;
-        $structural_element->release_date = "";
-        $structural_element->withdraw_date = "";
-        $structural_element->range_id = $this->user_id;
-        $structural_element->range_type = 'user';
-        $structural_element->parent_id = StructuralElement::getCoursewareUser($this->user_id)->id;
-        $structural_element->payload = json_encode([
-            'description'      => Request::get('description'),
-            'color'            => Request::get('color'),
-            'required_time'    => Request::get('required_time'),
-            'license_type'     => Request::get('license_type'),
-            'difficulty_start' => Request::get('difficulty_start'),
-            'difficulty_end'   => Request::get('difficulty_end'),
-        ]);
-        $structural_element->store();
-
-        // set image
-        if ($_FILES['previewfile'] && $_FILES['previewfile']['name']) {
-            $coursewareInstance = new Courseware\Instance($structural_element);
-            $publicFolder = Courseware\Filesystem\PublicFolder::findOrCreateTopFolder($coursewareInstance);
-            $fileRef = $this->handleUpload($publicFolder, $structural_element);
-            $structural_element->image_id = $fileRef->id;
-            $structural_element->store();
-        }
-
-        $this->redirect('contents/courseware/index');
-    }
-
-    private function handleUpload(Courseware\Filesystem\PublicFolder $folder, StructuralElement $structuralElement)
-    {
-        $file = $_FILES['previewfile'];
-        $upload = [
-            'tmp_name' => [$file['tmp_name']],
-            'name'     => [$file['name']],
-            'size'     => [$file['size']],
-            'type'     => [$file['type']],
-            'error'    => [$file['error']]
-        ];
-
-        $uploaded = FileManager::handleFileUpload(
-            $upload,
-            $folder
-        );
-
-        if ($uploaded['error']) {
-            throw new RuntimeException(implode("\n", $uploaded['error']));
-        }
-
-        if (count($uploaded['files'])) {
-            return $uploaded['files'][0];
-        }
-
-        throw new RuntimeException('Could not create preview image.');
-    }
-
-    private function setProjectsSidebar($action)
-    {
-        $sidebar = Sidebar::Get();
-        $actions = new ActionsWidget();
-        $actions->addLink(_('Neues Lernmaterial anlegen'), $this->url_for('contents/courseware/create_project'), Icon::create('add', 'clickable'))->asDialog('size=700');
-        $sidebar->addWidget($actions);
-    }
 
-    public function pdf_export_action($element_id, $with_children)
+    public function pdf_export_action($element_id, $with_children): void
     {
         $element = \Courseware\StructuralElement::findOneById($element_id);
 
@@ -438,7 +289,7 @@ class Contents_CoursewareController extends AuthenticatedController
      *
      * @param string $entry_element_id the shared struct element id
      */
-    public function shared_content_courseware_action($entry_element_id)
+    public function shared_content_courseware_action($entry_element_id): void
     {
         global $perm, $user;
 
@@ -463,7 +314,7 @@ class Contents_CoursewareController extends AuthenticatedController
 
         $this->user_id = $struct->owner_id;
 
-        $this->licenses = $this->getLicences();
+        $this->licenses = $this->getLicenses();
 
         $this->oer_enabled = Config::get()->OERCAMPUS_ENABLED && $perm->have_perm(Config::get()->OER_PUBLIC_STATUS);
 
diff --git a/app/controllers/course/courseware.php b/app/controllers/course/courseware.php
index 3b4a42a39e6..557798708a2 100644
--- a/app/controllers/course/courseware.php
+++ b/app/controllers/course/courseware.php
@@ -1,8 +1,9 @@
 <?php
 
+require_once __DIR__.'/../courseware_controller.php';
+
 use Courseware\StructuralElement;
-use Courseware\Instance;
-use Courseware\UserProgress;
+use Courseware\Unit;
 
 /**
  * @property ?string $entry_element_id
@@ -11,11 +12,11 @@ use Courseware\UserProgress;
  * @property mixed $courseware_progress_data
  * @property mixed $courseware_chapter_counter
  */
-class Course_CoursewareController extends AuthenticatedController
+class Course_CoursewareController extends CoursewareController
 {
     protected $_autobind = true;
 
-    public function before_filter(&$action, &$args)
+    public function before_filter(&$action, &$args): void
     {
         parent::before_filter($action, $args);
 
@@ -31,76 +32,56 @@ class Course_CoursewareController extends AuthenticatedController
         $this->last_visitdate = object_get_visit(Context::getId(), $this->studip_module->getPluginId());
     }
 
-    public function index_action()
+    public function index_action(): void
     {
-        /** @var array<mixed> $last */
-        $last = UserConfig::get($GLOBALS['user']->id)->getValue('COURSEWARE_LAST_ELEMENT');
-        if (isset($last[Context::getId()])) {
-            $this->entry_element_id = $last[Context::getId()];
-            /** @var ?StructuralElement $struct */
-            $struct = StructuralElement::findOneBySQL("id = ? AND range_id = ? AND range_type = 'course'", [
-                $this->entry_element_id,
-                Context::getId(),
-            ]);
-        }
-
-        // load courseware for course
-        if (!$this->entry_element_id || !$struct || !$struct->canRead($GLOBALS['user'])) {
-            $course = Course::find(Context::getId());
+        Navigation::activateItem('course/courseware/shelf');
+        $this->licenses = $this->getLicenses();
+        $this->setIndexSidebar();
+    }
 
-            if (!$course->courseware) {
-                // create initial courseware dataset
-                $instance = StructuralElement::createEmptyCourseware(Context::getId(), 'course');
-                $struct = $instance->getRoot();
-            }
+    public function courseware_action($unit_id = null):  void
+    {
+        global $perm, $user;
 
-            $this->entry_element_id = $course->courseware->id;
-        }
+        $this->user_id = $user->id;
+        /** @var array<mixed> $last */
+        $last = UserConfig::get($this->user_id)->getValue('COURSEWARE_LAST_ELEMENT');
 
-        $last[Context::getId()] = $this->entry_element_id;
-        UserConfig::get($GLOBALS['user']->id)->store('COURSEWARE_LAST_ELEMENT', $last);
+        if ($unit_id === null) {
+            $this->redirectToFirstUnit('course', Context::getId(), $last);
 
-        Navigation::activateItem('course/courseware/content');
-        $this->licenses = [];
-        $sorm_licenses = License::findBySQL('1 ORDER BY name ASC');
-        foreach ($sorm_licenses as $license) {
-            array_push($this->licenses, $license->toArray());
+            return;
         }
-        $this->licenses = json_encode($this->licenses);
 
-        // Make sure struct has value., to evaluate the export (edit) capability.
-        if (!isset($struct)) {
-            $struct = StructuralElement::findOneBySQL("id = ? AND range_id = ? AND range_type = 'course'", [
-                $this->entry_element_id,
-                Context::getId(),
-            ]);
+        $this->entry_element_id = null;
+        $this->unit_id = null;
+        $unit = Unit::find($unit_id);
+        if (isset($unit)) {
+            $this->setEntryElement('course', $unit, $last, Context::getId());
+
+            Navigation::activateItem('course/courseware/unit');
+            $this->licenses = $this->getLicenses();
+            $this->setCoursewareSidebar();
         }
-        $this->setIndexSidebar();
     }
 
-    public function dashboard_action(): void
+    public function tasks_action(): void
     {
         global $perm, $user;
         $this->is_teacher = $perm->have_studip_perm('tutor', Context::getId(), $user->id);
-        $this->courseware_progress_data = $this->getProgressData($this->is_teacher);
-        $this->courseware_chapter_counter = $this->getChapterCounter($this->courseware_progress_data);
-        Navigation::activateItem('course/courseware/dashboard');
-        $this->setDashboardSidebar();
+        Navigation::activateItem('course/courseware/tasks');
+        $this->setTasksSidebar();
     }
 
-    public function manager_action(): void
+    public function activities_action(): void
     {
-        $courseId = Context::getId();
-        $element = StructuralElement::getCoursewareCourse($courseId);
-        $instance = new Instance($element);
-        if (!$GLOBALS['perm']->have_studip_perm($instance->getEditingPermissionLevel(), $courseId)) {
-            $this->redirect('course/courseware/index');
-        } else {
-            Navigation::activateItem('course/courseware/manager');
-        }
+        global $perm, $user;
+        $this->is_teacher = $perm->have_studip_perm('tutor', Context::getId(), $user->id);
+        Navigation::activateItem('course/courseware/activities');
+        $this->setActivitiesSidebar();
     }
 
-    public function pdf_export_action($element_id, $with_children)
+    public function pdf_export_action($element_id, $with_children): void
     {
         $element = \Courseware\StructuralElement::findOneById($element_id);
         $user = User::find($GLOBALS['user']->id);
@@ -111,205 +92,19 @@ class Course_CoursewareController extends AuthenticatedController
     {
         $sidebar = Sidebar::Get();
         $sidebar->addWidget(new VueWidget('courseware-action-widget'));
-
-        $views = new TemplateWidget(
-            _('Suche'),
-            $this->get_template_factory()->open('course/courseware/search_widget')
-        );
-        $sidebar->addWidget($views)->addLayoutCSSClass('courseware-search-widget');
-
-        $sidebar->addWidget(new VueWidget('courseware-view-widget'));
-        $sidebar->addWidget(new VueWidget('courseware-export-widget'));
+        $sidebar->addWidget(new VueWidget('courseware-import-widget'));
     }
 
-    private function setDashboardSidebar(): void
+    private function setTasksSidebar(): void
     {
         $sidebar = Sidebar::Get();
-        $views = new TemplateWidget(
-            _('Ansichten'),
-            $this->get_template_factory()->open('course/courseware/dashboard_view_widget')
-        );
-        $sidebar->addWidget($views)->addLayoutCSSClass('courseware-dashboard-view-widget');
-    }
-
-    private function getProgressData(bool $showProgressForAllParticipants = false): iterable
-    {
-        /** @var ?\Course $course */
-        $course = Context::get();
-        if (!$course || !$course->courseware) {
-            return [];
-        }
-
-        $instance = new Instance($course->courseware);
-        $user = \User::findCurrent();
-
-        $elements = $this->findElements($instance, $user);
-        $progress = $this->computeSelfProgresses($instance, $user, $elements, $showProgressForAllParticipants);
-        $progress = $this->computeCumulativeProgresses($instance, $elements, $progress);
-
-        return $this->prepareProgressData($elements, $progress);
-    }
-
-    private function findElements(Instance $instance, User $user): iterable
-    {
-        $elements = $instance->getRoot()->findDescendants($user);
-        $elements[] = $instance->getRoot();
-
-        return array_combine(array_column($elements, 'id'), $elements);
-    }
-
-    private function computeChildrenOf(iterable &$elements): iterable
-    {
-        $childrenOf = [];
-        foreach ($elements as $elementId => $element) {
-            if ($element['parent_id']) {
-                if (!isset($childrenOf[$element['parent_id']])) {
-                    $childrenOf[$element['parent_id']] = [];
-                }
-                $childrenOf[$element['parent_id']][] = $elementId;
-            }
-        }
-
-        return $childrenOf;
-    }
-
-    private function computeSelfProgresses(
-        Instance $instance,
-        User $user,
-        iterable &$elements,
-        bool $showProgressForAllParticipants
-    ): iterable {
-        $progress = [];
-        /** @var \Course $course */
-        $course = $instance->getRange();
-        $allBlockIds = $instance->findAllBlocksGroupedByStructuralElementId(function ($row) {
-            return $row['id'];
-        });
-        $courseMemberIds = $showProgressForAllParticipants
-            ? array_column($course->getMembersWithStatus('autor'), 'user_id')
-            : [$user->getId()];
-
-        $sql =
-            'SELECT block_id, COUNT(grade) as count, SUM(grade) as grade ' .
-            'FROM cw_user_progresses ' .
-            'WHERE block_id IN (?) AND user_id IN (?) ' .
-            'GROUP BY block_id';
-        $userProgresses = \DBManager::get()->fetchGrouped($sql, [$allBlockIds, $courseMemberIds]);
-
-        foreach ($elements as $elementId => $element) {
-            $selfProgress = $this->getSelfProgresses($allBlockIds, $elementId, $userProgresses, $courseMemberIds);
-            $progress[$elementId] = [
-                'self' => $selfProgress['counter'] ? $selfProgress['progress'] / $selfProgress['counter'] : 1,
-            ];
-        }
-
-        return $progress;
-    }
-
-    private function getSelfProgresses(
-        array &$allBlockIds,
-        string $elementId,
-        array &$userProgresses,
-        array &$courseMemberIds
-    ): array {
-        $blks = $allBlockIds[$elementId] ?: [];
-        if (!count($blks)) {
-            return [
-                'counter' => 0,
-                'progress' => 1,
-            ];
-        }
-
-        $data = [
-            'counter' => count($blks),
-            'progress' => 0,
-        ];
-
-        $usersCounter = count($courseMemberIds);
-        foreach ($blks as $blk) {
-            $progresses = $userProgresses[$blk];
-            $usersProgress = $progresses['count'] ? (float) $progresses['grade'] : 0;
-            $data['progress'] += $usersCounter ? $usersProgress / $usersCounter : 0;
-        }
-
-        return $data;
-    }
-
-    private function computeCumulativeProgresses(Instance $instance, iterable &$elements, iterable &$progress): iterable
-    {
-        $childrenOf = $this->computeChildrenOf($elements);
-
-        // compute `cumulative` of each element
-        $visitor = function (&$progress, $element) use (&$childrenOf, &$elements, &$visitor) {
-            $elementId = $element->getId();
-            $numberOfNodes = 0;
-            $cumulative = 0;
-
-            // visit children first
-            if (isset($childrenOf[$elementId])) {
-                foreach ($childrenOf[$elementId] as $childId) {
-                    $visitor($progress, $elements[$childId]);
-                    $numberOfNodes += $progress[$childId]['numberOfNodes'];
-                    $cumulative += $progress[$childId]['cumulative'];
-                }
-            }
-
-            $progress[$elementId]['cumulative'] = $cumulative + $progress[$elementId]['self'];
-            $progress[$elementId]['numberOfNodes'] = $numberOfNodes + 1;
-
-            return $progress;
-        };
-
-        $visitor($progress, $instance->getRoot());
-
-        return $progress;
-    }
-
-    private function prepareProgressData(iterable &$elements, iterable &$progress): iterable
-    {
-        $data = [];
-        foreach ($elements as $elementId => $element) {
-            $elementProgress = $progress[$elementId];
-            $cumulative = $elementProgress['cumulative'] / $elementProgress['numberOfNodes'];
-
-            $data[$elementId] = [
-                'id' => (int) $elementId,
-                'parent_id' => (int) $element['parent_id'],
-                'name' => $element['title'],
-                'progress' => [
-                    'cumulative' => round($cumulative, 2) * 100,
-                    'self' => round($elementProgress['self'], 2) * 100,
-                ],
-            ];
-        }
-
-        return $data;
+        $sidebar->addWidget(new VueWidget('courseware-action-widget'));
     }
 
-    private function getChapterCounter(array &$chapters): array
+    private function setActivitiesSidebar(): void
     {
-        $finished = 0;
-        $started = 0;
-        $ahead = 0;
-
-        foreach ($chapters as $chapter) {
-            if ($chapter['parent_id'] != null) {
-                if ($chapter['progress']['self'] == 0) {
-                    $ahead += 1;
-                }
-                if ($chapter['progress']['self'] > 0 && $chapter['progress']['self'] < 100) {
-                    $started += 1;
-                }
-                if ($chapter['progress']['self'] == 100) {
-                    $finished += 1;
-                }
-            }
-        }
-
-        return [
-            'started' => $started,
-            'finished' => $finished,
-            'ahead' => $ahead,
-        ];
+        $sidebar = Sidebar::Get();
+        $sidebar->addWidget(new VueWidget('courseware-activities-widget-filter-type'));
+        $sidebar->addWidget(new VueWidget('courseware-activities-widget-filter-unit'));
     }
 }
diff --git a/app/controllers/courseware_controller.php b/app/controllers/courseware_controller.php
new file mode 100644
index 00000000000..30fec908f28
--- /dev/null
+++ b/app/controllers/courseware_controller.php
@@ -0,0 +1,76 @@
+<?php
+
+use Courseware\StructuralElement;
+use Courseware\Unit;
+
+abstract class CoursewareController extends AuthenticatedController
+{
+    public function redirectToFirstUnit(string $context, string $rangeId, array $last): void
+    {
+        $path = $context === 'user' ? 'contents' : $context;
+        $last_element = $this->getLastElement($last, $context, $rangeId);
+        if ($last_element) {
+            $unit = $last_element->findUnit($last);
+        } else {
+            $unit = Unit::findOneBySql('range_id = ? ORDER BY mkdate ASC', [$rangeId]);
+        }
+        $this->redirect($path . '/courseware/courseware/' . $unit->id);
+    }
+
+    public function setEntryElement(string $context, Unit $unit, array $last, string $rangeId): void
+    {
+        $this->unit_id = $unit->id;
+        $last_element = $this->getLastElement($last, $context, $rangeId);
+        if($last_element) {
+            $last_element_unit = $last_element->findUnit();
+        }
+        if ($last_element_unit->id === $unit->id) {
+            $this->entry_element_id = $last_element->id;
+        } else {
+            $this->entry_element_id = $unit->structural_element_id;
+        }
+        if ($this->entry_element_id) {
+            $last_element_item = $context === 'user' ? 'global' : $rangeId;
+            $last[$last_element_item] = $this->entry_element_id;
+            UserConfig::get($GLOBALS['user']->id)->store('COURSEWARE_LAST_ELEMENT', $last);
+        }
+    }
+
+    public function getLastElement(array $last, string $context, string $rangeId): ?StructuralElement
+    {
+        $last_element_item = $context === 'user' ? 'global' : $rangeId;
+        $last_element_id = $last[$last_element_item] ?? false;
+
+        if ($last_element_id) {
+            return StructuralElement::findOneBySQL("id = ? AND range_id = ? AND range_type = ?", [
+                $last_element_id,
+                $rangeId,
+                $context
+            ]);
+        }
+
+        return null;
+    }
+
+    public function getLicenses(): string
+    {
+        $licenses = License::findAndMapBySQL(
+            function (License $license) {
+                return $license->toArray();
+            },
+            '1 ORDER BY name ASC'
+        );
+
+        return json_encode($licenses);
+    }
+
+    public function setCoursewareSidebar(): void
+    {
+        $sidebar = \Sidebar::Get();
+        $sidebar->addWidget(new VueWidget('courseware-action-widget'));
+        $sidebar->addWidget(new VueWidget('courseware-search-widget'));
+        $sidebar->addWidget(new VueWidget('courseware-view-widget'));
+        $sidebar->addWidget(new VueWidget('courseware-import-widget'));
+        $sidebar->addWidget(new VueWidget('courseware-export-widget'));
+    }
+}
\ No newline at end of file
diff --git a/app/views/contents/courseware/bookmarks.php b/app/views/contents/courseware/bookmarks.php
index a0803209707..988c692ebfa 100644
--- a/app/views/contents/courseware/bookmarks.php
+++ b/app/views/contents/courseware/bookmarks.php
@@ -1,6 +1,6 @@
 <div
     id="courseware-content-bookmark-app"
     entry-type="users"
-    entry-id="<?= $user_id ?>"
+    entry-id="<?= htmlReady($user_id) ?>"
 >
 </div>
diff --git a/app/views/contents/courseware/courseware.php b/app/views/contents/courseware/courseware.php
index b50a9637014..bba4f1cc7ed 100644
--- a/app/views/contents/courseware/courseware.php
+++ b/app/views/contents/courseware/courseware.php
@@ -1,8 +1,10 @@
 <div
     id="courseware-index-app"
-    entry-element-id="<?= $entry_element_id ?>"
-    entry-type="users" entry-id="<?= $user_id ?>"
-    oer-enabled='<?= $oer_enabled ?>'
-    licenses='<?= $licenses ?>'
+    entry-element-id="<?= htmlReady($entry_element_id) ?>"
+    entry-type="users"
+    entry-id="<?= htmlReady($user_id) ?>"
+    unit-id="<?= htmlReady($unit_id) ?>"
+    oer-enabled='<?= htmlReady(Config::get()->OERCAMPUS_ENABLED) ?>'
+    licenses='<?= htmlReady($licenses) ?>'
     >
 </div>
diff --git a/app/views/contents/courseware/index.php b/app/views/contents/courseware/index.php
index c0d761d0dc3..b05d7311b69 100644
--- a/app/views/contents/courseware/index.php
+++ b/app/views/contents/courseware/index.php
@@ -1,10 +1,6 @@
-<script>
-    STUDIP.COURSEWARE_USERS_ROOT_ID = <?=$courseware_root->id ?>
-</script>
 <div
-    id="courseware-content-overview-app"
+    id="courseware-shelf-app"
     entry-type="users"
     entry-id="<?= $user_id ?>"
     licenses='<?= $licenses ?>'
->
-</div>
+></div>
\ No newline at end of file
diff --git a/app/views/course/courseware/activities.php b/app/views/course/courseware/activities.php
new file mode 100644
index 00000000000..67726e01077
--- /dev/null
+++ b/app/views/course/courseware/activities.php
@@ -0,0 +1,6 @@
+<div
+    id="courseware-activities-app"
+    entry-type="courses"
+    entry-id="<?= htmlReady(Context::getId()) ?>"
+>
+</div>
diff --git a/app/views/course/courseware/courseware.php b/app/views/course/courseware/courseware.php
new file mode 100644
index 00000000000..68503b0b029
--- /dev/null
+++ b/app/views/course/courseware/courseware.php
@@ -0,0 +1,10 @@
+<div
+    id="courseware-index-app"
+    entry-element-id="<?= htmlReady($entry_element_id) ?>"
+    entry-type="courses"
+    entry-id="<?= htmlReady(Context::getId()) ?>"
+    unit-id="<?= htmlReady($unit_id) ?>"
+    oer-enabled="<?= htmlReady(Config::get()->OERCAMPUS_ENABLED) ?>"
+    licenses='<?= htmlReady($licenses) ?>'
+    >
+</div>
\ No newline at end of file
diff --git a/app/views/course/courseware/dashboard.php b/app/views/course/courseware/dashboard.php
deleted file mode 100644
index 830cc90560d..00000000000
--- a/app/views/course/courseware/dashboard.php
+++ /dev/null
@@ -1,12 +0,0 @@
-<script>
-    STUDIP.courseware_progress_data = <?= json_encode($courseware_progress_data);?>;
-    STUDIP.courseware_chapter_counter = <?= json_encode($courseware_chapter_counter);?>;
-    STUDIP.is_teacher = <?= json_encode($is_teacher);?>;
-</script>
-
-<div
-    id="courseware-dashboard-app"
-    entry-type="courses"
-    entry-id="<?= Context::getId() ?>"
->
-</div>
diff --git a/app/views/course/courseware/index.php b/app/views/course/courseware/index.php
index 518f765a3c0..81296cbb149 100644
--- a/app/views/course/courseware/index.php
+++ b/app/views/course/courseware/index.php
@@ -1,9 +1,6 @@
 <div
-    id="courseware-index-app"
-    entry-element-id="<?= $entry_element_id ?>"
+    id="courseware-shelf-app"
     entry-type="courses"
     entry-id="<?= Context::getId() ?>"
-    oer-enabled="<?= Config::get()->OERCAMPUS_ENABLED?>"
     licenses='<?= $licenses ?>'
-    >
-</div>
+></div>
diff --git a/app/views/course/courseware/tasks.php b/app/views/course/courseware/tasks.php
new file mode 100644
index 00000000000..7ebd70a4166
--- /dev/null
+++ b/app/views/course/courseware/tasks.php
@@ -0,0 +1,6 @@
+<div
+    id="courseware-tasks-app"
+    entry-type="courses"
+    entry-id="<?= htmlReady(Context::getId()) ?>"
+>
+</div>
diff --git a/db/migrations/5.3.16_create_cw_units_table.php b/db/migrations/5.3.16_create_cw_units_table.php
new file mode 100644
index 00000000000..7b9fac35ff0
--- /dev/null
+++ b/db/migrations/5.3.16_create_cw_units_table.php
@@ -0,0 +1,52 @@
+<?php
+
+class CreateCwUnitsTable extends Migration
+{
+    public function description()
+    {
+        return 'create table for courseware units';
+    }
+
+    public function up()
+    {
+        $db = DBManager::get();
+
+        $query = "CREATE TABLE IF NOT EXISTS `cw_units` (
+            `id`                      INT(11) NOT NULL AUTO_INCREMENT,
+            `range_id`                CHAR(32) COLLATE latin1_bin NULL,
+            `range_type`              ENUM('course', 'user') COLLATE latin1_bin,
+            `structural_element_id`   INT(11) NOT NULL,
+            `content_type`            ENUM('courseware') CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
+            `public`                  TINYINT(4) NOT NULL DEFAULT '1',
+            `creator_id`              CHAR(32) COLLATE latin1_bin DEFAULT NULL,
+            `release_date`            INT(11) UNSIGNED DEFAULT NULL,
+            `withdraw_date`           INT(11) UNSIGNED NOT NULL,
+            `mkdate`                  INT(11) UNSIGNED NOT NULL,
+            `chdate`                  INT(11) UNSIGNED NOT NULL,
+
+            PRIMARY KEY (`id`),
+            INDEX index_range_id (`range_id`),
+            INDEX index_structural_element_id (`structural_element_id`)
+        )";
+        $db->exec($query);
+
+        //get all courseware root nodes
+        $query = "SELECT * FROM `cw_structural_elements` WHERE `parent_id` IS NULL";
+        $cw_root_nodes = $db->fetchAll($query);
+
+        // create unit for each courseware root node
+        $insert = $db->prepare(
+            "INSERT INTO `cw_units` (`range_id`, `range_type`, `structural_element_id`, `content_type`, `public`, `creator_id`) 
+             VALUES (?, ?, ?, 'courseware', true, ?)"
+        );
+        foreach ($cw_root_nodes as $courseware) {
+            $insert->execute([$courseware['range_id'], $courseware['range_type'], $courseware['id'], $courseware['owner_id']]);
+        }
+    }
+
+    public function down()
+    {
+        $db = \DBManager::get();
+        $db->exec('DROP TABLE IF EXISTS `cw_units`');
+    }
+}
diff --git a/db/migrations/5.3.17_change_cw_config.php b/db/migrations/5.3.17_change_cw_config.php
new file mode 100644
index 00000000000..949ec58e6ee
--- /dev/null
+++ b/db/migrations/5.3.17_change_cw_config.php
@@ -0,0 +1,56 @@
+<?php
+
+class ChangeCwConfig extends Migration
+{
+    public function description()
+    {
+        return 'change courseware config';
+    }
+
+    public function up()
+    {
+        $db = DBManager::get();
+        $query = "UPDATE `config` SET `value` = '{}', `type` = 'array' WHERE `config`.`field` = 'COURSEWARE_SEQUENTIAL_PROGRESSION'";
+        $db->exec($query);
+        
+        $query = "UPDATE `config` SET `value` = '{}', `type` = 'array' WHERE `config`.`field` = 'COURSEWARE_EDITING_PERMISSION'";
+        $db->exec($query);
+        
+        
+        $update_permission = $db->prepare("UPDATE `config_values` SET `value` = ? WHERE `field` = 'COURSEWARE_EDITING_PERMISSION' AND `range_id` = ?");
+        
+        $find_root = $db->prepare("SELECT * FROM `cw_structural_elements` WHERE `parent_id` IS NULL AND `range_id` = ? ");
+        
+        
+        // get all COURSEWARE_EDITING_PERMISSION 
+        $stmt = $db->prepare("SELECT * FROM `config_values` WHERE `field` = 'COURSEWARE_EDITING_PERMISSION'");
+        $stmt->execute();
+        $cw_permissions = $stmt->fetchAll();
+
+        foreach ($cw_permissions as $permission) {
+            $find_root->execute([$permission['range_id']]);
+            $root = $find_root->fetchAll();
+            $value = json_encode([$root[0]['id'] => $permission['value']], true);
+            $update_permission->execute([$value, $permission['range_id']]);
+        }
+
+        $update_progression = $db->prepare("UPDATE `config_values` SET `value` = ? WHERE `field` = 'COURSEWARE_SEQUENTIAL_PROGRESSION' AND `range_id` = ?");
+        
+        // get all COURSEWARE_SEQUENTIAL_PROGRESSION 
+        $stmt = $db->prepare("SELECT * FROM `config_values` WHERE `field` = 'COURSEWARE_SEQUENTIAL_PROGRESSION'");
+        $stmt->execute();
+        $cw_progressions = $stmt->fetchAll();
+        
+        foreach ($cw_progressions as $progression) {
+            $find_root->execute([$progression['range_id']]);
+            $root = $find_root->fetchAll();
+            $value = json_encode([$root[0]['id'] => $progression['value']], true);
+            $update_progression->execute([$value, $progression['range_id']]);
+        }
+    }
+
+    public function down()
+    {
+        $db = \DBManager::get();
+    }
+}
diff --git a/lib/activities/CoursewareProvider.php b/lib/activities/CoursewareProvider.php
index 335f7bb665c..f7710949f25 100644
--- a/lib/activities/CoursewareProvider.php
+++ b/lib/activities/CoursewareProvider.php
@@ -58,104 +58,225 @@ class CoursewareProvider implements ActivityProvider
      */
     public static function postActivity($event, $resource)
     {
-        $data = null;
-        switch ($event) {
-            case Block::class . 'DidCreate':
-                /**
-                 * @var \Courseware\Block $resource
-                 * @var \Courseware\StructuralElement $structuralElement
-                 */
-                $structuralElement = $resource->getStructuralElement();
-                $data = [
-                    'provider' => self::class,
-                    'context' => $structuralElement->range_type,
-                    'context_id' => $structuralElement->range_id,
-                    'content' => null,
-                    'actor_type' => 'user',
-                    'actor_id' => $resource->owner_id,
-                    'verb' => 'created',
-                    'object_id' => $structuralElement->id,
-                    'object_type' => 'courseware',
-                    'mkdate' => time(),
-                ];
-                break;
+        $structuralElement = null;
+        $rangeType = null;
+        if ($resource instanceof StructuralElement) {
+            $structuralElement = $resource;
+        }
+        if ($resource instanceof Task ||
+            $resource instanceof StructuralElementComment ||
+            $resource instanceof StructuralElementFeedback
+        ) {
+            $structuralElement = $resource['structural_element'];
+        }
+        if ($resource instanceof Block ||
+            $resource instanceof BlockComment ||
+            $resource instanceof BlockFeedback ||
+            $resource instanceof Container ||
+            $resource instanceof TaskFeedback
+        ) {
+            $structuralElement = $resource->getStructuralElement();
+        }
 
-            case Block::class . 'DidUpdate':
-                /**
-                 * @var \Courseware\Block $resource
-                 * @var \Courseware\StructuralElement $structuralElement
-                 */
-                $structuralElement = $resource->getStructuralElement();
-                $payload = $resource->type->getPayload();
-                if (
-                    (isset($payload['text']) && $payload['text'] != '') ||
-                    (isset($payload['content']) && $payload['content'] != '')
-                ) {
+        if ($structuralElement !== null) {
+            $rangeType = $structuralElement->range_type;
+        }
+
+        if ($rangeType === 'courses' || $rangeType === 'course') {
+            $data = null;
+            switch ($event) {
+                case Block::class . 'DidCreate':
+                    /**
+                     * @var \Courseware\Block $resource
+                     * @var \Courseware\StructuralElement $structuralElement
+                     */
+                    $blockType = $resource->type;
+                    $blockTitle = $blockType->getTitle() ?: $resource->getBlockType();
+                    $content = _('Ein Block vom Typ "%1$s" wurde auf der Seite "%2$s" eingefügt.');
+                    $content = sprintf($content, $blockTitle, $structuralElement->title);
                     $data = [
                         'provider' => self::class,
                         'context' => $structuralElement->range_type,
                         'context_id' => $structuralElement->range_id,
-                        'content' => null,
+                        'content' => $content,
+                        'actor_type' => 'user',
+                        'actor_id' => $resource->owner_id,
+                        'verb' => 'created',
+                        'object_id' => $structuralElement->id,
+                        'object_type' => 'courseware',
+                        'mkdate' => time(),
+                    ];
+                    break;
+
+                case Block::class . 'DidUpdate':
+                    /**
+                     * @var \Courseware\Block $resource
+                     * @var \Courseware\StructuralElement $structuralElement
+                     */
+                    if (!$resource->edit_blocker_id) {
+                        $blockType = $resource->type;
+                        $blockTitle = $blockType->getTitle() ?? $resource->getBlockType();
+                        $content = _('Ein Block vom Typ "%1$s" wurde auf der Seite "%2$s" verändert.');
+                        $content = sprintf($content, $blockTitle, $structuralElement->title);
+                        $data = [
+                            'provider' => self::class,
+                            'context' => $structuralElement->range_type,
+                            'context_id' => $structuralElement->range_id,
+                            'content' => $content,
+                            'actor_type' => 'user',
+                            'actor_id' => $resource->editor_id,
+                            'verb' => 'edited',
+                            'object_id' => $structuralElement->id,
+                            'object_type' => 'courseware',
+                            'mkdate' => time(),
+                        ];
+                    }
+                    break;
+
+                case Block::class . 'DidDelete':
+                    /**
+                     * @var \Courseware\Block $resource
+                     * @var \Courseware\StructuralElement $structuralElement
+                     */
+                    $blockType = $resource->type;
+                    $blockTitle = $blockType->getTitle() ?: $resource->getBlockType();
+                    $content = _('Ein Block vom Typ "%1$s" wurde auf der Seite "%2$s" gelöscht.');
+                    $content = sprintf($content, $blockTitle, $structuralElement->title);
+                    $data = [
+                        'provider' => self::class,
+                        'context' => $structuralElement->range_type,
+                        'context_id' => $structuralElement->range_id,
+                        'content' => $content,
                         'actor_type' => 'user',
                         'actor_id' => $resource->editor_id,
-                        'verb' => 'edited',
+                        'verb' => 'voided',
+                        'object_id' => $structuralElement->id,
+                        'object_type' => 'courseware',
+                        'mkdate' => time(),
+                    ];
+                    break;
+
+                case BlockComment::class . 'DidCreate':
+                    /**
+                     * @var \Courseware\BlockComment $resource
+                     * @var \Courseware\StructuralElement $structuralElement
+                     */
+                    $structuralElement = $resource->getStructuralElement();
+                    $data = [
+                        'provider' => self::class,
+                        'context' => $structuralElement->range_type,
+                        'context_id' => $structuralElement->range_id,
+                        'content' => $resource->comment,
+                        'actor_type' => 'user',
+                        'actor_id' => $resource->user_id,
+                        'verb' => 'interacted',
+                        'object_id' => $structuralElement->id,
+                        'object_type' => 'courseware',
+                        'mkdate' => time(),
+                    ];
+                    break;
+
+                case BlockFeedback::class . 'DidCreate':
+                    /**
+                     * @var \Courseware\BlockFeedback $resource
+                     * @var \Courseware\StructuralElement $structuralElement
+                     */
+                    $data = [
+                        'provider' => self::class,
+                        'context' => $structuralElement->range_type,
+                        'context_id' => $structuralElement->range_id,
+                        'content' => $resource->feedback,
+                        'actor_type' => 'user',
+                        'actor_id' => $resource->user_id,
+                        'verb' => 'answered',
+                        'object_id' => $structuralElement->id,
+                        'object_type' => 'courseware',
+                        'mkdate' => time(),
+                    ];
+                    break;
+
+                case Container::class . 'DidCreate':
+                    /**
+                     * @var \Courseware\Container $resource
+                     * @var \Courseware\StructuralElement $structuralElement
+                     */
+                    $containerType = $resource->type;
+                    $containerTitle = $containerType->getTitle() ?: _('unbekannt');
+                    $content = _('Ein Abschnitt vom Typ "%1$s" wurde auf der Seite "%2$s" eingefügt.');
+                    $content = sprintf($content, $containerTitle, $structuralElement->title);
+                    $data = [
+                        'provider' => self::class,
+                        'context' => $structuralElement->range_type,
+                        'context_id' => $structuralElement->range_id,
+                        'content' => $content,
+                        'actor_type' => 'user',
+                        'actor_id' => $resource->owner_id,
+                        'verb' => 'created',
                         'object_id' => $structuralElement->id,
                         'object_type' => 'courseware',
                         'mkdate' => time(),
                     ];
-                }
-                break;
+                    break;
 
-            case BlockComment::class . 'DidCreate':
-                /**
-                 * @var \Courseware\BlockComment $resource
-                 * @var \Courseware\StructuralElement $structuralElement
-                 */
-                $structuralElement = $resource->getStructuralElement();
-                $data = [
-                    'provider' => self::class,
-                    'context' => $structuralElement->range_type,
-                    'context_id' => $structuralElement->range_id,
-                    'content' => $resource->comment,
-                    'actor_type' => 'user',
-                    'actor_id' => $resource->user_id,
-                    'verb' => 'interacted',
-                    'object_id' => $structuralElement->id,
-                    'object_type' => 'courseware',
-                    'mkdate' => time(),
-                ];
-                break;
+                case Container::class . 'DidUpdate':
+                    /**
+                     * @var \Courseware\Block $resource
+                     * @var \Courseware\StructuralElement $structuralElement
+                     */
+                    if (!$resource->edit_blocker_id) {
+                        $containerType = $resource->type;
+                        $containerTitle = $containerType->getTitle() ?: _('unbekannt');
+                        $content = _('Ein Abschnitt vom Typ "%1$s" wurde auf der Seite "%2$s" verändert.');
+                        $content = sprintf($content, $containerTitle, $structuralElement->title);
+                        $data = [
+                            'provider' => self::class,
+                            'context' => $structuralElement->range_type,
+                            'context_id' => $structuralElement->range_id,
+                            'content' => $content,
+                            'actor_type' => 'user',
+                            'actor_id' => $resource->editor_id,
+                            'verb' => 'edited',
+                            'object_id' => $structuralElement->id,
+                            'object_type' => 'courseware',
+                            'mkdate' => time(),
+                        ];
+                    }
+                    break;
 
-            case BlockFeedback::class . 'DidCreate':
-                /**
-                 * @var \Courseware\BlockFeedback $resource
-                 * @var \Courseware\StructuralElement $structuralElement
-                 */
-                $structuralElement = $resource->getStructuralElement();
-                $data = [
-                    'provider' => self::class,
-                    'context' => $structuralElement->range_type,
-                    'context_id' => $structuralElement->range_id,
-                    'content' => $resource->feedback,
-                    'actor_type' => 'user',
-                    'actor_id' => $resource->user_id,
-                    'verb' => 'answered',
-                    'object_id' => $structuralElement->id,
-                    'object_type' => 'courseware',
-                    'mkdate' => time(),
-                ];
-                break;
+                case Container::class . 'DidDelete':
+                    /**
+                     * @var \Courseware\Container $resource
+                     * @var \Courseware\StructuralElement $structuralElement
+                     */
+                    $containerType = $resource->type;
+                    $containerTitle = $containerType->getTitle() ?: _('unbekannt');
+                    $content = _('Ein Abschnitt vom Typ "%1$s" wurde auf der Seite "%2$s" gelöscht.');
+                    $content = sprintf($content, $containerTitle, $structuralElement->title);
+                    $data = [
+                        'provider' => self::class,
+                        'context' => $structuralElement->range_type,
+                        'context_id' => $structuralElement->range_id,
+                        'content' => $content,
+                        'actor_type' => 'user',
+                        'actor_id' => $resource->editor_id,
+                        'verb' => 'voided',
+                        'object_id' => $structuralElement->id,
+                        'object_type' => 'courseware',
+                        'mkdate' => time(),
+                    ];
+                    break;
 
-            case StructuralElement::class . 'DidCreate':
-                /**
-                 * @var \Courseware\StructuralElement $resource
-                 */
-                if ($resource->range_type === 'courses') {
+                case StructuralElement::class . 'DidCreate':
+                    /**
+                     * @var \Courseware\StructuralElement $resource
+                     */
+                    $content = _('Eine Seite mit dem Titel "%s" wurde angelegt.');
+                    $content = sprintf($content, $structuralElement->title);
                     $data = [
                         'provider' => self::class,
                         'context' => $resource->range_type,
                         'context_id' => $resource->range_id,
-                        'content' => null,
+                        'content' => $content,
                         'actor_type' => 'user',
                         'actor_id' => $resource->owner_id,
                         'verb' => 'created',
@@ -163,56 +284,70 @@ class CoursewareProvider implements ActivityProvider
                         'object_type' => 'courseware',
                         'mkdate' => time(),
                     ];
-                }
-                break;
+                    break;
+                case StructuralElement::class . 'DidDelete':
+                    /**
+                     * @var \Courseware\StructuralElement $resource
+                     */
+                    $content = _('Eine Seite mit dem Titel "%s" wurde gelöscht.');
+                    $content = sprintf($content, $structuralElement->title);
+                    $data = [
+                        'provider' => self::class,
+                        'context' => $resource->range_type,
+                        'context_id' => $resource->range_id,
+                        'content' => null,
+                        'actor_type' => 'user',
+                        'actor_id' => $resource->owner_id,
+                        'verb' => 'voided',
+                        'object_id' => $resource->id,
+                        'object_type' => 'courseware',
+                        'mkdate' => time(),
+                    ];
+                    break;
 
-            case StructuralElementComment::class . 'DidCreate':
-                /**
-                 * @var \Courseware\StructuralElementComment $resource
-                 * @var \Courseware\StructuralElement $structuralElement
-                 */
-                $structuralElement = $resource['structural_element'];
-                $data = [
-                    'provider' => self::class,
-                    'context' => $structuralElement->range_type,
-                    'context_id' => $structuralElement->range_id,
-                    'content' => $resource->comment,
-                    'actor_type' => 'user',
-                    'actor_id' => $resource->user_id,
-                    'verb' => 'interacted',
-                    'object_id' => $structuralElement->id,
-                    'object_type' => 'courseware',
-                    'mkdate' => time(),
-                ];
-                break;
+                case StructuralElementComment::class . 'DidCreate':
+                    /**
+                     * @var \Courseware\StructuralElementComment $resource
+                     * @var \Courseware\StructuralElement $structuralElement
+                     */
+                    $data = [
+                        'provider' => self::class,
+                        'context' => $structuralElement->range_type,
+                        'context_id' => $structuralElement->range_id,
+                        'content' => $resource->comment,
+                        'actor_type' => 'user',
+                        'actor_id' => $resource->user_id,
+                        'verb' => 'interacted',
+                        'object_id' => $structuralElement->id,
+                        'object_type' => 'courseware',
+                        'mkdate' => time(),
+                    ];
+                    break;
 
-            case StructuralElementFeedback::class . 'DidCreate':
-                /**
-                 * @var \Courseware\StructuralElementFeedback $resource
-                 * @var \Courseware\StructuralElement $structuralElement
-                 */
-                $structuralElement = $resource['structural_element'];
-                $data = [
-                    'provider' => self::class,
-                    'context' => $structuralElement->range_type,
-                    'context_id' => $structuralElement->range_id,
-                    'content' => $resource->feedback,
-                    'actor_type' => 'user',
-                    'actor_id' => $resource->user_id,
-                    'verb' => 'answered',
-                    'object_id' => $structuralElement->id,
-                    'object_type' => 'courseware',
-                    'mkdate' => time(),
-                ];
-                break;
+                case StructuralElementFeedback::class . 'DidCreate':
+                    /**
+                     * @var \Courseware\StructuralElementFeedback $resource
+                     * @var \Courseware\StructuralElement $structuralElement
+                     */
+                    $data = [
+                        'provider' => self::class,
+                        'context' => $structuralElement->range_type,
+                        'context_id' => $structuralElement->range_id,
+                        'content' => $resource->feedback,
+                        'actor_type' => 'user',
+                        'actor_id' => $resource->user_id,
+                        'verb' => 'answered',
+                        'object_id' => $structuralElement->id,
+                        'object_type' => 'courseware',
+                        'mkdate' => time(),
+                    ];
+                    break;
 
-            case Task::class . 'DidCreate':
-                /**
-                 * @var \Courseware\Task $resource
-                 * @var \Courseware\StructuralElement $structuralElement
-                 */
-                $structuralElement = $resource['structural_element'];
-                if ($structuralElement->range_type === 'courses') {
+                case Task::class . 'DidCreate':
+                    /**
+                     * @var \Courseware\Task $resource
+                     * @var \Courseware\StructuralElement $structuralElement
+                     */
                     $data = [
                         'provider' => self::class,
                         'context' => $structuralElement->range_type,
@@ -220,21 +355,18 @@ class CoursewareProvider implements ActivityProvider
                         'content' => null,
                         'actor_type' => 'user',
                         'actor_id' => $resource->task_group->lecturer_id,
-                        'verb' => 'set',
+                        'verb' => 'created',
                         'object_id' => $structuralElement->id,
                         'object_type' => 'courseware',
                         'mkdate' => time(),
                     ];
-                }
-                break;
+                    break;
 
-            case TaskFeedback::class . 'DidCreate':
-                /**
-                 * @var \Courseware\TaskFeedback $resource
-                 * @var \Courseware\StructuralElement $structuralElement
-                 */
-                $structuralElement = $resource->getStructuralElement();
-                if ($structuralElement->range_type === 'courses') {
+                case TaskFeedback::class . 'DidCreate':
+                    /**
+                     * @var \Courseware\TaskFeedback $resource
+                     * @var \Courseware\StructuralElement $structuralElement
+                     */
                     $data = [
                         'provider' => self::class,
                         'context' => $structuralElement->range_type,
@@ -247,12 +379,11 @@ class CoursewareProvider implements ActivityProvider
                         'object_type' => 'courseware',
                         'mkdate' => time(),
                     ];
-                }
-                break;
-        }
-
-        if ($data) {
-            Activity::create($data);
+                    break;
+            }
+            if ($data) {
+                Activity::create($data);
+            }
         }
     }
 }
diff --git a/lib/classes/JsonApi/RouteMap.php b/lib/classes/JsonApi/RouteMap.php
index 312a90ab4ef..0709ef3a75d 100644
--- a/lib/classes/JsonApi/RouteMap.php
+++ b/lib/classes/JsonApi/RouteMap.php
@@ -430,6 +430,8 @@ class RouteMap
 
         $group->get('/courseware-blocks/{id}/user-progress', Routes\Courseware\UserProgressOfBlocksShow::class);
         $group->get('/courseware-user-progresses/{id}', Routes\Courseware\UserProgressesShow::class);
+        // not a JSON route
+        $group->get('/courseware-units/{id}/courseware-user-progresses', Routes\Courseware\UserProgressesOfUnitsShow::class);
         $group->patch('/courseware-user-progresses/{id}', Routes\Courseware\UserProgressesUpdate::class);
 
         $group->get('/courseware-blocks/{id}/comments', Routes\Courseware\BlockCommentsOfBlocksIndex::class);
@@ -468,6 +470,15 @@ class RouteMap
         $group->post('/courseware-public-links', Routes\Courseware\PublicLinksCreate::class);
         $group->patch('/courseware-public-links/{id}', Routes\Courseware\PublicLinksUpdate::class);
         $group->delete('/courseware-public-links/{id}', Routes\Courseware\PublicLinksDelete::class);
+
+        $group->get('/courses/{id}/courseware-units', Routes\Courseware\CoursesUnitsIndex::class);
+        $group->get('/users/{id}/courseware-units', Routes\Courseware\UsersUnitsIndex::class);
+        $group->get('/courseware-units/{id}', Routes\Courseware\UnitsShow::class);
+        $group->post('/courseware-units', Routes\Courseware\UnitsCreate::class);
+        $group->patch('/courseware-units/{id}', Routes\Courseware\UnitsUpdate::class);
+        $group->delete('/courseware-units/{id}', Routes\Courseware\UnitsDelete::class);
+        // not a JSON route
+        $group->post('/courseware-units/{id}/copy', Routes\Courseware\UnitsCopy::class);
     }
 
     private function addAuthenticatedFilesRoutes(RouteCollectorProxy $group): void
@@ -550,3 +561,4 @@ class RouteMap
         $group->map(['GET', 'PATCH', 'POST', 'DELETE'], $url, $handler);
     }
 }
+
diff --git a/lib/classes/JsonApi/Routes/Courseware/Authority.php b/lib/classes/JsonApi/Routes/Courseware/Authority.php
index 0331be71a50..1c7c9034192 100644
--- a/lib/classes/JsonApi/Routes/Courseware/Authority.php
+++ b/lib/classes/JsonApi/Routes/Courseware/Authority.php
@@ -14,10 +14,12 @@ use Courseware\Task;
 use Courseware\TaskFeedback;
 use Courseware\TaskGroup;
 use Courseware\Template;
+use Courseware\Unit;
 use Courseware\UserDataField;
 use Courseware\UserProgress;
 use Courseware\PublicLink;
 use User;
+use Course;
 
 /**
  * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
@@ -69,11 +71,7 @@ class Authority
                 return $structural_element->canEdit($user);
             }
 
-            $perm = $GLOBALS['perm']->have_studip_perm(
-                $structural_element->course->config->COURSEWARE_EDITING_PERMISSION,
-                $structural_element->course->id,
-                $user->id
-            );
+            $perm = $structural_element->hasEditingPermission($user);
 
             return $resource->getBlockerUserId() === $user->id || $perm;
         }
@@ -111,11 +109,7 @@ class Authority
             return $structural_element->canEdit($user);
         }
 
-        $perm = $GLOBALS['perm']->have_studip_perm(
-            $structural_element->course->config->COURSEWARE_EDITING_PERMISSION,
-            $structural_element->course->id,
-            $user->id
-        );
+        $perm = $structural_element->hasEditingPermission($user);
 
         return $resource->edit_blocker_id == '' || $resource->edit_blocker_id === $user->id || $perm;
     }
@@ -262,15 +256,7 @@ class Authority
 
     public static function canUpdateBlockComment(User $user, BlockComment $resource)
     {
-        if ($resource->block->container->structural_element->range_type === 'user') {
-            return $resource->block->container->structural_element->range_id === $user->id;
-        }
-
-        $perm = $GLOBALS['perm']->have_studip_perm(
-            $resource->block->container->structural_element->course->config->COURSEWARE_EDITING_PERMISSION,
-            $resource->block->container->structural_element->course->id,
-            $user->id
-        );
+        $perm = $resource->block->container->structural_element->hasEditingPermission($user);
         return $user->id === $resource->user_id || $perm;
     }
 
@@ -387,15 +373,7 @@ class Authority
             return true;
         }
 
-        if ($resource->structural_element->range_type === 'user') {
-            return $resource->structural_element->range_id === $user->id;
-        }
-
-        $perm = $GLOBALS['perm']->have_studip_perm(
-            $resource->structural_element->course->config->COURSEWARE_EDITING_PERMISSION,
-            $resource->structural_element->course->id,
-            $user->id
-        );
+        $perm = $resource->structural_element->hasEditingPermission($user);
 
         return $user->id == $resource->user_id || $perm;
     }
@@ -416,15 +394,7 @@ class Authority
             return true;
         }
 
-        if ($resource->range_type === 'user') {
-            return $resource->range_id === $user->id;
-        }
-
-        $perm = $GLOBALS['perm']->have_studip_perm(
-            $resource->course->config->COURSEWARE_EDITING_PERMISSION,
-            $resource->course->id,
-            $user->id
-        );
+        $perm = $resource->hasEditingPermission($user);
 
         return $perm;
     }
@@ -504,4 +474,39 @@ class Authority
         return (bool) $publicLink;
     }
 
+    public static function canShowUnit(User $user, Unit $resource): bool
+    {
+        return $resource->canRead($user);
+    }
+
+    public static function canIndexUnits(User $user): bool
+    {
+        return $GLOBALS['perm']->have_perm('root', $user->id);
+    }
+
+    public static function canCreateUnit(User $user): bool
+    {
+        return $GLOBALS['perm']->have_perm('tutor', $user->id);
+    }
+
+    public static function canUpdateUnit(User $user, Unit $resource): bool
+    {
+        return $resource->canEdit($user);
+    }
+
+    public static function canDeleteUnit(User $user, Unit $resource): bool
+    {
+        return self::canUpdateUnit($user, $resource);
+    }
+
+    public static function canIndexUnitsOfACourse(User $user, Course $course): bool
+    {
+        return $GLOBALS['perm']->have_studip_perm('user', $course->id, $user->id);
+    }
+
+    public static function canIndexUnitsOfAUser(User $request_user, User $user): bool
+    {
+        return $request_user->id === $user->id;
+    }
+
 }
diff --git a/lib/classes/JsonApi/Routes/Courseware/CoursesUnitsIndex.php b/lib/classes/JsonApi/Routes/Courseware/CoursesUnitsIndex.php
new file mode 100644
index 00000000000..dde67bcb18e
--- /dev/null
+++ b/lib/classes/JsonApi/Routes/Courseware/CoursesUnitsIndex.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace JsonApi\Routes\Courseware;
+
+use Courseware\Unit;
+use JsonApi\Errors\AuthorizationFailedException;
+use JsonApi\Errors\RecordNotFoundException;
+use JsonApi\JsonApiController;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+
+/**
+ * Displays the course's courseware units.
+ */
+class CoursesUnitsIndex extends JsonApiController
+{
+    use CoursewareInstancesHelper;
+
+    protected $allowedIncludePaths = [
+        'structural-element',
+        'creator',
+    ];
+
+    protected $allowedPagingParameters = ['offset', 'limit'];
+
+    /**
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function __invoke(Request $request, Response $response, $args)
+    {
+        $course = \Course::find($args['id']);
+        if (!$course) {
+            throw new RecordNotFoundException();
+        }
+        $user = $this->getUser($request);
+        if (!Authority::canIndexUnitsOfACourse($user, $course)) {
+            throw new AuthorizationFailedException();
+        }
+
+        $resources = Unit::findCoursesUnits($course);
+        $total = count($resources);
+        [$offset, $limit] = $this->getOffsetAndLimit();
+
+        return $this->getPaginatedContentResponse(array_slice($resources, $offset, $limit), $total);
+    }
+}
diff --git a/lib/classes/JsonApi/Routes/Courseware/CoursewareInstancesHelper.php b/lib/classes/JsonApi/Routes/Courseware/CoursewareInstancesHelper.php
index 46a2e689d67..a120f63e624 100644
--- a/lib/classes/JsonApi/Routes/Courseware/CoursewareInstancesHelper.php
+++ b/lib/classes/JsonApi/Routes/Courseware/CoursewareInstancesHelper.php
@@ -4,6 +4,7 @@ namespace JsonApi\Routes\Courseware;
 
 use Courseware\Instance;
 use Courseware\StructuralElement;
+use Courseware\Unit;
 use JsonApi\Errors\BadRequestException;
 use JsonApi\Errors\RecordNotFoundException;
 
@@ -31,7 +32,22 @@ trait CoursewareInstancesHelper
         if (!($method = $methods[$rangeType])) {
             throw new BadRequestException('Invalid range type: "' . $rangeType . '".');
         }
-        if (!($root = StructuralElement::$method($rangeId))) {
+        $root = null;
+        if ($rangeType !== 'sharedusers') {
+            $chunks = explode('_', $rangeId);
+            $courseId = $chunks[0];
+            $unitId = $chunks[1] ?? null;
+
+            if ($unitId) {
+                $unit = Unit::findOneBySQL('range_id = ? AND id = ?', [$courseId, $unitId]);
+            } else {
+                $unit = Unit::findOneBySQL('range_id = ?', [$courseId]);
+            }
+            $root = $unit->structural_element;
+        } else {
+            $root = StructuralElement::$method($rangeId);
+        }
+        if (!$root) {
             throw new RecordNotFoundException();
         }
 
diff --git a/lib/classes/JsonApi/Routes/Courseware/CoursewareInstancesUpdate.php b/lib/classes/JsonApi/Routes/Courseware/CoursewareInstancesUpdate.php
index 2b70cbf90b6..e48588413cf 100644
--- a/lib/classes/JsonApi/Routes/Courseware/CoursewareInstancesUpdate.php
+++ b/lib/classes/JsonApi/Routes/Courseware/CoursewareInstancesUpdate.php
@@ -22,7 +22,12 @@ class CoursewareInstancesUpdate extends JsonApiController
      */
     public function __invoke(Request $request, Response $response, $args)
     {
-        $resource = $this->findInstance($args['id']);
+        $chunks = explode('_', $args['id']);
+        $rangeType = $chunks[0];
+        $rangeId = $chunks[1];
+        $unitId = $chunks[2] ?? null;
+
+        $resource = $this->findInstanceWithRange($rangeType, $rangeId . '_' . $unitId);
         $json = $this->validate($request, $resource);
         if (!Authority::canUpdateCoursewareInstance($user = $this->getUser($request), $resource)) {
             throw new AuthorizationFailedException();
diff --git a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsCopy.php b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsCopy.php
index 6e14b72d181..234d8f0c37b 100644
--- a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsCopy.php
+++ b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsCopy.php
@@ -41,6 +41,13 @@ class StructuralElementsCopy extends NonJsonApiController
         if ($data['remove_purpose']) {
             $newElement->purpose = '';
         }
+        if (!empty($data['modifications'])) {
+            $newElement->title = $data['modifications']['title'] ?? $newElement->title;
+            $newElement->payload['color'] = $data['modifications']['color'] ?? 'studip-blue';
+            $newElement->payload['description'] = $data['modifications']['description'];
+        }
+
+        $newElement->store();
 
         return $this->redirectToStructuralElement($response, $newElement);
     }
diff --git a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsShow.php b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsShow.php
index 4aa07166220..40a96b3e421 100644
--- a/lib/classes/JsonApi/Routes/Courseware/StructuralElementsShow.php
+++ b/lib/classes/JsonApi/Routes/Courseware/StructuralElementsShow.php
@@ -31,7 +31,8 @@ class StructuralElementsShow extends JsonApiController
         'edit-blocker',
         'owner',
         'parent',
-        'target'
+        'target',
+        'unit',
     ];
 
     /**
diff --git a/lib/classes/JsonApi/Routes/Courseware/UnitsCopy.php b/lib/classes/JsonApi/Routes/Courseware/UnitsCopy.php
new file mode 100644
index 00000000000..53654598d52
--- /dev/null
+++ b/lib/classes/JsonApi/Routes/Courseware/UnitsCopy.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace JsonApi\Routes\Courseware;
+
+use JsonApi\NonJsonApiController;
+use Courseware\Unit;
+use JsonApi\Errors\AuthorizationFailedException;
+use JsonApi\Errors\BadRequestException;
+use JsonApi\Errors\RecordNotFoundException;
+use JsonApi\Errors\UnprocessableEntityException;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+
+/**
+* Copy an courseware unit into a course or users contents
+*
+* @author  Ron Lucke <lucke@elan-ev.de>
+* @license GPL2 or any later version
+*
+* @since   Stud.IP 5.3
+*/
+
+class UnitsCopy extends NonJsonApiController
+{
+    public function __invoke(Request $request, Response $response, array $args)
+    {
+        $data = $request->getParsedBody()['data'];
+
+        $sourceUnit = Unit::find($args['id']);
+        $user = $this->getUser($request);
+        $rangeId = $data['rangeId'];
+        $rangeType = $data['rangeType'];
+        $modified = $data['modified'];
+
+        if (!Authority::canCreateUnit($user)) {
+            throw new AuthorizationFailedException();
+        }
+
+        $newUnit = $sourceUnit->copy($user, $rangeId, $rangeType, $modified);
+
+        $response = $response->withHeader('Content-Type', 'application/json');
+        $response->getBody()->write((string) json_encode($newUnit));
+
+        return $response;
+    }
+}
\ No newline at end of file
diff --git a/lib/classes/JsonApi/Routes/Courseware/UnitsCreate.php b/lib/classes/JsonApi/Routes/Courseware/UnitsCreate.php
new file mode 100644
index 00000000000..8098bca93f8
--- /dev/null
+++ b/lib/classes/JsonApi/Routes/Courseware/UnitsCreate.php
@@ -0,0 +1,122 @@
+<?php
+
+namespace JsonApi\Routes\Courseware;
+
+use JsonApi\Errors\AuthorizationFailedException;
+use JsonApi\JsonApiController;
+use JsonApi\Routes\ValidationTrait;
+use JsonApi\Schemas\Courseware\Unit as UnitSchema;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+use Studip\Activity\Activity;
+
+/**
+ * Create a block in a container.
+ */
+class UnitsCreate extends JsonApiController
+{
+    use ValidationTrait;
+
+    /**
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function __invoke(Request $request, Response $response, $args)
+    {
+        $json = $this->validate($request);
+        $user = $this->getUser($request);
+        if (!Authority::canCreateUnit($user)) {
+            throw new AuthorizationFailedException();
+        }
+        $struct = $this->createUnit($user, $json);
+
+        return $this->getCreatedResponse($struct);
+    }
+
+    /**
+     * @SuppressWarnings(PHPMD.UnusedFormalParameters)
+     */
+    protected function validateResourceDocument($json, $data)
+    {
+        if (!self::arrayHas($json, 'data')) {
+            return 'Missing `data` member at document´s top level.';
+        }
+        if (UnitSchema::TYPE !== self::arrayGet($json, 'data.type')) {
+            return 'Wrong `type` member of document´s `data`.';
+        }
+        if (!self::arrayHas($json, 'data.attributes.title')) {
+            return 'Missing `title` value.';
+        }
+        if (!self::arrayHas($json, 'data.attributes.payload.description')) {
+            return 'Missing `description` value.';
+        }
+        if (!self::arrayHas($json, 'data.relationships.range')) {
+            return 'Missing `range` relationship.';
+        }
+        if (!$this->validateRange($json)) {
+            return 'Invalid `range` relationship.';
+        }
+    }
+
+    private function validateRange($json): bool
+    {
+        $rangeData = self::arrayGet($json, 'data.relationships.range.data');
+
+        if (!in_array($rangeData['type'], ['courses','users'])) {
+            return false;
+        }
+        if ($rangeData['type'] ===  'courses') {
+            $range = \Course::find($rangeData['id']);
+        } else {
+            $range = \User::find($rangeData['id']);
+        }
+
+        return isset($range);
+    }
+
+    private function createUnit(\User $user, array $json)
+    {
+        $range_id = self::arrayGet($json, 'data.relationships.range.data.id');
+        $range_type = self::getRangeType(self::arrayGet($json, 'data.relationships.range.data.type'));
+
+        $struct = \Courseware\StructuralElement::build([
+            'parent_id' => null,
+            'range_id' => $range_id,
+            'range_type' => $range_type,
+            'owner_id' => $user->id,
+            'editor_id' => $user->id,
+            'edit_blocker_id' => '',
+            'title' => self::arrayGet($json, 'data.attributes.title', ''),
+            'purpose' => self::arrayGet($json, 'data.attributes.purpose', ''),
+            'payload' => self::arrayGet($json, 'data.attributes.payload', ''),
+            'position' => 0
+        ]);
+
+        $struct->store();
+
+        $unit = \Courseware\Unit::build([
+            'range_id' => $range_id,
+            'range_type' => $range_type,
+            'structural_element_id' => $struct->id,
+            'content_type' => 'courseware',
+            'creator_id' => $user->id,
+            'public' => self::arrayGet($json, 'data.attributes.public', ''),
+            'release_date' => self::arrayGet($json, 'data.attributes.release-date', ''),
+            'withdraw_date' => self::arrayGet($json, 'data.attributes.withdraw-date', ''),
+        ]);
+        
+        $unit->store();
+
+        return $unit;
+    }
+
+    private function getRangeType($type): ?string
+    {
+        $type_map = [
+            'courses' => 'course',
+            'users'   => 'user',
+        ];
+
+        return $type_map[$type] ?? null;
+    }
+}
+
diff --git a/lib/classes/JsonApi/Routes/Courseware/UnitsDelete.php b/lib/classes/JsonApi/Routes/Courseware/UnitsDelete.php
new file mode 100644
index 00000000000..6c9f7088d49
--- /dev/null
+++ b/lib/classes/JsonApi/Routes/Courseware/UnitsDelete.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace JsonApi\Routes\Courseware;
+
+use Courseware\Unit;
+use JsonApi\Errors\AuthorizationFailedException;
+use JsonApi\Errors\RecordNotFoundException;
+use JsonApi\JsonApiController;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+
+/**
+ * Delete one StructuralElement.
+ */
+class UnitsDelete extends JsonApiController
+{
+    /**
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function __invoke(Request $request, Response $response, $args)
+    {
+        $resource = Unit::find($args['id']);
+        if (!$resource) {
+            throw new RecordNotFoundException();
+        }
+        $user = $this->getUser($request);
+        if (!Authority::canDeleteUnit($user, $resource)) {
+            throw new AuthorizationFailedException();
+        }
+        $resource->delete();
+
+        return $this->getCodeResponse(204);
+    }
+}
diff --git a/lib/classes/JsonApi/Routes/Courseware/UnitsIndex.php b/lib/classes/JsonApi/Routes/Courseware/UnitsIndex.php
new file mode 100644
index 00000000000..f9c9376aa53
--- /dev/null
+++ b/lib/classes/JsonApi/Routes/Courseware/UnitsIndex.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace JsonApi\Routes\Courseware;
+
+use Courseware\Unit;
+use JsonApi\Errors\AuthorizationFailedException;
+use JsonApi\JsonApiController;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+
+/**
+ * Displays all PublicLinks
+ */
+class UnitsIndex extends JsonApiController
+{
+    protected $allowedPagingParameters = ['offset', 'limit'];
+
+    protected $allowedIncludePaths = ['creator', 'structural-element'];
+
+    /**
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function __invoke(Request $request, Response $response, $args)
+    {
+        $user = $this->getUser($request);
+        if (!Authority::canIndexUnits($user)) {
+            throw new AuthorizationFailedException();
+        }
+
+        list($offset, $limit) = $this->getOffsetAndLimit();
+        $resources = Unit::findBySQL('1 ORDER BY mkdate LIMIT ? OFFSET ?', [$limit, $offset]);
+
+        return $this->getPaginatedContentResponse($resources, count($resources));
+    }
+}
\ No newline at end of file
diff --git a/lib/classes/JsonApi/Routes/Courseware/UnitsShow.php b/lib/classes/JsonApi/Routes/Courseware/UnitsShow.php
new file mode 100644
index 00000000000..37dbe3bee02
--- /dev/null
+++ b/lib/classes/JsonApi/Routes/Courseware/UnitsShow.php
@@ -0,0 +1,42 @@
+<?php
+
+namespace JsonApi\Routes\Courseware;
+
+use Courseware\Unit;
+use JsonApi\Errors\AuthorizationFailedException;
+use JsonApi\Errors\RecordNotFoundException;
+use JsonApi\Schemas\Courseware\Unit as UnitSchema;
+use JsonApi\JsonApiController;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+
+/**
+ * Displays one Task.
+ */
+class UnitsShow extends JsonApiController
+{
+    protected $allowedIncludePaths = [
+        UnitSchema::REL_CREATOR,
+        UnitSchema::REL_STRUCTURAL_ELEMENT
+    ];
+
+    /**
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     * @param array $args
+     * @return Response
+     */
+    public function __invoke(Request $request, Response $response, $args)
+    {
+        /** @var ?\Courseware\Unit $resource */
+        $resource = Unit::find($args['id']);
+        if (!$resource) {
+            throw new RecordNotFoundException();
+        }
+
+        if (!Authority::canShowUnit($this->getUser($request), $resource)) {
+            throw new AuthorizationFailedException();
+        }
+
+        return $this->getContentResponse($resource);
+    }
+}
diff --git a/lib/classes/JsonApi/Routes/Courseware/UnitsUpdate.php b/lib/classes/JsonApi/Routes/Courseware/UnitsUpdate.php
new file mode 100644
index 00000000000..53fccd3358f
--- /dev/null
+++ b/lib/classes/JsonApi/Routes/Courseware/UnitsUpdate.php
@@ -0,0 +1,95 @@
+<?php
+
+namespace JsonApi\Routes\Courseware;
+
+use Courseware\Unit;
+use JsonApi\Errors\AuthorizationFailedException;
+use JsonApi\Errors\RecordNotFoundException;
+use JsonApi\JsonApiController;
+use JsonApi\Routes\ValidationTrait;
+use JsonApi\Schemas\Courseware\Unit as UnitSchema;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+
+/**
+ * Update one Block.
+ */
+class UnitsUpdate extends JsonApiController
+{
+    use EditBlockAwareTrait;
+    use ValidationTrait;
+
+    /**
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function __invoke(Request $request, Response $response, $args)
+    {
+        $resource = Unit::find($args['id']);
+        if (!$resource) {
+            throw new RecordNotFoundException();
+        }
+        $json = $this->validate($request, $resource);
+        $user = $this->getUser($request);
+        if (!Authority::canUpdateUnit($user, $resource)) {
+            throw new AuthorizationFailedException();
+        }
+        $resource = $this->updateUnit($user, $resource, $json);
+
+        return $this->getContentResponse($resource);
+    }
+
+    /**
+     * @SuppressWarnings(PHPMD.UnusedFormalParameters)
+     */
+    protected function validateResourceDocument($json, $resource)
+    {
+        if (!self::arrayHas($json, 'data')) {
+            return 'Missing `data` member at document´s top level.';
+        }
+
+        if (UnitSchema::TYPE !== self::arrayGet($json, 'data.type')) {
+            return 'Wrong `type` member of document´s `data`.';
+        }
+
+        if (!self::arrayHas($json, 'data.id')) {
+            return 'Document must have an `id`.';
+        }
+
+        if (self::arrayHas($json, 'data.attributes.release-date')) {
+            $releaseDate = self::arrayGet($json, 'data.attributes.release-date');
+            if (!self::isValidTimestamp($releaseDate)) {
+                return '`release-date` is not an ISO 8601 timestamp.';
+            }
+        }
+
+        if (self::arrayHas($json, 'data.attributes.withdraw-date')) {
+            $withdrawDate = self::arrayGet($json, 'data.attributes.withdraw-date');
+            if (!self::isValidTimestamp($withdrawDate)) {
+                return '`withdraw-date` is not an ISO 8601 timestamp.';
+            }
+        }
+    }
+
+    private function updateUnit(\User $user, Unit $resource, array $json): Unit
+    {
+        if (self::arrayHas($json, 'data.attributes.public')) {
+            $resource->public = self::arrayGet($json, 'data.attributes.public');
+        }
+
+        if (self::arrayHas($json, 'data.attributes.release-date')) {
+            $releaseDate = self::arrayGet($json, 'data.attributes.release-date', '');
+            $releaseDate = self::fromISO8601($releaseDate);
+            $resource->release_date = $releaseDate->getTimestamp();
+        }
+
+        if (self::arrayHas($json, 'data.attributes.withdraw-date')) {
+            $withdrawDate = self::arrayGet($json, 'data.attributes.withdraw-date', '');
+            $withdrawDate = self::fromISO8601($withdrawDate);
+            $resource->withdraw_date = $withdrawDate->getTimestamp();
+        }
+
+        $resource->store();
+
+        return $resource;
+    }
+}
diff --git a/lib/classes/JsonApi/Routes/Courseware/UserProgressesOfUnitsShow.php b/lib/classes/JsonApi/Routes/Courseware/UserProgressesOfUnitsShow.php
new file mode 100644
index 00000000000..48e7c95e470
--- /dev/null
+++ b/lib/classes/JsonApi/Routes/Courseware/UserProgressesOfUnitsShow.php
@@ -0,0 +1,191 @@
+<?php
+
+namespace JsonApi\Routes\Courseware;
+
+use JsonApi\NonJsonApiController;
+use Courseware\Instance;
+use Courseware\StructuralElement;
+use Courseware\Unit;
+use Courseware\UserProgress;
+use JsonApi\Errors\AuthorizationFailedException;
+use JsonApi\Errors\BadRequestException;
+use JsonApi\Errors\RecordNotFoundException;
+use JsonApi\Errors\UnprocessableEntityException;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+
+/**
+ * Displays the progress of a user for a unit
+ *
+ * @author  Ron Lucke <lucke@elan-ev.de>
+ * @license GPL2 or any later version
+ *
+ * @since   Stud.IP 5.3
+ */
+
+class UserProgressesOfUnitsShow extends NonJsonApiController
+{
+    public function __invoke(Request $request, Response $response, array $args)
+    {
+        $user = $this->getUser($request);
+        $unit = Unit::find($args['id']);
+        if (!$unit) {
+            throw new RecordNotFoundException();
+        }
+        $root = $unit->structural_element;
+        if (!$GLOBALS['perm']->have_studip_perm('autor', $root->range_id) || !$unit->canRead($user)) {
+            throw new AuthorizationFailedException();
+        }
+        $instance = new Instance($root);
+        $isTeacher = $GLOBALS['perm']->have_studip_perm('tutor', $root->range_id);
+
+        $elements = $this->findElements($instance, $user);
+
+        $progress = $this->computeSelfProgresses($instance, $user, $elements, $isTeacher);
+        $progress = $this->computeCumulativeProgresses($instance, $elements, $progress);
+
+        $progresses = $this->prepareProgressData($elements, $progress);
+
+        $response = $response->withHeader('Content-Type', 'application/json');
+        $response->getBody()->write((string) json_encode($progresses));
+
+        return $response;
+    }
+
+    private function findElements(Instance $instance, \User $user): iterable
+    {
+        $elements = $instance->getRoot()->findDescendants($user);
+        $elements[] = $instance->getRoot();
+
+        return array_combine(array_column($elements, 'id'), $elements);
+    }
+
+    private function computeSelfProgresses(
+        Instance $instance,
+        \User $user,
+        iterable &$elements,
+        bool $showProgressForAllParticipants
+    ): iterable
+    {
+        $progress = [];
+        /** @var \Course $course */
+        $course = $instance->getRange();
+        $allBlockIds = $instance->findAllBlocksGroupedByStructuralElementId(function ($row) {
+            return $row['id'];
+        });
+        $courseMemberIds = $showProgressForAllParticipants
+            ? array_column($course->getMembersWithStatus('autor'), 'user_id')
+            : [$user->getId()];
+
+        $sql = "SELECT block_id, COUNT(grade) AS count, SUM(grade) AS grade
+                FROM cw_user_progresses
+                WHERE block_id IN (?) AND user_id IN (?)
+                GROUP BY block_id";
+
+        $userProgresses = \DBManager::get()->fetchGrouped($sql, [$allBlockIds, $courseMemberIds]);
+
+        foreach ($elements as $elementId => $element) {
+            $selfProgress = $this->getSelfProgresses($allBlockIds, $elementId, $userProgresses, $courseMemberIds);
+            $progress[$elementId] = [
+                'self' => $selfProgress['counter'] ? $selfProgress['progress'] / $selfProgress['counter'] : 1,
+            ];
+        }
+
+        return $progress;
+    }
+
+    private function getSelfProgresses(
+        iterable &$allBlockIds,
+        string $elementId,
+        array &$userProgresses,
+        array &$courseMemberIds
+    ): array {
+        $blks = $allBlockIds[$elementId] ?? [];
+        if (count($blks) === 0) {
+            return [
+                'counter' => 0,
+                'progress' => 1,
+            ];
+        }
+
+        $data = [
+            'counter' => count($blks),
+            'progress' => 0,
+        ];
+
+        $usersCounter = count($courseMemberIds);
+        foreach ($blks as $blk) {
+            $progresses = $userProgresses[$blk];
+            $usersProgress = $progresses['count'] ? (float) $progresses['sum'] : 0;
+            $data['progress'] += $usersProgress / $usersCounter;
+        }
+
+        return $data;
+    }
+
+    private function computeCumulativeProgresses(Instance $instance, iterable &$elements, iterable &$progress): iterable
+    {
+        $childrenOf = $this->computeChildrenOf($elements);
+
+        // compute `cumulative` of each element
+        $visitor = function (&$progress, $element) use (&$childrenOf, &$elements, &$visitor) {
+            $elementId = $element->getId();
+            $numberOfNodes = 0;
+            $cumulative = 0;
+
+            // visit children first
+            if (isset($childrenOf[$elementId])) {
+                foreach ($childrenOf[$elementId] as $childId) {
+                    $visitor($progress, $elements[$childId]);
+                    $numberOfNodes += $progress[$childId]['numberOfNodes'];
+                    $cumulative += $progress[$childId]['cumulative'];
+                }
+            }
+
+            $progress[$elementId]['cumulative'] = $cumulative + $progress[$elementId]['self'];
+            $progress[$elementId]['numberOfNodes'] = $numberOfNodes + 1;
+
+            return $progress;
+        };
+
+        $visitor($progress, $instance->getRoot());
+
+        return $progress;
+    }
+
+    private function computeChildrenOf(iterable &$elements): iterable
+    {
+        $childrenOf = [];
+        foreach ($elements as $elementId => $element) {
+            if ($element['parent_id']) {
+                if (!isset($childrenOf[$element['parent_id']])) {
+                    $childrenOf[$element['parent_id']] = [];
+                }
+                $childrenOf[$element['parent_id']][] = $elementId;
+            }
+        }
+
+        return $childrenOf;
+    }
+
+    private function prepareProgressData(iterable &$elements, iterable &$progress): iterable
+    {
+        $data = [];
+        foreach ($elements as $elementId => $element) {
+            $elementProgress = $progress[$elementId];
+            $cumulative = $elementProgress['cumulative'] / $elementProgress['numberOfNodes'];
+
+            $data[$elementId] = [
+                'id' => (int) $elementId,
+                'parent_id' => (int) $element['parent_id'],
+                'name' => $element['title'],
+                'progress' => [
+                    'cumulative' => round($cumulative, 2) * 100,
+                    'self' => round($elementProgress['self'], 2) * 100,
+                ],
+            ];
+        }
+
+        return $data;
+    }
+}
\ No newline at end of file
diff --git a/lib/classes/JsonApi/Routes/Courseware/UsersUnitsIndex.php b/lib/classes/JsonApi/Routes/Courseware/UsersUnitsIndex.php
new file mode 100644
index 00000000000..74bcd322db9
--- /dev/null
+++ b/lib/classes/JsonApi/Routes/Courseware/UsersUnitsIndex.php
@@ -0,0 +1,46 @@
+<?php
+
+namespace JsonApi\Routes\Courseware;
+
+use Courseware\Unit;
+use JsonApi\Errors\AuthorizationFailedException;
+use JsonApi\Errors\RecordNotFoundException;
+use JsonApi\JsonApiController;
+use Psr\Http\Message\ResponseInterface as Response;
+use Psr\Http\Message\ServerRequestInterface as Request;
+
+/**
+ * Displays the course's courseware units.
+ */
+class UsersUnitsIndex extends JsonApiController
+{
+    use CoursewareInstancesHelper;
+
+    protected $allowedIncludePaths = [
+        'structural-element',
+        'creator',
+    ];
+
+    protected $allowedPagingParameters = ['offset', 'limit'];
+
+    /**
+     * @SuppressWarnings(PHPMD.UnusedFormalParameter)
+     */
+    public function __invoke(Request $request, Response $response, $args)
+    {
+        $user = \User::find($args['id']);
+        if (!$user) {
+            throw new RecordNotFoundException();
+        }
+        $request_user = $this->getUser($request);
+        if (!Authority::canIndexUnitsOfAUser($request_user, $user)) {
+            throw new AuthorizationFailedException();
+        }
+
+        $resources = Unit::findUsersUnits($user);
+        $total = count($resources);
+        [$offset, $limit] = $this->getOffsetAndLimit();
+
+        return $this->getPaginatedContentResponse(array_slice($resources, $offset, $limit), $total);
+    }
+}
diff --git a/lib/classes/JsonApi/SchemaMap.php b/lib/classes/JsonApi/SchemaMap.php
index e7168cd9989..44b12d9639b 100644
--- a/lib/classes/JsonApi/SchemaMap.php
+++ b/lib/classes/JsonApi/SchemaMap.php
@@ -59,6 +59,7 @@ class SchemaMap
             \Courseware\StructuralElement::class => Schemas\Courseware\StructuralElement::class,
             \Courseware\StructuralElementComment::class => Schemas\Courseware\StructuralElementComment::class,
             \Courseware\StructuralElementFeedback::class => Schemas\Courseware\StructuralElementFeedback::class,
+            \Courseware\Unit::class => Schemas\Courseware\Unit::class,
             \Courseware\UserDataField::class => Schemas\Courseware\UserDataField::class,
             \Courseware\UserProgress::class => Schemas\Courseware\UserProgress::class,
             \Courseware\Task::class => Schemas\Courseware\Task::class,
diff --git a/lib/classes/JsonApi/Schemas/Courseware/Instance.php b/lib/classes/JsonApi/Schemas/Courseware/Instance.php
index 63a4d950b83..ff109661a41 100644
--- a/lib/classes/JsonApi/Schemas/Courseware/Instance.php
+++ b/lib/classes/JsonApi/Schemas/Courseware/Instance.php
@@ -19,8 +19,9 @@ class Instance extends SchemaProvider
     public function getId($resource): ?string
     {
         $root = $resource->getRoot();
+        $unit = \Courseware\Unit::findOneBySQL('structural_element_id = ?', [$root->id]);
 
-        return join('_', [$root->range_type, $root->range_id]);
+        return join('_', [$root->range_type, $root->range_id, $unit->id]);
     }
 
     /**
@@ -34,11 +35,12 @@ class Instance extends SchemaProvider
             'block-types' => array_map([$this, 'mapBlockType'], $resource->getBlockTypes()),
             'container-types' => array_map([$this, 'mapContainerType'], $resource->getContainerTypes()),
             'favorite-block-types' => $resource->getFavoriteBlockTypes($user),
-            'sequential-progression' => (bool) $resource->getSequentialProgression(),
+            'sequential-progression' => $resource->getSequentialProgression(),
             'editing-permission-level' => $resource->getEditingPermissionLevel(),
             'certificate-settings' => $resource->getCertificateSettings(),
             'reminder-settings' => $resource->getReminderSettings(),
-            'reset-progress-settings' => $resource->getResetProgressSettings()
+            'reset-progress-settings' => $resource->getResetProgressSettings(),
+            'root-id' => $resource->getRoot()->id
         ];
     }
 
diff --git a/lib/classes/JsonApi/Schemas/Courseware/StructuralElement.php b/lib/classes/JsonApi/Schemas/Courseware/StructuralElement.php
index b50b17979ef..4335e892417 100644
--- a/lib/classes/JsonApi/Schemas/Courseware/StructuralElement.php
+++ b/lib/classes/JsonApi/Schemas/Courseware/StructuralElement.php
@@ -23,6 +23,7 @@ class StructuralElement extends SchemaProvider
     const REL_PARENT = 'parent';
     const REL_USER = 'user';
     const REL_TASK = 'task';
+    const REL_UNIT = 'unit';
 
     /**
      * {@inheritdoc}
@@ -51,6 +52,7 @@ class StructuralElement extends SchemaProvider
             'write-approval' => $resource['write_approval']->getIterator(),
             'copy-approval' => $resource['copy_approval']->getIterator(),
             'can-edit' => $resource->canEdit($user),
+            'can-visit' => $resource->canVisit($user),
             'is-link' => (int) $resource['is_link'],
             'target-id' => (int)  $resource['target_id'],
             'external-relations' => $resource['external_relations']->getIterator(),
@@ -131,6 +133,12 @@ class StructuralElement extends SchemaProvider
             $this->shouldInclude($context, self::REL_TASK)
         );
 
+        $relationships = $this->addUnitRelationship(
+            $relationships,
+            $resource,
+            $this->shouldInclude($context, self::REL_UNIT)
+        );
+
         return $relationships;
     }
 
@@ -355,6 +363,22 @@ class StructuralElement extends SchemaProvider
         return $relationships;
     }
 
+    private function addUnitRelationship(array $relationships, $resource, $includeData): array
+    {
+        $relation = [
+            self::RELATIONSHIP_LINKS => [
+                Link::RELATED => $this->getRelationshipRelatedLink($resource, self::REL_UNIT),
+            ],
+        ];
+
+        $related = $resource->findUnit();
+        $relation[self::RELATIONSHIP_DATA] = $related;
+
+        $relationships[self::REL_UNIT] = $relation;
+
+        return $relationships;
+    }
+
     private static $memo = [];
 
     private function createLinkToCourse($rangeId)
diff --git a/lib/classes/JsonApi/Schemas/Courseware/Unit.php b/lib/classes/JsonApi/Schemas/Courseware/Unit.php
new file mode 100644
index 00000000000..a1c554ec62c
--- /dev/null
+++ b/lib/classes/JsonApi/Schemas/Courseware/Unit.php
@@ -0,0 +1,79 @@
+<?php
+
+namespace JsonApi\Schemas\Courseware;
+
+use JsonApi\Schemas\SchemaProvider;
+use Neomerx\JsonApi\Contracts\Schema\ContextInterface;
+use Neomerx\JsonApi\Schema\Link;
+
+class Unit extends SchemaProvider
+{
+    const TYPE = 'courseware-units';
+
+    const REL_CREATOR= 'creator';
+    const REL_RANGE = 'range';
+    const REL_STRUCTURAL_ELEMENT = 'structural-element';
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getId($resource): ?string
+    {
+        return $resource->id;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getAttributes($resource, ContextInterface $context): iterable
+    {
+        return [
+            'content-type' => (string) $resource['content_type'],
+            'public' => (int) $resource['public'],
+            'release-date' => $resource['release_date'] ? date('c', $resource['release_date']) : null,
+            'withdraw-date' => $resource['withdraw_date'] ? date('c', $resource['withdraw_date']) : null,
+            'mkdate'    => date('c', $resource['mkdate']),
+            'chdate'    => date('c', $resource['chdate']),
+        ];
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    public function getRelationships($resource, ContextInterface $context): iterable
+    {
+        $relationships = [];
+
+        $relationships[self::REL_CREATOR] = $resource['creator_id']
+            ? [
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($resource->creator),
+                ],
+                self::RELATIONSHIP_DATA => $resource->creator,
+            ]
+            : [self::RELATIONSHIP_DATA => null];
+
+        $relationships[self::REL_STRUCTURAL_ELEMENT] = $resource['structural_element_id']
+            ? [
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($resource->structural_element),
+                ],
+                self::RELATIONSHIP_DATA => $resource->structural_element,
+            ]
+            : [self::RELATIONSHIP_DATA => null];
+
+        $rangeType = $resource->range_type;
+        $range = $resource->$rangeType;
+
+        $relationships[self::REL_RANGE] = $range
+            ? [
+                self::RELATIONSHIP_LINKS => [
+                    Link::RELATED => $this->createLinkToResource($range),
+                ],
+                self::RELATIONSHIP_DATA => $range,
+            ]
+            : [self::RELATIONSHIP_DATA => null];
+
+        return $relationships;
+    }
+}
\ No newline at end of file
diff --git a/lib/models/Courseware/Instance.php b/lib/models/Courseware/Instance.php
index 0b388fe93ca..63bd34bd8aa 100644
--- a/lib/models/Courseware/Instance.php
+++ b/lib/models/Courseware/Instance.php
@@ -165,9 +165,10 @@ class Instance
     public function getSequentialProgression(): bool
     {
         $range = $this->getRange();
-        $config = $range->getConfiguration()->getValue('COURSEWARE_SEQUENTIAL_PROGRESSION');
+        $root = $this->getRoot();
+        $sequentialProgression = $range->getConfiguration()->COURSEWARE_SEQUENTIAL_PROGRESSION[$root->id];
 
-        return (bool) $config;
+        return (bool) $sequentialProgression;
     }
 
     /**
@@ -178,7 +179,10 @@ class Instance
     public function setSequentialProgression(bool $isSequentialProgression): void
     {
         $range = $this->getRange();
-        $range->getConfiguration()->store('COURSEWARE_SEQUENTIAL_PROGRESSION', $isSequentialProgression);
+        $root = $this->getRoot();
+        $progressions = $range->getConfiguration()->getValue('COURSEWARE_SEQUENTIAL_PROGRESSION');
+        $progressions[$root->id] = $isSequentialProgression ? 1 : 0;
+        $range->getConfiguration()->store('COURSEWARE_SEQUENTIAL_PROGRESSION', $progressions);
     }
 
     const EDITING_PERMISSION_DOZENT = 'dozent';
@@ -192,11 +196,15 @@ class Instance
     public function getEditingPermissionLevel(): string
     {
         $range = $this->getRange();
+        $root = $this->getRoot();
         /** @var string $editingPermissionLevel */
-        $editingPermissionLevel = $range->getConfiguration()->getValue('COURSEWARE_EDITING_PERMISSION');
-        $this->validateEditingPermissionLevel($editingPermissionLevel);
+        $editingPermissionLevel = $range->getConfiguration()->COURSEWARE_EDITING_PERMISSION[$root->id];
+        if ($editingPermissionLevel) {
+            $this->validateEditingPermissionLevel($editingPermissionLevel);
+            return $editingPermissionLevel;
+        }
 
-        return $editingPermissionLevel;
+        return self::EDITING_PERMISSION_TUTOR; // tutor is default
     }
 
     /**
@@ -209,7 +217,10 @@ class Instance
     {
         $this->validateEditingPermissionLevel($editingPermissionLevel);
         $range = $this->getRange();
-        $range->getConfiguration()->store('COURSEWARE_EDITING_PERMISSION', $editingPermissionLevel);
+        $root = $this->getRoot();
+        $permissions = $range->getConfiguration()->getValue('COURSEWARE_EDITING_PERMISSION');
+        $permissions[$root->id] = $editingPermissionLevel;
+        $range->getConfiguration()->store('COURSEWARE_EDITING_PERMISSION', $permissions);
     }
 
     /**
@@ -240,9 +251,10 @@ class Instance
     public function getCertificateSettings(): array
     {
         $range = $this->getRange();
+        $root = $this->getRoot();
         /** @var array $certificateSettings */
         $certificateSettings = json_decode(
-            $range->getConfiguration()->getValue('COURSEWARE_CERTIFICATE_SETTINGS'),
+            $range->getConfiguration()->COURSEWARE_CERTIFICATE_SETTINGS[$root->id],
             true
         )?: [];
         $this->validateCertificateSettings($certificateSettings);
@@ -259,8 +271,10 @@ class Instance
     {
         $this->validateCertificateSettings($certificateSettings);
         $range = $this->getRange();
-        $range->getConfiguration()->store('COURSEWARE_CERTIFICATE_SETTINGS',
-            count($certificateSettings) > 0 ? json_encode($certificateSettings) : null);
+        $root = $this->getRoot();
+        $settings = $range->getConfiguration()->getValue('COURSEWARE_CERTIFICATE_SETTINGS');
+        $settings[$root->id] = count($certificateSettings) > 0 ? json_encode($certificateSettings) : null;
+        $range->getConfiguration()->store('COURSEWARE_CERTIFICATE_SETTINGS', $settings);
     }
 
     /**
@@ -290,9 +304,10 @@ class Instance
     public function getReminderSettings(): array
     {
         $range = $this->getRange();
+        $root = $this->getRoot();
         /** @var int $reminderInterval */
         $reminderSettings = json_decode(
-            $range->getConfiguration()->getValue('COURSEWARE_REMINDER_SETTINGS'),
+            $range->getConfiguration()->COURSEWARE_REMINDER_SETTINGS[$root->id],
             true
         )?: [];
         $this->validateReminderSettings($reminderSettings);
@@ -309,8 +324,10 @@ class Instance
     {
         $this->validateReminderSettings($reminderSettings);
         $range = $this->getRange();
-        $range->getConfiguration()->store('COURSEWARE_REMINDER_SETTINGS',
-            count($reminderSettings) > 0 ? json_encode($reminderSettings) : null);
+        $root = $this->getRoot();
+        $settings = $range->getConfiguration()->getValue('COURSEWARE_REMINDER_SETTINGS');
+        $settings[$root->id] = count($reminderSettings) > 0 ? json_encode($reminderSettings) : null;
+        $range->getConfiguration()->store('COURSEWARE_REMINDER_SETTINGS', $settings);
     }
 
     /**
@@ -342,9 +359,10 @@ class Instance
     public function getResetProgressSettings(): array
     {
         $range = $this->getRange();
+        $root = $this->getRoot();
         /** @var int $reminderInterval */
         $resetProgressSettings = json_decode(
-            $range->getConfiguration()->getValue('COURSEWARE_RESET_PROGRESS_SETTINGS'),
+            $range->getConfiguration()->COURSEWARE_RESET_PROGRESS_SETTINGS[$root->id],
             true
         )?: [];
         $this->validateResetProgressSettings($resetProgressSettings);
@@ -361,8 +379,10 @@ class Instance
     {
         $this->validateResetProgressSettings($resetProgressSettings);
         $range = $this->getRange();
-        $range->getConfiguration()->store('COURSEWARE_RESET_PROGRESS_SETTINGS',
-            count($resetProgressSettings) > 0 ? json_encode($resetProgressSettings) : null);
+        $root = $this->getRoot();
+        $settings = $range->getConfiguration()->getValue('COURSEWARE_RESET_PROGRESS_SETTINGS');
+        $settings[$root->id] = count($resetProgressSettings) > 0 ? json_encode($resetProgressSettings) : null;
+        $range->getConfiguration()->store('COURSEWARE_RESET_PROGRESS_SETTINGS', $settings);
     }
 
     /**
diff --git a/lib/models/Courseware/StructuralElement.php b/lib/models/Courseware/StructuralElement.php
index 7206199e9ff..9aa4f90e625 100644
--- a/lib/models/Courseware/StructuralElement.php
+++ b/lib/models/Courseware/StructuralElement.php
@@ -375,7 +375,7 @@ class StructuralElement extends \SimpleORMap
     {
         return $GLOBALS['perm']->have_perm('root', $user->id) ||
             $GLOBALS['perm']->have_studip_perm(
-                \CourseConfig::get($this->range_id)->COURSEWARE_EDITING_PERMISSION,
+                \CourseConfig::get($this->range_id)->COURSEWARE_EDITING_PERMISSION[$this->getCoursewareCourse($this->range_id)->id],
                 $this->range_id,
                 $user->id
             );
@@ -653,6 +653,17 @@ class StructuralElement extends \SimpleORMap
         return $ancestors;
     }
 
+    public function findUnit()
+    {
+        if ($this->isRootNode()) {
+            $root = $this;
+        } else {
+            $root = $this->findAncestors()[0];
+        }
+
+        return Unit::findOneBySQL('structural_element_id = ?', [$root->id]); 
+    }
+
     /**
      * Returns the list of all descendants of this instance in depth-first search order.
      *
@@ -760,6 +771,42 @@ SQL;
         return $this->image ? $this->image->getDownloadURL() : null;
     }
 
+    /**
+     * Copies this instance into another course oder users contents.
+     *
+     * @param User              $user   this user will be the owner of the copy
+     * @param Range $parent the target where to copy this instance
+     *
+     * @return StructuralElement the copy of this instance
+     */
+    public function copyToRange(User $user, string $rangeId, string $rangeType, string $purpose = ''): StructuralElement
+    {
+        $element = self::build([
+            'parent_id' => null,
+            'range_id' => $rangeId,
+            'range_type' => $rangeType,
+            'owner_id' => $user->id,
+            'editor_id' => $user->id,
+            'edit_blocker_id' => null,
+            'title' => $this->title,
+            'purpose' => $purpose ?: $this->purpose,
+            'position' => 0,
+            'payload' => $this->payload,
+        ]);
+
+        $element->store();
+
+        $file_ref_id = $this->copyImage($user, $element);
+        $element->image_id = $file_ref_id;
+        $element->store();
+
+        $this->copyContainers($user, $element);
+
+        $this->copyChildren($user, $element, $purpose);
+
+        return $element;
+    }
+
     /**
      * Copies this instance as a child into another structural element.
      *
diff --git a/lib/models/Courseware/Unit.php b/lib/models/Courseware/Unit.php
new file mode 100644
index 00000000000..acf2a93ab7f
--- /dev/null
+++ b/lib/models/Courseware/Unit.php
@@ -0,0 +1,112 @@
+<?php
+
+namespace Courseware;
+
+use phootwork\collection\ArrayList;
+use phpDocumentor\Reflection\Types\Array_;
+use User;
+
+/**
+ * Courseware's units.
+ *
+ * @author  Ron Lucke <lucke@elan-ev.de>
+ * @license GPL2 or any later version
+ *
+ * @since   Stud.IP 5.3
+ * 
+ * @property int                            $id                     database column
+ * @property string                         $range_id               database column
+ * @property string                         $range_type             database column
+ * @property int                            $structural_element_id  database column
+ * @property string                         $content_type           database column
+ * @property int                            $public                 database column
+ * @property string                         $creator_id             database column
+ * @property int                            $release_date           database column
+ * @property int                            $withdraw_date          database column
+ * @property int                            $mkdate                 database column
+ * @property int                            $chdate                 database column
+ * @property \User                          $creator                belongs_to User
+ * @property \Courseware\StructuralElement  $structural_element     belongs_to Courseware\StructuralElement
+ * 
+ * @SuppressWarnings(PHPMD.TooManyPublicMethods)
+ * @SuppressWarnings(PHPMD.ExcessiveClassComplexity)
+ */
+
+class Unit extends \SimpleORMap
+{
+    protected static function configure($config = [])
+    {
+        $config['db_table'] = 'cw_units';
+
+        $config['has_one']['structural_element'] = [
+            'class_name' => StructuralElement::class,
+            'foreign_key' => 'structural_element_id',
+            'on_delete' => 'delete',
+        ];
+        $config['belongs_to']['course'] = [
+            'class_name'  => \Course::class,
+            'foreign_key' => 'range_id',
+            'assoc_foreign_key' => 'seminar_id',
+        ];
+        $config['belongs_to']['user'] = [
+            'class_name' => User::class,
+            'foreign_key' => 'range_id',
+            'assoc_foreign_key' => 'user_id',
+        ];
+        $config['belongs_to']['creator'] = [
+            'class_name' => User::class,
+            'foreign_key' => 'creator_id',
+        ];
+
+        parent::configure($config);
+    }
+
+    public static function findCoursesUnits(\Course $course): array
+    {
+        return self::findBySQL('range_id = ? AND range_type = ?', [$course->id, 'course']);
+    }
+
+    public static function findUsersUnits(\User $user): array
+    {
+        return self::findBySQL('range_id = ? AND range_type = ?', [$user->id, 'user']);
+    }
+
+    public function canRead(\User $user): bool
+    {
+        return $this->structural_element->canRead($user);
+    }
+
+    public function canEdit(\User $user): bool
+    {
+        return $this->structural_element->canEdit($user);;
+    }
+
+    public function copy(\User $user, string $rangeId, string $rangeType, array $modified = null): Unit
+    {
+        $sourceUnitElement = $this->structural_element;
+
+        $newElement = $sourceUnitElement->copyToRange($user, $rangeId, $rangeType);
+
+        if ($modified !== null) {
+            $newElement->title = $modified['title'] ?? $newElement->title;
+            $newElement->payload['color'] = $modified['color'] ?? 'studip-blue';
+            $newElement->payload['description'] = $modified['description'] ?? $newElement->payload['description'];
+            $newElement->store();
+        }
+
+        $newUnit = \Courseware\Unit::build([
+            'range_id' => $rangeId,
+            'range_type' => $rangeType,
+            'structural_element_id' => $newElement->id,
+            'content_type' => 'courseware',
+            'creator_id' => $user->id,
+            'public' => '',
+            'release_date' => '',
+            'withdraw_date' => '',
+        ]);
+        
+        $newUnit->store();
+
+        return $newUnit;
+    }
+}
diff --git a/lib/modules/CoursewareModule.class.php b/lib/modules/CoursewareModule.class.php
index e07d5d20aa8..76280e602e0 100644
--- a/lib/modules/CoursewareModule.class.php
+++ b/lib/modules/CoursewareModule.class.php
@@ -41,31 +41,21 @@ class CoursewareModule extends CorePlugin implements SystemPlugin, StudipModule,
         );
         $navigation->setImage(Icon::create('courseware', Icon::ROLE_INFO_ALT));
         $navigation->addSubNavigation(
-            'content',
-            new Navigation(_('Inhalt'), 'dispatch.php/course/courseware/?cid='.$courseId)
+            'shelf',
+            new Navigation(_('Lernmaterialien'), 'dispatch.php/course/courseware/?cid=' . $courseId)
         );
         $navigation->addSubNavigation(
-            'dashboard',
-            new Navigation(_('Übersicht'), 'dispatch.php/course/courseware/dashboard?cid='.$courseId)
+            'unit',
+            new Navigation(_('Inhalt'), 'dispatch.php/course/courseware/courseware?cid=' . $courseId)
+        );
+        $navigation->addSubNavigation(
+            'activities',
+            new Navigation(_('Aktivitäten'), 'dispatch.php/course/courseware/activities?cid=' . $courseId)
+        );
+        $navigation->addSubNavigation(
+            'tasks',
+            new Navigation(_('Aufgaben'), 'dispatch.php/course/courseware/tasks?cid=' . $courseId)
         );
-
-        if ($GLOBALS['perm']->have_studip_perm('dozent', $courseId)) {
-            $navigation->addSubNavigation(
-                'manager',
-                new Navigation(_('Verwaltung'), 'dispatch.php/course/courseware/manager?cid='.$courseId)
-            );
-        } else {
-            $element = StructuralElement::getCoursewareCourse($courseId);
-            if ($element !== null) {
-                $instance = new Instance($element);
-                if ($GLOBALS['perm']->have_studip_perm($instance->getEditingPermissionLevel(), $courseId)) {
-                    $navigation->addSubNavigation(
-                        'manager',
-                        new Navigation(_('Verwaltung'), 'dispatch.php/course/courseware/manager?cid='.$courseId)
-                    );
-                }
-            }
-        }
 
         return ['courseware' => $navigation];
     }
@@ -129,10 +119,10 @@ class CoursewareModule extends CorePlugin implements SystemPlugin, StudipModule,
     {
         return [
             'summary' => _('Lerninhalte erstellen, verteilen und erleben'),
-            'description' => _('Mit Courseware können Sie interaktive multimediale Lerninhalte erstellen und nutzen. '
+            'description' => _('Mit Courseware können Sie interaktive, multimediale Lerninhalte erstellen und nutzen. '
                              . 'Die Lerninhalte lassen sich hierarchisch unterteilen und können aus Texten, '
                              . 'Videosequenzen, Aufgaben, Kommunikationselementen und einer Vielzahl weiterer '
-                             . 'Elementen bestehen. Fertige Lerninhalte können exportiert und in andere Kurse oder '
+                             . 'Elemente bestehen. Fertige Lerninhalte können exportiert und in andere Kurse oder '
                              . 'andere Installationen importiert werden. Courseware ist nicht nur für digitale '
                              . 'Formate geeignet, sondern kann auch genutzt werden, um klassische '
                              . 'Präsenzveranstaltungen mit Online-Anteilen zu ergänzen. Formate wie integriertes '
diff --git a/lib/navigation/ContentsNavigation.php b/lib/navigation/ContentsNavigation.php
index 965f598b0b7..119de6a73d0 100644
--- a/lib/navigation/ContentsNavigation.php
+++ b/lib/navigation/ContentsNavigation.php
@@ -52,16 +52,12 @@ class ContentsNavigation extends Navigation
         $courseware->setImage(Icon::create('courseware'));
 
         $courseware->addSubNavigation(
-            'overview',
+            'shelf',
             new Navigation(_('Übersicht'), 'dispatch.php/contents/courseware/index')
         );
         $courseware->addSubNavigation(
             'courseware',
-            new Navigation(_('Persönliche Lernmaterialien'), 'dispatch.php/contents/courseware/courseware')
-        );
-        $courseware->addSubNavigation(
-            'courseware_manager',
-            new Navigation(_('Verwaltung persönlicher Lernmaterialien'), 'dispatch.php/contents/courseware/courseware_manager')
+            new Navigation(_('Inhalt'), 'dispatch.php/contents/courseware/courseware')
         );
         $courseware->addSubNavigation(
             'releases',
diff --git a/package-lock.json b/package-lock.json
index bcca55a6197..1e7c0274524 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5087,9 +5087,9 @@
             }
         },
         "node_modules/caniuse-lite": {
-            "version": "1.0.30001341",
-            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001341.tgz",
-            "integrity": "sha512-2SodVrFFtvGENGCv0ChVJIDQ0KPaS1cg7/qtfMaICgeMolDdo/Z2OD32F0Aq9yl6F4YFwGPBS5AaPqNYiW4PoA==",
+            "version": "1.0.30001429",
+            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001429.tgz",
+            "integrity": "sha512-511ThLu1hF+5RRRt0zYCf2U2yRr9GPF6m5y90SBCWsvSoYoW7yAGlv/elyPaNfvGCkp6kj/KFZWU0BMA69Prsg==",
             "dev": true,
             "funding": [
                 {
@@ -17974,9 +17974,9 @@
             }
         },
         "caniuse-lite": {
-            "version": "1.0.30001341",
-            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001341.tgz",
-            "integrity": "sha512-2SodVrFFtvGENGCv0ChVJIDQ0KPaS1cg7/qtfMaICgeMolDdo/Z2OD32F0Aq9yl6F4YFwGPBS5AaPqNYiW4PoA==",
+            "version": "1.0.30001429",
+            "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001429.tgz",
+            "integrity": "sha512-511ThLu1hF+5RRRt0zYCf2U2yRr9GPF6m5y90SBCWsvSoYoW7yAGlv/elyPaNfvGCkp6kj/KFZWU0BMA69Prsg==",
             "dev": true
         },
         "chalk": {
diff --git a/resources/assets/javascripts/bootstrap/courseware.js b/resources/assets/javascripts/bootstrap/courseware.js
index 7124ac9a8cc..503ae89f873 100644
--- a/resources/assets/javascripts/bootstrap/courseware.js
+++ b/resources/assets/javascripts/bootstrap/courseware.js
@@ -1,4 +1,15 @@
 STUDIP.domReady(() => {
+    if (document.getElementById('courseware-shelf-app')) {
+        STUDIP.Vue.load().then(({ createApp }) => {
+            import(
+                /* webpackChunkName: "courseware-shelf-app" */
+                '@/vue/courseware-shelf-app.js'
+            ).then(({ default: mountApp }) => {
+                return mountApp(STUDIP, createApp, '#courseware-shelf-app');
+            });
+        });
+    }
+
     if (document.getElementById('courseware-index-app')) {
         STUDIP.Vue.load().then(({ createApp }) => {
             import(
@@ -10,35 +21,35 @@ STUDIP.domReady(() => {
         });
     }
 
-    if (document.getElementById('courseware-dashboard-app')) {
+    if (document.getElementById('courseware-activities-app')) {
         STUDIP.Vue.load().then(({ createApp }) => {
             import(
-                /* webpackChunkName: "courseware-dashboard-app" */
-                '@/vue/courseware-dashboard-app.js'
+                /* webpackChunkName: "courseware-activities-app" */
+                '@/vue/courseware-activities-app.js'
             ).then(({ default: mountApp }) => {
-                return mountApp(STUDIP, createApp, '#courseware-dashboard-app');
+                return mountApp(STUDIP, createApp, '#courseware-activities-app');
             });
         });
     }
 
-    if (document.getElementById('courseware-manager-app')) {
+    if (document.getElementById('courseware-tasks-app')) {
         STUDIP.Vue.load().then(({ createApp }) => {
             import(
-                /* webpackChunkName: "courseware-manager-app" */
-                '@/vue/courseware-manager-app.js'
+                /* webpackChunkName: "courseware-tasks-app" */
+                '@/vue/courseware-tasks-app.js'
             ).then(({ default: mountApp }) => {
-                return mountApp(STUDIP, createApp, '#courseware-manager-app');
+                return mountApp(STUDIP, createApp, '#courseware-tasks-app');
             });
         });
     }
 
-    if (document.getElementById('courseware-content-overview-app')) {
+    if (document.getElementById('courseware-manager-app')) {
         STUDIP.Vue.load().then(({ createApp }) => {
             import(
-                /* webpackChunkName: "courseware-content-overview-app" */
-                '@/vue/courseware-content-overview-app.js'
+                /* webpackChunkName: "courseware-manager-app" */
+                '@/vue/courseware-manager-app.js'
             ).then(({ default: mountApp }) => {
-                return mountApp(STUDIP, createApp, '#courseware-content-overview-app');
+                return mountApp(STUDIP, createApp, '#courseware-manager-app');
             });
         });
     }
diff --git a/resources/assets/stylesheets/scss/buttons.scss b/resources/assets/stylesheets/scss/buttons.scss
index 05f2a26e504..ef819817c0e 100644
--- a/resources/assets/stylesheets/scss/buttons.scss
+++ b/resources/assets/stylesheets/scss/buttons.scss
@@ -106,6 +106,16 @@ button.button {
 .button.search {
     @include button-with-icon(search, clickable, info_alt);
 }
+.button.arr_left {
+    @include button-with-icon(arr_1left, clickable, info_alt);
+}
+.button.arr_right {
+    @include button-with-icon(arr_1right, clickable, info_alt);
+    &::before {
+        float: right;
+        margin: 1px -8px 0 5px;
+    }
+}
 
 /* Grouped Buttons */
 .button-group {
diff --git a/resources/assets/stylesheets/scss/courseware.scss b/resources/assets/stylesheets/scss/courseware.scss
index b1fc6be83e7..804165129e4 100644
--- a/resources/assets/stylesheets/scss/courseware.scss
+++ b/resources/assets/stylesheets/scss/courseware.scss
@@ -7,7 +7,8 @@ $companion-types: (
     alert: alert,
     sad: sad,
     happy: happy,
-    pointing: pointing-right
+    pointing: pointing-right,
+    curious: curious
 );
 
 $element-icons: (
@@ -246,9 +247,9 @@ c o n t e n t s  e n d
 r i b b o n
 * * * * * */
 $consum_ribbon_width: calc(100% - 58px);
-#course-courseware-index,
+#course-courseware-courseware,
 #contents-courseware-courseware,
-#contents-courseware-shared_content_courseware  {
+#contents-courseware-shared_content_courseware {
     &.consume {
         overflow: hidden;
     }
@@ -621,8 +622,8 @@ ribbon end
         scrollbar-color: $base-color #f5f5f5;
     }
 
-    .cw-wellcome-screen {
-        .cw-wellcome-screen-keyvisual {
+    .cw-welcome-screen {
+        .cw-welcome-screen-keyvisual {
             margin: 14px 0 42px 0;
             width: 100%;
             height: 400px;
@@ -635,7 +636,7 @@ ribbon end
             text-align: center;
             font-size: 2.25em;
         }
-        .cw-wellcome-screen-actions {
+        .cw-welcome-screen-actions {
             display: flex;
             flex-wrap: wrap;
             justify-content: center;
@@ -799,6 +800,8 @@ ribbon end
     }
 }
 
+
+
 /* * * * * * * * * * * *
  structual element end
 * * * * * * * * * * * */
@@ -2084,6 +2087,17 @@ v i e w  w i d g e t
         @include background-icon(oer-campus, clickable);
     }
 }
+.cw-import-widget {
+    .cw-import-widget-archive{
+        @include background-icon(file-archive, clickable);
+    }
+    .cw-import-widget-copy{
+        @include background-icon(files, clickable);
+    }
+    .cw-import-widget-import{
+        @include background-icon(import, clickable);
+    }
+}
 
 /* * * * * * * * * * * * * *
 v i e w  w i d g e t  e n d
@@ -2218,118 +2232,6 @@ textarea.studip-wysiwyg {
 w y s i w y g  e n d
 * * * * * * * * * * */
 
-/* * * * * *
-d i a l o g
-* * * * * */
-
-.studip-dialog-backdrop {
-    position: fixed;
-    top: 0;
-    bottom: 0;
-    left: 0;
-    right: 0;
-    background-color: fade-out($base-color, 0.5);
-    display: flex;
-    justify-content: center;
-    align-items: center;
-    z-index: 3001;
-}
-.studip-dialog-body {
-    position: absolute;
-    background: $white;
-    box-shadow: 0 0 8px fade-out($black, 0.5);
-    overflow-x: auto;
-    display: flex;
-    flex-direction: column;
-    padding: 3px;
-    margin: 3px;
-    max-height: 98vh;
-
-    .studip-dialog-header,
-    .studip-dialog-footer {
-        padding: 7px;
-        display: flex;
-    }
-    .studip-dialog-header {
-        background: $base-color none repeat scroll 0 0;
-        border-bottom: 1px solid $dark-gray-color-10;
-        color: $white;
-        justify-content: space-between;
-        font-size: 1.3em;
-        padding: 0.5em 1em;
-        cursor: grab;
-
-        &.drag-active {
-            cursor: grabbing;
-        }
-    }
-    .studip-dialog-close-button {
-        @include background-icon(decline, info-alt);
-        background-repeat: no-repeat;
-        background-position-y: center;
-        background-color: transparent;
-        border: none;
-
-        width: 22px;
-        height: 22px;
-        margin-right: -10px;
-        margin-left: 2em;
-        cursor: pointer;
-    }
-    .studip-dialog-content {
-        color: $black;
-        position: relative;
-        padding: 15px;
-        overflow-y: auto;
-        min-width: 100%;
-        // resize: both;
-        box-sizing: border-box;
-    }
-    .studip-dialog-footer {
-        border-top: 1px solid $dark-gray-color-10;
-        justify-content: center;
-    }
-
-    &.studip-dialog-warning,
-    &.studip-dialog-alert {
-        .studip-dialog-content {
-            padding: 15px 15px 15px 62px;
-            background-position: 12px center;
-            background-repeat: no-repeat;
-            box-sizing: border-box;
-            display: flex;
-            align-items: center;
-        }
-    }
-
-    &.studip-dialog-alert {
-        .studip-dialog-header {
-            background: $active-color none repeat scroll 0 0;
-        }
-        .studip-dialog-content {
-            @include background-icon(question-circle-full, attention, 32);
-        }
-    }
-    &.studip-dialog-warning {
-        .studip-dialog-header {
-            color: $black;
-            background: $activity-color none repeat scroll 0 0;
-        }
-        .studip-dialog-close-button {
-            @include background-icon(decline, clickable);
-            border: none;
-            background-color: transparent;
-        }
-        .studip-dialog-content {
-            @include background-icon(question-circle-full, status-yellow, 32);
-        }
-    }
-
-}
-/* * * * * * * * *
-d i a l o g  e n d
-* * * * * * * * */
-
 /* * * * * * * * *
 d a s h b o a r d
 * * * * * * * * */
@@ -2362,56 +2264,7 @@ d a s h b o a r d
         justify-content: center;
 
     }
-    .cw-dashboard-progress {
-
-        .cw-dashboard-progress-breadcrumb {
-            padding: 10px;
-            span {
-                color: $base-color;
-                cursor: pointer;
-
-                &:hover {
-                    color: $active-color;
-                }
-            }
-        }
-
-        .cw-dashboard-progress-chapter {
-            text-align: center;
-            margin-bottom: -3.5em;
-
-            h1 {
-                border: none;
-                margin: 0;
-                padding: 0;
-            }
-
-            .cw-progress-circle {
-                font-size: 18px;
-                margin: 1em auto;
-
-                &.cw-dashboard-progress-current {
-                    font-size: 12px;
-                    top: -4.5em;
-                    left: -2.5em;
-                }
-            }
-        }
-
-        .cw-dashboard-progress-subchapter-list {
-            border-top: solid thin $content-color-40;
-            height: 349px;
-            overflow-y: scroll;
-            overflow-x: hidden;
-            padding: 0 1em 0 1em;
-            scrollbar-width: thin;
-            scrollbar-color: $base-color $dark-gray-color-5;
 
-            .cw-dashboard-empty-info {
-                margin-top: 10px;
-            }
-        }
-    }
     &.cw-dashboard-task-view {
         display: unset;
         max-width: unset;
@@ -2422,12 +2275,6 @@ d a s h b o a r d
             max-height: unset;
         }
     }
-    &.cw-dashboard-activity-view {
-        .cw-dashboard-activities {
-            max-height: 760px;
-        }
-
-    }
 }
 
 #course-courseware-dashboard {
@@ -2450,60 +2297,16 @@ d a s h b o a r d
     }
 }
 
-.cw-dashboard-progress-item {
-    display: block;
-    border-bottom: solid thin $content-color-40;
-    padding: 10px 0;
-
-    &:hover{
-        background-color: hsla(217,6%,45%,.2);
-    }
-
-    &:last-child {
-        border: none;
-    }
-
-    .cw-dashboard-progress-item-value,
-    .cw-dashboard-progress-item-description {
-        display: inline-block;
-        vertical-align: top;
-    }
+.cw-activities-wrapper {
+    max-width: 1095px;
 
-    .cw-dashboard-progress-item-value {
-        width: 70px;
-        color: $base-color;
-        font-size: xx-large;
-
-        .cw-progress-circle {
-            font-size: 12px;
-            margin: 4px;
-        }
-    }
-    .cw-dashboard-progress-item-description {
-        color: $base-color;
-        padding-left: 14px;
-        text-overflow: ellipsis;
-        overflow: hidden;
-        white-space: nowrap;
-        padding: 0.5em 0 0 1em;
-        text-overflow: ellipsis;
-        overflow: hidden;
-        white-space: nowrap;
-    }
-}
-.cw-dashboard-activities-wrapper {
     .cw-companion-box {
         margin: 10px;
     }
 
-    .cw-dashboard-activities {
-        max-height: 525px;
+    .cw-activities {
         list-style: none;
         padding: 0;
-        scrollbar-width: thin;
-        scrollbar-color:$base-color #f5f5f5;
-        overflow-y: auto;
-        overflow-x: hidden;
 
         .cw-activity-item {
             border-bottom: solid thin $content-color-40;
@@ -2519,12 +2322,8 @@ d a s h b o a r d
                     padding-right: 0.5em;
                     vertical-align: text-bottom;
                 }
-                &.cw-activity-item-text {
-                    padding-left: 23px;
-                }
             }
         }
-
     }
 }
 
@@ -2537,10 +2336,6 @@ d a s h b o a r d
 
 .cw-dashboard-tasks-wrapper,
 .cw-dashboard-students-wrapper {
-    overflow-x: auto;
-    scrollbar-width: thin;
-    scrollbar-color:$base-color #f5f5f5;
-    max-height: 280px;
 
     table.default {
         margin: 0;
@@ -2576,6 +2371,99 @@ d a s h b o a r d
 d a s h b o a r d  e n d
 * * * * * * * * * * * */
 
+/* * * * * * * * * * * *
+    p r o g r e s s
+* * * * * * * * * * * */
+.cw-unit-progress {
+    .cw-unit-progress-breadcrumb {
+        padding: 10px;
+        span {
+            color: $base-color;
+            cursor: pointer;
+
+            &:hover {
+                color: $active-color;
+            }
+        }
+    }
+
+    .cw-unit-progress-chapter {
+        text-align: center;
+        margin-bottom: -3.5em;
+
+        h1 {
+            border: none;
+            margin: 0;
+            padding: 0;
+        }
+
+        .cw-progress-circle {
+            font-size: 18px;
+            margin: 1em auto;
+
+            &.cw-unit-progress-current {
+                font-size: 12px;
+                top: -4.5em;
+                left: -2.5em;
+            }
+        }
+    }
+
+    .cw-unit-progress-subchapter-list {
+        border-top: solid thin $content-color-40;
+        padding: 0 1em 0 1em;
+
+        .cw-dashboard-empty-info {
+            margin-top: 10px;
+        }
+    }
+}
+
+.cw-unit-progress-item {
+    display: block;
+    border-bottom: solid thin $content-color-40;
+    padding: 10px 0;
+
+    &:hover{
+        background-color: hsla(217,6%,45%,.2);
+    }
+
+    &:last-child {
+        border: none;
+    }
+
+    .cw-unit-progress-item-value,
+    .cw-unit-progress-item-description {
+        display: inline-block;
+        vertical-align: top;
+    }
+
+    .cw-unit-progress-item-value {
+        width: 70px;
+        color: $base-color;
+        font-size: xx-large;
+
+        .cw-progress-circle {
+            font-size: 12px;
+            margin: 4px;
+        }
+    }
+    .cw-unit-progress-item-description {
+        color: $base-color;
+        padding-left: 14px;
+        text-overflow: ellipsis;
+        overflow: hidden;
+        white-space: nowrap;
+        padding: 0.5em 0 0 1em;
+        text-overflow: ellipsis;
+        overflow: hidden;
+        white-space: nowrap;
+    }
+}
+/* * * * * * * * * * * *
+ p r o g r e s s  e n d
+* * * * * * * * * * * */
+
 /* * * * * *
 o b l o n g
 * * * * * */
@@ -5178,102 +5066,156 @@ cw tiles
     padding-left: 0;
     row-gap: 5px;
     column-gap: 5px;
+}
+.cw-tiles .tile,
+.cw-tile {
+    height: 420px;
+    width: 270px;
+    margin: 0;
+    background-color: $base-color;
+    &:last-child {
+        margin-right: 0;
+    }
 
-    .tile {
-        height: 420px;
-        width: 270px;
-        margin: 0;
-        background-color: $base-color;
-        cursor: pointer;
-        &:last-child {
-            margin-right: 0;
+    @each $name, $color in $tile-colors {
+        &.#{"" + $name} {
+            background-color: $color;
         }
+    };
+}
 
-        @each $name, $color in $tile-colors {
-            &.#{"" + $name} {
-                background-color: $color;
-            }
-        };
+.preview-image {
+    height: 180px;
+    width: 100%;
+    background-size: auto 180px;
+    background-repeat: no-repeat;
+    background-color: $content-color-20;
+    background-position: center;
+    &.default-image {
+            @include background-icon(courseware, clickable, 128);
     }
 
-    .preview-image {
-        height: 180px;
-        width: 100%;
-        background-size: auto 180px;
-        background-repeat: no-repeat;
-        background-color: $content-color-20;
-        background-position: center;
-        &.default-image {
-                @include background-icon(courseware, clickable, 128);
-        }
+    .overlay-text {
+        padding: 6px 7px;
+        margin: 4px;
+        background-color: rgba(255,255,255,0.8);
+        width: fit-content;
+        max-width: 100%;
+        height: 1.25em;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        float: right;
+        text-align: right;
+    }
 
-        .overlay-text {
-            padding: 0.25em;
-            margin: 0.25em;
-            background-color: rgba(255,255,255,0.8);
-            width: fit-content;
-            max-width: 100%;
-            height: 1.25em;
-            overflow: hidden;
-            text-overflow: ellipsis;
-            float: right;
-            text-align: right;
+    .overlay-action-menu {
+        padding: 0;
+        margin: 0.25em;
+        background-color: rgba(255,255,255,0.8);
+        width: fit-content;
+        max-width: 100%;
+        overflow: hidden;
+        float: right;
+        text-align: right;
+        .action-menu {
+            margin: 5px;
         }
     }
+}
 
-    .description {
-        height: 220px;
-        padding: 14px;
-        color: $white;
-        position: relative;
+.description {
+    height: 220px;
+    padding: 14px;
+    color: $white;
+    position: relative;
+    display: block;
 
-        header {
-            font-size: 20px;
-            line-height: 22px;
-            color: $white;
-            border: none;
-            margin-bottom: 0.75em;
-            width: 240px;
-            overflow: hidden;
-            text-overflow: ellipsis;
-            white-space: nowrap;
-            background-repeat: no-repeat;
-            background-position: 0 0;
+    header {
+        font-size: 20px;
+        line-height: 22px;
+        color: $white;
+        border: none;
+        width: 240px;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        white-space: nowrap;
+        background-repeat: no-repeat;
+        background-position: 0 0;
 
-            @each $type, $icon in $element-icons {
-                &.description-icon-#{$type} {
-                    width: 212px;
-                    padding-left: 28px;
-                    @include background-icon(#{$icon}, info_alt, 22);
-                }
+        @each $type, $icon in $element-icons {
+            &.description-icon-#{$type} {
+                width: 212px;
+                padding-left: 28px;
+                @include background-icon(#{$icon}, info_alt, 22);
             }
         }
+    }
 
-        .description-text-wrapper {
-            overflow: hidden;
-            height: 10em;
-            display: -webkit-box;
-            margin-bottom: 1em;
-            -webkit-line-clamp: 7;
-            -webkit-box-orient: vertical;
-            p {
-                text-align: left;
+    .progress-wrapper {
+        width: 100%;
+        padding: 1em 0;
+        border: none;
+        background: none;
+
+        progress {
+            display: block;
+            width: 100%;
+            height: 3px;
+            margin: 0;
+            border: none;
+            background: rgba(0,0,0,0.3);
+            &:-webkit-progress-bar {
+                background:  rgba(0,0,0,0.3);
+            }
+            &::-webkit-progress-value {
+                background: white;
+            }
+            &::-moz-progress-bar {
+                background: white;
             }
         }
+    }
 
-        footer {
-            width: 242px;
-            text-align: right;
-            color: $white;
-            white-space: nowrap;
-            overflow: hidden;
-            text-overflow: ellipsis;
 
-            img {
-                vertical-align: text-bottom;
-            }
+
+    .description-text-wrapper {
+        overflow: hidden;
+        height: 10em;
+        margin-top: 0.5em;
+        display: -webkit-box;
+        margin-bottom: 1em;
+        -webkit-line-clamp: 7;
+        -webkit-box-orient: vertical;
+        p {
+            text-align: left;
         }
     }
+
+    footer {
+        width: 242px;
+        text-align: right;
+        color: $white;
+        white-space: nowrap;
+        overflow: hidden;
+        text-overflow: ellipsis;
+
+        img {
+            vertical-align: text-bottom;
+        }
+    }
+}
+
+a[href].description {
+    transition: unset;
+}
+
+a.description,
+a.description:link,
+a.description:visited,
+a.description:hover {
+    height: 210px;
+    color: $white;
+    text-decoration: unset;
 }
 
 /*
@@ -5357,7 +5299,7 @@ cw tiles end
  .cw-file-input {
      width: stretch;
      border: solid thin $base-color;
-     font-size: 13px;
+     font-size: 14px;
     cursor: pointer;
 
     &::file-selector-button {
@@ -5374,6 +5316,20 @@ cw tiles end
         }
      }
  }
+ .cw-file-input-change {
+    border: solid thin $base-color;
+
+    button.button {
+        padding: 0.5em 1.5em;
+        margin: 0 0 0 -1px;
+        line-height: 100%;
+        border: none;
+        border-right: solid thin $base-color;
+    }
+    span {
+        padding: 0.5em 1.5em 0.5em 0.5em;
+    }
+ }
  /* * * * * * * * * * * * *
  i n p u t  f i l e  e n d
 * * * * * * * * * * * * * */
@@ -5412,3 +5368,31 @@ a s s i s t i v e
 /* * * * * * * * * * * * * * *
 e n d  a s s i s t i v e
 * * * * * * * * * * * * * * */
+
+/* * * * * * * * * * * * * * *
+w i z a r d  e l e m e n t s
+* * * * * * * * * * * * * * */
+.cw-element-selector-list {
+    list-style: none;
+    padding: 0;
+
+    .cw-element-selector-item {
+        display: block;
+        width: 100%;
+        border: solid thin $content-color-40;
+        padding: 0.5em;
+        margin-bottom: 5px;
+        background-color: $white;
+        color: $base-color;
+        text-align: left;
+        cursor: pointer;
+
+        &:hover {
+            color: $white;
+            background-color: $base-color;
+        }
+    }
+}
+/* * * * * * * * * * * * * * * * * *
+w i z a r d  e l e m e n t s  e n d 
+* * * * * * * * * * * * * * * * * */
diff --git a/resources/assets/stylesheets/scss/dialog.scss b/resources/assets/stylesheets/scss/dialog.scss
index d273ccb9d91..eaa4e3b20cf 100644
--- a/resources/assets/stylesheets/scss/dialog.scss
+++ b/resources/assets/stylesheets/scss/dialog.scss
@@ -307,3 +307,114 @@ h2.dialog-subtitle {
     margin-top: 0.25em;
     margin-bottom: 0.25em;
 }
+
+/* * * * * * * * * 
+v u e  d i a l o g
+* * * * * * * * */
+
+.studip-dialog-backdrop {
+    position: fixed;
+    top: 0;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    background-color: fade-out($base-color, 0.5);
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    z-index: 3001;
+}
+.studip-dialog-body {
+    position: absolute;
+    background: $white;
+    box-shadow: 0 0 8px fade-out($black, 0.5);
+    overflow-x: auto;
+    display: flex;
+    flex-direction: column;
+    padding: 3px;
+    margin: 3px;
+    max-height: 98vh;
+
+    .studip-dialog-header,
+    .studip-dialog-footer {
+        padding: 7px;
+        display: flex;
+    }
+    .studip-dialog-header {
+        background: $base-color none repeat scroll 0 0;
+        border-bottom: 1px solid $dark-gray-color-10;
+        color: $white;
+        justify-content: space-between;
+        font-size: 1.3em;
+        padding: 0.5em 1em;
+        cursor: grab;
+
+        &.drag-active {
+            cursor: grabbing;
+        }
+    }
+    .studip-dialog-close-button {
+        @include background-icon(decline, info-alt);
+        background-repeat: no-repeat;
+        background-position-y: center;
+        background-color: transparent;
+        border: none;
+
+        width: 22px;
+        height: 22px;
+        margin-right: -10px;
+        margin-left: 2em;
+        cursor: pointer;
+    }
+    .studip-dialog-content {
+        color: $black;
+        position: relative;
+        padding: 15px;
+        overflow-y: auto;
+        min-width: 100%;
+        box-sizing: border-box;
+    }
+    .studip-dialog-footer {
+        border-top: 1px solid $dark-gray-color-10;
+        justify-content: space-between;
+    }
+
+    &.studip-dialog-warning,
+    &.studip-dialog-alert {
+        .studip-dialog-content {
+            padding: 15px 15px 15px 62px;
+            background-position: 12px center;
+            background-repeat: no-repeat;
+            box-sizing: border-box;
+            display: flex;
+            align-items: center;
+        }
+    }
+
+    &.studip-dialog-alert {
+        .studip-dialog-header {
+            background: $active-color none repeat scroll 0 0;
+        }
+        .studip-dialog-content {
+            @include background-icon(question-circle-full, attention, 32);
+        }
+    }
+    &.studip-dialog-warning {
+        .studip-dialog-header {
+            color: $black;
+            background: $activity-color none repeat scroll 0 0;
+        }
+        .studip-dialog-close-button {
+            @include background-icon(decline, clickable);
+            border: none;
+            background-color: transparent;
+        }
+        .studip-dialog-content {
+            @include background-icon(question-circle-full, status-yellow, 32);
+        }
+    }
+
+}
+/* * * * * * * * * * * * *
+v u e  d i a l o g  e n d
+* * * * * * * * * * * * */
\ No newline at end of file
diff --git a/resources/assets/stylesheets/scss/wizard.scss b/resources/assets/stylesheets/scss/wizard.scss
new file mode 100644
index 00000000000..3ad3650912a
--- /dev/null
+++ b/resources/assets/stylesheets/scss/wizard.scss
@@ -0,0 +1,214 @@
+@import '../mixins';
+.wizard-wrapper {
+    display: flex;
+
+    .wizard-meta {
+        width: 270px;
+        min-height: 440px;
+        margin-top: 38px;
+
+        img {
+            margin: auto;
+            display: block;
+        }
+    
+        p {
+            margin: 15px;
+        }
+        .wizard-requirements {
+            span {
+                font-weight: 700;
+            }
+            ul {
+                padding: 4px 0;
+                li {
+                    list-style: none;
+                    button {
+                        padding: 2px 0;
+                        background-color: transparent;
+                        border: none;
+                        color: $base-color;
+                        cursor: pointer;
+                        &:hover {
+                            color: $red;
+                        }
+                    }
+                    img {
+                        padding-right: 4px;
+                        display: inline-block;
+                        vertical-align: sub;
+                    }
+                }
+            }
+        }
+    }
+    .wizard-content-wrapper {
+        flex-grow: 2;
+        margin-left: 15px;
+
+        h2 span.required {
+            color: $red;
+        }
+
+        .wizard-progress {
+            list-style: none;
+            padding: 0;
+            margin: 1.5em 0 2.5em 0;
+        
+            li {
+                display: inline-block;
+                position: relative;
+                margin-right: 60px;
+                border: solid 2px $base-color;
+                button {
+                    padding: 6px 0;
+                    height: 36px;
+                    width: 36px;
+                    cursor: pointer;
+                    background: no-repeat;
+                    border: none;
+                }
+                &.valid {
+                    background-color: $base-color;
+                }
+                &.invalid {
+                    background-color: white;
+                }
+                &.optional {
+                    border: dashed thin $base-color;
+                }
+                &::before {
+                    position: absolute;
+                    content: "";
+                    width: 62px;
+                    border: solid thin $base-color;
+                    top: 50%;
+                    transform: translateY(-50%);
+                    -o-transform: translateY(-50%);
+                    -ms-transform: translateY(-50%);
+                    -moz-transform: translateY(-50%);
+                    -webkit-transform: translateY(-50%);
+                    left: 100%;
+                }
+                &.active::after {
+                    position: absolute;
+                    content: "";
+                    width: 38px;
+                    height: 3px;
+                    background: $base-color;
+                    top: 44px;
+                    left: -1px;
+                }
+            }
+            li:last-child {
+                margin-right: 0;
+                &::before {
+                    display: none;
+                }
+            }
+            
+        }
+        
+        .wizard-list {
+            list-style: none;
+            padding: 0;
+            .wizard-item {
+                .wizard-content {
+                    max-width: 555px;
+                    max-height: 475px;
+                    overflow-y: auto;
+                    scrollbar-width: thin;
+                    scrollbar-color: $base-color #f5f5f5;
+
+                    .wizard-required {
+                        color: $red;
+                    }
+
+                    textarea {
+                        resize: vertical;
+                    }
+
+                    input[type="text"]::placeholder,
+                    textarea::placeholder {
+                        color: $dark-gray-color-60;
+                    }
+                }
+            }
+        }
+    }
+}
+
+
+form.default fieldset.radiobutton-set {
+    > legend {
+        margin: 0px;
+        width: 100%;
+    }
+    border: none;
+    padding: 0px;
+    margin-left: 0px;
+    margin-right: 0px;
+
+    > input[type=radio] {
+        opacity: 0;
+        position: absolute;
+        &:focus + label {
+            outline: auto;
+        }
+    }
+    > label {
+        cursor: pointer;
+        border: 1px solid $content-color-40;
+        transition: background-color 200ms;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        padding: 6px;
+        padding-bottom: 2px;
+        margin-bottom: 0;
+        border-top: none;
+        :not(.undecorated) {
+            text-indent: 0;
+        }
+        > .text {
+            width: 100%;
+            margin-left: 10px;
+        }
+        > .unchecked {
+            margin-right: 0;
+        }
+        > .check {
+            display: none;
+        }
+    }
+    > label:first-of-type {
+        border-top: 1px solid $content-color-40;
+    }
+    > label:last-child::after {
+        content: none;
+    }
+    > div {
+        border: 1px solid $content-color-40;
+        border-top: none;
+        display: none;
+        padding: 10px;
+
+    }
+    > input[type=radio]:checked + label {
+        background-color: $content-color-20;
+        transition: background-color 200ms;
+        > .unchecked {
+            display: none;
+        }
+        > .check {
+            display: inline-block;
+        }
+    }
+    > input[type=radio]:checked + label + div {
+        display: block;
+        .description {
+            animation-duration: 400ms;
+            animation-name: terms_of_use_fadein;
+        }
+    }
+}
\ No newline at end of file
diff --git a/resources/assets/stylesheets/studip.scss b/resources/assets/stylesheets/studip.scss
index 804a4224982..4cbfbd3c109 100644
--- a/resources/assets/stylesheets/studip.scss
+++ b/resources/assets/stylesheets/studip.scss
@@ -80,6 +80,10 @@
 @import "scss/responsive";
 @import "scss/resources";
 @import "scss/sidebar";
+@import "scss/tooltip";
+@import "scss/table_of_contents";
+@import "scss/wiki";
+@import "scss/wizard";
 @import "scss/select";
 @import "scss/selects";
 @import "scss/search";
@@ -92,14 +96,11 @@
 @import "scss/studygroup";
 @import "scss/studip-overlay";
 @import "scss/studip-selection";
-@import "scss/table_of_contents";
 @import "scss/tabs";
-@import "scss/tooltip";
 @import "scss/tfa";
 @import "scss/tour";
 @import "scss/typography";
 @import "scss/user-administration";
-@import "scss/wiki";
 @import "scss/multi_person_search";
 
 
diff --git a/resources/vue/components/StudipDialog.vue b/resources/vue/components/StudipDialog.vue
index 603b8b04942..8d885181227 100644
--- a/resources/vue/components/StudipDialog.vue
+++ b/resources/vue/components/StudipDialog.vue
@@ -61,28 +61,37 @@
                                     <div v-if="alert">{{ alert }}</div>
                                 </section>
                                 <footer class="studip-dialog-footer" ref="footer">
-                                    <button
-                                        v-if="buttonA"
-                                        :title="buttonA.text"
-                                        :class="[buttonA.class]"
-                                        class="button"
-                                        type="button"
-                                        @click="confirmDialog"
-                                    >
-                                        {{ buttonA.text }}
-                                    </button>
-                                    <slot name="dialogButtons"></slot>
-                                    <button
-                                        v-if="buttonB"
-                                        :title="buttonB.text"
-                                        :class="[buttonB.class]"
-                                        class="button"
-                                        type="button"
-                                        ref="buttonB"
-                                        @click="closeDialog"
-                                    >
-                                        {{ buttonB.text }}
-                                    </button>
+                                    <div class="studip-dialog-footer-buttonset-left">
+                                        <slot name="dialogButtonsBefore"></slot>
+                                    </div>
+                                    <div class="studip-dialog-footer-buttonset-center">
+                                        <button
+                                            v-if="buttonA"
+                                            :title="buttonA.text"
+                                            :class="[buttonA.class]"
+                                            :disabled="buttonA.disabled"
+                                            class="button"
+                                            type="button"
+                                            @click="confirmDialog"
+                                        >
+                                            {{ buttonA.text }}
+                                        </button>
+                                        <slot name="dialogButtons"></slot>
+                                        <button
+                                            v-if="buttonB"
+                                            :title="buttonB.text"
+                                            :class="[buttonB.class]"
+                                            class="button"
+                                            type="button"
+                                            ref="buttonB"
+                                            @click="closeDialog"
+                                        >
+                                            {{ buttonB.text }}
+                                        </button>
+                                    </div>
+                                    <div class="studip-dialog-footer-buttonset-right">
+                                        <slot name="dialogButtonsAfter"></slot>
+                                    </div>
                                 </footer>
                             </div>
                         </vue-resizeable>
@@ -106,11 +115,25 @@ export default {
         VueResizeable,
     },
     props: {
-        height: {type: String, default: '300'},
-        width: {type: String, default: '450'},
+        height: {
+            type: String,
+            default: '300'
+        },
+        width: {
+            type: String,
+            default: '450'
+        },
         title: String,
         confirmText: String,
         closeText: String,
+        confirmShow: {
+            type: Boolean,
+            default: true
+        },
+        confirmDisabled: {
+            type: Boolean,
+            default: false
+        },
         confirmClass: String,
         closeClass: String,
         question: String,
@@ -148,10 +171,11 @@ export default {
                 button.text = this.$gettext('Ja');
                 button.class = 'accept';
             }
-            if (this.confirmText) {
+            if (this.confirmText && this.confirmShow) {
                 button = {};
                 button.text = this.confirmText;
                 button.class = this.confirmClass;
+                button.disabled = this.confirmDisabled
             }
 
             return button;
diff --git a/resources/vue/components/StudipWizardDialog.vue b/resources/vue/components/StudipWizardDialog.vue
new file mode 100644
index 00000000000..7f73c1e160b
--- /dev/null
+++ b/resources/vue/components/StudipWizardDialog.vue
@@ -0,0 +1,254 @@
+<template>
+    <studip-dialog
+        :height="height"
+        :width="width"
+        :title="title"
+        :confirmText="confirmText"
+        :confirmClass="confirmClass"
+        :confirmDisabled="!showConfirm"
+        :closeText="closeText"
+        :closeClass="closeClass"
+        @close="$emit('close')"
+        @confirm="confirm"
+    >
+        <template v-slot:dialogContent>
+            <div class="wizard-wrapper">
+                <div class="wizard-meta">
+                    <studip-icon :shape="activeSlot.icon" :size="96"/>
+                    <p class="wizard-description">
+                        {{ activeSlot.description }}
+                    </p>
+                    <p v-if="requirements.length > 0" class="wizard-requirements">
+                        <span>{{ $gettext('Bitte geben Sie die folgenden Informationen an:') }}</span>
+                        <ul>
+                            <li v-for="(requirement, index) in requirements" :key="requirement.slot.name + '_' + index">
+                                <button @click="selectSlot(requirement.slot.id)">
+                                    <studip-icon
+                                        :shape="requirement.slot.icon"
+                                        :size="16"
+                                        role="clickable"
+                                    />{{ requirement.text }}
+                                </button>
+                            </li>
+                        </ul>
+                    </p>
+                </div>
+                <div class="wizard-content-wrapper">
+                    <h2>
+                        {{ activeSlot.title }}<span v-if="activeSlotRequiered" aria-hidden="true" class="required">*</span>
+                    </h2>
+                    <ul class="wizard-progress">
+                        <li
+                            v-for="progress in slots"
+                            :key="progress.id"
+                            :class="[
+                                isValid(progress.id) ? 'valid' : 'invalid',
+                                activeId === progress.id ? 'active' : 'inactive',
+                                isOptional(progress.id) ? 'optional' : ''
+                            ]"
+                        >
+                            <button
+                                ref="tabs"
+                                :title="progress.title"
+                                role="tab"
+                                :aria-selected="activeId === progress.id"
+                                :aria-controls="progress.name"
+                                :tabindex="0"
+                                @click="selectSlot(progress.id)"
+                                @keydown.right="nextContent"
+                                @keydown.left="prevContent"
+                            >
+                                <studip-icon
+                                    :shape="progress.icon"
+                                    :size="24"
+                                    :role="isValid(progress.id) ? 'info_alt' : 'clickable'"
+                                />
+                            </button>
+                        </li>
+                    </ul>
+                    <ul class="wizard-list">
+                        <li v-for="slot in slots" :key="slot.id">
+                            <div
+                                v-show="slot.id === activeId"
+                                class="wizard-item"
+                                role="tabpanel"
+                                :aria-labelledby="slot.name"
+                            >
+                                <div class="wizard-content">
+                                    <slot :name="slot.name" ></slot>
+                                </div>
+                            </div>
+                        </li>
+                    </ul>
+                </div>
+            </div>
+        </template>
+        <template v-slot:dialogButtonsBefore>
+            <button :style="{visibility: hasPrevContent ? 'visible' : 'hidden'}" class="button arr_left" @click="prevContent">
+                {{ $gettext('zurück') }}
+            </button>
+        </template>
+        <template v-slot:dialogButtonsAfter>
+            <button :style="{visibility: hasNextContent ? 'visible' : 'hidden'}" class="button arr_right" @click="nextContent">
+                {{ $gettext('weiter') }}
+            </button>
+        </template>
+    </studip-dialog>
+</template>
+
+<script>
+import StudipDialog from './StudipDialog.vue'
+import StudipIcon from './StudipIcon.vue';
+export default {
+    name: 'studip-wizard-dialog',
+    components: {
+        StudipDialog,
+        StudipIcon
+    },
+    props: {
+        title: {
+            type: String
+        },
+        confirmText: {
+            type: String
+        },
+        closeText: {
+            type: String
+        },
+        confirmClass: {
+            type: String,
+            default: 'accept'
+        },
+        closeClass: {
+            type: String,
+            default: 'cancel'
+        },
+        height: {
+            type: String,
+            default: '640'
+        },
+        width: {
+            type: String,
+            default: '880'
+        },
+        slots: {
+            type: Array,
+            required: true
+        },
+        lastRequiredSlotId: {
+            type: Number
+        },
+        requirements: {
+            type: Array,
+            default: () => []
+        }
+    },
+    data() {
+        return {
+            activeId: 1,
+            visitedIds: [1]
+        }
+    },
+    computed: {
+        hasPrevContent() {
+            if (this.activeId === 1) {
+                return false;
+            }
+
+            return true;
+        },
+        hasNextContent() {
+            if (this.activeId === this.slots.length) {
+                return false;
+            }
+
+            return true;
+        },
+        showConfirm() {
+            let valid = true;
+            if (this.lastRequiredSlotId !== undefined) {
+                this.slots.every(slot => {
+                    if (slot.id > this.lastRequiredSlotId) {
+                        return false;
+                    }
+                    if (!slot.valid) {
+                        valid = false;
+                    }
+
+                    return true;
+                });
+
+                return valid;
+            }
+
+            this.slots.forEach( slot => {
+                if (!slot.valid) {
+                    valid = false;
+                }
+            });
+
+            return valid;
+        },
+        activeSlot() {
+            return this.slots.filter(slot => this.activeId === slot.id)[0];
+        },
+        activeSlotRequiered() {
+            if (this.lastRequiredSlotId === undefined) {
+                return false;
+            }
+
+            return this.lastRequiredSlotId >= this.activeSlot.id;
+        },
+    },
+    methods: {
+        prevContent() {
+            if (!this.hasPrevContent) {
+                return;
+            } else {
+                this.activeId = this.activeId - 1;
+                this.$nextTick(() => {
+                    this.$refs.tabs[this.activeId - 1].focus();
+                });
+            }
+        },
+        nextContent() {
+            if (!this.hasNextContent) {
+                return;
+            } else {
+                this.activeId = this.activeId + 1;
+                this.$nextTick(() => {
+                    this.$refs.tabs[this.activeId - 1].focus();
+                });
+            }
+        },
+        selectSlot(id) {
+            this.activeId = id;
+        },
+        isValid(id) {
+            const slot = this.slots.find( slot => slot.id === id);
+            if (slot) {
+                return slot.valid && this.visitedIds.indexOf(id) !== -1;
+            }
+
+            return false;
+        },
+        isOptional(id) {
+            if (this.lastRequiredSlotId === undefined) {
+                return false;
+            }
+
+            return this.lastRequiredSlotId < id;
+        },
+        confirm() {
+            this.$emit('confirm');
+        }
+    },
+    watch: {
+        activeId(newVal) {
+            if (this.visitedIds.indexOf(newVal) === -1) {
+                this.visitedIds.push(newVal);
+            }
+        }
+    }
+}
+</script>
diff --git a/resources/vue/components/courseware/ActivitiesApp.vue b/resources/vue/components/courseware/ActivitiesApp.vue
new file mode 100644
index 00000000000..7bf8e238c94
--- /dev/null
+++ b/resources/vue/components/courseware/ActivitiesApp.vue
@@ -0,0 +1,31 @@
+<template>
+    <div class="cw-activities-wrapper">
+        <courseware-activities />
+        <MountingPortal mountTo="#courseware-activities-widget-filter-type" name="sidebar-filter-type">
+            <courseware-activities-widget-filter-type />
+        </MountingPortal>
+        <MountingPortal mountTo="#courseware-activities-widget-filter-unit" name="sidebar-filter-unit">
+            <courseware-activities-widget-filter-unit />
+        </MountingPortal>
+    </div>
+</template>
+
+<script>
+import CoursewareActivities from './CoursewareActivities.vue';
+import CoursewareActivitiesWidgetFilterType from './CoursewareActivitiesWidgetFilterType.vue';
+import CoursewareActivitiesWidgetFilterUnit from './CoursewareActivitiesWidgetFilterUnit.vue';
+import { mapGetters } from 'vuex';
+
+export default {
+    components: {
+        CoursewareActivities,
+        CoursewareActivitiesWidgetFilterType,
+        CoursewareActivitiesWidgetFilterUnit
+    },
+    computed: {
+        ...mapGetters({
+            userIsTeacher: 'userIsTeacher',
+        }),
+    },
+}
+</script>
\ No newline at end of file
diff --git a/resources/vue/components/courseware/AdminApp.vue b/resources/vue/components/courseware/AdminApp.vue
index 01bde3867f9..d210558b18b 100644
--- a/resources/vue/components/courseware/AdminApp.vue
+++ b/resources/vue/components/courseware/AdminApp.vue
@@ -15,6 +15,8 @@
 import CoursewareAdminActionWidget from './CoursewareAdminActionWidget.vue';
 import CoursewareAdminTemplates from './CoursewareAdminTemplates.vue';
 import CoursewareAdminViewWidget from './CoursewareAdminViewWidget.vue';
+import { mapGetters, mapActions } from 'vuex';
+
 export default {
     components: {
         CoursewareAdminActionWidget,
@@ -22,9 +24,9 @@ export default {
         CoursewareAdminViewWidget
     },
     computed: {
-        adminViewMode() {
-            return this.$store.getters.adminViewMode;
-        },
+        ...mapGetters({
+            adminViewMode: 'adminViewMode'
+        }),
         templatesView() {
             return this.adminViewMode === 'templates';
         },
diff --git a/resources/vue/components/courseware/ContentOverviewApp.vue b/resources/vue/components/courseware/ContentOverviewApp.vue
deleted file mode 100644
index 831a2e76cad..00000000000
--- a/resources/vue/components/courseware/ContentOverviewApp.vue
+++ /dev/null
@@ -1,25 +0,0 @@
-<template>
-  <div class="cw-content-overview">
-        <courseware-content-overview-elements />
-        <MountingPortal mountTo="#courseware-content-overview-action-widget" name="sidebar-actions">
-            <courseware-content-overview-action-widget />
-        </MountingPortal>
-        <MountingPortal mountTo="#courseware-content-overview-filter-widget" name="sidebar-filters">
-            <courseware-content-overview-filter-widget />
-        </MountingPortal>
-  </div>
-</template>
-
-<script>
-import CoursewareContentOverviewElements from './CoursewareContentOverviewElements.vue';
-import CoursewareContentOverviewActionWidget from './CoursewareContentOverviewActionWidget.vue';
-import CoursewareContentOverviewFilterWidget from './CoursewareContentOverviewFilterWidget.vue';
-
-export default {
-    components: {
-        CoursewareContentOverviewElements,
-        CoursewareContentOverviewActionWidget,
-        CoursewareContentOverviewFilterWidget
-    }
-}
-</script>
diff --git a/resources/vue/components/courseware/CoursewareActionWidget.vue b/resources/vue/components/courseware/CoursewareActionWidget.vue
index 8dbb7e48c91..172d2ae5192 100644
--- a/resources/vue/components/courseware/CoursewareActionWidget.vue
+++ b/resources/vue/components/courseware/CoursewareActionWidget.vue
@@ -2,54 +2,14 @@
     <sidebar-widget :title="$gettext('Aktionen')" v-if="structuralElement">
         <template #content>
             <ul class="widget-list widget-links cw-action-widget">
-                <li class="cw-action-widget-show-toc">
-                    <button @click="toggleTOC">
-                        {{ tocText }}
-                    </button>
-                </li>
-                <li class="cw-action-widget-show-consume-mode">
-                    <button @click="showConsumeMode">
-                        {{ $gettext('Fokusmodus einschalten') }}
-                    </button>
-                </li>
-                <li v-if="canEdit && !blockedByAnotherUser" class="cw-action-widget-edit">
-                    <button @click="editElement">
-                        {{ $gettext('Seite bearbeiten') }}
-                    </button>
-                </li>
-                <li v-if="canEdit && blockedByAnotherUser && userIsTeacher" class="cw-action-widget-remove-lock">
-                    <button @click="removeElementLock">
-                        {{ $gettext('Sperre aufheben') }}
-                    </button>
-                </li>
                 <li v-if="canEdit" class="cw-action-widget-add">
                     <button @click="addElement">
                         {{ $gettext('Seite hinzufügen') }}
                     </button>
                 </li>
-                <li class="cw-action-widget-info">
-                    <button @click="showElementInfo">
-                        {{ $gettext('Informationen anzeigen') }}
-                    </button>
-                </li>
-                <li class="cw-action-widget-star">
-                    <button @click="createBookmark">
-                        {{ $gettext('Lesezeichen setzen') }}
-                    </button>
-                </li>
-                <li v-if="context.type === 'users'" class="cw-action-widget-link">
+                <li v-if="inCourseContext && userIsTeacher" class="cw-action-widget-link">
                     <button @click="linkElement">
-                        {{ $gettext('Öffentlichen Link erzeugen') }}
-                    </button>
-                </li>
-                <li v-if="!isOwner" class="cw-action-widget-oer">
-                    <button @click="suggestOER">
-                        <translate>Material für den OER Campus vorschlagen</translate>
-                    </button>
-                </li>
-                <li v-if="!isRoot && canEdit && !blockedByAnotherUser" class="cw-action-widget-trash">
-                    <button @click="deleteElement">
-                        {{ $gettext('Seite löschen') }}
+                        {{ $gettext('Seite verknüpfen') }}
                     </button>
                 </li>
             </ul>
@@ -59,36 +19,19 @@
 
 <script>
 import SidebarWidget from '../SidebarWidget.vue';
-import CoursewareExport from '@/vue/mixins/courseware/export.js';
 import { mapActions, mapGetters } from 'vuex';
 
 export default {
     name: 'courseware-action-widget',
-    props: ['structuralElement', 'canVisit'],
+    props: ['structuralElement'],
     components: {
         SidebarWidget,
     },
-    mixins: [CoursewareExport],
     computed: {
         ...mapGetters({
-            userId: 'userId',
-            userIsTeacher: 'userIsTeacher',
-            consumeMode: 'consumeMode',
-            showToolbar: 'showToolbar',
             context: 'context',
-
-            blocked: 'currentElementBlocked',
-            blockerId: 'currentElementBlockerId',
-            blockedByThisUser: 'currentElementBlockedByThisUser',
-            blockedByAnotherUser: 'currentElementBlockedByAnotherUser',
+            userIsTeacher: 'userIsTeacher',
         }),
-        isRoot() {
-            if (!this.structuralElement) {
-                return true;
-            }
-
-            return this.structuralElement.relationships.parent.data === null;
-        },
         canEdit() {
             if (!this.structuralElement) {
                 return false;
@@ -98,96 +41,21 @@ export default {
         currentId() {
             return this.structuralElement?.id;
         },
-        tocText() {
-            return this.showToolbar ? this.$gettext('Inhaltsverzeichnis ausblenden') : this.$gettext('Inhaltsverzeichnis anzeigen');
-        },
-        isTask() {
-            return this.structuralElement?.relationships.task.data !== null;
-        },
-        isOwner() {
-            return this.structuralElement.relationships.owner.data.id === this.userId;
+        inCourseContext() {
+            return this.context.type === 'courses';
         }
     },
     methods: {
         ...mapActions({
-            showElementEditDialog: 'showElementEditDialog',
             showElementAddDialog: 'showElementAddDialog',
-            showElementDeleteDialog: 'showElementDeleteDialog',
-            showElementInfoDialog: 'showElementInfoDialog',
             showElementLinkDialog: 'showElementLinkDialog',
-            showElementRemoveLockDialog: 'showElementRemoveLockDialog',
-            updateShowSuggestOerDialog: 'updateShowSuggestOerDialog',
-            companionInfo: 'companionInfo',
-            addBookmark: 'addBookmark',
-            lockObject: 'lockObject',
-            setConsumeMode: 'coursewareConsumeMode',
-            setViewMode: 'coursewareViewMode',
-            setShowToolbar: 'coursewareShowToolbar',
-            setSelectedToolbarItem: 'coursewareSelectedToolbarItem',
-            loadStructuralElement: 'loadStructuralElement',
         }),
-        async editElement() {
-            await this.loadStructuralElement(this.currentId);
-            if (this.blockedByAnotherUser) {
-                this.companionInfo({ info: this.$gettext('Diese Seite wird bereits bearbeitet.') });
-
-                return false;
-            }
-            try {
-                await this.lockObject({ id: this.currentId, type: 'courseware-structural-elements' });
-            } catch(error) {
-                if (error.status === 409) {
-                    this.companionInfo({ info: this.$gettext('Diese Seite wird bereits bearbeitet.') });
-                } else {
-                    console.log(error);
-                }
-
-                return false;
-            }
-            this.showElementEditDialog(true);
-        },
-        async removeElementLock() {
-            this.showElementRemoveLockDialog(true);
-        },
-        async deleteElement() {
-            await this.loadStructuralElement(this.currentId);
-            if (this.blockedByAnotherUser) {
-                this.companionInfo({
-                    info: this.$gettextInterpolate(
-                        this.$gettext('Löschen nicht möglich, da %{blockingUserName} die Seite bearbeitet.'),
-                        {blockingUserName: this.blockingUserName}
-                    )
-                });
-
-                return false;
-            }
-            await this.lockObject({ id: this.currentId, type: 'courseware-structural-elements' });
-            this.showElementDeleteDialog(true);
-        },
         addElement() {
             this.showElementAddDialog(true);
         },
-        showElementInfo() {
-            this.showElementInfoDialog(true);
-        },
-        createBookmark() {
-            this.addBookmark(this.structuralElement);
-            this.companionInfo({ info: this.$gettext('Das Lesezeichen wurde gesetzt.') });
-        },
-        toggleTOC() {
-            this.setShowToolbar(!this.showToolbar);
-        },
-        showConsumeMode() {
-            this.setViewMode('read');
-            this.setSelectedToolbarItem('contents');
-            this.setConsumeMode(true);
-        },
-        suggestOER() {
-            this.updateShowSuggestOerDialog(true);
-        },
         linkElement() {
             this.showElementLinkDialog(true);
-        }
+        },
     },
 };
 </script>
diff --git a/resources/vue/components/courseware/CoursewareActivities.vue b/resources/vue/components/courseware/CoursewareActivities.vue
new file mode 100644
index 00000000000..aab705f4ee9
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareActivities.vue
@@ -0,0 +1,124 @@
+<template>
+    <section class="contentbox">
+        <header><h1>{{ $gettext('Aktivitäten') }}</h1></header>
+        <section>
+            <studip-progress-indicator
+                v-show="loading"
+                :description="$gettext('Lade Aktivitäten…')"
+            />
+            <courseware-companion-box
+                v-if="filteredActivitiesList.length === 0 && !loading"
+                mood="sad"
+                :msgCompanion="$gettext('Es wurden keine Aktivitäten gefunden.')"
+            />
+            <ul class="cw-activities">
+                <courseware-activity-item v-for="(item, index) in filteredActivitiesList" :key="index" :item="item" />
+            </ul>
+        </section>
+    </section>
+</template>
+
+<script>
+import CoursewareActivityItem from './CoursewareActivityItem.vue';
+import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
+import StudipProgressIndicator from '../StudipProgressIndicator.vue';
+
+import { mapActions, mapGetters } from 'vuex';
+
+export default {
+    name: 'courseware-activities',
+    components: {
+        CoursewareActivityItem,
+        CoursewareCompanionBox,
+        StudipProgressIndicator,
+    },
+    data() {
+        return {
+            activitiesList: [],
+            loading: false
+        }
+    },
+    computed: {
+        ...mapGetters({
+            userId: 'userId',
+            getUserById: 'users/byId',
+            context: 'context',
+            getStructuralElementById: 'courseware-structural-elements/byId',
+            getCoursewareUnitById: 'courseware-units/byId',
+            coursewareUnits: 'courseware-units/all',
+
+            typeFilter: 'typeFilter',
+            unitFilter: 'unitFilter'
+        }),
+        filteredActivitiesList() {
+            let list = this.activitiesList.slice().sort((a,b) => b.timestamp - a.timestamp);
+            if (['edited', 'created', 'answered', 'interacted', 'voided',].includes(this.typeFilter)) {
+                list = list.filter(activity => activity.type === this.typeFilter);
+            }
+            if (this.unitFilter !== 'all') {
+                list = list.filter(activity => activity.unitId === this.unitFilter);
+            }
+
+            return list;
+        },
+    },
+    mounted() {
+        this.getActivities();
+    },
+    methods: {
+        ...mapActions({
+            loadCoursewareActivities: 'loadCoursewareActivities',
+            loadStructuralElementById: 'courseware-structural-elements/loadById',
+        }),
+
+        async loadActivitiesElements(activities) {
+            const results = [];
+            for (const activity of activities) {
+                const structuralElementId = activity.relationships.object.meta["object-id"];
+                results.push(this.loadStructuralElementById({id: structuralElementId, options: { include: 'ancestors'} }));
+            }
+            // activity might contain structural element hidden for current user
+            return Promise.all(results).catch(e => { if (e.status !== 403) { console.error(e); } });
+        },
+
+        async getActivities() {
+            this.loading = true;
+            let activities = await this.loadCoursewareActivities({ userId: this.userId, courseId: this.context.id});
+            this.activitiesList = [];
+
+            await this.loadActivitiesElements(activities);
+
+            for (const activity of activities) {
+                let error = false;
+                let username = this.getUserById({ id: activity.relationships.actor.data.id }).attributes['formatted-name'];
+                const date = new Date(activity.attributes.mkdate);
+                const structuralElementId = activity.relationships.object.meta["object-id"];
+
+                const activityStructuralElement = this.getStructuralElementById({ id: structuralElementId });
+                if (activityStructuralElement === undefined || !activityStructuralElement.attributes['can-visit']) {
+                    error = true;
+                }
+                if (!error) {
+                    const unitId = activityStructuralElement.relationships?.unit?.data?.id ?? null;
+                    const unit = this.getCoursewareUnitById({id: unitId });
+                    let options = { year: 'numeric', month: '2-digit', day: '2-digit' };
+                    let data = {
+                        username: username,
+                        timestamp: date.getTime(),
+                        readableDate: date.toLocaleString('de-DE', options),
+                        type: activity.attributes.verb,
+                        title: activity.attributes.title,
+                        elementId: structuralElementId,
+                        unitId: unitId,
+                        unit: unit,
+                        contextId: activity.relationships.context.data.id,
+                        content: activity.attributes.content
+                    }
+                    this.activitiesList.push(data);
+                }
+            }
+            this.loading = false;
+        }
+    }
+};
+</script>
diff --git a/resources/vue/components/courseware/CoursewareActivitiesWidgetFilterType.vue b/resources/vue/components/courseware/CoursewareActivitiesWidgetFilterType.vue
new file mode 100644
index 00000000000..0c57271b644
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareActivitiesWidgetFilterType.vue
@@ -0,0 +1,61 @@
+<template>
+    <sidebar-widget :title="$gettext('Aktivitäten')">
+        <template #content>
+            <div class="cw-filter-widget">
+                <form class="default" @submit.prevent="">
+                    <select v-model="typeFilter">
+                        <option value="all">
+                            {{ $gettext('Alle') }}
+                        </option>
+                        <option value="edited">
+                            {{ $gettext('Bearbeitet') }}
+                        </option>
+                        <option value="created">
+                            {{ $gettext('Erstellt') }}
+                        </option>
+                        <option value="answered">
+                            {{ $gettext('Feedback') }}
+                        </option>
+                        <option value="interacted">
+                            {{ $gettext('Kommentiert') }}
+                        </option>
+                        <option value="voided">
+                            {{ $gettext('Gelöscht') }}
+                        </option>
+                    </select>
+                </form>
+            </div>
+        </template>
+    </sidebar-widget>
+</template>
+
+<script>
+import SidebarWidget from '../SidebarWidget.vue';
+
+import { mapActions } from 'vuex';
+
+export default {
+    name: 'courseware-activities-widget-filter-type',
+    components: {
+        SidebarWidget
+    },
+    data() {
+        return {
+            typeFilter: 'all',
+        };
+    },
+    methods: {
+        ...mapActions({
+            setTypeFilter: 'setTypeFilter',
+        }),
+        filterType() {
+            this.setTypeFilter(this.typeFilter);
+        },
+    },
+    watch: {
+        typeFilter() {
+            this.filterType();
+        },
+    }
+}
+</script>
diff --git a/resources/vue/components/courseware/CoursewareActivitiesWidgetFilterUnit.vue b/resources/vue/components/courseware/CoursewareActivitiesWidgetFilterUnit.vue
new file mode 100644
index 00000000000..d0142ea0077
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareActivitiesWidgetFilterUnit.vue
@@ -0,0 +1,58 @@
+<template>
+    <sidebar-widget :title="$gettext('Lernmaterial')">
+        <template #content>
+            <div class="cw-filter-widget">
+                <form class="default" @submit.prevent="">
+                    <select v-model="unitFilter">
+                        <option value="all">
+                            {{ $gettext('Alle') }}
+                        </option>
+                        <option v-for="unit in coursewareUnits" :key="unit.id" :value="unit.id">
+                                {{ getUnitTitle(unit) }}
+                        </option>
+                    </select>
+                </form>
+            </div>
+        </template>
+    </sidebar-widget>
+</template>
+
+<script>
+import SidebarWidget from '../SidebarWidget.vue';
+
+import { mapActions, mapGetters } from 'vuex';
+
+export default {
+    name: 'courseware-activities-widget-filter-unit',
+    components: {
+        SidebarWidget
+    },
+    data() {
+        return {
+            unitFilter: 'all'
+        };
+    },
+    computed: {
+        ...mapGetters({
+            getStructuralElementById: 'courseware-structural-elements/byId',
+            coursewareUnits: 'courseware-units/all',
+        }),
+    },
+    methods: {
+        ...mapActions({
+            setUnitFilter: 'setUnitFilter',
+        }),
+        filterUnit() {
+            this.setUnitFilter(this.unitFilter);
+        },
+        getUnitTitle(unit) {
+            return this.getStructuralElementById({id: unit.relationships['structural-element'].data.id }).attributes.title;
+        }
+    },
+    watch: {
+        unitFilter() {
+            this.filterUnit();
+        }
+    }
+}
+</script>
diff --git a/resources/vue/components/courseware/CoursewareActivityItem.vue b/resources/vue/components/courseware/CoursewareActivityItem.vue
index 8a7a4a9cb67..9520d36047f 100644
--- a/resources/vue/components/courseware/CoursewareActivityItem.vue
+++ b/resources/vue/components/courseware/CoursewareActivityItem.vue
@@ -3,14 +3,14 @@
         <p v-if="item.username" class="cw-activity-item-user">
             <a :href="userUrl"><studip-icon role="inactive" shape="headache" />{{ item.username }}</a>
         </p>
-        <p v-if="item.date" class="cw-activity-item-date">
-            <studip-icon role="inactive" shape="timetable" />{{ item.date }}
+        <p v-if="item.readableDate" class="cw-activity-item-date">
+            <studip-icon role="inactive" shape="timetable" />{{ item.readableDate }}
         </p>
         <p class="cw-activity-item-element">
-            <a :href="linkUrl" :title="item.complete_breadcrumb"><studip-icon role="inactive" :shape="shape" />{{ item.element_breadcrumb }}</a>
+            <a :href="linkUrl" :title="elementTitle"><studip-icon role="inactive" shape="content2" />{{ unitTitle }} | {{ breadcrumb }}</a>
         </p>
-        <p v-if="text" class="cw-activity-item-text">
-            <span v-html="text"></span>
+        <p v-if="content" class="cw-activity-item-content">
+            <studip-icon role="inactive" :shape="shape" /><span v-html="content"></span>
         </p>
     </li>
 </template>
@@ -18,6 +18,8 @@
 <script>
 import StudipIcon from './../StudipIcon.vue';
 
+import { mapGetters } from 'vuex';
+
 export default {
     name: 'courseware-activity-item',
     components: {
@@ -27,27 +29,22 @@ export default {
         item: Object,
     },
     computed: {
-        text() {
+        ...mapGetters({
+            context: 'context',
+            getStructuralElementById: 'courseware-structural-elements/byId',
+        }),
+        content() {
             if (this.item.content == null || this.item.content == '') {
-                return this.item.text;
+                return this.item.title;
             }
 
-            switch (this.item.type) {
-                case 'interacted':
-                    return this.item.username + ' commented: ' + this.item.content; //TODO: Localization
-                case 'answered':
-                    return this.item.username + ' added feedback: ' + this.item.content; //TODO: Localization
-                default:
-                    return this.item.text;
-            }
+            return this.item.content;
         },
-
         userUrl() {
             return STUDIP.URLHelper.base_url + 'dispatch.php/profile?username=' + this.item.username;
         },
-
         linkUrl() {
-            return STUDIP.URLHelper.base_url + 'dispatch.php/course/courseware/?cid=' + this.item.context_id + '#/structural_element/' + this.item.element_id;
+            return STUDIP.URLHelper.base_url + 'dispatch.php/course/courseware/courseware/' + this.item.unitId + '?cid=' + this.item.contextId + '#/structural_element/' + this.item.elementId;
         },
         shape() {
             switch (this.item.type) {
@@ -63,6 +60,39 @@ export default {
                     return 'question-circle-full';
             }
         },
+        breadcrumb() {
+            let breadcrumb = this.element.attributes.title;
+            let currentStructuralElement = this.element;
+            let i = 1; //max breadcrumb navigation depth check
+            while (currentStructuralElement.relationships.parent.data !== null) {
+                    let parentId = currentStructuralElement.relationships.parent.data.id;
+                    currentStructuralElement = this.getStructuralElementById({ id: parentId });
+                    if (currentStructuralElement === undefined) {
+                        break;
+                    }
+                    if (++i <= 3) {
+                        breadcrumb = currentStructuralElement.attributes.title + '/' + breadcrumb;
+                        if (currentStructuralElement.relationships.parent.data !== null && i === 3) {
+                            breadcrumb = '.../' + breadcrumb;
+                        }
+                    }
+                }
+
+            return breadcrumb;
+        },
+        element() {
+            return this.getStructuralElementById({ id: this.item.elementId });
+        },
+        elementTitle() {
+            return this.element?.attributes?.title ?? this.$gettext('unbekannt');
+        },
+        unitTitle() {
+            if (this.item.unit) {
+                return this.getStructuralElementById({id: this.item.unit.relationships['structural-element'].data.id }).attributes.title;
+            }
+
+            return '-';
+        }
     },
 };
 </script>
diff --git a/resources/vue/components/courseware/CoursewareAdminActionWidget.vue b/resources/vue/components/courseware/CoursewareAdminActionWidget.vue
index 9a8ce6128b1..6c02699650e 100644
--- a/resources/vue/components/courseware/CoursewareAdminActionWidget.vue
+++ b/resources/vue/components/courseware/CoursewareAdminActionWidget.vue
@@ -9,23 +9,25 @@
 </template>
 
 <script>
+import { mapGetters, mapActions } from 'vuex';
 
 export default {
     name: 'courseware-admin-action-widget',
     computed: {
-        adminViewMode() {
-            return this.$store.getters.adminViewMode;
-        },
+        ...mapGetters({
+            adminViewMode: 'adminViewMode',
+            showAddTemplateDialog: 'showAddTemplateDialog'
+        }),
         templatesView() {
             return this.adminViewMode === 'templates';
-        },
-        showAddTemplateDialog() {
-            return this.$store.getters.showAddTemplateDialog;
-        },
+        }
     },
     methods: {
+        ...mapActions({
+            setShowAddTemplateDialog: 'showAddTemplateDialog'
+        }),
         addTemplate() {
-            this.$store.dispatch('showAddTemplateDialog', true);
+            this.setShowAddTemplateDialog(true);
         }
     }
 }
diff --git a/resources/vue/components/courseware/CoursewareAdminTemplates.vue b/resources/vue/components/courseware/CoursewareAdminTemplates.vue
index b3f330c708f..a0b17e6a60c 100644
--- a/resources/vue/components/courseware/CoursewareAdminTemplates.vue
+++ b/resources/vue/components/courseware/CoursewareAdminTemplates.vue
@@ -151,10 +151,11 @@ export default {
         ...mapActions({
             createTemplate: 'courseware-templates/create',
             updateTemplate: 'courseware-templates/update',
-            deleteTemplate: 'courseware-templates/delete'
+            deleteTemplate: 'courseware-templates/delete',
+            setShowAddTemplateDialog: 'showAddTemplateDialog'
         }),
         closeAddDialog() {
-            this.$store.dispatch('showAddTemplateDialog', false);
+            this.setShowAddTemplateDialog(false);
             this.newTemplateName = '';
             this.newElementPurpose = '';
             this.importZip = null;
diff --git a/resources/vue/components/courseware/CoursewareAdminViewWidget.vue b/resources/vue/components/courseware/CoursewareAdminViewWidget.vue
index cc208f69d58..81a80715e37 100644
--- a/resources/vue/components/courseware/CoursewareAdminViewWidget.vue
+++ b/resources/vue/components/courseware/CoursewareAdminViewWidget.vue
@@ -9,22 +9,25 @@
 </template>
 
 <script>
+import { mapGetters, mapActions } from 'vuex';
 
 export default {
     name: 'courseware-admin-view-widget',
     computed: {
-        adminViewMode() {
-            return this.$store.getters.adminViewMode;
-        },
+        ...mapGetters({
+            adminViewMode: 'adminViewMode'
+        }),
         templatesView() {
             return this.adminViewMode === 'templates';
         },
     },
     methods: {
+        ...mapActions({
+            setAdminViewMode: 'adminViewMode'
+        }),
         setTemplatesView() {
-            this.$store.dispatch('adminViewMode', 'templates');
+            this.setAdminViewMode('templates');
         },
     }
-
 }
 </script>
diff --git a/resources/vue/components/courseware/CoursewareBlockAdderArea.vue b/resources/vue/components/courseware/CoursewareBlockAdderArea.vue
index 7bc25136e70..fc9b41f6a56 100644
--- a/resources/vue/components/courseware/CoursewareBlockAdderArea.vue
+++ b/resources/vue/components/courseware/CoursewareBlockAdderArea.vue
@@ -14,6 +14,8 @@
 
 <script>
 import StudipIcon from '../StudipIcon.vue';
+import { mapActions, mapGetters } from 'vuex';
+
 export default {
   components: { StudipIcon },
     name: 'courseware-block-adder-area',
@@ -27,23 +29,28 @@ export default {
         };
     },
     computed: {
+        ...mapGetters({
+            adderStorage: 'blockAdder',
+        }),
         adderDisable() {
-            return Object.keys(this.$store.getters.blockAdder).length !== 0 && !this.adderActive;
-        },
-        adderStorage() {
-            return this.$store.getters.blockAdder;
+            return Object.keys(this.adderStorage).length !== 0 && !this.adderActive;
         },
     },
     methods: {
+        ...mapActions({
+            coursewareBlockAdder: 'coursewareBlockAdder',
+            coursewareSelectedToolbarItem: 'coursewareSelectedToolbarItem',
+            coursewareShowToolbar: 'coursewareShowToolbar'
+        }),
         selectBlockAdder() {
             if (this.adderActive) {
                 this.adderActive = false;
-                this.$store.dispatch('coursewareBlockAdder', {});
+                this.coursewareBlockAdder({});
             } else {
                 this.adderActive = true;
-                this.$store.dispatch('coursewareBlockAdder', { container: this.container, section: this.section });
-                this.$store.dispatch('coursewareSelectedToolbarItem', 'blockadder');
-                this.$store.dispatch('coursewareShowToolbar', true);
+                this.coursewareBlockAdder({ container: this.container, section: this.section });
+                this.coursewareSelectedToolbarItem('blockadder');
+                this.coursewareShowToolbar(true);
             }
         },
     },
diff --git a/resources/vue/components/courseware/CoursewareBlockComments.vue b/resources/vue/components/courseware/CoursewareBlockComments.vue
index fb66627a07e..8e18909cc3d 100644
--- a/resources/vue/components/courseware/CoursewareBlockComments.vue
+++ b/resources/vue/components/courseware/CoursewareBlockComments.vue
@@ -18,7 +18,7 @@
 
 <script>
 import CoursewareTalkBubble from './CoursewareTalkBubble.vue';
-import { mapGetters } from 'vuex';
+import { mapActions, mapGetters } from 'vuex';
 
 export default {
     name: 'courseware-block-comments',
@@ -57,12 +57,16 @@ export default {
         }
     },
     methods: {
+        ...mapActions({
+            createComments: 'courseware-block-comments/create',
+            loadRelatedComments: 'courseware-block-comments/loadRelated'
+        }),
         async loadComments() {
             const parent = {
                 type: this.block.type,
                 id: this.block.id,
             };
-            await this.$store.dispatch('courseware-block-comments/loadRelated', {
+            await this.loadRelatedComments({
                 parent,
                 relationship: 'comments',
                 options: {
@@ -85,7 +89,7 @@ export default {
                 }
             };
 
-            await this.$store.dispatch('courseware-block-comments/create', data);
+            await this.createComments(data);
             this.loadComments();
             this.createComment = '';
         },
diff --git a/resources/vue/components/courseware/CoursewareBlockEdit.vue b/resources/vue/components/courseware/CoursewareBlockEdit.vue
index 55b8b3a1b9b..a218ab52c4e 100644
--- a/resources/vue/components/courseware/CoursewareBlockEdit.vue
+++ b/resources/vue/components/courseware/CoursewareBlockEdit.vue
@@ -14,6 +14,8 @@
 </template>
 
 <script>
+import { mapActions, mapGetters } from 'vuex';
+
 export default {
     name: 'courseware-block-edit',
     props: {
@@ -29,14 +31,17 @@ export default {
         this.originalBlock = this.block;
     },
     methods: {
+        ...mapActions({
+            coursewareBlockAdder: 'coursewareBlockAdder',
+            coursewareShowToolbar: 'coursewareShowToolbar'
+        }),
         deactivateToolbar() {
-            this.$store.dispatch('coursewareBlockAdder', {});
-            this.$store.dispatch('coursewareShowToolbar', false);
+            this.coursewareBlockAdder({});
+            this.coursewareShowToolbar(false);
         },
     },
     beforeDestroy() {
         if (this.exitHandler) {
-            console.log('autosave');
             this.$emit('store');
         }
     }
diff --git a/resources/vue/components/courseware/CoursewareBlockFeedback.vue b/resources/vue/components/courseware/CoursewareBlockFeedback.vue
index 312eeb9caa4..fe9e32bf1a6 100644
--- a/resources/vue/components/courseware/CoursewareBlockFeedback.vue
+++ b/resources/vue/components/courseware/CoursewareBlockFeedback.vue
@@ -28,7 +28,7 @@
 <script>
 import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
 import CoursewareTalkBubble from './CoursewareTalkBubble.vue';
-import { mapGetters } from 'vuex';
+import { mapActions, mapGetters } from 'vuex';
 
 
 export default {
@@ -68,6 +68,10 @@ export default {
         }
     },
     methods: {
+        ...mapActions({
+            createFeedback: 'courseware-block-feedback/create',
+            loadRelatedFeedback: 'courseware-block-feedback/loadRelated',
+        }),
         async postFeedback() {
             this.createFeedback({ blockId: this.block.id, feedback: this.feedbackText });
             this.feedbackText = '';
@@ -90,7 +94,7 @@ export default {
                 type: this.block.type,
                 id: this.block.id,
             };
-            await this.$store.dispatch('courseware-block-feedback/loadRelated', {
+            await this.loadRelatedFeedback({
                 parent,
                 relationship: 'feedback',
                 options: {
@@ -112,7 +116,7 @@ export default {
                     },
                 },
             };
-            await this.$store.dispatch('courseware-block-feedback/create', data, { root: true });
+            await this.createFeedback(data, { root: true });
             this.loadFeedback();
         }
     },
diff --git a/resources/vue/components/courseware/CoursewareBlockInfo.vue b/resources/vue/components/courseware/CoursewareBlockInfo.vue
index 1863c161225..5ef6e975d49 100644
--- a/resources/vue/components/courseware/CoursewareBlockInfo.vue
+++ b/resources/vue/components/courseware/CoursewareBlockInfo.vue
@@ -31,6 +31,7 @@
 
 <script>
 import IsoDate from './IsoDate.vue';
+import { mapActions, mapGetters } from 'vuex';
 
 export default {
     name: 'courseware-block-info',
@@ -39,8 +40,11 @@ export default {
         block: Object,
     },
     computed: {
+        ...mapGetters({
+            relatedUsers: 'users/related',
+        }),
         owner() {
-            const owner = this.$store.getters['users/related']({
+            const owner = this.relatedUsers({
                 parent: this.block,
                 relationship: 'owner',
             });
@@ -49,7 +53,7 @@ export default {
         },
 
         editor() {
-            const editor = this.$store.getters['users/related']({
+            const editor = this.relatedUsers({
                 parent: this.block,
                 relationship: 'editor',
             });
diff --git a/resources/vue/components/courseware/CoursewareBlockadderItem.vue b/resources/vue/components/courseware/CoursewareBlockadderItem.vue
index c882cd25696..93c31f424de 100644
--- a/resources/vue/components/courseware/CoursewareBlockadderItem.vue
+++ b/resources/vue/components/courseware/CoursewareBlockadderItem.vue
@@ -30,19 +30,20 @@ export default {
     computed: {
         ...mapGetters({
             blockAdder: 'blockAdder',
-            blockById: 'courseware-blocks/byId'
+            blockById: 'courseware-blocks/byId',
+            lastCreatedBlock: 'courseware-blocks/lastCreated',
         }),
     },
     methods: {
         ...mapActions({
-            createBlock: 'createBlockInContainer',
             companionInfo: 'companionInfo',
-            companionWarning: 'companionWarning',
             companionSuccess: 'companionSuccess',
-            updateContainer: 'updateContainer',
+            companionWarning: 'companionWarning',
+            createBlock: 'createBlockInContainer',
             lockObject: 'lockObject',
             unlockObject: 'unlockObject',
             loadBlock: 'courseware-blocks/loadById',
+            updateContainer: 'updateContainer',
         }),
         async addBlock() {
             if (Object.keys(this.blockAdder).length !== 0) {
@@ -55,7 +56,7 @@ export default {
                     blockType: this.type,
                 });
                 //get new Block
-                const newBlock = this.$store.getters['courseware-blocks/lastCreated'];
+                const newBlock = this.lastCreatedBlock;
                 // update container information -> new block id in sections
                 let container = this.blockAdder.container;
                 container.attributes.payload.sections[this.blockAdder.section].blocks.push(newBlock.id);
diff --git a/resources/vue/components/courseware/CoursewareCanvasBlock.vue b/resources/vue/components/courseware/CoursewareCanvasBlock.vue
index 31c9258f50c..8e4bb651348 100644
--- a/resources/vue/components/courseware/CoursewareCanvasBlock.vue
+++ b/resources/vue/components/courseware/CoursewareCanvasBlock.vue
@@ -272,6 +272,7 @@ export default {
             createFile: 'createFile',
             companionSuccess: 'companionSuccess',
             companionError: 'companionError',
+            updateUserDataFields: 'courseware-user-data-fields/update'
         }),
         initCurrentData() {
             this.currentTitle = this.title;
@@ -551,7 +552,7 @@ export default {
             data.attributes.payload.canvas_draw.clickTool = JSON.stringify(this.clickTool);
             data.attributes.payload.canvas_draw.Text = JSON.stringify(this.Text);
 
-            await this.$store.dispatch('courseware-user-data-fields/update', data);
+            await this.updateUserDataFields(data);
         },
         storeBlock() {
             let attributes = {};
diff --git a/resources/vue/components/courseware/CoursewareCompanionBox.vue b/resources/vue/components/courseware/CoursewareCompanionBox.vue
index f8dbbde812e..2e826956106 100644
--- a/resources/vue/components/courseware/CoursewareCompanionBox.vue
+++ b/resources/vue/components/courseware/CoursewareCompanionBox.vue
@@ -16,7 +16,7 @@ export default {
             type: String,
             default: 'default',
             validator: value => {
-                return ['default','unsure', 'special', 'sad', 'pointing'].includes(value);
+                return ['default','unsure', 'special', 'sad', 'pointing', 'curious'].includes(value);
             }
         }
     },
diff --git a/resources/vue/components/courseware/CoursewareCompanionOverlay.vue b/resources/vue/components/courseware/CoursewareCompanionOverlay.vue
index 5c4fc97c8f9..9cce904e758 100644
--- a/resources/vue/components/courseware/CoursewareCompanionOverlay.vue
+++ b/resources/vue/components/courseware/CoursewareCompanionOverlay.vue
@@ -9,7 +9,7 @@
 </template>
 
 <script>
-import { mapGetters } from 'vuex';
+import { mapActions, mapGetters } from 'vuex';
 
 export default {
     name: 'courseware-companion-overlay',
@@ -22,8 +22,11 @@ export default {
         }),
     },
     methods: {
+        ...mapActions({
+            coursewareShowCompanionOverlay: 'coursewareShowCompanionOverlay'
+        }),
         hideCompanion() {
-            this.$store.dispatch('coursewareShowCompanionOverlay', false);
+            this.coursewareShowCompanionOverlay(false);
         },
     },
     watch: {
diff --git a/resources/vue/components/courseware/CoursewareConfirmBlock.vue b/resources/vue/components/courseware/CoursewareConfirmBlock.vue
index 6d37a576d4b..2a3f9b5a4dd 100644
--- a/resources/vue/components/courseware/CoursewareConfirmBlock.vue
+++ b/resources/vue/components/courseware/CoursewareConfirmBlock.vue
@@ -79,6 +79,7 @@ export default {
     methods: {
         ...mapActions({
             updateBlock: 'updateBlockInContainer',
+            updateUserDataFields: 'courseware-user-data-fields/update'
         }),
         initCurrentData() {
             this.currentText = this.text;
@@ -99,7 +100,7 @@ export default {
             data.relationships.block.data.id = this.block.id;
             data.relationships.block.data.type = this.block.type;
 
-            await this.$store.dispatch('courseware-user-data-fields/update', data);
+            await this.updateUserDataFields(data);
             this.userProgress = 1;
             this.confirm = true;
         },
diff --git a/resources/vue/components/courseware/CoursewareContentOverviewActionWidget.vue b/resources/vue/components/courseware/CoursewareContentOverviewActionWidget.vue
deleted file mode 100644
index e98976efea0..00000000000
--- a/resources/vue/components/courseware/CoursewareContentOverviewActionWidget.vue
+++ /dev/null
@@ -1,25 +0,0 @@
-<template>
-    <ul class="widget-list widget-links cw-action-widget">
-        <li class="cw-action-widget-add" >
-            <a href="#" @click.prevent="addElement">
-                <translate>Neues Lernmaterial anlegen</translate>
-            </a>
-        </li>
-    </ul>
-</template>
-
-<script>
-import { mapActions } from 'vuex';
-
-export default {
-    name: 'courseware-content-overview-action-widget',
-    methods: {
-        ...mapActions({
-            setShowOverviewElementAddDialog: 'setShowOverviewElementAddDialog'
-        }),
-        addElement() {
-            this.setShowOverviewElementAddDialog(true);
-        }
-    }
-}
-</script>
diff --git a/resources/vue/components/courseware/CoursewareContentOverviewElements.vue b/resources/vue/components/courseware/CoursewareContentOverviewElements.vue
deleted file mode 100644
index 0e6a87fde0c..00000000000
--- a/resources/vue/components/courseware/CoursewareContentOverviewElements.vue
+++ /dev/null
@@ -1,622 +0,0 @@
-<template>
-    <div class="cw-contents-overview-wrapper">
-        <div v-if="root && filteredChildren.length > 0" class="cw-contents-overview-personal">
-            <h2>
-                <translate>Persönliche Lernmaterialien</translate>
-            </h2>
-            <ul class="cw-tiles">
-                <li
-                    v-for="child in filteredChildren"
-                    :key="child.id"
-                    class="tile"
-                    :class="[child.attributes.payload.color, filteredChildren.length > 3 ? '':  'cw-tile-margin']"
-                >
-                    <a :href="getElementUrl(child.id)" :title="child.attributes.title">
-                        <div
-                            class="preview-image"
-                            :class="[hasImage(child) ? '' : 'default-image']"
-                            :style="getChildStyle(child)"
-                        ></div>
-                        <div class="description">
-                            <header
-                                :class="[child.attributes.purpose !== '' ? 'description-icon-' + child.attributes.purpose : '']"
-                            >
-                                {{ child.attributes.title }}
-                            </header>
-                            <div class="description-text-wrapper">
-                                <p>{{ child.attributes.payload.description }}</p>
-                            </div>
-                            <footer>
-                                {{ countChildren(child) + 1 }}
-                                <translate
-                                    :translate-n="countChildren(child) + 1"
-                                    translate-plural="Seiten"
-                                >
-                                    Seite
-                                </translate>
-                            </footer>
-                        </div>
-                    </a>
-                </li>
-            </ul>
-        </div>
-        <div v-if="children.length === 0" class="cw-contents-overview-teaser">
-            <div class="cw-contents-overview-teaser-content">
-                <header><translate>Ihre persönlichen Lernmaterialien</translate></header>
-                <p><translate>Erstellen und Verwalten Sie hier ihre eigenen persönlichen Lernmaterialien in Form von ePorfolios,
-                            Vorlagen für Veranstaltungen oder einfach nur persönliche Inhalte für das Studium.
-                            Entwickeln Sie ihre eigenen (Lehr-)Materialien für Studium oder die Lehre und teilen diese mit anderen Nutzenden.</translate></p>
-                <button class="button" @click="addElement">
-                    <translate>Neues Lernmaterial anlegen</translate>
-                </button>
-            </div>
-        </div>
-        <studip-dialog
-                v-if="showOverviewElementAddDialog"
-                :title="$gettext('Neues Lernmaterial anlegen')"
-                height="600"
-                width="500"
-                :confirmText="$gettext('Erstellen')"
-                confirmClass="accept"
-                :closeText="$gettext('Schließen')"
-                closeClass="cancel"
-                class="cw-structural-element-dialog"
-                @close="closeAddDialog"
-                @confirm="createElement"
-        >
-            <template v-slot:dialogContent>
-
-                    <courseware-collapsible-box
-                    :title="$gettext('Grundeinstellungen')"
-                    :open="true"
-                    >
-                        <form class="default" @submit.prevent="">
-                            <label>
-                                <translate>Titel des Lernmaterials</translate><br />
-                                <input v-model="newElement.attributes.title" type="text" />
-                            </label>
-                            <label>
-                                <translate>Zusammenfassung</translate><br />
-                                <textarea v-model="newElement.attributes.payload.description"></textarea>
-                            </label>
-                            <label>
-                                <translate>Bild</translate>
-                                <br>
-                                <input ref="upload_image" type="file" accept="image/*" @change="checkUploadFile" />
-                                <courseware-companion-box
-                                    v-if="uploadFileError"
-                                    :msgCompanion="uploadFileError"
-                                    mood="sad"
-                                    class="cw-companion-box-in-form"
-                                />
-                            </label>
-                            <label>
-                                <translate>Art des Lernmaterials</translate>
-                                <select v-model="newElementPurpose">
-                                    <option value="content"><translate>Inhalt</translate></option>
-                                    <option value="template"><translate>Aufgabenvorlage</translate></option>
-                                    <option value="oer"><translate>OER-Material</translate></option>
-                                    <option value="portfolio"><translate>ePortfolio</translate></option>
-                                    <option value="draft"><translate>Entwurf</translate></option>
-                                    <option value="other"><translate>Sonstiges</translate></option>
-                                </select>
-                            </label>
-                            <label>
-                                <translate>Lernmaterialvorlage</translate>
-                                <select v-model="newElementTemplate">
-                                    <option :value="null"><translate>ohne Vorlage</translate></option>
-                                    <option
-                                        v-for="template in selectableTemplates"
-                                        :key="template.id"
-                                        :value="template"
-                                    >
-                                        {{ template.attributes.name }}
-                                    </option>
-                                </select>
-                            </label>
-                        </form>
-                    </courseware-collapsible-box>
-                    <courseware-collapsible-box :title="$gettext('Vorschau')">
-                        <div v-if="currentTemplateStructure" class="cw-template-preview">
-                            <div
-                                class="cw-template-preview-container-wrapper"
-                                v-for="container in currentTemplateStructure.containers"
-                                :key="container.id"
-                                :class="['cw-template-preview-container-' + container.attributes.payload.colspan]"
-                            >
-                                <div class="cw-template-preview-container-content">
-                                    <header class="cw-template-preview-container-title">
-                                        {{ container.attributes.title }} | {{ container.attributes.width }}
-                                    </header>
-                                    <div class="cw-template-preview-blocks" v-for="block in container.blocks" :key="block.id">
-                                        <header class="cw-template-preview-blocks-title">
-                                            {{ block.attributes.title }}
-                                        </header>
-                                    </div>
-                                </div>
-                            </div>
-                        </div>
-                        <courseware-companion-box
-                            v-else
-                            :msgCompanion="$gettext('Sie können eine Lernmaterialvorlage auswählen und hier eine Vorschau betrachten. Ohne Vorlage wird eine leere Seite erzeugt.')"
-                        />
-                    </courseware-collapsible-box>
-                    <courseware-collapsible-box
-                        :title="$gettext('Zusatzangaben')"
-                    >
-                        <form class="default" @submit.prevent="">
-                            <label>
-                                <translate>Lizenztyp</translate>
-                                <select v-model="newElement.attributes.payload.license_type">
-                                    <option v-for="license in licenses" :key="license.id" :value="license.id">
-                                        {{ license.name }}
-                                    </option>
-                                </select>
-                            </label>
-                            <label>
-                                <translate>Geschätzter zeitlicher Aufwand</translate>
-                                <input type="text" v-model="newElement.attributes.payload.required_time" />
-                            </label>
-                            <label>
-                                <translate>Niveau</translate><br />
-                                <translate>von</translate>
-                                <select v-model="newElement.attributes.payload.difficulty_start">
-                                    <option
-                                        v-for="difficulty_start in 12"
-                                        :key="difficulty_start"
-                                        :value="difficulty_start"
-                                    >
-                                        {{ difficulty_start }}
-                                    </option>
-                                </select>
-                                <translate>bis</translate>
-                                <select v-model="newElement.attributes.payload.difficulty_end">
-                                    <option
-                                        v-for="difficulty_end in 12"
-                                        :key="difficulty_end"
-                                        :value="difficulty_end"
-                                    >
-                                        {{ difficulty_end }}
-                                    </option>
-                                </select>
-                            </label>
-                            <label>
-                                <translate>Farbe</translate>
-                                <studip-select
-                                    v-model="newElement.attributes.payload.color"
-                                    :options="colors"
-                                    :reduce="(color) => color.class"
-                                    label="class"
-                                >
-                                    <template #open-indicator="selectAttributes">
-                                        <span v-bind="selectAttributes"
-                                            ><studip-icon shape="arr_1down" size="10"
-                                        /></span>
-                                    </template>
-                                    <template #no-options>
-                                        <translate>Es steht keine Auswahl zur Verfügung.</translate>
-                                    </template>
-                                    <template #selected-option="{ name, hex }">
-                                        <span class="vs__option-color" :style="{ 'background-color': hex }"></span
-                                        ><span>{{ name }}</span>
-                                    </template>
-                                    <template #option="{ name, hex }">
-                                        <span class="vs__option-color" :style="{ 'background-color': hex }"></span
-                                        ><span>{{ name }}</span>
-                                    </template>
-                                </studip-select>
-                            </label>
-                        </form>
-                    </courseware-collapsible-box>
-
-            </template>
-        </studip-dialog>
-
-        <div v-if="filteredShared.length > 0" class="cw-contents-overview-shared">
-            <h2>
-                <translate>Geteilte Lernmaterialien</translate>
-            </h2>
-            <ul class="cw-tiles">
-                <li
-                    v-for="element in filteredShared"
-                    :key="element.id"
-                    class="tile"
-                    :class="[element.attributes.payload.color, sharedElements.length > 3 ? '':  'cw-tile-margin']"
-                >
-                    <a :href="getSharedElementUrl(element.id)" :title="element.attributes.title">
-                        <div
-                            class="preview-image"
-                            :class="[hasImage(element) ? '' : 'default-image']"
-                            :style="getChildStyle(element)"
-                        >
-                            <div class="overlay-text">{{ getOwnerName(element) }}</div>
-                        </div>
-                        <div class="description">
-                            <header
-                                :class="[element.attributes.purpose !== '' ? 'description-icon-' + element.attributes.purpose : '']"
-                            >
-                                {{ element.attributes.title }}
-                            </header>
-                            <div class="description-text-wrapper">
-                                <p>{{ element.attributes.payload.description }}</p>
-                            </div>
-                            <footer>
-                                {{ countChildren(element) + 1 }}
-                                <translate
-                                    :translate-n="countChildren(element) + 1"
-                                    translate-plural="Seiten"
-                                >
-                                    Seite
-                                </translate>
-                            </footer>
-                        </div>
-                    </a>
-                </li>
-            </ul>
-        </div>
-        <courseware-companion-box
-            v-if="children.length !== 0 && filteredChildren.length === 0 && sharedElements.length !== 0 && filteredShared.length === 0"
-            :msgCompanion="$gettext('Für diese Auswahl wurden keine Lernmaterialien gefunden.')"
-            mood="pointing"
-        />
-
-        <courseware-companion-overlay />
-    </div>
-</template>
-
-<script>
-import { mapActions, mapGetters } from 'vuex';
-import CoursewareCollapsibleBox from './CoursewareCollapsibleBox.vue';
-import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
-import CoursewareCompanionOverlay from './CoursewareCompanionOverlay.vue';
-import StudipDialog from '../StudipDialog.vue';
-
-export default {
-    name: 'courseware-content-overview-elements',
-    components: {
-        CoursewareCollapsibleBox,
-        CoursewareCompanionOverlay,
-        CoursewareCompanionBox,
-        StudipDialog
-    },
-    data() {
-        return {
-            newElement: {
-                attributes: {
-                    payload: {},
-                },
-            },
-            newElementPurpose: 'content',
-            newElementTemplate: null,
-            uploadFileError: '',
-        }
-    },
-    computed: {
-        ...mapGetters({
-            getElement: 'courseware-structural-elements/byId',
-            licenses: 'licenses',
-            permissionFilter: 'permissionFilter',
-            purposeFilter: 'purposeFilter',
-            sourceFilter: 'sourceFilter',
-            showOverviewElementAddDialog: 'showOverviewElementAddDialog',
-            templates: 'courseware-templates/all',
-            sharedElements: 'courseware-structural-elements-shared/all',
-            userById: 'users/byId',
-        }),
-        root() {
-            return this.getElement({id: STUDIP.COURSEWARE_USERS_ROOT_ID});
-        },
-        children() {
-            let view = this;
-            let children = [];
-            if(this.root?.relationships?.children?.data) {
-                this.root.relationships.children.data.forEach(function(child){
-                    let element = view.getElement({id: child.id});
-                    children.push(element);
-                });
-            }
-
-            return children;
-        },
-        filteredChildren() {
-            if (!['all', 'personal'].includes(this.sourceFilter)) {
-                return [];
-            }
-            let children = this.children;
-            if (this.purposeFilter !== 'all') {
-                children = children.filter(child => { return child.attributes.purpose === this.purposeFilter});
-            }
-            if (this.permissionFilter !== 'read') {
-                children = children.filter(child => { return child.attributes['can-edit'] });
-            }
-
-            return children;
-        },
-        filteredShared() {
-            if (!['all', 'shared'].includes(this.sourceFilter)) {
-                return [];
-            }
-            let elements = this.sharedElements;
-            if (this.purposeFilter !== 'all') {
-                elements =  elements.filter(element => { return element.attributes.purpose === this.purposeFilter});
-            }
-            if (this.permissionFilter !== 'read') {
-                elements = elements.filter(element => { return element.attributes['can-edit'] });
-            }
-
-            return elements;
-        },
-        colors() {
-            const colors = [
-                {
-                    name: this.$gettext('Schwarz'),
-                    class: 'black',
-                    hex: '#000000',
-                    level: 100,
-                    icon: 'black',
-                    darkmode: true,
-                },
-                {
-                    name: this.$gettext('Weiß'),
-                    class: 'white',
-                    hex: '#ffffff',
-                    level: 100,
-                    icon: 'white',
-                    darkmode: false,
-                },
-
-                {
-                    name: this.$gettext('Blau'),
-                    class: 'studip-blue',
-                    hex: '#28497c',
-                    level: 100,
-                    icon: 'blue',
-                    darkmode: true,
-                },
-                {
-                    name: this.$gettext('Hellblau'),
-                    class: 'studip-lightblue',
-                    hex: '#e7ebf1',
-                    level: 40,
-                    icon: 'lightblue',
-                    darkmode: false,
-                },
-                {
-                    name: this.$gettext('Rot'),
-                    class: 'studip-red',
-                    hex: '#d60000',
-                    level: 100,
-                    icon: 'red',
-                    darkmode: false,
-                },
-                {
-                    name: this.$gettext('Grün'),
-                    class: 'studip-green',
-                    hex: '#008512',
-                    level: 100,
-                    icon: 'green',
-                    darkmode: true,
-                },
-                {
-                    name: this.$gettext('Gelb'),
-                    class: 'studip-yellow',
-                    hex: '#ffbd33',
-                    level: 100,
-                    icon: 'yellow',
-                    darkmode: false,
-                },
-                {
-                    name: this.$gettext('Grau'),
-                    class: 'studip-gray',
-                    hex: '#636a71',
-                    level: 100,
-                    icon: 'grey',
-                    darkmode: true,
-                },
-
-                {
-                    name: this.$gettext('Holzkohle'),
-                    class: 'charcoal',
-                    hex: '#3c454e',
-                    level: 100,
-                    icon: false,
-                    darkmode: true,
-                },
-                {
-                    name: this.$gettext('Königliches Purpur'),
-                    class: 'royal-purple',
-                    hex: '#8656a2',
-                    level: 80,
-                    icon: false,
-                    darkmode: true,
-                },
-                {
-                    name: this.$gettext('Leguangrün'),
-                    class: 'iguana-green',
-                    hex: '#66b570',
-                    level: 60,
-                    icon: false,
-                    darkmode: true,
-                },
-                {
-                    name: this.$gettext('Königin blau'),
-                    class: 'queen-blue',
-                    hex: '#536d96',
-                    level: 80,
-                    icon: false,
-                    darkmode: true,
-                },
-                {
-                    name: this.$gettext('Helles Seegrün'),
-                    class: 'verdigris',
-                    hex: '#41afaa',
-                    level: 80,
-                    icon: false,
-                    darkmode: true,
-                },
-                {
-                    name: this.$gettext('Maulbeere'),
-                    class: 'mulberry',
-                    hex: '#bf5796',
-                    level: 80,
-                    icon: false,
-                    darkmode: true,
-                },
-                {
-                    name: this.$gettext('Kürbis'),
-                    class: 'pumpkin',
-                    hex: '#f26e00',
-                    level: 100,
-                    icon: false,
-                    darkmode: true,
-                },
-                {
-                    name: this.$gettext('Sonnenschein'),
-                    class: 'sunglow',
-                    hex: '#ffca5c',
-                    level: 80,
-                    icon: false,
-                    darkmode: false,
-                },
-                {
-                    name: this.$gettext('Apfelgrün'),
-                    class: 'apple-green',
-                    hex: '#8bbd40',
-                    level: 80,
-                    icon: false,
-                    darkmode: true,
-                },
-            ];
-            let elementColors = [];
-            colors.forEach((color) => {
-                if (color.darkmode) {
-                    elementColors.push(color);
-                }
-            });
-
-            return elementColors;
-        },
-        selectableTemplates() {
-            return this.templates.filter(template => {
-                return template.attributes.purpose === this.newElementPurpose
-            });
-        },
-        currentTemplateStructure() {
-            if(this.newElementTemplate === null) {
-                return null;
-            }
-
-            return JSON.parse(this.newElementTemplate.attributes.structure);
-        }
-    },
-    methods: {
-        ...mapActions({
-            createStructuralElement: 'createStructuralElement',
-            createStructuralElementWithTemplate: 'createStructuralElementWithTemplate',
-            loadElement: 'courseware-structural-elements/loadById',
-            setShowOverviewElementAddDialog: 'setShowOverviewElementAddDialog',
-            uploadImageForStructuralElement: 'uploadImageForStructuralElement',
-            companionInfo: 'companionInfo',
-        }),
-        getChildStyle(child) {
-            let url = child.relationships?.image?.meta?.['download-url'];
-
-            if(url) {
-                return {'background-image': 'url(' + url + ')'};
-            } else {
-                return {};
-            }
-        },
-        hasImage(child) {
-            return child.relationships?.image?.data !== null;
-        },
-        getElementUrl(elementId) {
-            return STUDIP.URLHelper.base_url + 'dispatch.php/contents/courseware/courseware#/structural_element/' + elementId;
-        },
-        getSharedElementUrl(elementId) {
-            return STUDIP.URLHelper.base_url + 'dispatch.php/contents/courseware/shared_content_courseware/' + elementId;
-        },
-        getOwnerName(element) {
-            const ownerId = element.relationships.owner.data.id;
-            const owner = this.userById({ id: ownerId });
-
-            return owner.attributes['formatted-name'];
-        },
-        addElement() {
-            this.setShowOverviewElementAddDialog(true);
-        },
-        closeAddDialog() {
-            this.setShowOverviewElementAddDialog(false);
-            this.initNewElement();
-        },
-        async createElement() {
-            if (this.newElement.attributes.title == null ) {
-                this.companionInfo({ info: this.$gettext('Bitte geben Sie einen Titel für das Lernmaterial ein') });
-                return false;
-            }
-            this.setShowOverviewElementAddDialog(false);
-            const file = this.$refs?.upload_image?.files[0];
-            this.newElement.attributes.purpose = this.newElementPurpose;
-            await this.createStructuralElementWithTemplate({
-                attributes: this.newElement.attributes,
-                templateId: this.newElementTemplate ? this.newElementTemplate.id : null,
-                parentId: this.root.id,
-                currentId: this.root.id,
-            });
-            let newStructuralElement = this.$store.getters['courseware-structural-elements/lastCreated'];
-
-            if (file) {
-                await this.uploadImageForStructuralElement({
-                    structuralElement: newStructuralElement,
-                    file,
-                }).catch((error) => {
-                    console.error(error);
-                    this.companionInfo({ info: this.$gettext('Das Bild für das neue Lernmaterial konnte nicht gespeichert werden.') });
-                });
-                this.loadElement({id: newStructuralElement.id, options: {include: 'children'}});
-            }
-            this.initNewElement();
-
-        },
-        initNewElement() {
-            this.newElement = {
-                attributes: {
-                    payload: {},
-                    purpose: '',
-                },
-                template: ''
-            };
-        },
-        countChildren(element) {
-            let data = element.relationships.children.data;
-            if (data) {
-                return data.length;
-            }
-            return 0;
-        },
-        checkUploadFile() {
-            const file = this.$refs?.upload_image?.files[0];
-            if (file.size > 2097152) {
-                this.uploadFileError = this.$gettext('Diese Datei ist zu groß. Bitte wählen Sie eine Datei aus, die kleiner als 2MB groß ist.');
-            } else if (!file.type.includes('image')) {
-                this.uploadFileError = this.$gettext('Diese Datei ist kein Bild. Bitte wählen Sie ein Bild aus.');
-            } else {
-                this.uploadFileError = '';
-            }
-        }
-    },
-    watch: {
-        root(newRootObject) {
-            let view = this;
-            if (newRootObject) {
-                newRootObject.relationships.children.data.forEach(function(child) {
-                    view.loadElement({id: child.id, options: {include: 'children'}});
-                });
-            }
-        },
-        newElementPurpose() {
-            this.newElementTemplate = null;
-        }
-    }
-}
-</script>
diff --git a/resources/vue/components/courseware/CoursewareContentOverviewFilterWidget.vue b/resources/vue/components/courseware/CoursewareContentOverviewFilterWidget.vue
deleted file mode 100644
index 43fbd1e0c9c..00000000000
--- a/resources/vue/components/courseware/CoursewareContentOverviewFilterWidget.vue
+++ /dev/null
@@ -1,99 +0,0 @@
-<template>
-    <div class="cw-filter-widget">
-        <form class="default" @submit.prevent="">
-            <label>
-                <translate>Lernmaterialien</translate>
-                <select v-model="sourceFilter">
-                    <option value="all">
-                        <translate>Alle</translate>
-                    </option>
-                    <option value="personal">
-                        <translate>Persönliche</translate>
-                    </option>
-                    <option value="shared">
-                        <translate>Geteilte</translate>
-                    </option>
-                </select>
-            </label>
-            <label>
-                <translate>Zweck</translate>
-                <select v-model="purposeFilter">
-                    <option value="all">
-                        <translate>Alle</translate>
-                    </option>
-                    <option value="content">
-                        <translate>Inhalt</translate>
-                    </option>
-                    <option value="template">
-                        <translate>Aufgabenvorlage</translate>
-                    </option>
-                    <option value="oer">
-                        <translate>OER-Material</translate>
-                    </option>
-                    <option value="portfolio">
-                        <translate>ePortfolio</translate>
-                    </option>
-                    <option value="draft">
-                        <translate>Entwurf</translate>
-                    </option>
-                    <option value="other">
-                        <translate>Sonstiges</translate>
-                    </option>
-                </select>
-            </label>
-            <label>
-                <translate>Rechte</translate>
-                <select v-model="permissionFilter">
-                    <option value="read">
-                        <translate>Lesen</translate>
-                    </option>
-                    <option value="write">
-                        <translate>Lesen und schreiben</translate>
-                    </option>
-                </select>
-            </label>
-        </form>
-    </div>
-</template>
-
-<script>
-import { mapActions } from 'vuex';
-
-export default {
-    name: 'courseware-content-overview-filter-widget',
-    data() {
-        return {
-            permissionFilter: 'read',
-            purposeFilter: 'all',
-            sourceFilter: 'all',
-        };
-    },
-    methods: {
-        ...mapActions({
-            setPermissionFilter: 'setPermissionFilter',
-            setPurposeFilter: 'setPurposeFilter',
-            setSourceFilter: 'setSourceFilter',
-        }),
-        filterPermission() {
-            this.setPermissionFilter(this.permissionFilter);
-        },
-        filterPurpose() {
-            this.setPurposeFilter(this.purposeFilter);
-        },
-        filterSource() {
-            this.setSourceFilter(this.sourceFilter);
-        },
-    },
-    watch: {
-        permissionFilter() {
-            this.filterPermission();
-        },
-        purposeFilter() {
-            this.filterPurpose();
-        },
-        sourceFilter() {
-            this.filterSource();
-        },
-    }
-}
-</script>
diff --git a/resources/vue/components/courseware/CoursewareCourseDashboard.vue b/resources/vue/components/courseware/CoursewareCourseDashboard.vue
deleted file mode 100644
index 5f3b7ebed44..00000000000
--- a/resources/vue/components/courseware/CoursewareCourseDashboard.vue
+++ /dev/null
@@ -1,93 +0,0 @@
-<template>
-    <div class="cw-dashboard-wrapper">
-        <div v-if="defaultView" class="cw-dashboard cw-dashboard-default-view">
-            <courseware-collapsible-box :title="$gettext('Überblick')" :open="true" class="cw-dashboard-box cw-dashboard-box-full">
-                <div class="cw-dashboard-overview">
-                    <courseware-oblong :name="textChapterFinished" icon="accept" size="small">
-                        <template v-slot:oblongValue> {{ chapterCounter.finished }} </template>
-                    </courseware-oblong>
-                    <courseware-oblong :name="textChapterStarted" icon="play" size="small">
-                        <template v-slot:oblongValue> {{ chapterCounter.started }} </template>
-                    </courseware-oblong>
-                    <courseware-oblong :name="textChapterAhead" icon="timetable" size="small">
-                        <template v-slot:oblongValue> {{ chapterCounter.ahead }} </template>
-                    </courseware-oblong>
-                </div>
-            </courseware-collapsible-box>
-            <courseware-collapsible-box :title="$gettext('Fortschritt')" :open="true" class="cw-dashboard-box cw-dashboard-box-half">
-                <courseware-dashboard-progress />
-            </courseware-collapsible-box>
-            <courseware-collapsible-box :title="$gettext('Aktivitäten')" :open="true" class="cw-dashboard-box cw-dashboard-box-half cw-content-loading">
-                <courseware-dashboard-activities />
-            </courseware-collapsible-box>
-            <courseware-collapsible-box :title="$gettext('Aufgaben')" :open="true" class="cw-dashboard-box cw-dashboard-box-full">
-                <courseware-dashboard-tasks v-if="!userIsTeacher && teacherStatusLoaded"/>
-                <courseware-dashboard-students v-if="userIsTeacher && teacherStatusLoaded" />
-            </courseware-collapsible-box>
-        </div>
-        <div v-if="taskView" class="cw-dashboard cw-dashboard-task-view">
-            <courseware-dashboard-tasks v-if="!userIsTeacher && teacherStatusLoaded"/>
-            <courseware-dashboard-students v-if="userIsTeacher && teacherStatusLoaded" />
-        </div>
-        <div v-if="activityView" class="cw-dashboard cw-dashboard-activity-view">
-            <courseware-collapsible-box :title="$gettext('Aktivitäten')" :open="true" class="cw-dashboard-box cw-dashboard-box-full cw-content-loading">
-                <courseware-dashboard-activities />
-            </courseware-collapsible-box>
-        </div>
-        <courseware-companion-overlay />
-    </div>
-</template>
-
-<script>
-import CoursewareCollapsibleBox from './CoursewareCollapsibleBox.vue';
-import CoursewareDashboardProgress from './CoursewareDashboardProgress.vue';
-import CoursewareDashboardActivities from './CoursewareDashboardActivities.vue';
-import CoursewareDashboardTasks from './CoursewareDashboardTasks.vue'
-import CoursewareDashboardStudents from './CoursewareDashboardStudents.vue'
-import CoursewareOblong from './CoursewareOblong.vue';
-import CoursewareCompanionOverlay from './CoursewareCompanionOverlay.vue';
-import { mapGetters } from 'vuex';
-
-export default {
-    name: 'courseware-course-dashboard',
-    components: {
-        CoursewareCollapsibleBox,
-        CoursewareOblong,
-        CoursewareDashboardProgress,
-        CoursewareDashboardActivities,
-        CoursewareDashboardTasks,
-        CoursewareDashboardStudents,
-        CoursewareCompanionOverlay
-    },
-    data() {
-        return {
-            textChapterAhead: this.$gettext('bevorstehende Seiten'),
-            textChapterStarted: this.$gettext('angefangene Seiten'),
-            textChapterFinished: this.$gettext('abgeschlossene Seiten'),
-        };
-    },
-    computed: {
-        ...mapGetters({
-            dashboardViewMode: 'dashboardViewMode',
-            getCourseById: 'courses/byId',
-            getStructuralElementById: 'courseware-structural-elements/byId',
-            getUserById: 'users/byId',
-            teacherStatusLoaded: 'teacherStatusLoaded',
-            userId: 'userId',
-            userIsTeacher: 'userIsTeacher',
-        }),
-        chapterCounter() {
-            return STUDIP.courseware_chapter_counter;
-        },
-        defaultView() {
-            return this.dashboardViewMode === 'default';
-        },
-        taskView() {
-            return this.dashboardViewMode === 'task';
-        },
-        activityView() {
-            return this.dashboardViewMode === 'activity';
-        },
-    }
-};
-</script>
diff --git a/resources/vue/components/courseware/CoursewareDashboardActivities.vue b/resources/vue/components/courseware/CoursewareDashboardActivities.vue
deleted file mode 100644
index d068d3a8da7..00000000000
--- a/resources/vue/components/courseware/CoursewareDashboardActivities.vue
+++ /dev/null
@@ -1,110 +0,0 @@
-<template>
-    <div class="cw-dashboard-activities-wrapper">
-        <span v-if="loading">
-            <div class="loading-indicator">
-                <span class="load-1"></span>
-                <span class="load-2"></span>
-                <span class="load-3"></span>
-            </div>
-        </span>
-        <courseware-companion-box
-            v-if="activitiesList.length === 0 && !loading"
-            mood="sad"
-            :msgCompanion="$gettext('Es wurden keine Aktivitäten gefunden.')"
-        />
-        <ul class="cw-dashboard-activities">
-            <courseware-activity-item v-for="(item, index) in activitiesList" :key="index" :item="item" />
-        </ul>
-    </div>
-</template>
-
-<script>
-import CoursewareActivityItem from './CoursewareActivityItem.vue';
-import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
-import { mapActions, mapGetters } from 'vuex';
-
-export default {
-    name: 'courseware-dashboard-activities',
-    components: {
-        CoursewareActivityItem,
-        CoursewareCompanionBox,
-    },
-    props: {
-    },
-    data() {
-        return {
-            activitiesList: [],
-            loading: false
-        }
-    },
-    computed: {
-        ...mapGetters({
-            userId: 'userId',
-            getUserById: 'users/byId',
-            context: 'context',
-            getStructuralElementById: 'courseware-structural-elements/byId',
-        }),
-    },
-    created: function () {
-           this.getActivities();
-    },
-    methods: {
-        ...mapActions([
-            'loadCoursewareActivities'
-        ]),
-
-        async getActivities() {
-            this.loading = true;
-            let activities = await this.loadCoursewareActivities({ userId: this.userId, courseId: this.context.id});
-            this.activitiesList = [];
-
-            activities.forEach(activity => {
-                if(activity.type === 'activities') {
-                    let username = this.getUserById({ id: activity.relationships.actor.data.id }).attributes['formatted-name'];
-                    const date = new Date(activity.attributes.mkdate);
-                    const activityStructuralElement = this.getStructuralElementById({ id: activity.relationships.object.meta["object-id"] });
-
-                    let breadcrumb = activityStructuralElement.attributes.title;
-                    let completeBreadcrumb = activityStructuralElement.attributes.title;
-                    let currentStructuralElement = activityStructuralElement;
-                    if (currentStructuralElement === undefined) {
-                        return;
-                    }
-                    let i = 1; //max breadcrumb navigation depth check
-                    while (currentStructuralElement.relationships.parent.data !== null) {
-                        currentStructuralElement = this.getStructuralElementById({ id: currentStructuralElement.relationships.parent.data.id });
-                        if (currentStructuralElement === undefined) {
-                            break;
-                        }
-                        completeBreadcrumb = currentStructuralElement.attributes.title + '/' + completeBreadcrumb;
-                        
-                        if(++i <= 3) {
-                            breadcrumb = currentStructuralElement.attributes.title + '/' + breadcrumb;
-                            
-                            if(i == 3) {
-                                breadcrumb = '.../' + breadcrumb;
-                            }
-                        }
-                    }
-                    let options = { year: 'numeric', month: '2-digit', day: '2-digit' };
-                    let data = {
-                        username: username,
-                        date: date.toLocaleString('de-DE', options),
-                        type: activity.attributes.verb,
-                        text: activity.attributes.title,
-                        complete_breadcrumb: completeBreadcrumb,
-                        element_breadcrumb: breadcrumb,
-                        element_id: activity.relationships.object.meta["object-id"],
-                        context_id: activity.relationships.context.data.id,
-                        content: activity.attributes.content
-                    }
-
-                    this.activitiesList.push(data);
-                }
-            });
-
-            this.loading = false;
-        }
-    }
-};
-</script>
diff --git a/resources/vue/components/courseware/CoursewareDashboardStudents.vue b/resources/vue/components/courseware/CoursewareDashboardStudents.vue
index d0a15ce9197..6ad27bfa25d 100644
--- a/resources/vue/components/courseware/CoursewareDashboardStudents.vue
+++ b/resources/vue/components/courseware/CoursewareDashboardStudents.vue
@@ -46,7 +46,7 @@
                         </a>
                         <span v-else>{{ element.attributes.title }}</span>
                     </td>
-                    <td>{{ task.attributes.progress.toFixed(2) }}%</td>
+                    <td>{{ task.attributes?.progress?.toFixed(2) || '-.--' }}%</td>
                     <td>{{ getReadableDate(task.attributes['submission-date']) }}</td>
                     <td>
                         <studip-icon v-if="task.attributes.submitted" shape="accept" role="status-green" />
@@ -110,14 +110,8 @@
         <div v-else>
             <courseware-companion-box 
                 mood="pointing"
-                :msgCompanion="
-                    $gettext('Es wurden bisher keine Aufgaben gestellt.') + '<br>' + 
-                    $gettext('Wenn Sie eine Aufgabe stellen möchten, nutzen Sie bitte in der Verwaltung der Courseware die Funktion &quot;Aufgabe verteilen&quot;.')
-                "
+                :msgCompanion="$gettext('Es wurden bisher keine Aufgaben gestellt.')"
             >
-                <template v-slot:companionActions>
-                <a class="button" :href="managerUrl"><translate>Zur Verwaltung</translate></a>
-            </template>
             </courseware-companion-box>
         </div>
         <studip-dialog
@@ -206,6 +200,7 @@
                 </form>
             </template>
         </studip-dialog>
+        <courseware-tasks-dialog-distribute v-if="showTasksDistributeDialog"/>
     </div>
 </template>
 
@@ -214,9 +209,11 @@ import StudipIcon from './../StudipIcon.vue';
 import StudipDialog from './../StudipDialog.vue';
 import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
 import CoursewareDateInput from './CoursewareDateInput.vue';
+import CoursewareTasksDialogDistribute from './CoursewareTasksDialogDistribute.vue';
 import taskHelperMixin from '../../mixins/courseware/task-helper.js';
 import { mapActions, mapGetters } from 'vuex';
 
+
 export default {
     name: 'courseware-dashboard-students',
     mixins: [taskHelperMixin],
@@ -225,6 +222,7 @@ export default {
         CoursewareDateInput,
         StudipIcon,
         StudipDialog,
+        CoursewareTasksDialogDistribute,
     },
     data() {
         return {
@@ -261,6 +259,7 @@ export default {
             getElementById: 'courseware-structural-elements/byId',
             getFeedbackById: 'courseware-task-feedback/byId',
             relatedTaskGroups: 'courseware-task-groups/related',
+            showTasksDistributeDialog: 'showTasksDistributeDialog'
         }),
         tasks() {
             return this.allTasks.map((task) => {
diff --git a/resources/vue/components/courseware/CoursewareDashboardViewWidget.vue b/resources/vue/components/courseware/CoursewareDashboardViewWidget.vue
deleted file mode 100644
index e0848271eaa..00000000000
--- a/resources/vue/components/courseware/CoursewareDashboardViewWidget.vue
+++ /dev/null
@@ -1,56 +0,0 @@
-<template>
-    <ul class="widget-list widget-links sidebar-views cw-view-widget">
-        <li :class="{ active: defaultView }">
-            <a href="#" @click.prevent="setDefaultView">
-                <translate>Standard</translate>
-            </a>
-        </li>
-        <li :class="{ active: taskView }">
-            <a href="#" @click.prevent="setTaskView">
-                <translate>Aufgaben</translate>
-            </a>
-        </li>
-        <li :class="{ active: activityView }">
-            <a href="#" @click.prevent="setActivityView">
-                <translate>Aktivitäten</translate>
-            </a>
-        </li>
-    </ul>
-</template>
-
-<script>
-import { mapActions, mapGetters } from 'vuex';
-
-export default {
-    name: 'courseware-dashboard-view-widget',
-    computed: {
-        ...mapGetters({
-            dashboardViewMode: 'dashboardViewMode',
-            context: 'context',
-        }),
-        defaultView() {
-            return this.dashboardViewMode === 'default';
-        },
-        taskView() {
-            return this.dashboardViewMode === 'task';
-        },
-        activityView() {
-            return this.dashboardViewMode === 'activity';
-        },
-    },
-    methods: {
-        ...mapActions({
-            setDashboardViewMode: 'setDashboardViewMode'
-        }),
-        setDefaultView() {
-            this.setDashboardViewMode('default');
-        },
-        setTaskView() {
-            this.setDashboardViewMode('task');
-        },
-        setActivityView() {
-            this.setDashboardViewMode('activity');
-        },
-    },
-};
-</script>
diff --git a/resources/vue/components/courseware/CoursewareDefaultContainer.vue b/resources/vue/components/courseware/CoursewareDefaultContainer.vue
index b1eef065d47..fd89439b355 100644
--- a/resources/vue/components/courseware/CoursewareDefaultContainer.vue
+++ b/resources/vue/components/courseware/CoursewareDefaultContainer.vue
@@ -105,6 +105,7 @@ export default {
     },
     computed: {
         ...mapGetters({
+            blockAdder: 'blockAdder',
             userId: 'userId',
             userById: 'users/byId',
             viewMode: 'viewMode'
@@ -146,6 +147,7 @@ export default {
             deleteContainer: 'deleteContainer',
             lockObject: 'lockObject',
             unlockObject: 'unlockObject',
+            coursewareBlockAdder: 'coursewareBlockAdder'
         }),
         async displayEditDialog() {
             await this.loadContainer({ id: this.container.id, options: { include: 'edit-blocker' } });
@@ -227,8 +229,8 @@ export default {
                 containerId: this.container.id,
                 structuralElementId: this.container.relationships['structural-element'].data.id,
             });
-            if(Object.keys(this.$store.getters.blockAdder).length !== 0 && this.$store.getters.blockAdder.container.id === this.container.id) {
-                this.$store.dispatch('coursewareBlockAdder', {});
+            if(Object.keys(this.blockAdder).length !== 0 && this.blockAdder.container.id === this.container.id) {
+                this.coursewareBlockAdder({});
             }
             this.showDeleteDialog = false;
         },
diff --git a/resources/vue/components/courseware/CoursewareDownloadBlock.vue b/resources/vue/components/courseware/CoursewareDownloadBlock.vue
index d5c08d2eb35..403de4b0f9b 100644
--- a/resources/vue/components/courseware/CoursewareDownloadBlock.vue
+++ b/resources/vue/components/courseware/CoursewareDownloadBlock.vue
@@ -148,6 +148,7 @@ export default {
         ...mapActions({
             loadFileRef: 'file-refs/loadById',
             updateBlock: 'updateBlockInContainer',
+            updateUserDataFields: 'courseware-user-data-fields/update'
         }),
         initCurrentData() {
             this.currentTitle = this.title;
@@ -261,7 +262,7 @@ export default {
                     }
                 }
             };
-            this.$store.dispatch('courseware-user-data-fields/update', data);
+            this.updateUserDataFields(data);
             this.userProgress = 1;
         },
     },
diff --git a/resources/vue/components/courseware/CoursewareEmptyElementBox.vue b/resources/vue/components/courseware/CoursewareEmptyElementBox.vue
index b64413d26c8..4934e05b875 100644
--- a/resources/vue/components/courseware/CoursewareEmptyElementBox.vue
+++ b/resources/vue/components/courseware/CoursewareEmptyElementBox.vue
@@ -1,5 +1,5 @@
 <template>
-    <div class="cw-wellcome-screen">
+    <div class="cw-welcome-screen">
         <courseware-companion-box :msgCompanion="this.$gettext('Es wurden bisher noch keine Inhalte eingepflegt.')">
             <template v-slot:companionActions>
                 <button v-if="canEdit && noContainers" class="button" @click="addContainer"><translate>Einen Abschnitt hinzufügen</translate></button>
@@ -10,7 +10,7 @@
 </template>
 
 <script>
-import { mapGetters } from 'vuex';
+import { mapActions, mapGetters } from 'vuex';
 import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
 
 export default {
@@ -34,16 +34,23 @@ export default {
         }
     },
     methods: {
+        ...mapActions({
+            coursewareViewMode: 'coursewareViewMode',
+            coursewareConsumeMode: 'coursewareConsumeMode',
+            coursewareContainerAdder: 'coursewareContainerAdder',
+            coursewareSelectedToolbarItem: 'coursewareSelectedToolbarItem',
+            coursewareShowToolbar: 'coursewareShowToolbar'
+        }),
         addContainer() {
-            this.$store.dispatch('coursewareViewMode', 'edit');
-            this.$store.dispatch('coursewareConsumeMode', false);
-            this.$store.dispatch('coursewareContainerAdder', true);
-            this.$store.dispatch('coursewareSelectedToolbarItem', 'blockadder');
-            this.$store.dispatch('coursewareShowToolbar', true);
+            this.coursewareViewMode('edit');
+            this.coursewareConsumeMode(false);
+            this.coursewareContainerAdder(true);
+            this.coursewareSelectedToolbarItem('blockadder');
+            this.coursewareShowToolbar(true);
         },
         switchToEditView() {
-            this.$store.dispatch('coursewareViewMode', 'edit');
-            this.$store.dispatch('coursewareConsumeMode', false);
+            this.coursewareViewMode('edit');
+            this.coursewareConsumeMode(false);
         }
     }
 
diff --git a/resources/vue/components/courseware/CoursewareExportWidget.vue b/resources/vue/components/courseware/CoursewareExportWidget.vue
index 6dea7158198..2b4b6255bf3 100644
--- a/resources/vue/components/courseware/CoursewareExportWidget.vue
+++ b/resources/vue/components/courseware/CoursewareExportWidget.vue
@@ -4,21 +4,21 @@
             <ul class="widget-list widget-links cw-export-widget" v-if="structuralElement">
                 <li v-if="showExportArchiv" class="cw-export-widget-export">
                     <button @click="exportElement">
-                        <translate>Seite exportieren</translate>
+                        {{ $gettext('Lerninhalte exportieren') }}
                     </button>
                 </li>
                 <li v-if="showExportPdf" class="cw-export-widget-export-pdf">
                     <button @click="pdfElement">
-                        <translate>Seite als pdf-Dokument exportieren</translate>
+                        {{ $gettext('PDF-Dokument erstellen') }}
                     </button>
                 </li>
                 <li v-if="showOer" class="cw-export-widget-oer">
                     <button @click="oerElement">
-                        <translate>Seite auf dem OER Campus veröffentlichen</translate>
+                        {{ $gettext('Auf OER Campus veröffentlichen') }}
                     </button>
                 </li>
                 <li v-if="!showExportArchiv && !showExportPdf && !showOer">
-                    <translate>Keine Exportoptionen verfügbar</translate>
+                    {{ $gettext('Keine Exportoptionen verfügbar') }}
                 </li>
             </ul>
         </template>
diff --git a/resources/vue/components/courseware/CoursewareHeadlineBlock.vue b/resources/vue/components/courseware/CoursewareHeadlineBlock.vue
index 98bdc9cb4f3..21179bf0aae 100644
--- a/resources/vue/components/courseware/CoursewareHeadlineBlock.vue
+++ b/resources/vue/components/courseware/CoursewareHeadlineBlock.vue
@@ -168,12 +168,13 @@
 import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue';
 import CoursewareFileChooser from './CoursewareFileChooser.vue';
 import { blockMixin } from './block-mixin.js';
+import colorMixin from '@/vue/mixins/courseware/colors.js';
 import { mapGetters, mapActions } from 'vuex';
 import contentIcons from './content-icons.js';
 
 export default {
     name: 'courseware-headline-block',
-    mixins: [blockMixin],
+    mixins: [blockMixin, colorMixin],
     components: {
         CoursewareDefaultBlock,
         CoursewareFileChooser,
@@ -245,41 +246,10 @@ export default {
             return contentIcons;
         },
         colors() {
-            const colors = [
-                {name: this.$gettext('Schwarz'), class: 'black', hex: '#000000', level: 100, icon: 'black', darkmode: true},
-                {name: this.$gettext('Weiß'), class: 'white', hex: '#ffffff', level: 100, icon: 'white', darkmode: false},
-
-                {name: this.$gettext('Blau'), class: 'studip-blue', hex: '#28497c', level: 100, icon: 'blue', darkmode: true},
-                {name: this.$gettext('Hellblau'), class: 'studip-lightblue', hex: '#e7ebf1', level: 40, icon: 'lightblue', darkmode: false},
-                {name: this.$gettext('Rot'), class: 'studip-red', hex: '#d60000', level: 100, icon: 'red', darkmode: false},
-                {name: this.$gettext('Grün'), class: 'studip-green', hex: '#008512', level: 100, icon: 'green', darkmode: true},
-                {name: this.$gettext('Gelb'), class: 'studip-yellow', hex: '#ffbd33', level: 100, icon: 'yellow', darkmode: false},
-                {name: this.$gettext('Grau'), class: 'studip-gray', hex: '#636a71', level: 100, icon: 'grey', darkmode: true},
-
-                {name: this.$gettext('Holzkohle'), class: 'charcoal', hex: '#3c454e', level: 100, icon: false, darkmode: true},
-                {name: this.$gettext('Königliches Purpur'), class: 'royal-purple', hex: '#8656a2', level: 80, icon: false, darkmode: true},
-                {name: this.$gettext('Leguangrün'), class: 'iguana-green', hex: '#66b570', level: 60, icon: false, darkmode: true},
-                {name: this.$gettext('Königin blau'), class: 'queen-blue', hex: '#536d96', level: 80, icon: false, darkmode: true},
-                {name: this.$gettext('Helles Seegrün'), class: 'verdigris', hex: '#41afaa', level: 80, icon: false, darkmode: true},
-                {name: this.$gettext('Maulbeere'), class: 'mulberry', hex: '#bf5796', level: 80, icon: false, darkmode: true},
-                {name: this.$gettext('Kürbis'), class: 'pumpkin', hex: '#f26e00', level: 100, icon: false, darkmode: true},
-                {name: this.$gettext('Sonnenschein'), class: 'sunglow', hex: '#ffca5c', level: 80, icon: false, darkmode: false},
-                {name: this.$gettext('Apfelgrün'), class: 'apple-green', hex: '#8bbd40', level: 80, icon: false, darkmode: true},
-            ];
-
-            return colors;
+            return this.mixinColors;
         },
         iconColors() {
-            const iconColors = [
-                {name: this.$gettext('Schwarz'), class: 'black', hex: '#000000'},
-                {name: this.$gettext('Weiß'), class: 'white', hex: '#ffffff'},
-                {name: this.$gettext('Blau'), class: 'studip-blue', hex: '#28497c'},
-                {name: this.$gettext('Rot'), class: 'studip-red', hex: '#d60000'},
-                {name: this.$gettext('Grün'), class: 'studip-green', hex: '#008512'},
-                {name: this.$gettext('Gelb'), class: 'studip-yellow', hex: '#ffbd33'},
-            ];
-
-            return iconColors;
+            return this.mixinColors.filter(color => color.icon && color.class !== 'studip-lightblue');
         },
         textStyle() {
             let style = {};
diff --git a/resources/vue/components/courseware/CoursewareImportWidget.vue b/resources/vue/components/courseware/CoursewareImportWidget.vue
new file mode 100644
index 00000000000..d2ed4448dcb
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareImportWidget.vue
@@ -0,0 +1,42 @@
+<template>
+    <sidebar-widget :title="$gettext('Import')">
+        <template #content>
+            <ul class="widget-list widget-links cw-import-widget">
+                <li class="cw-import-widget-archive">
+                    <button @click="importElements">
+                        {{ $gettext('Lerninhalte importieren') }}
+                    </button>
+                </li>
+                <li class="cw-import-widget-copy">
+                    <button @click="copyElements">
+                        {{ $gettext('Lerninhalte kopieren') }}
+                    </button>
+                </li>
+            </ul>
+        </template>
+    </sidebar-widget>
+</template>
+
+<script>
+import SidebarWidget from '../SidebarWidget.vue';
+import { mapActions } from 'vuex';
+
+export default {
+    name: 'courseware-import-widget',
+    components: {
+        SidebarWidget,
+    },
+    methods: {
+        ...mapActions({
+            showElementImportDialog: 'showElementImportDialog',
+            showElementCopyDialog: 'showElementCopyDialog'
+        }),
+        importElements() {
+            this.showElementImportDialog(true);
+        },
+        copyElements() {
+            this.showElementCopyDialog(true);
+        },
+    },
+}
+</script>
\ No newline at end of file
diff --git a/resources/vue/components/courseware/CoursewareKeyPointBlock.vue b/resources/vue/components/courseware/CoursewareKeyPointBlock.vue
index 2fc29b45935..b495431d6c9 100644
--- a/resources/vue/components/courseware/CoursewareKeyPointBlock.vue
+++ b/resources/vue/components/courseware/CoursewareKeyPointBlock.vue
@@ -80,13 +80,14 @@
 
 <script>
 import CoursewareDefaultBlock from './CoursewareDefaultBlock.vue';
+import colorMixin from '@/vue/mixins/courseware/colors.js';
 import { blockMixin } from './block-mixin.js';
 import { mapActions } from 'vuex';
 import contentIcons from './content-icons.js';
 
 export default {
     name: 'courseware-key-point-block',
-    mixins: [blockMixin],
+    mixins: [blockMixin, colorMixin],
     components: {
         CoursewareDefaultBlock,
     },
@@ -110,36 +111,9 @@ export default {
             return contentIcons;
         },
         colors() {
-            const colors = [
-                {name: this.$gettext('Schwarz'), class: 'black', hex: '#000000', level: 100, icon: 'black', darkmode: true},
-                {name: this.$gettext('Weiß'), class: 'white', hex: '#ffffff', level: 100, icon: 'white', darkmode: false},
-
-                {name: this.$gettext('Blau'), class: 'studip-blue', hex: '#28497c', level: 100, icon: 'blue', darkmode: true},
-                {name: this.$gettext('Hellblau'), class: 'studip-lightblue', hex: '#e7ebf1', level: 40, icon: 'lightblue', darkmode: false},
-                {name: this.$gettext('Rot'), class: 'studip-red', hex: '#d60000', level: 100, icon: 'red', darkmode: false},
-                {name: this.$gettext('Grün'), class: 'studip-green', hex: '#008512', level: 100, icon: 'green', darkmode: true},
-                {name: this.$gettext('Gelb'), class: 'studip-yellow', hex: '#ffbd33', level: 100, icon: 'yellow', darkmode: false},
-                {name: this.$gettext('Grau'), class: 'studip-gray', hex: '#636a71', level: 100, icon: 'grey', darkmode: true},
-
-                {name: this.$gettext('Holzkohle'), class: 'charcoal', hex: '#3c454e', level: 100, icon: false, darkmode: true},
-                {name: this.$gettext('Königliches Purpur'), class: 'royal-purple', hex: '#8656a2', level: 80, icon: false, darkmode: true},
-                {name: this.$gettext('Leguangrün'), class: 'iguana-green', hex: '#66b570', level: 60, icon: false, darkmode: true},
-                {name: this.$gettext('Königin blau'), class: 'queen-blue', hex: '#536d96', level: 80, icon: false, darkmode: true},
-                {name: this.$gettext('Helles Seegrün'), class: 'verdigris', hex: '#41afaa', level: 80, icon: false, darkmode: true},
-                {name: this.$gettext('Maulbeere'), class: 'mulberry', hex: '#bf5796', level: 80, icon: false, darkmode: true},
-                {name: this.$gettext('Kürbis'), class: 'pumpkin', hex: '#f26e00', level: 100, icon: false, darkmode: true},
-                {name: this.$gettext('Sonnenschein'), class: 'sunglow', hex: '#ffca5c', level: 80, icon: false, darkmode: false},
-                {name: this.$gettext('Apfelgrün'), class: 'apple-green', hex: '#8bbd40', level: 80, icon: false, darkmode: true},
-            ];
-            let iconColors = [];
-
-            colors.forEach(color => {
-                if(color.icon && color.class !== 'white' && color.class !== 'studip-lightblue') {
-                    iconColors.push(color);
-                }
-            });
-
-            return iconColors;
+            return this.mixinColors.filter(color => 
+                color.icon && color.class !== 'white' && color.class !== 'studip-lightblue'
+            );
         },
         text() {
             return this.block?.attributes?.payload?.text;
diff --git a/resources/vue/components/courseware/CoursewareManagerElement.vue b/resources/vue/components/courseware/CoursewareManagerElement.vue
index 1de92270651..232d21c053e 100644
--- a/resources/vue/components/courseware/CoursewareManagerElement.vue
+++ b/resources/vue/components/courseware/CoursewareManagerElement.vue
@@ -177,6 +177,7 @@ export default {
         ...mapGetters({
             childrenById: 'courseware-structure/children',
             containerById: 'courseware-containers/byId',
+            filingData: 'filingData',
             structuralElementById: 'courseware-structural-elements/byId',
         }),
         isCurrent() {
@@ -308,9 +309,6 @@ export default {
                 .map((id) => this.structuralElementById({ id }))
                 .filter(Boolean);
         },
-        filingData() {
-            return this.$store.getters.filingData;
-        },
         copyProcessFailedMessage() {
             let message = this.$gettext('Der Kopiervorgang ist fehlgeschlagen.');
             if (this.text.copyProcessFailed.length) {
diff --git a/resources/vue/components/courseware/CoursewareManagerFiling.vue b/resources/vue/components/courseware/CoursewareManagerFiling.vue
index fa0a7133837..71aeb612e1c 100644
--- a/resources/vue/components/courseware/CoursewareManagerFiling.vue
+++ b/resources/vue/components/courseware/CoursewareManagerFiling.vue
@@ -12,6 +12,8 @@
 </template>
 
 <script>
+import { mapActions, mapGetters } from 'vuex';
+
 export default {
     name: 'courseware-manager-filing',
     props: {
@@ -27,19 +29,22 @@ export default {
         };
     },
     computed: {
-        filingData() {
-            return this.$store.getters.filingData;
-        },
+        ...mapGetters({
+            filingData: 'filingData',
+        }),
     },
     methods: {
+        ...mapActions({
+            cwManagerFilingData: 'cwManagerFilingData'
+        }),
         toggleFiling() {
             if (this.disabled) {
                 return false;
             }
             if (this.active) {
-                this.$store.dispatch('cwManagerFilingData', {});
+                this.cwManagerFilingData({});
             } else {
-                this.$store.dispatch('cwManagerFilingData', { parentId: this.parentId, itemType: this.itemType, parentItem: this.parentItem });
+                this.cwManagerFilingData({ parentId: this.parentId, itemType: this.itemType, parentItem: this.parentItem });
             }
         },
     },
diff --git a/resources/vue/components/courseware/CoursewareRibbon.vue b/resources/vue/components/courseware/CoursewareRibbon.vue
index bcb44c64bcd..1937c9c73b1 100644
--- a/resources/vue/components/courseware/CoursewareRibbon.vue
+++ b/resources/vue/components/courseware/CoursewareRibbon.vue
@@ -51,6 +51,7 @@
 
 <script>
 import CoursewareRibbonToolbar from './CoursewareRibbonToolbar.vue';
+import { mapActions, mapGetters } from 'vuex';
 
 export default {
     name: 'courseware-ribbon',
@@ -87,12 +88,10 @@ export default {
         };
     },
     computed: {
-        consumeMode() {
-            return this.$store.getters.consumeMode;
-        },
-        toolsActive() {
-            return this.$store.getters.showToolbar;
-        },
+        ...mapGetters({
+            consumeMode: 'consumeMode',
+            toolsActive: 'showToolbar'
+        }),
         breadcrumbFallback() {
             return window.outerWidth < 1200;
         },
@@ -105,21 +104,28 @@ export default {
         }
     },
     methods: {
+        ...mapActions({
+            coursewareConsumeMode: 'coursewareConsumeMode',
+            coursewareSelectedToolbarItem: 'coursewareSelectedToolbarItem',
+            coursewareViewMode: 'coursewareViewMode',
+            coursewareShowToolbar: 'coursewareShowToolbar'
+
+        }),
         toggleConsumeMode() {
             STUDIP.Vue.emit('toggle-focus-mode', !this.consumeMode);
             if (!this.consumeMode) {
-                this.$store.dispatch('coursewareConsumeMode', true);
-                this.$store.dispatch('coursewareSelectedToolbarItem', 'contents');
-                this.$store.dispatch('coursewareViewMode', 'read');
+                this.coursewareConsumeMode(true);
+                this.coursewareSelectedToolbarItem('contents');
+                this.coursewareViewMode('read');
             } else {
-                this.$store.dispatch('coursewareConsumeMode', false);
+                this.coursewareConsumeMode(false);
             }
         },
         activeToolbar() {
-            this.$store.dispatch('coursewareShowToolbar', true);
+            this.coursewareShowToolbar(true);
         },
         deactivateToolbar() {
-            this.$store.dispatch('coursewareShowToolbar', false);
+            this.coursewareShowToolbar(false);
         },
         handleScroll() {
             if (window.outerWidth > 767) {
diff --git a/resources/vue/components/courseware/CoursewareRibbonToolbar.vue b/resources/vue/components/courseware/CoursewareRibbonToolbar.vue
index d90616c78f1..e1373ba012d 100644
--- a/resources/vue/components/courseware/CoursewareRibbonToolbar.vue
+++ b/resources/vue/components/courseware/CoursewareRibbonToolbar.vue
@@ -36,17 +36,6 @@
                                 @blockAdded="$emit('blockAdded')"
                             />
                         </courseware-tab>
-                        <courseware-tab
-                            v-if="displaySettings"
-                            :name="$gettext('Einstellungen')"
-                            :selected="showAdmin"
-                            alias="admin"
-                            :index="2"
-                        >
-                            <courseware-tools-admin
-                                id="cw-ribbon-tool-admin"
-                            />
-                        </courseware-tab>
                     </courseware-tabs>
                     <button
                         :title="$gettext('schließen')"
@@ -62,7 +51,6 @@
 <script>
 import CoursewareTabs from './CoursewareTabs.vue';
 import CoursewareTab from './CoursewareTab.vue';
-import CoursewareToolsAdmin from './CoursewareToolsAdmin.vue';
 import CoursewareToolsBlockadder from './CoursewareToolsBlockadder.vue';
 import CoursewareToolsContents from './CoursewareToolsContents.vue';
 import { FocusTrap } from 'focus-trap-vue';
@@ -73,7 +61,6 @@ export default {
     components: {
         CoursewareTabs,
         CoursewareTab,
-        CoursewareToolsAdmin,
         CoursewareToolsBlockadder,
         CoursewareToolsContents,
         FocusTrap,
@@ -97,7 +84,6 @@ export default {
     data() {
         return {
             showContents: true,
-            showAdmin: false,
             showBlockAdder: false,
             trap: false,
             initialFocusElement: null
@@ -143,11 +129,11 @@ export default {
     },
     methods: {
         ...mapActions({
-            setToolbarItem: 'coursewareSelectedToolbarItem'
+            setToolbarItem: 'coursewareSelectedToolbarItem',
+            coursewareContainerAdder: 'coursewareContainerAdder'
         }),
         selectTool(alias) {
             this.showContents = false;
-            this.showAdmin = false;
             this.showBlockAdder = false;
 
             switch (alias) {
@@ -156,10 +142,6 @@ export default {
                     this.disableContainerAdder();
                     this.scrollToCurrent();
                     break;
-                case 'admin':
-                    this.showAdmin = true;
-                    this.disableContainerAdder();
-                    break;
                 case 'blockadder':
                     this.showBlockAdder = true;
                     break;
@@ -171,7 +153,7 @@ export default {
         },
         disableContainerAdder() {
             if (this.containerAdder !== false) {
-                this.$store.dispatch('coursewareContainerAdder', false);
+                this.coursewareContainerAdder(false);
             }
         },
         scrollToCurrent() {
diff --git a/resources/vue/components/courseware/CoursewareSearchWidget.vue b/resources/vue/components/courseware/CoursewareSearchWidget.vue
index 95f71f4d5f6..255c7998080 100644
--- a/resources/vue/components/courseware/CoursewareSearchWidget.vue
+++ b/resources/vue/components/courseware/CoursewareSearchWidget.vue
@@ -1,40 +1,49 @@
 <template>
-  <form class="sidebar-search" @submit.prevent="">
-      <ul class="needles">
-          <li>
-              <div class="input-group files-search">
-                  <input
-                      type="text"
-                      v-model="searchTerm"
-                      :aria-label="$gettext('Geben Sie einen Suchbegriff mit mindestens 3 Zeichen ein.')"
-                  />
-                  <button v-if="searched" @click.prevent="setShowSearchResults(false)"
-                          class="reset-search" :title="$gettext('Suche zurücksetzen')">
-                      <studip-icon shape="decline" size="20"></studip-icon>
-                  </button>
-                  <button
-                      type="submit"
-                      :value="$gettext('Suchen')"
-                      aria-controls="search"
-                      class="submit-search"
-                      @click="loadResults"
-                  >
-                      <studip-icon shape="search" size="20"></studip-icon>
-                  </button>
-              </div>
-          </li>
-      </ul>
-  </form>
+   <sidebar-widget :title="$gettext('Suche')">
+        <template #content>
+            <form class="sidebar-search" @submit.prevent="">
+                <ul class="needles">
+                    <li>
+                        <div class="input-group files-search">
+                            <input
+                                type="text"
+                                v-model="searchTerm"
+                                :aria-label="$gettext('Geben Sie einen Suchbegriff mit mindestens 3 Zeichen ein.')"
+                            />
+                            <a v-if="showSearchResults" @click.prevent="setShowSearchResults(false)"
+                                class="reset-search">
+                                <studip-icon shape="decline" size="20"></studip-icon>
+                            </a>
+                            <button
+                                type="submit"
+                                :value="$gettext('Suchen')"
+                                aria-controls="search"
+                                class="submit-search"
+                                @click="loadResults"
+                            >
+                                <studip-icon shape="search" size="20"></studip-icon>
+                            </button>
+                        </div>
+                    </li>
+                </ul>
+            </form>
+        </template>
+   </sidebar-widget>
 </template>
 
 <script>
-import axios from 'axios';
-import { mapActions, mapGetters } from 'vuex';
+import SidebarWidget from '../SidebarWidget.vue';
 import StudipIcon from '../StudipIcon.vue';
 
+import { mapActions, mapGetters } from 'vuex';
+import axios from 'axios';
+
 export default {
     name: 'courseware-search-widget',
-    components: { StudipIcon },
+    components: { 
+        StudipIcon,
+        SidebarWidget,
+    },
     data() {
         return {
             searchTerm: ''
@@ -44,16 +53,15 @@ export default {
         ...mapGetters({
             courseware: 'courseware',
             context: 'context',
+            showSearchResults: 'showSearchResults'
         }),
-        searched() {
-            return this.$store.state.courseware.showSearchResults
-        }
     },
     methods: {
         ...mapActions({
             setShowSearchResults: 'setShowSearchResults',
             setSearchResults: 'setSearchResults',
-            companionWarning: 'companionWarning'
+            companionWarning: 'companionWarning',
+            companionError: 'companionError'
         }),
         loadResults() {
             if (this.searchTerm.length < 3) {
@@ -78,11 +86,9 @@ export default {
                     this.setSearchResults([]);
                 }
             }).catch(error => {
-                console.debug(error);
+                this.companionError({ info: this.$gettext('Bei der Anfrage ist ein Fehler aufgetreten.')});
             });
         }
     }
-
-
 }
 </script>
diff --git a/resources/vue/components/courseware/CoursewareShelfActionWidget.vue b/resources/vue/components/courseware/CoursewareShelfActionWidget.vue
new file mode 100644
index 00000000000..44282b16d90
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareShelfActionWidget.vue
@@ -0,0 +1,30 @@
+<template>
+    <sidebar-widget :title="$gettext('Aktionen')">
+        <template #content>
+            <ul class="widget-list widget-links cw-action-widget">
+                <li class="cw-action-widget-add">
+                    <button @click="setShowUnitAddDialog(true)">
+                        {{ $gettext('Lernmaterial hinzufügen') }}
+                    </button>
+                </li>
+            </ul>
+        </template>
+    </sidebar-widget>
+</template>
+
+<script>
+import SidebarWidget from '../SidebarWidget.vue';
+import { mapActions } from 'vuex';
+
+export default {
+    name: 'courseware-shelf-action-widget',
+    components: {
+        SidebarWidget
+    },
+    methods: {
+        ...mapActions({
+            setShowUnitAddDialog: 'setShowUnitAddDialog',
+        }),
+    }
+}
+</script>
\ No newline at end of file
diff --git a/resources/vue/components/courseware/CoursewareShelfDialogAdd.vue b/resources/vue/components/courseware/CoursewareShelfDialogAdd.vue
new file mode 100644
index 00000000000..80f23a3f9d7
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareShelfDialogAdd.vue
@@ -0,0 +1,277 @@
+<template>
+    <studip-wizard-dialog
+        :title="$gettext('Lernmaterial hinzufügen')"
+        :confirmText="$gettext('Erstellen')"
+        :closeText="$gettext('Abbrechen')"
+        :slots="wizardSlots"
+        :lastRequiredSlotId="1"
+        :requirements="requirements"
+        @close="setShowUnitAddDialog(false)"
+        @confirm="createUnit"
+    >
+        <template v-slot:basic>
+            <form class="default" @submit.prevent="">
+                <label>
+                    <span>{{ text.title }}</span><span aria-hidden="true" class="wizard-required">*</span>
+                    <input type="text" v-model="addWizardData.title" required/>
+                </label>
+                <label>
+                    <span>{{ text.description }}</span><span aria-hidden="true" class="wizard-required">*</span>
+                    <textarea v-model="addWizardData.description" required/>
+                </label>
+            </form>
+        </template>
+        <template v-slot:layout>
+            <form class="default" @submit.prevent="">
+                <label>
+                        {{ $gettext('Bild') }}
+                        <br>
+                        <input class="cw-file-input" ref="upload_image" type="file" accept="image/*" @change="checkUploadFile"/>
+                        <courseware-companion-box
+                            v-if="uploadFileError"
+                            :msgCompanion="uploadFileError"
+                            mood="sad"
+                            class="cw-companion-box-in-form"
+                        />
+                </label>
+                <label>
+                    {{ $gettext('Farbe') }}
+                    <studip-select
+                        v-model="addWizardData.color"
+                        :options="colors"
+                        :reduce="(color) => color.class"
+                        label="class"
+                    >
+                        <template #open-indicator="selectAttributes">
+                            <span v-bind="selectAttributes"
+                                ><studip-icon shape="arr_1down" size="10"
+                            /></span>
+                        </template>
+                        <template #no-options>
+                            {{ $gettext('Es steht keine Auswahl zur Verfügung.') }}
+                        </template>
+                        <template #selected-option="{ name, hex }">
+                            <span class="vs__option-color" :style="{ 'background-color': hex }"></span
+                            ><span>{{ name }}</span>
+                        </template>
+                        <template #option="{ name, hex }">
+                            <span class="vs__option-color" :style="{ 'background-color': hex }"></span
+                            ><span>{{ name }}</span>
+                        </template>
+                    </studip-select>
+                </label>
+            </form>
+        </template>
+        <template v-slot:advanced>
+            <form class="default" @submit.prevent="">
+                <label>
+                    {{ $gettext('Art des Lernmaterials') }}
+                    <select v-model="addWizardData.purpose">
+                        <option value="content">{{ $gettext('Inhalt') }}</option>
+                        <option value="oer">{{ $gettext('OER-Material') }}</option>
+                        <option value="portfolio">{{ $gettext('ePortfolio') }}</option>
+                        <option value="draft">{{ $gettext('Entwurf') }}</option>
+                        <option v-if="!inCourseContext" value="template">{{ $gettext('Aufgabenvorlage') }}</option>
+                        <option value="other">{{ $gettext('Sonstiges') }}</option>
+                    </select>
+                </label>
+                <label>
+                    {{ $gettext('Lizenztyp') }}
+                    <select v-model="addWizardData.license_type">
+                        <option v-for="license in licenses" :key="license.id" :value="license.id">
+                            {{ license.name }}
+                        </option>
+                    </select>
+                </label>
+                <label>
+                    {{ $gettext('Geschätzter zeitlicher Aufwand') }}
+                    <input type="text" v-model="addWizardData.required_time" />
+                </label>
+                <label>
+                    {{ $gettext('Niveau') }}<br />
+                    {{ $gettext('von') }}
+                    <select v-model="addWizardData.difficulty_start">
+                        <option
+                            v-for="difficulty_start in 12"
+                            :key="difficulty_start"
+                            :value="difficulty_start"
+                        >
+                            {{ difficulty_start }}
+                        </option>
+                    </select>
+                    {{ $gettext('bis') }}
+                    <select v-model="addWizardData.difficulty_end">
+                        <option
+                            v-for="difficulty_end in 12"
+                            :key="difficulty_end"
+                            :value="difficulty_end"
+                        >
+                            {{ difficulty_end }}
+                        </option>
+                    </select>
+                </label>
+            </form>
+        </template>
+    </studip-wizard-dialog>
+</template>
+
+<script>
+import StudipSelect from './../StudipSelect.vue';
+import StudipWizardDialog from './../StudipWizardDialog.vue';
+import colorMixin from '@/vue/mixins/courseware/colors.js';
+import { mapActions, mapGetters } from 'vuex';
+
+export default {
+    name: 'courseware-shelf-dialog-add',
+    mixins: [colorMixin],
+    components: {
+        StudipWizardDialog,
+        StudipSelect,
+    },
+    data() {
+        return {
+            wizardSlots: [
+                { id: 1, valid: false, name: 'basic', title: this.$gettext('Grundeinstellungen'), icon: 'courseware',
+                  description: this.$gettext('Wählen Sie einen kurzen, prägnanten Titel und beschreiben Sie in einigen Worten den Inhalt des Lernmaterials. Eine Beschreibung erleichtert Lernenden die Auswahl des Lernmaterials.') },
+                { id: 2, valid: true, name: 'layout', title: this.$gettext('Erscheinung'), icon: 'picture',
+                  description: this.$gettext('Ein Vorschaubild motiviert Lernende das Lernmaterial zu erkunden. Die Kombination aus Bild und Farbe erleichtert das wiederfinden des Lernmaterials in der Übersicht.') },
+                { id: 3, valid: true, name: 'advanced', title: this.$gettext('Zusatzangaben'), icon: 'doctoral_cap',
+                  description: this.$gettext('Hier können Sie detaillierte Angaben zum Lernmaterial eintragen. Diese sind besonders interessant wenn das Lernmaterial als OER geteilt wird.') }
+            ],
+            text: {
+                title: this.$gettext('Titel des Lernmaterials'),
+                description: this.$gettext('Beschreibung')
+            },
+            addWizardData: {},
+            uploadFileError: '',
+            requirements: []
+        }
+    },
+    computed: {
+        ...mapGetters({
+            licenses: 'licenses',
+            context: 'context',
+            lastCreateCoursewareUnit: 'courseware-units/lastCreated',
+            structuralElementById:  'courseware-structural-elements/byId',
+        }),
+        inCourseContext() {
+            return this.context.type === 'courses';
+        },
+        colors() {
+            return this.mixinColors.filter(color => color.darkmode);
+        }
+    },
+    mounted() {
+        this.initAddWizardData();
+    },
+    methods: {
+        ...mapActions({
+            companionError: 'companionError',
+            companionInfo: 'companionInfo',
+            companionSuccess: 'companionSuccess',
+            createCoursewareUnit: 'courseware-units/create',
+            setShowUnitAddDialog: 'setShowUnitAddDialog',
+            loadStructuralElementById: 'courseware-structural-elements/loadById',
+            uploadImageForStructuralElement: 'uploadImageForStructuralElement',
+        }),
+        initAddWizardData() {
+            this.addWizardData = {
+                title: '',
+                description: '',
+                purpose: 'content',
+                color: 'studip-blue',
+            }
+        },
+        validateSlots() {
+            let valid = true;
+            this.wizardSlots.forEach(slot => {
+                if (!slot.valid) {
+                    valid = false;
+                }
+            });
+
+            return valid;
+        },
+        checkUploadFile() {
+            const file = this.$refs?.upload_image?.files[0];
+            if (file.size > 2097152) {
+                this.uploadFileError = this.$gettext('Diese Datei ist zu groß. Bitte wählen Sie eine Datei aus, die kleiner als 2MB ist.');
+            } else if (!file.type.includes('image')) {
+                this.uploadFileError = this.$gettext('Diese Datei ist kein Bild. Bitte wählen Sie ein Bild aus.');
+            } else {
+                this.uploadFileError = '';
+            }
+        },
+        async createUnit() {
+            if (!this.validateSlots()) {
+                this.companionError({
+                    info: this.$gettext('Bitte füllen Sie alle notwendigen Angaben aus.'),
+                });
+                return false;
+            }
+            const file = this.$refs?.upload_image?.files[0];
+            const unit = {
+                attributes: {
+                    title: this.addWizardData.title,
+                    purpose: this.addWizardData.purpose,
+                    payload: {
+                        description: this.addWizardData.description,
+                        color: this.addWizardData.color,
+                        license_type: this.addWizardData.license_type,
+                        required_time: this.addWizardData.required_time,
+                        difficulty_start: this.addWizardData.difficulty_start,
+                        difficulty_end: this.addWizardData.difficulty_end
+                    }
+                },
+                relationships: {
+                    range: {
+                        data: {
+                            type: this.context.type,
+                            id: this.context.id
+                        }
+                    }
+                }
+            };
+            this.setShowUnitAddDialog(false);
+
+            await this.createCoursewareUnit(unit, { root: true });
+            this.companionSuccess({ info: this.$gettext('Neues Lernmaterial angelegt.') });
+            const newElementId = this.lastCreateCoursewareUnit.relationships['structural-element'].data.id
+            await this.loadStructuralElementById({ id: newElementId });
+            let newStructuralElement = this.structuralElementById({id: newElementId});
+            if (file) {
+                this.uploadImageForStructuralElement({
+                    structuralElement: newStructuralElement,
+                    file,
+                }).then(() => {
+                    this.loadStructuralElementById({id: newStructuralElement.id, options: {include: 'children'}});
+                })
+                .catch((error) => {
+                    console.error(error);
+                    this.companionError({ info: this.$gettext('Das Bild für das neue Lernmaterial konnte nicht gespeichert werden.') });
+                });
+            }
+        }
+    },
+    watch: {
+        addWizardData: {
+            handler(newData) {
+                this.requirements = [];
+                const slot = this.wizardSlots[0];
+                if (newData.title !== '' && newData.description !== '') {
+                    slot.valid = true;
+                }
+                if (newData.title === '' ) {
+                    slot.valid = false;
+                    this.requirements.push({slot: slot, text: this.text.title });
+                }
+                if (newData.description === '') {
+                    slot.valid = false;
+                    this.requirements.push({slot:  slot, text: this.text.description });
+                }
+            },
+            deep: true
+        }
+    }
+}
+</script>
\ No newline at end of file
diff --git a/resources/vue/components/courseware/CoursewareShelfDialogCopy.vue b/resources/vue/components/courseware/CoursewareShelfDialogCopy.vue
new file mode 100644
index 00000000000..191150fce9a
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareShelfDialogCopy.vue
@@ -0,0 +1,362 @@
+<template>
+    <studip-wizard-dialog
+        :title="$gettext('Lernmaterial kopieren')"
+        :confirmText="$gettext('Kopieren')"
+        :closeText="$gettext('Abbrechen')"
+        :lastRequiredSlotId="2"
+        :requirements="requirements"
+        :slots="wizardSlots"
+        @close="close"
+        @confirm="copy"
+    >
+        <template v-slot:source>
+            <form class="default" @submit.prevent="">
+                <fieldset class="radiobutton-set">
+                    <template v-if="inCourseContext">
+                        <input
+                            id="cw-shelf-copy-source-self"
+                            type="radio"
+                            v-model="source"
+                            value="self"
+                            :aria-description="text.sourceSelf"
+                        />
+                        <label @click="source = 'self'" for="cw-shelf-copy-source-self">
+                            <div class="icon"><studip-icon shape="seminar" size="32"/></div>
+                            <div class="text">{{ text.sourceSelf }}</div>
+                            <studip-icon shape="radiobutton-unchecked" size="24" class="unchecked" />
+                            <studip-icon shape="check-circle" size="24" class="check" />
+                        </label>
+                    </template>
+                    <input
+                        id="cw-shelf-copy-source-courses"
+                        type="radio"
+                        v-model="source"
+                        value="courses"
+                        :aria-description="text.sourceCourses"
+                    />
+                    <label @click="source = 'courses'" for="cw-shelf-copy-source-courses">
+                        <div class="icon"><studip-icon shape="seminar" size="32"/></div>
+                        <div class="text">{{ text.sourceCourses }}</div>
+                        <studip-icon shape="radiobutton-unchecked" size="24" class="unchecked" />
+                        <studip-icon shape="check-circle" size="24" class="check" />
+                    </label>
+                    <input
+                        id="cw-shelf-copy-source-users"
+                        type="radio"
+                        v-model="source"
+                        value="users"
+                        :aria-description="text.sourceUsers"
+                    />
+                    <label @click="source = 'users'" for="cw-shelf-copy-source-users">
+                        <div class="icon"><studip-icon shape="content" size="32"/></div>
+                        <div class="text">{{ text.sourceUsers }}</div>
+                        <studip-icon shape="radiobutton-unchecked" size="24" class="unchecked" />
+                        <studip-icon shape="check-circle" size="24" class="check" />
+                    </label>
+                </fieldset>
+                <label v-if="source === 'courses'">
+                    <span>{{ $gettext('Veranstaltung') }}</span><span aria-hidden="true" class="wizard-required">*</span>
+                    <studip-select
+                        v-if="courses.length !== 0 && !loadingCourses"
+                        :options="courses"
+                        label="title"
+                        :clearable="false"
+                        :reduce="option => option.id"
+                        v-model="selectedRange"
+                    >
+                        <template #open-indicator="selectAttributes">
+                            <span v-bind="selectAttributes"
+                                ><studip-icon shape="arr_1down" size="10"
+                            /></span>
+                        </template>
+                        <template #no-options="{}">
+                            {{ $gettext('Es steht keine Auswahl zur Verfügung.') }}
+                        </template>
+                        <template #selected-option="{ attributes }">
+                            <span>{{ attributes.title }}</span>
+                        </template>
+                        <template #option="{ attributes }">
+                            <span>{{ attributes.title }}</span>
+                        </template>
+                    </studip-select>
+                    <p v-if="loadingCourses">
+                        {{$gettext('Lade Veranstaltungen…')}}
+                    </p>
+                    <p v-if="courses.length === 0 && !loadingCourses">
+                        {{$gettext('Es wurden keine geeigneten Veranstaltungen gefunden.')}}
+                    </p>
+                </label>
+            </form>
+        </template>
+        <template v-slot:unit>
+            <form class="default" @submit.prevent="">
+                <fieldset v-if="units.length !== 0" class="radiobutton-set">
+                    <template v-for="unit in units">
+                        <input
+                            :id="'cw-shelf-copy-unit-' + unit.id"
+                            type="radio"
+                            v-model="selectedUnit"
+                            :checked="unit.id === selectedUnitId"
+                            :value="unit"
+                            :key="'radio-' + unit.id"
+                            :aria-description="unit.element.attributes.title"
+                        />
+                        <label @click="selectedUnit = unit" :key="'label-' + unit.id" :for="'cw-shelf-copy-unit-' + unit.id">
+                            <div class="icon"><studip-icon shape="courseware" size="32"/></div>
+                            <div class="text">{{ unit.element.attributes.title }}</div>
+                            <studip-icon shape="radiobutton-unchecked" size="24" class="unchecked" />
+                            <studip-icon shape="check-circle" size="24" class="check" />
+                        </label>
+                    </template>
+                </fieldset>
+                <courseware-companion-box
+                    v-else
+                    mood="sad"
+                    :msgCompanion="$gettext('Für die gewählte Quelle stehen keine Lernmaterialien zur Verfügung.')"
+                />
+            </form>
+        </template>
+        <template v-slot:edit>
+            <form v-if="selectedUnit" class="default" @submit.prevent="">
+                <label>
+                    <span>{{$gettext('Titel')}}</span><span aria-hidden="true" class="wizard-required">*</span>
+                    <input type="text" v-model="modifiedTitle" :placeholder="selectedUnitTitle" required />
+                </label>
+                <label>
+                    {{$gettext('Farbe')}}
+                    <studip-select
+                        v-model="modifiedColor"
+                        :options="colors"
+                        :reduce="(color) => color.class"
+                        :clearable="false"
+                        label="class"
+                    >
+                        <template #open-indicator="selectAttributes">
+                            <span v-bind="selectAttributes"
+                                ><studip-icon shape="arr_1down" size="10"
+                            /></span>
+                        </template>
+                        <template #no-options>
+                            {{ $gettext('Es steht keine Auswahl zur Verfügung.') }}
+                        </template>
+                        <template #selected-option="{ name, hex }">
+                            <span class="vs__option-color" :style="{ 'background-color': hex }"></span
+                            ><span>{{ name }}</span>
+                        </template>
+                        <template #option="{ name, hex }">
+                            <span class="vs__option-color" :style="{ 'background-color': hex }"></span
+                            ><span>{{ name }}</span>
+                        </template>
+                    </studip-select>
+                </label>
+                <label>
+                    <span>{{$gettext('Beschreibung')}}</span><span aria-hidden="true" class="wizard-required">*</span>
+                    <textarea v-model="modifiedDescription" :placeholder="selectedUnitDescription" required />
+                </label>
+            </form>
+            <courseware-companion-box
+                    v-else
+                    mood="pointing"
+                    :msgCompanion="$gettext('Bitte wählen Sie ein Lernmaterial aus.')"
+                />
+        </template>
+    </studip-wizard-dialog>
+</template>
+
+<script>
+import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
+import colorMixin from '@/vue/mixins/courseware/colors.js';
+import StudipSelect from './../StudipSelect.vue';
+import StudipWizardDialog from './../StudipWizardDialog.vue';
+
+import { mapActions, mapGetters } from 'vuex'
+
+export default {
+    name: 'courseware-shelf-dialog-copy',
+    mixins: [colorMixin],
+    components: {
+        CoursewareCompanionBox,
+        StudipWizardDialog,
+        StudipSelect,
+    },
+    data() {
+        return {
+            wizardSlots: [
+                { id: 1, valid: false, name: 'source', title: this.$gettext('Quelle'), icon: 'network',
+                  description: this.$gettext('Wählen Sie hier den Ort in Stud.IP aus, an dem sich das zu kopierende Lernmaterial befindet.') },
+                { id: 2, valid: false, name: 'unit', title: this.$gettext('Lernmaterial'), icon: 'courseware',
+                  description: this.$gettext('Wählen Sie hier das gewünschte Lernmaterial aus der Liste aus. Eine Auswahl wird durch einen grauen Hintergrund und einen Kontrollhaken angezeigt.') },
+                { id: 3, valid: true, name: 'edit', title: this.$gettext('Anpassen'), icon: 'edit',
+                  description: this.$gettext('Sie können hier die Daten des zu kopierenden Lernmaterials anpassen. Eine Anpassung ist optional, Sie können das Lernmaterial auch unverändert kopieren.') },
+            ],
+            source: '',
+            loadingCourses: false,
+            courses: [],
+            selectedRange: '',
+            loadingUnits: false,
+            selectedUnit: null,
+            selectedUnitElement: null,
+            modifiedTitle: '',
+            modifiedColor: '',
+            modifiedDescription: '',
+
+            requirements: [],
+            text: {
+                source: this.$gettext('Quelle'),
+                unit: this.$gettext('Lernmaterial'),
+                sourceSelf: this.$gettext('Diese Veranstaltung'),
+                sourceCourses: this.$gettext('Veranstaltung'),
+                sourceUsers: this.$gettext('Arbeitsplatz'),
+
+            }
+        }
+    },
+    computed: {
+        ...mapGetters({
+            userId: 'userId',
+            coursewareUnits: 'courseware-units/all',
+            structuralElementById: 'courseware-structural-elements/byId',
+            context: 'context'
+        }),
+        colors() {
+            return this.mixinColors.filter(color => color.darkmode);
+        },
+        units() {
+            let units = this.coursewareUnits.filter(unit => unit.relationships.range.data.id === this.selectedRange);
+            units.forEach(unit => {
+                unit.element = this.getUnitElement(unit);
+            });
+
+            if (this.inCourseContext) {
+                units = units.filter(unit => unit.element.attributes.purpose !== 'template');
+            }
+
+            return units;
+        },
+        selectedUnitId() {
+            return this.selectedUnit?.id;
+        },
+        inCourseContext() {
+            return this.context.type === 'courses';
+        },
+        selectedUnitTitle() {
+            return this.selectedUnitElement.attributes.title ?? '';
+        },
+        selectedUnitDescription() {
+            return this.selectedUnitElement.attributes.payload.description ?? '';
+        }
+    },
+    async mounted() {
+        this.initWizardData();
+    },
+    methods: {
+        ...mapActions({
+            companionSuccess: 'companionSuccess',
+            loadCourseUnits: 'loadCourseUnits',
+            loadUsersCourses: 'loadUsersCourses',
+            loadUserUnits: 'loadUserUnits',
+            setShowUnitCopyDialog: 'setShowUnitCopyDialog',
+            copyUnit: 'copyUnit',
+        }),
+        initWizardData() {
+            this.source = this.inCourseContext ? 'self' : 'users';
+            this.selectedRange = '';
+            this.selectedUnit = null;
+        },
+        close() {
+            this.setShowUnitCopyDialog(false);
+            this.initWizardData();
+        },
+        getUnitElement(unit) {
+            return this.structuralElementById({id: unit.relationships['structural-element'].data.id});
+        },
+        async copy() {
+            if (this.selectedUnit) {
+                const element = this.getUnitElement(this.selectedUnit);
+                const modified = {
+                        title: this.modifiedTitle !== '' ? this.modifiedTitle : this.selectedUnitTitle,
+                        color: this.modifiedColor,
+                        description: this.modifiedDescription !== '' ? this.modifiedDescription : this.selectedUnitDescription
+                }
+                await this.copyUnit({ unitId: this.selectedUnit.id, modified: modified });
+                this.companionSuccess({ info: this.$gettext('Lernmaterial kopiert.') });
+                this.close();
+            }
+        },
+        async updateCourses() {
+            this.loadingCourses = true;
+            this.courses = await this.loadUsersCourses({ userId: this.userId, withCourseware: true });
+            this.loadingCourses = false;
+        },
+        async updateCourseUnits(cid) {
+            this.loadingUnits = true;
+            await this.loadCourseUnits(cid);
+            this.loadingUnits = false;
+        },
+        setElementData() {
+            this.selectedUnitElement = this.getUnitElement(this.selectedUnit);
+            this.modifiedTitle = this.selectedUnitElement.attributes.title;
+            this.modifiedColor = this.selectedUnitElement.attributes.payload.color;
+            this.modifiedDescription = this.selectedUnitElement.attributes.payload.description;
+        },
+        resetElementData() {
+            this.modifiedTitle = '';
+            this.modifiedColor = '';
+            this.modifiedDescription = '';
+        },
+        validateSelection() {
+            this.requirements = [];
+            if (this.selectedRange === '') {
+                this.requirements.push({slot: this.wizardSlots[0], text: this.text.source });
+            }
+            if (this.selectedUnit === null) {
+                this.requirements.push({slot: this.wizardSlots[1], text: this.text.unit });
+            }
+        }
+    },
+    watch: {
+        selectedUnit(newUnit) {
+            this.validateSelection();
+            const slot = this.wizardSlots[1];
+            if (newUnit !== null) {
+                slot.valid = true;
+                this.setElementData();
+            } else {
+                slot.valid = false;
+                this.resetElementData();
+            }
+        },
+        selectedRange(newRid) {
+            this.selectedUnit = null;
+            this.validateSelection();
+            const slot = this.wizardSlots[0];
+            
+            if (newRid !== '') {
+                slot.valid = true;
+                if (this.source === 'courses' || this.source === 'self') {
+                    this.updateCourseUnits(newRid);
+                }
+                if (this.source === 'users') {
+                    this.loadUserUnits(newRid);
+                }
+            } else {
+                slot.valid = false;
+            }
+        },
+        source(newSource) {
+            switch (newSource) {
+                case 'self':
+                    this.selectedRange = this.context.id;
+                    break;
+                case 'courses':
+                    this.selectedRange = '';
+                    this.updateCourses();
+                    break;
+                case 'users':
+                    this.selectedRange = this.userId;
+                    break;
+            }
+        }
+    }
+}
+</script>
\ No newline at end of file
diff --git a/resources/vue/components/courseware/CoursewareShelfDialogImport.vue b/resources/vue/components/courseware/CoursewareShelfDialogImport.vue
new file mode 100644
index 00000000000..171f34dfc48
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareShelfDialogImport.vue
@@ -0,0 +1,371 @@
+<template>
+    <div class="cw-shelf-dialog-import-wrapper">
+        <studip-wizard-dialog
+            v-if="!importRunning"
+            :title="$gettext('Lernmaterial importieren')"
+            :confirmText="$gettext('Importieren')"
+            :closeText="$gettext('Abbrechen')"
+            :slots="wizardSlots"
+            :lastRequiredSlotId="1"
+            :requirements="requirements"
+            @close="setShowUnitImportDialog(false)"
+            @confirm="importCoursewareArchiv"
+        >
+            <template v-slot:file>
+                <form class="default" @submit.prevent="">
+                    <label>
+                        <span>{{ text.import }}</span><span aria-hidden="true" class="wizard-required">*</span>
+                        <input v-show="importZipFile === null" ref="fileInput" class="cw-file-input" type="file" accept=".zip" @change="setImport" />
+                        <p v-show="importZipFile !== null" class="cw-file-input-change">
+                            <button class="button" @click="$refs.fileInput.click()">{{ $gettext('Datei ändern')}}</button><span>{{ importZipFile?.name }}</span>
+                        </p>
+                    </label>
+                    <fieldset v-show="archiveErrors.length > 0">
+                        <legend>{{$gettext('Fehler im Import-Archiv')}}</legend>
+                        <ul>
+                            <li v-for="(error, index) in archiveErrors" :key="index"> {{error}} </li>
+                        </ul>
+                    </fieldset>
+                </form>
+            </template>
+            <template v-slot:edit>
+                <form v-if="hasValidFile" class="default" @submit.prevent="">
+                    <label>
+                        {{ text.title }}
+                        <input type="text" v-model="modifiedData.title" :placeholder="loadedTitle" required />
+                    </label>
+                    <label>
+                        {{ text.color }}
+                        <studip-select
+                            v-model="modifiedData.color"
+                            :options="colors"
+                            :reduce="(color) => color.class"
+                            :clearable="false"
+                            label="class"
+                        >
+                            <template #open-indicator="selectAttributes">
+                                <span v-bind="selectAttributes"
+                                    ><studip-icon shape="arr_1down" size="10"
+                                /></span>
+                            </template>
+                            <template #no-options>
+                                {{$gettext('Es steht keine Auswahl zur Verfügung.')}}
+                            </template>
+                            <template #selected-option="{ name, hex }">
+                                <span class="vs__option-color" :style="{ 'background-color': hex }"></span
+                                ><span>{{ name }}</span>
+                            </template>
+                            <template #option="{ name, hex }">
+                                <span class="vs__option-color" :style="{ 'background-color': hex }"></span
+                                ><span>{{ name }}</span>
+                            </template>
+                        </studip-select>
+                    </label>
+                    <label>
+                        {{ text.description }}
+                        <textarea v-model="modifiedData.description" :placeholder="loadedDescription" required />
+                    </label>
+                </form>
+                <courseware-companion-box 
+                    v-else
+                    :msgCompanion="$gettext('Bitte wählen Sie ein Import-Archiv aus.')"
+                    mood="unsure"
+                />
+            </template>
+        </studip-wizard-dialog>
+        <studip-dialog
+            v-if="importRunning"
+            :title="$gettext('Lernmaterial importieren')"
+            :closeText="$gettext('Schließen')"
+            height="420"
+            @close="setShowUnitImportDialog(false)"
+        >
+            <template v-slot:dialogContent>
+                <div role="status" aria-live="polite">
+                    <courseware-companion-box 
+                        v-show="importDone && importErrors.length === 0"
+                        :msgCompanion="$gettext('Import erfolgreich!')"
+                        mood="special"
+                    />
+                    <courseware-companion-box
+                        v-show="importDone && importErrors.length > 0"
+                        :msgCompanion="$gettext('Import abgeschlossen. Es sind Fehler aufgetreten!')"
+                        mood="unsure"
+                    />
+                    <courseware-companion-box
+                        v-show="!importDone"
+                        :msgCompanion="$gettext('Import läuft. Bitte schließen Sie den Dialog nicht bis der Import abgeschlossen wurde.')"
+                        mood="pointing"
+                    />
+                </div>
+                <form v-if="!importDone" class="default" @submit.prevent="">
+                    <fieldset>
+                        <div v-if="!fileImportDone" class="cw-import-zip">
+                            <header>{{$gettext('Importiere Dateien')}}:</header>
+                            <div class="progress-bar-wrapper">
+                                <div class="progress-bar" role="progressbar" :style="{width: importFilesProgress + '%'}" :aria-valuenow="importFilesProgress" aria-valuemin="0" aria-valuemax="100">{{ importFilesProgress }}%</div>
+                            </div>
+                            {{ importFilesState }}
+                        </div>
+                        <div v-if="fileImportDone" class="cw-import-zip">
+                            <header>{{$gettext('Importiere Elemente')}}:</header>
+                            <div class="progress-bar-wrapper">
+                                <div class="progress-bar" role="progressbar" :style="{width: importStructuresProgress + '%'}" :aria-valuenow="importStructuresProgress" aria-valuemin="0" aria-valuemax="100">{{ importStructuresProgress }}%</div>
+                            </div>
+                            {{ importStructuresState }}
+                        </div>
+                    </fieldset>
+                    <fieldset v-show="importErrors.length > 0">
+                        <legend>{{$gettext('Fehlermeldungen')}}</legend>
+                        <ul>
+                            <li v-for="(error, index) in importErrors" :key="index"> {{error}} </li>
+                        </ul>
+                    </fieldset>
+                </form>
+            </template>
+        </studip-dialog>
+    </div>
+</template>
+
+<script>
+import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
+import CoursewareImport from '@/vue/mixins/courseware/import.js';
+import colorMixin from '@/vue/mixins/courseware/colors.js';
+import StudipWizardDialog from './../StudipWizardDialog.vue';
+
+import { mapActions, mapGetters } from 'vuex'
+import JSZip from 'jszip';
+
+export default {
+    name: 'courseware-shelf-dialog-import',
+    components: {
+        StudipWizardDialog,
+        CoursewareCompanionBox
+    },
+    mixins: [CoursewareImport, colorMixin],
+    data() {
+        return {
+            wizardSlots: [
+                { id: 1, valid: false, name: 'file', title: this.$gettext('Import-Archiv'), icon: 'file-archive',
+                  description: this.$gettext('Wählen Sie hier eine Courseware-Export-Archiv-Datei von Ihrer Festplatte aus. Bei Courseware-Export-Archiven handelt es sich um Zip-Dateien. Diese sollten mindestens die Dateien files.json und courseware.json enthalten.') },
+                { id: 2, valid: true, name: 'edit', title: this.$gettext('Anpassen'), icon: 'edit', description: this.$gettext('Sie können hier die Daten des zu importierenden Lernmaterials anpassen. Eine Anpassung ist optional, Sie können das Archiv auch unverändert importieren.') },
+            ],
+            modifiedData: {
+                title: '',
+                color: 'studip-blue',
+                description: ''
+            },
+            importArchivFile: null,
+            importRunning: false,
+            importZipFile: null,
+            zip: null,
+
+            loadedZipData: null,
+            archiveErrors: [],
+
+            requirements: [],
+            text: {
+                import: this.$gettext('Importdatei'),
+                title: this.$gettext('Titel'),
+                color: this.$gettext('Farbe'),
+                description: this.$gettext('Beschreibung'),
+            }
+        }
+    },
+    computed: {
+        ...mapGetters({
+            context: 'context',
+            importFilesState: 'importFilesState',
+            importFilesProgress: 'importFilesProgress',
+            importStructuresState: 'importStructuresState',
+            importStructuresProgress: 'importStructuresProgress',
+            importErrors: 'importErrors',
+            lastCreateCoursewareUnit: 'courseware-units/lastCreated',
+            
+        }),
+        colors() {
+            return this.mixinColors.filter(color => color.darkmode);
+        },
+        fileImportDone() {
+            return this.importFilesProgress === 100;
+        },
+        importDone() {
+            return this.importFilesProgress === 100 && this.importStructuresProgress === 100;
+        },
+        hasValidFile() {
+            return this.archiveErrors.length === 0 && this.loadedZipData !== null;
+        },
+        loadedTitle() {
+            return this.loadedZipData.courseware.attributes.title ?? '';
+        },
+        loadedDescription() {
+            return this.loadedZipData.courseware.attributes.payload.description ?? '';
+        }
+    },
+    methods: {
+        ...mapActions({
+            setShowUnitImportDialog: 'setShowUnitImportDialog',
+            createCoursewareUnit: 'courseware-units/create',
+            setImportFilesProgress: 'setImportFilesProgress',
+            setImportStructuresProgress: 'setImportStructuresProgress',
+            setImportErrors: 'setImportErrors',
+            loadStructuralElementById: 'courseware-structural-elements/loadById',
+            companionSuccess: 'companionSuccess',
+        }),
+        setImport(event) {
+            this.importZipFile = event.target.files[0];
+            this.loadZipData(); 
+        },
+
+        async loadZipData() {
+            const slot = this.wizardSlots[0];
+            const text = this.text.import;
+            this.archiveErrors = [];
+            this.loadedZipData = null;
+            this.modifiedData.title = '';
+            this.modifiedData.color = 'studip-blue';
+            this.modifiedData.description = '';
+            let filesError = false;
+            if (!this.importZipFile.type.includes('zip')) {
+                this.archiveErrors.push(this.$gettext('Die gewählte Datei ist kein Archiv.'));
+                filesError = true;
+            }
+            if (!filesError) {
+                try {
+                    this.zip = await JSZip.loadAsync(this.importZipFile);
+                } catch(error) {
+                    this.zip = null;
+                    this.archiveErrors.push(this.$gettext('Beim laden des Archivs ist ein Fehler aufgetreten. Vermutlich ist das Archiv beschädigt.'));
+                    filesError = true;
+                }
+                
+                if (this.zip) {
+                    if (this.zip.file('courseware.json') === null) {
+                        this.archiveErrors.push(this.$gettext('Das Archiv enthält keine courseware.json Datei.'));
+                        filesError = true;
+                    }
+                    if (this.zip.file('files.json') === null) {
+                        this.archiveErrors.push(this.$gettext('Das Archiv enthält keine files.json Datei.'));
+                        filesError = true;
+                    }
+                    if (this.zip.file('data.xml') !== null) {
+                        this.archiveErrors.push(this.$gettext(
+                            'Das Archiv enthält eine data.xml Datei. Möglicherweise handelt es sich um einen Export aus dem Courseware-Plugin. Diese Archive sind nicht kompatibel mit dieser Courseware.'
+                        ));
+                        filesError = true;
+                    }
+                }
+            }
+            if (filesError) {
+                this.updateRequirements(slot, text, false);
+                slot.valid = false;
+                return;
+            } else {
+                this.updateRequirements(slot, text, true);
+                slot.valid = true;
+            }
+
+            let data = await this.zip.file('courseware.json').async('string');
+            let courseware = null;
+            let data_settings = null;
+            let settings = null;
+            let data_files = await this.zip.file('files.json').async('string');
+            let files = null;
+            let jsonErrors = false;
+
+            try {
+                courseware = JSON.parse(data);
+            } catch (error) {
+                jsonErrors = true;
+                this.archiveErrors.push(this.$gettext('Die Beschreibung der Courseware-Inhalte ist nicht valide.'));
+                this.archiveErrors.push(error);
+            }
+
+            if (this.zip.file('settings.json') !== null) {
+                data_settings = await this.zip.file('settings.json').async('string');
+                try {
+                    settings = JSON.parse(data_settings);
+                } catch (error) {
+                    jsonErrors = true;
+                    this.archiveErrors.push(this.$gettext('Die Beschreibung der Courseware-Einstellungen ist nicht valide.'));
+                    this.archiveErrors.push(error);
+                }
+            }
+
+            try {
+                files = JSON.parse(data_files);
+            } catch (error) {
+                jsonErrors = true;
+                this.archiveErrors.push(this.$gettext('Die Beschreibung der Dateien ist nicht valide.'));
+                this.archiveErrors.push(error);
+            }
+            if (jsonErrors) {
+                return;
+            }
+
+            this.loadedZipData = {
+                courseware: courseware,
+                files: files,
+                settings: settings
+            }
+
+            this.modifiedData.title = courseware.attributes.title;
+            this.modifiedData.color = courseware.attributes.payload.color ?? 'studip-blue';
+            this.modifiedData.description = courseware.attributes.payload.description ?? '';
+        },
+
+        async importCoursewareArchiv() {
+            if (this.loadedZipData === null) {
+                return false;
+            }
+
+            this.setImportFilesProgress(0);
+            this.setImportStructuresProgress(0);
+            this.setImportErrors([]);
+
+            this.importRunning = true;
+
+            const title = this.modifiedData.title !==  '' ? this.modifiedData.title : this.loadedTitle;
+            const description = this.modifiedData.description !==  '' ? this.modifiedData.description : this.loadedDescription;
+
+            const unit = {
+                    attributes: {
+                        title: title,
+                        payload: {
+                            description: description,
+                            color: this.modifiedData.color,
+                        }
+                    },
+                    relationships: {
+                        range: {
+                            data: {
+                                type: this.context.type,
+                                id: this.context.id
+                            }
+                        }
+                    }
+                };
+            await this.createCoursewareUnit(unit, { root: true });
+            const newElementId = this.lastCreateCoursewareUnit.relationships['structural-element'].data.id;
+            await this.loadStructuralElementById({ id: newElementId });
+
+            const newStructuralElement = this.structuralElementById({id: newElementId});
+
+            await this.importCourseware(this.loadedZipData.courseware, newStructuralElement.id, this.loadedZipData.files, 'migrate', this.loadedZipData.settings);
+            this.companionSuccess({ info: this.$gettext('Lernmaterial importiert.') });
+        },
+        updateRequirements(slot, text, valid) {
+            const index = this.requirements.findIndex(req =>  req.slot.id === slot.id && req.text === text);
+            if (valid) {
+                if (index !== -1) {
+                    this.requirements.splice(index, 1);
+                }
+            } else {
+                if (index === -1) {
+                   this.requirements.push({slot: slot, text: text});
+                }
+            }
+        }
+    }
+}
+</script>
\ No newline at end of file
diff --git a/resources/vue/components/courseware/CoursewareShelfImportWidget.vue b/resources/vue/components/courseware/CoursewareShelfImportWidget.vue
new file mode 100644
index 00000000000..2c946e2a0bb
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareShelfImportWidget.vue
@@ -0,0 +1,37 @@
+<template>
+    <sidebar-widget :title="$gettext('Import')">
+        <template #content>
+            <ul class="widget-list widget-links cw-import-widget">
+                <li class="cw-import-widget-archive">
+                    <button @click="setShowUnitImportDialog(true)">
+                        {{ $gettext('Lernmaterial importieren') }}
+                    </button>
+                </li>
+                <li class="cw-import-widget-copy">
+                    <button @click="setShowUnitCopyDialog(true)">
+                        {{ $gettext('Lernmaterial kopieren') }}
+                    </button>
+                </li>
+            </ul>
+        </template>
+    </sidebar-widget>
+</template>
+
+<script>
+import SidebarWidget from '../SidebarWidget.vue';
+
+import { mapActions, mapGetters } from 'vuex';
+
+export default {
+    name: 'courseware-shelf-import-widget',
+    components: {
+        SidebarWidget,
+    },
+    methods: {
+        ...mapActions({
+            setShowUnitCopyDialog: 'setShowUnitCopyDialog',
+            setShowUnitImportDialog: 'setShowUnitImportDialog',
+        }),
+    }
+}
+</script>
\ No newline at end of file
diff --git a/resources/vue/components/courseware/CoursewareStructuralElement.vue b/resources/vue/components/courseware/CoursewareStructuralElement.vue
index 474bad1d618..7057cd4fbd9 100644
--- a/resources/vue/components/courseware/CoursewareStructuralElement.vue
+++ b/resources/vue/components/courseware/CoursewareStructuralElement.vue
@@ -89,7 +89,7 @@
                             :canEdit="canEdit"
                             :noContainers="noContainers"
                         />
-                        <courseware-wellcome-screen v-if="noContainers && isRoot && canEdit" />
+                        <courseware-welcome-screen v-if="noContainers && isRoot && canEdit" />
                     </div>
 
                     <div
@@ -357,6 +357,7 @@
                     :closeText="$gettext('Schließen')"
                     closeClass="cancel"
                     class="cw-structural-element-dialog"
+                    :height="inCourse ? '300' : '430'"
                     @close="closeAddDialog"
                     @confirm="createElement"
                 >
@@ -375,6 +376,30 @@
                                 <translate>Name der neuen Seite</translate><br />
                                 <input v-model="newChapterName" type="text" />
                             </label>
+                            <label v-if="!inCourse">
+                                <translate>Art des Lernmaterials</translate>
+                                <select v-model="newChapterPurpose">
+                                    <option value="content"><translate>Inhalt</translate></option>
+                                    <option v-if="!inCourse" value="template"><translate>Aufgabenvorlage</translate></option>
+                                    <option value="oer"><translate>OER-Material</translate></option>
+                                    <option value="portfolio"><translate>ePortfolio</translate></option>
+                                    <option value="draft"><translate>Entwurf</translate></option>
+                                    <option value="other"><translate>Sonstiges</translate></option>
+                                </select>
+                            </label>
+                            <label v-if="!inCourse">
+                                <translate>Lernmaterialvorlage</translate>
+                                <select v-model="newChapterTemplate">
+                                    <option :value="null"><translate>ohne Vorlage</translate></option>
+                                    <option
+                                        v-for="template in selectableTemplates"
+                                        :key="template.id"
+                                        :value="template"
+                                    >
+                                        {{ template.attributes.name }}
+                                    </option>
+                                </select>
+                            </label>
                         </form>
                     </template>
                 </studip-dialog>
@@ -587,15 +612,15 @@
                     @close="closeDeleteDialog"
                 ></studip-dialog>
                 <studip-dialog
-                    v-if="showLinkDialog"
+                    v-if="showPublicLinkDialog && inContent"
                     :title="$gettext('Öffentlichen Link für Seite erzeugen')"
                     :confirmText="$gettext('Erstellen')"
                     confirmClass="accept"
-                    :closeText="$gettext('Schließen')"
+                    :closeText="$gettext('Abbrechen')"
                     closeClass="cancel"
                     class="cw-structural-element-dialog"
-                    @close="closeLinkDialog"
-                    @confirm="createElementLink"
+                    @close="closePublicLinkDialog"
+                    @confirm="createElementPublicLink"
                 >
                     <template v-slot:dialogContent>
                         <form class="default" @submit.prevent="">
@@ -619,6 +644,10 @@
                     @confirm="executeRemoveLock"
                     @close="showElementRemoveLockDialog(false)"
                 ></studip-dialog>
+
+                <courseware-structural-element-dialog-import v-if="showImportDialog"/>
+                <courseware-structural-element-dialog-copy v-if="showCopyDialog" />
+                <courseware-structural-element-dialog-link v-if="showLinkDialog"/>
             </div>
             <div v-else>
                 <courseware-companion-box
@@ -634,12 +663,15 @@
 <script>
 import ContainerComponents from './container-components.js';
 import CoursewarePluginComponents from './plugin-components.js';
+import CoursewareStructuralElementDialogCopy from './CoursewareStructuralElementDialogCopy.vue';
+import CoursewareStructuralElementDialogImport from './CoursewareStructuralElementDialogImport.vue';
+import CoursewareStructuralElementDialogLink from './CoursewareStructuralElementDialogLink.vue';
+import CoursewareStructuralElementDiscussion from './CoursewareStructuralElementDiscussion.vue';
 import CoursewareStructuralElementPermissions from './CoursewareStructuralElementPermissions.vue';
 import CoursewareContentPermissions from './CoursewareContentPermissions.vue';
-import CoursewareStructuralElementDiscussion from './CoursewareStructuralElementDiscussion.vue';
 import CoursewareAccordionContainer from './CoursewareAccordionContainer.vue';
 import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
-import CoursewareWellcomeScreen from './CoursewareWellcomeScreen.vue';
+import CoursewareWelcomeScreen from './CoursewareWelcomeScreen.vue';
 import CoursewareEmptyElementBox from './CoursewareEmptyElementBox.vue';
 import CoursewareListContainer from './CoursewareListContainer.vue';
 import CoursewareTabsContainer from './CoursewareTabsContainer.vue';
@@ -648,6 +680,7 @@ import CoursewareTabs from './CoursewareTabs.vue';
 import CoursewareTab from './CoursewareTab.vue';
 import CoursewareExport from '@/vue/mixins/courseware/export.js';
 import CoursewareOerMessage from '@/vue/mixins/courseware/oermessage.js';
+import colorMixin from '@/vue/mixins/courseware/colors.js';
 import CoursewareDateInput from './CoursewareDateInput.vue';
 import { FocusTrap } from 'focus-trap-vue';
 import IsoDate from './IsoDate.vue';
@@ -658,6 +691,9 @@ import { mapActions, mapGetters } from 'vuex';
 export default {
     name: 'courseware-structural-element',
     components: {
+        CoursewareStructuralElementDialogCopy,
+        CoursewareStructuralElementDialogImport,
+        CoursewareStructuralElementDialogLink,
         CoursewareStructuralElementDiscussion,
         CoursewareStructuralElementPermissions,
         CoursewareContentPermissions,
@@ -666,7 +702,7 @@ export default {
         CoursewareAccordionContainer,
         CoursewareTabsContainer,
         CoursewareCompanionBox,
-        CoursewareWellcomeScreen,
+        CoursewareWelcomeScreen,
         CoursewareEmptyElementBox,
         CoursewareTabs,
         CoursewareTab,
@@ -678,12 +714,14 @@ export default {
     },
     props: ['canVisit', 'orderedStructuralElements', 'structuralElement'],
 
-    mixins: [CoursewareExport, CoursewareOerMessage],
+    mixins: [CoursewareExport, CoursewareOerMessage, colorMixin],
 
     data() {
         return {
             newChapterName: '',
             newChapterParent: 'descendant',
+            newChapterPurpose: 'content',
+            newChapterTemplate: null,
             currentElement: '',
             uploadFileError: '',
             textCompanionWrongContext: this.$gettext('Die angeforderte Seite ist nicht Teil dieser Courseware.'),
@@ -762,13 +800,16 @@ export default {
             pluginManager: 'pluginManager',
             showEditDialog: 'showStructuralElementEditDialog',
             showAddDialog: 'showStructuralElementAddDialog',
+            showImportDialog: 'showStructuralElementImportDialog',
+            showCopyDialog: 'showStructuralElementCopyDialog',
+            showLinkDialog: 'showStructuralElementLinkDialog',
             showExportDialog: 'showStructuralElementExportDialog',
             showPdfExportDialog: 'showStructuralElementPdfExportDialog',
             showInfoDialog: 'showStructuralElementInfoDialog',
             showDeleteDialog: 'showStructuralElementDeleteDialog',
             showOerDialog: 'showStructuralElementOerDialog',
             showSuggestOerDialog: 'showSuggestOerDialog',
-            showLinkDialog: 'showStructuralElementLinkDialog',
+            showPublicLinkDialog: 'showStructuralElementPublicLinkDialog',
             showRemoveLockDialog: 'showStructuralElementRemoveLockDialog',
             oerEnabled: 'oerEnabled',
             licenses: 'licenses',
@@ -778,11 +819,14 @@ export default {
             viewMode: 'viewMode',
             taskById: 'courseware-tasks/byId',
             userById: 'users/byId',
+            lastCreatedElement: 'courseware-structural-elements/lastCreated',
 
             blocked: 'currentElementBlocked',
             blockerId: 'currentElementBlockerId',
             blockedByThisUser: 'currentElementBlockedByThisUser',
             blockedByAnotherUser: 'currentElementBlockedByAnotherUser',
+
+            templates: 'courseware-templates/all',
         }),
 
         currentId() {
@@ -791,27 +835,27 @@ export default {
 
         textOer() {
             return {
-                title: this.$gettext('Seite auf dem OER Campus veröffentlichen'),
+                title: this.$gettext('Lerninhalte auf dem OER Campus veröffentlichen'),
                 confirm: this.$gettext('Veröffentlichen'),
-                close: this.$gettext('Schließen'),
+                close: this.$gettext('Abbrechen'),
             };
         },
 
         textSuggestOer() {
             return {
-                title: this.$gettext('Material für den OER Campus vorschlagen'),
-                confirm: this.$gettext('Material vorschlagen'),
-                close: this.$gettext('Schließen'),
+                title: this.$gettext('Lerninhalt für den OER Campus vorschlagen'),
+                confirm: this.$gettext('Lerninhalt vorschlagen'),
+                close: this.$gettext('Abbrechen'),
             };
         },
 
         inCourse() {
-            return this.$store.getters.context.type === 'courses';
+            return this.context.type === 'courses';
         },
 
         inContent() {
             // The rights tab in contents will be only visible to the owner.
-            return this.$store.getters.context.type === 'users' && this.userId === this.currentElement.relationships.user.data.id;
+            return this.context.type === 'users' && this.userId === this.currentElement.relationships.user.data.id;
         },
 
         textDelete() {
@@ -831,31 +875,30 @@ export default {
 
         validContext() {
             let valid = false;
-            let context = this.$store.getters.context;
-            if (context.type === 'courses' && this.currentElement.relationships) {
+            if (this.context.type === 'courses' && this.currentElement.relationships) {
                 if (
                     this.currentElement.relationships.course &&
-                    context.id === this.currentElement.relationships.course.data.id
+                    this.context.id === this.currentElement.relationships.course.data.id
                 ) {
                     valid = true;
                 }
             }
 
-            if (context.type === 'users' && this.currentElement.relationships) {
+            if (this.context.type === 'users' && this.currentElement.relationships) {
                 if (
                     this.currentElement.relationships.user &&
-                    context.id === this.currentElement.relationships.user.data.id
+                    this.context.id === this.currentElement.relationships.user.data.id
                 ) {
                     valid = true;
                 }
             }
-            if (context.type === 'sharedusers') {
-                if (context.id === this.courseware.relationships.root.data.id) {
+            if (this.context.type === 'sharedusers') {
+                if (this.context.id === this.courseware.relationships.root.data.id) {
                     valid = true;
                 }
             }
 
-            if (context.type === 'public') {
+            if (this.context.type === 'public') {
                 valid = true;
             }
 
@@ -988,7 +1031,7 @@ export default {
             let menu = [
                 { id: 4, label: this.$gettext('Informationen anzeigen'), icon: 'info', emit: 'showInfo' },
                 { id: 5, label: this.$gettext('Lesezeichen setzen'), icon: 'star', emit: 'setBookmark' },
-                { id: 6, label: this.$gettext('Material für den OER Campus vorschlagen'), icon: 'oer-campus', emit: 'showSuggest' },
+                { id: 6, label: this.$gettext('Lerninhalt für OER Campus vorschlagen'), icon: 'oer-campus', emit: 'showSuggest' },
 
             ];
             if (this.canEdit) {
@@ -1026,154 +1069,7 @@ export default {
             return menu;
         },
         colors() {
-            const colors = [
-                {
-                    name: this.$gettext('Schwarz'),
-                    class: 'black',
-                    hex: '#000000',
-                    level: 100,
-                    icon: 'black',
-                    darkmode: true,
-                },
-                {
-                    name: this.$gettext('Weiß'),
-                    class: 'white',
-                    hex: '#ffffff',
-                    level: 100,
-                    icon: 'white',
-                    darkmode: false,
-                },
-
-                {
-                    name: this.$gettext('Blau'),
-                    class: 'studip-blue',
-                    hex: '#28497c',
-                    level: 100,
-                    icon: 'blue',
-                    darkmode: true,
-                },
-                {
-                    name: this.$gettext('Hellblau'),
-                    class: 'studip-lightblue',
-                    hex: '#e7ebf1',
-                    level: 40,
-                    icon: 'lightblue',
-                    darkmode: false,
-                },
-                {
-                    name: this.$gettext('Rot'),
-                    class: 'studip-red',
-                    hex: '#d60000',
-                    level: 100,
-                    icon: 'red',
-                    darkmode: false,
-                },
-                {
-                    name: this.$gettext('Grün'),
-                    class: 'studip-green',
-                    hex: '#008512',
-                    level: 100,
-                    icon: 'green',
-                    darkmode: true,
-                },
-                {
-                    name: this.$gettext('Gelb'),
-                    class: 'studip-yellow',
-                    hex: '#ffbd33',
-                    level: 100,
-                    icon: 'yellow',
-                    darkmode: false,
-                },
-                {
-                    name: this.$gettext('Grau'),
-                    class: 'studip-gray',
-                    hex: '#636a71',
-                    level: 100,
-                    icon: 'grey',
-                    darkmode: true,
-                },
-
-                {
-                    name: this.$gettext('Holzkohle'),
-                    class: 'charcoal',
-                    hex: '#3c454e',
-                    level: 100,
-                    icon: false,
-                    darkmode: true,
-                },
-                {
-                    name: this.$gettext('Königliches Purpur'),
-                    class: 'royal-purple',
-                    hex: '#8656a2',
-                    level: 80,
-                    icon: false,
-                    darkmode: true,
-                },
-                {
-                    name: this.$gettext('Leguangrün'),
-                    class: 'iguana-green',
-                    hex: '#66b570',
-                    level: 60,
-                    icon: false,
-                    darkmode: true,
-                },
-                {
-                    name: this.$gettext('Königin blau'),
-                    class: 'queen-blue',
-                    hex: '#536d96',
-                    level: 80,
-                    icon: false,
-                    darkmode: true,
-                },
-                {
-                    name: this.$gettext('Helles Seegrün'),
-                    class: 'verdigris',
-                    hex: '#41afaa',
-                    level: 80,
-                    icon: false,
-                    darkmode: true,
-                },
-                {
-                    name: this.$gettext('Maulbeere'),
-                    class: 'mulberry',
-                    hex: '#bf5796',
-                    level: 80,
-                    icon: false,
-                    darkmode: true,
-                },
-                {
-                    name: this.$gettext('Kürbis'),
-                    class: 'pumpkin',
-                    hex: '#f26e00',
-                    level: 100,
-                    icon: false,
-                    darkmode: true,
-                },
-                {
-                    name: this.$gettext('Sonnenschein'),
-                    class: 'sunglow',
-                    hex: '#ffca5c',
-                    level: 80,
-                    icon: false,
-                    darkmode: false,
-                },
-                {
-                    name: this.$gettext('Apfelgrün'),
-                    class: 'apple-green',
-                    hex: '#8bbd40',
-                    level: 80,
-                    icon: false,
-                    darkmode: true,
-                },
-            ];
-            let elementColors = [];
-            colors.forEach((color) => {
-                if (color.darkmode) {
-                    elementColors.push(color);
-                }
-            });
-
-            return elementColors;
+            return this.mixinColors.filter(color => color.darkmode);
         },
         currentLicenseName() {
             for (let i = 0; i < this.licenses.length; i++) {
@@ -1313,6 +1209,11 @@ export default {
         ownerName() {
             return this.owner?.attributes['formatted-name'] ?? '?';
         },
+        selectableTemplates() {
+            return this.templates.filter(template => {
+                return template.attributes.purpose === this.newElementPurpose
+            });
+        },
     },
 
     methods: {
@@ -1336,7 +1237,7 @@ export default {
             showElementInfoDialog: 'showElementInfoDialog',
             showElementDeleteDialog: 'showElementDeleteDialog',
             showElementOerDialog: 'showElementOerDialog',
-            showElementLinkDialog: 'showElementLinkDialog',
+            showElementPublicLinkDialog: 'showElementPublicLinkDialog',
             showElementRemoveLockDialog: 'showElementRemoveLockDialog',
             updateShowSuggestOerDialog: 'updateShowSuggestOerDialog',
             updateContainer: 'updateContainer',
@@ -1415,7 +1316,7 @@ export default {
                     this.setBookmark();
                     break;
                 case 'linkElement':
-                    this.showElementLinkDialog(true);
+                    this.showElementPublicLinkDialog(true);
                     break;
             }
         },
@@ -1610,11 +1511,11 @@ export default {
             })
             .catch(error => {
                 this.companionError({ info: this.$gettext('Die Seite konnte nicht gelöscht werden.') });
-                console.debug(error);
             });
         },
-        createElement() {
-            let title = this.newChapterName; // this is the title of the new element
+        async createElement() {
+            const title = this.newChapterName; // this is the title of the new element
+            const purpose = this.newChapterPurpose;
             let parent_id = this.currentId; // new page is descandant as default
             let writeApproval = this.currentElement.attributes['write-approval'];
             let readApproval = this.currentElement.attributes['read-approval'];
@@ -1632,9 +1533,11 @@ export default {
             this.createStructuralElement({
                 attributes: {
                     title: title,
+                    purpose: purpose,
                     'write-approval':  writeApproval,
                     'read-approval': readApproval
                 },
+                templateId: this.newChapterTemplate ? this.newChapterTemplate.id : null,
                 parentId: parent_id,
                 currentId: this.currentId,
             })
@@ -1657,6 +1560,13 @@ export default {
                 this.companionError({ info: errorMessage });
             });
 
+            let newElement = this.lastCreatedElement;
+            this.companionSuccess({
+                info: this.$gettextInterpolate(
+                    this.$gettext('Die Seite %{ pageTitle } wurde erfolgreich angelegt.'),
+                    {pageTitle: newElement.attributes.title}
+                )
+            });
             this.newChapterName = '';
         },
         containerComponent(container) {
@@ -1676,7 +1586,7 @@ export default {
             this.suggestViaAction(this.currentElement, this.additionalText);
             this.updateShowSuggestOerDialog(false);
         },
-        async createElementLink() {
+        async createElementPublicLink() {
             const date = this.publicLink['expire-date'];
             const publicLink = {
                 attributes: {
@@ -1699,12 +1609,12 @@ export default {
             });
             this.closeLinkDialog();
         },
-        closeLinkDialog() {
+        closePublicLinkDialog() {
             this.publicLink = {
                 passsword: '',
                 'expire-date': ''
             };
-            this.showElementLinkDialog(false);
+            this.showElementPublicLinkDialog(false);
         },
         displayRemoveLockDialog() {
             this.showElementRemoveLockDialog(true);
diff --git a/resources/vue/components/courseware/CoursewareStructuralElementComments.vue b/resources/vue/components/courseware/CoursewareStructuralElementComments.vue
index da6de348159..197d1bfe066 100644
--- a/resources/vue/components/courseware/CoursewareStructuralElementComments.vue
+++ b/resources/vue/components/courseware/CoursewareStructuralElementComments.vue
@@ -19,7 +19,7 @@
 
 <script>
 import CoursewareTalkBubble from './CoursewareTalkBubble.vue';
-import { mapGetters } from 'vuex';
+import { mapActions, mapGetters } from 'vuex';
 
 export default {
     name: 'courseware-structural-element-comments',
@@ -58,12 +58,16 @@ export default {
         }
     },
     methods: {
+        ...mapActions({
+            createComments: 'courseware-structural-element-comments/create',
+            loadRelatedComments: 'courseware-structural-element-comments/loadRelated'
+        }),
         async loadComments() {
             const parent = {
                 type: this.structuralElement.type,
                 id: this.structuralElement.id,
             };
-            await this.$store.dispatch('courseware-structural-element-comments/loadRelated', {
+            await this.loadRelatedComments({
                 parent,
                 relationship: 'comments',
                 options: {
@@ -86,7 +90,7 @@ export default {
                 }
             };
 
-            await this.$store.dispatch('courseware-structural-element-comments/create', data);
+            await this.createComments(data);
             this.loadComments();
             this.createComment = '';
         },
diff --git a/resources/vue/components/courseware/CoursewareStructuralElementDialogCopy.vue b/resources/vue/components/courseware/CoursewareStructuralElementDialogCopy.vue
new file mode 100644
index 00000000000..35548bcb455
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareStructuralElementDialogCopy.vue
@@ -0,0 +1,466 @@
+<template>
+    <studip-wizard-dialog
+        :title="$gettext('Lerninhalte kopieren')"
+        :confirmText="$gettext('Kopieren')"
+        :closeText="$gettext('Abbrechen')"
+        :lastRequiredSlotId="3"
+        :requirements="requirements"
+        :slots="wizardSlots"
+        @close="showElementCopyDialog(false)"
+        @confirm="copyElement"
+    >
+        <template v-slot:source>
+            <form class="default" @submit.prevent="">
+                <fieldset class="radiobutton-set">
+                    <input
+                        id="cw-element-copy-source-self"
+                        v-if="inCourseContext"
+                        type="radio"
+                        v-model="source"
+                        value="self"
+                        :aria-description="text.sourceSelf"
+                    />
+                    <label v-if="inCourseContext" @click="source = 'self'" for="cw-element-copy-source-self">
+                        <div class="icon"><studip-icon shape="seminar" size="32"/></div>
+                        <div class="text">{{ text.sourceSelf }}</div>
+                        <studip-icon shape="radiobutton-unchecked" size="24" class="unchecked" />
+                        <studip-icon shape="check-circle" size="24" class="check" />
+                    </label>
+                    <input
+                        id="cw-element-copy-source-courses"
+                        type="radio"
+                        v-model="source"
+                        value="courses"
+                        :aria-description="text.sourceCourses"
+                    />
+                    <label @click="source = 'courses'" for="cw-element-copy-source-courses">
+                        <div class="icon"><studip-icon shape="seminar" size="32"/></div>
+                        <div class="text">{{ text.sourceCourses }}</div>
+                        <studip-icon shape="radiobutton-unchecked" size="24" class="unchecked" />
+                        <studip-icon shape="check-circle" size="24" class="check" />
+                    </label>
+                    <input
+                        id="cw-element-copy-source-users"
+                        type="radio"
+                        v-model="source"
+                        value="users"
+                        :aria-description="text.sourceUsers"
+                    />
+                    <label @click="source = 'users'" for="cw-element-copy-source-users">
+                        <div class="icon"><studip-icon shape="content" size="32"/></div>
+                        <div class="text">{{ text.sourceUsers }}</div>
+                        <studip-icon shape="radiobutton-unchecked" size="24" class="unchecked" />
+                        <studip-icon shape="check-circle" size="24" class="check" />
+                    </label>
+                </fieldset>
+                <label v-if="source === 'courses'">
+                    <span>{{ $gettext('Veranstaltung') }}</span><span aria-hidden="true" class="wizard-required">*</span>
+                    <studip-select
+                        v-if="courses.length !== 0 && !loadingCourses"
+                        :options="courses"
+                        label="title"
+                        :clearable="false"
+                        :reduce="option => option.id"
+                        v-model="selectedRange"
+                    >
+                        <template #open-indicator="selectAttributes">
+                            <span v-bind="selectAttributes"
+                                ><studip-icon shape="arr_1down" size="10"
+                            /></span>
+                        </template>
+                        <template #no-options="{}">
+                            {{ $gettext('Es steht keine Auswahl zur Verfügung.') }}
+                        </template>
+                        <template #selected-option="{ attributes }">
+                            <span>{{ attributes.title }}</span>
+                        </template>
+                        <template #option="{ attributes }">
+                            <span>{{ attributes.title }}</span>
+                        </template>
+                    </studip-select>
+                    <p v-if="loadingCourses">
+                        {{$gettext('Lade Veranstaltungen…')}}
+                    </p>
+                    <p v-if="courses.length === 0 && !loadingCourses">
+                        {{$gettext('Es wurden keine geeigneten Veranstaltungen gefunden.')}}
+                    </p>
+                </label>
+            </form>
+        </template>
+        <template v-slot:unit>
+            <form class="default" @submit.prevent="">
+                <fieldset v-if="units.length !== 0" class="radiobutton-set">
+                    <template v-for="unit in units">
+                        <input
+                            :id="'cw-element-copy-unit-' + unit.id"
+                            type="radio"
+                            :checked="unit.id === selectedUnitId"
+                            :value="unit.id"
+                            :key="'radio-' + unit.id"
+                            :aria-description="unit.element.attributes.title"
+                        />
+                        <label @click="selectedUnit = unit" :key="'label-' + unit.id" :for="'cw-shelf-copy-unit-' + unit.id">
+                            <div class="icon"><studip-icon shape="courseware" size="32"/></div>
+                            <div class="text">{{ unit.element.attributes.title }}</div>
+                            <studip-icon shape="radiobutton-unchecked" size="24" class="unchecked" />
+                            <studip-icon shape="check-circle" size="24" class="check" />
+                        </label>
+                    </template>
+                </fieldset>
+                <courseware-companion-box
+                    v-else
+                    mood="sad"
+                    :msgCompanion="$gettext('Für die gewählte Quelle stehen kein Lernmaterialien zur Verfügung.')"
+                />
+            </form>
+        </template>
+        <template v-slot:element>
+            <form v-if="selectedUnit" class="default" @submit.prevent="">
+                <fieldset class="radiobutton-set">
+                    <input id="cw-element-copy-element" type="radio" checked :aria-description="selectedElementTitle" />
+                    <label for="cw-element-copy-element"  @click="e => e.preventDefault()">
+                        <div class="icon"><studip-icon shape="content2" size="32"/></div>
+                        <div class="text">{{ selectedElementTitle }}</div>
+                        <studip-icon shape="check-circle" size="24" class="check" />
+                    </label>
+                </fieldset>
+                <button
+                    v-if="selectedElementParent"
+                    class="button"
+                    @click="selectElement(selectedElementParent.id)"
+                >
+                    {{ $gettextInterpolate(
+                        $gettext('zurück zu %{ parentTitle }'),
+                        { parentTitle: selectedElementParentTitle }
+                    ) }}
+                </button>
+                <fieldset>
+                    <legend>{{ $gettext('Unterseiten') }}</legend>
+                    <ul class="cw-element-selector-list">
+                        <li
+                            v-for="child in children"
+                            :key="child.id"
+                        >
+                            <button
+                                class="cw-element-selector-item"
+                                @click="selectElement(child.id)"
+                            >
+                                {{ child.attributes.title }}
+                            </button>
+                        </li>
+                        <li v-if="children.length === 0">
+                            {{ $gettext('Es wurden keine Unterseiten gefunden') }}
+                        </li>
+                    </ul>
+                </fieldset>
+            </form>
+            <courseware-companion-box
+                    v-else
+                    mood="pointing"
+                    :msgCompanion="$gettext('Bitte wählen Sie ein Lernmaterial aus.')"
+            />
+        </template>
+        <template v-slot:edit>
+            <form v-if="selectedUnit" class="default" @submit.prevent="">
+                <label>
+                    {{$gettext('Titel')}}
+                    <input type="text" v-model="modifiedTitle" required />
+                </label>
+                <label>
+                    {{$gettext('Farbe')}}
+                    <studip-select
+                        v-model="modifiedColor"
+                        :options="colors"
+                        :reduce="(color) => color.class"
+                        :clearable="false"
+                        label="class"
+                    >
+                        <template #open-indicator="selectAttributes">
+                            <span v-bind="selectAttributes"
+                                ><studip-icon shape="arr_1down" size="10"
+                            /></span>
+                        </template>
+                        <template #no-options>
+                            {{ $gettext('Es steht keine Auswahl zur Verfügung.') }}
+                        </template>
+                        <template #selected-option="{ name, hex }">
+                            <span class="vs__option-color" :style="{ 'background-color': hex }"></span
+                            ><span>{{ name }}</span>
+                        </template>
+                        <template #option="{ name, hex }">
+                            <span class="vs__option-color" :style="{ 'background-color': hex }"></span
+                            ><span>{{ name }}</span>
+                        </template>
+                    </studip-select>
+                </label>
+                <label>
+                    {{$gettext('Beschreibung')}}
+                    <textarea v-model="modifiedDescription" required />
+                </label>
+            </form>
+            <courseware-companion-box
+                    v-else
+                    mood="pointing"
+                    :msgCompanion="$gettext('Bitte wählen Sie eine Seite aus.')"
+            />
+        </template>
+    </studip-wizard-dialog>
+</template>
+
+<script>
+import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
+import colorMixin from '@/vue/mixins/courseware/colors.js';
+import StudipSelect from './../StudipSelect.vue';
+import StudipWizardDialog from './../StudipWizardDialog.vue';
+
+import { mapActions, mapGetters } from 'vuex'
+
+export default {
+    name: 'courseware-structural-element-dialog-copy',
+    mixins: [colorMixin],
+    components: {
+        CoursewareCompanionBox,
+        StudipWizardDialog,
+        StudipSelect,
+    },
+    data() {
+        return {
+            wizardSlots: [
+                { id: 1, valid: false, name: 'source', title: this.$gettext('Quelle'), icon: 'network',
+                  description: this.$gettext('Wählen Sie hier den Ort in Stud.IP aus, an dem sich der zu kopierende Lerninhalt befindet.') },
+                { id: 2, valid: false, name: 'unit', title: this.$gettext('Lernmaterial'), icon: 'courseware',
+                  description: this.$gettext('Wählen Sie das Lernmaterial aus, in dem sich der zu kopierende Lerninhalt befindet.') },
+                { id: 3, valid: false, name: 'element', title: this.$gettext('Seite'), icon: 'content2',
+                  description: this.$gettext('Wählen Sie die zu kopierende Seite aus. Vorausgewählt ist die oberste Seite des ausgewählten Lernmaterials. Unterseiten erreichen Sie über die Schaltflächen im Bereich "Unterseiten". Sie können über die "zurück zu" Schaltfläche das übergeordnete Element anwählen. Die ausgewählte Seite ist mit einem Kontrollhaken markiert.') },
+                { id: 4, valid: true, name: 'edit', title: this.$gettext('Anpassen'), icon: 'edit',
+                  description: this.$gettext('Sie können hier die Daten der zu kopierenden Seite anpassen. Eine Anpassung ist optional, Sie können die Seite auch unverändert kopieren.') },
+            ],
+            source: '',
+            loadingCourses: false,
+            courses: [],
+            selectedRange: '',
+            loadingUnits: false,
+            selectedUnit: null,
+            selectedElement: null,
+            modifiedTitle: '',
+            modifiedColor: '',
+            modifiedDescription: '',
+            requirements: [],
+            text: {
+                sourceSelf: this.$gettext('Diese Veranstaltung'),
+                sourceCourses: this.$gettext('Veranstaltung'),
+                sourceUsers: this.$gettext('Arbeitsplatz'),
+                source: this.$gettext('Quelle'),
+                unit: this.$gettext('Lernmaterial'),
+                element: this.$gettext('Seite'),
+            },
+        }
+    },
+    computed: {
+        ...mapGetters({
+            userId: 'userId',
+            coursewareUnits: 'courseware-units/all',
+            structuralElementById: 'courseware-structural-elements/byId',
+            context: 'context',
+            childrenById: 'courseware-structure/children',
+            currentElement: 'currentElement'
+        }),
+        colors() {
+            return this.mixinColors.filter(color => color.darkmode);
+        },
+        inCourseContext() {
+            return this.context.type === 'courses';
+        },
+        units() {
+            let units = this.coursewareUnits.filter(unit => unit.relationships.range.data.id === this.selectedRange);
+            units.forEach(unit => {
+                unit.element = this.getUnitElement(unit);
+            });
+
+            return units;
+        },
+        selectedUnitId() {
+            return this.selectedUnit?.id;
+        }, 
+        selectedUnitRootId() {
+            return this.selectedUnit?.relationships?.['structural-element']?.data?.id;
+        }, 
+        selectedElementTitle() {
+            return this.selectedElement?.attributes?.title;
+        },
+        selectedElementParent() {
+            let parentData = this.selectedElement?.relationships?.parent?.data;
+            if (parentData){
+                return this.structuralElementById({id: parentData.id});
+            }
+
+            return null;
+        },
+        selectedElementParentTitle() {
+            if (this.selectedElementParent) {
+                return this.selectedElementParent.attributes.title;
+            }
+
+            return '';
+        },
+        children() {
+            if (!this.selectedElement) {
+                return [];
+            }
+
+            return this.childrenById(this.selectedElement.id)
+                .map((id) => this.structuralElementById({ id }))
+                .filter(Boolean);
+        },
+    },
+    mounted() {
+        this.initWizardData();
+    },
+    methods: {
+        ...mapActions({
+            showElementCopyDialog: 'showElementCopyDialog',
+            loadCourseUnits: 'loadCourseUnits',
+            loadUserUnits: 'loadUserUnits',
+            loadUsersCourses: 'loadUsersCourses',
+            loadStructuralElement: 'courseware-structural-elements/loadById',
+            copyStructuralElement: 'copyStructuralElement',
+            companionError: 'companionError',
+            companionSuccess: 'companionSuccess',
+        }),
+        initWizardData() {
+            this.source = this.inCourseContext ? 'self' : 'users';
+            this.selectedRange = '';
+            this.selectedUnit = null;
+        },
+        getUnitElement(unit) {
+            return this.structuralElementById({id: unit.relationships['structural-element'].data.id});
+        },
+        async updateCourses() {
+            this.loadingCourses = true;
+            this.courses = await this.loadUsersCourses({ userId: this.userId, withCourseware: true });
+            this.loadingCourses = false;
+        },
+        async updateCourseUnits(cid) {
+            this.loadingUnits = true;
+            await this.loadCourseUnits(cid);
+            this.loadingUnits = false;
+        },
+        async updateUserUnits() {
+            this.loadingUnits = true;
+            await this.loadUserUnits(this.userId);
+            this.loadingUnits = false;
+        },
+        selectElement(id) {
+            this.selectedElement = this.structuralElementById({id: id});
+            this.loadStructuralElement({id: id, options: {include: 'children'}});
+        },
+        setElementData() {
+            this.modifiedTitle = this.selectedElement.attributes.title;
+            this.modifiedColor = this.selectedElement.attributes.payload.color;
+            this.modifiedDescription = this.selectedElement.attributes.payload.description;
+        },
+        resetElementData() {
+            this.modifiedTitle = '';
+            this.modifiedColor = '';
+            this.modifiedDescription = '';
+        },
+        copyElement() {
+            let view = this;
+            this.copyStructuralElement({
+                    parentId: this.currentElement,
+                    elementId: this.selectedElement.id,
+                    migrate: false,
+                    modifications: {
+                        title: view.modifiedTitle,
+                        color: view.modifiedColor,
+                        description: view.modifiedDescription
+                    }
+            })
+            .then( () => {
+                view.companionSuccess({
+                    info: view.$gettextInterpolate(
+                        view.$gettext('Die Seite %{ pageTitle } wurde erfolgreich kopiert.'),
+                        {pageTitle: view.selectedElementTitle}
+                    )
+                });
+            })
+            .catch(error => {
+                view.companionError({
+                    info: view.$gettextInterpolate(
+                        view.$gettext('Die Seite %{ pageTitle } konnte nicht kopiert werden.'),
+                        {pageTitle: view.selectedElementTitle}
+                    )
+                });
+            })
+            .finally(() => {
+                view.showElementCopyDialog(false);
+            });
+        },
+        validateSelection() {
+            this.requirements = [];
+            if (this.selectedRange === '') {
+                this.requirements.push({slot: this.wizardSlots[0], text: this.text.source });
+            }
+            if (this.selectedUnit === null) {
+                this.requirements.push({slot: this.wizardSlots[1], text: this.text.unit });
+            }
+            if (this.selectedUnit === null) {
+                this.requirements.push({slot: this.wizardSlots[2], text: this.text.element });
+            }
+        }
+    },
+    watch: {
+        selectedElement(newElement) {
+            this.validateSelection();
+            if (newElement !== null) {
+                this.wizardSlots[2].valid = true;
+                this.setElementData();
+            } else {
+                this.resetElementData();
+                this.wizardSlots[2].valid = false;
+            }
+            
+        },
+        async selectedUnit(newUnit) {
+            this.validateSelection();
+            if (newUnit !== null) {
+                this.wizardSlots[1].valid = true;
+                await this.loadStructuralElement({id: this.selectedUnitRootId, options: {include: 'children'}});
+                this.selectedElement = this.structuralElementById({id: this.selectedUnitRootId});
+            } else {
+                this.wizardSlots[1].valid = false;
+            }
+            
+        },
+        selectedRange(newRid) {
+            this.validateSelection();
+            this.selectedUnit = null;
+            if (newRid !== '') {
+                this.wizardSlots[0].valid = true;
+                if (this.source === 'courses' || this.source === 'self') {
+                    this.updateCourseUnits(newRid);
+                }
+                if (this.source === 'users') {
+                    this.loadUserUnits(newRid);
+                }
+            } else {
+                this.wizardSlots[0].valid = false;
+            }
+        },
+        source(newSource) {
+            switch (newSource) {
+                case 'self':
+                    this.selectedRange = this.context.id;
+                    break;
+                case 'courses':
+                    this.selectedRange = '';
+                    this.updateCourses();
+                    break;
+                case 'users':
+                    this.selectedRange = this.userId;
+                    break;
+            }
+        }
+    }
+}
+</script>
\ No newline at end of file
diff --git a/resources/vue/components/courseware/CoursewareStructuralElementDialogImport.vue b/resources/vue/components/courseware/CoursewareStructuralElementDialogImport.vue
new file mode 100644
index 00000000000..6c6bb817544
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareStructuralElementDialogImport.vue
@@ -0,0 +1,198 @@
+<template>
+    <studip-dialog
+        :title="$gettext('Lerninhalte importieren')"
+        :confirmText="$gettext('Importieren')"
+        :confirmDisabled="importRunning || importAborted"
+        :closeText="importRunning || importAborted ? $gettext('Schließen') : $gettext('Abbrechen')"
+        height="420"
+        @close="showElementImportDialog(false)"
+        @confirm="importCoursewareArchiv"
+    >
+        <template v-slot:dialogContent>
+            <form v-if="!importRunning && !importAborted" class="default" @submit.prevent="">
+                <label>
+                    {{$gettext('Importdatei')}}
+                    <input class="cw-file-input" ref="importFile" type="file" accept=".zip" @change="setImport" />
+                </label>
+                <label>
+                {{$gettext('Importverhalten')}}
+                <select v-model="importBehavior">
+                    <option value="default">{{$gettext('Inhalte anhängen')}}</option>
+                    <option value="migrate">{{$gettext('Inhalte zusammenführen')}}</option>
+                </select>
+            </label>
+            </form>
+            <div role="status" aria-live="polite">
+                <courseware-companion-box 
+                    v-show="importDone && importErrors.length === 0"
+                    :msgCompanion="$gettext('Import erfolgreich!')"
+                    mood="special"
+                />
+                <courseware-companion-box
+                    v-show="importDone && importErrors.length > 0"
+                    :msgCompanion="$gettext('Import abgeschlossen. Es sind Fehler aufgetreten!')"
+                    mood="unsure"
+                />
+                <courseware-companion-box
+                    v-show="!importDone && importRunning"
+                    :msgCompanion="$gettext('Import läuft. Bitte schließen Sie den Dialog nicht bis der Import abgeschlossen wurde.')"
+                    mood="pointing"
+                />
+                <courseware-companion-box
+                    v-show="importAborted"
+                    :msgCompanion="$gettext('Import abgebrochen. Es sind Fehler aufgetreten!')"
+                    mood="sad"
+                />
+            </div>
+            <form v-if="!importDone && importRunning" class="default" @submit.prevent="">
+                <fieldset>
+                    <div v-if="!fileImportDone" class="cw-import-zip">
+                        <header>{{$gettext('Importiere Dateien')}}:</header>
+                        <div class="progress-bar-wrapper">
+                            <div class="progress-bar" role="progressbar" :style="{width: importFilesProgress + '%'}" :aria-valuenow="importFilesProgress" aria-valuemin="0" aria-valuemax="100">{{ importFilesProgress }}%</div>
+                        </div>
+                        {{ importFilesState }}
+                    </div>
+                    <div v-if="fileImportDone" class="cw-import-zip">
+                        <header>{{$gettext('Importiere Elemente')}}:</header>
+                        <div class="progress-bar-wrapper">
+                            <div class="progress-bar" role="progressbar" :style="{width: importStructuresProgress + '%'}" :aria-valuenow="importStructuresProgress" aria-valuemin="0" aria-valuemax="100">{{ importStructuresProgress }}%</div>
+                        </div>
+                        {{ importStructuresState }}
+                    </div>
+                </fieldset>
+                <fieldset v-show="importErrors.length > 0">
+                    <legend>{{$gettext('Fehlermeldungen')}}</legend>
+                    <ul>
+                        <li v-for="(error, index) in importErrors" :key="index"> {{error}} </li>
+                    </ul>
+                </fieldset>
+            </form>
+        </template>
+    </studip-dialog>
+</template>
+<script>
+import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
+import CoursewareImport from '@/vue/mixins/courseware/import.js';
+
+import { mapActions, mapGetters } from 'vuex'
+import JSZip from 'jszip';
+
+export default {
+    name: 'courseware-structural-element-dialog-import',
+    components: {
+        CoursewareCompanionBox,
+    },
+    mixins: [CoursewareImport],
+    props: {
+        colors: Array
+    },
+    data() {
+        return {
+            importBehavior: 'default',
+            importRunning: false,
+            importZipFile: null,
+            zip: null,
+            importAborted: false,
+        }
+    },
+    computed: {
+        ...mapGetters({
+            currentElement: 'currentElement',
+            importFilesState: 'importFilesState',
+            importFilesProgress: 'importFilesProgress',
+            importStructuresState: 'importStructuresState',
+            importStructuresProgress: 'importStructuresProgress',
+            importErrors: 'importErrors',
+        }),
+        fileImportDone() {
+            return this.importFilesProgress === 100;
+        },
+        importDone() {
+            return (this.importFilesProgress === 100 && this.importStructuresProgress === 100);
+        }
+    },
+    methods: {
+        ...mapActions({
+            showElementImportDialog: 'showElementImportDialog',
+            loadCoursewareStructure: 'courseware-structure/load',
+            setImportFilesProgress: 'setImportFilesProgress',
+            setImportStructuresProgress: 'setImportStructuresProgress',
+            setImportErrors: 'setImportErrors',
+        }),
+        setImport(event) {
+            this.importZipFile = event.target.files[0];
+            this.setImportFilesProgress(0);
+            this.setImportStructuresProgress(0);
+            this.setImportErrors([]);
+        },
+        async importCoursewareArchiv() {
+            this.importAborted = false;
+            if (this.importZipFile === null) {
+                return false;
+            }
+
+            this.importRunning = true;
+            try {
+                this.zip = await JSZip.loadAsync(this.importZipFile);
+            } catch(error) {
+                this.setImportErrors([this.$gettext('Die gewählte Datei ist kein Archiv oder das Archiv ist beschädigt.')]);
+                this.importRunning = false;
+                this.importAborted = true;
+                return;
+            }
+            let errors = [];
+            let missingFiles = false;
+            if (this.zip.file('courseware.json') === null) {
+                errors.push(this.$gettext('Das Archiv enthält keine courseware.json Datei.'));
+                missingFiles = true;
+            }
+            if (this.zip.file('files.json') === null) {
+                errors.push(this.$gettext('Das Archiv enthält keine files.json Datei.'));
+                missingFiles = true;
+            }
+            if (this.zip.file('data.xml') !== null) {
+                errors.push(this.$gettext(
+                    'Das Archiv enthält eine data.xml Datei. Möglicherweise handelt es sich um einen Export aus dem Courseware-Plugin. Diese Archive sind nicht kompatibel mit dieser Courseware.'
+                ));
+            }
+            if (missingFiles) {
+                this.setImportErrors(errors);
+                this.importRunning = false;
+                this.importAborted = true;
+                return;
+            }
+
+            const data = await this.zip.file('courseware.json').async('string');
+            let courseware = null;
+            const data_files = await this.zip.file('files.json').async('string');
+            let files = null;
+            let jsonErrors = false;
+            try {
+                courseware = JSON.parse(data);
+            } catch (error) {
+                jsonErrors = true;
+                errors.push(this.$gettext('Die Beschreibung der Courseware-Inhalte ist nicht valide.'));
+                errors.push(error);
+            }
+            try {
+                files = JSON.parse(data_files);
+            } catch (error) {
+                jsonErrors = true;
+                errors.push(this.$gettext('Die Beschreibung der Dateien ist nicht valide.'));
+                errors.push(error);
+            }
+            if (jsonErrors) {
+                this.setImportErrors(errors);
+                this.importRunning = false;
+                this.importAborted = true;
+                return;
+            }
+
+            await this.loadCoursewareStructure();
+
+            await this.importCourseware(courseware, this.currentElement, files, this.importBehavior, null);
+        }
+    }
+}
+</script>
\ No newline at end of file
diff --git a/resources/vue/components/courseware/CoursewareStructuralElementDialogLink.vue b/resources/vue/components/courseware/CoursewareStructuralElementDialogLink.vue
new file mode 100644
index 00000000000..ee9fd4bcfe5
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareStructuralElementDialogLink.vue
@@ -0,0 +1,264 @@
+<template>
+    <studip-wizard-dialog
+        :title="$gettext('Seite verknüpfen')"
+        :confirmText="$gettext('Verknüpfen')"
+        :closeText="$gettext('Abbrechen')"
+        :lastRequiredSlotId="2"
+        :requirements="requirements"
+        :slots="wizardSlots"
+        @close="showElementLinkDialog(false)"
+        @confirm="linkElement"
+    >
+        <template v-slot:unit>
+            <form v-if="!loadingUnits" class="default" @submit.prevent="">
+                <fieldset v-if="hasUnits" class="radiobutton-set">
+                    <template v-for="unit in units">
+                        <input
+                            :id="'cw-element-link-unit-' + unit.id"
+                            type="radio"
+                            :checked="unit.id === selectedUnitId"
+                            :value="unit.id"
+                            :key="'radio-' + unit.id"
+                            :aria-description="unit.element.attributes.title"
+                        />
+                        <label @click="selectedUnit = unit" :key="'label-' + unit.id" :for="'cw-element-link-unit-' + unit.id">
+                            <div class="icon"><studip-icon shape="courseware" size="32"/></div>
+                            <div class="text">{{ unit.element.attributes.title }}</div>
+                            <studip-icon shape="radiobutton-unchecked" size="24" class="unchecked" />
+                            <studip-icon shape="check-circle" size="24" class="check" />
+                        </label>
+                    </template>
+                </fieldset>
+                <courseware-companion-box
+                    v-else
+                    mood="sad"
+                    :msgCompanion="$gettext('Es konnte leider kein Lernmaterial gefunden werden.')"
+                />
+            </form>
+            <studip-progress-indicator 
+                v-else
+                :description="$gettext('Lade Lernmaterialien…')"
+            />
+        </template>
+        <template v-slot:element>
+            <form class="default" @submit.prevent="">
+                <template v-if="selectedUnit">
+                    <fieldset class="radiobutton-set">
+                        <input id="cw-element-link-element"  type="radio" checked :aria-description="selectedElementTitle"/>
+                        <label for="cw-element-link-element" @click="e => e.preventDefault()">
+                            <div class="icon"><studip-icon shape="content2" size="32"/></div>
+                            <div class="text">{{ selectedElementTitle }}</div>
+                            <studip-icon shape="check-circle" size="24" class="check" />
+                        </label>
+                    </fieldset>
+                    <button
+                        v-if="selectedElementParent"
+                        class="button"
+                        @click="selectElement(selectedElementParent.id)"
+                    >
+                        {{ $gettextInterpolate(
+                            $gettext('zurück zu %{ parentTitle }'),
+                            { parentTitle: selectedElementParentTitle }
+                        ) }}
+                    </button>
+                    <fieldset>
+                        <legend>{{ $gettext('Unterseiten') }}</legend>
+                        <ul class="cw-element-selector-list">
+                            <li
+                                v-for="child in children"
+                                :key="child.id"
+                            >
+                                <button
+                                    class="cw-element-selector-item"
+                                    @click="selectElement(child.id)"
+                                >
+                                    {{ child.attributes.title }}
+                                </button>
+                                
+                            </li>
+                            <li v-if="children.length === 0">
+                                {{ $gettext('Es wurden keine Unterseiten gefunden.') }}
+                            </li>
+                        </ul>
+                    </fieldset>
+                </template>
+                <courseware-companion-box
+                    v-if="!selectedUnit"
+                    mood="pointing"
+                    :msgCompanion="$gettext('Bitte wählen Sie zuerst das Lernmaterial aus.')"
+                />
+            </form>
+        </template>
+    </studip-wizard-dialog>
+</template>
+
+<script>
+import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
+import StudipWizardDialog from './../StudipWizardDialog.vue';
+import StudipProgressIndicator from '../StudipProgressIndicator.vue';
+
+import { mapActions, mapGetters } from 'vuex'
+
+export default {
+    name: 'courseware-structural-element-dialog-link',
+    components: {
+        CoursewareCompanionBox,
+        StudipWizardDialog,
+        StudipProgressIndicator
+    },
+    data() {
+        return {
+            wizardSlots: [
+                {id: 1, valid: false, name: 'unit', title: this.$gettext('Lernmaterial'), icon: 'courseware', 
+                description: this.$gettext('Wählen Sie das Lernmaterial aus, in dem sich der zu verknüpfende Lerninhalt befindet.')},
+                {id: 2, valid: false, name: 'element', title: this.$gettext('Seite'), icon: 'content2', 
+                description: this.$gettext('Wählen Sie die zu verknüpfende Seite aus. Vorausgewählt ist die oberste Seite des ausgewählten Lernmaterials. Unterseiten erreichen Sie über die Schaltflächen im Bereich "Unterseiten". Sie können über die "zurück zu" Schaltfläche das übergeordnete Element anwählen. Die ausgewählte Seite ist mit einem Kontrollhaken markiert.')},            ],
+            loadingUnits: false,
+            selectedUnit: null,
+            selectedElement: null,
+            requirements: [],
+            text: {
+
+            }
+        }
+    },
+    computed: {
+        ...mapGetters({
+            userId: 'userId',
+            coursewareUnits: 'courseware-units/all',
+            structuralElementById: 'courseware-structural-elements/byId',
+            context: 'context',
+            childrenById: 'courseware-structure/children',
+            currentElement: 'currentElement'
+        }),
+        units() {
+            let units = this.coursewareUnits.filter(unit => unit.relationships.range.data.id === this.userId);
+            units.forEach(unit => {
+                unit.element = this.getUnitElement(unit);
+            });
+
+            return units;
+        },
+        hasUnits() {
+            return this.units.length !== 0;
+        },
+        selectedUnitId() {
+            return this.selectedUnit?.id;
+        }, 
+        selectedUnitRootId() {
+            return this.selectedUnit?.relationships?.['structural-element']?.data?.id;
+        }, 
+        selectedElementTitle() {
+            return this.selectedElement?.attributes?.title;
+        },
+        selectedElementParent() {
+            let parentData = this.selectedElement?.relationships?.parent?.data;
+            if (parentData){
+                return this.structuralElementById({id: parentData.id});
+            }
+
+            return null;
+        },
+        selectedElementParentTitle() {
+            if (this.selectedElementParent) {
+                return this.selectedElementParent.attributes.title;
+            }
+
+            return '';
+        },
+        children() {
+            if (!this.selectedElement) {
+                return [];
+            }
+
+            return this.childrenById(this.selectedElement.id)
+                .map((id) => this.structuralElementById({ id }))
+                .filter(Boolean);
+        },
+    },
+    mounted() {
+        this.initWizardData();
+        this.updateUserUnits();
+        },
+    methods: {
+        ...mapActions({
+            showElementLinkDialog: 'showElementLinkDialog',
+            loadUserUnits: 'loadUserUnits',
+            loadUsersCourses: 'loadUsersCourses',
+            loadStructuralElement: 'courseware-structural-elements/loadById',
+            linkStructuralElement: 'linkStructuralElement',
+            companionError: 'companionError',
+            companionSuccess: 'companionSuccess',
+        }),
+        initWizardData() {
+            this.selectedRange = '';
+            this.selectedUnit = null;
+            this.validateSelection();
+        },
+        async updateUserUnits() {
+            this.loadingUnits = true;
+            await this.loadUserUnits(this.userId);
+            this.loadingUnits = false;
+        },
+        getUnitElement(unit) {
+            return this.structuralElementById({id: unit.relationships['structural-element'].data.id});
+        },
+        linkElement() {
+            let view = this;
+            this.linkStructuralElement({
+                parentId: this.currentElement,
+                        elementId: this.selectedElement.id,
+                })
+                .then( () => {
+                    view.companionSuccess({
+                        info: view.$gettextInterpolate(
+                            view.$gettext('Die Seite %{ pageTitle } wurde erfolgreich verknüpft.'),
+                            { pageTitle: view.selectedElementTitle }
+                        )
+                    });
+                })
+                .catch( () => {
+                    view.companionError({
+                        info: view.$gettextInterpolate(
+                            view.$gettext('Die Seite %{ pageTitle } konnte nicht verknüpft werden.'),
+                            { pageTitle: view.selectedElementTitle }
+                        )
+                    });
+                })
+                .finally(() => {
+                    view.showElementLinkDialog(false);
+                });
+        },
+        selectElement(id) {
+            this.selectedElement = this.structuralElementById({id: id});
+            this.loadStructuralElement({id: id, options: {include: 'children'}});
+        },
+        validateSelection() {
+            this.requirements = [];
+            if (this.selectedUnit === null) {
+                this.requirements.push({slot: this.wizardSlots[0], text: this.$gettext('Lernmaterial') });
+            }
+        }
+    },
+    watch: {
+        selectedElement(newElement) {
+            this.validateSelection();
+            if (newElement !== null) {
+                this.wizardSlots[1].valid = true;
+            } else {
+                this.wizardSlots[1].valid = false;
+            }
+        },
+        async selectedUnit(newUnit) {
+            this.validateSelection();
+            if (newUnit !== null) {
+                this.wizardSlots[0].valid = true;
+                await this.loadStructuralElement({id: this.selectedUnitRootId, options: {include: 'children'}});
+                this.selectedElement = this.structuralElementById({id: this.selectedUnitRootId});
+            } else {
+                this.wizardSlots[0].valid = false;
+            }
+        },
+    }
+}
+</script>
\ No newline at end of file
diff --git a/resources/vue/components/courseware/CoursewareStructuralElementFeedback.vue b/resources/vue/components/courseware/CoursewareStructuralElementFeedback.vue
index cc079eada2d..249b13aa2a2 100644
--- a/resources/vue/components/courseware/CoursewareStructuralElementFeedback.vue
+++ b/resources/vue/components/courseware/CoursewareStructuralElementFeedback.vue
@@ -26,7 +26,7 @@
 <script>
 import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
 import CoursewareTalkBubble from './CoursewareTalkBubble.vue';
-import { mapGetters } from 'vuex';
+import { mapActions, mapGetters } from 'vuex';
 
 export default {
     name: 'courseware-structural-element-feedback',
@@ -68,6 +68,10 @@ export default {
         }
     },
     methods: {
+        ...mapActions({
+            createFeedback: 'courseware-structural-element-feedback/create',
+            loadRelatedFeedback: 'courseware-structural-element-feedback/loadRelated',
+        }),
         buildPayload(feedback) {
             const { id, type } = feedback;
             const user = this.getRelatedUser({ parent: { id, type }, relationship: 'user' });
@@ -86,7 +90,7 @@ export default {
                 type: this.structuralElement.type,
                 id: this.structuralElement.id,
             };
-            await this.$store.dispatch('courseware-structural-element-feedback/loadRelated', {
+            await this.loadRelatedFeedback({
                 parent,
                 relationship: 'feedback',
                 options: {
@@ -108,7 +112,7 @@ export default {
                     }
                 },
             };
-            await this.$store.dispatch('courseware-structural-element-feedback/create', data, { root: true });
+            await this.createFeedback( data, { root: true });
             this.feedbackText = '';
             this.loadFeedback();
         }
diff --git a/resources/vue/components/courseware/CoursewareTableOfContentsBlock.vue b/resources/vue/components/courseware/CoursewareTableOfContentsBlock.vue
index 1a582808109..9e6c7c3fcf6 100644
--- a/resources/vue/components/courseware/CoursewareTableOfContentsBlock.vue
+++ b/resources/vue/components/courseware/CoursewareTableOfContentsBlock.vue
@@ -157,7 +157,7 @@ export default {
                         taskId: taskId,
                     });
                 } catch(error) {
-                    console.debug(error);
+                    // nothing to do here
                 }
             }
         });
diff --git a/resources/vue/components/courseware/CoursewareTasksActionWidget.vue b/resources/vue/components/courseware/CoursewareTasksActionWidget.vue
new file mode 100644
index 00000000000..2f3ca3de0b8
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareTasksActionWidget.vue
@@ -0,0 +1,31 @@
+<template>
+    <sidebar-widget :title="$gettext('Aktionen')">
+        <template #content>
+            <ul class="widget-list widget-links cw-action-widget">
+                <li class="cw-action-widget-add">
+                    <button @click="setShowTasksDistributeDialog(true)">
+                        {{ $gettext('Aufgabe verteilen') }}
+                    </button>
+                </li>
+            </ul>
+        </template>
+    </sidebar-widget>
+</template>
+
+<script>
+import SidebarWidget from '../SidebarWidget.vue';
+
+import { mapActions } from 'vuex';
+
+export default {
+    name: 'courseware-tasks-action-widget',
+    components: {
+        SidebarWidget,
+    },
+    methods: {
+        ...mapActions({
+            setShowTasksDistributeDialog: 'setShowTasksDistributeDialog',
+        }),
+    }
+}
+</script>
\ No newline at end of file
diff --git a/resources/vue/components/courseware/CoursewareTasksDialogDistribute.vue b/resources/vue/components/courseware/CoursewareTasksDialogDistribute.vue
new file mode 100644
index 00000000000..9de8f3d540a
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareTasksDialogDistribute.vue
@@ -0,0 +1,633 @@
+<template>
+    <studip-wizard-dialog
+        :title="$gettext('Aufgabe verteilen')"
+        :confirmText="$gettext('Verteilen')"
+        :closeText="$gettext('Abbrechen')"
+        :lastRequiredSlotId="6"
+        :requirements="requirements"
+        :slots="wizardSlots"
+        @close="setShowTasksDistributeDialog(false)"
+        @confirm="distributeTask"
+    >
+        <template v-slot:sourceunit>
+            <form class="default" @submit.prevent="">
+                <fieldset v-if="sourceUnits.length !== 0" class="radiobutton-set">
+                    <template v-for="unit in sourceUnits">
+                        <input
+                            :id="'cw-task-dist-source-unit' + unit.id"
+                            type="radio"
+                            v-model="selectedSourceUnit"
+                            :checked="unit.id === selectedSourceUnitId"
+                            :value="unit"
+                            :key="'radio-' + unit.id"
+                            :aria-description="unit.element.attributes.title"
+                        />
+                        <label @click="selectedSourceUnit = unit" :key="'label-' + unit.id" :for="'cw-task-dist-source-unit' + unit.id">
+                            <div class="icon"><studip-icon shape="courseware" size="32"/></div>
+                            <div class="text">{{ unit.element.attributes.title }}</div>
+                            <studip-icon shape="radiobutton-unchecked" size="24" class="unchecked" />
+                            <studip-icon shape="check-circle" size="24" class="check" />
+                        </label>
+                    </template>
+                </fieldset>
+                <courseware-companion-box
+                    v-else
+                    mood="sad"
+                    :msgCompanion="$gettext('Es stehen keine Lernmaterialien zur Verfügung.')"
+                />
+            </form>
+        </template>
+        <template v-slot:task>
+            <form v-if="selectedSourceUnit" class="default" @submit.prevent="">
+                <fieldset class="radiobutton-set">
+                    <input id="cw-task-dist-task" type="radio" :checked="selectedTaskIsTask" :aria-description="selectedTaskTitle"/>
+                    <label for="cw-task-dist-task" @click="e => e.preventDefault()">
+                        <div class="icon"><studip-icon shape="content2" size="32"/></div>
+                        <div class="text">{{ selectedTaskTitle }}</div>
+                        <studip-icon v-if="selectedTaskIsTask" shape="check-circle" size="24" class="check" />
+                        <studip-icon v-else shape="decline-circle" size="24" class="unchecked" />
+                    </label>
+                </fieldset>
+                <button
+                    v-if="selectedTaskParent"
+                    class="button"
+                    @click="selectTask(selectedTaskParent.id)"
+                >
+                    {{  $gettext('zurück zur übergeordneten Seite') }}
+                </button>
+                <fieldset>
+                    <legend>{{ $gettext('Unterseiten') }}</legend>
+                    <ul class="cw-element-selector-list">
+                        <li
+                            v-for="child in taskChildren"
+                            :key="child.id"
+                        >
+                            <button
+                                class="cw-element-selector-item"
+                                @click="selectTask(child.id)"
+                            >
+                                {{ child.attributes.title }}
+                            </button>
+                        </li>
+                        <li v-if="taskChildren.length === 0">
+                            {{ $gettext('Es wurden keine Unterseiten gefunden.') }}
+                        </li>
+                    </ul>
+                </fieldset>
+            </form>
+            <courseware-companion-box
+                    v-else
+                    mood="pointing"
+                    :msgCompanion="$gettext('Bitte wählen Sie ein Lernmaterial aus.')"
+            />
+        </template>
+        <template v-slot:tasksettings>
+            <form v-if="selectedTaskIsTask" class="default" @submit.prevent="">
+                <label>
+                    <span>{{ $gettext('Aufgabentitel') }}</span><span aria-hidden="true" class="wizard-required">*</span>
+                    <input type="text" v-model="taskTitle" required/>
+                </label>
+                <label>
+                    <span>{{ $gettext('Abgabefrist') }}</span><span aria-hidden="true" class="wizard-required">*</span>
+                    <input type="date" v-model="submissionDate" />
+                </label>
+                <label>
+                    {{ $gettext('Inhalte ergänzen') }}
+                    <select class="size-s" v-model="solverMayAddBlocks">
+                        <option value="true">{{ $gettext('ja') }}</option>
+                        <option value="false">{{ $gettext('nein') }}</option>
+                    </select>
+                </label>
+            </form>
+            <courseware-companion-box
+                v-else
+                mood="pointing"
+                :msgCompanion="$gettext('Bitte wählen Sie eine Aufgabenvorlage aus.')"
+            />
+        </template>
+        <template v-slot:targetunit>
+            <form v-if="selectedTaskIsTask" class="default" @submit.prevent="">
+                <fieldset v-if="targetUnits.length !== 0" class="radiobutton-set">
+                    <template v-for="unit in targetUnits">
+                        <input
+                            :id="'cw-task-dist-target-unit' + unit.id"
+                            type="radio"
+                            v-model="selectedTargetUnit"
+                            :checked="unit.id === selectedTargetUnitId"
+                            :value="unit"
+                            :key="'radio-' + unit.id"
+                            :aria-description="unit.element.attributes.title"
+                        />
+                        <label @click="selectedTargetUnit = unit" :key="'label-' + unit.id" :for="'cw-task-dist-target-unit' + unit.id">
+                            <div class="icon"><studip-icon shape="courseware" size="32"/></div>
+                            <div class="text">{{ unit.element.attributes.title }}</div>
+                            <studip-icon shape="radiobutton-unchecked" size="24" class="unchecked" />
+                            <studip-icon shape="check-circle" size="24" class="check" />
+                        </label>
+                    </template>
+                </fieldset>
+                <courseware-companion-box
+                    v-else
+                    mood="sad"
+                    :msgCompanion="$gettext('Es stehen keine Lernmaterialien zur Verfügung.')"
+                />
+            </form>
+            <courseware-companion-box
+                v-else
+                mood="sad"
+                :msgCompanion="$gettext('Bitte wählen Sie eine Aufgabe aus.')"
+            />
+        </template>
+        <template v-slot:targetelement>
+            <form v-if="selectedTargetUnit && selectedTaskIsTask" class="default" @submit.prevent="">
+                <fieldset class="radiobutton-set">
+                    <input id="cw-task-dist-target-element" type="radio" checked  :aria-description="selectedTargetElementTitle"/>
+                    <label for="cw-task-dist-target-element" @click="e => e.preventDefault()">
+                        <div class="icon"><studip-icon shape="content2" size="32"/></div>
+                        <div class="text">{{ selectedTargetElementTitle }}</div>
+                        <studip-icon shape="check-circle" size="24" class="check" />
+                    </label>
+                </fieldset>
+                <button
+                    v-if="selectedTargetElementParent"
+                    class="button"
+                    @click="selectTargetElement(selectedTargetElementParent.id)"
+                >
+                    {{  $gettext('zurück zur übergeordneten Seite') }}
+                </button>
+                <fieldset>
+                    <legend>{{ $gettext('Unterseiten') }}</legend>
+                    <ul class="cw-element-selector-list">
+                        <li
+                            v-for="child in targetChildren"
+                            :key="child.id"
+                        >
+                            <button
+                                class="cw-element-selector-item"
+                                @click="selectTargetElement(child.id)"
+                            >
+                                {{ child.attributes.title }}
+                            </button>
+                        </li>
+                        <li v-if="targetChildren.length === 0">
+                            {{ $gettext('Es wurden keine Unterseiten gefunden.') }}
+                        </li>
+                    </ul>
+                </fieldset>
+            </form>
+            <courseware-companion-box
+                v-if="!selectedTaskIsTask"
+                mood="sad"
+                :msgCompanion="$gettext('Bitte wählen Sie eine Aufgabe aus.')"
+            />
+            <courseware-companion-box
+                v-if="!selectedTargetUnit"
+                mood="sad"
+                :msgCompanion="$gettext('Bitte wählen Sie ein Lernmaterial als Ziel aus.')"
+            />
+        </template>
+        <template v-slot:solver>
+            <form v-if="selectedTargetElement && selectedTaskIsTask" class="default" @submit.prevent="">
+                <label>
+                    {{ $gettext('Verteilen an') }}
+                    <select v-model="taskSolverType">
+                        <option value="autor">{{ $gettext('Studierende') }}</option>
+                        <option value="group">{{ $gettext('Gruppen') }}</option>
+                    </select>
+                </label>
+                <template v-if="taskSolverType === 'autor'" >
+                    <courseware-companion-box
+                        v-show="autor_members.length === 0"
+                        :msgCompanion="$gettext('Es wurden keine Studierenden in dieser Veranstaltung gefunden.')"
+                        mood="pointing"
+                    />
+                    <table v-show="autor_members.length > 0" class="default">
+                        <thead>
+                            <tr>
+                                <th><input type="checkbox" v-model="bulkSelectAutors"/></th>
+                                <th>{{ $gettext('Name') }}</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <tr v-for="user in autor_members" :key="user.user_id">
+                                <td><input type="checkbox" v-model="selectedAutors" :value="user.user_id" /></td>
+                                <td>{{ user.formattedname }}</td>
+                            </tr>
+                        </tbody>
+                    </table>
+                </template>
+                <template v-if="taskSolverType === 'group'">
+                    <courseware-companion-box
+                        v-show="groups.length === 0"
+                        :msgCompanion="$gettext('Es wurden keine Gruppen in dieser Veranstaltung gefunden.')"
+                        mood="pointing"
+                    />
+                    <table v-show="groups.length > 0" class="default">
+                        <thead>
+                            <tr>
+                                <th><input type="checkbox" v-model="bulkSelectGroups"/></th>
+                                <th>{{ $gettext('Gruppenname') }}</th>
+                            </tr>
+                        </thead>
+                        <tbody>
+                            <tr v-for="group in groups" :key="group.id">
+                                <td><input type="checkbox" v-model="selectedGroups" :value="group.id" /></td>
+                                <td>{{ group.name }}</td>
+                            </tr>
+                        </tbody>
+                    </table>
+                </template>
+            </form>
+            <courseware-companion-box
+                v-if="!selectedTaskIsTask"
+                mood="sad"
+                :msgCompanion="$gettext('Bitte wählen Sie eine Aufgabe aus.')"
+            />
+            <courseware-companion-box
+                v-if="!selectedTargetElement"
+                mood="sad"
+                :msgCompanion="$gettext('Bitte wählen Sie eine Seite aus.')"
+            />
+        </template>
+    </studip-wizard-dialog>
+</template>
+
+<script>
+import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
+import StudipWizardDialog from './../StudipWizardDialog.vue';
+
+import { mapActions, mapGetters } from 'vuex'
+
+export default {
+    name: 'courseware-tasks-dialog-distribute',
+    components: {
+        CoursewareCompanionBox,
+        StudipWizardDialog,
+    },
+    data() {
+        return {
+            wizardSlots: [
+                { id: 1, valid: false, name: 'sourceunit', title: this.$gettext('Lernmaterial'), icon: 'courseware',
+                  description: this.$gettext('Wählen Sie das Lernmaterial aus, in dem sich die Aufgabenvorlage befindet. Es sind nur Lernmaterialien aus Ihrem Arbeitsplatz aufgeführt.') },
+                { id: 2, valid: false, name: 'task', title: this.$gettext('Aufgabenvorlage'), icon: 'category-task',
+                  description: this.$gettext('Wählen Sie die zu verteilende Aufgabenvorlage aus. Vorausgewählt ist die oberste Seite des ausgewählten Lernmaterials. Unterseiten erreichen Sie über die Schaltflächen im Bereich "Unterseiten". Sie können über die "zurück zu" Schaltfläche das übergeordnete Element anwählen. Die ausgewählte Aufgabenvorlage ist mit einem Kontrollhaken markiert. Nur Seiten der Kategorie "Aufgabenvorlage" können verteilt werden.') },
+                { id: 3, valid: false, name: 'tasksettings', title: this.$gettext('Aufgabeneinstellungen'), icon: 'settings',
+                  description: this.$gettext('Wählen Sie hier die Einstellungen der Aufgabe. Es muss ein Aufgabentitel und eine Abgabenfrist gesetzt werden.') },
+                { id: 4, valid: false, name: 'targetunit', title: this.$gettext('Ziel-Lernmaterial'), icon: 'courseware',
+                  description: this.$gettext('Wählen Sie hier das Lernmaterial aus, in das die Aufgabe verteilt werden soll. Zum Bearbeiten der Aufgabe müssen Lernende Zugriff auf das Lernmaterial haben. Prüfen Sie gegebenenfalls die Leserechte und die Sichtbarkeit.') },
+                { id: 5, valid: false, name: 'targetelement', title: this.$gettext('Zielseite'), icon: 'content2',
+                  description: this.$gettext('Wählen Sie hier die Seite aus unterhalb der die Aufgabe verteilt werden soll. Zum bearbeiten der Aufgabe müssen Lernende Zugriff auf die Seite haben. Prüfen Sie ggf. die Leserechte und die Sichtbarkeit.') },
+                { id: 6, valid: false, name: 'solver', title: this.$gettext('Aufgabe zuweisen'), icon: 'group3',
+                  description: this.$gettext('Wählen Sie hier aus, an wen Sie die Aufgaben verteilen möchten. Aufgaben können entweder an Gruppen oder einzelne Teilnehmende verteilt werden. Über die Checkbox im Titel der Tabelle können Sie alles aus- bzw. abwählen.') },
+            ],
+            selectedSourceUnit: null,
+            taskTitle: '',
+            submissionDate: '',
+            solverMayAddBlocks: true,
+            selectedTask: null,
+            selectedTargetUnit: null,
+            selectedTargetElement: null,
+            taskSolverType: 'autor',
+            selectedAutors: [],
+            bulkSelectAutors: false,
+            selectedGroups: [],
+            bulkSelectGroups: false,
+            requirements: [],
+        }
+    },
+    computed: {
+        ...mapGetters({
+            userId: 'userId',
+            coursewareUnits: 'courseware-units/all',
+            structuralElementById: 'courseware-structural-elements/byId',
+            structuralElements: 'courseware-structural-elements/all',
+            context: 'context',
+            currentElement: 'currentElement',
+            relatedCourseMemberships: 'course-memberships/related',
+            relatedCourseStatusGroups: 'status-groups/related',
+            relatedUser: 'users/related',
+        }),
+        selectedSourceUnitId() {
+            return this.selectedSourceUnit?.id;
+        },
+        selectedSourceUnitRootId() {
+            return this.selectedSourceUnit?.relationships?.['structural-element']?.data?.id;
+        },
+        sourceUnits() {
+            let units = this.coursewareUnits.filter(unit => unit.relationships.range.data.id === this.userId);
+            units.forEach(unit => {
+                unit.element = this.getUnitElement(unit);
+            });
+
+            return units;
+        },
+        selectedTargetUnitId() {
+            return this.selectedTargetUnit?.id;
+        },
+        selectedTargetUnitRootId() {
+            return this.selectedTargetUnit?.relationships?.['structural-element']?.data?.id;
+        },
+        targetUnits() {
+            let units = this.coursewareUnits.filter(unit => unit.relationships.range.data.id === this.context.id);
+            units.forEach(unit => {
+                unit.element = this.getUnitElement(unit);
+            });
+
+            return units;
+        },
+        selectedTaskIsTask() {
+            return this.selectedTask?.attributes?.purpose === 'template';
+        },
+        selectedTaskTitle() {
+            return this.selectedTask?.attributes?.title;
+        },
+        selectedTaskParent() {
+            let parentData = this.selectedTask?.relationships?.parent?.data;
+            if (parentData){
+                return this.structuralElementById({id: parentData.id});
+            }
+
+            return null;
+        },
+        selectedTaskParentTitle() {
+            if (this.selectedTaskParent) {
+                return this.selectedTaskParent.attributes.title;
+            }
+
+            return '';
+        },
+        taskChildren() {
+            let children = [];
+            if (this.selectedTask) {
+                children = this.structuralElements.filter(
+                    element => element.relationships.parent?.data?.id === this.selectedTask.id
+                );
+            }
+
+            return children;
+        },
+        selectedTargetElementTitle() {
+            return this.selectedTargetElement?.attributes?.title;
+        },
+        selectedTargetElementParent() {
+            let parentData = this.selectedTargetElement?.relationships?.parent?.data;
+            if (parentData){
+                return this.structuralElementById({id: parentData.id});
+            }
+
+            return null;
+        },
+        selectedTargetElementParentTitle() {
+            if (this.selectedTargetElementParent) {
+                return this.selectedTargetElementParent.attributes.title;
+            }
+
+            return '';
+        },
+        targetChildren() {
+            let children = [];
+            if (this.selectedTargetElement) {
+                children = this.structuralElements.filter(
+                    element => element.relationships.parent?.data?.id === this.selectedTargetElement.id
+                );
+            }
+
+            return children;
+        },
+        users() {
+            const parent = { type: 'courses', id: this.context.id };
+            const relationship = 'memberships';
+            const memberships = this.relatedCourseMemberships({ parent, relationship });
+
+            return (
+                memberships?.map((membership) => {
+                    const parent = { type: membership.type, id: membership.id };
+                    const member = this.relatedUser({ parent, relationship: 'user' });
+
+                    return {
+                        user_id: member.id,
+                        formattedname: member.attributes['formatted-name'],
+                        username: member.attributes['username'],
+                        perm: membership.attributes['permission'],
+                    };
+                }) ?? []
+            );
+        },
+        groups() {
+            const parent = { type: 'courses', id: this.context.id };
+            const relationship = 'status-groups';
+            const statusGroups = this.relatedCourseStatusGroups({ parent, relationship });
+
+            return (
+                statusGroups?.map((statusGroup) => {
+                    return {
+                        id: statusGroup.id,
+                        name: statusGroup.attributes['name'],
+                    };
+                }) ?? []
+            );
+        },
+        autor_members() {
+            if (Object.keys(this.users).length === 0 && this.users.constructor === Object) {
+                return [];
+            }
+
+            let members = this.users
+                .filter(function (user) {
+                    return user.perm === 'autor';
+                })
+                .map((obj) => ({ ...obj, active: false }));
+
+            return members;
+        },
+    },
+    mounted() {
+        this.initWizardData();
+        const parent = { type: 'courses', id: this.context.id };
+        this.loadCourseMemberships({ parent, relationship: 'memberships', options: { include: 'user', 'page[offset]': 0, 'page[limit]': 10000, 'filter[permission]': 'autor' } });
+        this.loadCourseStatusGroups({ parent, relationship: 'status-groups' });
+    },
+    methods: {
+        ...mapActions({
+            setShowTasksDistributeDialog: 'setShowTasksDistributeDialog',
+            loadCourseUnits: 'loadCourseUnits',
+            loadUserUnits: 'loadUserUnits',
+            loadUsersCourses: 'loadUsersCourses',
+            loadStructuralElement: 'courseware-structural-elements/loadById',
+            copyStructuralElement: 'copyStructuralElement',
+            companionError: 'companionError',
+            companionSuccess: 'companionSuccess',
+            loadCourseMemberships: 'course-memberships/loadRelated',
+            loadCourseStatusGroups: 'status-groups/loadRelated',
+            createTaskGroup: 'createTaskGroup',
+        }),
+        async initWizardData() {
+            this.loadUserUnits(this.userId);
+            this.loadCourseUnits(this.context.id);
+            this.validate();
+        },
+        getUnitElement(unit) {
+            return this.structuralElementById({id: unit.relationships['structural-element'].data.id});
+        },
+        selectTask(id) {
+            this.selectedTask =  this.structuralElementById({id: id});
+            this.loadStructuralElement({id: id, options: {include: 'children'}});
+        },
+        selectTargetElement(id) {
+            this.selectedTargetElement = this.structuralElementById({id: id});
+            this.loadStructuralElement({id: id, options: {include: 'children'}});
+        },
+        async distributeTask() {
+            this.setShowTasksDistributeDialog(false);
+            const taskGroup = {
+                attributes: {
+                    title: this.taskTitle,
+                    'submission-date': new Date(this.submissionDate).toISOString(),
+                    'solver-may-add-blocks': this.solverMayAddBlocks,
+                },
+                relationships: {
+                    solvers: {
+                        data: [],
+                    },
+                    target: {
+                        data: {
+                            id: this.selectedTargetElement.id,
+                            type: 'courseware-structural-elements',
+                        },
+                    },
+                    'task-template': {
+                        data: {
+                            id: this.selectedTask.id,
+                            type: 'courseware-structural-elements',
+                        },
+                    },
+                },
+            };
+
+            let solvers;
+            if (this.taskSolverType === 'autor') {
+                solvers = this.selectedAutors.map((id) => ({ type: 'users', id }));
+            }
+            if (this.taskSolverType === 'group') {
+                solvers = this.selectedGroups.map((id) => ({ type: 'status-groups', id }));
+            }
+            taskGroup.relationships.solvers.data = solvers;
+
+            await this.createTaskGroup({ taskGroup });
+            this.companionSuccess({ info: this.$gettext('Aufgaben wurden verteilt.') });
+
+        },
+        validateSolvers() {
+            if (
+                (this.selectedAutors.length > 0 && this.taskSolverType === 'autor') ||
+                (this.selectedGroups.length > 0 && this.taskSolverType === 'group')
+            ) {
+                this.wizardSlots[5].valid = true;
+            } else {
+                this.wizardSlots[5].valid = false;
+            }
+
+            return this.wizardSlots[5].valid;
+        },
+        validateTaskSettings() {
+            if (this.taskTitle !== '' && this.submissionDate !== '') {
+                this.wizardSlots[2].valid = true;
+            } else {
+                this.wizardSlots[2].valid = false;
+            }
+
+            return this.wizardSlots[2].valid;
+        },
+        validate() {
+            this.requirements = [];
+            if (this.selectedSourceUnit === null) {
+                this.requirements.push({ slot: this.wizardSlots[0], text: this.$gettext('Lernmaterial') });
+            }
+            if (!this.selectedTaskIsTask) {
+                this.requirements.push({ slot: this.wizardSlots[1], text: this.$gettext('Aufgabenvorlage') });
+            }
+            if (!this.validateTaskSettings()) {
+                this.requirements.push({ slot: this.wizardSlots[2], text: this.$gettext('Aufgabeneinstellungen') });
+            }
+            if (this.selectedTargetUnit === null) {
+                this.requirements.push({ slot: this.wizardSlots[3], text: this.$gettext(' Ziel-Lernmaterial') });
+            }
+            if (this.selectedTargetElement === null) {
+                this.requirements.push({ slot: this.wizardSlots[4], text: this.$gettext(' Zielseite') });
+            }
+            if (!this.validateSolvers()) {
+                this.requirements.push({ slot: this.wizardSlots[5], text: this.$gettext('Aufgabe zuweisen') });
+            }
+        }
+    },
+    watch: {
+        async selectedSourceUnit(newUnit) {
+            this.validate();
+            if (newUnit !== null) {
+                this.wizardSlots[0].valid = true;
+                await this.loadStructuralElement({id: this.selectedSourceUnitRootId, options: {include: 'children'}});
+                this.selectedTask = this.structuralElementById({id: this.selectedSourceUnitRootId});
+            } else {
+                this.wizardSlots[0].valid = false;
+            }
+        },
+        selectedTask(newTask) {
+            this.validate();
+            if (newTask !== null && this.selectedTaskIsTask) {
+                this.wizardSlots[1].valid = true;
+            } else {
+                this.wizardSlots[1].valid = false;
+            }
+        },
+        async selectedTargetUnit(newUnit) {
+            this.validate();
+            if (newUnit !== null) {
+                this.wizardSlots[3].valid = true;
+                await this.loadStructuralElement({id: this.selectedTargetUnitRootId, options: {include: 'children'}});
+                this.selectedTargetElement = this.structuralElementById({id: this.selectedTargetUnitRootId});
+            } else {
+                this.wizardSlots[3].valid = false;
+            }
+        },
+        selectedTargetElement(newElement) {
+            this.validate();
+            if (newElement !== null) {
+                this.wizardSlots[4].valid = true;
+            } else {
+                this.wizardSlots[4].valid = false;
+            }
+        },
+
+        taskTitle() {
+            this.validate();
+        },
+        submissionDate() {
+            this.validate();
+        },
+
+        selectedAutors() {
+            this.validate();
+        },
+        selectedGroups() {
+            this.validate();
+        },
+        taskSolverType() {
+            this.validate();
+        },
+        bulkSelectAutors(newState) {
+            if (newState) {
+                this.selectedAutors = this.autor_members.map( autor => autor.user_id);
+            } else {
+                this.selectedAutors = [];
+            }
+        },
+        bulkSelectGroups(newState) {
+            if (newState) {
+                this.selectedGroups = this.groups.map( group => group.id);
+            } else {
+                this.selectedGroups = [];
+            }
+        }
+    }
+}
+</script>
diff --git a/resources/vue/components/courseware/CoursewareTile.vue b/resources/vue/components/courseware/CoursewareTile.vue
new file mode 100644
index 00000000000..6976acec1d7
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareTile.vue
@@ -0,0 +1,137 @@
+<template>
+    <component :is="tag" class="cw-tile" :class="[color]">
+        <div
+            class="preview-image"
+            :class="[hasImage ? '' : 'default-image']"
+            :style="previewImageStyle"
+        >
+            <div class="overlay-text" v-if="hasImageOverlay">
+                <slot name="image-overlay"></slot>
+            </div>
+            <div class="overlay-action-menu" v-if="hasImageOverlayWithActionMenu">
+                <slot name="image-overlay-with-action-menu"></slot>
+            </div>
+        </div>
+        <component
+            :is="hasDescriptionLink ? 'a' : 'div'"
+            :href="hasDescriptionLink ? descriptionLink : ''"
+            :title="descriptionTitle"
+            class="description"
+        >
+            <header
+                :class="[icon ? 'description-icon-' + icon : '']"
+            >
+                {{ title }}
+            </header>
+            <div
+                v-if="displayProgress"
+                :title="progressTitle"
+                class="progress-wrapper" >
+                <progress :value="progress" max="100">{{ progress }}</progress>
+            </div>
+            <div class="description-text-wrapper">
+                <p><slot name="description"></slot></p>
+            </div>
+            <footer>
+                <slot name="footer"></slot>
+            </footer>
+        </component>
+    </component>
+</template>
+
+<script>
+export default {
+    name: "courseware-tile",
+    props: {
+        tag: {
+            type: String,
+            default: "div",
+            validator: tag => {
+                return ["div", "li"].includes(tag);
+            }
+        },
+        color: {
+            type: String,
+            default: "studip-blue",
+            validator: value => {
+                return [
+                    "black",
+                    "charcoal",
+                    "royal-purple",
+                    "iguana-green",
+                    "queen-blue",
+                    "verdigris",
+                    "mulberry",
+                    "pumpkin",
+                    "sunglow",
+                    "apple-green",
+                    "studip-blue",
+                    "studip-lightblue",
+                    "studip-green",
+                    "studip-yellow",
+                    "studip-gray",
+                ].includes(value);
+            }
+        },
+        title: {
+            type: String,
+            default: "–"
+        },
+        icon: {
+            type: String
+        },
+        imageUrl: {
+            type: String
+        },
+        displayProgress: {
+            type: Boolean,
+            default: false
+        },
+        progress: {
+            type: Number,
+            validator: value => {
+                return value >= 0 && value <= 100;
+            }
+        },
+        descriptionLink: {
+            type: String,
+            default: ""
+        },
+        descriptionTitle: {
+            type: String,
+            default: ''
+        }
+    },
+    computed: {
+        hasImage() {
+            return this.imageUrl !== "" && this.imageUrl !== undefined;
+        },
+        hasImageOverlay() {
+            return this.$slots["image-overlay"] !== undefined;
+        },
+        hasImageOverlayWithActionMenu() {
+            return this.$slots["image-overlay-with-action-menu"] !== undefined;
+        },
+        previewImageStyle() {
+            if (this.hasImage) {
+                return { "background-image": "url(" + this.imageUrl + ")" };
+            }
+            else {
+                return {};
+            }
+        },
+        progressTitle() {
+            return this.$gettextInterpolate(this.$gettext("Fortschritt: %{progress}%"), { progress: this.progress });
+        },
+        hasDescriptionLink() {
+            return this.descriptionLink !== '';
+        }
+    },
+    methods: {
+        showProgress(e) {
+            e.preventDefault();
+            this.$emit("showProgress");
+        }
+    },
+}
+</script>
\ No newline at end of file
diff --git a/resources/vue/components/courseware/CoursewareTimelineBlock.vue b/resources/vue/components/courseware/CoursewareTimelineBlock.vue
index d8db9176710..31a678c76e7 100755
--- a/resources/vue/components/courseware/CoursewareTimelineBlock.vue
+++ b/resources/vue/components/courseware/CoursewareTimelineBlock.vue
@@ -146,12 +146,13 @@ import CoursewareTabs from './CoursewareTabs.vue';
 import CoursewareTab from './CoursewareTab.vue';
 import { mapActions } from 'vuex';
 import { blockMixin } from './block-mixin.js';
+import colorMixin from '@/vue/mixins/courseware/colors.js';
 import contentIcons from './content-icons.js';
 import StudipIcon from '../StudipIcon.vue';
 
 export default {
     name: 'courseware-timeline-block',
-    mixins: [blockMixin],
+    mixins: [blockMixin, colorMixin],
     components: {
         CoursewareDefaultBlock,
         CoursewareTabs,
@@ -186,26 +187,7 @@ export default {
             return contentIcons;
         },
         colors() {
-            const colors = [
-                {name: this.$gettext('Schwarz'), class: 'black', hex: '#000000', level: 100, icon: 'black', darkmode: true},
-
-                {name: this.$gettext('Blau'), class: 'studip-blue', hex: '#28497c', level: 100, icon: 'blue', darkmode: true},
-                {name: this.$gettext('Rot'), class: 'studip-red', hex: '#d60000', level: 100, icon: 'red', darkmode: false},
-                {name: this.$gettext('Grün'), class: 'studip-green', hex: '#008512', level: 100, icon: 'green', darkmode: true},
-                {name: this.$gettext('Gelb'), class: 'studip-yellow', hex: '#ffbd33', level: 100, icon: 'yellow', darkmode: false},
-                {name: this.$gettext('Grau'), class: 'studip-gray', hex: '#636a71', level: 100, icon: 'grey', darkmode: true},
-
-                {name: this.$gettext('Holzkohle'), class: 'charcoal', hex: '#3c454e', level: 100, icon: false, darkmode: true},
-                {name: this.$gettext('Königliches Purpur'), class: 'royal-purple', hex: '#8656a2', level: 80, icon: false, darkmode: true},
-                {name: this.$gettext('Leguangrün'), class: 'iguana-green', hex: '#66b570', level: 60, icon: false, darkmode: true},
-                {name: this.$gettext('Königin blau'), class: 'queen-blue', hex: '#536d96', level: 80, icon: false, darkmode: true},
-                {name: this.$gettext('Helles Seegrün'), class: 'verdigris', hex: '#41afaa', level: 80, icon: false, darkmode: true},
-                {name: this.$gettext('Maulbeere'), class: 'mulberry', hex: '#bf5796', level: 80, icon: false, darkmode: true},
-                {name: this.$gettext('Kürbis'), class: 'pumpkin', hex: '#f26e00', level: 100, icon: false, darkmode: true},
-                {name: this.$gettext('Apfelgrün'), class: 'apple-green', hex: '#8bbd40', level: 80, icon: false, darkmode: true},
-            ];
-
-            return colors;
+            return this.mixinColors.filter(color => color.class !== 'white' && color.class !== 'studip-lightblue');
         },
         sortedItems() {
             if (this.currentSort === 'none') {
diff --git a/resources/vue/components/courseware/CoursewareToolsAdmin.vue b/resources/vue/components/courseware/CoursewareToolsAdmin.vue
deleted file mode 100644
index 974e6fbbf8d..00000000000
--- a/resources/vue/components/courseware/CoursewareToolsAdmin.vue
+++ /dev/null
@@ -1,272 +0,0 @@
-<template>
-    <div class="cw-tools cw-tools-admin">
-        <form class="default" @submit.prevent="">
-            <fieldset>
-                <legend>{{ $gettext('Allgemeine Einstellungen') }}</legend>
-                <label>
-                    <span>{{ $gettext('Art der Inhaltsabfolge') }}</span>
-                    <select class="size-s" v-model="currentProgression">
-                        <option value="0">{{ $gettext('Frei') }}</option>
-                        <option value="1">{{ $gettext('Sequentiell') }}</option>
-                    </select>
-                </label>
-
-                <label>
-                    <span>{{ $gettext('Editierberechtigung für Tutor/-innen') }}</span>
-                    <select class="size-s" v-model="currentPermissionLevel">
-                        <option value="dozent">{{ $gettext('Nein') }}</option>
-                        <option value="tutor">{{ $gettext('Ja') }}</option>
-                    </select>
-                </label>
-            </fieldset>
-            <fieldset>
-                <legend>
-                    {{ $gettext('Zertifikate') }}
-                </legend>
-                <label>
-                    <input type="checkbox" name="makecert" v-model="makeCert">
-                    <span>
-                        {{ $gettext('Zertifikat bei Erreichen einer Fortschrittsgrenze versenden') }}
-                    </span>
-                    <studip-tooltip-icon :text="$gettext('Erreicht eine Person in diesem Lernmaterial den ' +
-                        'hier eingestellten Fortschritt, so erhält Sie ein PDF-Zertifikat per E-Mail.')"/>
-                </label>
-                <label v-if="makeCert">
-                    <span>
-                        {{ $gettext('Erforderlicher Fortschritt (in Prozent), um ein Zertifikat zu erhalten') }}
-                    </span>
-                    <input type="number" min="1" max="100" name="threshold" v-model="certThreshold">
-                </label>
-                <label v-if="makeCert">
-                    <span>
-                        {{ $gettext('Hintergrundbild des Zertifikats wählen') }}
-                    </span>
-                    <courseware-file-chooser :isImage="true" v-model="certImage"
-                                             @selectFile="updateCertImage"></courseware-file-chooser>
-                </label>
-            </fieldset>
-            <fieldset>
-                <legend>
-                    {{ $gettext('Erinnerungen') }}
-                </legend>
-                <label>
-                    <input type="checkbox" name="sendreminders" v-model="sendReminders">
-                    <span>
-                        {{ $gettext('Erinnerungsnachrichten an alle Teilnehmenden schicken') }}
-                    </span>
-                    <studip-tooltip-icon :text="$gettext('Hier können periodisch Nachrichten an alle ' +
-                    'Teilnehmenden verschickt werden, um z.B. an die Bearbeitung dieses Lernmaterials zu erinnern.')"/>
-                </label>
-
-                <label v-if="sendReminders">
-                    <span>
-                        {{ $gettext('Zeitraum zwischen Erinnerungen') }}
-                    </span>
-                    <select name="reminder_interval" v-model="reminderInterval">
-                        <option value="7">
-                            {{ $gettext('wöchentlich') }}
-                        </option>
-                        <option value="14">
-                            {{ $gettext('14-tägig') }}
-                        </option>
-                        <option value="30">
-                            {{ $gettext('monatlich') }}
-                        </option>
-                        <option value="90">
-                            {{ $gettext('vierteljährlich') }}
-                        </option>
-                        <option value="180">
-                            {{ $gettext('halbjährlich') }}
-                        </option>
-                        <option value="365">
-                            {{ $gettext('jährlich') }}
-                        </option>
-                    </select>
-                </label>
-                <label v-if="sendReminders" class="col-3">
-                    <span>
-                        {{ $gettext('Erstmalige Erinnerung am') }}
-                        <input type="date" name="reminder_start_date"
-                               v-model="reminderStartDate">
-                    </span>
-                </label>
-                <label v-if="sendReminders" class="col-3">
-                    <span>
-                        {{ $gettext('Letztmalige Erinnerung am') }}
-                        <input type="date" name="reminder_end_date"
-                               v-model="reminderEndDate">
-                    </span>
-                </label>
-                <label v-if="sendReminders">
-                    <span>
-                        {{ $gettext('Text der Erinnerungsmail') }}
-                        <textarea cols="70" rows="4" name="reminder_mail_text" data-editor="minimal"
-                                  v-model="reminderMailText"></textarea>
-                    </span>
-                </label>
-            </fieldset>
-            <fieldset>
-                <legend>
-                    {{ $gettext('Fortschritt') }}
-                </legend>
-                <label>
-                    <input type="checkbox" name="resetprogress" v-model="resetProgress">
-                    <span>
-                        {{ $gettext('Fortschritt periodisch auf 0 zurücksetzen') }}
-                    </span>
-                    <studip-tooltip-icon :text="$gettext('Hier kann eingestellt werden, den Fortschritt ' +
-                        'aller Teilnehmenden periodisch auf 0 zurückzusetzen.')"/>
-                </label>
-                <label v-if="resetProgress">
-                    <span>
-                        {{ $gettext('Zeitraum zum Rücksetzen des Fortschritts') }}
-                    </span>
-                    <select name="reset_progress_interval" v-model="resetProgressInterval">
-                        <option value="14">
-                            {{ $gettext('14-tägig') }}
-                        </option>
-                        <option value="30">
-                            {{ $gettext('monatlich') }}
-                        </option>
-                        <option value="90">
-                            {{ $gettext('vierteljährlich') }}
-                        </option>
-                        <option value="180">
-                            {{ $gettext('halbjährlich') }}
-                        </option>
-                        <option value="365">
-                            {{ $gettext('jährlich') }}
-                        </option>
-                    </select>
-                </label>
-                <label v-if="resetProgress" class="col-3">
-                    <span>
-                        {{ $gettext('Erstmaliges Zurücksetzen am') }}
-                        <input type="date" dataformatas="" name="reset_progress_start_date"
-                               v-model="resetProgressStartDate">
-                    </span>
-                </label>
-                <label v-if="resetProgress" class="col-3">
-                    <span>
-                        {{ $gettext('Letztmaliges Zurücksetzen am') }}
-                        <input type="date" name="reset_progress_end_date"
-                               v-model="resetProgressEndDate">
-                    </span>
-                </label>
-                <label v-if="resetProgress">
-                    <span>
-                        {{ $gettext('Text der Rücksetzungsmail') }}
-                        <textarea cols="70" rows="4" name="reset_progress_mail_text" data-editor="minimal"
-                                  v-model="resetProgressMailText"></textarea>
-                    </span>
-                </label>
-            </fieldset>
-        </form>
-        <button class="button" @click="store">{{ $gettext('Übernehmen') }}</button>
-    </div>
-</template>
-
-<script>
-import { mapActions, mapGetters } from 'vuex';
-import CoursewareFileChooser from "./CoursewareFileChooser.vue";
-import StudipTooltipIcon from '../StudipTooltipIcon.vue';
-
-export default {
-    name: 'cw-tools-admin',
-    components: { StudipTooltipIcon, CoursewareFileChooser },
-    data() {
-        return {
-            currentPermissionLevel: '',
-            currentProgression: '',
-            makeCert: false,
-            certThreshold: 0,
-            certImage: '',
-            sendReminders: false,
-            reminderInterval: 7,
-            reminderStartDate: '',
-            reminderEndDate: '',
-            reminderMailText: '',
-            resetProgress: false,
-            resetProgressInterval: 180,
-            resetProgressStartDate: '',
-            resetProgressEndDate: '',
-            resetProgressMailText: ''
-        };
-    },
-    computed: {
-        ...mapGetters({
-            courseware: 'courseware',
-        }),
-    },
-    methods: {
-        ...mapActions({
-            storeCoursewareSettings: 'storeCoursewareSettings',
-            companionSuccess: 'companionSuccess',
-        }),
-        initData() {
-            console.log(this.courseware.attributes);
-            this.currentPermissionLevel = this.courseware.attributes['editing-permission-level'];
-            this.currentProgression = this.courseware.attributes['sequential-progression'] ? '1' : '0';
-            this.certSettings = this.courseware.attributes['certificate-settings'];
-            this.makeCert = typeof(this.certSettings) === 'object' &&
-                Object.keys(this.certSettings).length > 0;
-            this.certThreshold = this.certSettings.threshold;
-            this.certImage = this.certSettings.image;
-            this.reminderSettings = this.courseware.attributes['reminder-settings'];
-            this.sendReminders = typeof(this.reminderSettings) === 'object' &&
-                Object.keys(this.reminderSettings).length > 0;
-            this.reminderInterval = this.reminderSettings.interval;
-            this.reminderStartDate = this.reminderSettings.startDate;
-            this.reminderEndDate = this.reminderSettings.endDate;
-            this.reminderMailText = this.reminderSettings.mailText;
-            this.resetProgressSettings = this.courseware.attributes['reset-progress-settings'];
-            this.resetProgress = typeof(this.resetProgressSettings) === 'object' &&
-                Object.keys(this.resetProgressSettings).length > 0;
-            this.resetProgressInterval = this.resetProgressSettings.interval;
-            this.resetProgressStartDate = this.resetProgressSettings.startDate;
-            this.resetProgressEndDate = this.resetProgressSettings.endDate;
-            this.resetProgressMailText = this.resetProgressSettings.mailText;
-        },
-        store() {
-            this.companionSuccess({
-                info: this.$gettext('Die Einstellungen wurden übernommen.'),
-            })
-            this.storeCoursewareSettings({
-                permission: this.currentPermissionLevel,
-                progression: this.currentProgression,
-                certificateSettings: this.generateCertificateSettings(),
-                reminderSettings: this.generateReminderSettings(),
-                resetProgressSettings: this.generateResetProgressSettings()
-            });
-        },
-        generateCertificateSettings() {
-            return this.makeCert ? {
-                threshold: this.certThreshold,
-                image: this.certImage
-            } : {};
-        },
-        generateReminderSettings() {
-            return this.sendReminders ? {
-                interval: this.reminderInterval,
-                startDate: this.reminderStartDate,
-                endDate: this.reminderEndDate,
-                mailText: this.reminderMailText
-            } : {};
-        },
-        generateResetProgressSettings() {
-            return this.resetProgress ? {
-                interval: this.resetProgressInterval,
-                startDate: this.resetProgressStartDate,
-                endDate: this.resetProgressEndDate,
-                mailText: this.resetProgressMailText
-            } : {};
-        },
-        updateCertImage(file) {
-            this.certImage = file.id;
-        }
-    },
-    mounted() {
-        this.initData();
-    },
-};
-</script>
diff --git a/resources/vue/components/courseware/CoursewareToolsBlockadder.vue b/resources/vue/components/courseware/CoursewareToolsBlockadder.vue
index 2485eb3ec89..97e5c47dc4e 100644
--- a/resources/vue/components/courseware/CoursewareToolsBlockadder.vue
+++ b/resources/vue/components/courseware/CoursewareToolsBlockadder.vue
@@ -110,8 +110,8 @@ import CoursewareCollapsibleBox from './CoursewareCollapsibleBox.vue';
 import CoursewareBlockadderItem from './CoursewareBlockadderItem.vue';
 import CoursewareContainerAdderItem from './CoursewareContainerAdderItem.vue';
 import CoursewareBlockHelper from './CoursewareBlockHelper.vue';
-import { mapGetters } from 'vuex';
 import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
+import { mapActions, mapGetters } from 'vuex';
 
 export default {
     name: 'cw-tools-blockadder',
@@ -185,6 +185,11 @@ export default {
         }
     },
     methods: {
+        ...mapActions({
+            removeFavoriteBlockType: 'removeFavoriteBlockType',
+            addFavoriteBlockType: 'addFavoriteBlockType',
+            coursewareContainerAdder: 'coursewareContainerAdder'
+        }),
         displayContainerAdder() {
             this.showContaineradder = true;
             this.showBlockadder = false;
@@ -196,9 +201,9 @@ export default {
         },
         toggleFavItem(block) {
             if (this.isBlockFav(block)) {
-                this.$store.dispatch('removeFavoriteBlockType', block.type);
+                this.removeFavoriteBlockType(block.type);
             } else {
-                this.$store.dispatch('addFavoriteBlockType', block.type);
+                this.addFavoriteBlockType(block.type);
             }
         },
         isBlockFav(block) {
@@ -212,7 +217,7 @@ export default {
             return isFav;
         },
         disableContainerAdder() {
-            this.$store.dispatch('coursewareContainerAdder', false);
+            this.coursewareContainerAdder(false);
         },
         endEditFavs() {
             this.showEditFavs = false;
diff --git a/resources/vue/components/courseware/CoursewareUnitItem.vue b/resources/vue/components/courseware/CoursewareUnitItem.vue
new file mode 100644
index 00000000000..a6483321b46
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareUnitItem.vue
@@ -0,0 +1,184 @@
+<template>
+    <div class="courseware-unit-item">
+        <courseware-tile
+            tag="li"
+            :color="color"
+            :title="title"
+            :descriptionLink="url"
+            :descriptionTitle="$gettext('Lernmaterial öffnen')"
+            :displayProgress="inCourseContext"
+            :progress="progress"
+            :imageUrl="imageUrl"
+        >
+            <template #image-overlay-with-action-menu>
+                <studip-action-menu
+                    class="cw-unit-action-menu"
+                    :items="menuItems"  
+                    :context="title"
+                    @showDelete="openDeleteDialog"
+                    @showExport="openExportDialog"
+                    @showProgress="openProgressDialog"
+                    @showSettings="openSettingsDialog"
+                    @copyUnit="copy"
+                />
+            </template>
+            <template #description>
+                {{ description }}
+            </template>
+        </courseware-tile>
+        <studip-dialog
+            v-if="showDeleteDialog"
+            :title="$gettext('Lernmaterial löschen')"
+            :question="$gettextInterpolate(
+                        $gettext('Möchten Sie das Lernmaterial %{ unitTitle } wirklich löschen?'),
+                        { unitTitle: title }
+                    )"
+            height="200"
+            @confirm="executeDelete"
+            @close="closeDeleteDialog"
+        ></studip-dialog>
+
+        <studip-dialog
+            v-if="showProgressDialog"
+            :title="$gettext('Fortschritt')"
+            :closeText="$gettext('Schließen')"
+            closeClass="cancel"
+            width="800"
+            height="600"
+            @close="closeProgressDialog"
+        >
+            <template v-slot:dialogContent>
+                <courseware-unit-progress :progressData="progresses" :unitId="unit.id" :rootId="unitElement.id"/>
+            </template>
+        </studip-dialog>
+
+        <courseware-unit-item-dialog-export v-if="showExportDialog" :unit="unit" @close="showExportDialog = false" />
+        <courseware-unit-item-dialog-settings v-if="showSettingsDialog" :unit="unit" @close="closeSettingsDialog"/>
+    </div>
+</template>
+
+<script>
+import CoursewareTile from './CoursewareTile.vue';
+import CoursewareUnitItemDialogExport from './CoursewareUnitItemDialogExport.vue';
+import CoursewareUnitItemDialogSettings from './CoursewareUnitItemDialogSettings.vue';
+import CoursewareUnitProgress from './CoursewareUnitProgress.vue';
+
+import { mapActions, mapGetters } from 'vuex';
+
+export default {
+    name: 'courseware-unit-item',
+    components: {
+        CoursewareTile,
+        CoursewareUnitItemDialogExport,
+        CoursewareUnitItemDialogSettings,
+        CoursewareUnitProgress,
+    },
+    props: {
+        unit: Object,
+    },
+    data() {
+        return {
+            showDeleteDialog: false,
+            showExportDialog: false,
+            showSettingsDialog: false,
+            showProgressDialog: false,
+            progresses: null
+        }
+    },
+    computed: {
+        ...mapGetters({
+            context: 'context',
+            structuralElementById: 'courseware-structural-elements/byId',
+            userIsTeacher: 'userIsTeacher'
+        }),
+        menuItems() {
+            let menu = [];
+            if (this.inCourseContext) {
+                menu.push({ id: 1, label: this.$gettext('Fortschritt'), icon: 'check-circle', emit: 'showProgress' });
+            }
+            if(this.userIsTeacher && this.inCourseContext) {
+                menu.push({ id: 2, label: this.$gettext('Einstellungen'), icon: 'admin', emit: 'showSettings' });
+            }
+            if(this.userIsTeacher || !this.inCourseContext) {
+                menu.push({ id: 3, label: this.$gettext('Kopieren'), icon: 'files', emit: 'copyUnit' });
+                menu.push({ id: 4, label: this.$gettext('Exportieren'), icon: 'export', emit: 'showExport' });
+                menu.push({ id: 5, label: this.$gettext('Löschen'), icon: 'trash', emit: 'showDelete' });
+            }
+
+            return menu;
+        },
+        unitElement() {
+            return this.structuralElementById({id: this.unit.relationships['structural-element'].data.id}) ?? null;
+        },
+        color() {
+            return this.unitElement?.attributes?.payload?.color ?? 'studip-blue';
+        },
+        title() {
+            return  this.unitElement?.attributes?.title ?? '';
+        },
+        description() {
+            return  this.unitElement?.attributes?.payload?.description ?? '';
+        },
+        imageUrl() {
+            return this.unitElement?.relationships?.image?.meta?.['download-url'] ?? '';
+        },
+        url() {
+            if (this.inCourseContext) {
+                return STUDIP.URLHelper.getURL('dispatch.php/course/courseware/courseware/' + this.unit.id , { cid: this.context.id });
+            } else {
+                return STUDIP.URLHelper.getURL('dispatch.php/contents/courseware/courseware/' + this.unit.id);
+            }
+        },
+        progress() {
+            if (this.unitElement) {
+                return this.progresses?.[this.unitElement.id]?.progress?.cumulative ?? 0;
+            }
+            return 0;
+        },
+        inCourseContext() {
+            return this.context.type === 'courses';
+        }
+    },
+    async mounted() {
+        if (this.inCourseContext) {
+            this.progresses = await this.loadUnitProgresses({unitId: this.unit.id});
+        }
+    },
+    methods: {
+        ...mapActions({
+            deleteUnit: 'deleteUnit',
+            loadUnitProgresses: 'loadUnitProgresses',
+            copyUnit: 'copyUnit',
+            companionSuccess: 'companionSuccess'
+        }),
+        executeDelete() {
+            this.deleteUnit({id: this.unit.id});
+        },
+        openDeleteDialog() {
+            this.showDeleteDialog = true;
+        },
+        closeDeleteDialog() {
+            this.showDeleteDialog = false;
+        },
+        openExportDialog() {
+            this.showExportDialog = true;
+        },
+        async openProgressDialog() {
+            this.showProgressDialog = true;
+            this.progresses = await this.loadUnitProgresses({unitId: this.unit.id});
+        },
+        closeProgressDialog() {
+            this.showProgressDialog = false;
+        },
+        openSettingsDialog() {
+            this.showSettingsDialog = true; 
+        },
+        closeSettingsDialog() {
+            this.showSettingsDialog = false;
+        },
+        async copy() {
+            await this.copyUnit({unitId: this.unit.id, modified: null});
+            this.companionSuccess({ info: this.$gettext('Lernmaterial kopiert.') });        }
+    }
+}
+</script>
\ No newline at end of file
diff --git a/resources/vue/components/courseware/CoursewareUnitItemDialogExport.vue b/resources/vue/components/courseware/CoursewareUnitItemDialogExport.vue
new file mode 100644
index 00000000000..a9c43766d75
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareUnitItemDialogExport.vue
@@ -0,0 +1,132 @@
+<template>
+        <studip-dialog
+            :title="$gettext('Lernmaterial exportieren')"
+            :confirmText="$gettext('Exportieren')"
+            confirmClass="accept"
+            :closeText="$gettext('Schließen')"
+            closeClass="cancel"
+            height="350"
+            @close="$emit('close')"
+            @confirm="executeExport"
+        >
+            <template v-slot:dialogContent>
+                <courseware-companion-box
+                    v-show="!exportRunning"
+                    :msgCompanion="$gettextInterpolate($gettext('Export des Lernmaterials: %{title}'), {title: title})"
+                    mood="curious"
+                />
+
+                <courseware-companion-box
+                    v-show="exportRunning"
+                    :msgCompanion="$gettextInterpolate($gettext('%{title} wird exportiert, bitte haben sie einen Moment Geduld...'), {title: title})"
+                    mood="pointing"
+                />
+                <div v-show="exportRunning" class="cw-import-zip">
+                    <header>{{ exportState }}:</header>
+                    <div class="progress-bar-wrapper">
+                        <div
+                            class="progress-bar"
+                            role="progressbar"
+                            :style="{ width: exportProgress + '%' }"
+                            :aria-valuenow="exportProgress"
+                            aria-valuemin="0"
+                            aria-valuemax="100"
+                        >
+                            {{ exportProgress }}%
+                        </div>
+                    </div>
+                </div>
+            </template>
+        </studip-dialog>
+</template>
+
+<script>
+import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
+import CoursewareExport from '@/vue/mixins/courseware/export.js';
+import { mapActions, mapGetters } from 'vuex';
+
+export default {
+    name: 'courseware-unit-item-dialog-export',
+    mixins: [CoursewareExport],
+    components: {
+        CoursewareCompanionBox,
+    },
+    props: {
+        unit: Object
+    },
+    data() {
+        return {
+            currentInstance: null,
+            exportRunning: false,
+        }
+    },
+    computed: {
+        ...mapGetters({
+            context: 'context',
+            exportProgress: 'exportProgress',
+            exportState: 'exportState',
+            instanceById: 'courseware-instances/byId',
+            structuralElementById: 'courseware-structural-elements/byId',
+            userIsTeacher: 'userIsTeacher', 
+        }),
+        instance() {
+            if (this.inCourseContext) {
+                return this.instanceById({id: 'course_' + this.context.id + '_' + this.unit.id});
+            } else {
+                return this.instanceById({id: 'user_' + this.context.id + '_' + this.unit.id});
+            }
+            
+        },
+        inCourseContext() {
+            return this.context.type === 'courses';
+        },
+        unitElement() {
+            return this.structuralElementById({id: this.unit.relationships['structural-element'].data.id}) ?? null;
+        },
+        title() {
+            return  this.unitElement?.attributes?.title ?? '';
+        },
+    },
+    methods: {
+        ...mapActions({
+            loadInstance: 'loadInstance',
+            setExportState: 'setExportState',
+            companionSuccess: 'companionSuccess'
+        }),
+        async loadUnitInstance() {
+            const context = {type: this.context.type, id: this.context.id, unit: this.unit.id};
+            await this.loadInstance(context);
+        },
+        async executeExport() {
+            if (this.exportRunning) {
+                return;
+            }
+
+            this.exportRunning = true;
+            
+            this.setExportState(this.$gettext('Lade Einstellungen'));
+            await this.loadUnitInstance();
+            this.setExportState('');
+
+            await this.sendExportZip(this.unitElement.id, {
+                withChildren: true,
+                completeExport: true,
+                settings: {
+                    'editing-permission-level': this.instance.attributes['editing-permission-level'] ?? 'tutor',
+                    'sequential-progression': this.instance.attributes['sequential-progression'] ?? 0,
+                    'certificate-settings': this.instance.attributes['certificate-settings'],
+                    'reminder-settings': this.instance.attributes['reminder-settings'],
+                    'reset-progress-settings': this.instance.attributes['reset-progress-settings']
+                }
+            });
+
+            this.exportRunning = false;
+            this.$emit('close');
+        },
+
+    },
+    async mounted() {
+        await this.loadUnitInstance();
+    }
+}
+</script>
\ No newline at end of file
diff --git a/resources/vue/components/courseware/CoursewareUnitItemDialogSettings.vue b/resources/vue/components/courseware/CoursewareUnitItemDialogSettings.vue
new file mode 100644
index 00000000000..8438df308b3
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareUnitItemDialogSettings.vue
@@ -0,0 +1,311 @@
+<template>
+    <studip-dialog
+        :title="$gettext('Einstellungen')"
+        :confirmText="$gettext('Speichern')"
+        confirmClass="accept"
+        :closeText="$gettext('Schließen')"
+        closeClass="cancel"
+        height="600"
+        width="500"
+        @close="$emit('close')"
+        @confirm="storeSettings"
+    >
+        <template v-slot:dialogContent>
+            <form v-if="!loadSettings" class="default" @submit.prevent="">
+                <fieldset>
+                    <legend>{{ $gettext('Allgemeine Einstellungen') }}</legend>
+                    <label>
+                        <span>{{ $gettext('Art der Inhaltsabfolge') }}</span>
+                        <select class="size-s" v-model="currentProgression">
+                            <option value="0">{{ $gettext('Frei') }}</option>
+                            <option value="1">{{ $gettext('Sequentiell') }}</option>
+                        </select>
+                    </label>
+
+                    <label>
+                        <span>{{ $gettext('Editierberechtigung für Tutor/-innen') }}</span>
+                        <select class="size-s" v-model="currentPermissionLevel">
+                            <option value="dozent">{{ $gettext('Nein') }}</option>
+                            <option value="tutor">{{ $gettext('Ja') }}</option>
+                        </select>
+                    </label>
+                </fieldset>
+                <fieldset>
+                    <legend>{{ $gettext('Zertifikate') }}</legend>
+                    <label>
+                        <input type="checkbox" name="makecert" v-model="makeCert">
+                        <span>
+                            {{ $gettext('Zertifikat bei Erreichen einer Fortschrittsgrenze versenden') }}
+                        </span>
+                        <studip-tooltip-icon :text="$gettext('Erreicht eine Person in diesem Lernmaterial den ' +
+                            'hier eingestellten Fortschritt, so erhält Sie ein PDF-Zertifikat per E-Mail.')"/>
+                    </label>
+                    <label v-if="makeCert">
+                        <span>
+                            {{ $gettext('Erforderlicher Fortschritt (in Prozent), um ein Zertifikat zu erhalten') }}
+                        </span>
+                        <input type="number" min="1" max="100" name="threshold" v-model="certThreshold">
+                    </label>
+                    <label v-if="makeCert">
+                        <span>
+                            {{ $gettext('Hintergrundbild des Zertifikats wählen') }}
+                        </span>
+                        <courseware-file-chooser :isImage="true" v-model="certImage" @selectFile="updateCertImage" />
+                    </label>
+                </fieldset>
+                <fieldset>
+                    <legend>
+                        {{ $gettext('Erinnerungen') }}
+                    </legend>
+                    <label>
+                        <input type="checkbox" name="sendreminders" v-model="sendReminders">
+                        <span>
+                            {{ $gettext('Erinnerungsnachrichten an alle Teilnehmenden schicken') }}
+                        </span>
+                        <studip-tooltip-icon :text="$gettext('Hier können periodisch Nachrichten an alle ' +
+                        'Teilnehmenden verschickt werden, um z.B. an die Bearbeitung dieses Lernmaterials zu erinnern.')"/>
+                    </label>
+
+                    <label v-if="sendReminders">
+                        <span>
+                            {{ $gettext('Zeitraum zwischen Erinnerungen') }}
+                        </span>
+                        <select name="reminder_interval" v-model="reminderInterval">
+                            <option value="7">
+                                {{ $gettext('wöchentlich') }}
+                            </option>
+                            <option value="14">
+                                {{ $gettext('14-tägig') }}
+                            </option>
+                            <option value="30">
+                                {{ $gettext('monatlich') }}
+                            </option>
+                            <option value="90">
+                                {{ $gettext('vierteljährlich') }}
+                            </option>
+                            <option value="180">
+                                {{ $gettext('halbjährlich') }}
+                            </option>
+                            <option value="365">
+                                {{ $gettext('jährlich') }}
+                            </option>
+                        </select>
+                    </label>
+                    <label v-if="sendReminders" class="col-3">
+                        <span>
+                            {{ $gettext('Erstmalige Erinnerung am') }}
+                            <input type="date" name="reminder_start_date"
+                                v-model="reminderStartDate">
+                        </span>
+                    </label>
+                    <label v-if="sendReminders" class="col-3">
+                        <span>
+                            {{ $gettext('Letztmalige Erinnerung am') }}
+                            <input type="date" name="reminder_end_date"
+                                v-model="reminderEndDate">
+                        </span>
+                    </label>
+                    <label v-if="sendReminders">
+                        <span>
+                            {{ $gettext('Text der Erinnerungsmail') }}
+                            <textarea cols="70" rows="4" name="reminder_mail_text" data-editor="minimal"
+                                    v-model="reminderMailText"></textarea>
+                        </span>
+                    </label>
+                </fieldset>
+                <fieldset>
+                    <legend>
+                        {{ $gettext('Fortschritt') }}
+                    </legend>
+                    <label>
+                        <input type="checkbox" name="resetprogress" v-model="resetProgress">
+                        <span>
+                            {{ $gettext('Fortschritt periodisch auf 0 zurücksetzen') }}
+                        </span>
+                        <studip-tooltip-icon :text="$gettext('Hier kann eingestellt werden, den Fortschritt ' +
+                            'aller Teilnehmenden periodisch auf 0 zurückzusetzen.')"/>
+                    </label>
+                    <label v-if="resetProgress">
+                        <span>
+                            {{ $gettext('Zeitraum zum Rücksetzen des Fortschritts') }}
+                        </span>
+                        <select name="reset_progress_interval" v-model="resetProgressInterval">
+                            <option value="14">
+                                {{ $gettext('14-tägig') }}
+                            </option>
+                            <option value="30">
+                                {{ $gettext('monatlich') }}
+                            </option>
+                            <option value="90">
+                                {{ $gettext('vierteljährlich') }}
+                            </option>
+                            <option value="180">
+                                {{ $gettext('halbjährlich') }}
+                            </option>
+                            <option value="365">
+                                {{ $gettext('jährlich') }}
+                            </option>
+                        </select>
+                    </label>
+                    <label v-if="resetProgress" class="col-3">
+                        <span>
+                            {{ $gettext('Erstmaliges Zurücksetzen am') }}
+                            <input type="date" dataformatas="" name="reset_progress_start_date"
+                                v-model="resetProgressStartDate">
+                        </span>
+                    </label>
+                    <label v-if="resetProgress" class="col-3">
+                        <span>
+                            {{ $gettext('Letztmaliges Zurücksetzen am') }}
+                            <input type="date" name="reset_progress_end_date"
+                                v-model="resetProgressEndDate">
+                        </span>
+                    </label>
+                    <label v-if="resetProgress">
+                        <span>
+                            {{ $gettext('Text der Rücksetzungsmail') }}
+                            <textarea cols="70" rows="4" name="reset_progress_mail_text" data-editor="minimal"
+                                    v-model="resetProgressMailText"></textarea>
+                        </span>
+                    </label>
+                </fieldset>
+            </form>
+            <studip-progress-indicator v-else :description="$gettext('Lade Einstellungen…')"/>
+
+        </template>
+    </studip-dialog>
+</template>
+
+<script>
+import CoursewareFileChooser from "./CoursewareFileChooser.vue";
+import StudipProgressIndicator from '../StudipProgressIndicator.vue';
+
+import { mapActions, mapGetters } from 'vuex';
+
+export default {
+    name: 'courseware-unit-item-dialog-settings',
+    components: {
+        CoursewareFileChooser,
+        StudipProgressIndicator,
+    },
+    props: {
+        unit: Object
+    },
+    data() {
+        return {
+            currentInstance: null,
+            loadSettings: false,
+            currentPermissionLevel: '',
+            currentProgression: 0,
+            makeCert: false,
+            certThreshold: 0,
+            certImage: '',
+            sendReminders: false,
+            reminderInterval: 7,
+            reminderStartDate: '',
+            reminderEndDate: '',
+            reminderMailText: '',
+            resetProgress: false,
+            resetProgressInterval: 180,
+            resetProgressStartDate: '',
+            resetProgressEndDate: '',
+            resetProgressMailText: ''
+        }
+    },
+    computed: {
+        ...mapGetters({
+            context: 'context',
+            instanceById: 'courseware-instances/byId',
+            userIsTeacher: 'userIsTeacher'
+        }),
+        instance() {
+            if (this.inCourseContext) {
+                return this.instanceById({id: 'course_' + this.context.id + '_' + this.unit.id});
+            } else {
+                return this.instanceById({id: 'user_' + this.context.id + '_' + this.unit.id});
+            }
+            
+        },
+        inCourseContext() {
+            return this.context.type === 'courses';
+        }
+    },
+    methods: {
+        ...mapActions({
+            loadInstance: 'loadInstance',
+            storeCoursewareSettings: 'storeCoursewareSettings',
+            companionSuccess: 'companionSuccess'
+        }),
+        async loadUnitInstance() {
+            const context = {type: this.context.type, id: this.context.id, unit: this.unit.id};
+            await this.loadInstance(context);
+        },
+        initData() {
+            this.currentPermissionLevel = this.currentInstance.attributes['editing-permission-level'];
+            this.currentProgression = this.currentInstance.attributes['sequential-progression'] ? '1' : '0';
+            this.certSettings = this.currentInstance.attributes['certificate-settings'];
+            this.makeCert = typeof(this.certSettings) === 'object' &&
+                Object.keys(this.certSettings).length > 0;
+            this.certThreshold = this.certSettings.threshold;
+            this.certImage = this.certSettings.image;
+            this.reminderSettings = this.currentInstance.attributes['reminder-settings'];
+            this.sendReminders = typeof(this.reminderSettings) === 'object' &&
+                Object.keys(this.reminderSettings).length > 0;
+            this.reminderInterval = this.reminderSettings.interval;
+            this.reminderStartDate = this.reminderSettings.startDate;
+            this.reminderEndDate = this.reminderSettings.endDate;
+            this.reminderMailText = this.reminderSettings.mailText;
+            this.resetProgressSettings = this.currentInstance.attributes['reset-progress-settings'];
+            this.resetProgress = typeof(this.resetProgressSettings) === 'object' &&
+                Object.keys(this.resetProgressSettings).length > 0;
+            this.resetProgressInterval = this.resetProgressSettings.interval;
+            this.resetProgressStartDate = this.resetProgressSettings.startDate;
+            this.resetProgressEndDate = this.resetProgressSettings.endDate;
+            this.resetProgressMailText = this.resetProgressSettings.mailText;
+        },
+        storeSettings() {
+            this.$emit('close');
+            this.currentInstance.attributes['editing-permission-level'] = this.currentPermissionLevel;
+            this.currentInstance.attributes['sequential-progression'] = this.currentProgression;
+            this.currentInstance.attributes['certificate-settings'] = this.generateCertificateSettings();
+            this.currentInstance.attributes['reminder-settings'] = this.generateReminderSettings();
+            this.currentInstance.attributes['reset-progress-settings'] = this.generateResetProgressSettings();
+            this.storeCoursewareSettings({
+                instance: this.currentInstance,
+            });
+        },
+        generateCertificateSettings() {
+            return this.makeCert ? {
+                threshold: this.certThreshold,
+                image: this.certImage
+            } : {};
+        },
+        generateReminderSettings() {
+            return this.sendReminders ? {
+                interval: this.reminderInterval,
+                startDate: this.reminderStartDate,
+                endDate: this.reminderEndDate,
+                mailText: this.reminderMailText
+            } : {};
+        },
+        generateResetProgressSettings() {
+            return this.resetProgress ? {
+                interval: this.resetProgressInterval,
+                startDate: this.resetProgressStartDate,
+                endDate: this.resetProgressEndDate,
+                mailText: this.resetProgressMailText
+            } : {};
+        },
+        updateCertImage(file) {
+            this.certImage = file.id;
+        }
+    },
+    async mounted() {
+        this.loadSettings = true;
+        await this.loadUnitInstance();
+        this.loadSettings = false;
+        this.currentInstance = this.instance;
+        this.initData();
+    }
+}
+</script>
\ No newline at end of file
diff --git a/resources/vue/components/courseware/CoursewareUnitItems.vue b/resources/vue/components/courseware/CoursewareUnitItems.vue
new file mode 100644
index 00000000000..5995baa7f19
--- /dev/null
+++ b/resources/vue/components/courseware/CoursewareUnitItems.vue
@@ -0,0 +1,77 @@
+<template>
+    <div class="cw-unit-items">
+        <ul v-if="hasUnits" class="cw-tiles">
+            <courseware-unit-item v-for="unit in units" :key="unit.id" :unit="unit"/>
+        </ul>
+        <div v-if="!hasUnits && userIsTeacher && inCourseContext" class="cw-contents-overview-teaser">
+            <div class="cw-contents-overview-teaser-content">
+                <header>{{ $gettext('Lernmaterialien') }}</header>
+                <p>
+                    {{ $gettext('Mit Courseware können Sie interaktive, multimediale Lerninhalte erstellen und nutzen. ' +
+                                'Die Lerninhalte lassen sich hierarchisch unterteilen und können aus Texten, Videosequenzen, ' +
+                                'Aufgaben, Kommunikationselementen und einer Vielzahl weiterer Elemente bestehen. ' +
+                                'Fertige Lerninhalte können exportiert und in andere Kurse oder andere Installationen importiert werden. ' +
+                                'Courseware ist nicht nur für digitale Formate geeignet, sondern kann auch genutzt werden, ' +
+                                'um klassische Präsenzveranstaltungen mit Online-Anteilen zu ergänzen. Formate wie integriertes Lernen ' +
+                                '(Blended Learning) lassen sich mit Courseware ideal umsetzen. Kollaboratives Lernen kann dank Schreibrechtevergabe ' +
+                                'und dem Einsatz von Courseware in Studiengruppen realisiert werden.') }}
+                </p>
+                <button class="button" @click="setShowUnitAddDialog(true)">
+                    {{ $gettext('Neues Lernmaterial anlegen') }}
+                </button>
+            </div>
+        </div>
+        <courseware-companion-box
+            v-if="!userIsTeacher && inCourseContext"
+            :msgCompanion="$gettext('Es wurden leider noch keine Lernmaterialien angelegt.')"
+            mood="sad"
+        />
+        <div v-if="!hasUnits && !inCourseContext" class="cw-contents-overview-teaser">
+            <div class="cw-contents-overview-teaser-content">
+                <header>{{ $gettext('Ihre persönlichen Lernmaterialien') }}</header>
+                <p>{{ $gettext('Erstellen und verwalten Sie hier Ihre eigenen persönlichen Lernmaterialien in Form von ePorfolios,' +
+                               'Vorlagen für Veranstaltungen oder einfach nur persönliche Inhalte für das Studium.' +
+                               'Entwickeln Sie Ihre eigenen (Lehr-)Materialien für Studium oder die Lehre und teilen diese mit anderen Nutzenden.') }}</p>
+                <button class="button" @click="setShowUnitAddDialog(true)">
+                    {{ $gettext('Neues Lernmaterial anlegen') }}
+                </button>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
+import CoursewareUnitItem from './CoursewareUnitItem.vue';
+
+import { mapActions, mapGetters } from 'vuex';
+
+export default {
+    name: 'courseware-unit-items',
+    components: {
+        CoursewareCompanionBox,
+        CoursewareUnitItem,
+    },
+    computed: {
+        ...mapGetters({
+            context: 'context',
+            coursewareUnits: 'courseware-units/all',
+            userIsTeacher: 'userIsTeacher'
+        }),
+        units() {
+            return this.coursewareUnits.filter(unit => unit.relationships.range.data.id === this.context.id) ?? [];
+        },
+        hasUnits() {
+            return this.units.length > 0;
+        },
+        inCourseContext() {
+            return this.context.type === 'courses';
+        }
+    },
+    methods: {
+        ...mapActions({
+            setShowUnitAddDialog: 'setShowUnitAddDialog',
+        }),
+    }
+}
+</script>
\ No newline at end of file
diff --git a/resources/vue/components/courseware/CoursewareDashboardProgress.vue b/resources/vue/components/courseware/CoursewareUnitProgress.vue
similarity index 72%
rename from resources/vue/components/courseware/CoursewareDashboardProgress.vue
rename to resources/vue/components/courseware/CoursewareUnitProgress.vue
index 6594318a742..61b53500a6b 100644
--- a/resources/vue/components/courseware/CoursewareDashboardProgress.vue
+++ b/resources/vue/components/courseware/CoursewareUnitProgress.vue
@@ -1,6 +1,6 @@
 <template>
-    <div class="cw-dashboard-progress">
-        <nav aria-label="Breadcrumb" class="cw-dashboard-progress-breadcrumb">
+    <div class="cw-unit-progress">
+        <nav aria-label="Breadcrumb" class="cw-unit-progress-breadcrumb">
             <a
                 v-if="parent"
                 href="#"
@@ -18,8 +18,8 @@
                 / {{ parent.name }}
             </a>
         </nav>
-        <div v-if="selected" class="cw-dashboard-progress-chapter">
-            <a :href="chapterUrl" :title="$gettextInterpolate($gettext('%{ pageTitle } öffnen'), {pageTitle: selected.name})">
+        <div v-if="selected" class="cw-unit-progress-chapter">
+            <a :href="chapterUrl" :title="$gettextInterpolate('%{ pageTitle } öffnen', {pageTitle: selected.name})">
                 <h1>{{ selected.name }}</h1>
             </a>
             <courseware-progress-circle
@@ -28,12 +28,12 @@
             />
             <courseware-progress-circle
                 :title="$gettext('diese Seite')"
-                class="cw-dashboard-progress-current"
+                class="cw-unit-progress-current"
                 :value="parseInt(selected.progress.self)"
             />
         </div>
-        <div class="cw-dashboard-progress-subchapter-list">
-            <courseware-dashboard-progress-item
+        <div class="cw-unit-progress-subchapter-list">
+            <courseware-unit-progress-item
                 v-for="chapter in children"
                 :key="chapter.id"
                 :name="chapter.name"
@@ -41,8 +41,8 @@
                 :chapterId="chapter.id"
                 @selectChapter="selectChapter"
             />
-            <div v-if="!children.length" class="cw-dashboard-empty-info">
-                <courseware-companion-box
+            <div v-if="!children.length" class="cw-unit-empty-info">
+                <courseware-companion-box 
                     mood="sad"
                     :msgCompanion="$gettext('Diese Seite enthält keine darunter liegenden Seiten.')"
                 />
@@ -52,32 +52,36 @@
 </template>
 
 <script>
-import StudipIcon from '../StudipIcon.vue';
 import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
-import CoursewareDashboardProgressItem from './CoursewareDashboardProgressItem.vue';
+import CoursewareUnitProgressItem from './CoursewareUnitProgressItem.vue';
 import CoursewareProgressCircle from './CoursewareProgressCircle.vue';
+import StudipIcon from '../StudipIcon.vue';
 
 export default {
-    name: 'courseware-dashboard-progress',
+    name: 'courseware-unit-progress',
     components: {
         CoursewareCompanionBox,
-        CoursewareDashboardProgressItem,
+        CoursewareUnitProgressItem,
         CoursewareProgressCircle,
         StudipIcon,
     },
+    props: {
+        progressData: Object,
+        unitId: String,
+        rootId: String
+    },
     data() {
         return {
             selected: null,
         };
     },
     computed: {
-        progressData() {
-            return STUDIP.courseware_progress_data;
-        },
         chapterUrl() {
             return (
                 STUDIP.URLHelper.base_url +
-                'dispatch.php/course/courseware/?cid=' +
+                'dispatch.php/course/courseware/courseware/'+
+                this.unitId +
+                '?cid=' +
                 STUDIP.URLHelper.parameters.cid +
                 '#/structural_element/' +
                 this.selected.id
@@ -100,7 +104,7 @@ export default {
     },
     methods: {
         visitRoot() {
-            this.selected = Object.values(this.progressData).find(({ parent_id }) => !!parent_id) ?? null;
+            this.selected = this.progressData[this.rootId];
         },
         selectChapter(id) {
             this.selected = this.progressData[id] ?? null;
diff --git a/resources/vue/components/courseware/CoursewareDashboardProgressItem.vue b/resources/vue/components/courseware/CoursewareUnitProgressItem.vue
similarity index 71%
rename from resources/vue/components/courseware/CoursewareDashboardProgressItem.vue
rename to resources/vue/components/courseware/CoursewareUnitProgressItem.vue
index e5ec0d23567..b83a6928984 100644
--- a/resources/vue/components/courseware/CoursewareDashboardProgressItem.vue
+++ b/resources/vue/components/courseware/CoursewareUnitProgressItem.vue
@@ -1,14 +1,14 @@
 <template>
     <a 
         href="#"
-        class="cw-dashboard-progress-item"
+        class="cw-unit-progress-item"
         :title="name"
         @click="$emit('selectChapter', chapterId)"
     >
-        <div class="cw-dashboard-progress-item-value">
+        <div class="cw-unit-progress-item-value">
             <courseware-progress-circle :value="parseInt(value)" />
         </div>
-        <div class="cw-dashboard-progress-item-description">
+        <div class="cw-unit-progress-item-description">
             {{ name }}
         </div>
     </a>
@@ -18,7 +18,7 @@
 import CoursewareProgressCircle from './CoursewareProgressCircle.vue';
 
 export default {
-    name: 'courseware-dashboard-progress-item',
+    name: 'courseware-unit-progress-item',
     components: {
         CoursewareProgressCircle,
     },
diff --git a/resources/vue/components/courseware/CoursewareViewWidget.vue b/resources/vue/components/courseware/CoursewareViewWidget.vue
index fb3de7441a2..f6ab44c21e5 100644
--- a/resources/vue/components/courseware/CoursewareViewWidget.vue
+++ b/resources/vue/components/courseware/CoursewareViewWidget.vue
@@ -74,7 +74,7 @@ export default {
             this.coursewareViewMode('edit');
         },
         setDiscussView() {
-            this.$store.dispatch('coursewareViewMode', 'discuss');
+            this.coursewareViewMode('discuss');
         },
     },
 };
diff --git a/resources/vue/components/courseware/CoursewareWellcomeScreen.vue b/resources/vue/components/courseware/CoursewareWelcomeScreen.vue
similarity index 57%
rename from resources/vue/components/courseware/CoursewareWellcomeScreen.vue
rename to resources/vue/components/courseware/CoursewareWelcomeScreen.vue
index 52a629eaf46..0f0352f39ae 100644
--- a/resources/vue/components/courseware/CoursewareWellcomeScreen.vue
+++ b/resources/vue/components/courseware/CoursewareWelcomeScreen.vue
@@ -1,16 +1,19 @@
 <template>
-    <div class="cw-wellcome-screen">
-        <div class="cw-wellcome-screen-keyvisual"></div>
+    <div class="cw-welcome-screen">
+        <div class="cw-welcome-screen-keyvisual"></div>
         <header>
-            <translate>Willkommen bei Courseware</translate>
+            {{ $gettext('Willkommen bei Courseware') }}
         </header>
-        <div class="cw-wellcome-screen-actions">
-            <a href="https://hilfe.studip.de/help/5.0/de/Basis.Courseware" target="_blank" class="button">
-                <translate>Mehr über Courseware erfahren</translate>
+        <div class="cw-welcome-screen-actions">
+            <a href="https://hilfe.studip.de/help/5.3/de/Basis.Courseware" target="_blank" class="button">
+                {{ $gettext('Mehr über Courseware erfahren') }}
             </a>
-            <button class="button" :title="$gettext('Fügt einen Standard-Abschnitt mit einem Text-Block hinzu')" @click="addDefault"><translate>Ersten Inhalt erstellen</translate></button>
-            <button class="button" @click="addContainer"><translate>Einen Abschnitt auswählen</translate></button>
-
+            <button class="button" :title="$gettext('Fügt einen Standard-Abschnitt mit einem Text-Block hinzu')" @click="addDefault">
+                {{ $gettext('Ersten Inhalt erstellen') }}
+            </button>
+            <button class="button" @click="addContainer">
+                {{ $gettext('Einen Abschnitt auswählen') }}
+            </button>
         </div>
     </div>
 </template>
@@ -19,16 +22,12 @@
 import { mapActions, mapGetters } from 'vuex';
 
 export default {
-    name: 'courseware-wellcome-screen',
-    components: {
-    },
-    props: {},
-    data() {
-        return{}
-    },
+    name: 'courseware-welcome-screen',
     computed: {
         ...mapGetters({
-            consumeMode: 'consumeMode'
+            consumeMode: 'consumeMode',
+            lastCreatedBlocks: 'courseware-blocks/lastCreated',
+            lastCreatedContainers: 'courseware-containers/lastCreated'
         }),
     },
     methods: {
@@ -40,12 +39,18 @@ export default {
             updateContainer: 'updateContainer',
             lockObject: 'lockObject',
             unlockObject: 'unlockObject',
+
+            coursewareConsumeMode: 'coursewareConsumeMode',
+            coursewareViewMode: 'coursewareViewMode',
+            coursewareContainerAdder: 'coursewareContainerAdder',
+            coursewareShowToolbar: 'coursewareShowToolbar'
+
         }),
         addContainer() {
-            this.$store.dispatch('coursewareConsumeMode', false);
-            this.$store.dispatch('coursewareViewMode', 'edit');
-            this.$store.dispatch('coursewareContainerAdder', true);
-            this.$store.dispatch('coursewareShowToolbar', true);
+            this.coursewareConsumeMode(false);
+            this.coursewareViewMode('edit');
+            this.coursewareContainerAdder(true);
+            this.coursewareShowToolbar(true);
         },
         async addDefault() {
             let attributes = {};
@@ -55,19 +60,19 @@ export default {
                 sections: [{ name: 'Liste', icon: '', blocks: [] }],
             };
             await this.createContainer({ structuralElementId: this.$route.params.id, attributes: attributes });
-            let newContainer = this.$store.getters['courseware-containers/lastCreated'];
+            let newContainer = this.lastCreatedContainers;
             await this.lockObject({ id: newContainer.id, type: 'courseware-containers' });
             await this.createBlock({
                 container: newContainer,
                 section: 0,
                 blockType: 'text',
             });
-            this.$store.dispatch('coursewareViewMode', 'edit');
-            this.$store.dispatch('coursewareConsumeMode', false);
+            this.coursewareViewMode('edit');
+            this.coursewareConsumeMode(false);
             this.companionSuccess({
-                info: this.$gettext('Das Elemente für Ihren ersten Inhalt wurden angelegt.'),
+                info: this.$gettext('Das Elemente für Ihren ersten Inhalt wurde angelegt.'),
             });
-            const newBlock = this.$store.getters['courseware-blocks/lastCreated'];
+            const newBlock = this.lastCreatedBlocks;
             newContainer.attributes.payload.sections[0].blocks.push(newBlock.id);
             const structuralElementId = this.$route.params.id
             await this.updateContainer({ container: newContainer, structuralElementId: structuralElementId });
diff --git a/resources/vue/components/courseware/DashboardApp.vue b/resources/vue/components/courseware/DashboardApp.vue
deleted file mode 100644
index 34290c55863..00000000000
--- a/resources/vue/components/courseware/DashboardApp.vue
+++ /dev/null
@@ -1,36 +0,0 @@
-<template>
-    <div class="cw-dashboard-wrapper">
-        <courseware-course-dashboard></courseware-course-dashboard>
-        <MountingPortal mountTo="#courseware-dashboard-view-widget" name="sidebar-views">
-            <courseware-dashboard-view-widget></courseware-dashboard-view-widget>
-        </MountingPortal>
-    </div>
-</template>
-
-<script>
-import CoursewareCourseDashboard from './CoursewareCourseDashboard.vue';
-import CoursewareDashboardViewWidget from './CoursewareDashboardViewWidget.vue';
-import { mapActions, mapGetters } from 'vuex';
-
-export default {
-    components: {
-        CoursewareCourseDashboard,
-        CoursewareDashboardViewWidget
-    },
-    computed: {
-        ...mapGetters({
-            userId: 'userId',
-        }),
-    },
-    methods: {
-        ...mapActions({
-            loadCoursewareStructure: 'courseware-structure/load',
-            loadTeacherStatus: 'loadTeacherStatus',
-        }),
-    },
-    async mounted() {
-        await this.loadCoursewareStructure();
-        await this.loadTeacherStatus(this.userId);
-    }
-};
-</script>
diff --git a/resources/vue/components/courseware/IndexApp.vue b/resources/vue/components/courseware/IndexApp.vue
index 7c9c15304b1..c0facd5d286 100644
--- a/resources/vue/components/courseware/IndexApp.vue
+++ b/resources/vue/components/courseware/IndexApp.vue
@@ -10,16 +10,19 @@
                 @select="selectStructuralElement"
             ></courseware-structural-element>
             <MountingPortal mountTo="#courseware-action-widget" name="sidebar-actions">
-                <courseware-action-widget :structural-element="selected" :canVisit="canVisit" v-if="!showSearchResults"></courseware-action-widget>
+                <courseware-action-widget v-if="!showSearchResults && canEditSelected" :structural-element="selected"></courseware-action-widget>
             </MountingPortal>
             <MountingPortal mountTo="#courseware-search-widget" name="sidebar-search">
-                <courseware-search-widget></courseware-search-widget>
+                <courseware-search-widget v-if="selected !== null"></courseware-search-widget>
             </MountingPortal>
             <MountingPortal mountTo="#courseware-view-widget" name="sidebar-views">
-                <courseware-view-widget :structural-element="selected" :canVisit="canVisit" v-if="!showSearchResults"></courseware-view-widget>
+                <courseware-view-widget v-if="!showSearchResults" :structural-element="selected" :canVisit="canVisit"></courseware-view-widget>
+            </MountingPortal>
+            <MountingPortal mountTo="#courseware-import-widget" name="sidebar-import">
+                <courseware-import-widget v-if="!showSearchResults && canEditSelected" :structural-element="selected"></courseware-import-widget>
             </MountingPortal>
             <MountingPortal mountTo="#courseware-export-widget" name="sidebar-export">
-                <courseware-export-widget :structural-element="selected" :canVisit="canVisit" v-if="!showSearchResults"></courseware-export-widget>
+                <courseware-export-widget v-if="!showSearchResults" :structural-element="selected" :canVisit="canVisit"></courseware-export-widget>
             </MountingPortal>
         </div>
         <studip-progress-indicator
@@ -42,6 +45,7 @@ import CoursewareSearchResults from './CoursewareSearchResults.vue';
 import CoursewareViewWidget from './CoursewareViewWidget.vue';
 import CoursewareActionWidget from './CoursewareActionWidget.vue';
 import CoursewareExportWidget from './CoursewareExportWidget.vue';
+import CoursewareImportWidget from './CoursewareImportWidget.vue';
 import CoursewareCompanionBox from './CoursewareCompanionBox.vue';
 import CoursewareSearchWidget from './CoursewareSearchWidget.vue';
 import CoursewareCompanionOverlay from './CoursewareCompanionOverlay.vue';
@@ -58,6 +62,7 @@ export default {
         CoursewareCompanionBox,
         StudipProgressIndicator,
         CoursewareExportWidget,
+        CoursewareImportWidget,
         CoursewareSearchWidget,
         CoursewareCompanionOverlay,
     },
@@ -89,6 +94,13 @@ export default {
                 default:
                     return this.$gettext('Beim Laden der Seite ist ein Fehler aufgetreten.');
             }
+        },
+        canEditSelected() {
+            if (this.selected) {
+                return this.selected.attributes['can-edit'];
+            }
+
+            return false;
         }
     },
     methods: {
@@ -98,7 +110,6 @@ export default {
             invalidateStructureCache: 'courseware-structure/invalidateCache',
             loadCoursewareStructure: 'courseware-structure/load',
             loadStructuralElement: 'loadStructuralElement',
-            loadTeacherStatus: 'loadTeacherStatus',
         }),
         async selectStructuralElement(id) {
             if (!id) {
@@ -120,9 +131,7 @@ export default {
             this.structureLoadingState = 'error';
             return;
         }
-        if (this.context.type === 'courses') {
-            await this.loadTeacherStatus(this.userId);
-        }
+
         this.structureLoadingState = 'done';
         const selectedId = this.$route.params?.id;
         await this.selectStructuralElement(selectedId);
diff --git a/resources/vue/components/courseware/ShelfApp.vue b/resources/vue/components/courseware/ShelfApp.vue
new file mode 100644
index 00000000000..e9f275d8d96
--- /dev/null
+++ b/resources/vue/components/courseware/ShelfApp.vue
@@ -0,0 +1,65 @@
+<template>
+    <div>
+        <div class="cw-shelf">
+            <courseware-unit-items />
+        </div>
+        <courseware-shelf-dialog-add v-if="showUnitAddDialog" />
+        <courseware-shelf-dialog-copy v-if="showUnitCopyDialog" />
+        <courseware-shelf-dialog-import v-if="showUnitImportDialog" />
+        <MountingPortal v-if="userIsTeacher || !inCourseContext" mountTo="#courseware-action-widget" name="sidebar-actions">
+            <courseware-shelf-action-widget></courseware-shelf-action-widget>
+        </MountingPortal>
+        <MountingPortal v-if="userIsTeacher || !inCourseContext" mountTo="#courseware-import-widget" name="sidebar-imports">
+            <courseware-shelf-import-widget></courseware-shelf-import-widget>
+        </MountingPortal>
+        <courseware-companion-overlay />
+    </div>
+</template>
+
+<script>
+import CoursewareShelfActionWidget from './CoursewareShelfActionWidget.vue';
+import CoursewareShelfImportWidget from './CoursewareShelfImportWidget.vue';
+import CoursewareShelfDialogAdd from './CoursewareShelfDialogAdd.vue';
+import CoursewareShelfDialogCopy from './CoursewareShelfDialogCopy.vue';
+import CoursewareShelfDialogImport from './CoursewareShelfDialogImport.vue';
+import CoursewareUnitItems from './CoursewareUnitItems.vue';
+import CoursewareCompanionOverlay from './CoursewareCompanionOverlay.vue';
+
+import { mapActions, mapGetters } from 'vuex';
+
+export default {
+    components: {
+        CoursewareShelfActionWidget,
+        CoursewareShelfImportWidget,
+        CoursewareShelfDialogAdd,
+        CoursewareShelfDialogCopy,
+        CoursewareShelfDialogImport,
+        CoursewareUnitItems,
+        CoursewareCompanionOverlay,
+    },
+    computed: {
+        ...mapGetters({
+            showUnitAddDialog: 'showUnitAddDialog',
+            showUnitCopyDialog: 'showUnitCopyDialog',
+            showUnitImportDialog: 'showUnitImportDialog',
+            showUnitLinkDialog: 'showUnitLinkDialog',
+            licenses: 'licenses',
+            context:'context',
+            userIsTeacher: 'userIsTeacher',
+            userId: 'userId'
+        }),
+        inCourseContext() {
+            return this.context.type === 'courses';
+        }
+
+    },
+    methods: {
+        ...mapActions({
+            setShowUnitAddDialog: 'setShowUnitAddDialog',
+            setShowUnitCopyDialog: 'setShowUnitCopyDialog',
+            setShowUnitImportDialog: 'setShowUnitImportDialog',
+            setShowUnitLinkDialog: 'setShowUnitLinkDialog',
+        }),
+    },
+}
+</script>
diff --git a/resources/vue/components/courseware/TasksApp.vue b/resources/vue/components/courseware/TasksApp.vue
new file mode 100644
index 00000000000..37ec927542c
--- /dev/null
+++ b/resources/vue/components/courseware/TasksApp.vue
@@ -0,0 +1,31 @@
+<template>
+    <div class="cw-tasks-wrapper">
+        <div class="cw-tasks-list">
+            <courseware-dashboard-students v-if="userIsTeacher" />
+            <courseware-dashboard-tasks v-else />
+        </div>
+        <MountingPortal mountTo="#courseware-action-widget" name="sidebar-actions" v-if="userIsTeacher">
+            <courseware-tasks-action-widget />
+        </MountingPortal>
+    </div>
+</template>
+
+<script>
+import CoursewareTasksActionWidget from './CoursewareTasksActionWidget.vue';
+import CoursewareDashboardTasks from './CoursewareDashboardTasks.vue'
+import CoursewareDashboardStudents from './CoursewareDashboardStudents.vue'
+import { mapGetters } from 'vuex';
+
+export default {
+    components: {
+        CoursewareTasksActionWidget,
+        CoursewareDashboardTasks,
+        CoursewareDashboardStudents,
+    },
+    computed: {
+        ...mapGetters({
+            userIsTeacher: 'userIsTeacher',
+        }),
+    },
+};
+</script>
diff --git a/resources/vue/courseware-content-overview-app.js b/resources/vue/courseware-activities-app.js
similarity index 64%
rename from resources/vue/courseware-content-overview-app.js
rename to resources/vue/courseware-activities-app.js
index ccacce86174..ee2eb849de2 100644
--- a/resources/vue/courseware-content-overview-app.js
+++ b/resources/vue/courseware-activities-app.js
@@ -1,23 +1,19 @@
-import ContentOverviewApp from './components/courseware/ContentOverviewApp.vue';
-import CoursewareStructureModule from './store/courseware/structure.module';
+import ActivitiesApp from './components/courseware/ActivitiesApp.vue';
 import { mapResourceModules } from '@elan-ev/reststate-vuex';
-import Vue from 'vue';
 import Vuex from 'vuex';
 import CoursewareModule from './store/courseware/courseware.module';
+import CoursewareActivitiesModule from './store/courseware/courseware-activities.module';
+import CoursewareStructureModule from './store/courseware/structure.module';
 import axios from 'axios';
-import vSelect from 'vue-select';
-import 'vue-select/dist/vue-select.css'
 
-Vue.component('v-select', vSelect);
-
-const mountApp = (STUDIP, createApp, element) => {
+const mountApp = async (STUDIP, createApp, element) => {
     const getHttpClient = () =>
-    axios.create({
-        baseURL: STUDIP.URLHelper.getURL(`jsonapi.php/v1`, {}, true),
-        headers: {
-            'Content-Type': 'application/vnd.api+json',
-        },
-    });
+        axios.create({
+            baseURL: STUDIP.URLHelper.getURL(`jsonapi.php/v1`, {}, true),
+            headers: {
+                'Content-Type': 'application/vnd.api+json',
+            },
+        });
 
     const httpClient = getHttpClient();
 
@@ -25,6 +21,7 @@ const mountApp = (STUDIP, createApp, element) => {
         modules: {
             courseware: CoursewareModule,
             'courseware-structure': CoursewareStructureModule,
+            'courseware-activities': CoursewareActivitiesModule,
             ...mapResourceModules({
                 names: [
                     'activities',
@@ -37,11 +34,15 @@ const mountApp = (STUDIP, createApp, element) => {
                     'courseware-containers',
                     'courseware-instances',
                     'courseware-structural-elements',
-                    'courseware-structural-elements-shared',
-                    'courseware-templates',
+                    'courseware-task-feedback',
+                    'courseware-task-groups',
+                    'courseware-tasks',
+                    'courseware-units',
                     'courseware-user-data-fields',
                     'courseware-user-progresses',
+                    'files',
                     'file-refs',
+                    'folders',
                     'users',
                     'institutes',
                     'semesters',
@@ -55,7 +56,6 @@ const mountApp = (STUDIP, createApp, element) => {
     });
     let entry_id = null;
     let entry_type = null;
-    let licenses = null;
     let elem;
 
     if ((elem = document.getElementById(element.substring(1))) !== undefined) {
@@ -67,34 +67,27 @@ const mountApp = (STUDIP, createApp, element) => {
             if (elem.attributes['entry-id'] !== undefined) {
                 entry_id = elem.attributes['entry-id'].value;
             }
-
-            if (elem.attributes['licenses'] !== undefined) {
-                licenses = JSON.parse(elem.attributes['licenses'].value);
-            }
         }
     }
 
     store.dispatch('setUserId', STUDIP.USER_ID);
-    store.dispatch('users/loadById', {id: STUDIP.USER_ID});
-    store.dispatch('courseware-structural-elements/loadById',{ id: STUDIP.COURSEWARE_USERS_ROOT_ID, options: { include: 'children'}});
-    store.dispatch('courseware-templates/loadAll');
+    await store.dispatch('users/loadById', {id: STUDIP.USER_ID});
     store.dispatch('setHttpClient', httpClient);
-    store.dispatch('licenses', licenses);
     store.dispatch('coursewareContext', {
         id: entry_id,
         type: entry_type,
     });
-
-    store.dispatch('courseware-structural-elements-shared/loadAll', { options: { include: 'owner' } });
+    await store.dispatch('loadTeacherStatus', STUDIP.USER_ID);
+    await store.dispatch('loadCourseUnits', entry_id);
 
     const app = createApp({
-        render: (h) => h(ContentOverviewApp),
-        store
+        render: (h) => h(ActivitiesApp),
+        store,
     });
 
     app.$mount(element);
 
     return app;
-}
+};
 
 export default mountApp;
diff --git a/resources/vue/courseware-index-app.js b/resources/vue/courseware-index-app.js
index e39d053b966..57a8936da1b 100644
--- a/resources/vue/courseware-index-app.js
+++ b/resources/vue/courseware-index-app.js
@@ -22,6 +22,7 @@ const mountApp = async (STUDIP, createApp, element) => {
     let elem_id = null;
     let entry_id = null;
     let entry_type = null;
+    let unit_id = null;
     let oer_enabled = null;
     let licenses = null;
     let elem;
@@ -40,6 +41,10 @@ const mountApp = async (STUDIP, createApp, element) => {
                 entry_id = elem.attributes['entry-id'].value;
             }
 
+            if (elem.attributes['unit-id'] !== undefined) {
+                unit_id = elem.attributes['unit-id'].value;
+            }
+
             if (elem.attributes['oer-enabled'] !== undefined) {
                 oer_enabled = elem.attributes['oer-enabled'].value;
             }
@@ -49,7 +54,6 @@ const mountApp = async (STUDIP, createApp, element) => {
             }
         }
     }
-
     const routes = [
         {
             path: '/',
@@ -97,8 +101,10 @@ const mountApp = async (STUDIP, createApp, element) => {
                     'courseware-task-feedback',
                     'courseware-task-groups',
                     'courseware-tasks',
+                    'courseware-templates',
                     'courseware-user-data-fields',
                     'courseware-user-progresses',
+                    'courseware-units',
                     'files',
                     'file-refs',
                     'folders',
@@ -124,12 +130,18 @@ const mountApp = async (STUDIP, createApp, element) => {
     store.dispatch('coursewareContext', {
         id: entry_id,
         type: entry_type,
+        unit: unit_id
     });
 
+    if (entry_type === 'courses') {
+        await store.dispatch('loadTeacherStatus', STUDIP.USER_ID);
+    }
+
     store.dispatch('coursewareCurrentElement', elem_id);
 
     store.dispatch('oerEnabled', oer_enabled);
     store.dispatch('licenses', licenses);
+    store.dispatch('courseware-templates/loadAll');
 
     const pluginManager = new PluginManager();
     store.dispatch('setPluginManager', pluginManager);
diff --git a/resources/vue/courseware-shelf-app.js b/resources/vue/courseware-shelf-app.js
new file mode 100644
index 00000000000..2d31aef10df
--- /dev/null
+++ b/resources/vue/courseware-shelf-app.js
@@ -0,0 +1,93 @@
+import CoursewareShelfModule from './store/courseware/courseware-shelf.module';
+import ShelfApp from './components/courseware/ShelfApp.vue';
+import Vuex from 'vuex';
+import axios from 'axios';
+import { mapResourceModules } from '@elan-ev/reststate-vuex';
+
+const mountApp = async (STUDIP, createApp, element) => {
+    const getHttpClient = () =>
+        axios.create({
+            baseURL: STUDIP.URLHelper.getURL(`jsonapi.php/v1`, {}, true),
+            headers: {
+                'Content-Type': 'application/vnd.api+json',
+            },
+        });
+
+    let elem;
+    let entry_id = null;
+    let entry_type = null;
+    let licenses = null;
+
+    if ((elem = document.getElementById(element.substring(1))) !== undefined) {
+        if (elem.attributes !== undefined) {
+            if (elem.attributes['entry-type'] !== undefined) {
+                entry_type = elem.attributes['entry-type'].value;
+            }
+
+            if (elem.attributes['entry-id'] !== undefined) {
+                entry_id = elem.attributes['entry-id'].value;
+            }
+
+            if (elem.attributes['licenses'] !== undefined) {
+                licenses = JSON.parse(elem.attributes['licenses'].value);
+            }
+        }
+    }
+
+    const httpClient = getHttpClient();
+
+    const store = new Vuex.Store({
+        modules: {
+            'courseware-shelf': CoursewareShelfModule,
+            ...mapResourceModules({
+                names: [
+                    'courses',
+                    'course-memberships',
+                    'courseware-blocks',
+                    'courseware-containers',
+                    'courseware-instances',
+                    'courseware-units',
+                    'courseware-user-data-fields',
+                    'courseware-user-progresses',
+                    'courseware-structural-elements',
+                    'files',
+                    'file-refs',
+                    'folders',
+                    'users',
+                    'institutes',
+                    'institute-memberships',
+                    'semesters',
+                    'sem-classes',
+                    'sem-types',
+                    'terms-of-use'
+                ],
+                httpClient,
+            }),
+        },
+    });
+    store.dispatch('setUrlHelper', STUDIP.URLHelper);
+    store.dispatch('setHttpClient', httpClient);
+    store.dispatch('setLicenses', licenses);
+    store.dispatch('setUserId', STUDIP.USER_ID);
+    await store.dispatch('users/loadById', {id: STUDIP.USER_ID});
+    store.dispatch('setContext', {
+        id: entry_id,
+        type: entry_type,
+    });
+    if (entry_type === 'courses') {
+        await store.dispatch('loadTeacherStatus', STUDIP.USER_ID);
+        await store.dispatch('loadCourseUnits', entry_id);
+    } else {
+        await store.dispatch('loadUserUnits', entry_id);
+    }
+
+    const app = createApp({
+        render: (h) => h(ShelfApp),
+        store,
+    });
+
+    app.$mount(element);
+
+};
+
+export default mountApp;
\ No newline at end of file
diff --git a/resources/vue/courseware-dashboard-app.js b/resources/vue/courseware-tasks-app.js
similarity index 88%
rename from resources/vue/courseware-dashboard-app.js
rename to resources/vue/courseware-tasks-app.js
index 5ddaf344ff1..2f332466d79 100644
--- a/resources/vue/courseware-dashboard-app.js
+++ b/resources/vue/courseware-tasks-app.js
@@ -1,7 +1,8 @@
-import DashboardApp from './components/courseware/DashboardApp.vue';
+import TasksApp from './components/courseware/TasksApp.vue';
 import { mapResourceModules } from '@elan-ev/reststate-vuex';
 import Vuex from 'vuex';
 import CoursewareModule from './store/courseware/courseware.module';
+import CoursewareTasksModule from './store/courseware/courseware-tasks.module';
 import CoursewareStructureModule from './store/courseware/structure.module';
 import axios from 'axios';
 
@@ -19,6 +20,7 @@ const mountApp = async (STUDIP, createApp, element) => {
     const store = new Vuex.Store({
         modules: {
             courseware: CoursewareModule,
+            tasks: CoursewareTasksModule,
             'courseware-structure': CoursewareStructureModule,
             ...mapResourceModules({
                 names: [
@@ -35,6 +37,7 @@ const mountApp = async (STUDIP, createApp, element) => {
                     'courseware-task-feedback',
                     'courseware-task-groups',
                     'courseware-tasks',
+                    'courseware-units',
                     'courseware-user-data-fields',
                     'courseware-user-progresses',
                     'files',
@@ -42,7 +45,6 @@ const mountApp = async (STUDIP, createApp, element) => {
                     'folders',
                     'users',
                     'institutes',
-                    'institute-memberships',
                     'semesters',
                     'sem-classes',
                     'sem-types',
@@ -69,12 +71,13 @@ const mountApp = async (STUDIP, createApp, element) => {
     }
 
     store.dispatch('setUserId', STUDIP.USER_ID);
-    await store.dispatch('users/loadById', { id: STUDIP.USER_ID });
+    await store.dispatch('users/loadById', {id: STUDIP.USER_ID});
     store.dispatch('setHttpClient', httpClient);
     store.dispatch('coursewareContext', {
         id: entry_id,
         type: entry_type,
     });
+    await store.dispatch('loadTeacherStatus', STUDIP.USER_ID);
     store.dispatch('courseware-tasks/loadAll', {
         options: {
             'filter[cid]': entry_id,
@@ -83,7 +86,7 @@ const mountApp = async (STUDIP, createApp, element) => {
     });
 
     const app = createApp({
-        render: (h) => h(DashboardApp),
+        render: (h) => h(TasksApp),
         store,
     });
 
diff --git a/resources/vue/mixins/courseware/colors.js b/resources/vue/mixins/courseware/colors.js
new file mode 100644
index 00000000000..870ae7cc301
--- /dev/null
+++ b/resources/vue/mixins/courseware/colors.js
@@ -0,0 +1,149 @@
+const colorMixin = {
+    computed: {
+        mixinColors() {
+            const colors = [
+                {
+                    name: this.$gettext('Schwarz'),
+                    class: 'black',
+                    hex: '#000000',
+                    level: 100,
+                    icon: 'black',
+                    darkmode: true,
+                },
+                {
+                    name: this.$gettext('Weiß'),
+                    class: 'white',
+                    hex: '#ffffff',
+                    level: 100,
+                    icon: 'white',
+                    darkmode: false,
+                },
+
+                {
+                    name: this.$gettext('Blau'),
+                    class: 'studip-blue',
+                    hex: '#28497c',
+                    level: 100,
+                    icon: 'blue',
+                    darkmode: true,
+                },
+                {
+                    name: this.$gettext('Hellblau'),
+                    class: 'studip-lightblue',
+                    hex: '#e7ebf1',
+                    level: 40,
+                    icon: 'lightblue',
+                    darkmode: false,
+                },
+                {
+                    name: this.$gettext('Rot'),
+                    class: 'studip-red',
+                    hex: '#d60000',
+                    level: 100,
+                    icon: 'red',
+                    darkmode: false,
+                },
+                {
+                    name: this.$gettext('Grün'),
+                    class: 'studip-green',
+                    hex: '#008512',
+                    level: 100,
+                    icon: 'green',
+                    darkmode: true,
+                },
+                {
+                    name: this.$gettext('Gelb'),
+                    class: 'studip-yellow',
+                    hex: '#ffbd33',
+                    level: 100,
+                    icon: 'yellow',
+                    darkmode: false,
+                },
+                {
+                    name: this.$gettext('Grau'),
+                    class: 'studip-gray',
+                    hex: '#636a71',
+                    level: 100,
+                    icon: 'grey',
+                    darkmode: true,
+                },
+
+                {
+                    name: this.$gettext('Holzkohle'),
+                    class: 'charcoal',
+                    hex: '#3c454e',
+                    level: 100,
+                    icon: false,
+                    darkmode: true,
+                },
+                {
+                    name: this.$gettext('Königliches Purpur'),
+                    class: 'royal-purple',
+                    hex: '#8656a2',
+                    level: 80,
+                    icon: false,
+                    darkmode: true,
+                },
+                {
+                    name: this.$gettext('Leguangrün'),
+                    class: 'iguana-green',
+                    hex: '#66b570',
+                    level: 60,
+                    icon: false,
+                    darkmode: true,
+                },
+                {
+                    name: this.$gettext('Königin blau'),
+                    class: 'queen-blue',
+                    hex: '#536d96',
+                    level: 80,
+                    icon: false,
+                    darkmode: true,
+                },
+                {
+                    name: this.$gettext('Helles Seegrün'),
+                    class: 'verdigris',
+                    hex: '#41afaa',
+                    level: 80,
+                    icon: false,
+                    darkmode: true,
+                },
+                {
+                    name: this.$gettext('Maulbeere'),
+                    class: 'mulberry',
+                    hex: '#bf5796',
+                    level: 80,
+                    icon: false,
+                    darkmode: true,
+                },
+                {
+                    name: this.$gettext('Kürbis'),
+                    class: 'pumpkin',
+                    hex: '#f26e00',
+                    level: 100,
+                    icon: false,
+                    darkmode: true,
+                },
+                {
+                    name: this.$gettext('Sonnenschein'),
+                    class: 'sunglow',
+                    hex: '#ffca5c',
+                    level: 80,
+                    icon: false,
+                    darkmode: false,
+                },
+                {
+                    name: this.$gettext('Apfelgrün'),
+                    class: 'apple-green',
+                    hex: '#8bbd40',
+                    level: 80,
+                    icon: false,
+                    darkmode: true,
+                },
+            ];
+            return colors;
+        }
+    },
+};
+
+export default colorMixin;
\ No newline at end of file
diff --git a/resources/vue/mixins/courseware/export.js b/resources/vue/mixins/courseware/export.js
index dd0b5229d0a..bdd0a3b0cdd 100644
--- a/resources/vue/mixins/courseware/export.js
+++ b/resources/vue/mixins/courseware/export.js
@@ -11,7 +11,10 @@ export default {
             containerById: 'courseware-containers/byId',
             folderById: 'folders/byId',
             filesById: 'files/byId',
+            fileRefsById: 'file-refs/byId',
             structuralElementById: 'courseware-structural-elements/byId',
+            allStructuralElements: 'courseware-structural-elements/all',
+            allBlocks: 'courseware-blocks/all',
         }),
     },
 
@@ -48,11 +51,13 @@ export default {
         },
 
         async createExportFile(root_id = null, options) {
-            let completeExport = false;
+            if (!options || !options.completeExport) {
+                options.completeExport = false;
+            }
 
             if (!root_id) {
                 root_id = this.courseware.relationships.root.data.id;
-                completeExport = true;
+                options.completeExport = true;
             }
             this.setExportState(this.$gettext('Exportiere Elemente'));
             this.setExportProgress(0);
@@ -62,7 +67,7 @@ export default {
             zip.file('courseware.json', JSON.stringify(exportData.json));
             zip.file('files.json', JSON.stringify(exportData.files.json));
 
-            if (completeExport) {
+            if (options.completeExport) {
                 zip.file('settings.json', JSON.stringify(exportData.settings));
             }
 
@@ -93,14 +98,14 @@ export default {
             if (options && options.withChildren === true) {
                 withChildren = true;
             }
-
+            await this.loadStructuralElement(root_id);
             let root_element = await this.structuralElementById({id: root_id});
 
             //prevent loss of data
             root_element = JSON.parse(JSON.stringify(root_element));
 
             // load whole courseware nonetheless, only export relevant elements
-            let elements = await this.$store.getters['courseware-structural-elements/all'];
+            let elements = await this.allStructuralElements;
             this.exportElementCounter = 0;
             if (withChildren) {
                 this.elementCounter = this.countElements(elements);
@@ -135,9 +140,21 @@ export default {
             delete root_element.links;
 
             let settings = {
-                'editing-permission-level': this.courseware.attributes['editing-permission-level'],
-                'sequential-progression': this.courseware.attributes['sequential-progression']
+                'editing-permission-level': 'tutor',
+                'sequential-progression': '0'
             };
+            if (this.courseware != null) {
+                settings = {
+                    'editing-permission-level': this.courseware.attributes['editing-permission-level'],
+                    'sequential-progression': this.courseware.attributes['sequential-progression']
+                };
+            }
+            if (options && options.settings) {
+                settings = {
+                    'editing-permission-level': options.settings['editing-permission-level'],
+                    'sequential-progression': options.settings['sequential-progression']
+                };
+            }
 
             return {
                 json: root_element,
@@ -189,7 +206,6 @@ export default {
                 this.companionInfo({ info: this.$gettext('Die Seite wurde an den OER Campus gesendet.') });
             }).catch(error => {
                 this.companionError({ info: this.$gettext('Beim Veröffentlichen der Seite ist ein Fehler aufgetreten.') });
-                console.debug(error);
             });
         },
 
@@ -198,15 +214,13 @@ export default {
 
             for (var i = 0; i < data.length; i++) {
                 if (data[i].relationships.parent.data?.id === parentId && data[i].attributes['can-edit']) {
+                    const content = { ...data[i] };
+                    await this.loadStructuralElement(content.id);
                     let new_childs = await this.exportStructuralElement(data[i].id, data);
                     this.exportElementCounter++;
-                    let content = { ...data[i] };
                     content.containers = [];
 
-                    await this.loadStructuralElement(content.id);
-
                     let element = this.structuralElementById({ id: content.id });
-
                     // load containers, if there are any for this struct
                     if (element.relationships.containers?.data?.length) {
                         for (var j = 0; j < element.relationships.containers.data.length; j++) {
@@ -237,9 +251,9 @@ export default {
         async exportStructuralElementImage(element) {
             let fileId = element.relationships.image?.data?.id;
             if (fileId) {
-                await this.$store.dispatch('file-refs/loadById', {id: fileId});
-                let fileRef = this.$store.getters['file-refs/byId']({id: fileId});
-
+                await this.loadFileRefsById({id: fileId});
+                let fileRef = this.fileRefsById({id: fileId});
+                
                 let fileRefData = {};
                 fileRefData.id = fileRef.id;
                 fileRefData.attributes = fileRef.attributes;
@@ -262,7 +276,7 @@ export default {
 
             container.blocks = [];
 
-            let blocks = this.$store.getters['courseware-blocks/all'];
+            let blocks = this.allBlocks;
 
             // now, load the blocks for this container, if there are any
             if (blocks.length) {
@@ -358,14 +372,15 @@ export default {
             }
         },
 
-        ...mapActions([
-            'loadStructuralElement',
-            'loadFileRefs',
-            'loadFolder',
-            'companionInfo',
-            'setExportState',
-            'setExportProgress'
-        ]),
+        ...mapActions({
+            loadStructuralElement: 'loadStructuralElement',
+            loadFileRefs: 'loadFileRefs',
+            loadFolder: 'loadFolder',
+            companionInfo: 'companionInfo',
+            setExportState: 'setExportState',
+            setExportProgress: 'setExportProgress',
+            loadFileRefsById: 'file-refs/loadById'
+        }),
     },
     watch: {
         exportElementCounter(counter) {
diff --git a/resources/vue/mixins/courseware/import.js b/resources/vue/mixins/courseware/import.js
index 100a0e5bca7..4b46ab89dae 100644
--- a/resources/vue/mixins/courseware/import.js
+++ b/resources/vue/mixins/courseware/import.js
@@ -9,6 +9,7 @@ export default {
             elementCounter: 0,
             importElementCounter: 0,
             currentImportErrors: [],
+            unitId: null,
         };
     },
 
@@ -16,8 +17,26 @@ export default {
         ...mapGetters({
             context: 'context',
             courseware: 'courseware-instances/all',
+            instanceById: 'courseware-instances/byId',
+            lastCreatedBlocks: 'courseware-blocks/lastCreated',
+            lastCreatedContainers: 'courseware-containers/lastCreated',
+            lastCreatedElements: 'courseware-structural-elements/lastCreated',
             structuralElementById: 'courseware-structural-elements/byId',
         }),
+        instance() {
+            if (this.unitId) {
+                if (this.inCourseContext) {
+                    return this.instanceById({id: 'course_' + this.context.id + '_' + this.unitId});
+                } else {
+                    return this.instanceById({id: 'user_' + this.context.id + '_' + this.unitId});
+                }
+            } else {
+                return null;
+            }
+        },
+        inCourseContext() {
+            return this.context.type === 'courses';
+        }
     },
 
     methods: {
@@ -26,7 +45,7 @@ export default {
             updateStructuralElement: 'updateStructuralElement'
         }),
 
-        async importCourseware(element, rootId, files, importBehavior)
+        async importCourseware(element, rootId, files, importBehavior, settings)
         {
             // import all files
             await this.uploadAllFiles(files);
@@ -40,7 +59,7 @@ export default {
                 await this.importStructuralElement([element], rootId, files);
             }
             if (importBehavior === 'migrate') {
-                await this.migrateCourseware(element, rootId, files);
+                await this.migrateCourseware(element, rootId, files, settings);
             }
         },
 
@@ -70,8 +89,20 @@ export default {
             return counter;
         },
 
-        async migrateCourseware(element, rootId, files) {
+        async migrateCourseware(element, rootId, files, settings) {
             let root = this.structuralElementById({ id: rootId });
+
+            if (settings) {
+                this.unitId = root.relationships.unit.data.id;
+                const context = {type: this.context.type, id: this.context.id, unit: this.unitId};
+                await this.loadInstance(context);
+                let currentInstance = this.instance;
+                currentInstance.attributes['editing-permission-level'] = settings['editing-permission-level'];
+                currentInstance.attributes['sequential-progression'] = settings['sequential-progression'];
+                this.storeCoursewareSettings({
+                    instance: currentInstance,
+                });
+            }
             // add containers and blocks
             if (element.containers?.length > 0) {
                 await Promise.all(element.containers.map((container) => this.importContainer(container, root, files)));
@@ -152,7 +183,7 @@ export default {
 
                     this.importElementCounter++;
 
-                    let new_element = this.$store.getters['courseware-structural-elements/lastCreated'];
+                    let new_element = this.lastCreatedElements;
 
                     if (element[i].imageId) {
                         await this.setStructuralElementImage(new_element, element[i].imageId, files);
@@ -205,7 +236,7 @@ export default {
             }
 
             this.importElementCounter++;
-            let new_container = this.$store.getters['courseware-containers/lastCreated'];
+            let new_container = this.lastCreatedContainers;
             await this.unlockObject({ id: new_container.id, type: 'courseware-containers' });
 
             if (container.blocks?.length) {
@@ -242,7 +273,7 @@ export default {
                 return null;
             }
 
-            let new_block = this.$store.getters['courseware-blocks/lastCreated'];
+            let new_block = this.lastCreatedBlocks;
 
             // update old id ids in payload part
             for (var i = 0; i < files.length; i++) {
@@ -394,6 +425,7 @@ export default {
             'createFolder',
             'createRootFolder',
             'createFile',
+            'loadInstance',
             'lockObject',
             'unlockObject',
             'setImportFilesState',
@@ -401,6 +433,7 @@ export default {
             'setImportStructuresState',
             'setImportStructuresProgress',
             'setImportErrors',
+            'storeCoursewareSettings',
             'uploadImageForStructuralElement'
         ]),
     },
diff --git a/resources/vue/store/courseware/courseware-activities.module.js b/resources/vue/store/courseware/courseware-activities.module.js
new file mode 100644
index 00000000000..2b96193f27a
--- /dev/null
+++ b/resources/vue/store/courseware/courseware-activities.module.js
@@ -0,0 +1,49 @@
+const getDefaultState = () => {
+    return {
+        typeFilter: 'all',
+        unitFilter: 'all',
+    };
+};
+
+const initialState = getDefaultState();
+
+const getters = {
+    typeFilter(state) {
+        return state.typeFilter;
+    },
+    unitFilter(state) {
+        return state.unitFilter;
+    },
+};
+
+export const state = { ...initialState };
+
+export const actions = {
+    // setters
+    setTypeFilter({ commit }, context) {
+        commit('setTypeFilter', context);
+    },
+
+    setUnitFilter({ commit }, context) {
+        commit('setUnitFilter', context);
+    },
+
+    // other actions
+};
+
+export const mutations = {
+    setTypeFilter(state, data){
+        state.typeFilter = data;
+    },
+
+    setUnitFilter(state, data){
+        state.unitFilter = data;
+    },
+};
+
+export default {
+    state,
+    actions,
+    mutations,
+    getters,
+};
diff --git a/resources/vue/store/courseware/courseware-shelf.module.js b/resources/vue/store/courseware/courseware-shelf.module.js
new file mode 100644
index 00000000000..1b91ff48f78
--- /dev/null
+++ b/resources/vue/store/courseware/courseware-shelf.module.js
@@ -0,0 +1,766 @@
+const getDefaultState = () => {
+    return {
+        context: null,
+        httpClient: null,
+        showCompanionOverlay: false,
+        msgCompanionOverlay: '',
+        styleCompanionOverlay: 'default',
+        showToolbar: false,
+        showUnitAddDialog: false,
+        showUnitCopyDialog: false,
+        showUnitImportDialog: false,
+        showUnitLinkDialog: false,
+        licenses: null,
+        userId: null,
+        exportState: '',
+        exportProgress: 0,
+        courseware: null,
+        userIsTeacher: false,
+        urlHelper: null,
+
+        importFilesState: '',
+        importFilesProgress: 0,
+        importStructuresState: '',
+        importStructuresProgress: 0,
+        importErrors: [],
+    };
+};
+
+const initialState = getDefaultState();
+
+const getters = {
+    context(state) {
+        return state.context;
+    },
+    httpClient(state) {
+        return state.httpClient;
+    },
+    showCompanionOverlay(state) {
+        return state.showCompanionOverlay;
+    },
+    msgCompanionOverlay(state) {
+        return state.msgCompanionOverlay;
+    },
+    styleCompanionOverlay(state) {
+        return state.styleCompanionOverlay;
+    },
+    showToolbar(state) {
+        return state.showToolbar;
+    },
+    showUnitAddDialog(state) {
+        return state.showUnitAddDialog;
+    },
+    showUnitCopyDialog(state) {
+        return state.showUnitCopyDialog;
+    },
+    showUnitImportDialog(state) {
+        return state.showUnitImportDialog;
+    },
+    showUnitLinkDialog(state) {
+        return state.showUnitLinkDialog;
+    },
+    licenses(state) {
+        return state.licenses;
+    },
+    userId(state) {
+        return state.userId;
+    },
+    courseware(state) {
+        return state.courseware;
+    },
+    exportState(state) {
+        return state.exportState;
+    },
+    exportProgress(state) {
+        return state.exportProgress;
+    },
+    userIsTeacher(state) {
+        return state.userIsTeacher;
+    },
+    urlHelper(state) {
+        return state.urlHelper;
+    },
+    importFilesState(state) {
+        return state.importFilesState;
+    },
+    importFilesProgress(state) {
+        return state.importFilesProgress;
+    },
+    importStructuresState(state) {
+        return state.importStructuresState;
+    },
+    importStructuresProgress(state) {
+        return state.importStructuresProgress;
+    },
+    importErrors(state) {
+        return state.importErrors;
+    },
+};
+
+export const state = { ...initialState };
+
+export const actions = {
+    // setters
+    setContext({ commit }, context) {
+        commit('setContext', context);
+    },
+    setHttpClient({ commit }, httpClient) {
+        commit('setHttpClient', httpClient);
+    },
+    setShowUnitAddDialog({ commit }, show) {
+        commit('setShowUnitAddDialog', show);
+    },
+    setShowUnitCopyDialog({ commit }, show) {
+        commit('setShowUnitCopyDialog', show);
+    },
+    setShowUnitImportDialog({ commit }, show) {
+        commit('setShowUnitImportDialog', show);
+    },
+    setShowUnitLinkDialog({ commit }, show) {
+        commit('setShowUnitLinkDialog', show);
+    },
+    setLicenses({ commit }, licenses) {
+        commit('setLicenses', licenses);
+    },
+    setShowCompanionOverlay(context, companionOverlay) {
+        context.commit('setShowCompanionOverlay', companionOverlay);
+    },
+    setMsgCompanionOverlay(context, companionOverlayMsg) {
+        context.commit('setMsgCompanionOverlay', companionOverlayMsg);
+    },
+    setStyleCompanionOverlay(context, companionOverlayStyle) {
+        context.commit('setStyleCompanionOverlay', companionOverlayStyle);
+    },
+    setUserId(context, id) {
+        context.commit('setUserId', id);
+    },
+    setExportState(context, state) {
+        context.commit('setExportState', state);
+    },
+    setExportProgress(context, percent) {
+        context.commit('setExportProgress', percent);
+    },
+    setUrlHelper(context, urlHelper) {
+        context.commit('setUrlHelper', urlHelper);
+    },
+
+    // other actions
+    loadCourseUnits({ dispatch }, cid) {
+        const parent = { type: 'courses', id: cid };
+        const relationship = 'courseware-units';
+        const options = { include: 'structural-element' }
+
+        return dispatch('loadRelatedPaginated', {
+            type: 'courseware-units',
+            parent,
+            relationship,
+            options,
+        });
+    },
+
+    loadUserUnits({ dispatch }, uid) {
+        const parent = { type: 'users', id: uid };
+        const relationship = 'courseware-units';
+        const options = { include: 'structural-element' }
+
+        return dispatch('loadRelatedPaginated', {
+            type: 'courseware-units',
+            parent,
+            relationship,
+            options,
+        });
+    },
+
+    async loadUnitProgresses({ getters }, { unitId }) {
+         const response = await state.httpClient.get(`courseware-units/${unitId}/courseware-user-progresses`);
+         if (response.status === 200) {
+            return response.data;
+         } else {
+            return null;
+         }
+    },
+
+    async loadRelatedPaginated({ dispatch, rootGetters }, { type, parent, relationship, options }) {
+        const limit = 100;
+        let offset = 0;
+
+        await loadPage(offset, limit);
+        const total = rootGetters[`${type}/lastMeta`].page.total;
+
+        const pages = [];
+        for (let page = 1; page * limit < total; page++) {
+            pages.push(loadPage(page * limit, limit));
+        }
+
+        return Promise.all(pages);
+
+        function loadPage(offset, limit) {
+            return dispatch(
+                `${type}/loadRelated`,
+                {
+                    parent,
+                    relationship,
+                    options: {
+                        ...options,
+                        'page[offset]': offset,
+                        'page[limit]': limit,
+                    },
+                    resetRelated: false,
+                },
+                { root: true }
+            )
+        }
+    },
+    async companionInfo({ dispatch }, { info }) {
+        await dispatch('setStyleCompanionOverlay', 'default');
+        await dispatch('setMsgCompanionOverlay', info);
+        return dispatch('setShowCompanionOverlay', true);
+    },
+
+    async companionSuccess({ dispatch }, { info }) {
+        await dispatch('setStyleCompanionOverlay', 'happy');
+        await dispatch('setMsgCompanionOverlay', info);
+        return dispatch('setShowCompanionOverlay', true);
+    },
+
+    async companionError({ dispatch }, { info }) {
+        await dispatch('setStyleCompanionOverlay', 'sad');
+        await dispatch('setMsgCompanionOverlay', info);
+        return dispatch('setShowCompanionOverlay', true);
+    },
+
+    async companionWarning({ dispatch }, { info }) {
+        await dispatch('setStyleCompanionOverlay', 'alert');
+        await dispatch('setMsgCompanionOverlay', info);
+        return dispatch('setShowCompanionOverlay', true);
+    },
+
+    async companionSpecial({ dispatch }, { info }) {
+        await dispatch('setStyleCompanionOverlay', 'special');
+        await dispatch('setMsgCompanionOverlay', info);
+        return dispatch('setShowCompanionOverlay', true);
+    },
+    coursewareShowCompanionOverlay({dispatch}, { data }) {
+        return dispatch('setShowCompanionOverlay', data);
+    },
+
+    async deleteUnit({ dispatch, state }, data) {
+        await dispatch('courseware-units/delete', data, { root: true });
+        if (state.context.type === 'courses') {
+            return dispatch('loadCourseUnits', state.context.id);
+        }
+        if (state.context.type === 'users') {
+            return dispatch('loadUserUnits', state.context.id);
+        }
+    },
+
+    async copyUnit({ dispatch, state }, { unitId, modified }) {
+        let rangeType = null;
+        let loadUnits = null;
+        if (state.context.type === 'courses') {
+            rangeType = 'course';
+            loadUnits = 'loadCourseUnits';
+        }
+        if (state.context.type === 'users') {
+            rangeType = 'user';
+            loadUnits = 'loadUserUnits';
+        }
+        if(!rangeType)  {
+            return false;
+        }
+        const copy = { data: { rangeId: state.context.id, rangeType: rangeType, modified: modified } };
+        await state.httpClient.post(`courseware-units/${unitId}/copy`, copy);
+
+        return dispatch(loadUnits, state.context.id);
+    },
+
+    async loadUsersCourses({ dispatch, rootGetters, state }, { userId, withCourseware }) {
+        const parent = {
+            type: 'users',
+            id: userId,
+        };
+        const relationship = 'course-memberships';
+        const options = {
+            include: 'course',
+        };
+        await dispatch('loadRelatedPaginated', {
+            type: 'course-memberships',
+            parent,
+            relationship,
+            options,
+        });
+
+        const memberships = rootGetters['course-memberships/related']({
+            parent,
+            relationship,
+        });
+
+        const otherMemberships = memberships.filter(({ attributes, relationships }) => {
+            return ['dozent', 'tutor'].includes(attributes.permission) && state.context.id !== relationships.course.data.id;
+        });
+
+        if (!withCourseware) {
+            return otherMemberships.map((membership) => {
+                return getCourse(membership);
+            });
+        }
+
+        const items = await Promise.all(
+            otherMemberships.map((membership) => {
+                const course = getCourse(membership);
+
+                return dispatch('loadRemoteCoursewareStructure', {
+                    rangeId: course.id,
+                    rangeType: course.type
+                }).then((instance) => ({ instance, membership, course }));
+            })
+        )
+
+        return items
+            .filter(({ instance, membership }) => {
+                return instance?.relationships?.root && (membership.attributes.permission === 'dozent' || instance.attributes['editing-permission-level'] === 'tutor');
+            })
+            .map(({ course }) => course);
+
+        function getCourse(membership) {
+            return rootGetters['courses/related']({ parent: membership, relationship: 'course' });
+        }
+    },
+
+    async loadRemoteCoursewareStructure({ dispatch, rootGetters }, { rangeId, rangeType }) {
+        const parent = {
+            id: rangeId,
+            type: rangeType,
+        };
+
+        const relationship = 'courseware';
+
+        return dispatch(`courseware-instances/loadRelated`, { parent, relationship }, { root: true }).then(
+            (response) => {
+                const instance = rootGetters['courseware-instances/related']({
+                    parent: parent,
+                    relationship: relationship,
+                });
+
+                return instance;
+            },
+            (error) => {
+                return null;
+            }
+        );
+    },
+    loadInstance({ commit, dispatch, rootGetters }, context) {
+        const parent = {
+                type: context.type,
+                id: context.id + '_' + context.unit
+        }
+
+        const relationship = 'courseware';
+        const options = {};
+
+        return dispatch(
+            `courseware-instances/loadRelated`,
+            {
+                parent,
+                relationship,
+                options,
+            },
+            { root: true }
+        ).then(() => {
+            return rootGetters['courseware-instances/related']({ parent, relationship });
+        });
+    },
+
+    loadStructuralElement({ dispatch }, structuralElementId) {
+        const options = {
+            include:
+                'children,containers,containers.edit-blocker,containers.blocks,containers.blocks.editor,containers.blocks.owner,containers.blocks.edit-blocker,editor,edit-blocker,owner',
+            'fields[users]': 'formatted-name',
+        };
+
+        return dispatch(
+            'courseware-structural-elements/loadById',
+            { id: structuralElementId, options },
+            { root: true }
+        );
+    },
+
+    loadContainer({ dispatch }, containerId) {
+        const options = {
+            include: 'blocks,blocks.edit-blocker','fields[users]': 'formatted-name',
+        };
+
+        return dispatch('courseware-containers/loadById', { id: containerId, options }, { root: true });
+    },
+
+    loadFileRefs({ dispatch, rootGetters }, block_id) {
+        const parent = {
+            type: 'courseware-blocks',
+            id: block_id,
+        };
+        const relationship = 'file-refs';
+
+        return dispatch('file-refs/loadRelated', { parent, relationship }, { root: true }).then(() =>
+            rootGetters['file-refs/related']({
+                parent,
+                relationship,
+            })
+        );
+    },
+
+    async createFile(context, { file, filedata, folder }) {
+        const termId = file?.relationships['terms-of-use']?.data?.id ?? null;
+        const formData = new FormData();
+        formData.append('file', filedata, file.attributes.name);
+        if (termId) {
+            formData.append('term-id', termId);
+        }
+        const url = `folders/${folder.id}/file-refs`;
+        let request = await state.httpClient.post(url, formData, {
+            headers: {
+                'Content-Type': 'multipart/form-data',
+            },
+        });
+        let response = null;
+        try {
+            response = await state.httpClient.get(request.headers.location);
+        }
+        catch(e) {
+            console.debug(e);
+            response = null;
+        }
+
+        return response ? response.data.data : response;
+    },
+
+    async createRootFolder({ dispatch, rootGetters }, { context, folder }) {
+        // get root folder for this context
+        await dispatch(
+            `${context.type}/loadRelated`,
+            {
+                parent: context,
+                relationship: 'folders',
+            },
+            { root: true }
+        );
+
+        let folders = await rootGetters[`${context.type}/related`]({
+            parent: context,
+            relationship: 'folders',
+        });
+
+        let rootFolder = null;
+
+        for (let i = 0; i < folders.length; i++) {
+            if (folders[i].attributes['folder-type'] === 'RootFolder') {
+                rootFolder = folders[i];
+            }
+        }
+
+        const newFolder = {
+            data: {
+                type: 'folders',
+                attributes: {
+                    name: folder.name,
+                    'folder-type': 'StandardFolder',
+                },
+                relationships: {
+                    parent: {
+                        data: {
+                            type: 'folders',
+                            id: rootFolder.id,
+                        },
+                    },
+                },
+            },
+        };
+
+        return state.httpClient.post(`${context.type}/${context.id}/folders`, newFolder).then((response) => {
+            return response.data.data;
+        });
+    },
+
+    async createFolder(store, { context, parent, folder }) {
+        const newFolder = {
+            data: {
+                type: 'folders',
+                attributes: {
+                    name: folder.name,
+                    'folder-type': folder.type,
+                },
+                relationships: {
+                    parent: parent,
+                },
+            },
+        };
+
+        return state.httpClient.post(`${context.type}/${context.id}/folders`, newFolder).then((response) => {
+            return response.data.data;
+        });
+    },
+
+    loadFolder({ dispatch }, folderId) {
+        const options = {};
+
+        return dispatch('folders/loadById', { id: folderId, options }, { root: true });
+    },
+
+    storeCoursewareSettings({ dispatch }, { instance }) {
+        return dispatch('courseware-instances/update', instance, { root: true });
+    },
+
+    async loadTeacherStatus({ dispatch, rootGetters, state, commit, getters }, userId) {
+        const user = rootGetters['users/byId']({ id: userId });
+
+        if (user.attributes.permission === 'root') {
+            commit('setUserIsTeacher', true);
+            return;
+        }
+        if (user.attributes.permission === 'admin') {
+            await dispatch('courses/loadById', { id: state.context.id });
+            const course = rootGetters['courses/byId']({id: state.context.id });
+            const instituteId = course.relationships.institute.data.id;
+
+            const parent = { type: 'users', id: `${userId}` };
+            const relationship = 'institute-memberships';
+            const options = {};
+            await dispatch('institute-memberships/loadRelated', { parent, relationship, options }, { root: true });
+            const instituteMemberships = rootGetters['institute-memberships/all'];
+            const instituteMembership = instituteMemberships.filter(membership => membership.relationships.institute.data.id === instituteId);
+
+            if (instituteMembership.length > 0 && instituteMembership[0].attributes.permission === 'admin') {
+                commit('setUserIsTeacher', true);
+                return;
+            }
+        }
+
+        const membershipId = `${state.context.id}_${userId}`;
+        try {
+            await dispatch('course-memberships/loadById', { id: membershipId });
+        } catch (error) {
+            console.error(`Could not find course membership for ${membershipId}.`);
+            commit('setUserIsTeacher', false);
+
+            return false;
+        }
+        const membership = rootGetters['course-memberships/byId']({ id: membershipId });
+        if (membership) {
+            const membershipPermission = membership.attributes.permission;
+            commit('setUserIsTeacher', membershipPermission === 'dozent' || membershipPermission === 'tutor');
+
+            return true;
+        } else {
+            console.error(`Could not find course membership for ${membershipId}.`);
+            commit('setUserIsTeacher', false);
+
+            return false;
+        }
+    },
+
+    uploadImageForStructuralElement({ dispatch, state }, { structuralElement, file }) {
+        const formData = new FormData();
+        formData.append('image', file);
+
+        const url = `courseware-structural-elements/${structuralElement.id}/image`;
+        return state.httpClient.post(url, formData, {
+            headers: {
+                'Content-Type': 'multipart/form-data',
+            },
+        });
+    },
+
+    setImportFilesState({ commit }, state) {
+        commit('setImportFilesState', state);
+    },
+    setImportFilesProgress({ commit }, percent) {
+        commit('setImportFilesProgress', percent);
+    },
+    setImportStructuresState({ commit }, state) {
+        commit('setImportStructuresState', state);
+    },
+    setImportStructuresProgress({ commit }, percent) {
+        commit('setImportStructuresProgress', percent);
+    },
+    setImportErrors({ commit }, errors) {
+        commit('setImportErrors', errors);
+    },
+
+    async createStructuralElement({ dispatch }, { attributes, parentId }) {
+        const data = {
+            attributes,
+            relationships: {
+                parent: {
+                    data: {
+                        type: 'courseware-structural-elements',
+                        id: parentId,
+                    },
+                },
+            },
+        };
+        await dispatch('courseware-structural-elements/create', data, { root: true });
+    },
+
+
+    async updateStructuralElement({ dispatch }, { element, id }) {
+        await dispatch('courseware-structural-elements/update', element, { root: true });
+
+        return dispatch('loadStructuralElement', id);
+    },
+
+    async createContainer({ dispatch }, { attributes, structuralElementId }) {
+        const data = {
+            attributes,
+            relationships: {
+                'structural-element': {
+                    data: {
+                        type: 'courseware-structural-elements',
+                        id: structuralElementId,
+                    },
+                },
+            },
+        };
+        await dispatch('courseware-containers/create', data, { root: true });
+
+        return dispatch('loadStructuralElement', structuralElementId);
+    },
+
+    async updateContainer({ dispatch }, { container, structuralElementId }) {
+        await dispatch('courseware-containers/update', container, { root: true });
+
+        return dispatch('loadStructuralElement', structuralElementId);
+    },
+
+    async createBlockInContainer({ dispatch }, { container, blockType }) {
+        const block = {
+            attributes: {
+                'block-type': blockType,
+                payload: null,
+            },
+            relationships: {
+                container: {
+                    data: { type: container.type, id: container.id },
+                },
+            },
+        };
+        await dispatch('courseware-blocks/create', block, { root: true });
+
+        return dispatch('loadContainer', container.id);
+    },
+
+    async updateBlockInContainer({ dispatch }, { attributes, blockId, containerId }) {
+        const container = {
+            type: 'courseware-containers',
+            id: containerId,
+        };
+        const block = {
+            type: 'courseware-blocks',
+            attributes: attributes,
+            id: blockId,
+            relationships: {
+                container: {
+                    data: { type: container.type, id: container.id },
+                },
+            },
+        };
+
+        await dispatch('courseware-blocks/update', block, { root: true });
+        await dispatch('unlockObject', { id: blockId, type: 'courseware-blocks' });
+
+        return dispatch('loadContainer', containerId);
+    },
+
+    lockObject({ dispatch, getters }, { id, type }) {
+        return dispatch(`${type}/setRelated`, {
+            parent: { id, type },
+            relationship: 'edit-blocker',
+            data: {
+                type: 'users',
+                id: getters.userId,
+            },
+        });
+    },
+
+    unlockObject({ dispatch }, { id, type }) {
+        return dispatch(`${type}/setRelated`, {
+            parent: { id, type },
+            relationship: 'edit-blocker',
+            data: null,
+        });
+    },
+};
+
+export const mutations = {
+    setContext(state, data){
+        state.context = data;
+    },
+    setHttpClient(state, data){
+        state.httpClient = data;
+    },
+    setShowCompanionOverlay(state, data) {
+        state.showCompanionOverlay = data;
+    },
+    setStyleCompanionOverlay(state, data) {
+        state.styleCompanionOverlay = data;
+    },
+    setMsgCompanionOverlay(state, data) {
+        state.msgCompanionOverlay = data;
+    },
+    setShowUnitAddDialog(state, data) {
+        state.showUnitAddDialog = data;
+    },
+    setShowUnitCopyDialog(state, data) {
+        state.showUnitCopyDialog = data;
+    },
+    setShowUnitImportDialog(state, data) {
+        state.showUnitImportDialog = data;
+    },
+    setShowUnitLinkDialog(state, data) {
+        state.showUnitLinkDialog = data;
+    },
+    setLicenses(state, data) {
+        state.licenses = data;
+    },
+    setUserId(state, data) {
+        state.userId = data;
+    },
+    setExportState(state, exportState) {
+        state.exportState = exportState;
+    },
+    setExportProgress(state, exportProgress) {
+        state.exportProgress = exportProgress;
+    },
+    setUserIsTeacher(state, isTeacher) {
+        state.teacherStatusLoaded = true;
+        state.userIsTeacher = isTeacher;
+    },
+    setUrlHelper(state, urlHelper) {
+        state.urlHelper = urlHelper;
+    },
+
+
+    setImportFilesState(state, importFilesState) {
+        state.importFilesState = importFilesState;
+    },
+
+    setImportFilesProgress(state, importFilesProgress) {
+        state.importFilesProgress = importFilesProgress;
+    },
+    setImportErrors(state, importErrors) {
+        state.importErrors = importErrors;
+    },
+
+    setImportStructuresState(state, importStructuresState) {
+        state.importStructuresState = importStructuresState;
+    },
+
+    setImportStructuresProgress(state, importStructuresProgress) {
+        state.importStructuresProgress = importStructuresProgress;
+    },
+};
+
+export default {
+    state,
+    actions,
+    mutations,
+    getters,
+};
diff --git a/resources/vue/store/courseware/courseware-tasks.module.js b/resources/vue/store/courseware/courseware-tasks.module.js
new file mode 100644
index 00000000000..fd5152dfa83
--- /dev/null
+++ b/resources/vue/store/courseware/courseware-tasks.module.js
@@ -0,0 +1,37 @@
+const getDefaultState = () => {
+    return {
+        showTasksDistributeDialog: false,
+    };
+};
+
+const initialState = getDefaultState();
+
+const getters = {
+    showTasksDistributeDialog(state) {
+        return state.showTasksDistributeDialog;
+    },
+};
+
+export const state = { ...initialState };
+
+export const actions = {
+    // setters
+    setShowTasksDistributeDialog({ commit }, context) {
+        commit('setShowTasksDistributeDialog', context);
+    },
+
+    // other actions
+};
+
+export const mutations = {
+    setShowTasksDistributeDialog(state, data){
+        state.showTasksDistributeDialog = data;
+    },
+};
+
+export default {
+    state,
+    actions,
+    mutations,
+    getters,
+};
diff --git a/resources/vue/store/courseware/courseware.module.js b/resources/vue/store/courseware/courseware.module.js
index b1f12f80914..71e6d38408d 100644
--- a/resources/vue/store/courseware/courseware.module.js
+++ b/resources/vue/store/courseware/courseware.module.js
@@ -30,12 +30,15 @@ const getDefaultState = () => {
 
         showStructuralElementEditDialog: false,
         showStructuralElementAddDialog: false,
+        showStructuralElementImportDialog: false,
+        showStructuralElementCopyDialog: false,
+        showStructuralElementLinkDialog: false,
         showStructuralElementExportDialog: false,
         showStructuralElementPdfExportDialog: false,
         showStructuralElementInfoDialog: false,
         showStructuralElementDeleteDialog: false,
         showStructuralElementOerDialog: false,
-        showStructuralElementLinkDialog: false,
+        showStructuralElementPublicLinkDialog: false,
         showStructuralElementRemoveLockDialog: false,
 
         showSuggestOerDialog: false,
@@ -83,7 +86,8 @@ const getters = {
         return rootGetters['courseware-structural-elements/byId']({ id });
     },
     currentElementBlocked(state, getters, rootState, rootGetters) {
-        return getters.currentStructuralElement?.relationships?.['edit-blocker']?.data !== null;
+        const elemData = getters.currentStructuralElement?.relationships?.['edit-blocker']?.data;
+        return elemData !== null && elemData !== '';
     },
     currentElementBlockerId(state, getters) {
         return getters.currentElementBlocked ? getters.currentStructuralElement?.relationships?.['edit-blocker']?.data?.id : null;
@@ -172,6 +176,15 @@ const getters = {
     showStructuralElementAddDialog(state) {
         return state.showStructuralElementAddDialog;
     },
+    showStructuralElementCopyDialog(state) {
+        return state.showStructuralElementCopyDialog;
+    },
+    showStructuralElementLinkDialog(state) {
+        return state.showStructuralElementLinkDialog;
+    },
+    showStructuralElementImportDialog(state) {
+        return state.showStructuralElementImportDialog;
+    },
     showStructuralElementExportDialog(state) {
         return state.showStructuralElementExportDialog;
     },
@@ -187,8 +200,8 @@ const getters = {
     showStructuralElementDeleteDialog(state) {
         return state.showStructuralElementDeleteDialog;
     },
-    showStructuralElementLinkDialog(state) {
-        return state.showStructuralElementLinkDialog;
+    showStructuralElementPublicLinkDialog(state) {
+        return state.showStructuralElementPublicLinkDialog;
     },
     showStructuralElementRemoveLockDialog(state) {
         return state.showStructuralElementRemoveLockDialog;
@@ -301,16 +314,7 @@ export const actions = {
 
         const activities = rootGetters['users/all'];
 
-        const parentFetchers = activities
-              .filter(({ type }) => type === 'activities')
-              .map((activity) => this.dispatch(
-                  'courseware-structural-elements/loadById',
-                  {
-                      id: activity.relationships.object.meta['object-id'],
-                  },
-              ));
-
-        return Promise.all(parentFetchers).then(() => activities);
+        return activities.filter(({ type }) => type === 'activities');
     },
 
     async createFile(context, { file, filedata, folder }) {
@@ -434,8 +438,8 @@ export const actions = {
             // console.log(resp);
         });
     },
-    async copyStructuralElement({ dispatch, getters, rootGetters }, { parentId, elementId, removePurpose, migrate }) {
-        const copy = { data: { parent_id: parentId, remove_purpose: removePurpose, migrate: migrate } };
+    async copyStructuralElement({ dispatch, getters, rootGetters }, { parentId, elementId, removePurpose, migrate, modifications }) {
+        const copy = { data: { parent_id: parentId, remove_purpose: removePurpose, migrate: migrate, modifications: modifications } };
 
         const result = await state.httpClient.post(`courseware-structural-elements/${elementId}/copy`, copy);
         const id = result.data.data.id;
@@ -836,6 +840,18 @@ export const actions = {
         context.commit('setShowStructuralElementAddDialog', bool);
     },
 
+    showElementImportDialog(context, bool) {
+        context.commit('setShowStructuralElementImportDialog', bool);
+    },
+
+    showElementCopyDialog(context, bool) {
+        context.commit('setShowStructuralElementCopyDialog', bool);
+    },
+
+    showElementLinkDialog(context, bool) {
+        context.commit('setShowStructuralElementLinkDialog', bool);
+    },
+
     showElementExportDialog(context, bool) {
         context.commit('setShowStructuralElementExportDialog', bool);
     },
@@ -860,8 +876,8 @@ export const actions = {
         context.commit('setShowStructuralElementDeleteDialog', bool);
     },
 
-    showElementLinkDialog(context, bool) {
-        context.commit('setShowStructuralElementLinkDialog', bool);
+    showElementPublicLinkDialog(context, bool) {
+        context.commit('setShowStructuralElementPublicLinkDialog', bool);
     },
 
     showElementRemoveLockDialog(context, bool) {
@@ -1294,7 +1310,32 @@ export const actions = {
         await dispatch('courseware-public-links/update', link, { root: true });
 
         return dispatch('courseware-public-links/loadById', { id: link.id }, { root: true });
-    }
+    },
+
+    loadCourseUnits({ dispatch }, cid) {
+        const parent = { type: 'courses', id: cid };
+        const relationship = 'courseware-units';
+        const options = { include: 'structural-element' }
+
+        return dispatch('loadRelatedPaginated', {
+            type: 'courseware-units',
+            parent,
+            relationship,
+            options,
+        });
+    },
+    loadUserUnits({ dispatch }, uid) {
+        const parent = { type: 'users', id: uid };
+        const relationship = 'courseware-units';
+        const options = { include: 'structural-element' }
+
+        return dispatch('loadRelatedPaginated', {
+            type: 'courseware-units',
+            parent,
+            relationship,
+            options,
+        });
+    },
 };
 
 /* eslint no-param-reassign: ["error", { "props": false }] */
@@ -1393,6 +1434,18 @@ export const mutations = {
         state.showStructuralElementAddDialog = showAdd;
     },
 
+    setShowStructuralElementImportDialog(state, showImport) {
+        state.showStructuralElementImportDialog = showImport;
+    },
+
+    setShowStructuralElementCopyDialog(state, showCopy) {
+        state.showStructuralElementCopyDialog = showCopy;
+    },
+
+    setShowStructuralElementLinkDialog(state, showLink) {
+        state.showStructuralElementLinkDialog = showLink;
+    },
+
     setShowStructuralElementExportDialog(state, showExport) {
         state.showStructuralElementExportDialog = showExport;
     },
@@ -1421,8 +1474,8 @@ export const mutations = {
         state.showOverviewElementAddDialog = showAdd;
     },
 
-    setShowStructuralElementLinkDialog(state, showLink) {
-        state.showStructuralElementLinkDialog = showLink;
+    setShowStructuralElementPublicLinkDialog(state, showPublicLink) {
+        state.showStructuralElementPublicLinkDialog = showPublicLink;
     },
 
     setShowStructuralElementRemoveLockDialog(state, showRemoveLock) {
diff --git a/resources/vue/store/courseware/structure.module.js b/resources/vue/store/courseware/structure.module.js
index 8f2a30f0b16..0eba83936a1 100644
--- a/resources/vue/store/courseware/structure.module.js
+++ b/resources/vue/store/courseware/structure.module.js
@@ -125,7 +125,11 @@ const actions = {
     },
 
     loadInstance({ commit, dispatch, rootGetters }, context) {
-        const parent = context;
+        let parent = context;
+        parent = {
+            type: context.type,
+            id: context.id + '_' + context.unit
+        }
         const relationship = 'courseware';
         const options = {
             include: 'bookmarks,root',
-- 
GitLab