From f2767008ffcf723bc73a4ea8781b31f23c2b68e5 Mon Sep 17 00:00:00 2001 From: Ron Lucke Date: Mon, 11 Dec 2023 10:37:55 +0000 Subject: TIC #3111 Closes #3111 Merge request studip/studip!2295 --- app/controllers/courseware_controller.php | 4 +- .../Courseware/CoursewareInstancesUpdate.php | 13 ++ .../JsonApi/Routes/Courseware/UnitsCreate.php | 6 + .../JsonApi/Schemas/Courseware/Instance.php | 1 + lib/models/Courseware/Instance.php | 39 ++++ lib/models/Courseware/Unit.php | 29 +++ .../stylesheets/scss/courseware/layouts/tile.scss | 7 +- .../assets/stylesheets/scss/courseware/shelf.scss | 11 + .../scss/courseware/structural-element.scss | 4 +- resources/vue/components/StudipIdentImage.vue | 247 +++++++++++++++++++++ .../blocks/CoursewareTableOfContentsBlock.vue | 115 +++------- .../courseware/layouts/CoursewareTile.vue | 23 +- .../structural-element/CoursewareRootContent.vue | 246 ++++++++++++++++++++ .../CoursewareStructuralElement.vue | 89 ++++++-- .../structural-element/CoursewareToolsContents.vue | 41 +++- .../structural-element/CoursewareTreeItem.vue | 8 +- .../courseware/unit/CoursewareShelfDialogAdd.vue | 15 +- .../courseware/unit/CoursewareUnitItem.vue | 2 +- .../unit/CoursewareUnitItemDialogLayout.vue | 174 +++++++++------ .../unit/CoursewareUnitItemDialogSettings.vue | 4 +- .../courseware/widgets/CoursewareViewWidget.vue | 4 + .../vue/store/courseware/courseware.module.js | 6 + 22 files changed, 897 insertions(+), 191 deletions(-) create mode 100644 resources/vue/components/StudipIdentImage.vue create mode 100644 resources/vue/components/courseware/structural-element/CoursewareRootContent.vue diff --git a/app/controllers/courseware_controller.php b/app/controllers/courseware_controller.php index ddee584..91fc9c8 100644 --- a/app/controllers/courseware_controller.php +++ b/app/controllers/courseware_controller.php @@ -35,10 +35,10 @@ abstract class CoursewareController extends AuthenticatedController if ($last_element) { $last_element_unit = $last_element->findUnit(); } - if (isset($last_element_unit) && $last_element_unit->id === $unit->id) { + if (isset($last_element_unit) && $last_element_unit->id === $unit->id && $last_element_unit->hasRootLayout()) { $this->entry_element_id = $last_element->id; } else { - $this->entry_element_id = $unit->structural_element_id; + $this->entry_element_id = $unit->findOrCreateFirstElement()->id; } if ($this->entry_element_id) { $last_element_item = $context === 'user' ? 'global' : $rangeId; diff --git a/lib/classes/JsonApi/Routes/Courseware/CoursewareInstancesUpdate.php b/lib/classes/JsonApi/Routes/Courseware/CoursewareInstancesUpdate.php index c5ef250..8bb0196 100644 --- a/lib/classes/JsonApi/Routes/Courseware/CoursewareInstancesUpdate.php +++ b/lib/classes/JsonApi/Routes/Courseware/CoursewareInstancesUpdate.php @@ -74,6 +74,16 @@ class CoursewareInstancesUpdate extends JsonApiController } } + if (self::arrayHas($json, 'data.attributes.root-layout')) { + $rootLayout = self::arrayGet($json, 'data.attributes.root-layout'); + if (!is_string($rootLayout)) { + return 'Attribute `root-layout` must be a string.'; + } + if (!$data->isValidRootLayout($rootLayout)) { + return 'Attribute `root-layout` contains an invalid value.'; + } + } + if (self::arrayHas($json, 'data.attributes.editing-permission-level')) { $editingPermissionLevel = self::arrayGet($json, 'data.attributes.editing-permission-level'); if (!is_string($editingPermissionLevel)) { @@ -118,6 +128,9 @@ class CoursewareInstancesUpdate extends JsonApiController $favorites = $get('data.attributes.favorite-block-types'); $instance->setFavoriteBlockTypes($user, $favorites); + $rootLayout = $get('data.attributes.root-layout'); + $instance->setRootLayout($rootLayout); + $sequentialProgression = $get('data.attributes.sequential-progression'); $instance->setSequentialProgression($sequentialProgression); diff --git a/lib/classes/JsonApi/Routes/Courseware/UnitsCreate.php b/lib/classes/JsonApi/Routes/Courseware/UnitsCreate.php index 6f88bca..f55fd6a 100644 --- a/lib/classes/JsonApi/Routes/Courseware/UnitsCreate.php +++ b/lib/classes/JsonApi/Routes/Courseware/UnitsCreate.php @@ -111,6 +111,12 @@ class UnitsCreate extends JsonApiController 'withdraw_date' => self::arrayGet($json, 'data.attributes.withdraw-date'), ]); + $instance = new \Courseware\Instance($struct); + + $instance->setRootLayout(self::arrayGet($json, 'data.attributes.settings.root-layout')); + + $instance->getUnit()->store(); + return $unit; } diff --git a/lib/classes/JsonApi/Schemas/Courseware/Instance.php b/lib/classes/JsonApi/Schemas/Courseware/Instance.php index d943759..7df0cf6 100644 --- a/lib/classes/JsonApi/Schemas/Courseware/Instance.php +++ b/lib/classes/JsonApi/Schemas/Courseware/Instance.php @@ -37,6 +37,7 @@ 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), + 'root-layout' => $resource->getRootLayout(), 'sequential-progression' => $resource->getSequentialProgression(), 'editing-permission-level' => $resource->getEditingPermissionLevel(), 'certificate-settings' => $resource->getCertificateSettings(), diff --git a/lib/models/Courseware/Instance.php b/lib/models/Courseware/Instance.php index b2308b9..1084ed5 100644 --- a/lib/models/Courseware/Instance.php +++ b/lib/models/Courseware/Instance.php @@ -175,6 +175,45 @@ class Instance } /** + * Returns which layout is set for root node of this coursware instance + * + * @return string name of the layout + */ + public function getRootLayout(): string + { + $rootLayout = $this->unit->config['root_layout']; + if ($rootLayout) { + $this->validateRootLayout($rootLayout); + return $rootLayout; + } + + return 'default'; + } + + /** + * Sets layout of the root node page of this courseware + * + * @param string name of the layout + */ + public function setRootLayout(string $rootLayout): void + { + $this->validateRootLayout($rootLayout); + $this->unit->config['root_layout'] = $rootLayout; + } + + public function isValidRootLayout(string $rootLayout): bool + { + return in_array($rootLayout, ['default', 'toc', 'classic', 'none']); + } + + private function validateRootLayout(string $rootLayout): void + { + if (!$this->isValidRootLayout($rootLayout)) { + throw new \InvalidArgumentException('Invalid root layout for courseware.'); + } + } + + /** * Returns whether this courseware instance uses a sequential progression through the structural elements. * * @return bool true if this courseware instance uses a sequential progression, false otherwise diff --git a/lib/models/Courseware/Unit.php b/lib/models/Courseware/Unit.php index 6f3535d..bf08328 100644 --- a/lib/models/Courseware/Unit.php +++ b/lib/models/Courseware/Unit.php @@ -172,4 +172,33 @@ class Unit extends \SimpleORMap implements \PrivacyObject $stmt = $db->prepare($query); $stmt->execute($args); } + + public function hasRootLayout() + { + return !isset($this->config['root_layout']) || $this->config['root_layout'] !== 'none'; + } + + public function findOrCreateFirstElement(): StructuralElement + { + if ($this->hasRootLayout()) { + return $this->structural_element; + } + + $children = $this->structural_element->children; + if (count($children) > 0) { + return $children[0]; + } + + $struct = StructuralElement::create([ + 'parent_id' => $this->structural_element->id, + 'range_id' => $this->range_id, + 'range_type' => $this->range_type, + 'owner_id' => $this->structural_element->owner_id, + 'editor_id' => $this->structural_element->editor_id, + 'title' => _('neue Seite'), + ]); + + + return $struct; + } } diff --git a/resources/assets/stylesheets/scss/courseware/layouts/tile.scss b/resources/assets/stylesheets/scss/courseware/layouts/tile.scss index c9cfd15..4fd93bd 100644 --- a/resources/assets/stylesheets/scss/courseware/layouts/tile.scss +++ b/resources/assets/stylesheets/scss/courseware/layouts/tile.scss @@ -3,8 +3,8 @@ display: flex; flex-wrap: wrap; padding-left: 0; - row-gap: 5px; - column-gap: 5px; + gap: 5px; + overflow: hidden; } .cw-tiles .tile, .cw-tile { @@ -29,9 +29,6 @@ background-repeat: no-repeat; background-color: var(--content-color-20); background-position: center; - &.default-image { - @include background-icon(courseware, clickable, 128); - } .overlay-handle { @extend .drag-handle; diff --git a/resources/assets/stylesheets/scss/courseware/shelf.scss b/resources/assets/stylesheets/scss/courseware/shelf.scss index 0b6c93b..fb339b5 100644 --- a/resources/assets/stylesheets/scss/courseware/shelf.scss +++ b/resources/assets/stylesheets/scss/courseware/shelf.scss @@ -62,3 +62,14 @@ height: 416px; } } + +.cw-unit-item-dialog-layout-content { + display: flex; + .cw-unit-item-dialog-layout-content-image { + padding-right: 2em; + } + .cw-unit-item-dialog-layout-content-settings { + width: 100%; + max-width: 500px; + } +} diff --git a/resources/assets/stylesheets/scss/courseware/structural-element.scss b/resources/assets/stylesheets/scss/courseware/structural-element.scss index 917684d..ed1ba38 100644 --- a/resources/assets/stylesheets/scss/courseware/structural-element.scss +++ b/resources/assets/stylesheets/scss/courseware/structural-element.scss @@ -157,7 +157,7 @@ .cw-structural-element-image-preview { display: block; max-height: 200px; - max-width: 400px; + max-width: 300px; width: auto; height: auto; margin: 0 auto; @@ -165,7 +165,7 @@ } .cw-structural-element-image-preview-placeholder { - width: 356px; + width: 300px; height: 200px; margin: 0 auto; background-color: var(--content-color-20); diff --git a/resources/vue/components/StudipIdentImage.vue b/resources/vue/components/StudipIdentImage.vue new file mode 100644 index 0000000..80c1fa6 --- /dev/null +++ b/resources/vue/components/StudipIdentImage.vue @@ -0,0 +1,247 @@ + + + + \ No newline at end of file diff --git a/resources/vue/components/courseware/blocks/CoursewareTableOfContentsBlock.vue b/resources/vue/components/courseware/blocks/CoursewareTableOfContentsBlock.vue index ef111bf..dd619dd 100644 --- a/resources/vue/components/courseware/blocks/CoursewareTableOfContentsBlock.vue +++ b/resources/vue/components/courseware/blocks/CoursewareTableOfContentsBlock.vue @@ -1,29 +1,17 @@